diff --git a/coverage.txt b/coverage.txt index 11ed75c5..3a7dc270 100644 --- a/coverage.txt +++ b/coverage.txt @@ -1,1715 +1,29 @@ mode: atomic -mode: atomic -mode: atomic -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:341.36,343.16 2 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:343.16,344.13 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:347.2,462.3 1 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:479.157,481.16 2 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:481.16,483.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:485.2,486.16 2 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:486.16,488.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:491.2,491.27 1 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:491.27,493.3 1 2 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:495.2,507.23 2 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:510.67,512.24 2 8 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:512.24,514.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:515.2,516.32 2 8 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:519.43,521.67 2 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:521.67,523.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:524.2,525.24 2 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:528.64,532.16 3 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:532.16,534.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:536.2,536.20 1 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:539.72,543.2 2 4 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:545.78,548.45 2 6 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:548.45,549.25 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:549.25,551.18 2 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:551.18,553.5 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:554.4,554.16 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:555.9,557.4 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:560.2,561.16 2 6 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:561.16,563.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:565.2,565.54 1 6 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:565.54,567.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:569.2,569.18 1 6 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:576.83,578.16 2 2 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:578.16,580.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:582.2,582.49 1 2 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:582.49,584.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:586.2,587.16 2 2 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:587.16,589.3 1 0 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:591.2,591.49 1 2 -github.com/jesseduffield/lazydocker/pkg/config/app_config.go:595.45,597.2 1 4 -github.com/jesseduffield/lazydocker/pkg/config/config_default_platform.go:7.42,12.2 1 4 -mode: atomic -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:29.50,31.54 2 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:31.54,33.3 1 2 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:34.2,35.31 2 1 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:35.31,37.3 1 1 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:38.2,38.14 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:42.50,44.33 2 8 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:44.33,46.3 1 1 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:47.2,47.61 1 7 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:52.71,54.37 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:54.37,56.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:57.2,58.41 2 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:67.43,68.46 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:68.46,70.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:71.2,73.36 3 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:73.36,78.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:79.2,79.38 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:79.38,84.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:85.2,85.38 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:85.38,90.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:91.2,91.38 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:91.38,96.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:97.2,97.30 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:102.79,105.2 2 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:109.66,111.2 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:114.44,118.2 3 4 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:121.22,127.2 5 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:130.79,131.36 1 7 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:131.36,133.3 1 5 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:134.2,134.12 1 7 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:138.24,139.11 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:139.11,141.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:142.2,142.10 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:146.59,147.28 1 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:147.28,149.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:150.2,150.41 1 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:150.41,152.3 1 1 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:154.2,157.54 3 2 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:161.36,164.2 2 16 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:166.50,167.31 1 5 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:167.31,169.3 1 2 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:170.2,171.27 2 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:171.27,172.40 1 4 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:172.40,174.43 2 8 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:174.43,176.5 1 4 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:179.2,179.18 1 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:182.81,184.43 2 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:184.43,185.28 1 6 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:185.28,186.12 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:188.3,188.38 1 6 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:188.38,190.4 1 6 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:191.3,191.57 1 6 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:193.2,193.29 1 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:198.57,199.39 1 5 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:199.39,200.43 1 10 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:200.43,202.4 1 2 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:204.2,204.13 1 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:207.38,210.29 3 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:210.29,211.26 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:211.26,213.4 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:213.9,215.22 2 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:215.22,217.5 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:218.4,218.14 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:221.2,221.16 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:224.39,227.29 3 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:227.29,228.26 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:228.26,230.4 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:230.9,232.22 2 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:232.22,234.5 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:235.4,235.14 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:238.2,238.16 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:241.59,245.2 3 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:248.52,264.13 3 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:264.13,266.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:267.2,267.27 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:271.52,286.13 3 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:286.13,288.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:289.2,289.22 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:293.38,295.29 2 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:295.29,297.22 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:297.22,299.4 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:301.2,301.33 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:305.65,307.2 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:310.57,311.17 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:311.17,313.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:315.2,318.21 3 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:318.21,320.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:321.2,322.27 2 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:322.27,324.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:326.2,326.15 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:331.34,334.24 3 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:334.24,336.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:337.2,337.19 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:340.43,342.28 2 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:342.28,344.17 2 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:344.17,346.4 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:348.2,348.19 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:348.19,350.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:351.2,351.12 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:354.49,355.22 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:355.22,357.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:357.8,359.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:362.37,363.32 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:363.32,365.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:367.2,367.17 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:367.17,369.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:371.2,371.29 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:371.29,372.15 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:373.117,374.12 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:375.11,376.16 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:380.2,380.13 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:384.40,386.2 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:390.56,392.2 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:394.73,397.16 2 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:397.16,399.3 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:400.2,400.16 1 3 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:401.14,402.23 1 1 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:403.14,406.63 2 1 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:406.63,408.4 1 0 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:409.3,409.34 1 1 -github.com/jesseduffield/lazydocker/pkg/utils/utils.go:410.10,411.86 1 1 -mode: atomic -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:20.51,22.36 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:22.36,23.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:23.26,25.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:27.2,27.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:30.55,38.2 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:40.50,41.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:41.26,43.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:44.2,45.39 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:45.39,47.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:48.2,48.23 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:52.70,53.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:53.12,56.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:56.16,58.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:60.3,60.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:60.13,63.23 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:63.23,65.24 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:65.24,67.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:68.5,68.75 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:68.75,70.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:74.3,74.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:74.29,75.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:75.42,77.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/app_status_manager.go:81.2,81.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:16.110,20.52 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:20.52,22.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:24.2,28.18 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:28.18,30.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:32.2,34.21 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:34.21,36.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:38.2,64.60 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:67.51,76.68 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:76.68,79.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:79.8,80.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:80.42,82.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:82.9,82.49 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:82.49,84.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:87.2,87.45 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:90.95,93.24 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:93.24,100.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:102.2,102.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:102.29,113.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:115.2,129.15 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:132.42,133.100 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:133.100,135.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:137.2,137.83 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:137.83,139.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:142.75,146.80 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:146.80,147.55 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:147.55,148.31 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:148.31,153.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:153.10,158.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:161.3,161.76 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:161.76,163.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:165.8,165.25 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:165.25,167.66 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:167.66,168.59 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:168.59,173.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:175.4,175.21 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:178.3,183.76 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:183.76,185.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:186.8,188.19 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:188.19,190.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:192.3,192.62 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:192.62,193.31 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:193.31,198.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:198.10,203.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:206.3,206.76 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/arrangement.go:206.76,208.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:16.127,17.49 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:17.49,18.55 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:18.55,20.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:22.3,22.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:22.22,23.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:23.41,25.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:28.3,28.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:32.49,33.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:33.42,35.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:36.2,38.12 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:41.76,45.10 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:45.10,46.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:46.30,48.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:49.8,51.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:52.2,52.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:55.95,63.2 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:65.106,68.16 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:68.16,70.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:71.2,72.54 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:75.86,79.16 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:79.16,81.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:82.2,83.15 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:83.15,85.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:86.2,88.40 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:88.40,90.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:91.2,91.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:94.35,97.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:102.133,104.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:106.142,108.40 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:108.40,109.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:109.46,110.56 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:110.56,112.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:114.3,115.17 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:115.17,117.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:118.3,119.69 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:119.69,121.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:122.3,122.59 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:124.2,124.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:127.116,129.135 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:129.135,131.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:132.2,132.124 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:132.124,134.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:136.2,136.131 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:136.131,138.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:139.2,139.122 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:139.122,141.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:143.2,143.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:146.56,150.2 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/confirmation_panel.go:152.51,158.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:19.89,21.64 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:21.64,23.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:26.41,26.64 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:32.127,34.15 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:34.15,36.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:38.2,40.73 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:40.73,42.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:46.2,48.6 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:48.6,49.10 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:50.21,51.10 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:52.19,54.18 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:54.18,58.5 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:59.4,59.28 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:59.28,61.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:66.67,71.12 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:71.12,75.3 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:77.2,77.40 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:77.40,80.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:82.2,82.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:82.15,83.40 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:83.40,85.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:88.2,88.74 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:88.74,91.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:93.2,93.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:96.34,97.50 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:97.50,101.41 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:101.41,103.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:107.112,116.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:116.16,118.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:120.2,120.63 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:120.63,122.17 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:122.17,124.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:125.8,127.17 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:127.17,129.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/container_logs.go:132.2,132.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:22.81,24.68 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:24.68,25.54 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:25.54,27.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:29.3,29.98 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:29.98,31.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:34.2,36.62 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:36.62,64.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:65.71,72.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:82.65,84.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:85.52,89.89 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:89.89,91.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:93.4,93.80 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:93.80,95.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:97.4,97.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:109.89,110.16 1 7 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:110.16,112.3 1 3 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:114.2,116.29 3 4 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:116.29,118.3 1 1 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:120.2,120.80 1 3 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:123.82,124.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:124.53,124.91 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:127.68,128.32 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:128.32,130.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:132.2,132.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:132.44,134.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:136.2,136.90 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:136.90,140.24 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:140.24,142.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:143.3,146.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:149.2,150.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:150.16,153.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:155.2,155.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:158.85,159.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:159.53,159.97 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:162.74,163.32 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:163.32,165.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:167.2,177.39 10 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:177.39,179.50 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:179.50,180.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:180.30,182.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:182.10,184.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:186.8,188.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:190.2,191.54 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:191.54,193.61 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:193.61,194.27 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:194.27,196.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:198.8,200.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:202.2,203.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:203.16,205.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:207.2,209.15 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:212.84,214.64 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:214.64,216.18 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:216.18,218.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:220.4,220.36 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:223.41,223.64 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:229.82,231.64 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:231.64,233.18 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:233.18,235.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:237.4,237.36 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:240.41,240.64 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:246.54,247.33 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:247.33,250.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:253.2,260.16 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:260.16,262.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:264.2,268.23 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:268.23,269.63 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:269.63,270.40 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:270.40,271.37 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:271.37,272.11 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:274.5,275.34 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:280.2,280.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:283.53,284.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:284.46,285.60 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:285.60,287.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:290.2,290.61 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:290.61,292.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:294.2,294.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:297.80,301.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:303.79,305.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:305.16,307.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:309.2,309.82 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:309.82,310.68 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:310.68,311.58 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:311.58,312.63 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:312.63,313.132 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:313.132,314.72 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:314.72,317.8 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:320.5,320.45 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:322.4,322.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:326.2,329.31 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:329.31,329.95 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:333.31,333.114 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:337.2,340.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:343.69,344.72 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:344.72,345.37 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:345.37,347.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:347.9,349.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:351.3,351.17 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:351.17,353.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:355.3,355.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:359.73,361.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:361.16,363.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:365.2,365.38 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:368.72,370.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:370.16,372.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:374.2,374.115 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:374.115,375.68 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:375.68,376.43 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:376.43,378.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:380.4,380.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:385.75,387.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:387.16,389.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:391.2,391.69 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:391.69,392.45 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:392.45,394.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:396.3,396.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:400.74,402.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:402.16,404.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:406.2,407.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:407.16,409.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:411.2,411.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:414.47,415.124 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:415.124,416.67 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:416.67,418.18 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:418.18,420.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:421.4,421.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:426.76,428.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:428.16,430.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:432.2,434.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:437.78,439.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:439.16,441.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:443.2,443.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:446.73,456.2 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:458.82,460.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:460.16,462.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:464.2,470.67 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:473.46,474.123 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:474.123,475.68 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:475.68,476.71 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:476.71,477.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:477.44,479.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:482.4,482.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:487.48,488.125 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:488.125,489.68 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:489.68,490.71 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:490.71,491.93 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:491.93,493.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:496.4,496.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:501.80,521.2 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:524.89,526.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:526.16,528.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:530.2,530.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:533.77,535.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:535.41,537.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:539.2,540.19 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:540.19,542.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:543.2,544.21 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:544.21,546.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/containers_panel.go:547.2,548.37 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:12.154,13.96 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:13.96,16.27 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:16.27,17.39 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:17.39,19.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:21.4,22.21 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:22.21,24.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:27.4,27.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:27.22,29.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:31.4,31.61 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:31.61,32.69 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:32.69,34.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:35.5,35.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:39.3,45.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:48.2,51.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:54.124,56.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/custom_commands.go:58.122,60.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:9.42,11.9 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:11.9,13.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:15.2,15.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:15.30,17.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:19.2,22.42 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:25.55,29.2 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:31.169,32.78 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:32.78,34.14 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:34.14,35.73 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:35.73,37.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:39.3,39.17 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:43.44,44.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:44.42,46.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:48.2,48.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:51.37,58.18 6 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:58.18,60.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:62.2,64.29 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:68.38,69.35 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:69.35,70.43 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:70.43,72.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:75.2,75.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/filtering.go:78.39,80.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:8.53,9.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:9.14,11.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:13.2,14.8 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:14.8,16.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:18.2,18.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:19.22,20.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:21.14,23.13 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:24.16,25.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:26.10,27.59 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:32.56,37.2 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:39.59,43.64 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:43.64,45.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:47.2,49.49 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:49.49,51.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:53.2,55.106 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:55.106,56.43 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:56.43,58.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:62.2,62.40 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:62.40,64.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:66.2,66.36 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:69.37,73.35 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:73.35,75.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:77.2,79.16 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:79.16,81.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:83.2,83.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:86.55,90.89 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:90.89,92.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:96.39,99.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:99.22,100.90 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:100.90,102.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:106.2,106.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:106.44,108.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:111.2,111.89 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:111.89,113.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:115.2,115.57 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:119.48,123.53 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:123.53,124.65 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:124.65,126.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:129.2,129.39 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:132.46,137.39 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:137.39,140.49 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:140.49,142.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/focus.go:145.2,145.39 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gocui.go:25.52,26.32 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gocui.go:26.32,29.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gocui.go:31.2,32.13 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gocui.go:32.13,34.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gocui.go:35.2,35.27 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gocui.go:39.51,41.27 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gocui.go:41.27,43.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gocui.go:44.2,44.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:118.189,147.2 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:149.45,157.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:159.72,161.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:161.12,164.22 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:164.22,165.34 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:165.34,167.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:169.4,169.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:175.29,180.16 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:180.16,182.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:183.2,186.50 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:186.50,188.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:190.2,194.65 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:194.65,196.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:198.2,198.45 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:198.45,200.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:202.2,205.12 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:205.12,206.34 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:206.34,207.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:207.18,208.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:210.4,210.58 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:210.58,213.13 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:215.4,215.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:219.2,221.45 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:221.45,223.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:224.2,224.52 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:224.52,226.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:229.2,231.42 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:231.42,233.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:235.2,235.32 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:235.32,238.17 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:238.17,240.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:242.3,242.47 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:242.47,244.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:247.2,253.12 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:253.12,261.3 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:263.2,264.26 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:264.26,266.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:267.2,267.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:270.29,280.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:282.48,284.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:286.27,287.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:287.12,288.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:288.46,290.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:292.2,292.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:292.12,293.60 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:293.60,295.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:297.2,297.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:297.12,298.45 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:298.45,300.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:302.2,302.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:302.12,303.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:303.46,305.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:307.2,307.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:307.12,308.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:308.44,310.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:314.70,317.29 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:317.29,318.17 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:318.17,320.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:321.3,322.30 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:325.1,326.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:326.6,329.21 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:329.21,330.11 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:331.22,332.11 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:333.26,335.19 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:336.12,345.19 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:349.3,349.7 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:349.7,350.11 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:351.22,352.11 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:353.34,359.62 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:360.26,362.19 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:369.47,371.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:373.38,375.21 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:375.21,377.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:378.2,378.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:378.26,379.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:379.41,381.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:383.2,383.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:386.57,387.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:387.41,388.102 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:388.102,390.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:392.2,392.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:397.32,398.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:398.29,400.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:402.2,402.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:405.65,406.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:406.18,408.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:410.2,411.29 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:411.29,413.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:414.2,414.76 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:417.49,419.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:419.16,421.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:423.2,423.31 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:426.49,427.57 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:427.57,429.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:430.2,430.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:433.72,434.98 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:434.98,437.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:440.48,441.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:441.44,443.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:445.2,446.13 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:449.51,450.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:450.46,452.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:453.2,453.21 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:456.42,458.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:460.40,461.38 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:461.38,461.52 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:464.60,469.6 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:469.6,470.10 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:471.21,472.10 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:473.19,474.71 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:474.71,475.35 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:475.35,477.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:486.32,488.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:488.16,489.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:491.2,493.45 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:493.45,494.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/gui.go:497.2,497.17 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:21.73,26.58 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:26.58,34.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:35.63,37.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:45.57,46.50 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:46.50,48.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:50.4,50.50 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:50.50,52.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:54.4,54.24 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:54.24,56.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:58.4,58.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:58.22,60.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:62.4,62.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:68.77,70.32 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:70.32,70.68 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:76.62,86.16 9 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:86.16,88.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:90.2,92.15 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:95.38,96.49 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:96.49,98.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:100.2,100.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:103.44,105.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:105.16,107.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:109.2,111.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:114.55,115.79 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:115.79,117.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:119.2,119.32 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:122.75,130.16 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:130.16,132.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:134.2,160.86 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:160.86,166.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:166.26,167.62 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:167.62,169.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:171.5,171.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:176.2,179.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:182.43,183.120 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:183.120,184.67 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:184.67,186.18 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:186.18,188.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:189.4,189.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:194.78,196.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:196.16,198.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:200.2,206.67 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/images_panel.go:209.76,221.2 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:21.35,24.22 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:25.12,26.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:27.17,28.31 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:32.2,32.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:33.10,34.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:35.10,36.17 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:37.10,38.17 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:39.13,40.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:41.13,42.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:43.13,44.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:45.13,46.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:47.13,48.16 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:49.13,50.16 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:53.2,53.31 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:57.52,514.44 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:514.44,523.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:525.2,525.112 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:525.112,535.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:537.2,537.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:537.44,539.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:541.2,543.44 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:543.44,567.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:569.2,569.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:569.44,570.32 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:570.32,578.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:581.2,581.17 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:584.49,587.35 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:587.35,588.107 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:588.107,590.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:593.2,593.73 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:593.73,595.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:597.2,597.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:600.73,601.49 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/keybindings.go:601.49,603.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:10.59,12.34 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:12.34,14.45 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:14.45,16.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:18.3,18.67 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:18.67,22.4 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:23.3,23.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:27.39,29.37 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:29.37,31.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:32.2,32.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:35.65,36.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:36.14,38.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:40.2,40.39 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:40.39,42.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:45.2,47.40 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:50.40,51.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:51.14,53.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:55.2,57.42 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:61.44,69.89 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:69.89,73.17 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:73.17,75.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:77.3,77.10 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:77.10,85.4 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:87.3,88.17 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:88.17,90.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:91.3,101.19 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:104.2,104.57 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:104.57,106.58 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:106.58,108.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:115.2,115.39 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:118.52,119.17 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:119.17,121.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:123.2,124.8 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:124.8,126.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/layout.go:129.67,133.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:9.38,15.2 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:17.40,23.49 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:23.49,26.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:28.2,29.36 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:29.36,31.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:33.2,33.74 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:36.67,42.2 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:44.68,50.30 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:50.30,51.43 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:51.43,53.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:56.2,57.43 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:57.43,59.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:61.2,61.74 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:64.67,67.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:69.66,74.2 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:76.52,81.9 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:81.9,83.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:85.2,86.40 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:89.68,94.2 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:96.67,99.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:101.41,102.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:102.29,104.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:106.2,108.34 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:108.34,110.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/main_panel.go:112.2,112.40 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:16.71,27.28 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:27.28,29.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:38.61,39.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:39.46,41.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:43.2,43.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:43.29,45.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:47.2,47.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:50.41,52.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:52.16,54.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:56.2,56.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:59.52,60.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:60.22,64.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:64.26,66.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:70.2,72.34 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:72.34,73.31 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:73.31,75.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:77.3,77.21 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:77.21,79.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:81.3,81.67 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:84.2,84.34 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:84.34,85.45 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:85.45,89.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:92.2,95.55 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:95.55,97.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:99.2,102.40 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:107.43,114.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:116.41,121.47 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:121.47,122.43 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:122.43,124.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:129.3,129.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/menu_panel.go:132.2,132.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:18.77,21.60 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:21.60,29.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:30.67,32.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:43.61,45.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:50.79,51.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:51.53,51.93 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:54.68,67.41 12 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:67.41,69.48 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:69.48,71.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:72.8,74.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:76.2,80.15 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:83.40,84.51 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:84.51,86.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:88.2,88.43 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:91.46,93.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:93.16,95.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:97.2,99.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:102.77,104.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:104.16,106.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:108.2,120.88 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:120.88,123.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:123.26,124.70 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:124.70,125.45 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:125.45,127.7 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:128.6,128.16 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:134.2,137.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:140.45,141.122 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:141.122,142.67 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:142.67,144.18 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:144.18,146.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:147.4,147.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:152.80,154.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:154.16,156.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:158.2,164.67 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/networks_panel.go:167.78,179.2 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:10.55,15.35 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:15.35,16.58 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:16.58,17.28 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:18.12,19.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:20.18,21.51 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:27.2,27.25 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:27.25,29.36 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:29.36,30.59 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:30.59,31.48 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:31.48,33.47 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:33.47,34.50 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:34.50,35.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:38.6,38.52 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:46.2,47.49 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:50.76,51.32 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:51.32,53.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:55.2,55.88 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:55.88,58.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:58.26,59.27 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:59.27,61.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:63.5,63.33 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/options_menu_panel.go:68.2,72.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels.go:5.45,7.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:22.76,25.60 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:25.60,26.49 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:26.49,44.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:46.5,52.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:54.67,56.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:66.61,68.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:75.40,78.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:80.41,82.46 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:82.46,83.66 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:83.66,85.53 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:85.53,87.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:91.2,91.20 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:94.74,95.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:95.53,95.80 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:98.37,113.2 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:115.74,119.35 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:119.35,135.14 7 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:135.14,137.51 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:137.51,139.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:142.4,142.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:147.86,148.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:148.53,150.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:153.69,155.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:157.69,159.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:161.31,172.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:175.70,177.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:177.16,179.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/project_panel.go:181.2,181.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:20.77,23.60 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:23.60,51.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:52.67,53.33 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:53.33,55.6 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:56.5,56.107 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:66.61,67.48 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:67.48,69.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:71.4,71.48 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:71.48,73.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:75.4,75.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:78.21,80.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:84.88,85.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:85.30,86.54 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:86.54,86.83 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:89.2,89.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:92.85,93.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:93.30,94.54 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:94.54,94.83 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:97.2,97.50 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:100.78,101.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:101.30,102.54 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:102.54,102.83 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:105.2,105.52 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:108.76,110.64 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:110.64,112.18 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:112.18,114.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:116.4,116.36 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:119.41,119.64 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:125.77,126.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:126.30,127.54 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:127.54,127.93 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:130.2,130.57 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:139.54,141.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:143.76,145.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:145.16,147.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:149.2,162.82 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:162.82,165.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:165.26,166.70 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:166.70,167.69 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:167.69,169.7 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:171.6,171.16 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:177.2,180.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:183.71,185.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:185.16,187.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:188.2,188.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:188.30,190.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:192.2,192.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:195.70,197.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:197.16,199.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:201.2,201.113 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:201.113,202.68 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:202.68,203.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:203.41,205.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:207.4,207.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:212.68,214.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:214.16,216.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:218.2,218.72 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:218.72,219.38 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:219.38,221.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:223.3,223.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:227.73,229.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:229.16,231.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:233.2,233.69 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:233.69,234.43 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:234.43,236.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:238.3,238.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:242.71,244.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:244.16,246.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:248.2,248.67 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:248.67,249.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:249.41,251.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:253.3,253.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:257.72,259.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:259.16,261.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:263.2,263.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:263.30,265.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:267.2,268.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:268.16,270.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:272.2,272.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:275.82,277.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:277.16,279.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:281.2,282.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:282.16,284.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:286.2,286.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:289.68,290.118 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:290.118,296.73 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:296.73,297.59 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:297.59,299.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:300.4,300.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:305.70,320.26 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:320.26,321.69 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:321.69,322.66 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:322.66,324.7 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:325.6,325.16 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:332.26,333.69 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:333.69,334.77 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:334.77,336.7 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:337.6,337.16 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:343.2,343.82 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:343.82,348.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:350.2,353.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:356.77,358.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:358.16,360.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:362.2,379.26 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:379.26,380.72 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:380.72,381.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:381.46,383.7 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:384.6,384.16 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:394.26,395.72 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:395.72,396.70 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:396.70,398.7 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:399.6,399.16 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:409.26,411.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:415.2,415.82 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:415.82,420.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:422.2,425.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:428.80,430.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:430.16,432.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:434.2,444.44 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:444.44,445.33 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:445.33,447.14 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:449.3,449.48 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:449.48,450.35 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:450.35,453.15 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:458.2,458.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:458.30,460.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:462.2,462.67 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:465.78,470.2 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:472.76,474.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:474.16,476.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:478.2,479.22 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:479.22,481.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:483.2,483.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:486.87,488.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:488.16,490.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:492.2,493.22 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:493.22,495.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/services_panel.go:497.2,497.46 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:15.52,19.40 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:19.40,21.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:23.2,27.39 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:27.39,29.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:31.2,33.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:36.43,44.12 6 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:44.12,48.49 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:48.49,50.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:53.2,55.34 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:55.34,59.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/subprocess.go:61.2,65.22 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:10.62,12.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:34.79,38.35 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:38.35,40.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:43.2,43.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:47.84,53.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:55.55,56.35 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:56.35,62.3 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:68.67,71.36 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:71.36,72.25 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:72.25,74.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:75.3,79.7 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:79.7,80.11 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:81.25,83.11 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:84.22,86.11 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:87.22,89.34 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/tasks_adapter.go:94.2,100.30 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/theme.go:8.60,10.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/theme.go:13.40,19.2 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:15.61,18.65 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:18.65,20.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:20.8,22.32 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:22.32,23.36 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:23.36,25.10 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:27.4,27.33 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:27.33,30.5 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:33.2,34.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:34.16,35.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:37.2,38.37 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:41.65,44.46 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:44.46,46.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:46.8,48.32 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:48.32,49.36 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:49.36,51.10 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:53.4,53.33 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:53.33,56.5 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:59.2,60.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:60.16,61.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:63.2,64.37 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:67.33,70.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:74.88,75.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:75.44,77.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:78.2,89.29 9 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:89.29,91.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:91.8,91.34 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:91.34,93.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:95.2,95.29 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:95.29,98.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:100.2,100.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:100.22,102.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:104.2,105.22 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:105.22,107.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:110.69,112.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:114.44,117.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:119.46,122.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:124.63,128.2 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:131.70,132.34 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:132.34,134.17 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:134.17,136.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:137.3,137.43 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:137.43,139.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:140.3,140.43 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:140.43,142.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:143.3,143.34 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:145.2,145.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:148.44,150.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:153.46,155.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:158.52,159.38 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:159.38,161.17 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:161.17,163.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:164.3,164.34 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:168.73,170.43 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:170.43,172.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:173.2,174.41 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:177.70,179.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:181.43,183.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:185.54,187.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:189.42,192.24 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:192.24,194.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:195.2,195.27 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:198.61,200.32 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:200.32,202.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:203.2,203.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:206.55,212.54 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:212.54,214.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:215.2,216.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:219.44,221.28 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:222.14,223.33 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:224.22,225.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:227.2,227.34 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:230.52,232.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:234.42,236.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:238.33,243.2 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:245.111,246.65 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:246.65,248.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:249.2,249.76 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:252.137,253.72 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:253.72,255.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:257.2,262.25 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:262.25,264.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:266.2,266.35 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:266.35,268.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:270.2,272.39 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:272.39,273.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:273.44,275.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:278.2,278.31 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:281.40,282.37 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:282.37,286.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:288.2,290.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:293.40,294.37 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:294.37,298.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:300.2,302.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:305.93,306.25 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:306.25,307.21 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:307.21,308.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:308.22,310.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:311.4,311.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:314.2,314.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:317.93,318.25 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:318.25,319.21 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:319.21,320.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:320.13,322.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:323.4,323.24 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:326.2,326.22 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:329.43,331.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:333.66,336.48 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:336.48,337.45 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:337.45,339.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:342.2,342.19 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:346.66,349.48 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:349.48,350.45 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:350.45,352.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:355.2,355.19 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:358.57,367.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:369.57,371.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/view_helpers.go:373.54,375.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:19.29,20.27 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:20.27,23.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:25.2,25.24 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:65.61,92.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:94.40,96.56 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:96.56,98.58 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:98.58,100.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:101.3,101.50 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:104.2,118.94 10 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:118.94,120.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:120.8,122.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:124.2,164.12 31 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:167.47,168.92 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:168.92,170.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:172.2,174.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:177.48,179.18 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:179.18,181.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:183.2,184.24 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:184.24,186.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:188.2,189.38 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:192.43,194.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:197.52,198.102 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:198.102,200.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:202.2,202.75 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/views.go:202.75,204.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:18.75,21.59 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:21.59,29.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:30.65,32.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:43.59,44.61 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:44.61,46.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:47.4,47.61 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:47.61,49.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:50.4,50.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:56.76,57.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:57.53,57.91 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:60.65,71.33 10 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:71.33,73.42 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:73.42,75.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:76.8,78.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:80.2,80.36 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:80.36,83.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:85.2,85.15 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:88.39,89.50 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:89.50,91.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:93.2,93.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:96.45,98.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:98.16,100.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:102.2,104.12 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:107.76,109.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:109.16,111.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:113.2,132.87 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:132.87,135.26 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:135.26,136.70 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:136.70,137.56 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:137.56,139.7 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:140.6,140.16 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:146.2,149.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:152.44,153.121 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:153.121,154.67 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:154.67,156.18 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:156.18,158.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:159.4,159.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:164.79,166.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:166.16,168.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:170.2,176.67 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/volumes_panel.go:179.77,191.2 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/window.go:10.50,12.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/window.go:14.48,16.2 1 0 -mode: atomic -mode: atomic -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:33.58,34.71 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:34.71,36.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:39.66,41.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:43.61,45.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:47.50,50.20 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:50.20,52.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:54.2,54.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:57.50,60.20 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:60.20,62.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:64.2,64.65 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/context_state.go:67.57,69.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:16.55,18.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:20.50,26.30 5 2 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:26.30,28.3 1 4 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:31.63,36.37 4 2 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:36.37,37.22 1 6 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:37.22,39.4 1 3 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:43.57,47.17 3 2 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:47.17,49.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:51.2,51.47 1 2 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:51.47,53.3 1 5 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:56.47,61.2 3 3 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:63.58,67.45 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:67.45,70.3 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:72.2,72.49 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:76.40,81.2 3 2 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:83.51,87.37 3 3 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:87.37,88.35 1 4 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:88.35,90.4 1 2 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:92.2,92.11 1 1 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:95.45,100.37 4 2 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:100.37,102.3 1 4 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:103.2,103.15 1 2 -github.com/jesseduffield/lazydocker/pkg/gui/panels/filtered_list.go:106.48,111.2 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/list_panel.go:14.57,16.25 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/list_panel.go:16.25,18.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/list_panel.go:20.2,20.33 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/list_panel.go:23.50,26.33 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/list_panel.go:26.33,28.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/list_panel.go:32.55,34.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/list_panel.go:36.44,38.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/list_panel.go:40.44,42.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:80.51,85.95 4 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:85.95,87.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:89.2,89.25 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:89.25,91.17 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:91.17,93.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:96.2,96.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:99.53,101.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:103.52,105.16 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:105.16,106.41 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:106.41,108.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:110.3,110.32 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:110.32,111.53 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:111.53,111.83 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:114.3,114.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:117.2,119.33 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:122.59,123.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:123.30,125.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:127.2,128.34 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:128.34,130.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:132.2,138.33 5 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:141.60,145.9 3 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:145.9,148.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:150.2,150.18 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:153.54,157.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:159.54,163.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:165.57,166.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:166.30,168.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:170.2,172.28 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:175.57,176.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:176.30,178.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:180.2,182.28 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:185.41,187.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:189.51,192.2 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:194.47,197.48 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:197.48,198.47 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:198.47,200.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:202.3,202.67 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:202.67,203.78 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:203.78,205.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:206.6,208.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:210.3,210.25 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:210.25,211.78 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:211.78,213.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:216.3,216.14 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:219.2,221.29 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:224.52,227.31 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:227.31,229.74 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:229.74,231.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:232.3,233.17 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:233.17,235.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:236.3,238.29 2 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:238.29,239.44 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:239.44,241.5 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:244.3,244.40 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:244.40,246.4 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:247.3,247.13 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:250.2,250.12 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:253.58,254.30 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:254.30,256.3 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:258.2,258.42 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:261.55,263.2 1 0 -github.com/jesseduffield/lazydocker/pkg/gui/panels/side_list_panel.go:265.47,266.22 1 0 -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 -mode: atomic -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 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:49.100,55.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:56.3,56.13 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:59.2,59.12 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:63.34,66.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:69.35,72.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:75.37,78.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:81.37,84.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:87.49,88.24 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:88.24,90.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:93.2,93.33 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:93.33,95.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:97.2,97.35 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:97.35,99.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:101.2,104.17 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:108.84,110.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:110.16,112.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:115.2,115.27 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:115.27,117.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:119.2,119.53 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:123.49,126.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:129.66,131.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:134.68,136.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:136.16,138.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:140.2,140.82 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container.go:144.42,146.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:138.68,143.23 4 1 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:143.23,145.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:146.2,146.14 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:150.66,152.23 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:152.23,154.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:155.2,155.14 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:158.82,164.2 4 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:167.64,168.22 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:168.22,170.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:172.2,172.37 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:172.37,173.48 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:173.48,176.4 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:180.59,184.23 4 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:184.23,186.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/container_stats.go:187.2,187.38 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:66.75,70.2 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:73.161,75.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:75.16,77.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:79.2,80.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:80.16,82.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:84.2,85.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:85.16,87.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:89.2,113.16 5 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:113.16,116.3 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:118.2,118.27 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:121.39,123.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:125.71,128.16 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:128.16,134.3 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:136.2,139.21 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:139.21,154.3 5 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:156.2,156.35 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:159.148,164.16 4 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:164.16,166.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:168.2,170.28 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:170.28,172.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:172.8,174.17 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:174.17,176.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:179.2,181.34 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:184.98,186.35 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:186.35,187.40 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:187.40,188.66 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:188.66,190.15 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:193.3,193.26 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:198.94,203.16 4 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:203.16,205.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:207.2,209.39 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:209.39,213.56 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:213.56,214.44 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:214.44,216.10 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:221.3,221.26 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:221.26,230.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:232.3,234.47 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:234.47,236.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:236.9,238.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:239.3,244.34 5 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:247.2,247.27 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:251.59,252.31 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:252.31,254.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:256.2,258.16 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:258.16,260.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:266.2,268.28 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:268.28,276.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:278.2,278.22 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:283.79,287.39 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:287.39,289.17 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:289.17,291.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:291.9,293.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:296.2,296.12 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:300.58,311.2 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:314.54,321.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:321.16,323.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:324.2,324.15 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:332.44,335.36 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:335.36,337.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:339.2,340.26 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:340.26,342.53 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:342.53,344.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:345.3,346.17 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:346.17,348.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:349.3,349.37 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:352.2,352.26 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:352.26,357.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:359.2,360.22 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:360.22,360.56 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:361.74,361.108 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:364.2,366.16 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:366.16,368.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:369.2,370.9 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:370.9,372.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:373.2,374.9 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:374.9,376.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:378.2,378.29 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:378.29,380.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/docker.go:388.2,388.31 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/dummies.go:14.37,16.2 1 19 -github.com/jesseduffield/lazydocker/pkg/commands/dummies.go:19.44,29.2 2 19 -github.com/jesseduffield/lazydocker/pkg/commands/dummies.go:32.34,36.2 3 19 -github.com/jesseduffield/lazydocker/pkg/commands/dummies.go:39.45,41.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/dummies.go:44.78,52.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/errors.go:18.33,19.16 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/errors.go:19.16,21.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/errors.go:23.2,23.28 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/errors.go:35.61,39.2 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/errors.go:42.52,44.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/errors.go:46.39,48.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/errors.go:51.45,53.35 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/errors.go:53.35,55.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/errors.go:56.2,56.14 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:31.70,32.85 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:32.85,34.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:36.2,36.12 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:39.85,41.25 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:41.25,43.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:45.2,46.18 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:46.18,48.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:49.2,50.23 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:50.23,52.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:54.2,56.64 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:56.64,60.3 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:62.2,66.18 4 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:66.18,68.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:70.2,75.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:79.49,81.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:81.16,83.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:85.2,85.85 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:85.85,87.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:89.2,92.33 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:96.59,98.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:98.16,100.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:102.2,104.31 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:104.31,107.20 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:107.20,109.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:111.3,114.25 4 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:114.25,118.88 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:118.88,119.40 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:119.40,121.11 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:126.3,135.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:138.2,138.23 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/image.go:142.45,145.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/network.go:23.63,25.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/network.go:25.16,27.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/network.go:29.2,31.35 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/network.go:31.35,40.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/network.go:42.2,42.25 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/network.go:46.47,49.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/network.go:52.34,54.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:42.75,50.2 1 19 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:54.71,56.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:59.74,65.2 5 5 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:68.102,74.2 5 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:77.76,79.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:82.56,85.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:88.71,91.2 2 5 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:94.99,97.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:99.73,102.32 2 2 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:102.32,111.3 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:111.8,113.3 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:115.2,115.86 1 2 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:119.54,122.2 2 3 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:125.50,127.16 2 4 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:127.16,129.3 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:130.2,130.22 1 3 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:130.22,132.3 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:133.2,133.15 1 2 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:136.71,138.16 2 5 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:138.16,142.9 2 3 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:142.9,144.4 1 2 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:145.3,145.28 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:147.2,147.26 1 2 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:151.53,160.2 5 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:163.49,172.2 5 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:176.66,178.18 2 4 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:178.18,180.3 1 3 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:181.2,181.18 1 4 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:181.18,182.50 1 2 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:182.50,184.4 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:186.2,186.18 1 4 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:186.18,188.3 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:190.2,190.51 1 3 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:194.88,196.2 1 3 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:199.50,201.32 2 5 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:201.32,207.3 2 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:207.8,215.3 2 4 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:216.2,216.32 1 5 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:221.52,223.2 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:226.67,228.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:228.16,230.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:231.2,234.16 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:234.16,236.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:237.2,237.12 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:241.78,243.16 2 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:243.16,246.3 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:248.2,248.56 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:248.56,251.3 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:252.2,252.40 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:252.40,255.3 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:257.2,257.28 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:261.51,264.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:267.59,268.41 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:268.41,269.25 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:269.25,271.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:272.3,272.20 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:274.2,274.18 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:280.61,284.16 4 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:284.16,285.26 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:285.26,287.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:288.3,288.31 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:290.2,290.12 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:294.48,296.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:296.16,298.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:299.2,299.29 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:303.64,305.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:308.66,311.37 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:311.37,313.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:315.2,315.35 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:315.35,317.17 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:317.17,319.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:321.3,321.27 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:327.2,332.27 4 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:332.27,334.13 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:334.13,336.18 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:336.18,338.5 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:340.4,340.45 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:340.45,342.5 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:344.4,344.52 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:344.52,345.19 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:345.19,347.6 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:350.4,350.44 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:350.44,352.5 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:354.4,354.13 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:358.2,360.26 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:360.26,362.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:363.2,363.12 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:367.47,369.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os.go:372.55,374.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/os_default_platform.go:10.30,18.2 1 19 -github.com/jesseduffield/lazydocker/pkg/commands/service.go:23.76,25.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/service.go:28.32,30.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/service.go:33.30,35.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/service.go:38.35,40.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/service.go:43.33,45.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/service.go:47.59,53.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/service.go:56.47,58.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/service.go:61.49,72.2 5 0 -github.com/jesseduffield/lazydocker/pkg/commands/service.go:75.66,83.2 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/volume.go:23.61,25.16 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/volume.go:25.16,27.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/volume.go:29.2,33.33 3 0 -github.com/jesseduffield/lazydocker/pkg/commands/volume.go:33.33,42.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/volume.go:44.2,44.24 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/volume.go:48.46,51.2 2 0 -github.com/jesseduffield/lazydocker/pkg/commands/volume.go:54.43,56.2 1 0 -mode: atomic -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:32.53,36.83 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:36.83,38.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:39.39,39.61 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:48.66,52.16 4 3 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:52.16,55.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:58.2,58.23 1 3 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:58.23,60.17 2 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:60.17,62.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:63.3,64.17 2 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:64.17,66.4 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:68.3,68.21 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:70.2,70.26 1 2 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:75.33,75.47 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:85.44,87.2 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:89.117,91.16 2 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:91.16,93.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:94.2,97.16 3 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:97.16,99.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:103.2,108.16 5 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:108.16,110.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:113.2,118.8 2 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:123.87,127.6 3 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:127.6,128.10 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:129.21,130.20 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:131.14,131.14 0 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:134.3,135.17 2 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:135.17,136.12 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:138.3,138.13 1 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:143.79,145.16 2 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:145.16,147.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:148.2,149.12 2 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:152.101,156.16 4 1 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:156.16,158.3 1 0 -github.com/jesseduffield/lazydocker/pkg/commands/ssh/ssh.go:159.2,159.17 1 1 -mode: atomic -mode: atomic +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:34.90,36.2 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:39.31,40.26 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:40.26,42.3 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:44.2,46.12 2 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:46.12,49.3 2 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:51.2,51.9 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:52.11,53.9 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:54.37,55.41 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:59.66,60.12 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:60.12,69.27 8 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:69.27,72.4 2 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:73.3,78.27 4 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:78.27,82.4 3 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:84.3,92.13 2 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:92.13,96.4 3 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:99.2,99.12 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:102.23,105.15 3 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:105.15,107.3 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:109.2,113.18 5 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:119.159,122.45 2 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:122.45,123.20 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:123.20,125.4 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:126.3,130.7 4 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:130.7,131.11 1 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:132.25,134.11 2 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:135.22,137.11 2 0 +github.com/christophe-duc/lazypodman/pkg/tasks/tasks.go:138.22,140.26 2 0 diff --git a/lazypodman b/lazypodman index 0fcce3f9..6962c7c3 100755 Binary files a/lazypodman and b/lazypodman differ diff --git a/pkg/commands/runtime_libpod.go b/pkg/commands/runtime_libpod.go index 477e1941..b771ff82 100644 --- a/pkg/commands/runtime_libpod.go +++ b/pkg/commands/runtime_libpod.go @@ -8,6 +8,8 @@ import ( "github.com/containers/podman/v5/libpod" "github.com/containers/podman/v5/libpod/define" + "go.podman.io/common/libimage" + nettypes "go.podman.io/common/libnetwork/types" ) // LibpodRuntime implements ContainerRuntime using libpod directly. @@ -193,25 +195,25 @@ func (r *LibpodRuntime) ContainerStats(ctx context.Context, id string, stream bo // ListImages returns all images. func (r *LibpodRuntime) ListImages(ctx context.Context) ([]ImageSummary, error) { - imgs, err := r.runtime.ImageRuntime().GetImages() + imgs, err := r.runtime.LibimageRuntime().ListImages(ctx, nil) if err != nil { return nil, err } - return convertLibpodImageList(imgs), nil + return convertLibpodImageList(ctx, imgs), nil } // InspectImage returns detailed information about an image. func (r *LibpodRuntime) InspectImage(ctx context.Context, id string) (*ImageDetails, error) { - img, _, err := r.runtime.ImageRuntime().LookupImage(id, nil) + img, _, err := r.runtime.LibimageRuntime().LookupImage(id, nil) if err != nil { return nil, err } - return convertLibpodImageInspect(img), nil + return convertLibpodImageInspect(ctx, img), nil } // ImageHistory returns the history of an image. func (r *LibpodRuntime) ImageHistory(ctx context.Context, id string) ([]ImageHistoryEntry, error) { - img, _, err := r.runtime.ImageRuntime().LookupImage(id, nil) + img, _, err := r.runtime.LibimageRuntime().LookupImage(id, nil) if err != nil { return nil, err } @@ -224,17 +226,20 @@ func (r *LibpodRuntime) ImageHistory(ctx context.Context, id string) ([]ImageHis // RemoveImage removes an image. func (r *LibpodRuntime) RemoveImage(ctx context.Context, id string, force bool) error { - img, _, err := r.runtime.ImageRuntime().LookupImage(id, nil) + img, _, err := r.runtime.LibimageRuntime().LookupImage(id, nil) if err != nil { return err } - _, err = r.runtime.ImageRuntime().RemoveImages(ctx, []string{img.ID()}, &libimage.RemoveImagesOptions{Force: force}) - return err + _, errs := r.runtime.LibimageRuntime().RemoveImages(ctx, []string{img.ID()}, &libimage.RemoveImagesOptions{Force: force}) + if len(errs) > 0 { + return errs[0] + } + return nil } // PruneImages removes unused images. func (r *LibpodRuntime) PruneImages(ctx context.Context) error { - _, err := r.runtime.ImageRuntime().PruneImages(ctx, nil) + _, err := r.runtime.LibimageRuntime().PruneImages(ctx, nil) return err } @@ -443,40 +448,59 @@ func convertLibpodContainerStats(stats *define.ContainerStats) ContainerStatsEnt } } -func convertLibpodImageList(imgs []*libimage.Image) []ImageSummary { +func convertLibpodImageList(ctx context.Context, imgs []*libimage.Image) []ImageSummary { result := make([]ImageSummary, len(imgs)) for i, img := range imgs { + size, _ := img.Size() // Ignore error for list view + labels, _ := img.Labels(ctx) // Ignore error for list view result[i] = ImageSummary{ ID: img.ID(), RepoTags: img.Names(), Created: img.Created().Unix(), - Size: img.Size(), - Labels: img.Labels(), + Size: size, + Labels: labels, } } return result } -func convertLibpodImageInspect(img *libimage.Image) *ImageDetails { +func convertLibpodImageInspect(ctx context.Context, img *libimage.Image) *ImageDetails { if img == nil { return nil } + data, err := img.Inspect(ctx, nil) + if err != nil { + return nil + } + var created time.Time + if data.Created != nil { + created = *data.Created + } return &ImageDetails{ - ID: img.ID(), - RepoTags: img.Names(), - Created: img.Created(), - Size: img.Size(), - Architecture: img.Architecture(), - Os: img.OS(), + ID: data.ID, + RepoTags: data.RepoTags, + RepoDigests: data.RepoDigests, + Parent: data.Parent, + Comment: data.Comment, + Created: created, + Author: data.Author, + Architecture: data.Architecture, + Os: data.Os, + Size: data.Size, + VirtualSize: data.VirtualSize, } } -func convertLibpodImageHistory(history []*libimage.ImageHistoryLayer) []ImageHistoryEntry { +func convertLibpodImageHistory(history []libimage.ImageHistory) []ImageHistoryEntry { result := make([]ImageHistoryEntry, len(history)) for i, layer := range history { + created := int64(0) + if layer.Created != nil { + created = layer.Created.Unix() + } result[i] = ImageHistoryEntry{ ID: layer.ID, - Created: layer.Created.Unix(), + Created: created, CreatedBy: layer.CreatedBy, Tags: layer.Tags, Size: layer.Size, @@ -502,7 +526,7 @@ func convertLibpodVolumeList(vols []*libpod.Volume) []VolumeSummary { return result } -func convertLibpodNetworkList(nets []types.Network) []NetworkSummary { +func convertLibpodNetworkList(nets []nettypes.Network) []NetworkSummary { result := make([]NetworkSummary, len(nets)) for i, nw := range nets { result[i] = NetworkSummary{ diff --git a/pkg/commands/runtime_socket.go b/pkg/commands/runtime_socket.go index d9e38f7e..ee6eee24 100644 --- a/pkg/commands/runtime_socket.go +++ b/pkg/commands/runtime_socket.go @@ -3,8 +3,10 @@ package commands import ( "context" "encoding/json" + "strings" "time" + "github.com/containers/podman/v5/libpod/define" "github.com/containers/podman/v5/pkg/bindings" "github.com/containers/podman/v5/pkg/bindings/containers" "github.com/containers/podman/v5/pkg/bindings/images" @@ -12,6 +14,9 @@ import ( "github.com/containers/podman/v5/pkg/bindings/system" "github.com/containers/podman/v5/pkg/bindings/volumes" "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/containers/podman/v5/pkg/domain/entities/types" + handlertypes "github.com/containers/podman/v5/pkg/api/handlers/types" + nettypes "go.podman.io/common/libnetwork/types" ) // SocketRuntime implements ContainerRuntime using Podman's REST API bindings. @@ -115,7 +120,16 @@ func (r *SocketRuntime) ContainerTop(ctx context.Context, id string) ([]string, if err != nil { return nil, nil, err } - return result.Titles, result.Processes, nil + // Parse the result - first line is headers, rest are processes + if len(result) == 0 { + return nil, nil, nil + } + headers := strings.Fields(result[0]) + processes := make([][]string, 0, len(result)-1) + for _, line := range result[1:] { + processes = append(processes, strings.Fields(line)) + } + return headers, processes, nil } // PruneContainers removes all stopped containers. @@ -271,11 +285,20 @@ func (r *SocketRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) defer close(errChan) opts := &system.EventsOptions{} - podmanEventChan, err := system.Events(r.conn, nil, opts) - if err != nil { - errChan <- err - return - } + podmanEventChan := make(chan types.Event) + cancelChan := make(chan bool) + + go func() { + <-ctx.Done() + close(cancelChan) + }() + + go func() { + err := system.Events(r.conn, podmanEventChan, cancelChan, opts) + if err != nil { + errChan <- err + } + }() for podmanEvent := range podmanEventChan { event := Event{ @@ -303,6 +326,11 @@ func (r *SocketRuntime) Events(ctx context.Context) (<-chan Event, <-chan error) func convertPodmanContainerList(podmanContainers []entities.ListContainer) []ContainerSummary { result := make([]ContainerSummary, len(podmanContainers)) for i, c := range podmanContainers { + var sizeRw, sizeRootFs int64 + if c.Size != nil { + sizeRw = c.Size.RwSize + sizeRootFs = c.Size.RootFsSize + } result[i] = ContainerSummary{ ID: c.ID, Names: c.Names, @@ -314,8 +342,8 @@ func convertPodmanContainerList(podmanContainers []entities.ListContainer) []Con Status: c.Status, Ports: convertPodmanPorts(c.Ports), Labels: c.Labels, - SizeRw: c.Size.RwSize, - SizeRootFs: c.Size.RootFsSize, + SizeRw: sizeRw, + SizeRootFs: sizeRootFs, Pod: c.Pod, PodName: c.PodName, } @@ -332,7 +360,7 @@ func commandToString(cmd []string) string { return string(data) } -func convertPodmanPorts(ports []entities.PortMapping) []PortMapping { +func convertPodmanPorts(ports []nettypes.PortMapping) []PortMapping { result := make([]PortMapping, len(ports)) for i, p := range ports { result[i] = PortMapping{ @@ -345,7 +373,7 @@ func convertPodmanPorts(ports []entities.PortMapping) []PortMapping { return result } -func convertPodmanContainerInspect(data *entities.InspectContainerData) *ContainerDetails { +func convertPodmanContainerInspect(data *define.InspectContainerData) *ContainerDetails { if data == nil { return nil } @@ -361,7 +389,6 @@ func convertPodmanContainerInspect(data *entities.InspectContainerData) *Contain ResolvConfPath: data.ResolvConfPath, HostnamePath: data.HostnamePath, HostsPath: data.HostsPath, - LogPath: data.LogPath, RestartCount: int(data.RestartCount), Driver: data.Driver, MountLabel: data.MountLabel, @@ -393,9 +420,13 @@ func convertPodmanContainerInspect(data *entities.InspectContainerData) *Contain } if data.Config != nil { + var onBuild []string + if data.Config.OnBuild != nil { + onBuild = []string{*data.Config.OnBuild} + } details.Config = &ContainerConfig{ Hostname: data.Config.Hostname, - Domainname: data.Config.Domainname, + Domainname: data.Config.DomainName, User: data.Config.User, AttachStdin: data.Config.AttachStdin, AttachStdout: data.Config.AttachStdout, @@ -408,7 +439,7 @@ func convertPodmanContainerInspect(data *entities.InspectContainerData) *Contain Image: data.Config.Image, WorkingDir: data.Config.WorkingDir, Entrypoint: data.Config.Entrypoint, - OnBuild: data.Config.OnBuild, + OnBuild: onBuild, Labels: data.Config.Labels, StopSignal: data.Config.StopSignal, } @@ -417,7 +448,7 @@ func convertPodmanContainerInspect(data *entities.InspectContainerData) *Contain return details } -func convertPodmanContainerStats(stat entities.ContainerStats) ContainerStatsEntry { +func convertPodmanContainerStats(stat define.ContainerStats) ContainerStatsEntry { return ContainerStatsEntry{ Read: time.Now(), PreRead: time.Now().Add(-time.Second), @@ -451,10 +482,10 @@ func convertPodmanImageList(podmanImages []*entities.ImageSummary) []ImageSummar RepoDigests: img.RepoDigests, Created: img.Created, Size: img.Size, - SharedSize: img.SharedSize, + SharedSize: int64(img.SharedSize), VirtualSize: img.VirtualSize, Labels: img.Labels, - Containers: img.Containers, + Containers: int64(img.Containers), } } return result @@ -480,21 +511,25 @@ func convertPodmanImageInspect(data *entities.ImageInspectReport) *ImageDetails } if data.RootFS != nil { + layers := make([]string, len(data.RootFS.Layers)) + for i, layer := range data.RootFS.Layers { + layers[i] = layer.String() + } details.RootFS = RootFS{ Type: data.RootFS.Type, - Layers: data.RootFS.Layers, + Layers: layers, } } return details } -func convertPodmanImageHistory(history []*entities.ImageHistoryLayer) []ImageHistoryEntry { +func convertPodmanImageHistory(history []*handlertypes.HistoryResponse) []ImageHistoryEntry { result := make([]ImageHistoryEntry, len(history)) for i, layer := range history { result[i] = ImageHistoryEntry{ ID: layer.ID, - Created: layer.Created.Unix(), + Created: layer.Created, CreatedBy: layer.CreatedBy, Tags: layer.Tags, Size: layer.Size, @@ -520,7 +555,7 @@ func convertPodmanVolumeList(podmanVolumes []*entities.VolumeListReport) []Volum return result } -func convertPodmanNetworkList(podmanNetworks []entities.NetworkListReport) []NetworkSummary { +func convertPodmanNetworkList(podmanNetworks []nettypes.Network) []NetworkSummary { result := make([]NetworkSummary, len(podmanNetworks)) for i, nw := range podmanNetworks { result[i] = NetworkSummary{ diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 494a9c7e..c2f1a904 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -67,7 +67,7 @@ func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container] // where a container restarts but the new logs don't get read. // Note that this might be jarring if we have a lot of logs and the container // restarts a lot, so let's keep an eye on it. - return "containers-" + container.ID + "-" + container.Container.State + return "containers-" + container.ID + "-" + container.Summary.State }, }, ListPanel: panels.ListPanel[*commands.Container]{ @@ -89,7 +89,7 @@ func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container] return false } - if !gui.State.ShowExitedContainers && container.Container.State == "exited" { + if !gui.State.ShowExitedContainers && container.Summary.State == "exited" { return false } @@ -112,13 +112,13 @@ func sortContainers(a *commands.Container, b *commands.Container, legacySort boo return a.Name < b.Name } - stateLeft := containerStates[a.Container.State] - stateRight := containerStates[b.Container.State] + stateLeft := containerStates[a.Summary.State] + stateRight := containerStates[b.Summary.State] if stateLeft == stateRight { return a.Name < b.Name } - return containerStates[a.Container.State] < containerStates[b.Container.State] + return containerStates[a.Summary.State] < containerStates[b.Summary.State] } func (gui *Gui) renderContainerEnv(container *commands.Container) tasks.TaskFunc { @@ -532,11 +532,11 @@ func (gui *Gui) handleContainersOpenInBrowserCommand(g *gocui.Gui, v *gocui.View func (gui *Gui) openContainerInBrowser(ctr *commands.Container) error { // skip if no any ports - if len(ctr.Container.Ports) == 0 { + if len(ctr.Summary.Ports) == 0 { return nil } // skip if the first port is not published - port := ctr.Container.Ports[0] + port := ctr.Summary.Ports[0] if port.IP == "" { return nil } diff --git a/pkg/gui/images_panel.go b/pkg/gui/images_panel.go index 22323efb..e475eade 100644 --- a/pkg/gui/images_panel.go +++ b/pkg/gui/images_panel.go @@ -76,10 +76,10 @@ func (gui *Gui) imageConfigStr(image *commands.Image) string { padding := 10 output := "" output += utils.WithPadding("Name: ", padding) + image.Name + "\n" - output += utils.WithPadding("ID: ", padding) + image.Image.ID + "\n" - output += utils.WithPadding("Tags: ", padding) + utils.ColoredString(strings.Join(image.Image.RepoTags, ", "), color.FgGreen) + "\n" - output += utils.WithPadding("Size: ", padding) + utils.FormatDecimalBytes(int(image.Image.Size)) + "\n" - output += utils.WithPadding("Created: ", padding) + fmt.Sprintf("%v", time.Unix(image.Image.Created, 0).Format(time.RFC1123)) + "\n" + output += utils.WithPadding("ID: ", padding) + image.Summary.ID + "\n" + output += utils.WithPadding("Tags: ", padding) + utils.ColoredString(strings.Join(image.Summary.RepoTags, ", "), color.FgGreen) + "\n" + output += utils.WithPadding("Size: ", padding) + utils.FormatDecimalBytes(int(image.Summary.Size)) + "\n" + output += utils.WithPadding("Created: ", padding) + fmt.Sprintf("%v", time.Unix(image.Summary.Created, 0).Format(time.RFC1123)) + "\n" history, err := image.RenderHistory() if err != nil { diff --git a/pkg/gui/networks_panel.go b/pkg/gui/networks_panel.go index 750b15cf..a2f58ed0 100644 --- a/pkg/gui/networks_panel.go +++ b/pkg/gui/networks_panel.go @@ -54,19 +54,19 @@ func (gui *Gui) renderNetworkConfig(network *commands.Network) tasks.TaskFunc { func (gui *Gui) networkConfigStr(network *commands.Network) string { padding := 15 output := "" - output += utils.WithPadding("ID: ", padding) + network.Network.ID + "\n" + output += utils.WithPadding("ID: ", padding) + network.Summary.ID + "\n" output += utils.WithPadding("Name: ", padding) + network.Name + "\n" - output += utils.WithPadding("Driver: ", padding) + network.Network.Driver + "\n" - output += utils.WithPadding("Scope: ", padding) + network.Network.Scope + "\n" - output += utils.WithPadding("EnabledIPV6: ", padding) + strconv.FormatBool(network.Network.EnableIPv6) + "\n" - output += utils.WithPadding("Internal: ", padding) + strconv.FormatBool(network.Network.Internal) + "\n" - output += utils.WithPadding("Attachable: ", padding) + strconv.FormatBool(network.Network.Attachable) + "\n" - output += utils.WithPadding("Ingress: ", padding) + strconv.FormatBool(network.Network.Ingress) + "\n" + output += utils.WithPadding("Driver: ", padding) + network.Summary.Driver + "\n" + output += utils.WithPadding("Scope: ", padding) + network.Summary.Scope + "\n" + output += utils.WithPadding("EnabledIPV6: ", padding) + strconv.FormatBool(network.Summary.EnableIPv6) + "\n" + output += utils.WithPadding("Internal: ", padding) + strconv.FormatBool(network.Summary.Internal) + "\n" + output += utils.WithPadding("Attachable: ", padding) + strconv.FormatBool(network.Summary.Attachable) + "\n" + output += utils.WithPadding("Ingress: ", padding) + strconv.FormatBool(network.Summary.Ingress) + "\n" output += utils.WithPadding("Containers: ", padding) - if len(network.Network.Containers) > 0 { + if len(network.Summary.Containers) > 0 { output += "\n" - for _, v := range network.Network.Containers { + for _, v := range network.Summary.Containers { output += utils.FormatMapItem(padding, v.Name, v.EndpointID) } } else { @@ -74,8 +74,8 @@ func (gui *Gui) networkConfigStr(network *commands.Network) string { } output += "\n" - output += utils.WithPadding("Labels: ", padding) + utils.FormatMap(padding, network.Network.Labels) + "\n" - output += utils.WithPadding("Options: ", padding) + utils.FormatMap(padding, network.Network.Options) + output += utils.WithPadding("Labels: ", padding) + utils.FormatMap(padding, network.Summary.Labels) + "\n" + output += utils.WithPadding("Options: ", padding) + utils.FormatMap(padding, network.Summary.Options) return output } diff --git a/pkg/gui/presentation/containers.go b/pkg/gui/presentation/containers.go index 22b38508..baf42565 100644 --- a/pkg/gui/presentation/containers.go +++ b/pkg/gui/presentation/containers.go @@ -6,7 +6,6 @@ import ( "strconv" "strings" - "github.com/docker/docker/api/types/container" "github.com/fatih/color" "github.com/christophe-duc/lazypodman/pkg/commands" "github.com/christophe-duc/lazypodman/pkg/config" @@ -26,11 +25,11 @@ func GetContainerDisplayStrings(guiConfig *config.GuiConfig, container *commands } func displayContainerImage(container *commands.Container) string { - return strings.TrimPrefix(container.Container.Image, "sha256:") + return strings.TrimPrefix(container.Summary.Image, "sha256:") } func displayPorts(c *commands.Container) string { - portStrings := lo.Map(c.Container.Ports, func(port container.Port, _ int) string { + portStrings := lo.Map(c.Summary.Ports, func(port commands.PortMapping, _ int) string { if port.PublicPort == 0 { return fmt.Sprintf("%d/%s", port.PrivatePort, port.Type) } @@ -77,13 +76,13 @@ func getContainerDisplayStatus(guiConfig *config.GuiConfig, c *commands.Containe var containerState string switch guiConfig.ContainerStatusHealthStyle { case "short": - containerState = shortStatusMap[c.Container.State] + containerState = shortStatusMap[c.Summary.State] case "icon": - containerState = string(iconStatusMap[c.Container.State]) + containerState = string(iconStatusMap[c.Summary.State]) case "long": fallthrough default: - containerState = c.Container.State + containerState = c.Summary.State } return utils.ColoredString(containerState, getContainerColor(c)) @@ -95,7 +94,7 @@ func getContainerDisplaySubstatus(guiConfig *config.GuiConfig, c *commands.Conta return "" } - switch c.Container.State { + switch c.Summary.State { case "exited": return utils.ColoredString( fmt.Sprintf("(%s)", strconv.Itoa(c.Details.State.ExitCode)), getContainerColor(c), @@ -176,7 +175,7 @@ func getDisplayCPUPerc(c *commands.Container) string { // getContainerColor Container color func getContainerColor(c *commands.Container) color.Attribute { - switch c.Container.State { + switch c.Summary.State { case "exited": // This means the colour may be briefly yellow and then switch to red upon starting // Not sure what a better alternative is. diff --git a/pkg/gui/presentation/images.go b/pkg/gui/presentation/images.go index 75363538..f4988761 100644 --- a/pkg/gui/presentation/images.go +++ b/pkg/gui/presentation/images.go @@ -9,6 +9,6 @@ func GetImageDisplayStrings(image *commands.Image) []string { return []string{ image.Name, image.Tag, - utils.FormatDecimalBytes(int(image.Image.Size)), + utils.FormatDecimalBytes(int(image.Summary.Size)), } } diff --git a/pkg/gui/presentation/networks.go b/pkg/gui/presentation/networks.go index 1472cc60..eb349ac5 100644 --- a/pkg/gui/presentation/networks.go +++ b/pkg/gui/presentation/networks.go @@ -3,5 +3,5 @@ package presentation import "github.com/christophe-duc/lazypodman/pkg/commands" func GetNetworkDisplayStrings(network *commands.Network) []string { - return []string{network.Network.Driver, network.Name} + return []string{network.Summary.Driver, network.Name} } diff --git a/pkg/gui/presentation/volumes.go b/pkg/gui/presentation/volumes.go index bcf5c3be..c053f6ba 100644 --- a/pkg/gui/presentation/volumes.go +++ b/pkg/gui/presentation/volumes.go @@ -3,5 +3,5 @@ package presentation import "github.com/christophe-duc/lazypodman/pkg/commands" func GetVolumeDisplayStrings(volume *commands.Volume) []string { - return []string{volume.Volume.Driver, volume.Name} + return []string{volume.Summary.Driver, volume.Name} } diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index 5613b69f..e1ce43e1 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -53,7 +53,7 @@ func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] { if service.Container == nil { return "services-" + service.ID } - return "services-" + service.ID + "-" + service.Container.ID + "-" + service.Container.Container.State + return "services-" + service.ID + "-" + service.Container.ID + "-" + service.Container.Summary.State }, }, ListPanel: panels.ListPanel[*commands.Service]{ diff --git a/pkg/gui/volumes_panel.go b/pkg/gui/volumes_panel.go index 46929602..81ceb489 100644 --- a/pkg/gui/volumes_panel.go +++ b/pkg/gui/volumes_panel.go @@ -41,10 +41,10 @@ func (gui *Gui) getVolumesPanel() *panels.SideListPanel[*commands.Volume] { // because those are the ones you typically care about. // Within that, we also sort them alphabetically Sort: func(a *commands.Volume, b *commands.Volume) bool { - if len(a.Volume.Labels) == 0 && len(b.Volume.Labels) > 0 { + if len(a.Summary.Labels) == 0 && len(b.Summary.Labels) > 0 { return false } - if len(a.Volume.Labels) > 0 && len(b.Volume.Labels) == 0 { + if len(a.Summary.Labels) > 0 && len(b.Summary.Labels) == 0 { return true } return a.Name < b.Name @@ -61,25 +61,25 @@ func (gui *Gui) volumeConfigStr(volume *commands.Volume) string { padding := 15 output := "" output += utils.WithPadding("Name: ", padding) + volume.Name + "\n" - output += utils.WithPadding("Driver: ", padding) + volume.Volume.Driver + "\n" - output += utils.WithPadding("Scope: ", padding) + volume.Volume.Scope + "\n" - output += utils.WithPadding("Mountpoint: ", padding) + volume.Volume.Mountpoint + "\n" - output += utils.WithPadding("Labels: ", padding) + utils.FormatMap(padding, volume.Volume.Labels) + "\n" - output += utils.WithPadding("Options: ", padding) + utils.FormatMap(padding, volume.Volume.Options) + "\n" + output += utils.WithPadding("Driver: ", padding) + volume.Summary.Driver + "\n" + output += utils.WithPadding("Scope: ", padding) + volume.Summary.Scope + "\n" + output += utils.WithPadding("Mountpoint: ", padding) + volume.Summary.Mountpoint + "\n" + output += utils.WithPadding("Labels: ", padding) + utils.FormatMap(padding, volume.Summary.Labels) + "\n" + output += utils.WithPadding("Options: ", padding) + utils.FormatMap(padding, volume.Summary.Options) + "\n" output += utils.WithPadding("Status: ", padding) - if volume.Volume.Status != nil { + if volume.Summary.Status != nil { output += "\n" - for k, v := range volume.Volume.Status { + for k, v := range volume.Summary.Status { output += utils.FormatMapItem(padding, k, v) } } else { output += "n/a" } - if volume.Volume.UsageData != nil { - output += utils.WithPadding("RefCount: ", padding) + fmt.Sprintf("%d", volume.Volume.UsageData.RefCount) + "\n" - output += utils.WithPadding("Size: ", padding) + utils.FormatBinaryBytes(int(volume.Volume.UsageData.Size)) + "\n" + if volume.Summary.UsageData != nil { + output += utils.WithPadding("RefCount: ", padding) + fmt.Sprintf("%d", volume.Summary.UsageData.RefCount) + "\n" + output += utils.WithPadding("Size: ", padding) + utils.FormatBinaryBytes(int(volume.Summary.UsageData.Size)) + "\n" } return output diff --git a/vendor/dario.cat/mergo/.deepsource.toml b/vendor/dario.cat/mergo/.deepsource.toml new file mode 100644 index 00000000..a8bc979e --- /dev/null +++ b/vendor/dario.cat/mergo/.deepsource.toml @@ -0,0 +1,12 @@ +version = 1 + +test_patterns = [ + "*_test.go" +] + +[[analyzers]] +name = "go" +enabled = true + + [analyzers.meta] + import_path = "dario.cat/mergo" \ No newline at end of file diff --git a/vendor/dario.cat/mergo/.gitignore b/vendor/dario.cat/mergo/.gitignore new file mode 100644 index 00000000..45ad0f1a --- /dev/null +++ b/vendor/dario.cat/mergo/.gitignore @@ -0,0 +1,36 @@ +#### joe made this: http://goel.io/joe + +#### go #### +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Golang/Intellij +.idea + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +#### vim #### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags diff --git a/vendor/dario.cat/mergo/.travis.yml b/vendor/dario.cat/mergo/.travis.yml new file mode 100644 index 00000000..d324c43b --- /dev/null +++ b/vendor/dario.cat/mergo/.travis.yml @@ -0,0 +1,12 @@ +language: go +arch: + - amd64 + - ppc64le +install: + - go get -t + - go get golang.org/x/tools/cmd/cover + - go get github.com/mattn/goveralls +script: + - go test -race -v ./... +after_script: + - $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN diff --git a/vendor/dario.cat/mergo/CODE_OF_CONDUCT.md b/vendor/dario.cat/mergo/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..469b4490 --- /dev/null +++ b/vendor/dario.cat/mergo/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/dario.cat/mergo/CONTRIBUTING.md b/vendor/dario.cat/mergo/CONTRIBUTING.md new file mode 100644 index 00000000..0a1ff9f9 --- /dev/null +++ b/vendor/dario.cat/mergo/CONTRIBUTING.md @@ -0,0 +1,112 @@ + +# Contributing to mergo + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 + +> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [I Have a Question](#i-have-a-question) +- [I Want To Contribute](#i-want-to-contribute) +- [Reporting Bugs](#reporting-bugs) +- [Suggesting Enhancements](#suggesting-enhancements) + +## Code of Conduct + +This project and everyone participating in it is governed by the +[mergo Code of Conduct](https://github.com/imdario/mergoblob/master/CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report unacceptable behavior +to <>. + + +## I Have a Question + +> If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/imdario/mergo). + +Before you ask a question, it is best to search for existing [Issues](https://github.com/imdario/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. + +If you then still feel the need to ask a question and need clarification, we recommend the following: + +- Open an [Issue](https://github.com/imdario/mergo/issues/new). +- Provide as much context as you can about what you're running into. +- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. + +We will then take care of the issue as soon as possible. + +## I Want To Contribute + +> ### Legal Notice +> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. + +### Reporting Bugs + + +#### Before Submitting a Bug Report + +A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. + +- Make sure that you are using the latest version. +- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)). +- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/imdario/mergoissues?q=label%3Abug). +- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. +- Collect information about the bug: +- Stack trace (Traceback) +- OS, Platform and Version (Windows, Linux, macOS, x86, ARM) +- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. +- Possibly your input and the output +- Can you reliably reproduce the issue? And can you also reproduce it with older versions? + + +#### How Do I Submit a Good Bug Report? + +> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . + + +We use GitHub issues to track bugs and errors. If you run into an issue with the project: + +- Open an [Issue](https://github.com/imdario/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) +- Explain the behavior you would expect and the actual behavior. +- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. +- Provide the information you collected in the previous section. + +Once it's filed: + +- The project team will label the issue accordingly. +- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. +- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone. + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. + + +#### Before Submitting an Enhancement + +- Make sure that you are using the latest version. +- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration. +- Perform a [search](https://github.com/imdario/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. + + +#### How Do I Submit a Good Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://github.com/imdario/mergo/issues). + +- Use a **clear and descriptive title** for the issue to identify the suggestion. +- Provide a **step-by-step description of the suggested enhancement** in as many details as possible. +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. +- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. +- **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration. + + +## Attribution +This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)! diff --git a/vendor/dario.cat/mergo/FUNDING.json b/vendor/dario.cat/mergo/FUNDING.json new file mode 100644 index 00000000..0585e1fe --- /dev/null +++ b/vendor/dario.cat/mergo/FUNDING.json @@ -0,0 +1,7 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0x6160020e7102237aC41bdb156e94401692D76930" + } + } +} diff --git a/vendor/dario.cat/mergo/LICENSE b/vendor/dario.cat/mergo/LICENSE new file mode 100644 index 00000000..68668029 --- /dev/null +++ b/vendor/dario.cat/mergo/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2013 Dario Castañé. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/dario.cat/mergo/README.md b/vendor/dario.cat/mergo/README.md new file mode 100644 index 00000000..0e4a59af --- /dev/null +++ b/vendor/dario.cat/mergo/README.md @@ -0,0 +1,253 @@ +# Mergo + +[![GitHub release][5]][6] +[![GoCard][7]][8] +[![Test status][1]][2] +[![OpenSSF Scorecard][21]][22] +[![OpenSSF Best Practices][19]][20] +[![Coverage status][9]][10] +[![Sourcegraph][11]][12] +[![FOSSA status][13]][14] + +[![GoDoc][3]][4] +[![Become my sponsor][15]][16] +[![Tidelift][17]][18] + +[1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master +[2]: https://github.com/imdario/mergo/actions/workflows/tests.yml +[3]: https://godoc.org/github.com/imdario/mergo?status.svg +[4]: https://godoc.org/github.com/imdario/mergo +[5]: https://img.shields.io/github/release/imdario/mergo.svg +[6]: https://github.com/imdario/mergo/releases +[7]: https://goreportcard.com/badge/imdario/mergo +[8]: https://goreportcard.com/report/github.com/imdario/mergo +[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master +[10]: https://coveralls.io/github/imdario/mergo?branch=master +[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg +[12]: https://sourcegraph.com/github.com/imdario/mergo?badge +[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield +[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield +[15]: https://img.shields.io/github/sponsors/imdario +[16]: https://github.com/sponsors/imdario +[17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo +[18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo +[19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge +[20]: https://bestpractices.coreinfrastructure.org/projects/7177 +[21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge +[22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo + +A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. + +Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). + +Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche. + +## Status + +Mergo is stable and frozen, ready for production. Check a short list of the projects using at large scale it [here](https://github.com/imdario/mergo#mergo-in-the-wild). + +No new features are accepted. They will be considered for a future v2 that improves the implementation and fixes bugs for corner cases. + +### Important notes + +#### 1.0.0 + +In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`. No more v1 versions will be released. + +If the vanity URL is causing issues in your project due to a dependency pulling Mergo - it isn't a direct dependency in your project - it is recommended to use [replace](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) to pin the version to the last one with the old import URL: + +``` +replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 +``` + +#### 0.3.9 + +Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules. + +Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code. + +If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u dario.cat/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). + +### Donations + +If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes: + +Donate using Liberapay +Become my sponsor + +### Mergo in the wild + +Mergo is used by [thousands](https://deps.dev/go/dario.cat%2Fmergo/v1.0.0/dependents) [of](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.16/dependents) [projects](https://deps.dev/go/github.com%2Fimdario%2Fmergo/v0.3.12), including: + +* [containerd/containerd](https://github.com/containerd/containerd) +* [datadog/datadog-agent](https://github.com/datadog/datadog-agent) +* [docker/cli/](https://github.com/docker/cli/) +* [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser) +* [go-micro/go-micro](https://github.com/go-micro/go-micro) +* [grafana/loki](https://github.com/grafana/loki) +* [masterminds/sprig](github.com/Masterminds/sprig) +* [moby/moby](https://github.com/moby/moby) +* [slackhq/nebula](https://github.com/slackhq/nebula) +* [volcano-sh/volcano](https://github.com/volcano-sh/volcano) + +## Install + + go get dario.cat/mergo + + // use in your .go code + import ( + "dario.cat/mergo" + ) + +## Usage + +You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). + +```go +if err := mergo.Merge(&dst, src); err != nil { + // ... +} +``` + +Also, you can merge overwriting values using the transformer `WithOverride`. + +```go +if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { + // ... +} +``` + +If you need to override pointers, so the source pointer's value is assigned to the destination's pointer, you must use `WithoutDereference`: + +```go +package main + +import ( + "fmt" + + "dario.cat/mergo" +) + +type Foo struct { + A *string + B int64 +} + +func main() { + first := "first" + second := "second" + src := Foo{ + A: &first, + B: 2, + } + + dest := Foo{ + A: &second, + B: 1, + } + + mergo.Merge(&dest, src, mergo.WithOverride, mergo.WithoutDereference) +} +``` + +Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field. + +```go +if err := mergo.Map(&dst, srcMap); err != nil { + // ... +} +``` + +Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values. + +Here is a nice example: + +```go +package main + +import ( + "fmt" + "dario.cat/mergo" +) + +type Foo struct { + A string + B int64 +} + +func main() { + src := Foo{ + A: "one", + B: 2, + } + dest := Foo{ + A: "two", + } + mergo.Merge(&dest, src) + fmt.Println(dest) + // Will print + // {two 2} +} +``` + +### Transformers + +Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`? + +```go +package main + +import ( + "fmt" + "dario.cat/mergo" + "reflect" + "time" +) + +type timeTransformer struct { +} + +func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { + if typ == reflect.TypeOf(time.Time{}) { + return func(dst, src reflect.Value) error { + if dst.CanSet() { + isZero := dst.MethodByName("IsZero") + result := isZero.Call([]reflect.Value{}) + if result[0].Bool() { + dst.Set(src) + } + } + return nil + } + } + return nil +} + +type Snapshot struct { + Time time.Time + // ... +} + +func main() { + src := Snapshot{time.Now()} + dest := Snapshot{} + mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) + fmt.Println(dest) + // Will print + // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } +} +``` + +## Contact me + +If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario) + +## About + +Written by [Dario Castañé](http://dario.im). + +## License + +[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE). + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large) diff --git a/vendor/dario.cat/mergo/SECURITY.md b/vendor/dario.cat/mergo/SECURITY.md new file mode 100644 index 00000000..3788fcc1 --- /dev/null +++ b/vendor/dario.cat/mergo/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 1.x.x | :white_check_mark: | +| < 1.0 | :x: | + +## Security contact information + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. diff --git a/vendor/dario.cat/mergo/doc.go b/vendor/dario.cat/mergo/doc.go new file mode 100644 index 00000000..7d96ec05 --- /dev/null +++ b/vendor/dario.cat/mergo/doc.go @@ -0,0 +1,148 @@ +// Copyright 2013 Dario Castañé. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. + +Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). + +# Status + +It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc. + +# Important notes + +1.0.0 + +In 1.0.0 Mergo moves to a vanity URL `dario.cat/mergo`. + +0.3.9 + +Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules. + +Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code. + +If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u dario.cat/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). + +# Install + +Do your usual installation procedure: + + go get dario.cat/mergo + + // use in your .go code + import ( + "dario.cat/mergo" + ) + +# Usage + +You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). + + if err := mergo.Merge(&dst, src); err != nil { + // ... + } + +Also, you can merge overwriting values using the transformer WithOverride. + + if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { + // ... + } + +Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field. + + if err := mergo.Map(&dst, srcMap); err != nil { + // ... + } + +Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values. + +Here is a nice example: + + package main + + import ( + "fmt" + "dario.cat/mergo" + ) + + type Foo struct { + A string + B int64 + } + + func main() { + src := Foo{ + A: "one", + B: 2, + } + dest := Foo{ + A: "two", + } + mergo.Merge(&dest, src) + fmt.Println(dest) + // Will print + // {two 2} + } + +# Transformers + +Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time? + + package main + + import ( + "fmt" + "dario.cat/mergo" + "reflect" + "time" + ) + + type timeTransformer struct { + } + + func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { + if typ == reflect.TypeOf(time.Time{}) { + return func(dst, src reflect.Value) error { + if dst.CanSet() { + isZero := dst.MethodByName("IsZero") + result := isZero.Call([]reflect.Value{}) + if result[0].Bool() { + dst.Set(src) + } + } + return nil + } + } + return nil + } + + type Snapshot struct { + Time time.Time + // ... + } + + func main() { + src := Snapshot{time.Now()} + dest := Snapshot{} + mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{})) + fmt.Println(dest) + // Will print + // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } + } + +# Contact me + +If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario + +# About + +Written by Dario Castañé: https://da.rio.hn + +# License + +BSD 3-Clause license, as Go language. +*/ +package mergo diff --git a/vendor/dario.cat/mergo/map.go b/vendor/dario.cat/mergo/map.go new file mode 100644 index 00000000..759b4f74 --- /dev/null +++ b/vendor/dario.cat/mergo/map.go @@ -0,0 +1,178 @@ +// Copyright 2014 Dario Castañé. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Based on src/pkg/reflect/deepequal.go from official +// golang's stdlib. + +package mergo + +import ( + "fmt" + "reflect" + "unicode" + "unicode/utf8" +) + +func changeInitialCase(s string, mapper func(rune) rune) string { + if s == "" { + return s + } + r, n := utf8.DecodeRuneInString(s) + return string(mapper(r)) + s[n:] +} + +func isExported(field reflect.StructField) bool { + r, _ := utf8.DecodeRuneInString(field.Name) + return r >= 'A' && r <= 'Z' +} + +// Traverses recursively both values, assigning src's fields values to dst. +// The map argument tracks comparisons that have already been seen, which allows +// short circuiting on recursive types. +func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { + overwrite := config.Overwrite + if dst.CanAddr() { + addr := dst.UnsafeAddr() + h := 17 * addr + seen := visited[h] + typ := dst.Type() + for p := seen; p != nil; p = p.next { + if p.ptr == addr && p.typ == typ { + return nil + } + } + // Remember, remember... + visited[h] = &visit{typ, seen, addr} + } + zeroValue := reflect.Value{} + switch dst.Kind() { + case reflect.Map: + dstMap := dst.Interface().(map[string]interface{}) + for i, n := 0, src.NumField(); i < n; i++ { + srcType := src.Type() + field := srcType.Field(i) + if !isExported(field) { + continue + } + fieldName := field.Name + fieldName = changeInitialCase(fieldName, unicode.ToLower) + if _, ok := dstMap[fieldName]; !ok || (!isEmptyValue(reflect.ValueOf(src.Field(i).Interface()), !config.ShouldNotDereference) && overwrite) || config.overwriteWithEmptyValue { + dstMap[fieldName] = src.Field(i).Interface() + } + } + case reflect.Ptr: + if dst.IsNil() { + v := reflect.New(dst.Type().Elem()) + dst.Set(v) + } + dst = dst.Elem() + fallthrough + case reflect.Struct: + srcMap := src.Interface().(map[string]interface{}) + for key := range srcMap { + config.overwriteWithEmptyValue = true + srcValue := srcMap[key] + fieldName := changeInitialCase(key, unicode.ToUpper) + dstElement := dst.FieldByName(fieldName) + if dstElement == zeroValue { + // We discard it because the field doesn't exist. + continue + } + srcElement := reflect.ValueOf(srcValue) + dstKind := dstElement.Kind() + srcKind := srcElement.Kind() + if srcKind == reflect.Ptr && dstKind != reflect.Ptr { + srcElement = srcElement.Elem() + srcKind = reflect.TypeOf(srcElement.Interface()).Kind() + } else if dstKind == reflect.Ptr { + // Can this work? I guess it can't. + if srcKind != reflect.Ptr && srcElement.CanAddr() { + srcPtr := srcElement.Addr() + srcElement = reflect.ValueOf(srcPtr) + srcKind = reflect.Ptr + } + } + + if !srcElement.IsValid() { + continue + } + if srcKind == dstKind { + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface { + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else if srcKind == reflect.Map { + if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else { + return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) + } + } + } + return +} + +// Map sets fields' values in dst from src. +// src can be a map with string keys or a struct. dst must be the opposite: +// if src is a map, dst must be a valid pointer to struct. If src is a struct, +// dst must be map[string]interface{}. +// It won't merge unexported (private) fields and will do recursively +// any exported field. +// If dst is a map, keys will be src fields' names in lower camel case. +// Missing key in src that doesn't match a field in dst will be skipped. This +// doesn't apply if dst is a map. +// This is separated method from Merge because it is cleaner and it keeps sane +// semantics: merging equal types, mapping different (restricted) types. +func Map(dst, src interface{}, opts ...func(*Config)) error { + return _map(dst, src, opts...) +} + +// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by +// non-empty src attribute values. +// Deprecated: Use Map(…) with WithOverride +func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { + return _map(dst, src, append(opts, WithOverride)...) +} + +func _map(dst, src interface{}, opts ...func(*Config)) error { + if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { + return ErrNonPointerArgument + } + var ( + vDst, vSrc reflect.Value + err error + ) + config := &Config{} + + for _, opt := range opts { + opt(config) + } + + if vDst, vSrc, err = resolveValues(dst, src); err != nil { + return err + } + // To be friction-less, we redirect equal-type arguments + // to deepMerge. Only because arguments can be anything. + if vSrc.Kind() == vDst.Kind() { + return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) + } + switch vSrc.Kind() { + case reflect.Struct: + if vDst.Kind() != reflect.Map { + return ErrExpectedMapAsDestination + } + case reflect.Map: + if vDst.Kind() != reflect.Struct { + return ErrExpectedStructAsDestination + } + default: + return ErrNotSupported + } + return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config) +} diff --git a/vendor/dario.cat/mergo/merge.go b/vendor/dario.cat/mergo/merge.go new file mode 100644 index 00000000..fd47c95b --- /dev/null +++ b/vendor/dario.cat/mergo/merge.go @@ -0,0 +1,409 @@ +// Copyright 2013 Dario Castañé. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Based on src/pkg/reflect/deepequal.go from official +// golang's stdlib. + +package mergo + +import ( + "fmt" + "reflect" +) + +func hasMergeableFields(dst reflect.Value) (exported bool) { + for i, n := 0, dst.NumField(); i < n; i++ { + field := dst.Type().Field(i) + if field.Anonymous && dst.Field(i).Kind() == reflect.Struct { + exported = exported || hasMergeableFields(dst.Field(i)) + } else if isExportedComponent(&field) { + exported = exported || len(field.PkgPath) == 0 + } + } + return +} + +func isExportedComponent(field *reflect.StructField) bool { + pkgPath := field.PkgPath + if len(pkgPath) > 0 { + return false + } + c := field.Name[0] + if 'a' <= c && c <= 'z' || c == '_' { + return false + } + return true +} + +type Config struct { + Transformers Transformers + Overwrite bool + ShouldNotDereference bool + AppendSlice bool + TypeCheck bool + overwriteWithEmptyValue bool + overwriteSliceWithEmptyValue bool + sliceDeepCopy bool + debug bool +} + +type Transformers interface { + Transformer(reflect.Type) func(dst, src reflect.Value) error +} + +// Traverses recursively both values, assigning src's fields values to dst. +// The map argument tracks comparisons that have already been seen, which allows +// short circuiting on recursive types. +func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { + overwrite := config.Overwrite + typeCheck := config.TypeCheck + overwriteWithEmptySrc := config.overwriteWithEmptyValue + overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue + sliceDeepCopy := config.sliceDeepCopy + + if !src.IsValid() { + return + } + if dst.CanAddr() { + addr := dst.UnsafeAddr() + h := 17 * addr + seen := visited[h] + typ := dst.Type() + for p := seen; p != nil; p = p.next { + if p.ptr == addr && p.typ == typ { + return nil + } + } + // Remember, remember... + visited[h] = &visit{typ, seen, addr} + } + + if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() { + if fn := config.Transformers.Transformer(dst.Type()); fn != nil { + err = fn(dst, src) + return + } + } + + switch dst.Kind() { + case reflect.Struct: + if hasMergeableFields(dst) { + for i, n := 0, dst.NumField(); i < n; i++ { + if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil { + return + } + } + } else { + if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) { + dst.Set(src) + } + } + case reflect.Map: + if dst.IsNil() && !src.IsNil() { + if dst.CanSet() { + dst.Set(reflect.MakeMap(dst.Type())) + } else { + dst = src + return + } + } + + if src.Kind() != reflect.Map { + if overwrite && dst.CanSet() { + dst.Set(src) + } + return + } + + for _, key := range src.MapKeys() { + srcElement := src.MapIndex(key) + if !srcElement.IsValid() { + continue + } + dstElement := dst.MapIndex(key) + switch srcElement.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice: + if srcElement.IsNil() { + if overwrite { + dst.SetMapIndex(key, srcElement) + } + continue + } + fallthrough + default: + if !srcElement.CanInterface() { + continue + } + switch reflect.TypeOf(srcElement.Interface()).Kind() { + case reflect.Struct: + fallthrough + case reflect.Ptr: + fallthrough + case reflect.Map: + srcMapElm := srcElement + dstMapElm := dstElement + if srcMapElm.CanInterface() { + srcMapElm = reflect.ValueOf(srcMapElm.Interface()) + if dstMapElm.IsValid() { + dstMapElm = reflect.ValueOf(dstMapElm.Interface()) + } + } + if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil { + return + } + case reflect.Slice: + srcSlice := reflect.ValueOf(srcElement.Interface()) + + var dstSlice reflect.Value + if !dstElement.IsValid() || dstElement.IsNil() { + dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len()) + } else { + dstSlice = reflect.ValueOf(dstElement.Interface()) + } + + if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { + if typeCheck && srcSlice.Type() != dstSlice.Type() { + return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) + } + dstSlice = srcSlice + } else if config.AppendSlice { + if srcSlice.Type() != dstSlice.Type() { + return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) + } + dstSlice = reflect.AppendSlice(dstSlice, srcSlice) + } else if sliceDeepCopy { + i := 0 + for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ { + srcElement := srcSlice.Index(i) + dstElement := dstSlice.Index(i) + + if srcElement.CanInterface() { + srcElement = reflect.ValueOf(srcElement.Interface()) + } + if dstElement.CanInterface() { + dstElement = reflect.ValueOf(dstElement.Interface()) + } + + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } + + } + dst.SetMapIndex(key, dstSlice) + } + } + + if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) { + if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice { + continue + } + if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map { + continue + } + } + + if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) { + if dst.IsNil() { + dst.Set(reflect.MakeMap(dst.Type())) + } + dst.SetMapIndex(key, srcElement) + } + } + + // Ensure that all keys in dst are deleted if they are not in src. + if overwriteWithEmptySrc { + for _, key := range dst.MapKeys() { + srcElement := src.MapIndex(key) + if !srcElement.IsValid() { + dst.SetMapIndex(key, reflect.Value{}) + } + } + } + case reflect.Slice: + if !dst.CanSet() { + break + } + if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy { + dst.Set(src) + } else if config.AppendSlice { + if src.Type() != dst.Type() { + return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type()) + } + dst.Set(reflect.AppendSlice(dst, src)) + } else if sliceDeepCopy { + for i := 0; i < src.Len() && i < dst.Len(); i++ { + srcElement := src.Index(i) + dstElement := dst.Index(i) + if srcElement.CanInterface() { + srcElement = reflect.ValueOf(srcElement.Interface()) + } + if dstElement.CanInterface() { + dstElement = reflect.ValueOf(dstElement.Interface()) + } + + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } + } + case reflect.Ptr: + fallthrough + case reflect.Interface: + if isReflectNil(src) { + if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) { + dst.Set(src) + } + break + } + + if src.Kind() != reflect.Interface { + if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) { + if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { + dst.Set(src) + } + } else if src.Kind() == reflect.Ptr { + if !config.ShouldNotDereference { + if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { + return + } + } else if src.Elem().Kind() != reflect.Struct { + if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() { + dst.Set(src) + } + } + } else if dst.Elem().Type() == src.Type() { + if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { + return + } + } else { + return ErrDifferentArgumentsTypes + } + break + } + + if dst.IsNil() || overwrite { + if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) { + dst.Set(src) + } + break + } + + if dst.Elem().Kind() == src.Elem().Kind() { + if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { + return + } + break + } + default: + mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) + if mustSet { + if dst.CanSet() { + dst.Set(src) + } else { + dst = src + } + } + } + + return +} + +// Merge will fill any empty for value type attributes on the dst struct using corresponding +// src attributes if they themselves are not empty. dst and src must be valid same-type structs +// and dst must be a pointer to struct. +// It won't merge unexported (private) fields and will do recursively any exported field. +func Merge(dst, src interface{}, opts ...func(*Config)) error { + return merge(dst, src, opts...) +} + +// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by +// non-empty src attribute values. +// Deprecated: use Merge(…) with WithOverride +func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { + return merge(dst, src, append(opts, WithOverride)...) +} + +// WithTransformers adds transformers to merge, allowing to customize the merging of some types. +func WithTransformers(transformers Transformers) func(*Config) { + return func(config *Config) { + config.Transformers = transformers + } +} + +// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values. +func WithOverride(config *Config) { + config.Overwrite = true +} + +// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values. +func WithOverwriteWithEmptyValue(config *Config) { + config.Overwrite = true + config.overwriteWithEmptyValue = true +} + +// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice. +func WithOverrideEmptySlice(config *Config) { + config.overwriteSliceWithEmptyValue = true +} + +// WithoutDereference prevents dereferencing pointers when evaluating whether they are empty +// (i.e. a non-nil pointer is never considered empty). +func WithoutDereference(config *Config) { + config.ShouldNotDereference = true +} + +// WithAppendSlice will make merge append slices instead of overwriting it. +func WithAppendSlice(config *Config) { + config.AppendSlice = true +} + +// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride). +func WithTypeCheck(config *Config) { + config.TypeCheck = true +} + +// WithSliceDeepCopy will merge slice element one by one with Overwrite flag. +func WithSliceDeepCopy(config *Config) { + config.sliceDeepCopy = true + config.Overwrite = true +} + +func merge(dst, src interface{}, opts ...func(*Config)) error { + if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { + return ErrNonPointerArgument + } + var ( + vDst, vSrc reflect.Value + err error + ) + + config := &Config{} + + for _, opt := range opts { + opt(config) + } + + if vDst, vSrc, err = resolveValues(dst, src); err != nil { + return err + } + if vDst.Type() != vSrc.Type() { + return ErrDifferentArgumentsTypes + } + return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) +} + +// IsReflectNil is the reflect value provided nil +func isReflectNil(v reflect.Value) bool { + k := v.Kind() + switch k { + case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr: + // Both interface and slice are nil if first word is 0. + // Both are always bigger than a word; assume flagIndir. + return v.IsNil() + default: + return false + } +} diff --git a/vendor/dario.cat/mergo/mergo.go b/vendor/dario.cat/mergo/mergo.go new file mode 100644 index 00000000..0a721e2d --- /dev/null +++ b/vendor/dario.cat/mergo/mergo.go @@ -0,0 +1,81 @@ +// Copyright 2013 Dario Castañé. All rights reserved. +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Based on src/pkg/reflect/deepequal.go from official +// golang's stdlib. + +package mergo + +import ( + "errors" + "reflect" +) + +// Errors reported by Mergo when it finds invalid arguments. +var ( + ErrNilArguments = errors.New("src and dst must not be nil") + ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type") + ErrNotSupported = errors.New("only structs, maps, and slices are supported") + ErrExpectedMapAsDestination = errors.New("dst was expected to be a map") + ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct") + ErrNonPointerArgument = errors.New("dst must be a pointer") +) + +// During deepMerge, must keep track of checks that are +// in progress. The comparison algorithm assumes that all +// checks in progress are true when it reencounters them. +// Visited are stored in a map indexed by 17 * a1 + a2; +type visit struct { + typ reflect.Type + next *visit + ptr uintptr +} + +// From src/pkg/encoding/json/encode.go. +func isEmptyValue(v reflect.Value, shouldDereference bool) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + if v.IsNil() { + return true + } + if shouldDereference { + return isEmptyValue(v.Elem(), shouldDereference) + } + return false + case reflect.Func: + return v.IsNil() + case reflect.Invalid: + return true + } + return false +} + +func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) { + if dst == nil || src == nil { + err = ErrNilArguments + return + } + vDst = reflect.ValueOf(dst).Elem() + if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map && vDst.Kind() != reflect.Slice { + err = ErrNotSupported + return + } + vSrc = reflect.ValueOf(src) + // We check if vSrc is a pointer to dereference it. + if vSrc.Kind() == reflect.Ptr { + vSrc = vSrc.Elem() + } + return +} diff --git a/vendor/github.com/Azure/go-ansiterm/LICENSE b/vendor/github.com/Azure/go-ansiterm/LICENSE new file mode 100644 index 00000000..e3d9a64d --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/Azure/go-ansiterm/README.md b/vendor/github.com/Azure/go-ansiterm/README.md new file mode 100644 index 00000000..261c041e --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/README.md @@ -0,0 +1,12 @@ +# go-ansiterm + +This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent. + +For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position. + +The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go). + +See parser_test.go for examples exercising the state machine and generating appropriate function calls. + +----- +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/vendor/github.com/Azure/go-ansiterm/SECURITY.md b/vendor/github.com/Azure/go-ansiterm/SECURITY.md new file mode 100644 index 00000000..e138ec5d --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/vendor/github.com/Azure/go-ansiterm/constants.go b/vendor/github.com/Azure/go-ansiterm/constants.go new file mode 100644 index 00000000..96504a33 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/constants.go @@ -0,0 +1,188 @@ +package ansiterm + +const LogEnv = "DEBUG_TERMINAL" + +// ANSI constants +// References: +// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm +// -- http://man7.org/linux/man-pages/man4/console_codes.4.html +// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html +// -- http://en.wikipedia.org/wiki/ANSI_escape_code +// -- http://vt100.net/emu/dec_ansi_parser +// -- http://vt100.net/emu/vt500_parser.svg +// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html +// -- http://www.inwap.com/pdp10/ansicode.txt +const ( + // ECMA-48 Set Graphics Rendition + // Note: + // -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved + // -- Fonts could possibly be supported via SetCurrentConsoleFontEx + // -- Windows does not expose the per-window cursor (i.e., caret) blink times + ANSI_SGR_RESET = 0 + ANSI_SGR_BOLD = 1 + ANSI_SGR_DIM = 2 + _ANSI_SGR_ITALIC = 3 + ANSI_SGR_UNDERLINE = 4 + _ANSI_SGR_BLINKSLOW = 5 + _ANSI_SGR_BLINKFAST = 6 + ANSI_SGR_REVERSE = 7 + _ANSI_SGR_INVISIBLE = 8 + _ANSI_SGR_LINETHROUGH = 9 + _ANSI_SGR_FONT_00 = 10 + _ANSI_SGR_FONT_01 = 11 + _ANSI_SGR_FONT_02 = 12 + _ANSI_SGR_FONT_03 = 13 + _ANSI_SGR_FONT_04 = 14 + _ANSI_SGR_FONT_05 = 15 + _ANSI_SGR_FONT_06 = 16 + _ANSI_SGR_FONT_07 = 17 + _ANSI_SGR_FONT_08 = 18 + _ANSI_SGR_FONT_09 = 19 + _ANSI_SGR_FONT_10 = 20 + _ANSI_SGR_DOUBLEUNDERLINE = 21 + ANSI_SGR_BOLD_DIM_OFF = 22 + _ANSI_SGR_ITALIC_OFF = 23 + ANSI_SGR_UNDERLINE_OFF = 24 + _ANSI_SGR_BLINK_OFF = 25 + _ANSI_SGR_RESERVED_00 = 26 + ANSI_SGR_REVERSE_OFF = 27 + _ANSI_SGR_INVISIBLE_OFF = 28 + _ANSI_SGR_LINETHROUGH_OFF = 29 + ANSI_SGR_FOREGROUND_BLACK = 30 + ANSI_SGR_FOREGROUND_RED = 31 + ANSI_SGR_FOREGROUND_GREEN = 32 + ANSI_SGR_FOREGROUND_YELLOW = 33 + ANSI_SGR_FOREGROUND_BLUE = 34 + ANSI_SGR_FOREGROUND_MAGENTA = 35 + ANSI_SGR_FOREGROUND_CYAN = 36 + ANSI_SGR_FOREGROUND_WHITE = 37 + _ANSI_SGR_RESERVED_01 = 38 + ANSI_SGR_FOREGROUND_DEFAULT = 39 + ANSI_SGR_BACKGROUND_BLACK = 40 + ANSI_SGR_BACKGROUND_RED = 41 + ANSI_SGR_BACKGROUND_GREEN = 42 + ANSI_SGR_BACKGROUND_YELLOW = 43 + ANSI_SGR_BACKGROUND_BLUE = 44 + ANSI_SGR_BACKGROUND_MAGENTA = 45 + ANSI_SGR_BACKGROUND_CYAN = 46 + ANSI_SGR_BACKGROUND_WHITE = 47 + _ANSI_SGR_RESERVED_02 = 48 + ANSI_SGR_BACKGROUND_DEFAULT = 49 + // 50 - 65: Unsupported + + ANSI_MAX_CMD_LENGTH = 4096 + + MAX_INPUT_EVENTS = 128 + DEFAULT_WIDTH = 80 + DEFAULT_HEIGHT = 24 + + ANSI_BEL = 0x07 + ANSI_BACKSPACE = 0x08 + ANSI_TAB = 0x09 + ANSI_LINE_FEED = 0x0A + ANSI_VERTICAL_TAB = 0x0B + ANSI_FORM_FEED = 0x0C + ANSI_CARRIAGE_RETURN = 0x0D + ANSI_ESCAPE_PRIMARY = 0x1B + ANSI_ESCAPE_SECONDARY = 0x5B + ANSI_OSC_STRING_ENTRY = 0x5D + ANSI_COMMAND_FIRST = 0x40 + ANSI_COMMAND_LAST = 0x7E + DCS_ENTRY = 0x90 + CSI_ENTRY = 0x9B + OSC_STRING = 0x9D + ANSI_PARAMETER_SEP = ";" + ANSI_CMD_G0 = '(' + ANSI_CMD_G1 = ')' + ANSI_CMD_G2 = '*' + ANSI_CMD_G3 = '+' + ANSI_CMD_DECPNM = '>' + ANSI_CMD_DECPAM = '=' + ANSI_CMD_OSC = ']' + ANSI_CMD_STR_TERM = '\\' + + KEY_CONTROL_PARAM_2 = ";2" + KEY_CONTROL_PARAM_3 = ";3" + KEY_CONTROL_PARAM_4 = ";4" + KEY_CONTROL_PARAM_5 = ";5" + KEY_CONTROL_PARAM_6 = ";6" + KEY_CONTROL_PARAM_7 = ";7" + KEY_CONTROL_PARAM_8 = ";8" + KEY_ESC_CSI = "\x1B[" + KEY_ESC_N = "\x1BN" + KEY_ESC_O = "\x1BO" + + FILL_CHARACTER = ' ' +) + +func getByteRange(start byte, end byte) []byte { + bytes := make([]byte, 0, 32) + for i := start; i <= end; i++ { + bytes = append(bytes, byte(i)) + } + + return bytes +} + +var toGroundBytes = getToGroundBytes() +var executors = getExecuteBytes() + +// SPACE 20+A0 hex Always and everywhere a blank space +// Intermediate 20-2F hex !"#$%&'()*+,-./ +var intermeds = getByteRange(0x20, 0x2F) + +// Parameters 30-3F hex 0123456789:;<=>? +// CSI Parameters 30-39, 3B hex 0123456789; +var csiParams = getByteRange(0x30, 0x3F) + +var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...) + +// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ +var upperCase = getByteRange(0x40, 0x5F) + +// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~ +var lowerCase = getByteRange(0x60, 0x7E) + +// Alphabetics 40-7E hex (all of upper and lower case) +var alphabetics = append(upperCase, lowerCase...) + +var printables = getByteRange(0x20, 0x7F) + +var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E) +var escapeToGroundBytes = getEscapeToGroundBytes() + +// See http://www.vt100.net/emu/vt500_parser.png for description of the complex +// byte ranges below + +func getEscapeToGroundBytes() []byte { + escapeToGroundBytes := getByteRange(0x30, 0x4F) + escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...) + escapeToGroundBytes = append(escapeToGroundBytes, 0x59) + escapeToGroundBytes = append(escapeToGroundBytes, 0x5A) + escapeToGroundBytes = append(escapeToGroundBytes, 0x5C) + escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...) + return escapeToGroundBytes +} + +func getExecuteBytes() []byte { + executeBytes := getByteRange(0x00, 0x17) + executeBytes = append(executeBytes, 0x19) + executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...) + return executeBytes +} + +func getToGroundBytes() []byte { + groundBytes := []byte{0x18} + groundBytes = append(groundBytes, 0x1A) + groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...) + groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...) + groundBytes = append(groundBytes, 0x99) + groundBytes = append(groundBytes, 0x9A) + groundBytes = append(groundBytes, 0x9C) + return groundBytes +} + +// Delete 7F hex Always and everywhere ignored +// C1 Control 80-9F hex 32 additional control characters +// G1 Displayable A1-FE hex 94 additional displayable characters +// Special A0+FF hex Same as SPACE and DELETE diff --git a/vendor/github.com/Azure/go-ansiterm/context.go b/vendor/github.com/Azure/go-ansiterm/context.go new file mode 100644 index 00000000..8d66e777 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/context.go @@ -0,0 +1,7 @@ +package ansiterm + +type ansiContext struct { + currentChar byte + paramBuffer []byte + interBuffer []byte +} diff --git a/vendor/github.com/Azure/go-ansiterm/csi_entry_state.go b/vendor/github.com/Azure/go-ansiterm/csi_entry_state.go new file mode 100644 index 00000000..bcbe00d0 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/csi_entry_state.go @@ -0,0 +1,49 @@ +package ansiterm + +type csiEntryState struct { + baseState +} + +func (csiState csiEntryState) Handle(b byte) (s state, e error) { + csiState.parser.logf("CsiEntry::Handle %#x", b) + + nextState, err := csiState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case sliceContains(alphabetics, b): + return csiState.parser.ground, nil + case sliceContains(csiCollectables, b): + return csiState.parser.csiParam, nil + case sliceContains(executors, b): + return csiState, csiState.parser.execute() + } + + return csiState, nil +} + +func (csiState csiEntryState) Transition(s state) error { + csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name()) + csiState.baseState.Transition(s) + + switch s { + case csiState.parser.ground: + return csiState.parser.csiDispatch() + case csiState.parser.csiParam: + switch { + case sliceContains(csiParams, csiState.parser.context.currentChar): + csiState.parser.collectParam() + case sliceContains(intermeds, csiState.parser.context.currentChar): + csiState.parser.collectInter() + } + } + + return nil +} + +func (csiState csiEntryState) Enter() error { + csiState.parser.clear() + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/csi_param_state.go b/vendor/github.com/Azure/go-ansiterm/csi_param_state.go new file mode 100644 index 00000000..7ed5e01c --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/csi_param_state.go @@ -0,0 +1,38 @@ +package ansiterm + +type csiParamState struct { + baseState +} + +func (csiState csiParamState) Handle(b byte) (s state, e error) { + csiState.parser.logf("CsiParam::Handle %#x", b) + + nextState, err := csiState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case sliceContains(alphabetics, b): + return csiState.parser.ground, nil + case sliceContains(csiCollectables, b): + csiState.parser.collectParam() + return csiState, nil + case sliceContains(executors, b): + return csiState, csiState.parser.execute() + } + + return csiState, nil +} + +func (csiState csiParamState) Transition(s state) error { + csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name()) + csiState.baseState.Transition(s) + + switch s { + case csiState.parser.ground: + return csiState.parser.csiDispatch() + } + + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go b/vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go new file mode 100644 index 00000000..1c719db9 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go @@ -0,0 +1,36 @@ +package ansiterm + +type escapeIntermediateState struct { + baseState +} + +func (escState escapeIntermediateState) Handle(b byte) (s state, e error) { + escState.parser.logf("escapeIntermediateState::Handle %#x", b) + nextState, err := escState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case sliceContains(intermeds, b): + return escState, escState.parser.collectInter() + case sliceContains(executors, b): + return escState, escState.parser.execute() + case sliceContains(escapeIntermediateToGroundBytes, b): + return escState.parser.ground, nil + } + + return escState, nil +} + +func (escState escapeIntermediateState) Transition(s state) error { + escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name()) + escState.baseState.Transition(s) + + switch s { + case escState.parser.ground: + return escState.parser.escDispatch() + } + + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/escape_state.go b/vendor/github.com/Azure/go-ansiterm/escape_state.go new file mode 100644 index 00000000..6390abd2 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/escape_state.go @@ -0,0 +1,47 @@ +package ansiterm + +type escapeState struct { + baseState +} + +func (escState escapeState) Handle(b byte) (s state, e error) { + escState.parser.logf("escapeState::Handle %#x", b) + nextState, err := escState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case b == ANSI_ESCAPE_SECONDARY: + return escState.parser.csiEntry, nil + case b == ANSI_OSC_STRING_ENTRY: + return escState.parser.oscString, nil + case sliceContains(executors, b): + return escState, escState.parser.execute() + case sliceContains(escapeToGroundBytes, b): + return escState.parser.ground, nil + case sliceContains(intermeds, b): + return escState.parser.escapeIntermediate, nil + } + + return escState, nil +} + +func (escState escapeState) Transition(s state) error { + escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name()) + escState.baseState.Transition(s) + + switch s { + case escState.parser.ground: + return escState.parser.escDispatch() + case escState.parser.escapeIntermediate: + return escState.parser.collectInter() + } + + return nil +} + +func (escState escapeState) Enter() error { + escState.parser.clear() + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/event_handler.go b/vendor/github.com/Azure/go-ansiterm/event_handler.go new file mode 100644 index 00000000..98087b38 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/event_handler.go @@ -0,0 +1,90 @@ +package ansiterm + +type AnsiEventHandler interface { + // Print + Print(b byte) error + + // Execute C0 commands + Execute(b byte) error + + // CUrsor Up + CUU(int) error + + // CUrsor Down + CUD(int) error + + // CUrsor Forward + CUF(int) error + + // CUrsor Backward + CUB(int) error + + // Cursor to Next Line + CNL(int) error + + // Cursor to Previous Line + CPL(int) error + + // Cursor Horizontal position Absolute + CHA(int) error + + // Vertical line Position Absolute + VPA(int) error + + // CUrsor Position + CUP(int, int) error + + // Horizontal and Vertical Position (depends on PUM) + HVP(int, int) error + + // Text Cursor Enable Mode + DECTCEM(bool) error + + // Origin Mode + DECOM(bool) error + + // 132 Column Mode + DECCOLM(bool) error + + // Erase in Display + ED(int) error + + // Erase in Line + EL(int) error + + // Insert Line + IL(int) error + + // Delete Line + DL(int) error + + // Insert Character + ICH(int) error + + // Delete Character + DCH(int) error + + // Set Graphics Rendition + SGR([]int) error + + // Pan Down + SU(int) error + + // Pan Up + SD(int) error + + // Device Attributes + DA([]string) error + + // Set Top and Bottom Margins + DECSTBM(int, int) error + + // Index + IND() error + + // Reverse Index + RI() error + + // Flush updates from previous commands + Flush() error +} diff --git a/vendor/github.com/Azure/go-ansiterm/ground_state.go b/vendor/github.com/Azure/go-ansiterm/ground_state.go new file mode 100644 index 00000000..52451e94 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/ground_state.go @@ -0,0 +1,24 @@ +package ansiterm + +type groundState struct { + baseState +} + +func (gs groundState) Handle(b byte) (s state, e error) { + gs.parser.context.currentChar = b + + nextState, err := gs.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + switch { + case sliceContains(printables, b): + return gs, gs.parser.print() + + case sliceContains(executors, b): + return gs, gs.parser.execute() + } + + return gs, nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/osc_string_state.go b/vendor/github.com/Azure/go-ansiterm/osc_string_state.go new file mode 100644 index 00000000..194d5e9c --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/osc_string_state.go @@ -0,0 +1,23 @@ +package ansiterm + +type oscStringState struct { + baseState +} + +func (oscState oscStringState) Handle(b byte) (s state, e error) { + oscState.parser.logf("OscString::Handle %#x", b) + nextState, err := oscState.baseState.Handle(b) + if nextState != nil || err != nil { + return nextState, err + } + + // There are several control characters and sequences which can + // terminate an OSC string. Most of them are handled by the baseState + // handler. The ANSI_BEL character is a special case which behaves as a + // terminator only for an OSC string. + if b == ANSI_BEL { + return oscState.parser.ground, nil + } + + return oscState, nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/parser.go b/vendor/github.com/Azure/go-ansiterm/parser.go new file mode 100644 index 00000000..03cec7ad --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/parser.go @@ -0,0 +1,151 @@ +package ansiterm + +import ( + "errors" + "log" + "os" +) + +type AnsiParser struct { + currState state + eventHandler AnsiEventHandler + context *ansiContext + csiEntry state + csiParam state + dcsEntry state + escape state + escapeIntermediate state + error state + ground state + oscString state + stateMap []state + + logf func(string, ...interface{}) +} + +type Option func(*AnsiParser) + +func WithLogf(f func(string, ...interface{})) Option { + return func(ap *AnsiParser) { + ap.logf = f + } +} + +func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser { + ap := &AnsiParser{ + eventHandler: evtHandler, + context: &ansiContext{}, + } + for _, o := range opts { + o(ap) + } + + if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" { + logFile, _ := os.Create("ansiParser.log") + logger := log.New(logFile, "", log.LstdFlags) + if ap.logf != nil { + l := ap.logf + ap.logf = func(s string, v ...interface{}) { + l(s, v...) + logger.Printf(s, v...) + } + } else { + ap.logf = logger.Printf + } + } + + if ap.logf == nil { + ap.logf = func(string, ...interface{}) {} + } + + ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}} + ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}} + ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}} + ap.escape = escapeState{baseState{name: "Escape", parser: ap}} + ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}} + ap.error = errorState{baseState{name: "Error", parser: ap}} + ap.ground = groundState{baseState{name: "Ground", parser: ap}} + ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}} + + ap.stateMap = []state{ + ap.csiEntry, + ap.csiParam, + ap.dcsEntry, + ap.escape, + ap.escapeIntermediate, + ap.error, + ap.ground, + ap.oscString, + } + + ap.currState = getState(initialState, ap.stateMap) + + ap.logf("CreateParser: parser %p", ap) + return ap +} + +func getState(name string, states []state) state { + for _, el := range states { + if el.Name() == name { + return el + } + } + + return nil +} + +func (ap *AnsiParser) Parse(bytes []byte) (int, error) { + for i, b := range bytes { + if err := ap.handle(b); err != nil { + return i, err + } + } + + return len(bytes), ap.eventHandler.Flush() +} + +func (ap *AnsiParser) handle(b byte) error { + ap.context.currentChar = b + newState, err := ap.currState.Handle(b) + if err != nil { + return err + } + + if newState == nil { + ap.logf("WARNING: newState is nil") + return errors.New("New state of 'nil' is invalid.") + } + + if newState != ap.currState { + if err := ap.changeState(newState); err != nil { + return err + } + } + + return nil +} + +func (ap *AnsiParser) changeState(newState state) error { + ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name()) + + // Exit old state + if err := ap.currState.Exit(); err != nil { + ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err) + return err + } + + // Perform transition action + if err := ap.currState.Transition(newState); err != nil { + ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err) + return err + } + + // Enter new state + if err := newState.Enter(); err != nil { + ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err) + return err + } + + ap.currState = newState + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go b/vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go new file mode 100644 index 00000000..de0a1f9c --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go @@ -0,0 +1,99 @@ +package ansiterm + +import ( + "strconv" +) + +func parseParams(bytes []byte) ([]string, error) { + paramBuff := make([]byte, 0, 0) + params := []string{} + + for _, v := range bytes { + if v == ';' { + if len(paramBuff) > 0 { + // Completed parameter, append it to the list + s := string(paramBuff) + params = append(params, s) + paramBuff = make([]byte, 0, 0) + } + } else { + paramBuff = append(paramBuff, v) + } + } + + // Last parameter may not be terminated with ';' + if len(paramBuff) > 0 { + s := string(paramBuff) + params = append(params, s) + } + + return params, nil +} + +func parseCmd(context ansiContext) (string, error) { + return string(context.currentChar), nil +} + +func getInt(params []string, dflt int) int { + i := getInts(params, 1, dflt)[0] + return i +} + +func getInts(params []string, minCount int, dflt int) []int { + ints := []int{} + + for _, v := range params { + i, _ := strconv.Atoi(v) + // Zero is mapped to the default value in VT100. + if i == 0 { + i = dflt + } + ints = append(ints, i) + } + + if len(ints) < minCount { + remaining := minCount - len(ints) + for i := 0; i < remaining; i++ { + ints = append(ints, dflt) + } + } + + return ints +} + +func (ap *AnsiParser) modeDispatch(param string, set bool) error { + switch param { + case "?3": + return ap.eventHandler.DECCOLM(set) + case "?6": + return ap.eventHandler.DECOM(set) + case "?25": + return ap.eventHandler.DECTCEM(set) + } + return nil +} + +func (ap *AnsiParser) hDispatch(params []string) error { + if len(params) == 1 { + return ap.modeDispatch(params[0], true) + } + + return nil +} + +func (ap *AnsiParser) lDispatch(params []string) error { + if len(params) == 1 { + return ap.modeDispatch(params[0], false) + } + + return nil +} + +func getEraseParam(params []string) int { + param := getInt(params, 0) + if param < 0 || 3 < param { + param = 0 + } + + return param +} diff --git a/vendor/github.com/Azure/go-ansiterm/parser_actions.go b/vendor/github.com/Azure/go-ansiterm/parser_actions.go new file mode 100644 index 00000000..0bb5e51e --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/parser_actions.go @@ -0,0 +1,119 @@ +package ansiterm + +func (ap *AnsiParser) collectParam() error { + currChar := ap.context.currentChar + ap.logf("collectParam %#x", currChar) + ap.context.paramBuffer = append(ap.context.paramBuffer, currChar) + return nil +} + +func (ap *AnsiParser) collectInter() error { + currChar := ap.context.currentChar + ap.logf("collectInter %#x", currChar) + ap.context.paramBuffer = append(ap.context.interBuffer, currChar) + return nil +} + +func (ap *AnsiParser) escDispatch() error { + cmd, _ := parseCmd(*ap.context) + intermeds := ap.context.interBuffer + ap.logf("escDispatch currentChar: %#x", ap.context.currentChar) + ap.logf("escDispatch: %v(%v)", cmd, intermeds) + + switch cmd { + case "D": // IND + return ap.eventHandler.IND() + case "E": // NEL, equivalent to CRLF + err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN) + if err == nil { + err = ap.eventHandler.Execute(ANSI_LINE_FEED) + } + return err + case "M": // RI + return ap.eventHandler.RI() + } + + return nil +} + +func (ap *AnsiParser) csiDispatch() error { + cmd, _ := parseCmd(*ap.context) + params, _ := parseParams(ap.context.paramBuffer) + ap.logf("Parsed params: %v with length: %d", params, len(params)) + + ap.logf("csiDispatch: %v(%v)", cmd, params) + + switch cmd { + case "@": + return ap.eventHandler.ICH(getInt(params, 1)) + case "A": + return ap.eventHandler.CUU(getInt(params, 1)) + case "B": + return ap.eventHandler.CUD(getInt(params, 1)) + case "C": + return ap.eventHandler.CUF(getInt(params, 1)) + case "D": + return ap.eventHandler.CUB(getInt(params, 1)) + case "E": + return ap.eventHandler.CNL(getInt(params, 1)) + case "F": + return ap.eventHandler.CPL(getInt(params, 1)) + case "G": + return ap.eventHandler.CHA(getInt(params, 1)) + case "H": + ints := getInts(params, 2, 1) + x, y := ints[0], ints[1] + return ap.eventHandler.CUP(x, y) + case "J": + param := getEraseParam(params) + return ap.eventHandler.ED(param) + case "K": + param := getEraseParam(params) + return ap.eventHandler.EL(param) + case "L": + return ap.eventHandler.IL(getInt(params, 1)) + case "M": + return ap.eventHandler.DL(getInt(params, 1)) + case "P": + return ap.eventHandler.DCH(getInt(params, 1)) + case "S": + return ap.eventHandler.SU(getInt(params, 1)) + case "T": + return ap.eventHandler.SD(getInt(params, 1)) + case "c": + return ap.eventHandler.DA(params) + case "d": + return ap.eventHandler.VPA(getInt(params, 1)) + case "f": + ints := getInts(params, 2, 1) + x, y := ints[0], ints[1] + return ap.eventHandler.HVP(x, y) + case "h": + return ap.hDispatch(params) + case "l": + return ap.lDispatch(params) + case "m": + return ap.eventHandler.SGR(getInts(params, 1, 0)) + case "r": + ints := getInts(params, 2, 1) + top, bottom := ints[0], ints[1] + return ap.eventHandler.DECSTBM(top, bottom) + default: + ap.logf("ERROR: Unsupported CSI command: '%s', with full context: %v", cmd, ap.context) + return nil + } + +} + +func (ap *AnsiParser) print() error { + return ap.eventHandler.Print(ap.context.currentChar) +} + +func (ap *AnsiParser) clear() error { + ap.context = &ansiContext{} + return nil +} + +func (ap *AnsiParser) execute() error { + return ap.eventHandler.Execute(ap.context.currentChar) +} diff --git a/vendor/github.com/Azure/go-ansiterm/states.go b/vendor/github.com/Azure/go-ansiterm/states.go new file mode 100644 index 00000000..f2ea1fcd --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/states.go @@ -0,0 +1,71 @@ +package ansiterm + +type stateID int + +type state interface { + Enter() error + Exit() error + Handle(byte) (state, error) + Name() string + Transition(state) error +} + +type baseState struct { + name string + parser *AnsiParser +} + +func (base baseState) Enter() error { + return nil +} + +func (base baseState) Exit() error { + return nil +} + +func (base baseState) Handle(b byte) (s state, e error) { + + switch { + case b == CSI_ENTRY: + return base.parser.csiEntry, nil + case b == DCS_ENTRY: + return base.parser.dcsEntry, nil + case b == ANSI_ESCAPE_PRIMARY: + return base.parser.escape, nil + case b == OSC_STRING: + return base.parser.oscString, nil + case sliceContains(toGroundBytes, b): + return base.parser.ground, nil + } + + return nil, nil +} + +func (base baseState) Name() string { + return base.name +} + +func (base baseState) Transition(s state) error { + if s == base.parser.ground { + execBytes := []byte{0x18} + execBytes = append(execBytes, 0x1A) + execBytes = append(execBytes, getByteRange(0x80, 0x8F)...) + execBytes = append(execBytes, getByteRange(0x91, 0x97)...) + execBytes = append(execBytes, 0x99) + execBytes = append(execBytes, 0x9A) + + if sliceContains(execBytes, base.parser.context.currentChar) { + return base.parser.execute() + } + } + + return nil +} + +type dcsEntryState struct { + baseState +} + +type errorState struct { + baseState +} diff --git a/vendor/github.com/Azure/go-ansiterm/utilities.go b/vendor/github.com/Azure/go-ansiterm/utilities.go new file mode 100644 index 00000000..39211449 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/utilities.go @@ -0,0 +1,21 @@ +package ansiterm + +import ( + "strconv" +) + +func sliceContains(bytes []byte, b byte) bool { + for _, v := range bytes { + if v == b { + return true + } + } + + return false +} + +func convertBytesToInteger(bytes []byte) int { + s := string(bytes) + i, _ := strconv.Atoi(s) + return i +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/ansi.go b/vendor/github.com/Azure/go-ansiterm/winterm/ansi.go new file mode 100644 index 00000000..5599082a --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/ansi.go @@ -0,0 +1,196 @@ +// +build windows + +package winterm + +import ( + "fmt" + "os" + "strconv" + "strings" + "syscall" + + "github.com/Azure/go-ansiterm" + windows "golang.org/x/sys/windows" +) + +// Windows keyboard constants +// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx. +const ( + VK_PRIOR = 0x21 // PAGE UP key + VK_NEXT = 0x22 // PAGE DOWN key + VK_END = 0x23 // END key + VK_HOME = 0x24 // HOME key + VK_LEFT = 0x25 // LEFT ARROW key + VK_UP = 0x26 // UP ARROW key + VK_RIGHT = 0x27 // RIGHT ARROW key + VK_DOWN = 0x28 // DOWN ARROW key + VK_SELECT = 0x29 // SELECT key + VK_PRINT = 0x2A // PRINT key + VK_EXECUTE = 0x2B // EXECUTE key + VK_SNAPSHOT = 0x2C // PRINT SCREEN key + VK_INSERT = 0x2D // INS key + VK_DELETE = 0x2E // DEL key + VK_HELP = 0x2F // HELP key + VK_F1 = 0x70 // F1 key + VK_F2 = 0x71 // F2 key + VK_F3 = 0x72 // F3 key + VK_F4 = 0x73 // F4 key + VK_F5 = 0x74 // F5 key + VK_F6 = 0x75 // F6 key + VK_F7 = 0x76 // F7 key + VK_F8 = 0x77 // F8 key + VK_F9 = 0x78 // F9 key + VK_F10 = 0x79 // F10 key + VK_F11 = 0x7A // F11 key + VK_F12 = 0x7B // F12 key + + RIGHT_ALT_PRESSED = 0x0001 + LEFT_ALT_PRESSED = 0x0002 + RIGHT_CTRL_PRESSED = 0x0004 + LEFT_CTRL_PRESSED = 0x0008 + SHIFT_PRESSED = 0x0010 + NUMLOCK_ON = 0x0020 + SCROLLLOCK_ON = 0x0040 + CAPSLOCK_ON = 0x0080 + ENHANCED_KEY = 0x0100 +) + +type ansiCommand struct { + CommandBytes []byte + Command string + Parameters []string + IsSpecial bool +} + +func newAnsiCommand(command []byte) *ansiCommand { + + if isCharacterSelectionCmdChar(command[1]) { + // Is Character Set Selection commands + return &ansiCommand{ + CommandBytes: command, + Command: string(command), + IsSpecial: true, + } + } + + // last char is command character + lastCharIndex := len(command) - 1 + + ac := &ansiCommand{ + CommandBytes: command, + Command: string(command[lastCharIndex]), + IsSpecial: false, + } + + // more than a single escape + if lastCharIndex != 0 { + start := 1 + // skip if double char escape sequence + if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY { + start++ + } + // convert this to GetNextParam method + ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP) + } + + return ac +} + +func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 { + if index < 0 || index >= len(ac.Parameters) { + return defaultValue + } + + param, err := strconv.ParseInt(ac.Parameters[index], 10, 16) + if err != nil { + return defaultValue + } + + return int16(param) +} + +func (ac *ansiCommand) String() string { + return fmt.Sprintf("0x%v \"%v\" (\"%v\")", + bytesToHex(ac.CommandBytes), + ac.Command, + strings.Join(ac.Parameters, "\",\"")) +} + +// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands. +// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html. +func isAnsiCommandChar(b byte) bool { + switch { + case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY: + return true + case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM: + // non-CSI escape sequence terminator + return true + case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL: + // String escape sequence terminator + return true + } + return false +} + +func isXtermOscSequence(command []byte, current byte) bool { + return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL) +} + +func isCharacterSelectionCmdChar(b byte) bool { + return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3) +} + +// bytesToHex converts a slice of bytes to a human-readable string. +func bytesToHex(b []byte) string { + hex := make([]string, len(b)) + for i, ch := range b { + hex[i] = fmt.Sprintf("%X", ch) + } + return strings.Join(hex, "") +} + +// ensureInRange adjusts the passed value, if necessary, to ensure it is within +// the passed min / max range. +func ensureInRange(n int16, min int16, max int16) int16 { + if n < min { + return min + } else if n > max { + return max + } else { + return n + } +} + +func GetStdFile(nFile int) (*os.File, uintptr) { + var file *os.File + + // syscall uses negative numbers + // windows package uses very big uint32 + // Keep these switches split so we don't have to convert ints too much. + switch uint32(nFile) { + case windows.STD_INPUT_HANDLE: + file = os.Stdin + case windows.STD_OUTPUT_HANDLE: + file = os.Stdout + case windows.STD_ERROR_HANDLE: + file = os.Stderr + default: + switch nFile { + case syscall.STD_INPUT_HANDLE: + file = os.Stdin + case syscall.STD_OUTPUT_HANDLE: + file = os.Stdout + case syscall.STD_ERROR_HANDLE: + file = os.Stderr + default: + panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile)) + } + } + + fd, err := syscall.GetStdHandle(nFile) + if err != nil { + panic(fmt.Errorf("Invalid standard handle identifier: %v -- %v", nFile, err)) + } + + return file, uintptr(fd) +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/api.go b/vendor/github.com/Azure/go-ansiterm/winterm/api.go new file mode 100644 index 00000000..6055e33b --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/api.go @@ -0,0 +1,327 @@ +// +build windows + +package winterm + +import ( + "fmt" + "syscall" + "unsafe" +) + +//=========================================================================================================== +// IMPORTANT NOTE: +// +// The methods below make extensive use of the "unsafe" package to obtain the required pointers. +// Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack +// variables) the pointers reference *before* the API completes. +// +// As a result, in those cases, the code must hint that the variables remain in active by invoking the +// dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer +// require unsafe pointers. +// +// If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform +// the garbage collector the variables remain in use if: +// +// -- The value is not a pointer (e.g., int32, struct) +// -- The value is not referenced by the method after passing the pointer to Windows +// +// See http://golang.org/doc/go1.3. +//=========================================================================================================== + +var ( + kernel32DLL = syscall.NewLazyDLL("kernel32.dll") + + getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo") + setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo") + setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition") + setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode") + getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo") + setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize") + scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA") + setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute") + setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo") + writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW") + readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW") + waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject") +) + +// Windows Console constants +const ( + // Console modes + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx. + ENABLE_PROCESSED_INPUT = 0x0001 + ENABLE_LINE_INPUT = 0x0002 + ENABLE_ECHO_INPUT = 0x0004 + ENABLE_WINDOW_INPUT = 0x0008 + ENABLE_MOUSE_INPUT = 0x0010 + ENABLE_INSERT_MODE = 0x0020 + ENABLE_QUICK_EDIT_MODE = 0x0040 + ENABLE_EXTENDED_FLAGS = 0x0080 + ENABLE_AUTO_POSITION = 0x0100 + ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 + + ENABLE_PROCESSED_OUTPUT = 0x0001 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + DISABLE_NEWLINE_AUTO_RETURN = 0x0008 + ENABLE_LVB_GRID_WORLDWIDE = 0x0010 + + // Character attributes + // Note: + // -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan). + // Clearing all foreground or background colors results in black; setting all creates white. + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes. + FOREGROUND_BLUE uint16 = 0x0001 + FOREGROUND_GREEN uint16 = 0x0002 + FOREGROUND_RED uint16 = 0x0004 + FOREGROUND_INTENSITY uint16 = 0x0008 + FOREGROUND_MASK uint16 = 0x000F + + BACKGROUND_BLUE uint16 = 0x0010 + BACKGROUND_GREEN uint16 = 0x0020 + BACKGROUND_RED uint16 = 0x0040 + BACKGROUND_INTENSITY uint16 = 0x0080 + BACKGROUND_MASK uint16 = 0x00F0 + + COMMON_LVB_MASK uint16 = 0xFF00 + COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000 + COMMON_LVB_UNDERSCORE uint16 = 0x8000 + + // Input event types + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx. + KEY_EVENT = 0x0001 + MOUSE_EVENT = 0x0002 + WINDOW_BUFFER_SIZE_EVENT = 0x0004 + MENU_EVENT = 0x0008 + FOCUS_EVENT = 0x0010 + + // WaitForSingleObject return codes + WAIT_ABANDONED = 0x00000080 + WAIT_FAILED = 0xFFFFFFFF + WAIT_SIGNALED = 0x0000000 + WAIT_TIMEOUT = 0x00000102 + + // WaitForSingleObject wait duration + WAIT_INFINITE = 0xFFFFFFFF + WAIT_ONE_SECOND = 1000 + WAIT_HALF_SECOND = 500 + WAIT_QUARTER_SECOND = 250 +) + +// Windows API Console types +// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD) +// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment +type ( + CHAR_INFO struct { + UnicodeChar uint16 + Attributes uint16 + } + + CONSOLE_CURSOR_INFO struct { + Size uint32 + Visible int32 + } + + CONSOLE_SCREEN_BUFFER_INFO struct { + Size COORD + CursorPosition COORD + Attributes uint16 + Window SMALL_RECT + MaximumWindowSize COORD + } + + COORD struct { + X int16 + Y int16 + } + + SMALL_RECT struct { + Left int16 + Top int16 + Right int16 + Bottom int16 + } + + // INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx. + INPUT_RECORD struct { + EventType uint16 + KeyEvent KEY_EVENT_RECORD + } + + KEY_EVENT_RECORD struct { + KeyDown int32 + RepeatCount uint16 + VirtualKeyCode uint16 + VirtualScanCode uint16 + UnicodeChar uint16 + ControlKeyState uint32 + } + + WINDOW_BUFFER_SIZE struct { + Size COORD + } +) + +// boolToBOOL converts a Go bool into a Windows int32. +func boolToBOOL(f bool) int32 { + if f { + return int32(1) + } else { + return int32(0) + } +} + +// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx. +func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error { + r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0) + return checkError(r1, r2, err) +} + +// SetConsoleCursorInfo sets the size and visiblity of the console cursor. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx. +func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error { + r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0) + return checkError(r1, r2, err) +} + +// SetConsoleCursorPosition location of the console cursor. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx. +func SetConsoleCursorPosition(handle uintptr, coord COORD) error { + r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord)) + use(coord) + return checkError(r1, r2, err) +} + +// GetConsoleMode gets the console mode for given file descriptor +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx. +func GetConsoleMode(handle uintptr) (mode uint32, err error) { + err = syscall.GetConsoleMode(syscall.Handle(handle), &mode) + return mode, err +} + +// SetConsoleMode sets the console mode for given file descriptor +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx. +func SetConsoleMode(handle uintptr, mode uint32) error { + r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0) + use(mode) + return checkError(r1, r2, err) +} + +// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer. +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx. +func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) { + info := CONSOLE_SCREEN_BUFFER_INFO{} + err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0)) + if err != nil { + return nil, err + } + return &info, nil +} + +func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error { + r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char))) + use(scrollRect) + use(clipRect) + use(destOrigin) + use(char) + return checkError(r1, r2, err) +} + +// SetConsoleScreenBufferSize sets the size of the console screen buffer. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx. +func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error { + r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord)) + use(coord) + return checkError(r1, r2, err) +} + +// SetConsoleTextAttribute sets the attributes of characters written to the +// console screen buffer by the WriteFile or WriteConsole function. +// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx. +func SetConsoleTextAttribute(handle uintptr, attribute uint16) error { + r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0) + use(attribute) + return checkError(r1, r2, err) +} + +// SetConsoleWindowInfo sets the size and position of the console screen buffer's window. +// Note that the size and location must be within and no larger than the backing console screen buffer. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx. +func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error { + r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect))) + use(isAbsolute) + use(rect) + return checkError(r1, r2, err) +} + +// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx. +func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error { + r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion))) + use(buffer) + use(bufferSize) + use(bufferCoord) + return checkError(r1, r2, err) +} + +// ReadConsoleInput reads (and removes) data from the console input buffer. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx. +func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error { + r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count))) + use(buffer) + return checkError(r1, r2, err) +} + +// WaitForSingleObject waits for the passed handle to be signaled. +// It returns true if the handle was signaled; false otherwise. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx. +func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) { + r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait))) + switch r1 { + case WAIT_ABANDONED, WAIT_TIMEOUT: + return false, nil + case WAIT_SIGNALED: + return true, nil + } + use(msWait) + return false, err +} + +// String helpers +func (info CONSOLE_SCREEN_BUFFER_INFO) String() string { + return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize) +} + +func (coord COORD) String() string { + return fmt.Sprintf("%v,%v", coord.X, coord.Y) +} + +func (rect SMALL_RECT) String() string { + return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom) +} + +// checkError evaluates the results of a Windows API call and returns the error if it failed. +func checkError(r1, r2 uintptr, err error) error { + // Windows APIs return non-zero to indicate success + if r1 != 0 { + return nil + } + + // Return the error if provided, otherwise default to EINVAL + if err != nil { + return err + } + return syscall.EINVAL +} + +// coordToPointer converts a COORD into a uintptr (by fooling the type system). +func coordToPointer(c COORD) uintptr { + // Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass. + return uintptr(*((*uint32)(unsafe.Pointer(&c)))) +} + +// use is a no-op, but the compiler cannot see that it is. +// Calling use(p) ensures that p is kept live until that point. +func use(p interface{}) {} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go b/vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go new file mode 100644 index 00000000..cbec8f72 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go @@ -0,0 +1,100 @@ +// +build windows + +package winterm + +import "github.com/Azure/go-ansiterm" + +const ( + FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE + BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE +) + +// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the +// request represented by the passed ANSI mode. +func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) { + switch ansiMode { + + // Mode styles + case ansiterm.ANSI_SGR_BOLD: + windowsMode = windowsMode | FOREGROUND_INTENSITY + + case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF: + windowsMode &^= FOREGROUND_INTENSITY + + case ansiterm.ANSI_SGR_UNDERLINE: + windowsMode = windowsMode | COMMON_LVB_UNDERSCORE + + case ansiterm.ANSI_SGR_REVERSE: + inverted = true + + case ansiterm.ANSI_SGR_REVERSE_OFF: + inverted = false + + case ansiterm.ANSI_SGR_UNDERLINE_OFF: + windowsMode &^= COMMON_LVB_UNDERSCORE + + // Foreground colors + case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT: + windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK) + + case ansiterm.ANSI_SGR_FOREGROUND_BLACK: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) + + case ansiterm.ANSI_SGR_FOREGROUND_RED: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED + + case ansiterm.ANSI_SGR_FOREGROUND_GREEN: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN + + case ansiterm.ANSI_SGR_FOREGROUND_YELLOW: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN + + case ansiterm.ANSI_SGR_FOREGROUND_BLUE: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE + + case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE + + case ansiterm.ANSI_SGR_FOREGROUND_CYAN: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE + + case ansiterm.ANSI_SGR_FOREGROUND_WHITE: + windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE + + // Background colors + case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT: + // Black with no intensity + windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK) + + case ansiterm.ANSI_SGR_BACKGROUND_BLACK: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) + + case ansiterm.ANSI_SGR_BACKGROUND_RED: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED + + case ansiterm.ANSI_SGR_BACKGROUND_GREEN: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN + + case ansiterm.ANSI_SGR_BACKGROUND_YELLOW: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN + + case ansiterm.ANSI_SGR_BACKGROUND_BLUE: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE + + case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE + + case ansiterm.ANSI_SGR_BACKGROUND_CYAN: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE + + case ansiterm.ANSI_SGR_BACKGROUND_WHITE: + windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE + } + + return windowsMode, inverted +} + +// invertAttributes inverts the foreground and background colors of a Windows attributes value +func invertAttributes(windowsMode uint16) uint16 { + return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4) +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go b/vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go new file mode 100644 index 00000000..3ee06ea7 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go @@ -0,0 +1,101 @@ +// +build windows + +package winterm + +const ( + horizontal = iota + vertical +) + +func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT { + if h.originMode { + sr := h.effectiveSr(info.Window) + return SMALL_RECT{ + Top: sr.top, + Bottom: sr.bottom, + Left: 0, + Right: info.Size.X - 1, + } + } else { + return SMALL_RECT{ + Top: info.Window.Top, + Bottom: info.Window.Bottom, + Left: 0, + Right: info.Size.X - 1, + } + } +} + +// setCursorPosition sets the cursor to the specified position, bounded to the screen size +func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error { + position.X = ensureInRange(position.X, window.Left, window.Right) + position.Y = ensureInRange(position.Y, window.Top, window.Bottom) + err := SetConsoleCursorPosition(h.fd, position) + if err != nil { + return err + } + h.logf("Cursor position set: (%d, %d)", position.X, position.Y) + return err +} + +func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error { + return h.moveCursor(vertical, param) +} + +func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error { + return h.moveCursor(horizontal, param) +} + +func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + position := info.CursorPosition + switch moveMode { + case horizontal: + position.X += int16(param) + case vertical: + position.Y += int16(param) + } + + if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) moveCursorLine(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + position := info.CursorPosition + position.X = 0 + position.Y += int16(param) + + if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + position := info.CursorPosition + position.X = int16(param) - 1 + + if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go b/vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go new file mode 100644 index 00000000..244b5fa2 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go @@ -0,0 +1,84 @@ +// +build windows + +package winterm + +import "github.com/Azure/go-ansiterm" + +func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error { + // Ignore an invalid (negative area) request + if toCoord.Y < fromCoord.Y { + return nil + } + + var err error + + var coordStart = COORD{} + var coordEnd = COORD{} + + xCurrent, yCurrent := fromCoord.X, fromCoord.Y + xEnd, yEnd := toCoord.X, toCoord.Y + + // Clear any partial initial line + if xCurrent > 0 { + coordStart.X, coordStart.Y = xCurrent, yCurrent + coordEnd.X, coordEnd.Y = xEnd, yCurrent + + err = h.clearRect(attributes, coordStart, coordEnd) + if err != nil { + return err + } + + xCurrent = 0 + yCurrent += 1 + } + + // Clear intervening rectangular section + if yCurrent < yEnd { + coordStart.X, coordStart.Y = xCurrent, yCurrent + coordEnd.X, coordEnd.Y = xEnd, yEnd-1 + + err = h.clearRect(attributes, coordStart, coordEnd) + if err != nil { + return err + } + + xCurrent = 0 + yCurrent = yEnd + } + + // Clear remaining partial ending line + coordStart.X, coordStart.Y = xCurrent, yCurrent + coordEnd.X, coordEnd.Y = xEnd, yEnd + + err = h.clearRect(attributes, coordStart, coordEnd) + if err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error { + region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X} + width := toCoord.X - fromCoord.X + 1 + height := toCoord.Y - fromCoord.Y + 1 + size := uint32(width) * uint32(height) + + if size <= 0 { + return nil + } + + buffer := make([]CHAR_INFO, size) + + char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes} + for i := 0; i < int(size); i++ { + buffer[i] = char + } + + err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go b/vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go new file mode 100644 index 00000000..2d27fa1d --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go @@ -0,0 +1,118 @@ +// +build windows + +package winterm + +// effectiveSr gets the current effective scroll region in buffer coordinates +func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion { + top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom) + bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom) + if top >= bottom { + top = window.Top + bottom = window.Bottom + } + return scrollRegion{top: top, bottom: bottom} +} + +func (h *windowsAnsiEventHandler) scrollUp(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + sr := h.effectiveSr(info.Window) + return h.scroll(param, sr, info) +} + +func (h *windowsAnsiEventHandler) scrollDown(param int) error { + return h.scrollUp(-param) +} + +func (h *windowsAnsiEventHandler) deleteLines(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + start := info.CursorPosition.Y + sr := h.effectiveSr(info.Window) + // Lines cannot be inserted or deleted outside the scrolling region. + if start >= sr.top && start <= sr.bottom { + sr.top = start + return h.scroll(param, sr, info) + } else { + return nil + } +} + +func (h *windowsAnsiEventHandler) insertLines(param int) error { + return h.deleteLines(-param) +} + +// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates. +func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error { + h.logf("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom) + h.logf("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom) + + // Copy from and clip to the scroll region (full buffer width) + scrollRect := SMALL_RECT{ + Top: sr.top, + Bottom: sr.bottom, + Left: 0, + Right: info.Size.X - 1, + } + + // Origin to which area should be copied + destOrigin := COORD{ + X: 0, + Y: sr.top - int16(param), + } + + char := CHAR_INFO{ + UnicodeChar: ' ', + Attributes: h.attributes, + } + + if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil { + return err + } + return nil +} + +func (h *windowsAnsiEventHandler) deleteCharacters(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + return h.scrollLine(param, info.CursorPosition, info) +} + +func (h *windowsAnsiEventHandler) insertCharacters(param int) error { + return h.deleteCharacters(-param) +} + +// scrollLine scrolls a line horizontally starting at the provided position by a number of columns. +func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error { + // Copy from and clip to the scroll region (full buffer width) + scrollRect := SMALL_RECT{ + Top: position.Y, + Bottom: position.Y, + Left: position.X, + Right: info.Size.X - 1, + } + + // Origin to which area should be copied + destOrigin := COORD{ + X: position.X - int16(columns), + Y: position.Y, + } + + char := CHAR_INFO{ + UnicodeChar: ' ', + Attributes: h.attributes, + } + + if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/utilities.go b/vendor/github.com/Azure/go-ansiterm/winterm/utilities.go new file mode 100644 index 00000000..afa7635d --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/utilities.go @@ -0,0 +1,9 @@ +// +build windows + +package winterm + +// AddInRange increments a value by the passed quantity while ensuring the values +// always remain within the supplied min / max range. +func addInRange(n int16, increment int16, min int16, max int16) int16 { + return ensureInRange(n+increment, min, max) +} diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go b/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go new file mode 100644 index 00000000..2d40fb75 --- /dev/null +++ b/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go @@ -0,0 +1,743 @@ +// +build windows + +package winterm + +import ( + "bytes" + "log" + "os" + "strconv" + + "github.com/Azure/go-ansiterm" +) + +type windowsAnsiEventHandler struct { + fd uintptr + file *os.File + infoReset *CONSOLE_SCREEN_BUFFER_INFO + sr scrollRegion + buffer bytes.Buffer + attributes uint16 + inverted bool + wrapNext bool + drewMarginByte bool + originMode bool + marginByte byte + curInfo *CONSOLE_SCREEN_BUFFER_INFO + curPos COORD + logf func(string, ...interface{}) +} + +type Option func(*windowsAnsiEventHandler) + +func WithLogf(f func(string, ...interface{})) Option { + return func(w *windowsAnsiEventHandler) { + w.logf = f + } +} + +func CreateWinEventHandler(fd uintptr, file *os.File, opts ...Option) ansiterm.AnsiEventHandler { + infoReset, err := GetConsoleScreenBufferInfo(fd) + if err != nil { + return nil + } + + h := &windowsAnsiEventHandler{ + fd: fd, + file: file, + infoReset: infoReset, + attributes: infoReset.Attributes, + } + for _, o := range opts { + o(h) + } + + if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" { + logFile, _ := os.Create("winEventHandler.log") + logger := log.New(logFile, "", log.LstdFlags) + if h.logf != nil { + l := h.logf + h.logf = func(s string, v ...interface{}) { + l(s, v...) + logger.Printf(s, v...) + } + } else { + h.logf = logger.Printf + } + } + + if h.logf == nil { + h.logf = func(string, ...interface{}) {} + } + + return h +} + +type scrollRegion struct { + top int16 + bottom int16 +} + +// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the +// current cursor position and scroll region settings, in which case it returns +// true. If no special handling is necessary, then it does nothing and returns +// false. +// +// In the false case, the caller should ensure that a carriage return +// and line feed are inserted or that the text is otherwise wrapped. +func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) { + if h.wrapNext { + if err := h.Flush(); err != nil { + return false, err + } + h.clearWrap() + } + pos, info, err := h.getCurrentInfo() + if err != nil { + return false, err + } + sr := h.effectiveSr(info.Window) + if pos.Y == sr.bottom { + // Scrolling is necessary. Let Windows automatically scroll if the scrolling region + // is the full window. + if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom { + if includeCR { + pos.X = 0 + h.updatePos(pos) + } + return false, nil + } + + // A custom scroll region is active. Scroll the window manually to simulate + // the LF. + if err := h.Flush(); err != nil { + return false, err + } + h.logf("Simulating LF inside scroll region") + if err := h.scrollUp(1); err != nil { + return false, err + } + if includeCR { + pos.X = 0 + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return false, err + } + } + return true, nil + + } else if pos.Y < info.Window.Bottom { + // Let Windows handle the LF. + pos.Y++ + if includeCR { + pos.X = 0 + } + h.updatePos(pos) + return false, nil + } else { + // The cursor is at the bottom of the screen but outside the scroll + // region. Skip the LF. + h.logf("Simulating LF outside scroll region") + if includeCR { + if err := h.Flush(); err != nil { + return false, err + } + pos.X = 0 + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return false, err + } + } + return true, nil + } +} + +// executeLF executes a LF without a CR. +func (h *windowsAnsiEventHandler) executeLF() error { + handled, err := h.simulateLF(false) + if err != nil { + return err + } + if !handled { + // Windows LF will reset the cursor column position. Write the LF + // and restore the cursor position. + pos, _, err := h.getCurrentInfo() + if err != nil { + return err + } + h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED) + if pos.X != 0 { + if err := h.Flush(); err != nil { + return err + } + h.logf("Resetting cursor position for LF without CR") + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return err + } + } + } + return nil +} + +func (h *windowsAnsiEventHandler) Print(b byte) error { + if h.wrapNext { + h.buffer.WriteByte(h.marginByte) + h.clearWrap() + if _, err := h.simulateLF(true); err != nil { + return err + } + } + pos, info, err := h.getCurrentInfo() + if err != nil { + return err + } + if pos.X == info.Size.X-1 { + h.wrapNext = true + h.marginByte = b + } else { + pos.X++ + h.updatePos(pos) + h.buffer.WriteByte(b) + } + return nil +} + +func (h *windowsAnsiEventHandler) Execute(b byte) error { + switch b { + case ansiterm.ANSI_TAB: + h.logf("Execute(TAB)") + // Move to the next tab stop, but preserve auto-wrap if already set. + if !h.wrapNext { + pos, info, err := h.getCurrentInfo() + if err != nil { + return err + } + pos.X = (pos.X + 8) - pos.X%8 + if pos.X >= info.Size.X { + pos.X = info.Size.X - 1 + } + if err := h.Flush(); err != nil { + return err + } + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return err + } + } + return nil + + case ansiterm.ANSI_BEL: + h.buffer.WriteByte(ansiterm.ANSI_BEL) + return nil + + case ansiterm.ANSI_BACKSPACE: + if h.wrapNext { + if err := h.Flush(); err != nil { + return err + } + h.clearWrap() + } + pos, _, err := h.getCurrentInfo() + if err != nil { + return err + } + if pos.X > 0 { + pos.X-- + h.updatePos(pos) + h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE) + } + return nil + + case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED: + // Treat as true LF. + return h.executeLF() + + case ansiterm.ANSI_LINE_FEED: + // Simulate a CR and LF for now since there is no way in go-ansiterm + // to tell if the LF should include CR (and more things break when it's + // missing than when it's incorrectly added). + handled, err := h.simulateLF(true) + if handled || err != nil { + return err + } + return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED) + + case ansiterm.ANSI_CARRIAGE_RETURN: + if h.wrapNext { + if err := h.Flush(); err != nil { + return err + } + h.clearWrap() + } + pos, _, err := h.getCurrentInfo() + if err != nil { + return err + } + if pos.X != 0 { + pos.X = 0 + h.updatePos(pos) + h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN) + } + return nil + + default: + return nil + } +} + +func (h *windowsAnsiEventHandler) CUU(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("CUU: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorVertical(-param) +} + +func (h *windowsAnsiEventHandler) CUD(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("CUD: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorVertical(param) +} + +func (h *windowsAnsiEventHandler) CUF(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("CUF: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorHorizontal(param) +} + +func (h *windowsAnsiEventHandler) CUB(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("CUB: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorHorizontal(-param) +} + +func (h *windowsAnsiEventHandler) CNL(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("CNL: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorLine(param) +} + +func (h *windowsAnsiEventHandler) CPL(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("CPL: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorLine(-param) +} + +func (h *windowsAnsiEventHandler) CHA(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("CHA: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.moveCursorColumn(param) +} + +func (h *windowsAnsiEventHandler) VPA(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("VPA: [[%d]]", param) + h.clearWrap() + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + window := h.getCursorWindow(info) + position := info.CursorPosition + position.Y = window.Top + int16(param) - 1 + return h.setCursorPosition(position, window) +} + +func (h *windowsAnsiEventHandler) CUP(row int, col int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("CUP: [[%d %d]]", row, col) + h.clearWrap() + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + window := h.getCursorWindow(info) + position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1} + return h.setCursorPosition(position, window) +} + +func (h *windowsAnsiEventHandler) HVP(row int, col int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("HVP: [[%d %d]]", row, col) + h.clearWrap() + return h.CUP(row, col) +} + +func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("DECTCEM: [%v]", []string{strconv.FormatBool(visible)}) + h.clearWrap() + return nil +} + +func (h *windowsAnsiEventHandler) DECOM(enable bool) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("DECOM: [%v]", []string{strconv.FormatBool(enable)}) + h.clearWrap() + h.originMode = enable + return h.CUP(1, 1) +} + +func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("DECCOLM: [%v]", []string{strconv.FormatBool(use132)}) + h.clearWrap() + if err := h.ED(2); err != nil { + return err + } + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + targetWidth := int16(80) + if use132 { + targetWidth = 132 + } + if info.Size.X < targetWidth { + if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil { + h.logf("set buffer failed: %v", err) + return err + } + } + window := info.Window + window.Left = 0 + window.Right = targetWidth - 1 + if err := SetConsoleWindowInfo(h.fd, true, window); err != nil { + h.logf("set window failed: %v", err) + return err + } + if info.Size.X > targetWidth { + if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil { + h.logf("set buffer failed: %v", err) + return err + } + } + return SetConsoleCursorPosition(h.fd, COORD{0, 0}) +} + +func (h *windowsAnsiEventHandler) ED(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("ED: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + + // [J -- Erases from the cursor to the end of the screen, including the cursor position. + // [1J -- Erases from the beginning of the screen to the cursor, including the cursor position. + // [2J -- Erases the complete display. The cursor does not move. + // Notes: + // -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles + + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + var start COORD + var end COORD + + switch param { + case 0: + start = info.CursorPosition + end = COORD{info.Size.X - 1, info.Size.Y - 1} + + case 1: + start = COORD{0, 0} + end = info.CursorPosition + + case 2: + start = COORD{0, 0} + end = COORD{info.Size.X - 1, info.Size.Y - 1} + } + + err = h.clearRange(h.attributes, start, end) + if err != nil { + return err + } + + // If the whole buffer was cleared, move the window to the top while preserving + // the window-relative cursor position. + if param == 2 { + pos := info.CursorPosition + window := info.Window + pos.Y -= window.Top + window.Bottom -= window.Top + window.Top = 0 + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return err + } + if err := SetConsoleWindowInfo(h.fd, true, window); err != nil { + return err + } + } + + return nil +} + +func (h *windowsAnsiEventHandler) EL(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("EL: [%v]", strconv.Itoa(param)) + h.clearWrap() + + // [K -- Erases from the cursor to the end of the line, including the cursor position. + // [1K -- Erases from the beginning of the line to the cursor, including the cursor position. + // [2K -- Erases the complete line. + + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + var start COORD + var end COORD + + switch param { + case 0: + start = info.CursorPosition + end = COORD{info.Size.X, info.CursorPosition.Y} + + case 1: + start = COORD{0, info.CursorPosition.Y} + end = info.CursorPosition + + case 2: + start = COORD{0, info.CursorPosition.Y} + end = COORD{info.Size.X, info.CursorPosition.Y} + } + + err = h.clearRange(h.attributes, start, end) + if err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) IL(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("IL: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.insertLines(param) +} + +func (h *windowsAnsiEventHandler) DL(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("DL: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.deleteLines(param) +} + +func (h *windowsAnsiEventHandler) ICH(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("ICH: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.insertCharacters(param) +} + +func (h *windowsAnsiEventHandler) DCH(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("DCH: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.deleteCharacters(param) +} + +func (h *windowsAnsiEventHandler) SGR(params []int) error { + if err := h.Flush(); err != nil { + return err + } + strings := []string{} + for _, v := range params { + strings = append(strings, strconv.Itoa(v)) + } + + h.logf("SGR: [%v]", strings) + + if len(params) <= 0 { + h.attributes = h.infoReset.Attributes + h.inverted = false + } else { + for _, attr := range params { + + if attr == ansiterm.ANSI_SGR_RESET { + h.attributes = h.infoReset.Attributes + h.inverted = false + continue + } + + h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr)) + } + } + + attributes := h.attributes + if h.inverted { + attributes = invertAttributes(attributes) + } + err := SetConsoleTextAttribute(h.fd, attributes) + if err != nil { + return err + } + + return nil +} + +func (h *windowsAnsiEventHandler) SU(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("SU: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.scrollUp(param) +} + +func (h *windowsAnsiEventHandler) SD(param int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("SD: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() + return h.scrollDown(param) +} + +func (h *windowsAnsiEventHandler) DA(params []string) error { + h.logf("DA: [%v]", params) + // DA cannot be implemented because it must send data on the VT100 input stream, + // which is not available to go-ansiterm. + return nil +} + +func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error { + if err := h.Flush(); err != nil { + return err + } + h.logf("DECSTBM: [%d, %d]", top, bottom) + + // Windows is 0 indexed, Linux is 1 indexed + h.sr.top = int16(top - 1) + h.sr.bottom = int16(bottom - 1) + + // This command also moves the cursor to the origin. + h.clearWrap() + return h.CUP(1, 1) +} + +func (h *windowsAnsiEventHandler) RI() error { + if err := h.Flush(); err != nil { + return err + } + h.logf("RI: []") + h.clearWrap() + + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + sr := h.effectiveSr(info.Window) + if info.CursorPosition.Y == sr.top { + return h.scrollDown(1) + } + + return h.moveCursorVertical(-1) +} + +func (h *windowsAnsiEventHandler) IND() error { + h.logf("IND: []") + return h.executeLF() +} + +func (h *windowsAnsiEventHandler) Flush() error { + h.curInfo = nil + if h.buffer.Len() > 0 { + h.logf("Flush: [%s]", h.buffer.Bytes()) + if _, err := h.buffer.WriteTo(h.file); err != nil { + return err + } + } + + if h.wrapNext && !h.drewMarginByte { + h.logf("Flush: drawing margin byte '%c'", h.marginByte) + + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}} + size := COORD{1, 1} + position := COORD{0, 0} + region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y} + if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil { + return err + } + h.drewMarginByte = true + } + return nil +} + +// cacheConsoleInfo ensures that the current console screen information has been queried +// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos. +func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) { + if h.curInfo == nil { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return COORD{}, nil, err + } + h.curInfo = info + h.curPos = info.CursorPosition + } + return h.curPos, h.curInfo, nil +} + +func (h *windowsAnsiEventHandler) updatePos(pos COORD) { + if h.curInfo == nil { + panic("failed to call getCurrentInfo before calling updatePos") + } + h.curPos = pos +} + +// clearWrap clears the state where the cursor is in the margin +// waiting for the next character before wrapping the line. This must +// be done before most operations that act on the cursor. +func (h *windowsAnsiEventHandler) clearWrap() { + h.wrapNext = false + h.drewMarginByte = false +} diff --git a/vendor/github.com/BurntSushi/toml/.gitignore b/vendor/github.com/BurntSushi/toml/.gitignore new file mode 100644 index 00000000..fe79e3ad --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/.gitignore @@ -0,0 +1,2 @@ +/toml.test +/toml-test diff --git a/vendor/github.com/BurntSushi/toml/COPYING b/vendor/github.com/BurntSushi/toml/COPYING new file mode 100644 index 00000000..01b57432 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/COPYING @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 TOML authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/BurntSushi/toml/README.md b/vendor/github.com/BurntSushi/toml/README.md new file mode 100644 index 00000000..235496ee --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/README.md @@ -0,0 +1,120 @@ +TOML stands for Tom's Obvious, Minimal Language. This Go package provides a +reflection interface similar to Go's standard library `json` and `xml` packages. + +Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0). + +Documentation: https://pkg.go.dev/github.com/BurntSushi/toml + +See the [releases page](https://github.com/BurntSushi/toml/releases) for a +changelog; this information is also in the git tag annotations (e.g. `git show +v0.4.0`). + +This library requires Go 1.18 or newer; add it to your go.mod with: + + % go get github.com/BurntSushi/toml@latest + +It also comes with a TOML validator CLI tool: + + % go install github.com/BurntSushi/toml/cmd/tomlv@latest + % tomlv some-toml-file.toml + +### Examples +For the simplest example, consider some TOML file as just a list of keys and +values: + +```toml +Age = 25 +Cats = [ "Cauchy", "Plato" ] +Pi = 3.14 +Perfection = [ 6, 28, 496, 8128 ] +DOB = 1987-07-05T05:45:00Z +``` + +Which can be decoded with: + +```go +type Config struct { + Age int + Cats []string + Pi float64 + Perfection []int + DOB time.Time +} + +var conf Config +_, err := toml.Decode(tomlData, &conf) +``` + +You can also use struct tags if your struct field name doesn't map to a TOML key +value directly: + +```toml +some_key_NAME = "wat" +``` + +```go +type TOML struct { + ObscureKey string `toml:"some_key_NAME"` +} +``` + +Beware that like other decoders **only exported fields** are considered when +encoding and decoding; private fields are silently ignored. + +### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces +Here's an example that automatically parses values in a `mail.Address`: + +```toml +contacts = [ + "Donald Duck ", + "Scrooge McDuck ", +] +``` + +Can be decoded with: + +```go +// Create address type which satisfies the encoding.TextUnmarshaler interface. +type address struct { + *mail.Address +} + +func (a *address) UnmarshalText(text []byte) error { + var err error + a.Address, err = mail.ParseAddress(string(text)) + return err +} + +// Decode it. +func decode() { + blob := ` + contacts = [ + "Donald Duck ", + "Scrooge McDuck ", + ] + ` + + var contacts struct { + Contacts []address + } + + _, err := toml.Decode(blob, &contacts) + if err != nil { + log.Fatal(err) + } + + for _, c := range contacts.Contacts { + fmt.Printf("%#v\n", c.Address) + } + + // Output: + // &mail.Address{Name:"Donald Duck", Address:"donald@duckburg.com"} + // &mail.Address{Name:"Scrooge McDuck", Address:"scrooge@duckburg.com"} +} +``` + +To target TOML specifically you can implement `UnmarshalTOML` TOML interface in +a similar way. + +### More complex usage +See the [`_example/`](/_example) directory for a more complex example. diff --git a/vendor/github.com/BurntSushi/toml/decode.go b/vendor/github.com/BurntSushi/toml/decode.go new file mode 100644 index 00000000..3fa516ca --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/decode.go @@ -0,0 +1,638 @@ +package toml + +import ( + "bytes" + "encoding" + "encoding/json" + "fmt" + "io" + "io/fs" + "math" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +// Unmarshaler is the interface implemented by objects that can unmarshal a +// TOML description of themselves. +type Unmarshaler interface { + UnmarshalTOML(any) error +} + +// Unmarshal decodes the contents of data in TOML format into a pointer v. +// +// See [Decoder] for a description of the decoding process. +func Unmarshal(data []byte, v any) error { + _, err := NewDecoder(bytes.NewReader(data)).Decode(v) + return err +} + +// Decode the TOML data in to the pointer v. +// +// See [Decoder] for a description of the decoding process. +func Decode(data string, v any) (MetaData, error) { + return NewDecoder(strings.NewReader(data)).Decode(v) +} + +// DecodeFile reads the contents of a file and decodes it with [Decode]. +func DecodeFile(path string, v any) (MetaData, error) { + fp, err := os.Open(path) + if err != nil { + return MetaData{}, err + } + defer fp.Close() + return NewDecoder(fp).Decode(v) +} + +// DecodeFS reads the contents of a file from [fs.FS] and decodes it with +// [Decode]. +func DecodeFS(fsys fs.FS, path string, v any) (MetaData, error) { + fp, err := fsys.Open(path) + if err != nil { + return MetaData{}, err + } + defer fp.Close() + return NewDecoder(fp).Decode(v) +} + +// Primitive is a TOML value that hasn't been decoded into a Go value. +// +// This type can be used for any value, which will cause decoding to be delayed. +// You can use [PrimitiveDecode] to "manually" decode these values. +// +// NOTE: The underlying representation of a `Primitive` value is subject to +// change. Do not rely on it. +// +// NOTE: Primitive values are still parsed, so using them will only avoid the +// overhead of reflection. They can be useful when you don't know the exact type +// of TOML data until runtime. +type Primitive struct { + undecoded any + context Key +} + +// The significand precision for float32 and float64 is 24 and 53 bits; this is +// the range a natural number can be stored in a float without loss of data. +const ( + maxSafeFloat32Int = 16777215 // 2^24-1 + maxSafeFloat64Int = int64(9007199254740991) // 2^53-1 +) + +// Decoder decodes TOML data. +// +// TOML tables correspond to Go structs or maps; they can be used +// interchangeably, but structs offer better type safety. +// +// TOML table arrays correspond to either a slice of structs or a slice of maps. +// +// TOML datetimes correspond to [time.Time]. Local datetimes are parsed in the +// local timezone. +// +// [time.Duration] types are treated as nanoseconds if the TOML value is an +// integer, or they're parsed with time.ParseDuration() if they're strings. +// +// All other TOML types (float, string, int, bool and array) correspond to the +// obvious Go types. +// +// An exception to the above rules is if a type implements the TextUnmarshaler +// interface, in which case any primitive TOML value (floats, strings, integers, +// booleans, datetimes) will be converted to a []byte and given to the value's +// UnmarshalText method. See the Unmarshaler example for a demonstration with +// email addresses. +// +// # Key mapping +// +// TOML keys can map to either keys in a Go map or field names in a Go struct. +// The special `toml` struct tag can be used to map TOML keys to struct fields +// that don't match the key name exactly (see the example). A case insensitive +// match to struct names will be tried if an exact match can't be found. +// +// The mapping between TOML values and Go values is loose. That is, there may +// exist TOML values that cannot be placed into your representation, and there +// may be parts of your representation that do not correspond to TOML values. +// This loose mapping can be made stricter by using the IsDefined and/or +// Undecoded methods on the MetaData returned. +// +// This decoder does not handle cyclic types. Decode will not terminate if a +// cyclic type is passed. +type Decoder struct { + r io.Reader +} + +// NewDecoder creates a new Decoder. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{r: r} +} + +var ( + unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem() + unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + primitiveType = reflect.TypeOf((*Primitive)(nil)).Elem() +) + +// Decode TOML data in to the pointer `v`. +func (dec *Decoder) Decode(v any) (MetaData, error) { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr { + s := "%q" + if reflect.TypeOf(v) == nil { + s = "%v" + } + + return MetaData{}, fmt.Errorf("toml: cannot decode to non-pointer "+s, reflect.TypeOf(v)) + } + if rv.IsNil() { + return MetaData{}, fmt.Errorf("toml: cannot decode to nil value of %q", reflect.TypeOf(v)) + } + + // Check if this is a supported type: struct, map, any, or something that + // implements UnmarshalTOML or UnmarshalText. + rv = indirect(rv) + rt := rv.Type() + if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map && + !(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) && + !rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) { + return MetaData{}, fmt.Errorf("toml: cannot decode to type %s", rt) + } + + // TODO: parser should read from io.Reader? Or at the very least, make it + // read from []byte rather than string + data, err := io.ReadAll(dec.r) + if err != nil { + return MetaData{}, err + } + + p, err := parse(string(data)) + if err != nil { + return MetaData{}, err + } + + md := MetaData{ + mapping: p.mapping, + keyInfo: p.keyInfo, + keys: p.ordered, + decoded: make(map[string]struct{}, len(p.ordered)), + context: nil, + data: data, + } + return md, md.unify(p.mapping, rv) +} + +// PrimitiveDecode is just like the other Decode* functions, except it decodes a +// TOML value that has already been parsed. Valid primitive values can *only* be +// obtained from values filled by the decoder functions, including this method. +// (i.e., v may contain more [Primitive] values.) +// +// Meta data for primitive values is included in the meta data returned by the +// Decode* functions with one exception: keys returned by the Undecoded method +// will only reflect keys that were decoded. Namely, any keys hidden behind a +// Primitive will be considered undecoded. Executing this method will update the +// undecoded keys in the meta data. (See the example.) +func (md *MetaData) PrimitiveDecode(primValue Primitive, v any) error { + md.context = primValue.context + defer func() { md.context = nil }() + return md.unify(primValue.undecoded, rvalue(v)) +} + +// markDecodedRecursive is a helper to mark any key under the given tmap as +// decoded, recursing as needed +func markDecodedRecursive(md *MetaData, tmap map[string]any) { + for key := range tmap { + md.decoded[md.context.add(key).String()] = struct{}{} + if tmap, ok := tmap[key].(map[string]any); ok { + md.context = append(md.context, key) + markDecodedRecursive(md, tmap) + md.context = md.context[0 : len(md.context)-1] + } + } +} + +// unify performs a sort of type unification based on the structure of `rv`, +// which is the client representation. +// +// Any type mismatch produces an error. Finding a type that we don't know +// how to handle produces an unsupported type error. +func (md *MetaData) unify(data any, rv reflect.Value) error { + // Special case. Look for a `Primitive` value. + // TODO: #76 would make this superfluous after implemented. + if rv.Type() == primitiveType { + // Save the undecoded data and the key context into the primitive + // value. + context := make(Key, len(md.context)) + copy(context, md.context) + rv.Set(reflect.ValueOf(Primitive{ + undecoded: data, + context: context, + })) + return nil + } + + rvi := rv.Interface() + if v, ok := rvi.(Unmarshaler); ok { + err := v.UnmarshalTOML(data) + if err != nil { + return md.parseErr(err) + } + // Assume the Unmarshaler decoded everything, so mark all keys under + // this table as decoded. + if tmap, ok := data.(map[string]any); ok { + markDecodedRecursive(md, tmap) + } + if aot, ok := data.([]map[string]any); ok { + for _, tmap := range aot { + markDecodedRecursive(md, tmap) + } + } + return nil + } + if v, ok := rvi.(encoding.TextUnmarshaler); ok { + return md.unifyText(data, v) + } + + // TODO: + // The behavior here is incorrect whenever a Go type satisfies the + // encoding.TextUnmarshaler interface but also corresponds to a TOML hash or + // array. In particular, the unmarshaler should only be applied to primitive + // TOML values. But at this point, it will be applied to all kinds of values + // and produce an incorrect error whenever those values are hashes or arrays + // (including arrays of tables). + + k := rv.Kind() + + if k >= reflect.Int && k <= reflect.Uint64 { + return md.unifyInt(data, rv) + } + switch k { + case reflect.Struct: + return md.unifyStruct(data, rv) + case reflect.Map: + return md.unifyMap(data, rv) + case reflect.Array: + return md.unifyArray(data, rv) + case reflect.Slice: + return md.unifySlice(data, rv) + case reflect.String: + return md.unifyString(data, rv) + case reflect.Bool: + return md.unifyBool(data, rv) + case reflect.Interface: + if rv.NumMethod() > 0 { /// Only empty interfaces are supported. + return md.e("unsupported type %s", rv.Type()) + } + return md.unifyAnything(data, rv) + case reflect.Float32, reflect.Float64: + return md.unifyFloat64(data, rv) + } + return md.e("unsupported type %s", rv.Kind()) +} + +func (md *MetaData) unifyStruct(mapping any, rv reflect.Value) error { + tmap, ok := mapping.(map[string]any) + if !ok { + if mapping == nil { + return nil + } + return md.e("type mismatch for %s: expected table but found %s", rv.Type().String(), fmtType(mapping)) + } + + for key, datum := range tmap { + var f *field + fields := cachedTypeFields(rv.Type()) + for i := range fields { + ff := &fields[i] + if ff.name == key { + f = ff + break + } + if f == nil && strings.EqualFold(ff.name, key) { + f = ff + } + } + if f != nil { + subv := rv + for _, i := range f.index { + subv = indirect(subv.Field(i)) + } + + if isUnifiable(subv) { + md.decoded[md.context.add(key).String()] = struct{}{} + md.context = append(md.context, key) + + err := md.unify(datum, subv) + if err != nil { + return err + } + md.context = md.context[0 : len(md.context)-1] + } else if f.name != "" { + return md.e("cannot write unexported field %s.%s", rv.Type().String(), f.name) + } + } + } + return nil +} + +func (md *MetaData) unifyMap(mapping any, rv reflect.Value) error { + keyType := rv.Type().Key().Kind() + if keyType != reflect.String && keyType != reflect.Interface { + return fmt.Errorf("toml: cannot decode to a map with non-string key type (%s in %q)", + keyType, rv.Type()) + } + + tmap, ok := mapping.(map[string]any) + if !ok { + if tmap == nil { + return nil + } + return md.badtype("map", mapping) + } + if rv.IsNil() { + rv.Set(reflect.MakeMap(rv.Type())) + } + for k, v := range tmap { + md.decoded[md.context.add(k).String()] = struct{}{} + md.context = append(md.context, k) + + rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) + + err := md.unify(v, indirect(rvval)) + if err != nil { + return err + } + md.context = md.context[0 : len(md.context)-1] + + rvkey := indirect(reflect.New(rv.Type().Key())) + + switch keyType { + case reflect.Interface: + rvkey.Set(reflect.ValueOf(k)) + case reflect.String: + rvkey.SetString(k) + } + + rv.SetMapIndex(rvkey, rvval) + } + return nil +} + +func (md *MetaData) unifyArray(data any, rv reflect.Value) error { + datav := reflect.ValueOf(data) + if datav.Kind() != reflect.Slice { + if !datav.IsValid() { + return nil + } + return md.badtype("slice", data) + } + if l := datav.Len(); l != rv.Len() { + return md.e("expected array length %d; got TOML array of length %d", rv.Len(), l) + } + return md.unifySliceArray(datav, rv) +} + +func (md *MetaData) unifySlice(data any, rv reflect.Value) error { + datav := reflect.ValueOf(data) + if datav.Kind() != reflect.Slice { + if !datav.IsValid() { + return nil + } + return md.badtype("slice", data) + } + n := datav.Len() + if rv.IsNil() || rv.Cap() < n { + rv.Set(reflect.MakeSlice(rv.Type(), n, n)) + } + rv.SetLen(n) + return md.unifySliceArray(datav, rv) +} + +func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { + l := data.Len() + for i := 0; i < l; i++ { + err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i))) + if err != nil { + return err + } + } + return nil +} + +func (md *MetaData) unifyString(data any, rv reflect.Value) error { + _, ok := rv.Interface().(json.Number) + if ok { + if i, ok := data.(int64); ok { + rv.SetString(strconv.FormatInt(i, 10)) + } else if f, ok := data.(float64); ok { + rv.SetString(strconv.FormatFloat(f, 'f', -1, 64)) + } else { + return md.badtype("string", data) + } + return nil + } + + if s, ok := data.(string); ok { + rv.SetString(s) + return nil + } + return md.badtype("string", data) +} + +func (md *MetaData) unifyFloat64(data any, rv reflect.Value) error { + rvk := rv.Kind() + + if num, ok := data.(float64); ok { + switch rvk { + case reflect.Float32: + if num < -math.MaxFloat32 || num > math.MaxFloat32 { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } + fallthrough + case reflect.Float64: + rv.SetFloat(num) + default: + panic("bug") + } + return nil + } + + if num, ok := data.(int64); ok { + if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) || + (rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) { + return md.parseErr(errUnsafeFloat{i: num, size: rvk.String()}) + } + rv.SetFloat(float64(num)) + return nil + } + + return md.badtype("float", data) +} + +func (md *MetaData) unifyInt(data any, rv reflect.Value) error { + _, ok := rv.Interface().(time.Duration) + if ok { + // Parse as string duration, and fall back to regular integer parsing + // (as nanosecond) if this is not a string. + if s, ok := data.(string); ok { + dur, err := time.ParseDuration(s) + if err != nil { + return md.parseErr(errParseDuration{s}) + } + rv.SetInt(int64(dur)) + return nil + } + } + + num, ok := data.(int64) + if !ok { + return md.badtype("integer", data) + } + + rvk := rv.Kind() + switch { + case rvk >= reflect.Int && rvk <= reflect.Int64: + if (rvk == reflect.Int8 && (num < math.MinInt8 || num > math.MaxInt8)) || + (rvk == reflect.Int16 && (num < math.MinInt16 || num > math.MaxInt16)) || + (rvk == reflect.Int32 && (num < math.MinInt32 || num > math.MaxInt32)) { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } + rv.SetInt(num) + case rvk >= reflect.Uint && rvk <= reflect.Uint64: + unum := uint64(num) + if rvk == reflect.Uint8 && (num < 0 || unum > math.MaxUint8) || + rvk == reflect.Uint16 && (num < 0 || unum > math.MaxUint16) || + rvk == reflect.Uint32 && (num < 0 || unum > math.MaxUint32) { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } + rv.SetUint(unum) + default: + panic("unreachable") + } + return nil +} + +func (md *MetaData) unifyBool(data any, rv reflect.Value) error { + if b, ok := data.(bool); ok { + rv.SetBool(b) + return nil + } + return md.badtype("boolean", data) +} + +func (md *MetaData) unifyAnything(data any, rv reflect.Value) error { + rv.Set(reflect.ValueOf(data)) + return nil +} + +func (md *MetaData) unifyText(data any, v encoding.TextUnmarshaler) error { + var s string + switch sdata := data.(type) { + case Marshaler: + text, err := sdata.MarshalTOML() + if err != nil { + return err + } + s = string(text) + case encoding.TextMarshaler: + text, err := sdata.MarshalText() + if err != nil { + return err + } + s = string(text) + case fmt.Stringer: + s = sdata.String() + case string: + s = sdata + case bool: + s = fmt.Sprintf("%v", sdata) + case int64: + s = fmt.Sprintf("%d", sdata) + case float64: + s = fmt.Sprintf("%f", sdata) + default: + return md.badtype("primitive (string-like)", data) + } + if err := v.UnmarshalText([]byte(s)); err != nil { + return md.parseErr(err) + } + return nil +} + +func (md *MetaData) badtype(dst string, data any) error { + return md.e("incompatible types: TOML value has type %s; destination has type %s", fmtType(data), dst) +} + +func (md *MetaData) parseErr(err error) error { + k := md.context.String() + d := string(md.data) + return ParseError{ + Message: err.Error(), + err: err, + LastKey: k, + Position: md.keyInfo[k].pos.withCol(d), + Line: md.keyInfo[k].pos.Line, + input: d, + } +} + +func (md *MetaData) e(format string, args ...any) error { + f := "toml: " + if len(md.context) > 0 { + f = fmt.Sprintf("toml: (last key %q): ", md.context) + p := md.keyInfo[md.context.String()].pos + if p.Line > 0 { + f = fmt.Sprintf("toml: line %d (last key %q): ", p.Line, md.context) + } + } + return fmt.Errorf(f+format, args...) +} + +// rvalue returns a reflect.Value of `v`. All pointers are resolved. +func rvalue(v any) reflect.Value { + return indirect(reflect.ValueOf(v)) +} + +// indirect returns the value pointed to by a pointer. +// +// Pointers are followed until the value is not a pointer. New values are +// allocated for each nil pointer. +// +// An exception to this rule is if the value satisfies an interface of interest +// to us (like encoding.TextUnmarshaler). +func indirect(v reflect.Value) reflect.Value { + if v.Kind() != reflect.Ptr { + if v.CanSet() { + pv := v.Addr() + pvi := pv.Interface() + if _, ok := pvi.(encoding.TextUnmarshaler); ok { + return pv + } + if _, ok := pvi.(Unmarshaler); ok { + return pv + } + } + return v + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + return indirect(reflect.Indirect(v)) +} + +func isUnifiable(rv reflect.Value) bool { + if rv.CanSet() { + return true + } + rvi := rv.Interface() + if _, ok := rvi.(encoding.TextUnmarshaler); ok { + return true + } + if _, ok := rvi.(Unmarshaler); ok { + return true + } + return false +} + +// fmt %T with "interface {}" replaced with "any", which is far more readable. +func fmtType(t any) string { + return strings.ReplaceAll(fmt.Sprintf("%T", t), "interface {}", "any") +} diff --git a/vendor/github.com/BurntSushi/toml/deprecated.go b/vendor/github.com/BurntSushi/toml/deprecated.go new file mode 100644 index 00000000..155709a8 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/deprecated.go @@ -0,0 +1,29 @@ +package toml + +import ( + "encoding" + "io" +) + +// TextMarshaler is an alias for encoding.TextMarshaler. +// +// Deprecated: use encoding.TextMarshaler +type TextMarshaler encoding.TextMarshaler + +// TextUnmarshaler is an alias for encoding.TextUnmarshaler. +// +// Deprecated: use encoding.TextUnmarshaler +type TextUnmarshaler encoding.TextUnmarshaler + +// DecodeReader is an alias for NewDecoder(r).Decode(v). +// +// Deprecated: use NewDecoder(reader).Decode(&value). +func DecodeReader(r io.Reader, v any) (MetaData, error) { return NewDecoder(r).Decode(v) } + +// PrimitiveDecode is an alias for MetaData.PrimitiveDecode(). +// +// Deprecated: use MetaData.PrimitiveDecode. +func PrimitiveDecode(primValue Primitive, v any) error { + md := MetaData{decoded: make(map[string]struct{})} + return md.unify(primValue.undecoded, rvalue(v)) +} diff --git a/vendor/github.com/BurntSushi/toml/doc.go b/vendor/github.com/BurntSushi/toml/doc.go new file mode 100644 index 00000000..82c90a90 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/doc.go @@ -0,0 +1,8 @@ +// Package toml implements decoding and encoding of TOML files. +// +// This package supports TOML v1.0.0, as specified at https://toml.io +// +// The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator, +// and can be used to verify if TOML document is valid. It can also be used to +// print the type of each key. +package toml diff --git a/vendor/github.com/BurntSushi/toml/encode.go b/vendor/github.com/BurntSushi/toml/encode.go new file mode 100644 index 00000000..ac196e7d --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/encode.go @@ -0,0 +1,776 @@ +package toml + +import ( + "bufio" + "bytes" + "encoding" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "reflect" + "sort" + "strconv" + "strings" + "time" + + "github.com/BurntSushi/toml/internal" +) + +type tomlEncodeError struct{ error } + +var ( + errArrayNilElement = errors.New("toml: cannot encode array with nil element") + errNonString = errors.New("toml: cannot encode a map with non-string key type") + errNoKey = errors.New("toml: top-level values must be Go maps or structs") + errAnything = errors.New("") // used in testing +) + +var dblQuotedReplacer = strings.NewReplacer( + "\"", "\\\"", + "\\", "\\\\", + "\x00", `\u0000`, + "\x01", `\u0001`, + "\x02", `\u0002`, + "\x03", `\u0003`, + "\x04", `\u0004`, + "\x05", `\u0005`, + "\x06", `\u0006`, + "\x07", `\u0007`, + "\b", `\b`, + "\t", `\t`, + "\n", `\n`, + "\x0b", `\u000b`, + "\f", `\f`, + "\r", `\r`, + "\x0e", `\u000e`, + "\x0f", `\u000f`, + "\x10", `\u0010`, + "\x11", `\u0011`, + "\x12", `\u0012`, + "\x13", `\u0013`, + "\x14", `\u0014`, + "\x15", `\u0015`, + "\x16", `\u0016`, + "\x17", `\u0017`, + "\x18", `\u0018`, + "\x19", `\u0019`, + "\x1a", `\u001a`, + "\x1b", `\u001b`, + "\x1c", `\u001c`, + "\x1d", `\u001d`, + "\x1e", `\u001e`, + "\x1f", `\u001f`, + "\x7f", `\u007f`, +) + +var ( + marshalToml = reflect.TypeOf((*Marshaler)(nil)).Elem() + marshalText = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + timeType = reflect.TypeOf((*time.Time)(nil)).Elem() +) + +// Marshaler is the interface implemented by types that can marshal themselves +// into valid TOML. +type Marshaler interface { + MarshalTOML() ([]byte, error) +} + +// Marshal returns a TOML representation of the Go value. +// +// See [Encoder] for a description of the encoding process. +func Marshal(v any) ([]byte, error) { + buff := new(bytes.Buffer) + if err := NewEncoder(buff).Encode(v); err != nil { + return nil, err + } + return buff.Bytes(), nil +} + +// Encoder encodes a Go to a TOML document. +// +// The mapping between Go values and TOML values should be precisely the same as +// for [Decode]. +// +// time.Time is encoded as a RFC 3339 string, and time.Duration as its string +// representation. +// +// The [Marshaler] and [encoding.TextMarshaler] interfaces are supported to +// encoding the value as custom TOML. +// +// If you want to write arbitrary binary data then you will need to use +// something like base64 since TOML does not have any binary types. +// +// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes +// are encoded first. +// +// Go maps will be sorted alphabetically by key for deterministic output. +// +// The toml struct tag can be used to provide the key name; if omitted the +// struct field name will be used. If the "omitempty" option is present the +// following value will be skipped: +// +// - arrays, slices, maps, and string with len of 0 +// - struct with all zero values +// - bool false +// +// If omitzero is given all int and float types with a value of 0 will be +// skipped. +// +// Encoding Go values without a corresponding TOML representation will return an +// error. Examples of this includes maps with non-string keys, slices with nil +// elements, embedded non-struct types, and nested slices containing maps or +// structs. (e.g. [][]map[string]string is not allowed but []map[string]string +// is okay, as is []map[string][]string). +// +// NOTE: only exported keys are encoded due to the use of reflection. Unexported +// keys are silently discarded. +type Encoder struct { + Indent string // string for a single indentation level; default is two spaces. + hasWritten bool // written any output to w yet? + w *bufio.Writer +} + +// NewEncoder create a new Encoder. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{w: bufio.NewWriter(w), Indent: " "} +} + +// Encode writes a TOML representation of the Go value to the [Encoder]'s writer. +// +// An error is returned if the value given cannot be encoded to a valid TOML +// document. +func (enc *Encoder) Encode(v any) error { + rv := eindirect(reflect.ValueOf(v)) + err := enc.safeEncode(Key([]string{}), rv) + if err != nil { + return err + } + return enc.w.Flush() +} + +func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { + defer func() { + if r := recover(); r != nil { + if terr, ok := r.(tomlEncodeError); ok { + err = terr.error + return + } + panic(r) + } + }() + enc.encode(key, rv) + return nil +} + +func (enc *Encoder) encode(key Key, rv reflect.Value) { + // If we can marshal the type to text, then we use that. This prevents the + // encoder for handling these types as generic structs (or whatever the + // underlying type of a TextMarshaler is). + switch { + case isMarshaler(rv): + enc.writeKeyValue(key, rv, false) + return + case rv.Type() == primitiveType: // TODO: #76 would make this superfluous after implemented. + enc.encode(key, reflect.ValueOf(rv.Interface().(Primitive).undecoded)) + return + } + + k := rv.Kind() + switch k { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64, + reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: + enc.writeKeyValue(key, rv, false) + case reflect.Array, reflect.Slice: + if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { + enc.eArrayOfTables(key, rv) + } else { + enc.writeKeyValue(key, rv, false) + } + case reflect.Interface: + if rv.IsNil() { + return + } + enc.encode(key, rv.Elem()) + case reflect.Map: + if rv.IsNil() { + return + } + enc.eTable(key, rv) + case reflect.Ptr: + if rv.IsNil() { + return + } + enc.encode(key, rv.Elem()) + case reflect.Struct: + enc.eTable(key, rv) + default: + encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k)) + } +} + +// eElement encodes any value that can be an array element. +func (enc *Encoder) eElement(rv reflect.Value) { + switch v := rv.Interface().(type) { + case time.Time: // Using TextMarshaler adds extra quotes, which we don't want. + format := time.RFC3339Nano + switch v.Location() { + case internal.LocalDatetime: + format = "2006-01-02T15:04:05.999999999" + case internal.LocalDate: + format = "2006-01-02" + case internal.LocalTime: + format = "15:04:05.999999999" + } + switch v.Location() { + default: + enc.wf(v.Format(format)) + case internal.LocalDatetime, internal.LocalDate, internal.LocalTime: + enc.wf(v.In(time.UTC).Format(format)) + } + return + case Marshaler: + s, err := v.MarshalTOML() + if err != nil { + encPanic(err) + } + if s == nil { + encPanic(errors.New("MarshalTOML returned nil and no error")) + } + enc.w.Write(s) + return + case encoding.TextMarshaler: + s, err := v.MarshalText() + if err != nil { + encPanic(err) + } + if s == nil { + encPanic(errors.New("MarshalText returned nil and no error")) + } + enc.writeQuoted(string(s)) + return + case time.Duration: + enc.writeQuoted(v.String()) + return + case json.Number: + n, _ := rv.Interface().(json.Number) + + if n == "" { /// Useful zero value. + enc.w.WriteByte('0') + return + } else if v, err := n.Int64(); err == nil { + enc.eElement(reflect.ValueOf(v)) + return + } else if v, err := n.Float64(); err == nil { + enc.eElement(reflect.ValueOf(v)) + return + } + encPanic(fmt.Errorf("unable to convert %q to int64 or float64", n)) + } + + switch rv.Kind() { + case reflect.Ptr: + enc.eElement(rv.Elem()) + return + case reflect.String: + enc.writeQuoted(rv.String()) + case reflect.Bool: + enc.wf(strconv.FormatBool(rv.Bool())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + enc.wf(strconv.FormatInt(rv.Int(), 10)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + enc.wf(strconv.FormatUint(rv.Uint(), 10)) + case reflect.Float32: + f := rv.Float() + if math.IsNaN(f) { + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("nan") + } else if math.IsInf(f, 0) { + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("inf") + } else { + enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32))) + } + case reflect.Float64: + f := rv.Float() + if math.IsNaN(f) { + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("nan") + } else if math.IsInf(f, 0) { + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("inf") + } else { + enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64))) + } + case reflect.Array, reflect.Slice: + enc.eArrayOrSliceElement(rv) + case reflect.Struct: + enc.eStruct(nil, rv, true) + case reflect.Map: + enc.eMap(nil, rv, true) + case reflect.Interface: + enc.eElement(rv.Elem()) + default: + encPanic(fmt.Errorf("unexpected type: %s", fmtType(rv.Interface()))) + } +} + +// By the TOML spec, all floats must have a decimal with at least one number on +// either side. +func floatAddDecimal(fstr string) string { + if !strings.Contains(fstr, ".") { + return fstr + ".0" + } + return fstr +} + +func (enc *Encoder) writeQuoted(s string) { + enc.wf("\"%s\"", dblQuotedReplacer.Replace(s)) +} + +func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { + length := rv.Len() + enc.wf("[") + for i := 0; i < length; i++ { + elem := eindirect(rv.Index(i)) + enc.eElement(elem) + if i != length-1 { + enc.wf(", ") + } + } + enc.wf("]") +} + +func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { + if len(key) == 0 { + encPanic(errNoKey) + } + for i := 0; i < rv.Len(); i++ { + trv := eindirect(rv.Index(i)) + if isNil(trv) { + continue + } + enc.newline() + enc.wf("%s[[%s]]", enc.indentStr(key), key) + enc.newline() + enc.eMapOrStruct(key, trv, false) + } +} + +func (enc *Encoder) eTable(key Key, rv reflect.Value) { + if len(key) == 1 { + // Output an extra newline between top-level tables. + // (The newline isn't written if nothing else has been written though.) + enc.newline() + } + if len(key) > 0 { + enc.wf("%s[%s]", enc.indentStr(key), key) + enc.newline() + } + enc.eMapOrStruct(key, rv, false) +} + +func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) { + switch rv.Kind() { + case reflect.Map: + enc.eMap(key, rv, inline) + case reflect.Struct: + enc.eStruct(key, rv, inline) + default: + // Should never happen? + panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) + } +} + +func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) { + rt := rv.Type() + if rt.Key().Kind() != reflect.String { + encPanic(errNonString) + } + + // Sort keys so that we have deterministic output. And write keys directly + // underneath this key first, before writing sub-structs or sub-maps. + var mapKeysDirect, mapKeysSub []reflect.Value + for _, mapKey := range rv.MapKeys() { + if typeIsTable(tomlTypeOfGo(eindirect(rv.MapIndex(mapKey)))) { + mapKeysSub = append(mapKeysSub, mapKey) + } else { + mapKeysDirect = append(mapKeysDirect, mapKey) + } + } + + writeMapKeys := func(mapKeys []reflect.Value, trailC bool) { + sort.Slice(mapKeys, func(i, j int) bool { return mapKeys[i].String() < mapKeys[j].String() }) + for i, mapKey := range mapKeys { + val := eindirect(rv.MapIndex(mapKey)) + if isNil(val) { + continue + } + + if inline { + enc.writeKeyValue(Key{mapKey.String()}, val, true) + if trailC || i != len(mapKeys)-1 { + enc.wf(", ") + } + } else { + enc.encode(key.add(mapKey.String()), val) + } + } + } + + if inline { + enc.wf("{") + } + writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0) + writeMapKeys(mapKeysSub, false) + if inline { + enc.wf("}") + } +} + +func pointerTo(t reflect.Type) reflect.Type { + if t.Kind() == reflect.Ptr { + return pointerTo(t.Elem()) + } + return t +} + +func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) { + // Write keys for fields directly under this key first, because if we write + // a field that creates a new table then all keys under it will be in that + // table (not the one we're writing here). + // + // Fields is a [][]int: for fieldsDirect this always has one entry (the + // struct index). For fieldsSub it contains two entries: the parent field + // index from tv, and the field indexes for the fields of the sub. + var ( + rt = rv.Type() + fieldsDirect, fieldsSub [][]int + addFields func(rt reflect.Type, rv reflect.Value, start []int) + ) + addFields = func(rt reflect.Type, rv reflect.Value, start []int) { + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + isEmbed := f.Anonymous && pointerTo(f.Type).Kind() == reflect.Struct + if f.PkgPath != "" && !isEmbed { /// Skip unexported fields. + continue + } + opts := getOptions(f.Tag) + if opts.skip { + continue + } + + frv := eindirect(rv.Field(i)) + + // Need to make a copy because ... ehm, I don't know why... I guess + // allocating a new array can cause it to fail(?) + // + // Done for: https://github.com/BurntSushi/toml/issues/430 + // Previously only on 32bit for: https://github.com/BurntSushi/toml/issues/314 + copyStart := make([]int, len(start)) + copy(copyStart, start) + start = copyStart + + // Treat anonymous struct fields with tag names as though they are + // not anonymous, like encoding/json does. + // + // Non-struct anonymous fields use the normal encoding logic. + if isEmbed { + if getOptions(f.Tag).name == "" && frv.Kind() == reflect.Struct { + addFields(frv.Type(), frv, append(start, f.Index...)) + continue + } + } + + if typeIsTable(tomlTypeOfGo(frv)) { + fieldsSub = append(fieldsSub, append(start, f.Index...)) + } else { + fieldsDirect = append(fieldsDirect, append(start, f.Index...)) + } + } + } + addFields(rt, rv, nil) + + writeFields := func(fields [][]int, totalFields int) { + for _, fieldIndex := range fields { + fieldType := rt.FieldByIndex(fieldIndex) + fieldVal := rv.FieldByIndex(fieldIndex) + + opts := getOptions(fieldType.Tag) + if opts.skip { + continue + } + if opts.omitempty && isEmpty(fieldVal) { + continue + } + + fieldVal = eindirect(fieldVal) + + if isNil(fieldVal) { /// Don't write anything for nil fields. + continue + } + + keyName := fieldType.Name + if opts.name != "" { + keyName = opts.name + } + + if opts.omitzero && isZero(fieldVal) { + continue + } + + if inline { + enc.writeKeyValue(Key{keyName}, fieldVal, true) + if fieldIndex[0] != totalFields-1 { + enc.wf(", ") + } + } else { + enc.encode(key.add(keyName), fieldVal) + } + } + } + + if inline { + enc.wf("{") + } + + l := len(fieldsDirect) + len(fieldsSub) + writeFields(fieldsDirect, l) + writeFields(fieldsSub, l) + if inline { + enc.wf("}") + } +} + +// tomlTypeOfGo returns the TOML type name of the Go value's type. +// +// It is used to determine whether the types of array elements are mixed (which +// is forbidden). If the Go value is nil, then it is illegal for it to be an +// array element, and valueIsNil is returned as true. +// +// The type may be `nil`, which means no concrete TOML type could be found. +func tomlTypeOfGo(rv reflect.Value) tomlType { + if isNil(rv) || !rv.IsValid() { + return nil + } + + if rv.Kind() == reflect.Struct { + if rv.Type() == timeType { + return tomlDatetime + } + if isMarshaler(rv) { + return tomlString + } + return tomlHash + } + + if isMarshaler(rv) { + return tomlString + } + + switch rv.Kind() { + case reflect.Bool: + return tomlBool + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64: + return tomlInteger + case reflect.Float32, reflect.Float64: + return tomlFloat + case reflect.Array, reflect.Slice: + if isTableArray(rv) { + return tomlArrayHash + } + return tomlArray + case reflect.Ptr, reflect.Interface: + return tomlTypeOfGo(rv.Elem()) + case reflect.String: + return tomlString + case reflect.Map: + return tomlHash + default: + encPanic(errors.New("unsupported type: " + rv.Kind().String())) + panic("unreachable") + } +} + +func isMarshaler(rv reflect.Value) bool { + return rv.Type().Implements(marshalText) || rv.Type().Implements(marshalToml) +} + +// isTableArray reports if all entries in the array or slice are a table. +func isTableArray(arr reflect.Value) bool { + if isNil(arr) || !arr.IsValid() || arr.Len() == 0 { + return false + } + + ret := true + for i := 0; i < arr.Len(); i++ { + tt := tomlTypeOfGo(eindirect(arr.Index(i))) + // Don't allow nil. + if tt == nil { + encPanic(errArrayNilElement) + } + + if ret && !typeEqual(tomlHash, tt) { + ret = false + } + } + return ret +} + +type tagOptions struct { + skip bool // "-" + name string + omitempty bool + omitzero bool +} + +func getOptions(tag reflect.StructTag) tagOptions { + t := tag.Get("toml") + if t == "-" { + return tagOptions{skip: true} + } + var opts tagOptions + parts := strings.Split(t, ",") + opts.name = parts[0] + for _, s := range parts[1:] { + switch s { + case "omitempty": + opts.omitempty = true + case "omitzero": + opts.omitzero = true + } + } + return opts +} + +func isZero(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rv.Uint() == 0 + case reflect.Float32, reflect.Float64: + return rv.Float() == 0.0 + } + return false +} + +func isEmpty(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Array, reflect.Slice, reflect.Map, reflect.String: + return rv.Len() == 0 + case reflect.Struct: + if rv.Type().Comparable() { + return reflect.Zero(rv.Type()).Interface() == rv.Interface() + } + // Need to also check if all the fields are empty, otherwise something + // like this with uncomparable types will always return true: + // + // type a struct{ field b } + // type b struct{ s []string } + // s := a{field: b{s: []string{"AAA"}}} + for i := 0; i < rv.NumField(); i++ { + if !isEmpty(rv.Field(i)) { + return false + } + } + return true + case reflect.Bool: + return !rv.Bool() + case reflect.Ptr: + return rv.IsNil() + } + return false +} + +func (enc *Encoder) newline() { + if enc.hasWritten { + enc.wf("\n") + } +} + +// Write a key/value pair: +// +// key = +// +// This is also used for "k = v" in inline tables; so something like this will +// be written in three calls: +// +// ┌───────────────────┐ +// │ ┌───┐ ┌────┐│ +// v v v v vv +// key = {k = 1, k2 = 2} +func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) { + /// Marshaler used on top-level document; call eElement() to just call + /// Marshal{TOML,Text}. + if len(key) == 0 { + enc.eElement(val) + return + } + enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) + enc.eElement(val) + if !inline { + enc.newline() + } +} + +func (enc *Encoder) wf(format string, v ...any) { + _, err := fmt.Fprintf(enc.w, format, v...) + if err != nil { + encPanic(err) + } + enc.hasWritten = true +} + +func (enc *Encoder) indentStr(key Key) string { + return strings.Repeat(enc.Indent, len(key)-1) +} + +func encPanic(err error) { + panic(tomlEncodeError{err}) +} + +// Resolve any level of pointers to the actual value (e.g. **string → string). +func eindirect(v reflect.Value) reflect.Value { + if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface { + if isMarshaler(v) { + return v + } + if v.CanAddr() { /// Special case for marshalers; see #358. + if pv := v.Addr(); isMarshaler(pv) { + return pv + } + } + return v + } + + if v.IsNil() { + return v + } + + return eindirect(v.Elem()) +} + +func isNil(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return rv.IsNil() + default: + return false + } +} diff --git a/vendor/github.com/BurntSushi/toml/error.go b/vendor/github.com/BurntSushi/toml/error.go new file mode 100644 index 00000000..b7077d3a --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/error.go @@ -0,0 +1,347 @@ +package toml + +import ( + "fmt" + "strings" +) + +// ParseError is returned when there is an error parsing the TOML syntax such as +// invalid syntax, duplicate keys, etc. +// +// In addition to the error message itself, you can also print detailed location +// information with context by using [ErrorWithPosition]: +// +// toml: error: Key 'fruit' was already created and cannot be used as an array. +// +// At line 4, column 2-7: +// +// 2 | fruit = [] +// 3 | +// 4 | [[fruit]] # Not allowed +// ^^^^^ +// +// [ErrorWithUsage] can be used to print the above with some more detailed usage +// guidance: +// +// toml: error: newlines not allowed within inline tables +// +// At line 1, column 18: +// +// 1 | x = [{ key = 42 # +// ^ +// +// Error help: +// +// Inline tables must always be on a single line: +// +// table = {key = 42, second = 43} +// +// It is invalid to split them over multiple lines like so: +// +// # INVALID +// table = { +// key = 42, +// second = 43 +// } +// +// Use regular for this: +// +// [table] +// key = 42 +// second = 43 +type ParseError struct { + Message string // Short technical message. + Usage string // Longer message with usage guidance; may be blank. + Position Position // Position of the error + LastKey string // Last parsed key, may be blank. + + // Line the error occurred. + // + // Deprecated: use [Position]. + Line int + + err error + input string +} + +// Position of an error. +type Position struct { + Line int // Line number, starting at 1. + Col int // Error column, starting at 1. + Start int // Start of error, as byte offset starting at 0. + Len int // Length of the error in bytes. +} + +func (p Position) withCol(tomlFile string) Position { + var ( + pos int + lines = strings.Split(tomlFile, "\n") + ) + for i := range lines { + ll := len(lines[i]) + 1 // +1 for the removed newline + if pos+ll >= p.Start { + p.Col = p.Start - pos + 1 + if p.Col < 1 { // Should never happen, but just in case. + p.Col = 1 + } + break + } + pos += ll + } + return p +} + +func (pe ParseError) Error() string { + if pe.LastKey == "" { + return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, pe.Message) + } + return fmt.Sprintf("toml: line %d (last key %q): %s", + pe.Position.Line, pe.LastKey, pe.Message) +} + +// ErrorWithPosition returns the error with detailed location context. +// +// See the documentation on [ParseError]. +func (pe ParseError) ErrorWithPosition() string { + if pe.input == "" { // Should never happen, but just in case. + return pe.Error() + } + + // TODO: don't show control characters as literals? This may not show up + // well everywhere. + + var ( + lines = strings.Split(pe.input, "\n") + b = new(strings.Builder) + ) + if pe.Position.Len == 1 { + fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n", + pe.Message, pe.Position.Line, pe.Position.Col) + } else { + fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n", + pe.Message, pe.Position.Line, pe.Position.Col, pe.Position.Col+pe.Position.Len-1) + } + if pe.Position.Line > 2 { + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, expandTab(lines[pe.Position.Line-3])) + } + if pe.Position.Line > 1 { + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, expandTab(lines[pe.Position.Line-2])) + } + + /// Expand tabs, so that the ^^^s are at the correct position, but leave + /// "column 10-13" intact. Adjusting this to the visual column would be + /// better, but we don't know the tabsize of the user in their editor, which + /// can be 8, 4, 2, or something else. We can't know. So leaving it as the + /// character index is probably the "most correct". + expanded := expandTab(lines[pe.Position.Line-1]) + diff := len(expanded) - len(lines[pe.Position.Line-1]) + + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, expanded) + fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", pe.Position.Col-1+diff), strings.Repeat("^", pe.Position.Len)) + return b.String() +} + +// ErrorWithUsage returns the error with detailed location context and usage +// guidance. +// +// See the documentation on [ParseError]. +func (pe ParseError) ErrorWithUsage() string { + m := pe.ErrorWithPosition() + if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" { + lines := strings.Split(strings.TrimSpace(u.Usage()), "\n") + for i := range lines { + if lines[i] != "" { + lines[i] = " " + lines[i] + } + } + return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n" + } + return m +} + +func expandTab(s string) string { + var ( + b strings.Builder + l int + fill = func(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = ' ' + } + return string(b) + } + ) + b.Grow(len(s)) + for _, r := range s { + switch r { + case '\t': + tw := 8 - l%8 + b.WriteString(fill(tw)) + l += tw + default: + b.WriteRune(r) + l += 1 + } + } + return b.String() +} + +type ( + errLexControl struct{ r rune } + errLexEscape struct{ r rune } + errLexUTF8 struct{ b byte } + errParseDate struct{ v string } + errLexInlineTableNL struct{} + errLexStringNL struct{} + errParseRange struct { + i any // int or float + size string // "int64", "uint16", etc. + } + errUnsafeFloat struct { + i interface{} // float32 or float64 + size string // "float32" or "float64" + } + errParseDuration struct{ d string } +) + +func (e errLexControl) Error() string { + return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r) +} +func (e errLexControl) Usage() string { return "" } + +func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) } +func (e errLexEscape) Usage() string { return usageEscape } +func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) } +func (e errLexUTF8) Usage() string { return "" } +func (e errParseDate) Error() string { return fmt.Sprintf("invalid datetime: %q", e.v) } +func (e errParseDate) Usage() string { return usageDate } +func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" } +func (e errLexInlineTableNL) Usage() string { return usageInlineNewline } +func (e errLexStringNL) Error() string { return "strings cannot contain newlines" } +func (e errLexStringNL) Usage() string { return usageStringNewline } +func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) } +func (e errParseRange) Usage() string { return usageIntOverflow } +func (e errUnsafeFloat) Error() string { + return fmt.Sprintf("%v is out of the safe %s range", e.i, e.size) +} +func (e errUnsafeFloat) Usage() string { return usageUnsafeFloat } +func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) } +func (e errParseDuration) Usage() string { return usageDuration } + +const usageEscape = ` +A '\' inside a "-delimited string is interpreted as an escape character. + +The following escape sequences are supported: +\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX + +To prevent a '\' from being recognized as an escape character, use either: + +- a ' or '''-delimited string; escape characters aren't processed in them; or +- write two backslashes to get a single backslash: '\\'. + +If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/' +instead of '\' will usually also work: "C:/Users/martin". +` + +const usageInlineNewline = ` +Inline tables must always be on a single line: + + table = {key = 42, second = 43} + +It is invalid to split them over multiple lines like so: + + # INVALID + table = { + key = 42, + second = 43 + } + +Use regular for this: + + [table] + key = 42 + second = 43 +` + +const usageStringNewline = ` +Strings must always be on a single line, and cannot span more than one line: + + # INVALID + string = "Hello, + world!" + +Instead use """ or ''' to split strings over multiple lines: + + string = """Hello, + world!""" +` + +const usageIntOverflow = ` +This number is too large; this may be an error in the TOML, but it can also be a +bug in the program that uses too small of an integer. + +The maximum and minimum values are: + + size │ lowest │ highest + ───────┼────────────────┼────────────── + int8 │ -128 │ 127 + int16 │ -32,768 │ 32,767 + int32 │ -2,147,483,648 │ 2,147,483,647 + int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷ + uint8 │ 0 │ 255 + uint16 │ 0 │ 65,535 + uint32 │ 0 │ 4,294,967,295 + uint64 │ 0 │ 1.8 × 10¹⁸ + +int refers to int32 on 32-bit systems and int64 on 64-bit systems. +` + +const usageUnsafeFloat = ` +This number is outside of the "safe" range for floating point numbers; whole +(non-fractional) numbers outside the below range can not always be represented +accurately in a float, leading to some loss of accuracy. + +Explicitly mark a number as a fractional unit by adding ".0", which will incur +some loss of accuracy; for example: + + f = 2_000_000_000.0 + +Accuracy ranges: + + float32 = 16,777,215 + float64 = 9,007,199,254,740,991 +` + +const usageDuration = ` +A duration must be as "number", without any spaces. Valid units are: + + ns nanoseconds (billionth of a second) + us, µs microseconds (millionth of a second) + ms milliseconds (thousands of a second) + s seconds + m minutes + h hours + +You can combine multiple units; for example "5m10s" for 5 minutes and 10 +seconds. +` + +const usageDate = ` +A TOML datetime must be in one of the following formats: + + 2006-01-02T15:04:05Z07:00 Date and time, with timezone. + 2006-01-02T15:04:05 Date and time, but without timezone. + 2006-01-02 Date without a time or timezone. + 15:04:05 Just a time, without any timezone. + +Seconds may optionally have a fraction, up to nanosecond precision: + + 15:04:05.123 + 15:04:05.856018510 +` + +// TOML 1.1: +// The seconds part in times is optional, and may be omitted: +// 2006-01-02T15:04Z07:00 +// 2006-01-02T15:04 +// 15:04 diff --git a/vendor/github.com/BurntSushi/toml/internal/tz.go b/vendor/github.com/BurntSushi/toml/internal/tz.go new file mode 100644 index 00000000..022f15bc --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/internal/tz.go @@ -0,0 +1,36 @@ +package internal + +import "time" + +// Timezones used for local datetime, date, and time TOML types. +// +// The exact way times and dates without a timezone should be interpreted is not +// well-defined in the TOML specification and left to the implementation. These +// defaults to current local timezone offset of the computer, but this can be +// changed by changing these variables before decoding. +// +// TODO: +// Ideally we'd like to offer people the ability to configure the used timezone +// by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit +// tricky: the reason we use three different variables for this is to support +// round-tripping – without these specific TZ names we wouldn't know which +// format to use. +// +// There isn't a good way to encode this right now though, and passing this sort +// of information also ties in to various related issues such as string format +// encoding, encoding of comments, etc. +// +// So, for the time being, just put this in internal until we can write a good +// comprehensive API for doing all of this. +// +// The reason they're exported is because they're referred from in e.g. +// internal/tag. +// +// Note that this behaviour is valid according to the TOML spec as the exact +// behaviour is left up to implementations. +var ( + localOffset = func() int { _, o := time.Now().Zone(); return o }() + LocalDatetime = time.FixedZone("datetime-local", localOffset) + LocalDate = time.FixedZone("date-local", localOffset) + LocalTime = time.FixedZone("time-local", localOffset) +) diff --git a/vendor/github.com/BurntSushi/toml/lex.go b/vendor/github.com/BurntSushi/toml/lex.go new file mode 100644 index 00000000..1c3b4770 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/lex.go @@ -0,0 +1,1272 @@ +package toml + +import ( + "fmt" + "reflect" + "runtime" + "strings" + "unicode" + "unicode/utf8" +) + +type itemType int + +const ( + itemError itemType = iota + itemNIL // used in the parser to indicate no type + itemEOF + itemText + itemString + itemStringEsc + itemRawString + itemMultilineString + itemRawMultilineString + itemBool + itemInteger + itemFloat + itemDatetime + itemArray // the start of an array + itemArrayEnd + itemTableStart + itemTableEnd + itemArrayTableStart + itemArrayTableEnd + itemKeyStart + itemKeyEnd + itemCommentStart + itemInlineTableStart + itemInlineTableEnd +) + +const eof = 0 + +type stateFn func(lx *lexer) stateFn + +func (p Position) String() string { + return fmt.Sprintf("at line %d; start %d; length %d", p.Line, p.Start, p.Len) +} + +type lexer struct { + input string + start int + pos int + line int + state stateFn + items chan item + tomlNext bool + esc bool + + // Allow for backing up up to 4 runes. This is necessary because TOML + // contains 3-rune tokens (""" and '''). + prevWidths [4]int + nprev int // how many of prevWidths are in use + atEOF bool // If we emit an eof, we can still back up, but it is not OK to call next again. + + // A stack of state functions used to maintain context. + // + // The idea is to reuse parts of the state machine in various places. For + // example, values can appear at the top level or within arbitrarily nested + // arrays. The last state on the stack is used after a value has been lexed. + // Similarly for comments. + stack []stateFn +} + +type item struct { + typ itemType + val string + err error + pos Position +} + +func (lx *lexer) nextItem() item { + for { + select { + case item := <-lx.items: + return item + default: + lx.state = lx.state(lx) + //fmt.Printf(" STATE %-24s current: %-10s stack: %s\n", lx.state, lx.current(), lx.stack) + } + } +} + +func lex(input string, tomlNext bool) *lexer { + lx := &lexer{ + input: input, + state: lexTop, + items: make(chan item, 10), + stack: make([]stateFn, 0, 10), + line: 1, + tomlNext: tomlNext, + } + return lx +} + +func (lx *lexer) push(state stateFn) { + lx.stack = append(lx.stack, state) +} + +func (lx *lexer) pop() stateFn { + if len(lx.stack) == 0 { + return lx.errorf("BUG in lexer: no states to pop") + } + last := lx.stack[len(lx.stack)-1] + lx.stack = lx.stack[0 : len(lx.stack)-1] + return last +} + +func (lx *lexer) current() string { + return lx.input[lx.start:lx.pos] +} + +func (lx lexer) getPos() Position { + p := Position{ + Line: lx.line, + Start: lx.start, + Len: lx.pos - lx.start, + } + if p.Len <= 0 { + p.Len = 1 + } + return p +} + +func (lx *lexer) emit(typ itemType) { + // Needed for multiline strings ending with an incomplete UTF-8 sequence. + if lx.start > lx.pos { + lx.error(errLexUTF8{lx.input[lx.pos]}) + return + } + lx.items <- item{typ: typ, pos: lx.getPos(), val: lx.current()} + lx.start = lx.pos +} + +func (lx *lexer) emitTrim(typ itemType) { + lx.items <- item{typ: typ, pos: lx.getPos(), val: strings.TrimSpace(lx.current())} + lx.start = lx.pos +} + +func (lx *lexer) next() (r rune) { + if lx.atEOF { + panic("BUG in lexer: next called after EOF") + } + if lx.pos >= len(lx.input) { + lx.atEOF = true + return eof + } + + if lx.input[lx.pos] == '\n' { + lx.line++ + } + lx.prevWidths[3] = lx.prevWidths[2] + lx.prevWidths[2] = lx.prevWidths[1] + lx.prevWidths[1] = lx.prevWidths[0] + if lx.nprev < 4 { + lx.nprev++ + } + + r, w := utf8.DecodeRuneInString(lx.input[lx.pos:]) + if r == utf8.RuneError && w == 1 { + lx.error(errLexUTF8{lx.input[lx.pos]}) + return utf8.RuneError + } + + // Note: don't use peek() here, as this calls next(). + if isControl(r) || (r == '\r' && (len(lx.input)-1 == lx.pos || lx.input[lx.pos+1] != '\n')) { + lx.errorControlChar(r) + return utf8.RuneError + } + + lx.prevWidths[0] = w + lx.pos += w + return r +} + +// ignore skips over the pending input before this point. +func (lx *lexer) ignore() { + lx.start = lx.pos +} + +// backup steps back one rune. Can be called 4 times between calls to next. +func (lx *lexer) backup() { + if lx.atEOF { + lx.atEOF = false + return + } + if lx.nprev < 1 { + panic("BUG in lexer: backed up too far") + } + w := lx.prevWidths[0] + lx.prevWidths[0] = lx.prevWidths[1] + lx.prevWidths[1] = lx.prevWidths[2] + lx.prevWidths[2] = lx.prevWidths[3] + lx.nprev-- + + lx.pos -= w + if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { + lx.line-- + } +} + +// accept consumes the next rune if it's equal to `valid`. +func (lx *lexer) accept(valid rune) bool { + if lx.next() == valid { + return true + } + lx.backup() + return false +} + +// peek returns but does not consume the next rune in the input. +func (lx *lexer) peek() rune { + r := lx.next() + lx.backup() + return r +} + +// skip ignores all input that matches the given predicate. +func (lx *lexer) skip(pred func(rune) bool) { + for { + r := lx.next() + if pred(r) { + continue + } + lx.backup() + lx.ignore() + return + } +} + +// error stops all lexing by emitting an error and returning `nil`. +// +// Note that any value that is a character is escaped if it's a special +// character (newlines, tabs, etc.). +func (lx *lexer) error(err error) stateFn { + if lx.atEOF { + return lx.errorPrevLine(err) + } + lx.items <- item{typ: itemError, pos: lx.getPos(), err: err} + return nil +} + +// errorfPrevline is like error(), but sets the position to the last column of +// the previous line. +// +// This is so that unexpected EOF or NL errors don't show on a new blank line. +func (lx *lexer) errorPrevLine(err error) stateFn { + pos := lx.getPos() + pos.Line-- + pos.Len = 1 + pos.Start = lx.pos - 1 + lx.items <- item{typ: itemError, pos: pos, err: err} + return nil +} + +// errorPos is like error(), but allows explicitly setting the position. +func (lx *lexer) errorPos(start, length int, err error) stateFn { + pos := lx.getPos() + pos.Start = start + pos.Len = length + lx.items <- item{typ: itemError, pos: pos, err: err} + return nil +} + +// errorf is like error, and creates a new error. +func (lx *lexer) errorf(format string, values ...any) stateFn { + if lx.atEOF { + pos := lx.getPos() + if lx.pos >= 1 && lx.input[lx.pos-1] == '\n' { + pos.Line-- + } + pos.Len = 1 + pos.Start = lx.pos - 1 + lx.items <- item{typ: itemError, pos: pos, err: fmt.Errorf(format, values...)} + return nil + } + lx.items <- item{typ: itemError, pos: lx.getPos(), err: fmt.Errorf(format, values...)} + return nil +} + +func (lx *lexer) errorControlChar(cc rune) stateFn { + return lx.errorPos(lx.pos-1, 1, errLexControl{cc}) +} + +// lexTop consumes elements at the top level of TOML data. +func lexTop(lx *lexer) stateFn { + r := lx.next() + if isWhitespace(r) || isNL(r) { + return lexSkip(lx, lexTop) + } + switch r { + case '#': + lx.push(lexTop) + return lexCommentStart + case '[': + return lexTableStart + case eof: + if lx.pos > lx.start { + return lx.errorf("unexpected EOF") + } + lx.emit(itemEOF) + return nil + } + + // At this point, the only valid item can be a key, so we back up + // and let the key lexer do the rest. + lx.backup() + lx.push(lexTopEnd) + return lexKeyStart +} + +// lexTopEnd is entered whenever a top-level item has been consumed. (A value +// or a table.) It must see only whitespace, and will turn back to lexTop +// upon a newline. If it sees EOF, it will quit the lexer successfully. +func lexTopEnd(lx *lexer) stateFn { + r := lx.next() + switch { + case r == '#': + // a comment will read to a newline for us. + lx.push(lexTop) + return lexCommentStart + case isWhitespace(r): + return lexTopEnd + case isNL(r): + lx.ignore() + return lexTop + case r == eof: + lx.emit(itemEOF) + return nil + } + return lx.errorf("expected a top-level item to end with a newline, comment, or EOF, but got %q instead", r) +} + +// lexTable lexes the beginning of a table. Namely, it makes sure that +// it starts with a character other than '.' and ']'. +// It assumes that '[' has already been consumed. +// It also handles the case that this is an item in an array of tables. +// e.g., '[[name]]'. +func lexTableStart(lx *lexer) stateFn { + if lx.peek() == '[' { + lx.next() + lx.emit(itemArrayTableStart) + lx.push(lexArrayTableEnd) + } else { + lx.emit(itemTableStart) + lx.push(lexTableEnd) + } + return lexTableNameStart +} + +func lexTableEnd(lx *lexer) stateFn { + lx.emit(itemTableEnd) + return lexTopEnd +} + +func lexArrayTableEnd(lx *lexer) stateFn { + if r := lx.next(); r != ']' { + return lx.errorf("expected end of table array name delimiter ']', but got %q instead", r) + } + lx.emit(itemArrayTableEnd) + return lexTopEnd +} + +func lexTableNameStart(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.peek(); { + case r == ']' || r == eof: + return lx.errorf("unexpected end of table name (table names cannot be empty)") + case r == '.': + return lx.errorf("unexpected table separator (table names cannot be empty)") + case r == '"' || r == '\'': + lx.ignore() + lx.push(lexTableNameEnd) + return lexQuotedName + default: + lx.push(lexTableNameEnd) + return lexBareName + } +} + +// lexTableNameEnd reads the end of a piece of a table name, optionally +// consuming whitespace. +func lexTableNameEnd(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.next(); { + case isWhitespace(r): + return lexTableNameEnd + case r == '.': + lx.ignore() + return lexTableNameStart + case r == ']': + return lx.pop() + default: + return lx.errorf("expected '.' or ']' to end table name, but got %q instead", r) + } +} + +// lexBareName lexes one part of a key or table. +// +// It assumes that at least one valid character for the table has already been +// read. +// +// Lexes only one part, e.g. only 'a' inside 'a.b'. +func lexBareName(lx *lexer) stateFn { + r := lx.next() + if isBareKeyChar(r, lx.tomlNext) { + return lexBareName + } + lx.backup() + lx.emit(itemText) + return lx.pop() +} + +// lexBareName lexes one part of a key or table. +// +// It assumes that at least one valid character for the table has already been +// read. +// +// Lexes only one part, e.g. only '"a"' inside '"a".b'. +func lexQuotedName(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r): + return lexSkip(lx, lexValue) + case r == '"': + lx.ignore() // ignore the '"' + return lexString + case r == '\'': + lx.ignore() // ignore the "'" + return lexRawString + case r == eof: + return lx.errorf("unexpected EOF; expected value") + default: + return lx.errorf("expected value but found %q instead", r) + } +} + +// lexKeyStart consumes all key parts until a '='. +func lexKeyStart(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.peek(); { + case r == '=' || r == eof: + return lx.errorf("unexpected '=': key name appears blank") + case r == '.': + return lx.errorf("unexpected '.': keys cannot start with a '.'") + case r == '"' || r == '\'': + lx.ignore() + fallthrough + default: // Bare key + lx.emit(itemKeyStart) + return lexKeyNameStart + } +} + +func lexKeyNameStart(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.peek(); { + case r == '=' || r == eof: + return lx.errorf("unexpected '='") + case r == '.': + return lx.errorf("unexpected '.'") + case r == '"' || r == '\'': + lx.ignore() + lx.push(lexKeyEnd) + return lexQuotedName + default: + lx.push(lexKeyEnd) + return lexBareName + } +} + +// lexKeyEnd consumes the end of a key and trims whitespace (up to the key +// separator). +func lexKeyEnd(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.next(); { + case isWhitespace(r): + return lexSkip(lx, lexKeyEnd) + case r == eof: + return lx.errorf("unexpected EOF; expected key separator '='") + case r == '.': + lx.ignore() + return lexKeyNameStart + case r == '=': + lx.emit(itemKeyEnd) + return lexSkip(lx, lexValue) + default: + if r == '\n' { + return lx.errorPrevLine(fmt.Errorf("expected '.' or '=', but got %q instead", r)) + } + return lx.errorf("expected '.' or '=', but got %q instead", r) + } +} + +// lexValue starts the consumption of a value anywhere a value is expected. +// lexValue will ignore whitespace. +// After a value is lexed, the last state on the next is popped and returned. +func lexValue(lx *lexer) stateFn { + // We allow whitespace to precede a value, but NOT newlines. + // In array syntax, the array states are responsible for ignoring newlines. + r := lx.next() + switch { + case isWhitespace(r): + return lexSkip(lx, lexValue) + case isDigit(r): + lx.backup() // avoid an extra state and use the same as above + return lexNumberOrDateStart + } + switch r { + case '[': + lx.ignore() + lx.emit(itemArray) + return lexArrayValue + case '{': + lx.ignore() + lx.emit(itemInlineTableStart) + return lexInlineTableValue + case '"': + if lx.accept('"') { + if lx.accept('"') { + lx.ignore() // Ignore """ + return lexMultilineString + } + lx.backup() + } + lx.ignore() // ignore the '"' + return lexString + case '\'': + if lx.accept('\'') { + if lx.accept('\'') { + lx.ignore() // Ignore """ + return lexMultilineRawString + } + lx.backup() + } + lx.ignore() // ignore the "'" + return lexRawString + case '.': // special error case, be kind to users + return lx.errorf("floats must start with a digit, not '.'") + case 'i', 'n': + if (lx.accept('n') && lx.accept('f')) || (lx.accept('a') && lx.accept('n')) { + lx.emit(itemFloat) + return lx.pop() + } + case '-', '+': + return lexDecimalNumberStart + } + if unicode.IsLetter(r) { + // Be permissive here; lexBool will give a nice error if the + // user wrote something like + // x = foo + // (i.e. not 'true' or 'false' but is something else word-like.) + lx.backup() + return lexBool + } + if r == eof { + return lx.errorf("unexpected EOF; expected value") + } + if r == '\n' { + return lx.errorPrevLine(fmt.Errorf("expected value but found %q instead", r)) + } + return lx.errorf("expected value but found %q instead", r) +} + +// lexArrayValue consumes one value in an array. It assumes that '[' or ',' +// have already been consumed. All whitespace and newlines are ignored. +func lexArrayValue(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r) || isNL(r): + return lexSkip(lx, lexArrayValue) + case r == '#': + lx.push(lexArrayValue) + return lexCommentStart + case r == ',': + return lx.errorf("unexpected comma") + case r == ']': + return lexArrayEnd + } + + lx.backup() + lx.push(lexArrayValueEnd) + return lexValue +} + +// lexArrayValueEnd consumes everything between the end of an array value and +// the next value (or the end of the array): it ignores whitespace and newlines +// and expects either a ',' or a ']'. +func lexArrayValueEnd(lx *lexer) stateFn { + switch r := lx.next(); { + case isWhitespace(r) || isNL(r): + return lexSkip(lx, lexArrayValueEnd) + case r == '#': + lx.push(lexArrayValueEnd) + return lexCommentStart + case r == ',': + lx.ignore() + return lexArrayValue // move on to the next value + case r == ']': + return lexArrayEnd + default: + return lx.errorf("expected a comma (',') or array terminator (']'), but got %s", runeOrEOF(r)) + } +} + +// lexArrayEnd finishes the lexing of an array. +// It assumes that a ']' has just been consumed. +func lexArrayEnd(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemArrayEnd) + return lx.pop() +} + +// lexInlineTableValue consumes one key/value pair in an inline table. +// It assumes that '{' or ',' have already been consumed. Whitespace is ignored. +func lexInlineTableValue(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r): + return lexSkip(lx, lexInlineTableValue) + case isNL(r): + if lx.tomlNext { + return lexSkip(lx, lexInlineTableValue) + } + return lx.errorPrevLine(errLexInlineTableNL{}) + case r == '#': + lx.push(lexInlineTableValue) + return lexCommentStart + case r == ',': + return lx.errorf("unexpected comma") + case r == '}': + return lexInlineTableEnd + } + lx.backup() + lx.push(lexInlineTableValueEnd) + return lexKeyStart +} + +// lexInlineTableValueEnd consumes everything between the end of an inline table +// key/value pair and the next pair (or the end of the table): +// it ignores whitespace and expects either a ',' or a '}'. +func lexInlineTableValueEnd(lx *lexer) stateFn { + switch r := lx.next(); { + case isWhitespace(r): + return lexSkip(lx, lexInlineTableValueEnd) + case isNL(r): + if lx.tomlNext { + return lexSkip(lx, lexInlineTableValueEnd) + } + return lx.errorPrevLine(errLexInlineTableNL{}) + case r == '#': + lx.push(lexInlineTableValueEnd) + return lexCommentStart + case r == ',': + lx.ignore() + lx.skip(isWhitespace) + if lx.peek() == '}' { + if lx.tomlNext { + return lexInlineTableValueEnd + } + return lx.errorf("trailing comma not allowed in inline tables") + } + return lexInlineTableValue + case r == '}': + return lexInlineTableEnd + default: + return lx.errorf("expected a comma or an inline table terminator '}', but got %s instead", runeOrEOF(r)) + } +} + +func runeOrEOF(r rune) string { + if r == eof { + return "end of file" + } + return "'" + string(r) + "'" +} + +// lexInlineTableEnd finishes the lexing of an inline table. +// It assumes that a '}' has just been consumed. +func lexInlineTableEnd(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemInlineTableEnd) + return lx.pop() +} + +// lexString consumes the inner contents of a string. It assumes that the +// beginning '"' has already been consumed and ignored. +func lexString(lx *lexer) stateFn { + r := lx.next() + switch { + case r == eof: + return lx.errorf(`unexpected EOF; expected '"'`) + case isNL(r): + return lx.errorPrevLine(errLexStringNL{}) + case r == '\\': + lx.push(lexString) + return lexStringEscape + case r == '"': + lx.backup() + if lx.esc { + lx.esc = false + lx.emit(itemStringEsc) + } else { + lx.emit(itemString) + } + lx.next() + lx.ignore() + return lx.pop() + } + return lexString +} + +// lexMultilineString consumes the inner contents of a string. It assumes that +// the beginning '"""' has already been consumed and ignored. +func lexMultilineString(lx *lexer) stateFn { + r := lx.next() + switch r { + default: + return lexMultilineString + case eof: + return lx.errorf(`unexpected EOF; expected '"""'`) + case '\\': + return lexMultilineStringEscape + case '"': + /// Found " → try to read two more "". + if lx.accept('"') { + if lx.accept('"') { + /// Peek ahead: the string can contain " and "", including at the + /// end: """str""""" + /// 6 or more at the end, however, is an error. + if lx.peek() == '"' { + /// Check if we already lexed 5 's; if so we have 6 now, and + /// that's just too many man! + /// + /// Second check is for the edge case: + /// + /// two quotes allowed. + /// vv + /// """lol \"""""" + /// ^^ ^^^---- closing three + /// escaped + /// + /// But ugly, but it works + if strings.HasSuffix(lx.current(), `"""""`) && !strings.HasSuffix(lx.current(), `\"""""`) { + return lx.errorf(`unexpected '""""""'`) + } + lx.backup() + lx.backup() + return lexMultilineString + } + + lx.backup() /// backup: don't include the """ in the item. + lx.backup() + lx.backup() + lx.esc = false + lx.emit(itemMultilineString) + lx.next() /// Read over ''' again and discard it. + lx.next() + lx.next() + lx.ignore() + return lx.pop() + } + lx.backup() + } + return lexMultilineString + } +} + +// lexRawString consumes a raw string. Nothing can be escaped in such a string. +// It assumes that the beginning "'" has already been consumed and ignored. +func lexRawString(lx *lexer) stateFn { + r := lx.next() + switch { + default: + return lexRawString + case r == eof: + return lx.errorf(`unexpected EOF; expected "'"`) + case isNL(r): + return lx.errorPrevLine(errLexStringNL{}) + case r == '\'': + lx.backup() + lx.emit(itemRawString) + lx.next() + lx.ignore() + return lx.pop() + } +} + +// lexMultilineRawString consumes a raw string. Nothing can be escaped in such a +// string. It assumes that the beginning triple-' has already been consumed and +// ignored. +func lexMultilineRawString(lx *lexer) stateFn { + r := lx.next() + switch r { + default: + return lexMultilineRawString + case eof: + return lx.errorf(`unexpected EOF; expected "'''"`) + case '\'': + /// Found ' → try to read two more ''. + if lx.accept('\'') { + if lx.accept('\'') { + /// Peek ahead: the string can contain ' and '', including at the + /// end: '''str''''' + /// 6 or more at the end, however, is an error. + if lx.peek() == '\'' { + /// Check if we already lexed 5 's; if so we have 6 now, and + /// that's just too many man! + if strings.HasSuffix(lx.current(), "'''''") { + return lx.errorf(`unexpected "''''''"`) + } + lx.backup() + lx.backup() + return lexMultilineRawString + } + + lx.backup() /// backup: don't include the ''' in the item. + lx.backup() + lx.backup() + lx.emit(itemRawMultilineString) + lx.next() /// Read over ''' again and discard it. + lx.next() + lx.next() + lx.ignore() + return lx.pop() + } + lx.backup() + } + return lexMultilineRawString + } +} + +// lexMultilineStringEscape consumes an escaped character. It assumes that the +// preceding '\\' has already been consumed. +func lexMultilineStringEscape(lx *lexer) stateFn { + if isNL(lx.next()) { /// \ escaping newline. + return lexMultilineString + } + lx.backup() + lx.push(lexMultilineString) + return lexStringEscape(lx) +} + +func lexStringEscape(lx *lexer) stateFn { + lx.esc = true + r := lx.next() + switch r { + case 'e': + if !lx.tomlNext { + return lx.error(errLexEscape{r}) + } + fallthrough + case 'b': + fallthrough + case 't': + fallthrough + case 'n': + fallthrough + case 'f': + fallthrough + case 'r': + fallthrough + case '"': + fallthrough + case ' ', '\t': + // Inside """ .. """ strings you can use \ to escape newlines, and any + // amount of whitespace can be between the \ and \n. + fallthrough + case '\\': + return lx.pop() + case 'x': + if !lx.tomlNext { + return lx.error(errLexEscape{r}) + } + return lexHexEscape + case 'u': + return lexShortUnicodeEscape + case 'U': + return lexLongUnicodeEscape + } + return lx.error(errLexEscape{r}) +} + +func lexHexEscape(lx *lexer) stateFn { + var r rune + for i := 0; i < 2; i++ { + r = lx.next() + if !isHex(r) { + return lx.errorf(`expected two hexadecimal digits after '\x', but got %q instead`, lx.current()) + } + } + return lx.pop() +} + +func lexShortUnicodeEscape(lx *lexer) stateFn { + var r rune + for i := 0; i < 4; i++ { + r = lx.next() + if !isHex(r) { + return lx.errorf(`expected four hexadecimal digits after '\u', but got %q instead`, lx.current()) + } + } + return lx.pop() +} + +func lexLongUnicodeEscape(lx *lexer) stateFn { + var r rune + for i := 0; i < 8; i++ { + r = lx.next() + if !isHex(r) { + return lx.errorf(`expected eight hexadecimal digits after '\U', but got %q instead`, lx.current()) + } + } + return lx.pop() +} + +// lexNumberOrDateStart processes the first character of a value which begins +// with a digit. It exists to catch values starting with '0', so that +// lexBaseNumberOrDate can differentiate base prefixed integers from other +// types. +func lexNumberOrDateStart(lx *lexer) stateFn { + r := lx.next() + switch r { + case '0': + return lexBaseNumberOrDate + } + + if !isDigit(r) { + // The only way to reach this state is if the value starts + // with a digit, so specifically treat anything else as an + // error. + return lx.errorf("expected a digit but got %q", r) + } + + return lexNumberOrDate +} + +// lexNumberOrDate consumes either an integer, float or datetime. +func lexNumberOrDate(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexNumberOrDate + } + switch r { + case '-', ':': + return lexDatetime + case '_': + return lexDecimalNumber + case '.', 'e', 'E': + return lexFloat + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDatetime consumes a Datetime, to a first approximation. +// The parser validates that it matches one of the accepted formats. +func lexDatetime(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexDatetime + } + switch r { + case '-', ':', 'T', 't', ' ', '.', 'Z', 'z', '+': + return lexDatetime + } + + lx.backup() + lx.emitTrim(itemDatetime) + return lx.pop() +} + +// lexHexInteger consumes a hexadecimal integer after seeing the '0x' prefix. +func lexHexInteger(lx *lexer) stateFn { + r := lx.next() + if isHex(r) { + return lexHexInteger + } + switch r { + case '_': + return lexHexInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexOctalInteger consumes an octal integer after seeing the '0o' prefix. +func lexOctalInteger(lx *lexer) stateFn { + r := lx.next() + if isOctal(r) { + return lexOctalInteger + } + switch r { + case '_': + return lexOctalInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexBinaryInteger consumes a binary integer after seeing the '0b' prefix. +func lexBinaryInteger(lx *lexer) stateFn { + r := lx.next() + if isBinary(r) { + return lexBinaryInteger + } + switch r { + case '_': + return lexBinaryInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDecimalNumber consumes a decimal float or integer. +func lexDecimalNumber(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexDecimalNumber + } + switch r { + case '.', 'e', 'E': + return lexFloat + case '_': + return lexDecimalNumber + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDecimalNumber consumes the first digit of a number beginning with a sign. +// It assumes the sign has already been consumed. Values which start with a sign +// are only allowed to be decimal integers or floats. +// +// The special "nan" and "inf" values are also recognized. +func lexDecimalNumberStart(lx *lexer) stateFn { + r := lx.next() + + // Special error cases to give users better error messages + switch r { + case 'i': + if !lx.accept('n') || !lx.accept('f') { + return lx.errorf("invalid float: '%s'", lx.current()) + } + lx.emit(itemFloat) + return lx.pop() + case 'n': + if !lx.accept('a') || !lx.accept('n') { + return lx.errorf("invalid float: '%s'", lx.current()) + } + lx.emit(itemFloat) + return lx.pop() + case '0': + p := lx.peek() + switch p { + case 'b', 'o', 'x': + return lx.errorf("cannot use sign with non-decimal numbers: '%s%c'", lx.current(), p) + } + case '.': + return lx.errorf("floats must start with a digit, not '.'") + } + + if isDigit(r) { + return lexDecimalNumber + } + + return lx.errorf("expected a digit but got %q", r) +} + +// lexBaseNumberOrDate differentiates between the possible values which +// start with '0'. It assumes that before reaching this state, the initial '0' +// has been consumed. +func lexBaseNumberOrDate(lx *lexer) stateFn { + r := lx.next() + // Note: All datetimes start with at least two digits, so we don't + // handle date characters (':', '-', etc.) here. + if isDigit(r) { + return lexNumberOrDate + } + switch r { + case '_': + // Can only be decimal, because there can't be an underscore + // between the '0' and the base designator, and dates can't + // contain underscores. + return lexDecimalNumber + case '.', 'e', 'E': + return lexFloat + case 'b': + r = lx.peek() + if !isBinary(r) { + lx.errorf("not a binary number: '%s%c'", lx.current(), r) + } + return lexBinaryInteger + case 'o': + r = lx.peek() + if !isOctal(r) { + lx.errorf("not an octal number: '%s%c'", lx.current(), r) + } + return lexOctalInteger + case 'x': + r = lx.peek() + if !isHex(r) { + lx.errorf("not a hexadecimal number: '%s%c'", lx.current(), r) + } + return lexHexInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexFloat consumes the elements of a float. It allows any sequence of +// float-like characters, so floats emitted by the lexer are only a first +// approximation and must be validated by the parser. +func lexFloat(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexFloat + } + switch r { + case '_', '.', '-', '+', 'e', 'E': + return lexFloat + } + + lx.backup() + lx.emit(itemFloat) + return lx.pop() +} + +// lexBool consumes a bool string: 'true' or 'false. +func lexBool(lx *lexer) stateFn { + var rs []rune + for { + r := lx.next() + if !unicode.IsLetter(r) { + lx.backup() + break + } + rs = append(rs, r) + } + s := string(rs) + switch s { + case "true", "false": + lx.emit(itemBool) + return lx.pop() + } + return lx.errorf("expected value but found %q instead", s) +} + +// lexCommentStart begins the lexing of a comment. It will emit +// itemCommentStart and consume no characters, passing control to lexComment. +func lexCommentStart(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemCommentStart) + return lexComment +} + +// lexComment lexes an entire comment. It assumes that '#' has been consumed. +// It will consume *up to* the first newline character, and pass control +// back to the last state on the stack. +func lexComment(lx *lexer) stateFn { + switch r := lx.next(); { + case isNL(r) || r == eof: + lx.backup() + lx.emit(itemText) + return lx.pop() + default: + return lexComment + } +} + +// lexSkip ignores all slurped input and moves on to the next state. +func lexSkip(lx *lexer, nextState stateFn) stateFn { + lx.ignore() + return nextState +} + +func (s stateFn) String() string { + name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name() + if i := strings.LastIndexByte(name, '.'); i > -1 { + name = name[i+1:] + } + if s == nil { + name = "" + } + return name + "()" +} + +func (itype itemType) String() string { + switch itype { + case itemError: + return "Error" + case itemNIL: + return "NIL" + case itemEOF: + return "EOF" + case itemText: + return "Text" + case itemString, itemStringEsc, itemRawString, itemMultilineString, itemRawMultilineString: + return "String" + case itemBool: + return "Bool" + case itemInteger: + return "Integer" + case itemFloat: + return "Float" + case itemDatetime: + return "DateTime" + case itemTableStart: + return "TableStart" + case itemTableEnd: + return "TableEnd" + case itemKeyStart: + return "KeyStart" + case itemKeyEnd: + return "KeyEnd" + case itemArray: + return "Array" + case itemArrayEnd: + return "ArrayEnd" + case itemCommentStart: + return "CommentStart" + case itemInlineTableStart: + return "InlineTableStart" + case itemInlineTableEnd: + return "InlineTableEnd" + } + panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) +} + +func (item item) String() string { + return fmt.Sprintf("(%s, %s)", item.typ, item.val) +} + +func isWhitespace(r rune) bool { return r == '\t' || r == ' ' } +func isNL(r rune) bool { return r == '\n' || r == '\r' } +func isControl(r rune) bool { // Control characters except \t, \r, \n + switch r { + case '\t', '\r', '\n': + return false + default: + return (r >= 0x00 && r <= 0x1f) || r == 0x7f + } +} +func isDigit(r rune) bool { return r >= '0' && r <= '9' } +func isBinary(r rune) bool { return r == '0' || r == '1' } +func isOctal(r rune) bool { return r >= '0' && r <= '7' } +func isHex(r rune) bool { return (r >= '0' && r <= '9') || (r|0x20 >= 'a' && r|0x20 <= 'f') } +func isBareKeyChar(r rune, tomlNext bool) bool { + return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || + (r >= '0' && r <= '9') || r == '_' || r == '-' +} diff --git a/vendor/github.com/BurntSushi/toml/meta.go b/vendor/github.com/BurntSushi/toml/meta.go new file mode 100644 index 00000000..0d337026 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/meta.go @@ -0,0 +1,145 @@ +package toml + +import ( + "strings" +) + +// MetaData allows access to meta information about TOML data that's not +// accessible otherwise. +// +// It allows checking if a key is defined in the TOML data, whether any keys +// were undecoded, and the TOML type of a key. +type MetaData struct { + context Key // Used only during decoding. + + keyInfo map[string]keyInfo + mapping map[string]any + keys []Key + decoded map[string]struct{} + data []byte // Input file; for errors. +} + +// IsDefined reports if the key exists in the TOML data. +// +// The key should be specified hierarchically, for example to access the TOML +// key "a.b.c" you would use IsDefined("a", "b", "c"). Keys are case sensitive. +// +// Returns false for an empty key. +func (md *MetaData) IsDefined(key ...string) bool { + if len(key) == 0 { + return false + } + + var ( + hash map[string]any + ok bool + hashOrVal any = md.mapping + ) + for _, k := range key { + if hash, ok = hashOrVal.(map[string]any); !ok { + return false + } + if hashOrVal, ok = hash[k]; !ok { + return false + } + } + return true +} + +// Type returns a string representation of the type of the key specified. +// +// Type will return the empty string if given an empty key or a key that does +// not exist. Keys are case sensitive. +func (md *MetaData) Type(key ...string) string { + if ki, ok := md.keyInfo[Key(key).String()]; ok { + return ki.tomlType.typeString() + } + return "" +} + +// Keys returns a slice of every key in the TOML data, including key groups. +// +// Each key is itself a slice, where the first element is the top of the +// hierarchy and the last is the most specific. The list will have the same +// order as the keys appeared in the TOML data. +// +// All keys returned are non-empty. +func (md *MetaData) Keys() []Key { + return md.keys +} + +// Undecoded returns all keys that have not been decoded in the order in which +// they appear in the original TOML document. +// +// This includes keys that haven't been decoded because of a [Primitive] value. +// Once the Primitive value is decoded, the keys will be considered decoded. +// +// Also note that decoding into an empty interface will result in no decoding, +// and so no keys will be considered decoded. +// +// In this sense, the Undecoded keys correspond to keys in the TOML document +// that do not have a concrete type in your representation. +func (md *MetaData) Undecoded() []Key { + undecoded := make([]Key, 0, len(md.keys)) + for _, key := range md.keys { + if _, ok := md.decoded[key.String()]; !ok { + undecoded = append(undecoded, key) + } + } + return undecoded +} + +// Key represents any TOML key, including key groups. Use [MetaData.Keys] to get +// values of this type. +type Key []string + +func (k Key) String() string { + // This is called quite often, so it's a bit funky to make it faster. + var b strings.Builder + b.Grow(len(k) * 25) +outer: + for i, kk := range k { + if i > 0 { + b.WriteByte('.') + } + if kk == "" { + b.WriteString(`""`) + } else { + for _, r := range kk { + // "Inline" isBareKeyChar + if !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-') { + b.WriteByte('"') + b.WriteString(dblQuotedReplacer.Replace(kk)) + b.WriteByte('"') + continue outer + } + } + b.WriteString(kk) + } + } + return b.String() +} + +func (k Key) maybeQuoted(i int) string { + if k[i] == "" { + return `""` + } + for _, r := range k[i] { + if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-' { + continue + } + return `"` + dblQuotedReplacer.Replace(k[i]) + `"` + } + return k[i] +} + +// Like append(), but only increase the cap by 1. +func (k Key) add(piece string) Key { + newKey := make(Key, len(k)+1) + copy(newKey, k) + newKey[len(k)] = piece + return newKey +} + +func (k Key) parent() Key { return k[:len(k)-1] } // all except the last piece. +func (k Key) last() string { return k[len(k)-1] } // last piece of this key. diff --git a/vendor/github.com/BurntSushi/toml/parse.go b/vendor/github.com/BurntSushi/toml/parse.go new file mode 100644 index 00000000..e3ea8a9a --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/parse.go @@ -0,0 +1,845 @@ +package toml + +import ( + "fmt" + "math" + "os" + "strconv" + "strings" + "time" + "unicode/utf8" + + "github.com/BurntSushi/toml/internal" +) + +type parser struct { + lx *lexer + context Key // Full key for the current hash in scope. + currentKey string // Base key name for everything except hashes. + pos Position // Current position in the TOML file. + tomlNext bool + + ordered []Key // List of keys in the order that they appear in the TOML data. + + keyInfo map[string]keyInfo // Map keyname → info about the TOML key. + mapping map[string]any // Map keyname → key value. + implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names"). +} + +type keyInfo struct { + pos Position + tomlType tomlType +} + +func parse(data string) (p *parser, err error) { + _, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110") + + defer func() { + if r := recover(); r != nil { + if pErr, ok := r.(ParseError); ok { + pErr.input = data + err = pErr + return + } + panic(r) + } + }() + + // Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString() + // which mangles stuff. UTF-16 BOM isn't strictly valid, but some tools add + // it anyway. + if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { // UTF-16 + data = data[2:] + } else if strings.HasPrefix(data, "\xef\xbb\xbf") { // UTF-8 + data = data[3:] + } + + // Examine first few bytes for NULL bytes; this probably means it's a UTF-16 + // file (second byte in surrogate pair being NULL). Again, do this here to + // avoid having to deal with UTF-8/16 stuff in the lexer. + ex := 6 + if len(data) < 6 { + ex = len(data) + } + if i := strings.IndexRune(data[:ex], 0); i > -1 { + return nil, ParseError{ + Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8", + Position: Position{Line: 1, Col: 1, Start: i, Len: 1}, + Line: 1, + input: data, + } + } + + p = &parser{ + keyInfo: make(map[string]keyInfo), + mapping: make(map[string]any), + lx: lex(data, tomlNext), + ordered: make([]Key, 0), + implicits: make(map[string]struct{}), + tomlNext: tomlNext, + } + for { + item := p.next() + if item.typ == itemEOF { + break + } + p.topLevel(item) + } + + return p, nil +} + +func (p *parser) panicErr(it item, err error) { + panic(ParseError{ + Message: err.Error(), + err: err, + Position: it.pos.withCol(p.lx.input), + Line: it.pos.Len, + LastKey: p.current(), + }) +} + +func (p *parser) panicItemf(it item, format string, v ...any) { + panic(ParseError{ + Message: fmt.Sprintf(format, v...), + Position: it.pos.withCol(p.lx.input), + Line: it.pos.Len, + LastKey: p.current(), + }) +} + +func (p *parser) panicf(format string, v ...any) { + panic(ParseError{ + Message: fmt.Sprintf(format, v...), + Position: p.pos.withCol(p.lx.input), + Line: p.pos.Line, + LastKey: p.current(), + }) +} + +func (p *parser) next() item { + it := p.lx.nextItem() + //fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.pos.Line, it.val) + if it.typ == itemError { + if it.err != nil { + panic(ParseError{ + Message: it.err.Error(), + err: it.err, + Position: it.pos.withCol(p.lx.input), + Line: it.pos.Line, + LastKey: p.current(), + }) + } + + p.panicItemf(it, "%s", it.val) + } + return it +} + +func (p *parser) nextPos() item { + it := p.next() + p.pos = it.pos + return it +} + +func (p *parser) bug(format string, v ...any) { + panic(fmt.Sprintf("BUG: "+format+"\n\n", v...)) +} + +func (p *parser) expect(typ itemType) item { + it := p.next() + p.assertEqual(typ, it.typ) + return it +} + +func (p *parser) assertEqual(expected, got itemType) { + if expected != got { + p.bug("Expected '%s' but got '%s'.", expected, got) + } +} + +func (p *parser) topLevel(item item) { + switch item.typ { + case itemCommentStart: // # .. + p.expect(itemText) + case itemTableStart: // [ .. ] + name := p.nextPos() + + var key Key + for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() { + key = append(key, p.keyString(name)) + } + p.assertEqual(itemTableEnd, name.typ) + + p.addContext(key, false) + p.setType("", tomlHash, item.pos) + p.ordered = append(p.ordered, key) + case itemArrayTableStart: // [[ .. ]] + name := p.nextPos() + + var key Key + for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() { + key = append(key, p.keyString(name)) + } + p.assertEqual(itemArrayTableEnd, name.typ) + + p.addContext(key, true) + p.setType("", tomlArrayHash, item.pos) + p.ordered = append(p.ordered, key) + case itemKeyStart: // key = .. + outerContext := p.context + /// Read all the key parts (e.g. 'a' and 'b' in 'a.b') + k := p.nextPos() + var key Key + for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { + key = append(key, p.keyString(k)) + } + p.assertEqual(itemKeyEnd, k.typ) + + /// The current key is the last part. + p.currentKey = key.last() + + /// All the other parts (if any) are the context; need to set each part + /// as implicit. + context := key.parent() + for i := range context { + p.addImplicitContext(append(p.context, context[i:i+1]...)) + } + p.ordered = append(p.ordered, p.context.add(p.currentKey)) + + /// Set value. + vItem := p.next() + val, typ := p.value(vItem, false) + p.setValue(p.currentKey, val) + p.setType(p.currentKey, typ, vItem.pos) + + /// Remove the context we added (preserving any context from [tbl] lines). + p.context = outerContext + p.currentKey = "" + default: + p.bug("Unexpected type at top level: %s", item.typ) + } +} + +// Gets a string for a key (or part of a key in a table name). +func (p *parser) keyString(it item) string { + switch it.typ { + case itemText: + return it.val + case itemString, itemStringEsc, itemMultilineString, + itemRawString, itemRawMultilineString: + s, _ := p.value(it, false) + return s.(string) + default: + p.bug("Unexpected key type: %s", it.typ) + } + panic("unreachable") +} + +var datetimeRepl = strings.NewReplacer( + "z", "Z", + "t", "T", + " ", "T") + +// value translates an expected value from the lexer into a Go value wrapped +// as an empty interface. +func (p *parser) value(it item, parentIsArray bool) (any, tomlType) { + switch it.typ { + case itemString: + return it.val, p.typeOfPrimitive(it) + case itemStringEsc: + return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it) + case itemMultilineString: + return p.replaceEscapes(it, p.stripEscapedNewlines(stripFirstNewline(it.val))), p.typeOfPrimitive(it) + case itemRawString: + return it.val, p.typeOfPrimitive(it) + case itemRawMultilineString: + return stripFirstNewline(it.val), p.typeOfPrimitive(it) + case itemInteger: + return p.valueInteger(it) + case itemFloat: + return p.valueFloat(it) + case itemBool: + switch it.val { + case "true": + return true, p.typeOfPrimitive(it) + case "false": + return false, p.typeOfPrimitive(it) + default: + p.bug("Expected boolean value, but got '%s'.", it.val) + } + case itemDatetime: + return p.valueDatetime(it) + case itemArray: + return p.valueArray(it) + case itemInlineTableStart: + return p.valueInlineTable(it, parentIsArray) + default: + p.bug("Unexpected value type: %s", it.typ) + } + panic("unreachable") +} + +func (p *parser) valueInteger(it item) (any, tomlType) { + if !numUnderscoresOK(it.val) { + p.panicItemf(it, "Invalid integer %q: underscores must be surrounded by digits", it.val) + } + if numHasLeadingZero(it.val) { + p.panicItemf(it, "Invalid integer %q: cannot have leading zeroes", it.val) + } + + num, err := strconv.ParseInt(it.val, 0, 64) + if err != nil { + // Distinguish integer values. Normally, it'd be a bug if the lexer + // provides an invalid integer, but it's possible that the number is + // out of range of valid values (which the lexer cannot determine). + // So mark the former as a bug but the latter as a legitimate user + // error. + if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { + p.panicErr(it, errParseRange{i: it.val, size: "int64"}) + } else { + p.bug("Expected integer value, but got '%s'.", it.val) + } + } + return num, p.typeOfPrimitive(it) +} + +func (p *parser) valueFloat(it item) (any, tomlType) { + parts := strings.FieldsFunc(it.val, func(r rune) bool { + switch r { + case '.', 'e', 'E': + return true + } + return false + }) + for _, part := range parts { + if !numUnderscoresOK(part) { + p.panicItemf(it, "Invalid float %q: underscores must be surrounded by digits", it.val) + } + } + if len(parts) > 0 && numHasLeadingZero(parts[0]) { + p.panicItemf(it, "Invalid float %q: cannot have leading zeroes", it.val) + } + if !numPeriodsOK(it.val) { + // As a special case, numbers like '123.' or '1.e2', + // which are valid as far as Go/strconv are concerned, + // must be rejected because TOML says that a fractional + // part consists of '.' followed by 1+ digits. + p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val) + } + val := strings.Replace(it.val, "_", "", -1) + signbit := false + if val == "+nan" || val == "-nan" { + signbit = val == "-nan" + val = "nan" + } + num, err := strconv.ParseFloat(val, 64) + if err != nil { + if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { + p.panicErr(it, errParseRange{i: it.val, size: "float64"}) + } else { + p.panicItemf(it, "Invalid float value: %q", it.val) + } + } + if signbit { + num = math.Copysign(num, -1) + } + return num, p.typeOfPrimitive(it) +} + +var dtTypes = []struct { + fmt string + zone *time.Location + next bool +}{ + {time.RFC3339Nano, time.Local, false}, + {"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false}, + {"2006-01-02", internal.LocalDate, false}, + {"15:04:05.999999999", internal.LocalTime, false}, + + // tomlNext + {"2006-01-02T15:04Z07:00", time.Local, true}, + {"2006-01-02T15:04", internal.LocalDatetime, true}, + {"15:04", internal.LocalTime, true}, +} + +func (p *parser) valueDatetime(it item) (any, tomlType) { + it.val = datetimeRepl.Replace(it.val) + var ( + t time.Time + ok bool + err error + ) + for _, dt := range dtTypes { + if dt.next && !p.tomlNext { + continue + } + t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone) + if err == nil { + if missingLeadingZero(it.val, dt.fmt) { + p.panicErr(it, errParseDate{it.val}) + } + ok = true + break + } + } + if !ok { + p.panicErr(it, errParseDate{it.val}) + } + return t, p.typeOfPrimitive(it) +} + +// Go's time.Parse() will accept numbers without a leading zero; there isn't any +// way to require it. https://github.com/golang/go/issues/29911 +// +// Depend on the fact that the separators (- and :) should always be at the same +// location. +func missingLeadingZero(d, l string) bool { + for i, c := range []byte(l) { + if c == '.' || c == 'Z' { + return false + } + if (c < '0' || c > '9') && d[i] != c { + return true + } + } + return false +} + +func (p *parser) valueArray(it item) (any, tomlType) { + p.setType(p.currentKey, tomlArray, it.pos) + + var ( + // Initialize to a non-nil slice to make it consistent with how S = [] + // decodes into a non-nil slice inside something like struct { S + // []string }. See #338 + array = make([]any, 0, 2) + ) + for it = p.next(); it.typ != itemArrayEnd; it = p.next() { + if it.typ == itemCommentStart { + p.expect(itemText) + continue + } + + val, typ := p.value(it, true) + array = append(array, val) + + // XXX: type isn't used here, we need it to record the accurate type + // information. + // + // Not entirely sure how to best store this; could use "key[0]", + // "key[1]" notation, or maybe store it on the Array type? + _ = typ + } + return array, tomlArray +} + +func (p *parser) valueInlineTable(it item, parentIsArray bool) (any, tomlType) { + var ( + topHash = make(map[string]any) + outerContext = p.context + outerKey = p.currentKey + ) + + p.context = append(p.context, p.currentKey) + prevContext := p.context + p.currentKey = "" + + p.addImplicit(p.context) + p.addContext(p.context, parentIsArray) + + /// Loop over all table key/value pairs. + for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { + if it.typ == itemCommentStart { + p.expect(itemText) + continue + } + + /// Read all key parts. + k := p.nextPos() + var key Key + for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { + key = append(key, p.keyString(k)) + } + p.assertEqual(itemKeyEnd, k.typ) + + /// The current key is the last part. + p.currentKey = key.last() + + /// All the other parts (if any) are the context; need to set each part + /// as implicit. + context := key.parent() + for i := range context { + p.addImplicitContext(append(p.context, context[i:i+1]...)) + } + p.ordered = append(p.ordered, p.context.add(p.currentKey)) + + /// Set the value. + val, typ := p.value(p.next(), false) + p.setValue(p.currentKey, val) + p.setType(p.currentKey, typ, it.pos) + + hash := topHash + for _, c := range context { + h, ok := hash[c] + if !ok { + h = make(map[string]any) + hash[c] = h + } + hash, ok = h.(map[string]any) + if !ok { + p.panicf("%q is not a table", p.context) + } + } + hash[p.currentKey] = val + + /// Restore context. + p.context = prevContext + } + p.context = outerContext + p.currentKey = outerKey + return topHash, tomlHash +} + +// numHasLeadingZero checks if this number has leading zeroes, allowing for '0', +// +/- signs, and base prefixes. +func numHasLeadingZero(s string) bool { + if len(s) > 1 && s[0] == '0' && !(s[1] == 'b' || s[1] == 'o' || s[1] == 'x') { // Allow 0b, 0o, 0x + return true + } + if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' { + return true + } + return false +} + +// numUnderscoresOK checks whether each underscore in s is surrounded by +// characters that are not underscores. +func numUnderscoresOK(s string) bool { + switch s { + case "nan", "+nan", "-nan", "inf", "-inf", "+inf": + return true + } + accept := false + for _, r := range s { + if r == '_' { + if !accept { + return false + } + } + + // isHex is a superset of all the permissible characters surrounding an + // underscore. + accept = isHex(r) + } + return accept +} + +// numPeriodsOK checks whether every period in s is followed by a digit. +func numPeriodsOK(s string) bool { + period := false + for _, r := range s { + if period && !isDigit(r) { + return false + } + period = r == '.' + } + return !period +} + +// Set the current context of the parser, where the context is either a hash or +// an array of hashes, depending on the value of the `array` parameter. +// +// Establishing the context also makes sure that the key isn't a duplicate, and +// will create implicit hashes automatically. +func (p *parser) addContext(key Key, array bool) { + /// Always start at the top level and drill down for our context. + hashContext := p.mapping + keyContext := make(Key, 0, len(key)-1) + + /// We only need implicit hashes for the parents. + for _, k := range key.parent() { + _, ok := hashContext[k] + keyContext = append(keyContext, k) + + // No key? Make an implicit hash and move on. + if !ok { + p.addImplicit(keyContext) + hashContext[k] = make(map[string]any) + } + + // If the hash context is actually an array of tables, then set + // the hash context to the last element in that array. + // + // Otherwise, it better be a table, since this MUST be a key group (by + // virtue of it not being the last element in a key). + switch t := hashContext[k].(type) { + case []map[string]any: + hashContext = t[len(t)-1] + case map[string]any: + hashContext = t + default: + p.panicf("Key '%s' was already created as a hash.", keyContext) + } + } + + p.context = keyContext + if array { + // If this is the first element for this array, then allocate a new + // list of tables for it. + k := key.last() + if _, ok := hashContext[k]; !ok { + hashContext[k] = make([]map[string]any, 0, 4) + } + + // Add a new table. But make sure the key hasn't already been used + // for something else. + if hash, ok := hashContext[k].([]map[string]any); ok { + hashContext[k] = append(hash, make(map[string]any)) + } else { + p.panicf("Key '%s' was already created and cannot be used as an array.", key) + } + } else { + p.setValue(key.last(), make(map[string]any)) + } + p.context = append(p.context, key.last()) +} + +// setValue sets the given key to the given value in the current context. +// It will make sure that the key hasn't already been defined, account for +// implicit key groups. +func (p *parser) setValue(key string, value any) { + var ( + tmpHash any + ok bool + hash = p.mapping + keyContext = make(Key, 0, len(p.context)+1) + ) + for _, k := range p.context { + keyContext = append(keyContext, k) + if tmpHash, ok = hash[k]; !ok { + p.bug("Context for key '%s' has not been established.", keyContext) + } + switch t := tmpHash.(type) { + case []map[string]any: + // The context is a table of hashes. Pick the most recent table + // defined as the current hash. + hash = t[len(t)-1] + case map[string]any: + hash = t + default: + p.panicf("Key '%s' has already been defined.", keyContext) + } + } + keyContext = append(keyContext, key) + + if _, ok := hash[key]; ok { + // Normally redefining keys isn't allowed, but the key could have been + // defined implicitly and it's allowed to be redefined concretely. (See + // the `valid/implicit-and-explicit-after.toml` in toml-test) + // + // But we have to make sure to stop marking it as an implicit. (So that + // another redefinition provokes an error.) + // + // Note that since it has already been defined (as a hash), we don't + // want to overwrite it. So our business is done. + if p.isArray(keyContext) { + p.removeImplicit(keyContext) + hash[key] = value + return + } + if p.isImplicit(keyContext) { + p.removeImplicit(keyContext) + return + } + // Otherwise, we have a concrete key trying to override a previous key, + // which is *always* wrong. + p.panicf("Key '%s' has already been defined.", keyContext) + } + + hash[key] = value +} + +// setType sets the type of a particular value at a given key. It should be +// called immediately AFTER setValue. +// +// Note that if `key` is empty, then the type given will be applied to the +// current context (which is either a table or an array of tables). +func (p *parser) setType(key string, typ tomlType, pos Position) { + keyContext := make(Key, 0, len(p.context)+1) + keyContext = append(keyContext, p.context...) + if len(key) > 0 { // allow type setting for hashes + keyContext = append(keyContext, key) + } + // Special case to make empty keys ("" = 1) work. + // Without it it will set "" rather than `""`. + // TODO: why is this needed? And why is this only needed here? + if len(keyContext) == 0 { + keyContext = Key{""} + } + p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos} +} + +// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and +// "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly). +func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} } +func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) } +func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok } +func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray } +func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) } + +// current returns the full key name of the current context. +func (p *parser) current() string { + if len(p.currentKey) == 0 { + return p.context.String() + } + if len(p.context) == 0 { + return p.currentKey + } + return fmt.Sprintf("%s.%s", p.context, p.currentKey) +} + +func stripFirstNewline(s string) string { + if len(s) > 0 && s[0] == '\n' { + return s[1:] + } + if len(s) > 1 && s[0] == '\r' && s[1] == '\n' { + return s[2:] + } + return s +} + +// stripEscapedNewlines removes whitespace after line-ending backslashes in +// multiline strings. +// +// A line-ending backslash is an unescaped \ followed only by whitespace until +// the next newline. After a line-ending backslash, all whitespace is removed +// until the next non-whitespace character. +func (p *parser) stripEscapedNewlines(s string) string { + var ( + b strings.Builder + i int + ) + b.Grow(len(s)) + for { + ix := strings.Index(s[i:], `\`) + if ix < 0 { + b.WriteString(s) + return b.String() + } + i += ix + + if len(s) > i+1 && s[i+1] == '\\' { + // Escaped backslash. + i += 2 + continue + } + // Scan until the next non-whitespace. + j := i + 1 + whitespaceLoop: + for ; j < len(s); j++ { + switch s[j] { + case ' ', '\t', '\r', '\n': + default: + break whitespaceLoop + } + } + if j == i+1 { + // Not a whitespace escape. + i++ + continue + } + if !strings.Contains(s[i:j], "\n") { + // This is not a line-ending backslash. (It's a bad escape sequence, + // but we can let replaceEscapes catch it.) + i++ + continue + } + b.WriteString(s[:i]) + s = s[j:] + i = 0 + } +} + +func (p *parser) replaceEscapes(it item, str string) string { + var ( + b strings.Builder + skip = 0 + ) + b.Grow(len(str)) + for i, c := range str { + if skip > 0 { + skip-- + continue + } + if c != '\\' { + b.WriteRune(c) + continue + } + + if i >= len(str) { + p.bug("Escape sequence at end of string.") + return "" + } + switch str[i+1] { + default: + p.bug("Expected valid escape code after \\, but got %q.", str[i+1]) + case ' ', '\t': + p.panicItemf(it, "invalid escape: '\\%c'", str[i+1]) + case 'b': + b.WriteByte(0x08) + skip = 1 + case 't': + b.WriteByte(0x09) + skip = 1 + case 'n': + b.WriteByte(0x0a) + skip = 1 + case 'f': + b.WriteByte(0x0c) + skip = 1 + case 'r': + b.WriteByte(0x0d) + skip = 1 + case 'e': + if p.tomlNext { + b.WriteByte(0x1b) + skip = 1 + } + case '"': + b.WriteByte(0x22) + skip = 1 + case '\\': + b.WriteByte(0x5c) + skip = 1 + // The lexer guarantees the correct number of characters are present; + // don't need to check here. + case 'x': + if p.tomlNext { + escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4]) + b.WriteRune(escaped) + skip = 3 + } + case 'u': + escaped := p.asciiEscapeToUnicode(it, str[i+2:i+6]) + b.WriteRune(escaped) + skip = 5 + case 'U': + escaped := p.asciiEscapeToUnicode(it, str[i+2:i+10]) + b.WriteRune(escaped) + skip = 9 + } + } + return b.String() +} + +func (p *parser) asciiEscapeToUnicode(it item, s string) rune { + hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) + if err != nil { + p.bug("Could not parse '%s' as a hexadecimal number, but the lexer claims it's OK: %s", s, err) + } + if !utf8.ValidRune(rune(hex)) { + p.panicItemf(it, "Escaped character '\\u%s' is not valid UTF-8.", s) + } + return rune(hex) +} diff --git a/vendor/github.com/BurntSushi/toml/type_fields.go b/vendor/github.com/BurntSushi/toml/type_fields.go new file mode 100644 index 00000000..10c51f7e --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/type_fields.go @@ -0,0 +1,238 @@ +package toml + +// Struct field handling is adapted from code in encoding/json: +// +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the Go distribution. + +import ( + "reflect" + "sort" + "sync" +) + +// A field represents a single field found in a struct. +type field struct { + name string // the name of the field (`toml` tag included) + tag bool // whether field has a `toml` tag + index []int // represents the depth of an anonymous field + typ reflect.Type // the type of the field +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from toml tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that TOML should recognize for the given +// type. The algorithm is breadth-first search over the set of structs to +// include - the top struct and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + var count map[reflect.Type]int + var nextCount map[reflect.Type]int + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { // unexported + continue + } + opts := getOptions(sf.Tag) + if opts.skip { + continue + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := opts.name != "" + name := opts.name + if name == "" { + name = sf.Name + } + fields = append(fields, field{name, tagged, index, ft}) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + f := field{name: ft.Name(), index: index, typ: ft} + next = append(next, f) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with TOML tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// TOML tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} diff --git a/vendor/github.com/BurntSushi/toml/type_toml.go b/vendor/github.com/BurntSushi/toml/type_toml.go new file mode 100644 index 00000000..1c090d33 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/type_toml.go @@ -0,0 +1,65 @@ +package toml + +// tomlType represents any Go type that corresponds to a TOML type. +// While the first draft of the TOML spec has a simplistic type system that +// probably doesn't need this level of sophistication, we seem to be militating +// toward adding real composite types. +type tomlType interface { + typeString() string +} + +// typeEqual accepts any two types and returns true if they are equal. +func typeEqual(t1, t2 tomlType) bool { + if t1 == nil || t2 == nil { + return false + } + return t1.typeString() == t2.typeString() +} + +func typeIsTable(t tomlType) bool { + return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash) +} + +type tomlBaseType string + +func (btype tomlBaseType) typeString() string { return string(btype) } +func (btype tomlBaseType) String() string { return btype.typeString() } + +var ( + tomlInteger tomlBaseType = "Integer" + tomlFloat tomlBaseType = "Float" + tomlDatetime tomlBaseType = "Datetime" + tomlString tomlBaseType = "String" + tomlBool tomlBaseType = "Bool" + tomlArray tomlBaseType = "Array" + tomlHash tomlBaseType = "Hash" + tomlArrayHash tomlBaseType = "ArrayHash" +) + +// typeOfPrimitive returns a tomlType of any primitive value in TOML. +// Primitive values are: Integer, Float, Datetime, String and Bool. +// +// Passing a lexer item other than the following will cause a BUG message +// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime. +func (p *parser) typeOfPrimitive(lexItem item) tomlType { + switch lexItem.typ { + case itemInteger: + return tomlInteger + case itemFloat: + return tomlFloat + case itemDatetime: + return tomlDatetime + case itemString, itemStringEsc: + return tomlString + case itemMultilineString: + return tomlString + case itemRawString: + return tomlString + case itemRawMultilineString: + return tomlString + case itemBool: + return tomlBool + } + p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) + panic("unreachable") +} diff --git a/vendor/github.com/VividCortex/ewma/.gitignore b/vendor/github.com/VividCortex/ewma/.gitignore new file mode 100644 index 00000000..c66769f6 --- /dev/null +++ b/vendor/github.com/VividCortex/ewma/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.*.sw? +/coverage.txt \ No newline at end of file diff --git a/vendor/github.com/VividCortex/ewma/.whitesource b/vendor/github.com/VividCortex/ewma/.whitesource new file mode 100644 index 00000000..d7eebc0c --- /dev/null +++ b/vendor/github.com/VividCortex/ewma/.whitesource @@ -0,0 +1,3 @@ +{ + "settingsInheritedFrom": "VividCortex/whitesource-config@master" +} \ No newline at end of file diff --git a/vendor/github.com/VividCortex/ewma/LICENSE b/vendor/github.com/VividCortex/ewma/LICENSE new file mode 100644 index 00000000..a78d643e --- /dev/null +++ b/vendor/github.com/VividCortex/ewma/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013 VividCortex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/VividCortex/ewma/README.md b/vendor/github.com/VividCortex/ewma/README.md new file mode 100644 index 00000000..87b4a3c7 --- /dev/null +++ b/vendor/github.com/VividCortex/ewma/README.md @@ -0,0 +1,145 @@ +# EWMA + +[![GoDoc](https://godoc.org/github.com/VividCortex/ewma?status.svg)](https://godoc.org/github.com/VividCortex/ewma) +![build](https://github.com/VividCortex/ewma/workflows/build/badge.svg) +[![codecov](https://codecov.io/gh/VividCortex/ewma/branch/master/graph/badge.svg)](https://codecov.io/gh/VividCortex/ewma) + +This repo provides Exponentially Weighted Moving Average algorithms, or EWMAs for short, [based on our +Quantifying Abnormal Behavior talk](https://vividcortex.com/blog/2013/07/23/a-fast-go-library-for-exponential-moving-averages/). + +### Exponentially Weighted Moving Average + +An exponentially weighted moving average is a way to continuously compute a type of +average for a series of numbers, as the numbers arrive. After a value in the series is +added to the average, its weight in the average decreases exponentially over time. This +biases the average towards more recent data. EWMAs are useful for several reasons, chiefly +their inexpensive computational and memory cost, as well as the fact that they represent +the recent central tendency of the series of values. + +The EWMA algorithm requires a decay factor, alpha. The larger the alpha, the more the average +is biased towards recent history. The alpha must be between 0 and 1, and is typically +a fairly small number, such as 0.04. We will discuss the choice of alpha later. + +The algorithm works thus, in pseudocode: + +1. Multiply the next number in the series by alpha. +2. Multiply the current value of the average by 1 minus alpha. +3. Add the result of steps 1 and 2, and store it as the new current value of the average. +4. Repeat for each number in the series. + +There are special-case behaviors for how to initialize the current value, and these vary +between implementations. One approach is to start with the first value in the series; +another is to average the first 10 or so values in the series using an arithmetic average, +and then begin the incremental updating of the average. Each method has pros and cons. + +It may help to look at it pictorially. Suppose the series has five numbers, and we choose +alpha to be 0.50 for simplicity. Here's the series, with numbers in the neighborhood of 300. + +![Data Series](https://user-images.githubusercontent.com/279875/28242350-463289a2-6977-11e7-88ca-fd778ccef1f0.png) + +Now let's take the moving average of those numbers. First we set the average to the value +of the first number. + +![EWMA Step 1](https://user-images.githubusercontent.com/279875/28242353-464c96bc-6977-11e7-9981-dc4e0789c7ba.png) + +Next we multiply the next number by alpha, multiply the current value by 1-alpha, and add +them to generate a new value. + +![EWMA Step 2](https://user-images.githubusercontent.com/279875/28242351-464abefa-6977-11e7-95d0-43900f29bef2.png) + +This continues until we are done. + +![EWMA Step N](https://user-images.githubusercontent.com/279875/28242352-464c58f0-6977-11e7-8cd0-e01e4efaac7f.png) + +Notice how each of the values in the series decays by half each time a new value +is added, and the top of the bars in the lower portion of the image represents the +size of the moving average. It is a smoothed, or low-pass, average of the original +series. + +For further reading, see [Exponentially weighted moving average](http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average) on wikipedia. + +### Choosing Alpha + +Consider a fixed-size sliding-window moving average (not an exponentially weighted moving average) +that averages over the previous N samples. What is the average age of each sample? It is N/2. + +Now suppose that you wish to construct a EWMA whose samples have the same average age. The formula +to compute the alpha required for this is: alpha = 2/(N+1). Proof is in the book +"Production and Operations Analysis" by Steven Nahmias. + +So, for example, if you have a time-series with samples once per second, and you want to get the +moving average over the previous minute, you should use an alpha of .032786885. This, by the way, +is the constant alpha used for this repository's SimpleEWMA. + +### Implementations + +This repository contains two implementations of the EWMA algorithm, with different properties. + +The implementations all conform to the MovingAverage interface, and the constructor returns +that type. + +Current implementations assume an implicit time interval of 1.0 between every sample added. +That is, the passage of time is treated as though it's the same as the arrival of samples. +If you need time-based decay when samples are not arriving precisely at set intervals, then +this package will not support your needs at present. + +#### SimpleEWMA + +A SimpleEWMA is designed for low CPU and memory consumption. It **will** have different behavior than the VariableEWMA +for multiple reasons. It has no warm-up period and it uses a constant +decay. These properties let it use less memory. It will also behave +differently when it's equal to zero, which is assumed to mean +uninitialized, so if a value is likely to actually become zero over time, +then any non-zero value will cause a sharp jump instead of a small change. + +#### VariableEWMA + +Unlike SimpleEWMA, this supports a custom age which must be stored, and thus uses more memory. +It also has a "warmup" time when you start adding values to it. It will report a value of 0.0 +until you have added the required number of samples to it. It uses some memory to store the +number of samples added to it. As a result it uses a little over twice the memory of SimpleEWMA. + +## Usage + +### API Documentation + +View the GoDoc generated documentation [here](http://godoc.org/github.com/VividCortex/ewma). + +```go +package main + +import "github.com/VividCortex/ewma" + +func main() { + samples := [100]float64{ + 4599, 5711, 4746, 4621, 5037, 4218, 4925, 4281, 5207, 5203, 5594, 5149, + } + + e := ewma.NewMovingAverage() //=> Returns a SimpleEWMA if called without params + a := ewma.NewMovingAverage(5) //=> returns a VariableEWMA with a decay of 2 / (5 + 1) + + for _, f := range samples { + e.Add(f) + a.Add(f) + } + + e.Value() //=> 13.577404704631077 + a.Value() //=> 1.5806140565521463e-12 +} +``` + +## Contributing + +We only accept pull requests for minor fixes or improvements. This includes: + +* Small bug fixes +* Typos +* Documentation or comments + +Please open issues to discuss new features. Pull requests for new features will be rejected, +so we recommend forking the repository and making changes in your fork for your use case. + +## License + +This repository is Copyright (c) 2013 VividCortex, Inc. All rights reserved. +It is licensed under the MIT license. Please see the LICENSE file for applicable license terms. diff --git a/vendor/github.com/VividCortex/ewma/codecov.yml b/vendor/github.com/VividCortex/ewma/codecov.yml new file mode 100644 index 00000000..0d36d903 --- /dev/null +++ b/vendor/github.com/VividCortex/ewma/codecov.yml @@ -0,0 +1,6 @@ +coverage: + status: + project: + default: + threshold: 15% + patch: off diff --git a/vendor/github.com/VividCortex/ewma/ewma.go b/vendor/github.com/VividCortex/ewma/ewma.go new file mode 100644 index 00000000..44d5d53e --- /dev/null +++ b/vendor/github.com/VividCortex/ewma/ewma.go @@ -0,0 +1,126 @@ +// Package ewma implements exponentially weighted moving averages. +package ewma + +// Copyright (c) 2013 VividCortex, Inc. All rights reserved. +// Please see the LICENSE file for applicable license terms. + +const ( + // By default, we average over a one-minute period, which means the average + // age of the metrics in the period is 30 seconds. + AVG_METRIC_AGE float64 = 30.0 + + // The formula for computing the decay factor from the average age comes + // from "Production and Operations Analysis" by Steven Nahmias. + DECAY float64 = 2 / (float64(AVG_METRIC_AGE) + 1) + + // For best results, the moving average should not be initialized to the + // samples it sees immediately. The book "Production and Operations + // Analysis" by Steven Nahmias suggests initializing the moving average to + // the mean of the first 10 samples. Until the VariableEwma has seen this + // many samples, it is not "ready" to be queried for the value of the + // moving average. This adds some memory cost. + WARMUP_SAMPLES uint8 = 10 +) + +// MovingAverage is the interface that computes a moving average over a time- +// series stream of numbers. The average may be over a window or exponentially +// decaying. +type MovingAverage interface { + Add(float64) + Value() float64 + Set(float64) +} + +// NewMovingAverage constructs a MovingAverage that computes an average with the +// desired characteristics in the moving window or exponential decay. If no +// age is given, it constructs a default exponentially weighted implementation +// that consumes minimal memory. The age is related to the decay factor alpha +// by the formula given for the DECAY constant. It signifies the average age +// of the samples as time goes to infinity. +func NewMovingAverage(age ...float64) MovingAverage { + if len(age) == 0 || age[0] == AVG_METRIC_AGE { + return new(SimpleEWMA) + } + return &VariableEWMA{ + decay: 2 / (age[0] + 1), + } +} + +// A SimpleEWMA represents the exponentially weighted moving average of a +// series of numbers. It WILL have different behavior than the VariableEWMA +// for multiple reasons. It has no warm-up period and it uses a constant +// decay. These properties let it use less memory. It will also behave +// differently when it's equal to zero, which is assumed to mean +// uninitialized, so if a value is likely to actually become zero over time, +// then any non-zero value will cause a sharp jump instead of a small change. +// However, note that this takes a long time, and the value may just +// decays to a stable value that's close to zero, but which won't be mistaken +// for uninitialized. See http://play.golang.org/p/litxBDr_RC for example. +type SimpleEWMA struct { + // The current value of the average. After adding with Add(), this is + // updated to reflect the average of all values seen thus far. + value float64 +} + +// Add adds a value to the series and updates the moving average. +func (e *SimpleEWMA) Add(value float64) { + if e.value == 0 { // this is a proxy for "uninitialized" + e.value = value + } else { + e.value = (value * DECAY) + (e.value * (1 - DECAY)) + } +} + +// Value returns the current value of the moving average. +func (e *SimpleEWMA) Value() float64 { + return e.value +} + +// Set sets the EWMA's value. +func (e *SimpleEWMA) Set(value float64) { + e.value = value +} + +// VariableEWMA represents the exponentially weighted moving average of a series of +// numbers. Unlike SimpleEWMA, it supports a custom age, and thus uses more memory. +type VariableEWMA struct { + // The multiplier factor by which the previous samples decay. + decay float64 + // The current value of the average. + value float64 + // The number of samples added to this instance. + count uint8 +} + +// Add adds a value to the series and updates the moving average. +func (e *VariableEWMA) Add(value float64) { + switch { + case e.count < WARMUP_SAMPLES: + e.count++ + e.value += value + case e.count == WARMUP_SAMPLES: + e.count++ + e.value = e.value / float64(WARMUP_SAMPLES) + e.value = (value * e.decay) + (e.value * (1 - e.decay)) + default: + e.value = (value * e.decay) + (e.value * (1 - e.decay)) + } +} + +// Value returns the current value of the average, or 0.0 if the series hasn't +// warmed up yet. +func (e *VariableEWMA) Value() float64 { + if e.count <= WARMUP_SAMPLES { + return 0.0 + } + + return e.value +} + +// Set sets the EWMA's value. +func (e *VariableEWMA) Set(value float64) { + e.value = value + if e.count <= WARMUP_SAMPLES { + e.count = WARMUP_SAMPLES + 1 + } +} diff --git a/vendor/github.com/acarl005/stripansi/LICENSE b/vendor/github.com/acarl005/stripansi/LICENSE new file mode 100644 index 00000000..00abe0db --- /dev/null +++ b/vendor/github.com/acarl005/stripansi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Andrew Carlson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/acarl005/stripansi/README.md b/vendor/github.com/acarl005/stripansi/README.md new file mode 100644 index 00000000..8bdb1f50 --- /dev/null +++ b/vendor/github.com/acarl005/stripansi/README.md @@ -0,0 +1,30 @@ +Strip ANSI +========== + +This Go package removes ANSI escape codes from strings. + +Ideally, we would prevent these from appearing in any text we want to process. +However, sometimes this can't be helped, and we need to be able to deal with that noise. +This will use a regexp to remove those unwanted escape codes. + + +## Install + +```sh +$ go get -u github.com/acarl005/stripansi +``` + +## Usage + +```go +import ( + "fmt" + "github.com/acarl005/stripansi" +) + +func main() { + msg := "\x1b[38;5;140m foo\x1b[0m bar" + cleanMsg := stripansi.Strip(msg) + fmt.Println(cleanMsg) // " foo bar" +} +``` diff --git a/vendor/github.com/acarl005/stripansi/stripansi.go b/vendor/github.com/acarl005/stripansi/stripansi.go new file mode 100644 index 00000000..235732a7 --- /dev/null +++ b/vendor/github.com/acarl005/stripansi/stripansi.go @@ -0,0 +1,13 @@ +package stripansi + +import ( + "regexp" +) + +const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" + +var re = regexp.MustCompile(ansi) + +func Strip(str string) string { + return re.ReplaceAllString(str, "") +} diff --git a/vendor/github.com/aead/serpent/.gitignore b/vendor/github.com/aead/serpent/.gitignore new file mode 100644 index 00000000..9d3d8437 --- /dev/null +++ b/vendor/github.com/aead/serpent/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +.vscode + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/aead/serpent/LICENSE b/vendor/github.com/aead/serpent/LICENSE new file mode 100644 index 00000000..b6a9210b --- /dev/null +++ b/vendor/github.com/aead/serpent/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Andreas Auernhammer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/aead/serpent/README.md b/vendor/github.com/aead/serpent/README.md new file mode 100644 index 00000000..6dbceee8 --- /dev/null +++ b/vendor/github.com/aead/serpent/README.md @@ -0,0 +1,9 @@ +[![Godoc Reference](https://godoc.org/github.com/aead/serpent?status.svg)](https://godoc.org/github.com/aead/serpent) + +## The Serpent block cipher + +Serpent is a symmetric key block cipher that was a finalist in the Advanced Encryption Standard (AES) contest, +where it was ranked second to Rijndael. Serpent was designed by Ross Anderson, Eli Biham, and Lars Knudsen. + +### Installation +Install in your GOPATH: `go get -u github.com/aead/serpent` diff --git a/vendor/github.com/aead/serpent/sbox_ref.go b/vendor/github.com/aead/serpent/sbox_ref.go new file mode 100644 index 00000000..515afc69 --- /dev/null +++ b/vendor/github.com/aead/serpent/sbox_ref.go @@ -0,0 +1,316 @@ +// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. +// Use of this source code is governed by a license that can be +// found in the LICENSE file. + +package serpent + +// The linear transformation of serpent +// This version, tries not to minimize the +// number of registers, but maximize parallism. +func linear(v0, v1, v2, v3 *uint32) { + t0 := ((*v0 << 13) | (*v0 >> (32 - 13))) + t2 := ((*v2 << 3) | (*v2 >> (32 - 3))) + t1 := *v1 ^ t0 ^ t2 + t3 := *v3 ^ t2 ^ (t0 << 3) + *v1 = (t1 << 1) | (t1 >> (32 - 1)) + *v3 = (t3 << 7) | (t3 >> (32 - 7)) + t0 ^= *v1 ^ *v3 + t2 ^= *v3 ^ (*v1 << 7) + *v0 = (t0 << 5) | (t0 >> (32 - 5)) + *v2 = (t2 << 22) | (t2 >> (32 - 22)) +} + +// The inverse linear transformation of serpent +// This version, tries not to minimize the +// number of registers, but maximize parallism. +func linearInv(v0, v1, v2, v3 *uint32) { + t2 := (*v2 >> 22) | (*v2 << (32 - 22)) + t0 := (*v0 >> 5) | (*v0 << (32 - 5)) + t2 ^= *v3 ^ (*v1 << 7) + t0 ^= *v1 ^ *v3 + t3 := (*v3 >> 7) | (*v3 << (32 - 7)) + t1 := (*v1 >> 1) | (*v1 << (32 - 1)) + *v3 = t3 ^ t2 ^ (t0 << 3) + *v1 = t1 ^ t0 ^ t2 + *v2 = (t2 >> 3) | (t2 << (32 - 3)) + *v0 = (t0 >> 13) | (t0 << (32 - 13)) +} + +// The following functions sb0,sb1, ..., sb7 represent the 8 Serpent S-Boxes. +// sb0Inv til sb7Inv are the inverse functions (e.g. sb0Inv is the Inverse to sb0 +// and vice versa). +// The S-Boxes differ from the original Serpent definitions. This is for +// optimisation. The functions use the Serpent S-Box improvements for (non x86) +// from Dr. B. R. Gladman and Sam Simpson. + +// S-Box 0 +func sb0(r0, r1, r2, r3 *uint32) { + t0 := *r0 ^ *r3 + t1 := *r2 ^ t0 + t2 := *r1 ^ t1 + *r3 = (*r0 & *r3) ^ t2 + t3 := *r0 ^ (*r1 & t0) + *r2 = t2 ^ (*r2 | t3) + t4 := *r3 & (t1 ^ t3) + *r1 = (^t1) ^ t4 + *r0 = t4 ^ (^t3) +} + +// Inverse S-Box 0 +func sb0Inv(r0, r1, r2, r3 *uint32) { + t0 := ^(*r0) + t1 := *r0 ^ *r1 + t2 := *r3 ^ (t0 | t1) + t3 := *r2 ^ t2 + *r2 = t1 ^ t3 + t4 := t0 ^ (*r3 & t1) + *r1 = t2 ^ (*r2 & t4) + *r3 = (*r0 & t2) ^ (t3 | *r1) + *r0 = *r3 ^ (t3 ^ t4) +} + +// S-Box 1 +func sb1(r0, r1, r2, r3 *uint32) { + t0 := *r1 ^ (^(*r0)) + t1 := *r2 ^ (*r0 | t0) + *r2 = *r3 ^ t1 + t2 := *r1 ^ (*r3 | t0) + t3 := t0 ^ *r2 + *r3 = t3 ^ (t1 & t2) + t4 := t1 ^ t2 + *r1 = *r3 ^ t4 + *r0 = t1 ^ (t3 & t4) +} + +// Inverse S-Box 1 +func sb1Inv(r0, r1, r2, r3 *uint32) { + t0 := *r1 ^ *r3 + t1 := *r0 ^ (*r1 & t0) + t2 := t0 ^ t1 + *r3 = *r2 ^ t2 + t3 := *r1 ^ (t0 & t1) + t4 := *r3 | t3 + *r1 = t1 ^ t4 + t5 := ^(*r1) + t6 := *r3 ^ t3 + *r0 = t5 ^ t6 + *r2 = t2 ^ (t5 | t6) +} + +// S-Box 2 +func sb2(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + v3 := *r3 // save r3 + t0 := ^v0 + t1 := *r1 ^ v3 + t2 := *r2 & t0 + *r0 = t1 ^ t2 + t3 := *r2 ^ t0 + t4 := *r2 ^ *r0 + t5 := *r1 & t4 + *r3 = t3 ^ t5 + *r2 = v0 ^ ((v3 | t5) & (*r0 | t3)) + *r1 = (t1 ^ *r3) ^ (*r2 ^ (v3 | t0)) +} + +// Inverse S-Box 2 +func sb2Inv(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + v3 := *r3 // save r3 + t0 := *r1 ^ v3 + t1 := ^t0 + t2 := v0 ^ *r2 + t3 := *r2 ^ t0 + t4 := *r1 & t3 + *r0 = t2 ^ t4 + t5 := v0 | t1 + t6 := v3 ^ t5 + t7 := t2 | t6 + *r3 = t0 ^ t7 + t8 := ^t3 + t9 := *r0 | *r3 + *r1 = t8 ^ t9 + *r2 = (v3 & t8) ^ (t2 ^ t9) +} + +// S-Box 3 +func sb3(r0, r1, r2, r3 *uint32) { + v1 := *r1 // save r1 + v3 := *r3 // save r3 + t0 := *r0 ^ *r1 + t1 := *r0 & *r2 + t2 := *r0 | *r3 + t3 := *r2 ^ *r3 + t4 := t0 & t2 + t5 := t1 | t4 + *r2 = t3 ^ t5 + t6 := *r1 ^ t2 + t7 := t5 ^ t6 + t8 := t3 & t7 + *r0 = t0 ^ t8 + t9 := *r2 & *r0 + *r1 = t7 ^ t9 + *r3 = (v1 | v3) ^ (t3 ^ t9) +} + +// Inverse S-Box 3 +func sb3Inv(r0, r1, r2, r3 *uint32) { + t0 := *r0 | *r1 + t1 := *r1 ^ *r2 + t2 := *r1 & t1 + t3 := *r0 ^ t2 + t4 := *r2 ^ t3 + t5 := *r3 | t3 + *r0 = t1 ^ t5 + t6 := t1 | t5 + t7 := *r3 ^ t6 + *r2 = t4 ^ t7 + t8 := t0 ^ t7 + t9 := *r0 & t8 + *r3 = t3 ^ t9 + *r1 = *r3 ^ (*r0 ^ t8) +} + +// S-Box 4 +func sb4(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + t0 := v0 ^ *r3 + t1 := *r3 & t0 + t2 := *r2 ^ t1 + t3 := *r1 | t2 + *r3 = t0 ^ t3 + t4 := ^(*r1) + t5 := t0 | t4 + *r0 = t2 ^ t5 + t6 := v0 & *r0 + t7 := t0 ^ t4 + t8 := t3 & t7 + *r2 = t6 ^ t8 + *r1 = (v0 ^ t2) ^ (t7 & *r2) +} + +// Inverse S-Box 4 +func sb4Inv(r0, r1, r2, r3 *uint32) { + v3 := *r3 // save r3 + t0 := *r2 | v3 + t1 := *r0 & t0 + t2 := *r1 ^ t1 + t3 := *r0 & t2 + t4 := *r2 ^ t3 + *r1 = v3 ^ t4 + t5 := ^(*r0) + t6 := t4 & *r1 + *r3 = t2 ^ t6 + t7 := *r1 | t5 + t8 := v3 ^ t7 + *r0 = *r3 ^ t8 + *r2 = (t2 & t8) ^ (*r1 ^ t5) +} + +// S-Box 5 +func sb5(r0, r1, r2, r3 *uint32) { + v1 := *r1 // save r1 + t0 := ^(*r0) + t1 := *r0 ^ v1 + t2 := *r0 ^ *r3 + t3 := *r2 ^ t0 + t4 := t1 | t2 + *r0 = t3 ^ t4 + t5 := *r3 & *r0 + t6 := t1 ^ *r0 + *r1 = t5 ^ t6 + t7 := t0 | *r0 + t8 := t1 | t5 + t9 := t2 ^ t7 + *r2 = t8 ^ t9 + *r3 = (v1 ^ t5) ^ (*r1 & t9) +} + +// Inverse S-Box 5 +func sb5Inv(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + v1 := *r1 // save r1 + v3 := *r3 // save r3 + t0 := ^(*r2) + t1 := v1 & t0 + t2 := v3 ^ t1 + t3 := v0 & t2 + t4 := v1 ^ t0 + *r3 = t3 ^ t4 + t5 := v1 | *r3 + t6 := v0 & t5 + *r1 = t2 ^ t6 + t7 := v0 | v3 + t8 := t0 ^ t5 + *r0 = t7 ^ t8 + *r2 = (v1 & t7) ^ (t3 | (v0 ^ *r2)) +} + +// S-Box 6 +func sb6(r0, r1, r2, r3 *uint32) { + t0 := ^(*r0) + t1 := *r0 ^ *r3 + t2 := *r1 ^ t1 + t3 := t0 | t1 + t4 := *r2 ^ t3 + *r1 = *r1 ^ t4 + t5 := t1 | *r1 + t6 := *r3 ^ t5 + t7 := t4 & t6 + *r2 = t2 ^ t7 + t8 := t4 ^ t6 + *r0 = *r2 ^ t8 + *r3 = (^t4) ^ (t2 & t8) +} + +// Inverse S-Box 6 +func sb6Inv(r0, r1, r2, r3 *uint32) { + v1 := *r1 // save r1 + v3 := *r3 // save r3 + t0 := ^(*r0) + t1 := *r0 ^ v1 + t2 := *r2 ^ t1 + t3 := *r2 | t0 + t4 := v3 ^ t3 + *r1 = t2 ^ t4 + t5 := t2 & t4 + t6 := t1 ^ t5 + t7 := v1 | t6 + *r3 = t4 ^ t7 + t8 := v1 | *r3 + *r0 = t6 ^ t8 + *r2 = (v3 & t0) ^ (t2 ^ t8) +} + +// S-Box 7 +func sb7(r0, r1, r2, r3 *uint32) { + t0 := *r1 ^ *r2 + t1 := *r2 & t0 + t2 := *r3 ^ t1 + t3 := *r0 ^ t2 + t4 := *r3 | t0 + t5 := t3 & t4 + *r1 = *r1 ^ t5 + t6 := t2 | *r1 + t7 := *r0 & t3 + *r3 = t0 ^ t7 + t8 := t3 ^ t6 + t9 := *r3 & t8 + *r2 = t2 ^ t9 + *r0 = (^t8) ^ (*r3 & *r2) +} + +// Inverse S-Box 7 +func sb7Inv(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + v3 := *r3 // save r3 + t0 := *r2 | (v0 & *r1) + t1 := v3 & (v0 | *r1) + *r3 = t0 ^ t1 + t2 := ^v3 + t3 := *r1 ^ t1 + t4 := t3 | (*r3 ^ t2) + *r1 = v0 ^ t4 + *r0 = (*r2 ^ t3) ^ (v3 | *r1) + *r2 = (t0 ^ *r1) ^ (*r0 ^ (v0 & *r3)) +} diff --git a/vendor/github.com/aead/serpent/serpent.go b/vendor/github.com/aead/serpent/serpent.go new file mode 100644 index 00000000..b3fb811d --- /dev/null +++ b/vendor/github.com/aead/serpent/serpent.go @@ -0,0 +1,119 @@ +// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. +// Use of this source code is governed by a license that can be +// found in the LICENSE file. + +// Package serpent implements the Serpent block cipher +// submitted to the AES challenge. Serpent was designed by +// Ross Anderson, Eli Biham und Lars Knudsen. +// The block cipher takes a 128, 192 or 256 bit key and +// has a block size of 128 bit. +package serpent // import "github.com/aead/serpent" + +import ( + "crypto/cipher" + "errors" +) + +// BlockSize is the serpent block size in bytes. +const BlockSize = 16 + +const phi = 0x9e3779b9 // The Serpent phi constant (sqrt(5) - 1) * 2**31 + +var errKeySize = errors.New("invalid key size") + +// NewCipher returns a new cipher.Block implementing the serpent block cipher. +// The key argument must be 128, 192 or 256 bit (16, 24, 32 byte). +func NewCipher(key []byte) (cipher.Block, error) { + if k := len(key); k != 16 && k != 24 && k != 32 { + return nil, errKeySize + } + s := &subkeys{} + s.keySchedule(key) + return s, nil +} + +// The 132 32 bit subkeys of serpent +type subkeys [132]uint32 + +func (s *subkeys) BlockSize() int { return BlockSize } + +func (s *subkeys) Encrypt(dst, src []byte) { + if len(src) < BlockSize { + panic("src buffer to small") + } + if len(dst) < BlockSize { + panic("dst buffer to small") + } + encryptBlock(dst, src, s) +} + +func (s *subkeys) Decrypt(dst, src []byte) { + if len(src) < BlockSize { + panic("src buffer to small") + } + if len(dst) < BlockSize { + panic("dst buffer to small") + } + decryptBlock(dst, src, s) +} + +// The key schedule of serpent. +func (s *subkeys) keySchedule(key []byte) { + var k [16]uint32 + j := 0 + for i := 0; i+4 <= len(key); i += 4 { + k[j] = uint32(key[i]) | uint32(key[i+1])<<8 | uint32(key[i+2])<<16 | uint32(key[i+3])<<24 + j++ + } + if j < 8 { + k[j] = 1 + } + + for i := 8; i < 16; i++ { + x := k[i-8] ^ k[i-5] ^ k[i-3] ^ k[i-1] ^ phi ^ uint32(i-8) + k[i] = (x << 11) | (x >> 21) + s[i-8] = k[i] + } + for i := 8; i < 132; i++ { + x := s[i-8] ^ s[i-5] ^ s[i-3] ^ s[i-1] ^ phi ^ uint32(i) + s[i] = (x << 11) | (x >> 21) + } + + sb3(&s[0], &s[1], &s[2], &s[3]) + sb2(&s[4], &s[5], &s[6], &s[7]) + sb1(&s[8], &s[9], &s[10], &s[11]) + sb0(&s[12], &s[13], &s[14], &s[15]) + sb7(&s[16], &s[17], &s[18], &s[19]) + sb6(&s[20], &s[21], &s[22], &s[23]) + sb5(&s[24], &s[25], &s[26], &s[27]) + sb4(&s[28], &s[29], &s[30], &s[31]) + + sb3(&s[32], &s[33], &s[34], &s[35]) + sb2(&s[36], &s[37], &s[38], &s[39]) + sb1(&s[40], &s[41], &s[42], &s[43]) + sb0(&s[44], &s[45], &s[46], &s[47]) + sb7(&s[48], &s[49], &s[50], &s[51]) + sb6(&s[52], &s[53], &s[54], &s[55]) + sb5(&s[56], &s[57], &s[58], &s[59]) + sb4(&s[60], &s[61], &s[62], &s[63]) + + sb3(&s[64], &s[65], &s[66], &s[67]) + sb2(&s[68], &s[69], &s[70], &s[71]) + sb1(&s[72], &s[73], &s[74], &s[75]) + sb0(&s[76], &s[77], &s[78], &s[79]) + sb7(&s[80], &s[81], &s[82], &s[83]) + sb6(&s[84], &s[85], &s[86], &s[87]) + sb5(&s[88], &s[89], &s[90], &s[91]) + sb4(&s[92], &s[93], &s[94], &s[95]) + + sb3(&s[96], &s[97], &s[98], &s[99]) + sb2(&s[100], &s[101], &s[102], &s[103]) + sb1(&s[104], &s[105], &s[106], &s[107]) + sb0(&s[108], &s[109], &s[110], &s[111]) + sb7(&s[112], &s[113], &s[114], &s[115]) + sb6(&s[116], &s[117], &s[118], &s[119]) + sb5(&s[120], &s[121], &s[122], &s[123]) + sb4(&s[124], &s[125], &s[126], &s[127]) + + sb3(&s[128], &s[129], &s[130], &s[131]) +} diff --git a/vendor/github.com/aead/serpent/serpent_ref.go b/vendor/github.com/aead/serpent/serpent_ref.go new file mode 100644 index 00000000..2d3ff02a --- /dev/null +++ b/vendor/github.com/aead/serpent/serpent_ref.go @@ -0,0 +1,276 @@ +// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. +// Use of this source code is governed by a license that can be +// found in the LICENSE file. + +package serpent + +// Encrypts one block with the given 132 sub-keys sk. +func encryptBlock(dst, src []byte, sk *subkeys) { + // Transform the input block to 4 x 32 bit registers + r0 := uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 | uint32(src[3])<<24 + r1 := uint32(src[4]) | uint32(src[5])<<8 | uint32(src[6])<<16 | uint32(src[7])<<24 + r2 := uint32(src[8]) | uint32(src[9])<<8 | uint32(src[10])<<16 | uint32(src[11])<<24 + r3 := uint32(src[12]) | uint32(src[13])<<8 | uint32(src[14])<<16 | uint32(src[15])<<24 + + // Encrypt the block with the 132 sub-keys and 8 S-Boxes + r0, r1, r2, r3 = r0^sk[0], r1^sk[1], r2^sk[2], r3^sk[3] + sb0(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[4], r1^sk[5], r2^sk[6], r3^sk[7] + sb1(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[8], r1^sk[9], r2^sk[10], r3^sk[11] + sb2(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[12], r1^sk[13], r2^sk[14], r3^sk[15] + sb3(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[16], r1^sk[17], r2^sk[18], r3^sk[19] + sb4(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[20], r1^sk[21], r2^sk[22], r3^sk[23] + sb5(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[24], r1^sk[25], r2^sk[26], r3^sk[27] + sb6(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[28], r1^sk[29], r2^sk[30], r3^sk[31] + sb7(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + + r0, r1, r2, r3 = r0^sk[32], r1^sk[33], r2^sk[34], r3^sk[35] + sb0(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[36], r1^sk[37], r2^sk[38], r3^sk[39] + sb1(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[40], r1^sk[41], r2^sk[42], r3^sk[43] + sb2(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[44], r1^sk[45], r2^sk[46], r3^sk[47] + sb3(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[48], r1^sk[49], r2^sk[50], r3^sk[51] + sb4(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[52], r1^sk[53], r2^sk[54], r3^sk[55] + sb5(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[56], r1^sk[57], r2^sk[58], r3^sk[59] + sb6(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[60], r1^sk[61], r2^sk[62], r3^sk[63] + sb7(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + + r0, r1, r2, r3 = r0^sk[64], r1^sk[65], r2^sk[66], r3^sk[67] + sb0(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[68], r1^sk[69], r2^sk[70], r3^sk[71] + sb1(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[72], r1^sk[73], r2^sk[74], r3^sk[75] + sb2(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[76], r1^sk[77], r2^sk[78], r3^sk[79] + sb3(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[80], r1^sk[81], r2^sk[82], r3^sk[83] + sb4(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[84], r1^sk[85], r2^sk[86], r3^sk[87] + sb5(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[88], r1^sk[89], r2^sk[90], r3^sk[91] + sb6(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[92], r1^sk[93], r2^sk[94], r3^sk[95] + sb7(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + + r0, r1, r2, r3 = r0^sk[96], r1^sk[97], r2^sk[98], r3^sk[99] + sb0(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[100], r1^sk[101], r2^sk[102], r3^sk[103] + sb1(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[104], r1^sk[105], r2^sk[106], r3^sk[107] + sb2(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[108], r1^sk[109], r2^sk[110], r3^sk[111] + sb3(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[112], r1^sk[113], r2^sk[114], r3^sk[115] + sb4(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[116], r1^sk[117], r2^sk[118], r3^sk[119] + sb5(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[120], r1^sk[121], r2^sk[122], r3^sk[123] + sb6(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[124], r1^sk[125], r2^sk[126], r3^sk[127] + sb7(&r0, &r1, &r2, &r3) + + // whitening + r0 ^= sk[128] + r1 ^= sk[129] + r2 ^= sk[130] + r3 ^= sk[131] + + // write the encrypted block to the output + + dst[0] = byte(r0) + dst[1] = byte(r0 >> 8) + dst[2] = byte(r0 >> 16) + dst[3] = byte(r0 >> 24) + dst[4] = byte(r1) + dst[5] = byte(r1 >> 8) + dst[6] = byte(r1 >> 16) + dst[7] = byte(r1 >> 24) + dst[8] = byte(r2) + dst[9] = byte(r2 >> 8) + dst[10] = byte(r2 >> 16) + dst[11] = byte(r2 >> 24) + dst[12] = byte(r3) + dst[13] = byte(r3 >> 8) + dst[14] = byte(r3 >> 16) + dst[15] = byte(r3 >> 24) +} + +// Decrypts one block with the given 132 sub-keys sk. +func decryptBlock(dst, src []byte, sk *subkeys) { + // Transform the input block to 4 x 32 bit registers + r0 := uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 | uint32(src[3])<<24 + r1 := uint32(src[4]) | uint32(src[5])<<8 | uint32(src[6])<<16 | uint32(src[7])<<24 + r2 := uint32(src[8]) | uint32(src[9])<<8 | uint32(src[10])<<16 | uint32(src[11])<<24 + r3 := uint32(src[12]) | uint32(src[13])<<8 | uint32(src[14])<<16 | uint32(src[15])<<24 + + // undo whitening + r0 ^= sk[128] + r1 ^= sk[129] + r2 ^= sk[130] + r3 ^= sk[131] + + // Decrypt the block with the 132 sub-keys and 8 S-Boxes + sb7Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[124], r1^sk[125], r2^sk[126], r3^sk[127] + linearInv(&r0, &r1, &r2, &r3) + sb6Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[120], r1^sk[121], r2^sk[122], r3^sk[123] + linearInv(&r0, &r1, &r2, &r3) + sb5Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[116], r1^sk[117], r2^sk[118], r3^sk[119] + linearInv(&r0, &r1, &r2, &r3) + sb4Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[112], r1^sk[113], r2^sk[114], r3^sk[115] + linearInv(&r0, &r1, &r2, &r3) + sb3Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[108], r1^sk[109], r2^sk[110], r3^sk[111] + linearInv(&r0, &r1, &r2, &r3) + sb2Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[104], r1^sk[105], r2^sk[106], r3^sk[107] + linearInv(&r0, &r1, &r2, &r3) + sb1Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[100], r1^sk[101], r2^sk[102], r3^sk[103] + linearInv(&r0, &r1, &r2, &r3) + sb0Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[96], r1^sk[97], r2^sk[98], r3^sk[99] + linearInv(&r0, &r1, &r2, &r3) + + sb7Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[92], r1^sk[93], r2^sk[94], r3^sk[95] + linearInv(&r0, &r1, &r2, &r3) + sb6Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[88], r1^sk[89], r2^sk[90], r3^sk[91] + linearInv(&r0, &r1, &r2, &r3) + sb5Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[84], r1^sk[85], r2^sk[86], r3^sk[87] + linearInv(&r0, &r1, &r2, &r3) + sb4Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[80], r1^sk[81], r2^sk[82], r3^sk[83] + linearInv(&r0, &r1, &r2, &r3) + sb3Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[76], r1^sk[77], r2^sk[78], r3^sk[79] + linearInv(&r0, &r1, &r2, &r3) + sb2Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[72], r1^sk[73], r2^sk[74], r3^sk[75] + linearInv(&r0, &r1, &r2, &r3) + sb1Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[68], r1^sk[69], r2^sk[70], r3^sk[71] + linearInv(&r0, &r1, &r2, &r3) + sb0Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[64], r1^sk[65], r2^sk[66], r3^sk[67] + linearInv(&r0, &r1, &r2, &r3) + + sb7Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[60], r1^sk[61], r2^sk[62], r3^sk[63] + linearInv(&r0, &r1, &r2, &r3) + sb6Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[56], r1^sk[57], r2^sk[58], r3^sk[59] + linearInv(&r0, &r1, &r2, &r3) + sb5Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[52], r1^sk[53], r2^sk[54], r3^sk[55] + linearInv(&r0, &r1, &r2, &r3) + sb4Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[48], r1^sk[49], r2^sk[50], r3^sk[51] + linearInv(&r0, &r1, &r2, &r3) + sb3Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[44], r1^sk[45], r2^sk[46], r3^sk[47] + linearInv(&r0, &r1, &r2, &r3) + sb2Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[40], r1^sk[41], r2^sk[42], r3^sk[43] + linearInv(&r0, &r1, &r2, &r3) + sb1Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[36], r1^sk[37], r2^sk[38], r3^sk[39] + linearInv(&r0, &r1, &r2, &r3) + sb0Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[32], r1^sk[33], r2^sk[34], r3^sk[35] + linearInv(&r0, &r1, &r2, &r3) + + sb7Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[28], r1^sk[29], r2^sk[30], r3^sk[31] + linearInv(&r0, &r1, &r2, &r3) + sb6Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[24], r1^sk[25], r2^sk[26], r3^sk[27] + linearInv(&r0, &r1, &r2, &r3) + sb5Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[20], r1^sk[21], r2^sk[22], r3^sk[23] + linearInv(&r0, &r1, &r2, &r3) + sb4Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[16], r1^sk[17], r2^sk[18], r3^sk[19] + linearInv(&r0, &r1, &r2, &r3) + sb3Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[12], r1^sk[13], r2^sk[14], r3^sk[15] + linearInv(&r0, &r1, &r2, &r3) + sb2Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[8], r1^sk[9], r2^sk[10], r3^sk[11] + linearInv(&r0, &r1, &r2, &r3) + sb1Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[4], r1^sk[5], r2^sk[6], r3^sk[7] + linearInv(&r0, &r1, &r2, &r3) + sb0Inv(&r0, &r1, &r2, &r3) + + r0 ^= sk[0] + r1 ^= sk[1] + r2 ^= sk[2] + r3 ^= sk[3] + + // write the decrypted block to the output + dst[0] = byte(r0) + dst[1] = byte(r0 >> 8) + dst[2] = byte(r0 >> 16) + dst[3] = byte(r0 >> 24) + dst[4] = byte(r1) + dst[5] = byte(r1 >> 8) + dst[6] = byte(r1 >> 16) + dst[7] = byte(r1 >> 24) + dst[8] = byte(r2) + dst[9] = byte(r2 >> 8) + dst[10] = byte(r2 >> 16) + dst[11] = byte(r2 >> 24) + dst[12] = byte(r3) + dst[13] = byte(r3 >> 8) + dst[14] = byte(r3 >> 16) + dst[15] = byte(r3 >> 24) +} diff --git a/vendor/github.com/blang/semver/v4/LICENSE b/vendor/github.com/blang/semver/v4/LICENSE new file mode 100644 index 00000000..5ba5c86f --- /dev/null +++ b/vendor/github.com/blang/semver/v4/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Benedikt Lang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/github.com/blang/semver/v4/json.go b/vendor/github.com/blang/semver/v4/json.go new file mode 100644 index 00000000..a74bf7c4 --- /dev/null +++ b/vendor/github.com/blang/semver/v4/json.go @@ -0,0 +1,23 @@ +package semver + +import ( + "encoding/json" +) + +// MarshalJSON implements the encoding/json.Marshaler interface. +func (v Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. +func (v *Version) UnmarshalJSON(data []byte) (err error) { + var versionString string + + if err = json.Unmarshal(data, &versionString); err != nil { + return + } + + *v, err = Parse(versionString) + + return +} diff --git a/vendor/github.com/blang/semver/v4/range.go b/vendor/github.com/blang/semver/v4/range.go new file mode 100644 index 00000000..95f7139b --- /dev/null +++ b/vendor/github.com/blang/semver/v4/range.go @@ -0,0 +1,416 @@ +package semver + +import ( + "fmt" + "strconv" + "strings" + "unicode" +) + +type wildcardType int + +const ( + noneWildcard wildcardType = iota + majorWildcard wildcardType = 1 + minorWildcard wildcardType = 2 + patchWildcard wildcardType = 3 +) + +func wildcardTypefromInt(i int) wildcardType { + switch i { + case 1: + return majorWildcard + case 2: + return minorWildcard + case 3: + return patchWildcard + default: + return noneWildcard + } +} + +type comparator func(Version, Version) bool + +var ( + compEQ comparator = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 0 + } + compNE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) != 0 + } + compGT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 1 + } + compGE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) >= 0 + } + compLT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == -1 + } + compLE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) <= 0 + } +) + +type versionRange struct { + v Version + c comparator +} + +// rangeFunc creates a Range from the given versionRange. +func (vr *versionRange) rangeFunc() Range { + return Range(func(v Version) bool { + return vr.c(v, vr.v) + }) +} + +// Range represents a range of versions. +// A Range can be used to check if a Version satisfies it: +// +// range, err := semver.ParseRange(">1.0.0 <2.0.0") +// range(semver.MustParse("1.1.1") // returns true +type Range func(Version) bool + +// OR combines the existing Range with another Range using logical OR. +func (rf Range) OR(f Range) Range { + return Range(func(v Version) bool { + return rf(v) || f(v) + }) +} + +// AND combines the existing Range with another Range using logical AND. +func (rf Range) AND(f Range) Range { + return Range(func(v Version) bool { + return rf(v) && f(v) + }) +} + +// ParseRange parses a range and returns a Range. +// If the range could not be parsed an error is returned. +// +// Valid ranges are: +// - "<1.0.0" +// - "<=1.0.0" +// - ">1.0.0" +// - ">=1.0.0" +// - "1.0.0", "=1.0.0", "==1.0.0" +// - "!1.0.0", "!=1.0.0" +// +// A Range can consist of multiple ranges separated by space: +// Ranges can be linked by logical AND: +// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0" +// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2 +// +// Ranges can also be linked by logical OR: +// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" +// +// AND has a higher precedence than OR. It's not possible to use brackets. +// +// Ranges can be combined by both AND and OR +// +// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` +func ParseRange(s string) (Range, error) { + parts := splitAndTrim(s) + orParts, err := splitORParts(parts) + if err != nil { + return nil, err + } + expandedParts, err := expandWildcardVersion(orParts) + if err != nil { + return nil, err + } + var orFn Range + for _, p := range expandedParts { + var andFn Range + for _, ap := range p { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + vr, err := buildVersionRange(opStr, vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) + } + rf := vr.rangeFunc() + + // Set function + if andFn == nil { + andFn = rf + } else { // Combine with existing function + andFn = andFn.AND(rf) + } + } + if orFn == nil { + orFn = andFn + } else { + orFn = orFn.OR(andFn) + } + + } + return orFn, nil +} + +// splitORParts splits the already cleaned parts by '||'. +// Checks for invalid positions of the operator and returns an +// error if found. +func splitORParts(parts []string) ([][]string, error) { + var ORparts [][]string + last := 0 + for i, p := range parts { + if p == "||" { + if i == 0 { + return nil, fmt.Errorf("First element in range is '||'") + } + ORparts = append(ORparts, parts[last:i]) + last = i + 1 + } + } + if last == len(parts) { + return nil, fmt.Errorf("Last element in range is '||'") + } + ORparts = append(ORparts, parts[last:]) + return ORparts, nil +} + +// buildVersionRange takes a slice of 2: operator and version +// and builds a versionRange, otherwise an error. +func buildVersionRange(opStr, vStr string) (*versionRange, error) { + c := parseComparator(opStr) + if c == nil { + return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) + } + v, err := Parse(vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) + } + + return &versionRange{ + v: v, + c: c, + }, nil + +} + +// inArray checks if a byte is contained in an array of bytes +func inArray(s byte, list []byte) bool { + for _, el := range list { + if el == s { + return true + } + } + return false +} + +// splitAndTrim splits a range string by spaces and cleans whitespaces +func splitAndTrim(s string) (result []string) { + last := 0 + var lastChar byte + excludeFromSplit := []byte{'>', '<', '='} + for i := 0; i < len(s); i++ { + if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { + if last < i-1 { + result = append(result, s[last:i]) + } + last = i + 1 + } else if s[i] != ' ' { + lastChar = s[i] + } + } + if last < len(s)-1 { + result = append(result, s[last:]) + } + + for i, v := range result { + result[i] = strings.Replace(v, " ", "", -1) + } + + // parts := strings.Split(s, " ") + // for _, x := range parts { + // if s := strings.TrimSpace(x); len(s) != 0 { + // result = append(result, s) + // } + // } + return +} + +// splitComparatorVersion splits the comparator from the version. +// Input must be free of leading or trailing spaces. +func splitComparatorVersion(s string) (string, string, error) { + i := strings.IndexFunc(s, unicode.IsDigit) + if i == -1 { + return "", "", fmt.Errorf("Could not get version from string: %q", s) + } + return strings.TrimSpace(s[0:i]), s[i:], nil +} + +// getWildcardType will return the type of wildcard that the +// passed version contains +func getWildcardType(vStr string) wildcardType { + parts := strings.Split(vStr, ".") + nparts := len(parts) + wildcard := parts[nparts-1] + + possibleWildcardType := wildcardTypefromInt(nparts) + if wildcard == "x" { + return possibleWildcardType + } + + return noneWildcard +} + +// createVersionFromWildcard will convert a wildcard version +// into a regular version, replacing 'x's with '0's, handling +// special cases like '1.x.x' and '1.x' +func createVersionFromWildcard(vStr string) string { + // handle 1.x.x + vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) + vStr2 = strings.Replace(vStr2, ".x", ".0", 1) + parts := strings.Split(vStr2, ".") + + // handle 1.x + if len(parts) == 2 { + return vStr2 + ".0" + } + + return vStr2 +} + +// incrementMajorVersion will increment the major version +// of the passed version +func incrementMajorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[0]) + if err != nil { + return "", err + } + parts[0] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// incrementMajorVersion will increment the minor version +// of the passed version +func incrementMinorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[1]) + if err != nil { + return "", err + } + parts[1] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// expandWildcardVersion will expand wildcards inside versions +// following these rules: +// +// * when dealing with patch wildcards: +// >= 1.2.x will become >= 1.2.0 +// <= 1.2.x will become < 1.3.0 +// > 1.2.x will become >= 1.3.0 +// < 1.2.x will become < 1.2.0 +// != 1.2.x will become < 1.2.0 >= 1.3.0 +// +// * when dealing with minor wildcards: +// >= 1.x will become >= 1.0.0 +// <= 1.x will become < 2.0.0 +// > 1.x will become >= 2.0.0 +// < 1.0 will become < 1.0.0 +// != 1.x will become < 1.0.0 >= 2.0.0 +// +// * when dealing with wildcards without +// version operator: +// 1.2.x will become >= 1.2.0 < 1.3.0 +// 1.x will become >= 1.0.0 < 2.0.0 +func expandWildcardVersion(parts [][]string) ([][]string, error) { + var expandedParts [][]string + for _, p := range parts { + var newParts []string + for _, ap := range p { + if strings.Contains(ap, "x") { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + + versionWildcardType := getWildcardType(vStr) + flatVersion := createVersionFromWildcard(vStr) + + var resultOperator string + var shouldIncrementVersion bool + switch opStr { + case ">": + resultOperator = ">=" + shouldIncrementVersion = true + case ">=": + resultOperator = ">=" + case "<": + resultOperator = "<" + case "<=": + resultOperator = "<" + shouldIncrementVersion = true + case "", "=", "==": + newParts = append(newParts, ">="+flatVersion) + resultOperator = "<" + shouldIncrementVersion = true + case "!=", "!": + newParts = append(newParts, "<"+flatVersion) + resultOperator = ">=" + shouldIncrementVersion = true + } + + var resultVersion string + if shouldIncrementVersion { + switch versionWildcardType { + case patchWildcard: + resultVersion, _ = incrementMinorVersion(flatVersion) + case minorWildcard: + resultVersion, _ = incrementMajorVersion(flatVersion) + } + } else { + resultVersion = flatVersion + } + + ap = resultOperator + resultVersion + } + newParts = append(newParts, ap) + } + expandedParts = append(expandedParts, newParts) + } + + return expandedParts, nil +} + +func parseComparator(s string) comparator { + switch s { + case "==": + fallthrough + case "": + fallthrough + case "=": + return compEQ + case ">": + return compGT + case ">=": + return compGE + case "<": + return compLT + case "<=": + return compLE + case "!": + fallthrough + case "!=": + return compNE + } + + return nil +} + +// MustParseRange is like ParseRange but panics if the range cannot be parsed. +func MustParseRange(s string) Range { + r, err := ParseRange(s) + if err != nil { + panic(`semver: ParseRange(` + s + `): ` + err.Error()) + } + return r +} diff --git a/vendor/github.com/blang/semver/v4/semver.go b/vendor/github.com/blang/semver/v4/semver.go new file mode 100644 index 00000000..307de610 --- /dev/null +++ b/vendor/github.com/blang/semver/v4/semver.go @@ -0,0 +1,476 @@ +package semver + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +const ( + numbers string = "0123456789" + alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + alphanum = alphas + numbers +) + +// SpecVersion is the latest fully supported spec version of semver +var SpecVersion = Version{ + Major: 2, + Minor: 0, + Patch: 0, +} + +// Version represents a semver compatible version +type Version struct { + Major uint64 + Minor uint64 + Patch uint64 + Pre []PRVersion + Build []string //No Precedence +} + +// Version to string +func (v Version) String() string { + b := make([]byte, 0, 5) + b = strconv.AppendUint(b, v.Major, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Minor, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Patch, 10) + + if len(v.Pre) > 0 { + b = append(b, '-') + b = append(b, v.Pre[0].String()...) + + for _, pre := range v.Pre[1:] { + b = append(b, '.') + b = append(b, pre.String()...) + } + } + + if len(v.Build) > 0 { + b = append(b, '+') + b = append(b, v.Build[0]...) + + for _, build := range v.Build[1:] { + b = append(b, '.') + b = append(b, build...) + } + } + + return string(b) +} + +// FinalizeVersion discards prerelease and build number and only returns +// major, minor and patch number. +func (v Version) FinalizeVersion() string { + b := make([]byte, 0, 5) + b = strconv.AppendUint(b, v.Major, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Minor, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Patch, 10) + return string(b) +} + +// Equals checks if v is equal to o. +func (v Version) Equals(o Version) bool { + return (v.Compare(o) == 0) +} + +// EQ checks if v is equal to o. +func (v Version) EQ(o Version) bool { + return (v.Compare(o) == 0) +} + +// NE checks if v is not equal to o. +func (v Version) NE(o Version) bool { + return (v.Compare(o) != 0) +} + +// GT checks if v is greater than o. +func (v Version) GT(o Version) bool { + return (v.Compare(o) == 1) +} + +// GTE checks if v is greater than or equal to o. +func (v Version) GTE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// GE checks if v is greater than or equal to o. +func (v Version) GE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// LT checks if v is less than o. +func (v Version) LT(o Version) bool { + return (v.Compare(o) == -1) +} + +// LTE checks if v is less than or equal to o. +func (v Version) LTE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// LE checks if v is less than or equal to o. +func (v Version) LE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// Compare compares Versions v to o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v Version) Compare(o Version) int { + if v.Major != o.Major { + if v.Major > o.Major { + return 1 + } + return -1 + } + if v.Minor != o.Minor { + if v.Minor > o.Minor { + return 1 + } + return -1 + } + if v.Patch != o.Patch { + if v.Patch > o.Patch { + return 1 + } + return -1 + } + + // Quick comparison if a version has no prerelease versions + if len(v.Pre) == 0 && len(o.Pre) == 0 { + return 0 + } else if len(v.Pre) == 0 && len(o.Pre) > 0 { + return 1 + } else if len(v.Pre) > 0 && len(o.Pre) == 0 { + return -1 + } + + i := 0 + for ; i < len(v.Pre) && i < len(o.Pre); i++ { + if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { + continue + } else if comp == 1 { + return 1 + } else { + return -1 + } + } + + // If all pr versions are the equal but one has further prversion, this one greater + if i == len(v.Pre) && i == len(o.Pre) { + return 0 + } else if i == len(v.Pre) && i < len(o.Pre) { + return -1 + } else { + return 1 + } + +} + +// IncrementPatch increments the patch version +func (v *Version) IncrementPatch() error { + v.Patch++ + return nil +} + +// IncrementMinor increments the minor version +func (v *Version) IncrementMinor() error { + v.Minor++ + v.Patch = 0 + return nil +} + +// IncrementMajor increments the major version +func (v *Version) IncrementMajor() error { + v.Major++ + v.Minor = 0 + v.Patch = 0 + return nil +} + +// Validate validates v and returns error in case +func (v Version) Validate() error { + // Major, Minor, Patch already validated using uint64 + + for _, pre := range v.Pre { + if !pre.IsNum { //Numeric prerelease versions already uint64 + if len(pre.VersionStr) == 0 { + return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) + } + if !containsOnly(pre.VersionStr, alphanum) { + return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) + } + } + } + + for _, build := range v.Build { + if len(build) == 0 { + return fmt.Errorf("Build meta data can not be empty %q", build) + } + if !containsOnly(build, alphanum) { + return fmt.Errorf("Invalid character(s) found in build meta data %q", build) + } + } + + return nil +} + +// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error +func New(s string) (*Version, error) { + v, err := Parse(s) + vp := &v + return vp, err +} + +// Make is an alias for Parse, parses version string and returns a validated Version or error +func Make(s string) (Version, error) { + return Parse(s) +} + +// ParseTolerant allows for certain version specifications that do not strictly adhere to semver +// specs to be parsed by this library. It does so by normalizing versions before passing them to +// Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions +// with only major and minor components specified, and removes leading 0s. +func ParseTolerant(s string) (Version, error) { + s = strings.TrimSpace(s) + s = strings.TrimPrefix(s, "v") + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + // Remove leading zeros. + for i, p := range parts { + if len(p) > 1 { + p = strings.TrimLeft(p, "0") + if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") { + p = "0" + p + } + parts[i] = p + } + } + // Fill up shortened versions. + if len(parts) < 3 { + if strings.ContainsAny(parts[len(parts)-1], "+-") { + return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") + } + for len(parts) < 3 { + parts = append(parts, "0") + } + } + s = strings.Join(parts, ".") + + return Parse(s) +} + +// Parse parses version string and returns a validated Version or error +func Parse(s string) (Version, error) { + if len(s) == 0 { + return Version{}, errors.New("Version string empty") + } + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + if len(parts) != 3 { + return Version{}, errors.New("No Major.Minor.Patch elements found") + } + + // Major + if !containsOnly(parts[0], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) + } + if hasLeadingZeroes(parts[0]) { + return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) + } + major, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return Version{}, err + } + + // Minor + if !containsOnly(parts[1], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) + } + if hasLeadingZeroes(parts[1]) { + return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) + } + minor, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return Version{}, err + } + + v := Version{} + v.Major = major + v.Minor = minor + + var build, prerelease []string + patchStr := parts[2] + + if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { + build = strings.Split(patchStr[buildIndex+1:], ".") + patchStr = patchStr[:buildIndex] + } + + if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { + prerelease = strings.Split(patchStr[preIndex+1:], ".") + patchStr = patchStr[:preIndex] + } + + if !containsOnly(patchStr, numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) + } + if hasLeadingZeroes(patchStr) { + return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) + } + patch, err := strconv.ParseUint(patchStr, 10, 64) + if err != nil { + return Version{}, err + } + + v.Patch = patch + + // Prerelease + for _, prstr := range prerelease { + parsedPR, err := NewPRVersion(prstr) + if err != nil { + return Version{}, err + } + v.Pre = append(v.Pre, parsedPR) + } + + // Build meta data + for _, str := range build { + if len(str) == 0 { + return Version{}, errors.New("Build meta data is empty") + } + if !containsOnly(str, alphanum) { + return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) + } + v.Build = append(v.Build, str) + } + + return v, nil +} + +// MustParse is like Parse but panics if the version cannot be parsed. +func MustParse(s string) Version { + v, err := Parse(s) + if err != nil { + panic(`semver: Parse(` + s + `): ` + err.Error()) + } + return v +} + +// PRVersion represents a PreRelease Version +type PRVersion struct { + VersionStr string + VersionNum uint64 + IsNum bool +} + +// NewPRVersion creates a new valid prerelease version +func NewPRVersion(s string) (PRVersion, error) { + if len(s) == 0 { + return PRVersion{}, errors.New("Prerelease is empty") + } + v := PRVersion{} + if containsOnly(s, numbers) { + if hasLeadingZeroes(s) { + return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) + } + num, err := strconv.ParseUint(s, 10, 64) + + // Might never be hit, but just in case + if err != nil { + return PRVersion{}, err + } + v.VersionNum = num + v.IsNum = true + } else if containsOnly(s, alphanum) { + v.VersionStr = s + v.IsNum = false + } else { + return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) + } + return v, nil +} + +// IsNumeric checks if prerelease-version is numeric +func (v PRVersion) IsNumeric() bool { + return v.IsNum +} + +// Compare compares two PreRelease Versions v and o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v PRVersion) Compare(o PRVersion) int { + if v.IsNum && !o.IsNum { + return -1 + } else if !v.IsNum && o.IsNum { + return 1 + } else if v.IsNum && o.IsNum { + if v.VersionNum == o.VersionNum { + return 0 + } else if v.VersionNum > o.VersionNum { + return 1 + } else { + return -1 + } + } else { // both are Alphas + if v.VersionStr == o.VersionStr { + return 0 + } else if v.VersionStr > o.VersionStr { + return 1 + } else { + return -1 + } + } +} + +// PreRelease version to string +func (v PRVersion) String() string { + if v.IsNum { + return strconv.FormatUint(v.VersionNum, 10) + } + return v.VersionStr +} + +func containsOnly(s string, set string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !strings.ContainsRune(set, r) + }) == -1 +} + +func hasLeadingZeroes(s string) bool { + return len(s) > 1 && s[0] == '0' +} + +// NewBuildVersion creates a new valid build version +func NewBuildVersion(s string) (string, error) { + if len(s) == 0 { + return "", errors.New("Buildversion is empty") + } + if !containsOnly(s, alphanum) { + return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) + } + return s, nil +} + +// FinalizeVersion returns the major, minor and patch number only and discards +// prerelease and build number. +func FinalizeVersion(s string) (string, error) { + v, err := Parse(s) + if err != nil { + return "", err + } + v.Pre = nil + v.Build = nil + + finalVer := v.String() + return finalVer, nil +} diff --git a/vendor/github.com/blang/semver/v4/sort.go b/vendor/github.com/blang/semver/v4/sort.go new file mode 100644 index 00000000..e18f8808 --- /dev/null +++ b/vendor/github.com/blang/semver/v4/sort.go @@ -0,0 +1,28 @@ +package semver + +import ( + "sort" +) + +// Versions represents multiple versions. +type Versions []Version + +// Len returns length of version collection +func (s Versions) Len() int { + return len(s) +} + +// Swap swaps two versions inside the collection by its indices +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Less checks if version at index i is less than version at index j +func (s Versions) Less(i, j int) bool { + return s[i].LT(s[j]) +} + +// Sort sorts a slice of versions +func Sort(versions []Version) { + sort.Sort(Versions(versions)) +} diff --git a/vendor/github.com/blang/semver/v4/sql.go b/vendor/github.com/blang/semver/v4/sql.go new file mode 100644 index 00000000..db958134 --- /dev/null +++ b/vendor/github.com/blang/semver/v4/sql.go @@ -0,0 +1,30 @@ +package semver + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements the database/sql.Scanner interface. +func (v *Version) Scan(src interface{}) (err error) { + var str string + switch src := src.(type) { + case string: + str = src + case []byte: + str = string(src) + default: + return fmt.Errorf("version.Scan: cannot convert %T to string", src) + } + + if t, err := Parse(str); err == nil { + *v = t + } + + return +} + +// Value implements the database/sql/driver.Valuer interface. +func (v Version) Value() (driver.Value, error) { + return v.String(), nil +} diff --git a/vendor/github.com/checkpoint-restore/checkpointctl/LICENSE b/vendor/github.com/checkpoint-restore/checkpointctl/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/github.com/checkpoint-restore/checkpointctl/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/checkpoint-restore/checkpointctl/lib/annotations.go b/vendor/github.com/checkpoint-restore/checkpointctl/lib/annotations.go new file mode 100644 index 00000000..8d0ad980 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/checkpointctl/lib/annotations.go @@ -0,0 +1,65 @@ +package metadata + +const ( + // CheckpointAnnotationEngine specifies the name of the container engine (e.g., podman, cri-o). + CheckpointAnnotationEngine = "org.criu.checkpoint.engine.name" + + // CheckpointAnnotationEngineVersion specifies the version of the container engine. + CheckpointAnnotationEngineVersion = "org.criu.checkpoint.engine.version" + + // CheckpointAnnotationName specifies the name of the container associated with the checkpoint. + CheckpointAnnotationName = "org.criu.checkpoint.container.name" + + // CheckpointAnnotationPod specifies the name of the pod associated with the checkpoint. + CheckpointAnnotationPod = "org.criu.checkpoint.pod.name" + + // CheckpointAnnotationPodID specifies the ID of the pod associated with the checkpoint. + CheckpointAnnotationPodID = "org.criu.checkpoint.pod.id" + + // CheckpointAnnotationNamespace specifies the namespace of the pod associated with the checkpoint. + CheckpointAnnotationNamespace = "org.criu.checkpoint.pod.namespace" + + // CheckpointAnnotationRootfsImageName specifies the name of the root filesystem image associated with the checkpoint. + CheckpointAnnotationRootfsImageName = "org.criu.checkpoint.rootfsImageName" + + // CheckpointAnnotationRootfsImageUserRequested specifies the name of the root filesystem image requested by the user. + CheckpointAnnotationRootfsImageUserRequested = "org.criu.checkpoint.rootfsImageUserRequested" + + // CheckpointAnnotationRootfsImageSha specifies the SHA hash of the root filesystem image associated with the checkpoint. + CheckpointAnnotationRootfsImageSha = "org.criu.checkpoint.rootfsImageSha" + + // CheckpointAnnotationRootfsImageID specifies the ID of the root filesystem image associated with the checkpoint. + CheckpointAnnotationRootfsImageID = "org.criu.checkpoint.rootfsImageID" + + // CheckpointAnnotationRawImageName specifies the original unprocessed name of the image used to create the container. + CheckpointAnnotationRawImageName = "org.criu.checkpoint.rawImageName" + + // CheckpointAnnotationRuntimeName specifies the runtime used on the host where the checkpoint was created. + CheckpointAnnotationRuntimeName = "org.criu.checkpoint.runtime.name" + + // CheckpointAnnotationRuntimeVersion specifies the version of the runtime used on the host where the checkpoint was created. + CheckpointAnnotationRuntimeVersion = "org.criu.checkpoint.runtime.version" + + // CheckpointAnnotationCriuVersion specifies the version of CRIU used on the host where the checkpoint was created. + CheckpointAnnotationCriuVersion = "org.criu.checkpoint.criu.version" + + // CheckpointAnnotationConmonVersion specifies the version of conmon used on the host where the checkpoint was created. + CheckpointAnnotationConmonVersion = "org.criu.checkpoint.conmon.version" + + // CheckpointAnnotationHostArch specifies the CPU architecture of the host where the checkpoint was created. + CheckpointAnnotationHostArch = "org.criu.checkpoint.host.arch" + + // CheckpointAnnotationHostKernel specifies the kernel version used by the host where the checkpoint was created. + CheckpointAnnotationHostKernel = "org.criu.checkpoint.host.kernel" + + // CheckpointAnnotationCgroupVersion specifies the cgroup version used by the host where the checkpoint was created. + CheckpointAnnotationCgroupVersion = "org.criu.checkpoint.cgroup.version" + + // CheckpointAnnotationDistributionVersion specifies the name of the host distribution on which the checkpoint was created. + // This annotation is particularly useful because some distributions may include non-upstream patches + // that can cause CRIU (Checkpoint/Restore in Userspace) to fail. + CheckpointAnnotationDistributionName = "org.criu.checkpoint.distribution.name" + + // CheckpointAnnotationDistributionVersion specifies the version of the host distribution on which the checkpoint was created. + CheckpointAnnotationDistributionVersion = "org.criu.checkpoint.distribution.version" +) diff --git a/vendor/github.com/checkpoint-restore/checkpointctl/lib/metadata.go b/vendor/github.com/checkpoint-restore/checkpointctl/lib/metadata.go new file mode 100644 index 00000000..1d68f887 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/checkpointctl/lib/metadata.go @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 + +package metadata + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +const ( + // container archive + ConfigDumpFile = "config.dump" + SpecDumpFile = "spec.dump" + StatusDumpFile = "status.dump" + NetworkStatusFile = "network.status" + CheckpointDirectory = "checkpoint" + CheckpointVolumesDirectory = "volumes" + DevShmCheckpointTar = "devshm-checkpoint.tar" + RootFsDiffTar = "rootfs-diff.tar" + DeletedFilesFile = "deleted.files" + DumpLogFile = "dump.log" + RestoreLogFile = "restore.log" + // pod archive + PodOptionsFile = "pod.options" + PodDumpFile = "pod.dump" + // containerd only + StatusFile = "status" + // CRIU Images + PagesPrefix = "pages-" + AmdgpuPagesPrefix = "amdgpu-pages-" +) + +// This is a reduced copy of what Podman uses to store checkpoint metadata +type ContainerConfig struct { + ID string `json:"id"` + Name string `json:"name"` + RootfsImage string `json:"rootfsImage,omitempty"` + RootfsImageRef string `json:"rootfsImageRef,omitempty"` + RootfsImageName string `json:"rootfsImageName,omitempty"` + OCIRuntime string `json:"runtime,omitempty"` + CreatedTime time.Time `json:"createdTime"` + CheckpointedAt time.Time `json:"checkpointedTime"` + RestoredAt time.Time `json:"restoredTime"` + Restored bool `json:"restored"` +} + +type Spec struct { + Annotations map[string]string `json:"annotations,omitempty"` +} + +type ContainerdStatus struct { + CreatedAt int64 + StartedAt int64 + FinishedAt int64 + ExitCode int32 + Pid uint32 + Reason string + Message string +} + +// This structure is used by the KubernetesContainerCheckpointMetadata structure +type KubernetesCheckpoint struct { + Archive string `json:"archive,omitempty"` + Size int64 `json:"size,omitempty"` + Timestamp int64 `json:"timestamp,omitempty"` +} + +// This structure is the basis for Kubernetes to track how many checkpoints +// for a certain container have been created. +type KubernetesContainerCheckpointMetadata struct { + PodFullName string `json:"podFullName,omitempty"` + ContainerName string `json:"containerName,omitempty"` + TotalSize int64 `json:"totalSize,omitempty"` + Checkpoints []KubernetesCheckpoint `json:"checkpoints"` +} + +func ReadContainerCheckpointSpecDump(checkpointDirectory string) (*spec.Spec, string, error) { + var specDump spec.Spec + specDumpFile, err := ReadJSONFile(&specDump, checkpointDirectory, SpecDumpFile) + + return &specDump, specDumpFile, err +} + +func ReadContainerCheckpointConfigDump(checkpointDirectory string) (*ContainerConfig, string, error) { + var containerConfig ContainerConfig + configDumpFile, err := ReadJSONFile(&containerConfig, checkpointDirectory, ConfigDumpFile) + + return &containerConfig, configDumpFile, err +} + +func ReadContainerCheckpointDeletedFiles(checkpointDirectory string) ([]string, string, error) { + var deletedFiles []string + deletedFilesFile, err := ReadJSONFile(&deletedFiles, checkpointDirectory, DeletedFilesFile) + + return deletedFiles, deletedFilesFile, err +} + +func ReadContainerCheckpointStatusFile(checkpointDirectory string) (*ContainerdStatus, string, error) { + var containerdStatus ContainerdStatus + statusFile, err := ReadJSONFile(&containerdStatus, checkpointDirectory, StatusFile) + + return &containerdStatus, statusFile, err +} + +// WriteJSONFile marshalls and writes the given data to a JSON file +func WriteJSONFile(v interface{}, dir, file string) (string, error) { + fileJSON, err := json.MarshalIndent(v, "", " ") + if err != nil { + return "", fmt.Errorf("error marshalling JSON: %w", err) + } + file = filepath.Join(dir, file) + if err := os.WriteFile(file, fileJSON, 0o600); err != nil { + return "", err + } + + return file, nil +} + +func ReadJSONFile(v interface{}, dir, file string) (string, error) { + file = filepath.Join(dir, file) + content, err := os.ReadFile(file) + if err != nil { + return "", err + } + if err = json.Unmarshal(content, v); err != nil { + return "", fmt.Errorf("failed to unmarshal %s: %w", file, err) + } + + return file, nil +} + +func ByteToString(b int64) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + + return fmt.Sprintf("%.1f %ciB", + float64(b)/float64(div), "KMGTPE"[exp]) +} diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/.gitignore b/vendor/github.com/checkpoint-restore/go-criu/v7/.gitignore new file mode 100644 index 00000000..eb1b08bc --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/.gitignore @@ -0,0 +1,18 @@ +test/test +test/test.coverage +test/piggie/piggie +test/phaul/phaul +test/phaul/phaul.coverage +test/loop/loop +test/mmapper/mmapper +test/crit/crit-test +test/crit/test-imgs +test/crit/crit-test.coverage +test/.coverage/ +image +scripts/magic-gen/*.h +scripts/magic-gen/expected.go +scripts/magic-gen/output.go +crit/bin +crit/test-imgs/ +__pycache__ diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/.golangci.yml b/vendor/github.com/checkpoint-restore/go-criu/v7/.golangci.yml new file mode 100644 index 00000000..a0d20be2 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/.golangci.yml @@ -0,0 +1,22 @@ +linters: + presets: + - bugs + - performance + - unused + - format + disable: + - musttag + enable: + - whitespace + - misspell + - dupl + - gosimple + - stylecheck + +linters-settings: + exhaustive: + default-signifies-exhaustive: true + gosec: + excludes: + # https://github.com/securego/gosec/issues/1185 + - G115 diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/LICENSE b/vendor/github.com/checkpoint-restore/go-criu/v7/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/MAINTAINERS b/vendor/github.com/checkpoint-restore/go-criu/v7/MAINTAINERS new file mode 100644 index 00000000..4611c33b --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/MAINTAINERS @@ -0,0 +1,4 @@ +Adrian Reber +Kir Kolyshkin +Prajwal S N +Radostin Stoyanov diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/Makefile b/vendor/github.com/checkpoint-restore/go-criu/v7/Makefile new file mode 100644 index 00000000..12e06423 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/Makefile @@ -0,0 +1,42 @@ +SHELL = /bin/bash +GO ?= go +CC ?= gcc + +all: build + +lint: + golangci-lint run ./... + +build: rpc/rpc.pb.go stats/stats.pb.go + $(GO) build -v ./... + # Build crit binary + $(MAKE) -C crit bin/crit + +test: build + $(MAKE) -C test + +coverage: + $(MAKE) -C test coverage + +rpc/rpc.proto: + curl -sSL https://raw.githubusercontent.com/checkpoint-restore/criu/master/images/rpc.proto -o $@ + +rpc/rpc.pb.go: rpc/rpc.proto + protoc --go_out=. --go_opt=M$^=rpc/ $^ + +stats/stats.proto: + curl -sSL https://raw.githubusercontent.com/checkpoint-restore/criu/master/images/stats.proto -o $@ + +stats/stats.pb.go: stats/stats.proto + protoc --go_out=. --go_opt=M$^=stats/ $^ + +vendor: + $(GO) mod tidy + $(GO) mod vendor + $(GO) mod verify + +clean: + $(MAKE) -C crit/ clean + $(MAKE) -C test/ clean + +.PHONY: build test lint vendor coverage clean diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/README.md b/vendor/github.com/checkpoint-restore/go-criu/v7/README.md new file mode 100644 index 00000000..14a08eb7 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/README.md @@ -0,0 +1,112 @@ + +# go-criu -- Go bindings for CRIU + +[![test](https://github.com/checkpoint-restore/go-criu/workflows/ci/badge.svg?branch=master)](https://github.com/checkpoint-restore/go-criu/actions?query=workflow%3Aci) +[![verify](https://github.com/checkpoint-restore/go-criu/workflows/verify/badge.svg?branch=master)](https://github.com/checkpoint-restore/go-criu/actions?query=workflow%3Averify) +[![Go Reference](https://pkg.go.dev/badge/github.com/checkpoint-restore/go-criu.svg)](https://pkg.go.dev/github.com/checkpoint-restore/go-criu) + +This repository provides Go bindings for [CRIU](https://criu.org/). +The code is based on the Go-based PHaul implementation from the CRIU repository. +For easier inclusion into other Go projects, the CRIU Go bindings have been +moved to this repository. + +## CRIU + +The Go bindings provide an easy way to use the CRIU RPC calls from Go without +the need to set up all the infrastructure to make the actual RPC connection to CRIU. + +The following example would print the version of CRIU: + +```go +import ( + "log" + + "github.com/checkpoint-restore/go-criu/v7" +) + +func main() { + c := criu.MakeCriu() + version, err := c.GetCriuVersion() + if err != nil { + log.Fatalln(err) + } + log.Println(version) +} +``` + +or to just check if at least a certain CRIU version is installed: + +```go + c := criu.MakeCriu() + result, err := c.IsCriuAtLeast(31100) +``` + +## CRIT + +The `crit` package provides bindings to decode, encode, and manipulate +CRIU image files natively within Go. It also provides a CLI tool similar +to the original CRIT Python tool. To get started with this, see the docs +at [CRIT (Go library)](https://criu.org/CRIT_%28Go_library%29). + +## Releases + +The first go-criu release was 3.11 based on CRIU 3.11. The initial plan +was to follow CRIU so that go-criu would carry the same version number as +CRIU. + +As go-criu is imported in other projects and as Go modules are expected +to follow Semantic Versioning go-criu will also follow Semantic Versioning +starting with the 4.0.0 release. + +The following table shows the relation between go-criu and criu versions: + +| Major version | Latest release | CRIU version | +| -------------- | -------------- | ------------ | +| v7             | 7.2.0         | 3.19         | +| v7             | 7.0.0         | 3.18         | +| v6             | 6.3.0         | 3.17         | +| v5             | 5.3.0         | 3.16         | +| v5             | 5.0.0         | 3.15         | +| v4             | 4.1.0         | 3.14         | + +## How to contribute + +While bug fixes can first be identified via an "issue", that is not required. +It's ok to just open up a PR with the fix, but make sure you include the same +information you would have included in an issue - like how to reproduce it. + +PRs for new features should include some background on what use cases the +new code is trying to address. When possible and when it makes sense, try to +break-up larger PRs into smaller ones - it's easier to review smaller +code changes. But only if those smaller ones make sense as stand-alone PRs. + +Regardless of the type of PR, all PRs should include: + +* well documented code changes +* additional testcases. Ideally, they should fail w/o your code change applied +* documentation changes + +Squash your commits into logical pieces of work that might want to be reviewed +separate from the rest of the PRs. Ideally, each commit should implement a +single idea, and the PR branch should pass the tests at every commit. GitHub +makes it easy to review the cumulative effect of many commits; so, when in +doubt, use smaller commits. + +PRs that fix issues should include a reference like `Closes #XXXX` in the +commit message so that github will automatically close the referenced issue +when the PR is merged. + +Contributors must assert that they are in compliance with the [Developer +Certificate of Origin 1.1](http://developercertificate.org/). This is achieved +by adding a "Signed-off-by" line containing the contributor's name and e-mail +to every commit message. Your signature certifies that you wrote the patch or +otherwise have the right to pass it on as an open-source patch. + +## License and copyright + +Unless mentioned otherwise in a specific file's header, all code in +this project is released under the Apache 2.0 license. + +The author of a change remains the copyright holder of their code +(no copyright assignment). The list of authors and contributors can be +retrieved from the git commit history and in some cases, the file headers. diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/codecov.yml b/vendor/github.com/checkpoint-restore/go-criu/v7/codecov.yml new file mode 100644 index 00000000..b52d0c49 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "test" diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/features.go b/vendor/github.com/checkpoint-restore/go-criu/v7/features.go new file mode 100644 index 00000000..c62e69e0 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/features.go @@ -0,0 +1,45 @@ +package criu + +import ( + "errors" + + "github.com/checkpoint-restore/go-criu/v7/rpc" +) + +// Feature checking in go-criu is based on the libcriu feature checking function. + +// Feature checking allows the user to check if CRIU supports +// certain features. There are CRIU features which do not depend +// on the version of CRIU but on kernel features or architecture. +// +// One example is memory tracking. Memory tracking can be disabled +// in the kernel or there are architectures which do not support +// it (aarch64 for example). By using the feature check a libcriu +// user can easily query CRIU if a certain feature is available. +// +// The features which should be checked can be marked in the +// structure 'struct criu_feature_check'. Each structure member +// that is set to true will result in CRIU checking for the +// availability of that feature in the current combination of +// CRIU/kernel/architecture. +// +// Available features will be set to true when the function +// returns successfully. Missing features will be set to false. + +func (c *Criu) FeatureCheck(features *rpc.CriuFeatures) (*rpc.CriuFeatures, error) { + resp, err := c.doSwrkWithResp( + rpc.CriuReqType_FEATURE_CHECK, + nil, + nil, + features, + ) + if err != nil { + return nil, err + } + + if resp.GetType() != rpc.CriuReqType_FEATURE_CHECK { + return nil, errors.New("unexpected CRIU RPC response") + } + + return features, nil +} diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/main.go b/vendor/github.com/checkpoint-restore/go-criu/v7/main.go new file mode 100644 index 00000000..8f29d2ee --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/main.go @@ -0,0 +1,274 @@ +package criu + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strconv" + "syscall" + + "github.com/checkpoint-restore/go-criu/v7/rpc" + "google.golang.org/protobuf/proto" +) + +// Criu struct +type Criu struct { + swrkCmd *exec.Cmd + swrkSk *os.File + swrkPath string +} + +// MakeCriu returns the Criu object required for most operations +func MakeCriu() *Criu { + return &Criu{ + swrkPath: "criu", + } +} + +// SetCriuPath allows setting the path to the CRIU binary +// if it is in a non standard location +func (c *Criu) SetCriuPath(path string) { + c.swrkPath = path +} + +// Prepare sets up everything for the RPC communication to CRIU +func (c *Criu) Prepare() error { + fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_SEQPACKET, 0) + if err != nil { + return err + } + + cln := os.NewFile(uintptr(fds[0]), "criu-xprt-cln") + syscall.CloseOnExec(fds[0]) + srv := os.NewFile(uintptr(fds[1]), "criu-xprt-srv") + defer srv.Close() + + args := []string{"swrk", strconv.Itoa(fds[1])} + // #nosec G204 + cmd := exec.Command(c.swrkPath, args...) + + err = cmd.Start() + if err != nil { + cln.Close() + return err + } + + c.swrkCmd = cmd + c.swrkSk = cln + + return nil +} + +// Cleanup cleans up +func (c *Criu) Cleanup() error { + var errs []error + if c.swrkCmd != nil { + if err := c.swrkSk.Close(); err != nil { + errs = append(errs, err) + } + c.swrkSk = nil + if err := c.swrkCmd.Wait(); err != nil { + errs = append(errs, fmt.Errorf("criu swrk failed: %w", err)) + } + c.swrkCmd = nil + } + return errors.Join(errs...) +} + +func (c *Criu) sendAndRecv(reqB []byte) ([]byte, int, error) { + cln := c.swrkSk + _, err := cln.Write(reqB) + if err != nil { + return nil, 0, err + } + + respB := make([]byte, 2*4096) + n, err := cln.Read(respB) + if err != nil { + return nil, 0, err + } + + return respB, n, nil +} + +func (c *Criu) doSwrk(reqType rpc.CriuReqType, opts *rpc.CriuOpts, nfy Notify) error { + resp, err := c.doSwrkWithResp(reqType, opts, nfy, nil) + if err != nil { + return err + } + respType := resp.GetType() + if respType != reqType { + return errors.New("unexpected CRIU RPC response") + } + + return nil +} + +func (c *Criu) doSwrkWithResp(reqType rpc.CriuReqType, opts *rpc.CriuOpts, nfy Notify, features *rpc.CriuFeatures) (resp *rpc.CriuResp, retErr error) { + req := rpc.CriuReq{ + Type: &reqType, + Opts: opts, + } + + if nfy != nil { + opts.NotifyScripts = proto.Bool(true) + } + + if features != nil { + req.Features = features + } + + if c.swrkCmd == nil { + err := c.Prepare() + if err != nil { + return nil, err + } + + defer func() { + // append any cleanup errors to the returned error + err := c.Cleanup() + if err != nil { + retErr = errors.Join(retErr, err) + } + }() + } + + for { + reqB, err := proto.Marshal(&req) + if err != nil { + return nil, err + } + + respB, respS, err := c.sendAndRecv(reqB) + if err != nil { + return nil, err + } + + resp = &rpc.CriuResp{} + err = proto.Unmarshal(respB[:respS], resp) + if err != nil { + return nil, err + } + + if !resp.GetSuccess() { + return resp, fmt.Errorf("operation failed (msg:%s err:%d)", + resp.GetCrErrmsg(), resp.GetCrErrno()) + } + + respType := resp.GetType() + if respType != rpc.CriuReqType_NOTIFY { + break + } + if nfy == nil { + return resp, errors.New("unexpected notify") + } + + notify := resp.GetNotify() + switch notify.GetScript() { + case "pre-dump": + err = nfy.PreDump() + case "post-dump": + err = nfy.PostDump() + case "pre-restore": + err = nfy.PreRestore() + case "post-restore": + err = nfy.PostRestore(notify.GetPid()) + case "network-lock": + err = nfy.NetworkLock() + case "network-unlock": + err = nfy.NetworkUnlock() + case "setup-namespaces": + err = nfy.SetupNamespaces(notify.GetPid()) + case "post-setup-namespaces": + err = nfy.PostSetupNamespaces() + case "post-resume": + err = nfy.PostResume() + default: + err = nil + } + + if err != nil { + return resp, err + } + + req = rpc.CriuReq{ + Type: &respType, + NotifySuccess: proto.Bool(true), + } + } + + return resp, nil +} + +// Dump dumps a process +func (c *Criu) Dump(opts *rpc.CriuOpts, nfy Notify) error { + return c.doSwrk(rpc.CriuReqType_DUMP, opts, nfy) +} + +// Restore restores a process +func (c *Criu) Restore(opts *rpc.CriuOpts, nfy Notify) error { + return c.doSwrk(rpc.CriuReqType_RESTORE, opts, nfy) +} + +// PreDump does a pre-dump +func (c *Criu) PreDump(opts *rpc.CriuOpts, nfy Notify) error { + return c.doSwrk(rpc.CriuReqType_PRE_DUMP, opts, nfy) +} + +// StartPageServer starts the page server +func (c *Criu) StartPageServer(opts *rpc.CriuOpts) error { + return c.doSwrk(rpc.CriuReqType_PAGE_SERVER, opts, nil) +} + +// StartPageServerChld starts the page server and returns PID and port +func (c *Criu) StartPageServerChld(opts *rpc.CriuOpts) (int, int, error) { + resp, err := c.doSwrkWithResp(rpc.CriuReqType_PAGE_SERVER_CHLD, opts, nil, nil) + if err != nil { + return 0, 0, err + } + + return int(resp.GetPs().GetPid()), int(resp.GetPs().GetPort()), nil +} + +// GetCriuVersion executes the VERSION RPC call and returns the version +// as an integer. Major * 10000 + Minor * 100 + SubLevel +func (c *Criu) GetCriuVersion() (int, error) { + resp, err := c.doSwrkWithResp(rpc.CriuReqType_VERSION, nil, nil, nil) + if err != nil { + return 0, err + } + + if resp.GetType() != rpc.CriuReqType_VERSION { + return 0, errors.New("unexpected CRIU RPC response") + } + + version := resp.GetVersion().GetMajorNumber() * 10000 + version += resp.GetVersion().GetMinorNumber() * 100 + if resp.GetVersion().GetSublevel() != 0 { + version += resp.GetVersion().GetSublevel() + } + + if resp.GetVersion().GetGitid() != "" { + // taken from runc: if it is a git release -> increase minor by 1 + version -= (version % 100) + version += 100 + } + + return int(version), nil +} + +// IsCriuAtLeast checks if the version is at least the same +// as the parameter version +func (c *Criu) IsCriuAtLeast(version int) (bool, error) { + criuVersion, err := c.GetCriuVersion() + if err != nil { + return false, err + } + + if criuVersion >= version { + return true, nil + } + + return false, nil +} diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/notify.go b/vendor/github.com/checkpoint-restore/go-criu/v7/notify.go new file mode 100644 index 00000000..a177f2bb --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/notify.go @@ -0,0 +1,62 @@ +package criu + +// Notify interface +type Notify interface { + PreDump() error + PostDump() error + PreRestore() error + PostRestore(pid int32) error + NetworkLock() error + NetworkUnlock() error + SetupNamespaces(pid int32) error + PostSetupNamespaces() error + PostResume() error +} + +// NoNotify struct +type NoNotify struct{} + +// PreDump NoNotify +func (c NoNotify) PreDump() error { + return nil +} + +// PostDump NoNotify +func (c NoNotify) PostDump() error { + return nil +} + +// PreRestore NoNotify +func (c NoNotify) PreRestore() error { + return nil +} + +// PostRestore NoNotify +func (c NoNotify) PostRestore(pid int32) error { + return nil +} + +// NetworkLock NoNotify +func (c NoNotify) NetworkLock() error { + return nil +} + +// NetworkUnlock NoNotify +func (c NoNotify) NetworkUnlock() error { + return nil +} + +// SetupNamespaces NoNotify +func (c NoNotify) SetupNamespaces(pid int32) error { + return nil +} + +// PostSetupNamespaces NoNotify +func (c NoNotify) PostSetupNamespaces() error { + return nil +} + +// PostResume NoNotify +func (c NoNotify) PostResume() error { + return nil +} diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/rpc/rpc.pb.go b/vendor/github.com/checkpoint-restore/go-criu/v7/rpc/rpc.pb.go new file mode 100644 index 00000000..730496b0 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/rpc/rpc.pb.go @@ -0,0 +1,2390 @@ +// SPDX-License-Identifier: MIT + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v4.23.4 +// source: rpc/rpc.proto + +package rpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CriuCgMode int32 + +const ( + CriuCgMode_IGNORE CriuCgMode = 0 + CriuCgMode_CG_NONE CriuCgMode = 1 + CriuCgMode_PROPS CriuCgMode = 2 + CriuCgMode_SOFT CriuCgMode = 3 + CriuCgMode_FULL CriuCgMode = 4 + CriuCgMode_STRICT CriuCgMode = 5 + CriuCgMode_DEFAULT CriuCgMode = 6 +) + +// Enum value maps for CriuCgMode. +var ( + CriuCgMode_name = map[int32]string{ + 0: "IGNORE", + 1: "CG_NONE", + 2: "PROPS", + 3: "SOFT", + 4: "FULL", + 5: "STRICT", + 6: "DEFAULT", + } + CriuCgMode_value = map[string]int32{ + "IGNORE": 0, + "CG_NONE": 1, + "PROPS": 2, + "SOFT": 3, + "FULL": 4, + "STRICT": 5, + "DEFAULT": 6, + } +) + +func (x CriuCgMode) Enum() *CriuCgMode { + p := new(CriuCgMode) + *p = x + return p +} + +func (x CriuCgMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CriuCgMode) Descriptor() protoreflect.EnumDescriptor { + return file_rpc_rpc_proto_enumTypes[0].Descriptor() +} + +func (CriuCgMode) Type() protoreflect.EnumType { + return &file_rpc_rpc_proto_enumTypes[0] +} + +func (x CriuCgMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *CriuCgMode) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = CriuCgMode(num) + return nil +} + +// Deprecated: Use CriuCgMode.Descriptor instead. +func (CriuCgMode) EnumDescriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{0} +} + +type CriuNetworkLockMethod int32 + +const ( + CriuNetworkLockMethod_IPTABLES CriuNetworkLockMethod = 1 + CriuNetworkLockMethod_NFTABLES CriuNetworkLockMethod = 2 + CriuNetworkLockMethod_SKIP CriuNetworkLockMethod = 3 +) + +// Enum value maps for CriuNetworkLockMethod. +var ( + CriuNetworkLockMethod_name = map[int32]string{ + 1: "IPTABLES", + 2: "NFTABLES", + 3: "SKIP", + } + CriuNetworkLockMethod_value = map[string]int32{ + "IPTABLES": 1, + "NFTABLES": 2, + "SKIP": 3, + } +) + +func (x CriuNetworkLockMethod) Enum() *CriuNetworkLockMethod { + p := new(CriuNetworkLockMethod) + *p = x + return p +} + +func (x CriuNetworkLockMethod) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CriuNetworkLockMethod) Descriptor() protoreflect.EnumDescriptor { + return file_rpc_rpc_proto_enumTypes[1].Descriptor() +} + +func (CriuNetworkLockMethod) Type() protoreflect.EnumType { + return &file_rpc_rpc_proto_enumTypes[1] +} + +func (x CriuNetworkLockMethod) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *CriuNetworkLockMethod) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = CriuNetworkLockMethod(num) + return nil +} + +// Deprecated: Use CriuNetworkLockMethod.Descriptor instead. +func (CriuNetworkLockMethod) EnumDescriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{1} +} + +type CriuPreDumpMode int32 + +const ( + CriuPreDumpMode_SPLICE CriuPreDumpMode = 1 + CriuPreDumpMode_VM_READ CriuPreDumpMode = 2 +) + +// Enum value maps for CriuPreDumpMode. +var ( + CriuPreDumpMode_name = map[int32]string{ + 1: "SPLICE", + 2: "VM_READ", + } + CriuPreDumpMode_value = map[string]int32{ + "SPLICE": 1, + "VM_READ": 2, + } +) + +func (x CriuPreDumpMode) Enum() *CriuPreDumpMode { + p := new(CriuPreDumpMode) + *p = x + return p +} + +func (x CriuPreDumpMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CriuPreDumpMode) Descriptor() protoreflect.EnumDescriptor { + return file_rpc_rpc_proto_enumTypes[2].Descriptor() +} + +func (CriuPreDumpMode) Type() protoreflect.EnumType { + return &file_rpc_rpc_proto_enumTypes[2] +} + +func (x CriuPreDumpMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *CriuPreDumpMode) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = CriuPreDumpMode(num) + return nil +} + +// Deprecated: Use CriuPreDumpMode.Descriptor instead. +func (CriuPreDumpMode) EnumDescriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{2} +} + +type CriuReqType int32 + +const ( + CriuReqType_EMPTY CriuReqType = 0 + CriuReqType_DUMP CriuReqType = 1 + CriuReqType_RESTORE CriuReqType = 2 + CriuReqType_CHECK CriuReqType = 3 + CriuReqType_PRE_DUMP CriuReqType = 4 + CriuReqType_PAGE_SERVER CriuReqType = 5 + CriuReqType_NOTIFY CriuReqType = 6 + CriuReqType_CPUINFO_DUMP CriuReqType = 7 + CriuReqType_CPUINFO_CHECK CriuReqType = 8 + CriuReqType_FEATURE_CHECK CriuReqType = 9 + CriuReqType_VERSION CriuReqType = 10 + CriuReqType_WAIT_PID CriuReqType = 11 + CriuReqType_PAGE_SERVER_CHLD CriuReqType = 12 + CriuReqType_SINGLE_PRE_DUMP CriuReqType = 13 +) + +// Enum value maps for CriuReqType. +var ( + CriuReqType_name = map[int32]string{ + 0: "EMPTY", + 1: "DUMP", + 2: "RESTORE", + 3: "CHECK", + 4: "PRE_DUMP", + 5: "PAGE_SERVER", + 6: "NOTIFY", + 7: "CPUINFO_DUMP", + 8: "CPUINFO_CHECK", + 9: "FEATURE_CHECK", + 10: "VERSION", + 11: "WAIT_PID", + 12: "PAGE_SERVER_CHLD", + 13: "SINGLE_PRE_DUMP", + } + CriuReqType_value = map[string]int32{ + "EMPTY": 0, + "DUMP": 1, + "RESTORE": 2, + "CHECK": 3, + "PRE_DUMP": 4, + "PAGE_SERVER": 5, + "NOTIFY": 6, + "CPUINFO_DUMP": 7, + "CPUINFO_CHECK": 8, + "FEATURE_CHECK": 9, + "VERSION": 10, + "WAIT_PID": 11, + "PAGE_SERVER_CHLD": 12, + "SINGLE_PRE_DUMP": 13, + } +) + +func (x CriuReqType) Enum() *CriuReqType { + p := new(CriuReqType) + *p = x + return p +} + +func (x CriuReqType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CriuReqType) Descriptor() protoreflect.EnumDescriptor { + return file_rpc_rpc_proto_enumTypes[3].Descriptor() +} + +func (CriuReqType) Type() protoreflect.EnumType { + return &file_rpc_rpc_proto_enumTypes[3] +} + +func (x CriuReqType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *CriuReqType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = CriuReqType(num) + return nil +} + +// Deprecated: Use CriuReqType.Descriptor instead. +func (CriuReqType) EnumDescriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{3} +} + +type CriuPageServerInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` + Port *int32 `protobuf:"varint,2,opt,name=port" json:"port,omitempty"` + Pid *int32 `protobuf:"varint,3,opt,name=pid" json:"pid,omitempty"` + Fd *int32 `protobuf:"varint,4,opt,name=fd" json:"fd,omitempty"` +} + +func (x *CriuPageServerInfo) Reset() { + *x = CriuPageServerInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuPageServerInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuPageServerInfo) ProtoMessage() {} + +func (x *CriuPageServerInfo) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuPageServerInfo.ProtoReflect.Descriptor instead. +func (*CriuPageServerInfo) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{0} +} + +func (x *CriuPageServerInfo) GetAddress() string { + if x != nil && x.Address != nil { + return *x.Address + } + return "" +} + +func (x *CriuPageServerInfo) GetPort() int32 { + if x != nil && x.Port != nil { + return *x.Port + } + return 0 +} + +func (x *CriuPageServerInfo) GetPid() int32 { + if x != nil && x.Pid != nil { + return *x.Pid + } + return 0 +} + +func (x *CriuPageServerInfo) GetFd() int32 { + if x != nil && x.Fd != nil { + return *x.Fd + } + return 0 +} + +type CriuVethPair struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IfIn *string `protobuf:"bytes,1,req,name=if_in,json=ifIn" json:"if_in,omitempty"` + IfOut *string `protobuf:"bytes,2,req,name=if_out,json=ifOut" json:"if_out,omitempty"` +} + +func (x *CriuVethPair) Reset() { + *x = CriuVethPair{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuVethPair) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuVethPair) ProtoMessage() {} + +func (x *CriuVethPair) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuVethPair.ProtoReflect.Descriptor instead. +func (*CriuVethPair) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{1} +} + +func (x *CriuVethPair) GetIfIn() string { + if x != nil && x.IfIn != nil { + return *x.IfIn + } + return "" +} + +func (x *CriuVethPair) GetIfOut() string { + if x != nil && x.IfOut != nil { + return *x.IfOut + } + return "" +} + +type ExtMountMap struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key *string `protobuf:"bytes,1,req,name=key" json:"key,omitempty"` + Val *string `protobuf:"bytes,2,req,name=val" json:"val,omitempty"` +} + +func (x *ExtMountMap) Reset() { + *x = ExtMountMap{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExtMountMap) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExtMountMap) ProtoMessage() {} + +func (x *ExtMountMap) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExtMountMap.ProtoReflect.Descriptor instead. +func (*ExtMountMap) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{2} +} + +func (x *ExtMountMap) GetKey() string { + if x != nil && x.Key != nil { + return *x.Key + } + return "" +} + +func (x *ExtMountMap) GetVal() string { + if x != nil && x.Val != nil { + return *x.Val + } + return "" +} + +type JoinNamespace struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ns *string `protobuf:"bytes,1,req,name=ns" json:"ns,omitempty"` + NsFile *string `protobuf:"bytes,2,req,name=ns_file,json=nsFile" json:"ns_file,omitempty"` + ExtraOpt *string `protobuf:"bytes,3,opt,name=extra_opt,json=extraOpt" json:"extra_opt,omitempty"` +} + +func (x *JoinNamespace) Reset() { + *x = JoinNamespace{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *JoinNamespace) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JoinNamespace) ProtoMessage() {} + +func (x *JoinNamespace) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use JoinNamespace.ProtoReflect.Descriptor instead. +func (*JoinNamespace) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{3} +} + +func (x *JoinNamespace) GetNs() string { + if x != nil && x.Ns != nil { + return *x.Ns + } + return "" +} + +func (x *JoinNamespace) GetNsFile() string { + if x != nil && x.NsFile != nil { + return *x.NsFile + } + return "" +} + +func (x *JoinNamespace) GetExtraOpt() string { + if x != nil && x.ExtraOpt != nil { + return *x.ExtraOpt + } + return "" +} + +type InheritFd struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key *string `protobuf:"bytes,1,req,name=key" json:"key,omitempty"` + Fd *int32 `protobuf:"varint,2,req,name=fd" json:"fd,omitempty"` +} + +func (x *InheritFd) Reset() { + *x = InheritFd{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InheritFd) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InheritFd) ProtoMessage() {} + +func (x *InheritFd) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InheritFd.ProtoReflect.Descriptor instead. +func (*InheritFd) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{4} +} + +func (x *InheritFd) GetKey() string { + if x != nil && x.Key != nil { + return *x.Key + } + return "" +} + +func (x *InheritFd) GetFd() int32 { + if x != nil && x.Fd != nil { + return *x.Fd + } + return 0 +} + +type CgroupRoot struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ctrl *string `protobuf:"bytes,1,opt,name=ctrl" json:"ctrl,omitempty"` + Path *string `protobuf:"bytes,2,req,name=path" json:"path,omitempty"` +} + +func (x *CgroupRoot) Reset() { + *x = CgroupRoot{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CgroupRoot) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CgroupRoot) ProtoMessage() {} + +func (x *CgroupRoot) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CgroupRoot.ProtoReflect.Descriptor instead. +func (*CgroupRoot) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{5} +} + +func (x *CgroupRoot) GetCtrl() string { + if x != nil && x.Ctrl != nil { + return *x.Ctrl + } + return "" +} + +func (x *CgroupRoot) GetPath() string { + if x != nil && x.Path != nil { + return *x.Path + } + return "" +} + +type UnixSk struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Inode *uint32 `protobuf:"varint,1,req,name=inode" json:"inode,omitempty"` +} + +func (x *UnixSk) Reset() { + *x = UnixSk{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UnixSk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnixSk) ProtoMessage() {} + +func (x *UnixSk) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UnixSk.ProtoReflect.Descriptor instead. +func (*UnixSk) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{6} +} + +func (x *UnixSk) GetInode() uint32 { + if x != nil && x.Inode != nil { + return *x.Inode + } + return 0 +} + +type CriuOpts struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ImagesDirFd *int32 `protobuf:"varint,1,req,name=images_dir_fd,json=imagesDirFd,def=-1" json:"images_dir_fd,omitempty"` + ImagesDir *string `protobuf:"bytes,68,opt,name=images_dir,json=imagesDir" json:"images_dir,omitempty"` // used only if images_dir_fd == -1 + Pid *int32 `protobuf:"varint,2,opt,name=pid" json:"pid,omitempty"` // if not set on dump, will dump requesting process + LeaveRunning *bool `protobuf:"varint,3,opt,name=leave_running,json=leaveRunning" json:"leave_running,omitempty"` + ExtUnixSk *bool `protobuf:"varint,4,opt,name=ext_unix_sk,json=extUnixSk" json:"ext_unix_sk,omitempty"` + TcpEstablished *bool `protobuf:"varint,5,opt,name=tcp_established,json=tcpEstablished" json:"tcp_established,omitempty"` + EvasiveDevices *bool `protobuf:"varint,6,opt,name=evasive_devices,json=evasiveDevices" json:"evasive_devices,omitempty"` + ShellJob *bool `protobuf:"varint,7,opt,name=shell_job,json=shellJob" json:"shell_job,omitempty"` + FileLocks *bool `protobuf:"varint,8,opt,name=file_locks,json=fileLocks" json:"file_locks,omitempty"` + LogLevel *int32 `protobuf:"varint,9,opt,name=log_level,json=logLevel,def=2" json:"log_level,omitempty"` + LogFile *string `protobuf:"bytes,10,opt,name=log_file,json=logFile" json:"log_file,omitempty"` // No subdirs are allowed. Consider using work-dir + Ps *CriuPageServerInfo `protobuf:"bytes,11,opt,name=ps" json:"ps,omitempty"` + NotifyScripts *bool `protobuf:"varint,12,opt,name=notify_scripts,json=notifyScripts" json:"notify_scripts,omitempty"` + Root *string `protobuf:"bytes,13,opt,name=root" json:"root,omitempty"` + ParentImg *string `protobuf:"bytes,14,opt,name=parent_img,json=parentImg" json:"parent_img,omitempty"` + TrackMem *bool `protobuf:"varint,15,opt,name=track_mem,json=trackMem" json:"track_mem,omitempty"` + AutoDedup *bool `protobuf:"varint,16,opt,name=auto_dedup,json=autoDedup" json:"auto_dedup,omitempty"` + WorkDirFd *int32 `protobuf:"varint,17,opt,name=work_dir_fd,json=workDirFd" json:"work_dir_fd,omitempty"` + LinkRemap *bool `protobuf:"varint,18,opt,name=link_remap,json=linkRemap" json:"link_remap,omitempty"` + Veths []*CriuVethPair `protobuf:"bytes,19,rep,name=veths" json:"veths,omitempty"` // DEPRECATED, use external instead + CpuCap *uint32 `protobuf:"varint,20,opt,name=cpu_cap,json=cpuCap,def=4294967295" json:"cpu_cap,omitempty"` + ForceIrmap *bool `protobuf:"varint,21,opt,name=force_irmap,json=forceIrmap" json:"force_irmap,omitempty"` + ExecCmd []string `protobuf:"bytes,22,rep,name=exec_cmd,json=execCmd" json:"exec_cmd,omitempty"` + ExtMnt []*ExtMountMap `protobuf:"bytes,23,rep,name=ext_mnt,json=extMnt" json:"ext_mnt,omitempty"` // DEPRECATED, use external instead + ManageCgroups *bool `protobuf:"varint,24,opt,name=manage_cgroups,json=manageCgroups" json:"manage_cgroups,omitempty"` // backward compatibility + CgRoot []*CgroupRoot `protobuf:"bytes,25,rep,name=cg_root,json=cgRoot" json:"cg_root,omitempty"` + RstSibling *bool `protobuf:"varint,26,opt,name=rst_sibling,json=rstSibling" json:"rst_sibling,omitempty"` // swrk only + InheritFd []*InheritFd `protobuf:"bytes,27,rep,name=inherit_fd,json=inheritFd" json:"inherit_fd,omitempty"` // swrk only + AutoExtMnt *bool `protobuf:"varint,28,opt,name=auto_ext_mnt,json=autoExtMnt" json:"auto_ext_mnt,omitempty"` + ExtSharing *bool `protobuf:"varint,29,opt,name=ext_sharing,json=extSharing" json:"ext_sharing,omitempty"` + ExtMasters *bool `protobuf:"varint,30,opt,name=ext_masters,json=extMasters" json:"ext_masters,omitempty"` + SkipMnt []string `protobuf:"bytes,31,rep,name=skip_mnt,json=skipMnt" json:"skip_mnt,omitempty"` + EnableFs []string `protobuf:"bytes,32,rep,name=enable_fs,json=enableFs" json:"enable_fs,omitempty"` + UnixSkIno []*UnixSk `protobuf:"bytes,33,rep,name=unix_sk_ino,json=unixSkIno" json:"unix_sk_ino,omitempty"` // DEPRECATED, use external instead + ManageCgroupsMode *CriuCgMode `protobuf:"varint,34,opt,name=manage_cgroups_mode,json=manageCgroupsMode,enum=CriuCgMode" json:"manage_cgroups_mode,omitempty"` + GhostLimit *uint32 `protobuf:"varint,35,opt,name=ghost_limit,json=ghostLimit,def=1048576" json:"ghost_limit,omitempty"` + IrmapScanPaths []string `protobuf:"bytes,36,rep,name=irmap_scan_paths,json=irmapScanPaths" json:"irmap_scan_paths,omitempty"` + External []string `protobuf:"bytes,37,rep,name=external" json:"external,omitempty"` + EmptyNs *uint32 `protobuf:"varint,38,opt,name=empty_ns,json=emptyNs" json:"empty_ns,omitempty"` + JoinNs []*JoinNamespace `protobuf:"bytes,39,rep,name=join_ns,json=joinNs" json:"join_ns,omitempty"` + CgroupProps *string `protobuf:"bytes,41,opt,name=cgroup_props,json=cgroupProps" json:"cgroup_props,omitempty"` + CgroupPropsFile *string `protobuf:"bytes,42,opt,name=cgroup_props_file,json=cgroupPropsFile" json:"cgroup_props_file,omitempty"` + CgroupDumpController []string `protobuf:"bytes,43,rep,name=cgroup_dump_controller,json=cgroupDumpController" json:"cgroup_dump_controller,omitempty"` + FreezeCgroup *string `protobuf:"bytes,44,opt,name=freeze_cgroup,json=freezeCgroup" json:"freeze_cgroup,omitempty"` + Timeout *uint32 `protobuf:"varint,45,opt,name=timeout" json:"timeout,omitempty"` + TcpSkipInFlight *bool `protobuf:"varint,46,opt,name=tcp_skip_in_flight,json=tcpSkipInFlight" json:"tcp_skip_in_flight,omitempty"` + WeakSysctls *bool `protobuf:"varint,47,opt,name=weak_sysctls,json=weakSysctls" json:"weak_sysctls,omitempty"` + LazyPages *bool `protobuf:"varint,48,opt,name=lazy_pages,json=lazyPages" json:"lazy_pages,omitempty"` + StatusFd *int32 `protobuf:"varint,49,opt,name=status_fd,json=statusFd" json:"status_fd,omitempty"` + OrphanPtsMaster *bool `protobuf:"varint,50,opt,name=orphan_pts_master,json=orphanPtsMaster" json:"orphan_pts_master,omitempty"` + ConfigFile *string `protobuf:"bytes,51,opt,name=config_file,json=configFile" json:"config_file,omitempty"` + TcpClose *bool `protobuf:"varint,52,opt,name=tcp_close,json=tcpClose" json:"tcp_close,omitempty"` + LsmProfile *string `protobuf:"bytes,53,opt,name=lsm_profile,json=lsmProfile" json:"lsm_profile,omitempty"` + TlsCacert *string `protobuf:"bytes,54,opt,name=tls_cacert,json=tlsCacert" json:"tls_cacert,omitempty"` + TlsCacrl *string `protobuf:"bytes,55,opt,name=tls_cacrl,json=tlsCacrl" json:"tls_cacrl,omitempty"` + TlsCert *string `protobuf:"bytes,56,opt,name=tls_cert,json=tlsCert" json:"tls_cert,omitempty"` + TlsKey *string `protobuf:"bytes,57,opt,name=tls_key,json=tlsKey" json:"tls_key,omitempty"` + Tls *bool `protobuf:"varint,58,opt,name=tls" json:"tls,omitempty"` + TlsNoCnVerify *bool `protobuf:"varint,59,opt,name=tls_no_cn_verify,json=tlsNoCnVerify" json:"tls_no_cn_verify,omitempty"` + CgroupYard *string `protobuf:"bytes,60,opt,name=cgroup_yard,json=cgroupYard" json:"cgroup_yard,omitempty"` + PreDumpMode *CriuPreDumpMode `protobuf:"varint,61,opt,name=pre_dump_mode,json=preDumpMode,enum=CriuPreDumpMode,def=1" json:"pre_dump_mode,omitempty"` + PidfdStoreSk *int32 `protobuf:"varint,62,opt,name=pidfd_store_sk,json=pidfdStoreSk" json:"pidfd_store_sk,omitempty"` + LsmMountContext *string `protobuf:"bytes,63,opt,name=lsm_mount_context,json=lsmMountContext" json:"lsm_mount_context,omitempty"` + NetworkLock *CriuNetworkLockMethod `protobuf:"varint,64,opt,name=network_lock,json=networkLock,enum=CriuNetworkLockMethod,def=1" json:"network_lock,omitempty"` + MntnsCompatMode *bool `protobuf:"varint,65,opt,name=mntns_compat_mode,json=mntnsCompatMode" json:"mntns_compat_mode,omitempty"` + SkipFileRwxCheck *bool `protobuf:"varint,66,opt,name=skip_file_rwx_check,json=skipFileRwxCheck" json:"skip_file_rwx_check,omitempty"` + Unprivileged *bool `protobuf:"varint,67,opt,name=unprivileged" json:"unprivileged,omitempty"` + LeaveStopped *bool `protobuf:"varint,69,opt,name=leave_stopped,json=leaveStopped" json:"leave_stopped,omitempty"` + DisplayStats *bool `protobuf:"varint,70,opt,name=display_stats,json=displayStats" json:"display_stats,omitempty"` + LogToStderr *bool `protobuf:"varint,71,opt,name=log_to_stderr,json=logToStderr" json:"log_to_stderr,omitempty"` // optional bool check_mounts = 128; +} + +// Default values for CriuOpts fields. +const ( + Default_CriuOpts_ImagesDirFd = int32(-1) + Default_CriuOpts_LogLevel = int32(2) + Default_CriuOpts_CpuCap = uint32(4294967295) + Default_CriuOpts_GhostLimit = uint32(1048576) + Default_CriuOpts_PreDumpMode = CriuPreDumpMode_SPLICE + Default_CriuOpts_NetworkLock = CriuNetworkLockMethod_IPTABLES +) + +func (x *CriuOpts) Reset() { + *x = CriuOpts{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuOpts) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuOpts) ProtoMessage() {} + +func (x *CriuOpts) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuOpts.ProtoReflect.Descriptor instead. +func (*CriuOpts) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{7} +} + +func (x *CriuOpts) GetImagesDirFd() int32 { + if x != nil && x.ImagesDirFd != nil { + return *x.ImagesDirFd + } + return Default_CriuOpts_ImagesDirFd +} + +func (x *CriuOpts) GetImagesDir() string { + if x != nil && x.ImagesDir != nil { + return *x.ImagesDir + } + return "" +} + +func (x *CriuOpts) GetPid() int32 { + if x != nil && x.Pid != nil { + return *x.Pid + } + return 0 +} + +func (x *CriuOpts) GetLeaveRunning() bool { + if x != nil && x.LeaveRunning != nil { + return *x.LeaveRunning + } + return false +} + +func (x *CriuOpts) GetExtUnixSk() bool { + if x != nil && x.ExtUnixSk != nil { + return *x.ExtUnixSk + } + return false +} + +func (x *CriuOpts) GetTcpEstablished() bool { + if x != nil && x.TcpEstablished != nil { + return *x.TcpEstablished + } + return false +} + +func (x *CriuOpts) GetEvasiveDevices() bool { + if x != nil && x.EvasiveDevices != nil { + return *x.EvasiveDevices + } + return false +} + +func (x *CriuOpts) GetShellJob() bool { + if x != nil && x.ShellJob != nil { + return *x.ShellJob + } + return false +} + +func (x *CriuOpts) GetFileLocks() bool { + if x != nil && x.FileLocks != nil { + return *x.FileLocks + } + return false +} + +func (x *CriuOpts) GetLogLevel() int32 { + if x != nil && x.LogLevel != nil { + return *x.LogLevel + } + return Default_CriuOpts_LogLevel +} + +func (x *CriuOpts) GetLogFile() string { + if x != nil && x.LogFile != nil { + return *x.LogFile + } + return "" +} + +func (x *CriuOpts) GetPs() *CriuPageServerInfo { + if x != nil { + return x.Ps + } + return nil +} + +func (x *CriuOpts) GetNotifyScripts() bool { + if x != nil && x.NotifyScripts != nil { + return *x.NotifyScripts + } + return false +} + +func (x *CriuOpts) GetRoot() string { + if x != nil && x.Root != nil { + return *x.Root + } + return "" +} + +func (x *CriuOpts) GetParentImg() string { + if x != nil && x.ParentImg != nil { + return *x.ParentImg + } + return "" +} + +func (x *CriuOpts) GetTrackMem() bool { + if x != nil && x.TrackMem != nil { + return *x.TrackMem + } + return false +} + +func (x *CriuOpts) GetAutoDedup() bool { + if x != nil && x.AutoDedup != nil { + return *x.AutoDedup + } + return false +} + +func (x *CriuOpts) GetWorkDirFd() int32 { + if x != nil && x.WorkDirFd != nil { + return *x.WorkDirFd + } + return 0 +} + +func (x *CriuOpts) GetLinkRemap() bool { + if x != nil && x.LinkRemap != nil { + return *x.LinkRemap + } + return false +} + +func (x *CriuOpts) GetVeths() []*CriuVethPair { + if x != nil { + return x.Veths + } + return nil +} + +func (x *CriuOpts) GetCpuCap() uint32 { + if x != nil && x.CpuCap != nil { + return *x.CpuCap + } + return Default_CriuOpts_CpuCap +} + +func (x *CriuOpts) GetForceIrmap() bool { + if x != nil && x.ForceIrmap != nil { + return *x.ForceIrmap + } + return false +} + +func (x *CriuOpts) GetExecCmd() []string { + if x != nil { + return x.ExecCmd + } + return nil +} + +func (x *CriuOpts) GetExtMnt() []*ExtMountMap { + if x != nil { + return x.ExtMnt + } + return nil +} + +func (x *CriuOpts) GetManageCgroups() bool { + if x != nil && x.ManageCgroups != nil { + return *x.ManageCgroups + } + return false +} + +func (x *CriuOpts) GetCgRoot() []*CgroupRoot { + if x != nil { + return x.CgRoot + } + return nil +} + +func (x *CriuOpts) GetRstSibling() bool { + if x != nil && x.RstSibling != nil { + return *x.RstSibling + } + return false +} + +func (x *CriuOpts) GetInheritFd() []*InheritFd { + if x != nil { + return x.InheritFd + } + return nil +} + +func (x *CriuOpts) GetAutoExtMnt() bool { + if x != nil && x.AutoExtMnt != nil { + return *x.AutoExtMnt + } + return false +} + +func (x *CriuOpts) GetExtSharing() bool { + if x != nil && x.ExtSharing != nil { + return *x.ExtSharing + } + return false +} + +func (x *CriuOpts) GetExtMasters() bool { + if x != nil && x.ExtMasters != nil { + return *x.ExtMasters + } + return false +} + +func (x *CriuOpts) GetSkipMnt() []string { + if x != nil { + return x.SkipMnt + } + return nil +} + +func (x *CriuOpts) GetEnableFs() []string { + if x != nil { + return x.EnableFs + } + return nil +} + +func (x *CriuOpts) GetUnixSkIno() []*UnixSk { + if x != nil { + return x.UnixSkIno + } + return nil +} + +func (x *CriuOpts) GetManageCgroupsMode() CriuCgMode { + if x != nil && x.ManageCgroupsMode != nil { + return *x.ManageCgroupsMode + } + return CriuCgMode_IGNORE +} + +func (x *CriuOpts) GetGhostLimit() uint32 { + if x != nil && x.GhostLimit != nil { + return *x.GhostLimit + } + return Default_CriuOpts_GhostLimit +} + +func (x *CriuOpts) GetIrmapScanPaths() []string { + if x != nil { + return x.IrmapScanPaths + } + return nil +} + +func (x *CriuOpts) GetExternal() []string { + if x != nil { + return x.External + } + return nil +} + +func (x *CriuOpts) GetEmptyNs() uint32 { + if x != nil && x.EmptyNs != nil { + return *x.EmptyNs + } + return 0 +} + +func (x *CriuOpts) GetJoinNs() []*JoinNamespace { + if x != nil { + return x.JoinNs + } + return nil +} + +func (x *CriuOpts) GetCgroupProps() string { + if x != nil && x.CgroupProps != nil { + return *x.CgroupProps + } + return "" +} + +func (x *CriuOpts) GetCgroupPropsFile() string { + if x != nil && x.CgroupPropsFile != nil { + return *x.CgroupPropsFile + } + return "" +} + +func (x *CriuOpts) GetCgroupDumpController() []string { + if x != nil { + return x.CgroupDumpController + } + return nil +} + +func (x *CriuOpts) GetFreezeCgroup() string { + if x != nil && x.FreezeCgroup != nil { + return *x.FreezeCgroup + } + return "" +} + +func (x *CriuOpts) GetTimeout() uint32 { + if x != nil && x.Timeout != nil { + return *x.Timeout + } + return 0 +} + +func (x *CriuOpts) GetTcpSkipInFlight() bool { + if x != nil && x.TcpSkipInFlight != nil { + return *x.TcpSkipInFlight + } + return false +} + +func (x *CriuOpts) GetWeakSysctls() bool { + if x != nil && x.WeakSysctls != nil { + return *x.WeakSysctls + } + return false +} + +func (x *CriuOpts) GetLazyPages() bool { + if x != nil && x.LazyPages != nil { + return *x.LazyPages + } + return false +} + +func (x *CriuOpts) GetStatusFd() int32 { + if x != nil && x.StatusFd != nil { + return *x.StatusFd + } + return 0 +} + +func (x *CriuOpts) GetOrphanPtsMaster() bool { + if x != nil && x.OrphanPtsMaster != nil { + return *x.OrphanPtsMaster + } + return false +} + +func (x *CriuOpts) GetConfigFile() string { + if x != nil && x.ConfigFile != nil { + return *x.ConfigFile + } + return "" +} + +func (x *CriuOpts) GetTcpClose() bool { + if x != nil && x.TcpClose != nil { + return *x.TcpClose + } + return false +} + +func (x *CriuOpts) GetLsmProfile() string { + if x != nil && x.LsmProfile != nil { + return *x.LsmProfile + } + return "" +} + +func (x *CriuOpts) GetTlsCacert() string { + if x != nil && x.TlsCacert != nil { + return *x.TlsCacert + } + return "" +} + +func (x *CriuOpts) GetTlsCacrl() string { + if x != nil && x.TlsCacrl != nil { + return *x.TlsCacrl + } + return "" +} + +func (x *CriuOpts) GetTlsCert() string { + if x != nil && x.TlsCert != nil { + return *x.TlsCert + } + return "" +} + +func (x *CriuOpts) GetTlsKey() string { + if x != nil && x.TlsKey != nil { + return *x.TlsKey + } + return "" +} + +func (x *CriuOpts) GetTls() bool { + if x != nil && x.Tls != nil { + return *x.Tls + } + return false +} + +func (x *CriuOpts) GetTlsNoCnVerify() bool { + if x != nil && x.TlsNoCnVerify != nil { + return *x.TlsNoCnVerify + } + return false +} + +func (x *CriuOpts) GetCgroupYard() string { + if x != nil && x.CgroupYard != nil { + return *x.CgroupYard + } + return "" +} + +func (x *CriuOpts) GetPreDumpMode() CriuPreDumpMode { + if x != nil && x.PreDumpMode != nil { + return *x.PreDumpMode + } + return Default_CriuOpts_PreDumpMode +} + +func (x *CriuOpts) GetPidfdStoreSk() int32 { + if x != nil && x.PidfdStoreSk != nil { + return *x.PidfdStoreSk + } + return 0 +} + +func (x *CriuOpts) GetLsmMountContext() string { + if x != nil && x.LsmMountContext != nil { + return *x.LsmMountContext + } + return "" +} + +func (x *CriuOpts) GetNetworkLock() CriuNetworkLockMethod { + if x != nil && x.NetworkLock != nil { + return *x.NetworkLock + } + return Default_CriuOpts_NetworkLock +} + +func (x *CriuOpts) GetMntnsCompatMode() bool { + if x != nil && x.MntnsCompatMode != nil { + return *x.MntnsCompatMode + } + return false +} + +func (x *CriuOpts) GetSkipFileRwxCheck() bool { + if x != nil && x.SkipFileRwxCheck != nil { + return *x.SkipFileRwxCheck + } + return false +} + +func (x *CriuOpts) GetUnprivileged() bool { + if x != nil && x.Unprivileged != nil { + return *x.Unprivileged + } + return false +} + +func (x *CriuOpts) GetLeaveStopped() bool { + if x != nil && x.LeaveStopped != nil { + return *x.LeaveStopped + } + return false +} + +func (x *CriuOpts) GetDisplayStats() bool { + if x != nil && x.DisplayStats != nil { + return *x.DisplayStats + } + return false +} + +func (x *CriuOpts) GetLogToStderr() bool { + if x != nil && x.LogToStderr != nil { + return *x.LogToStderr + } + return false +} + +type CriuDumpResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Restored *bool `protobuf:"varint,1,opt,name=restored" json:"restored,omitempty"` +} + +func (x *CriuDumpResp) Reset() { + *x = CriuDumpResp{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuDumpResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuDumpResp) ProtoMessage() {} + +func (x *CriuDumpResp) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuDumpResp.ProtoReflect.Descriptor instead. +func (*CriuDumpResp) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{8} +} + +func (x *CriuDumpResp) GetRestored() bool { + if x != nil && x.Restored != nil { + return *x.Restored + } + return false +} + +type CriuRestoreResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pid *int32 `protobuf:"varint,1,req,name=pid" json:"pid,omitempty"` +} + +func (x *CriuRestoreResp) Reset() { + *x = CriuRestoreResp{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuRestoreResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuRestoreResp) ProtoMessage() {} + +func (x *CriuRestoreResp) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuRestoreResp.ProtoReflect.Descriptor instead. +func (*CriuRestoreResp) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{9} +} + +func (x *CriuRestoreResp) GetPid() int32 { + if x != nil && x.Pid != nil { + return *x.Pid + } + return 0 +} + +type CriuNotify struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Script *string `protobuf:"bytes,1,opt,name=script" json:"script,omitempty"` + Pid *int32 `protobuf:"varint,2,opt,name=pid" json:"pid,omitempty"` +} + +func (x *CriuNotify) Reset() { + *x = CriuNotify{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuNotify) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuNotify) ProtoMessage() {} + +func (x *CriuNotify) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuNotify.ProtoReflect.Descriptor instead. +func (*CriuNotify) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{10} +} + +func (x *CriuNotify) GetScript() string { + if x != nil && x.Script != nil { + return *x.Script + } + return "" +} + +func (x *CriuNotify) GetPid() int32 { + if x != nil && x.Pid != nil { + return *x.Pid + } + return 0 +} + +// List of features which can queried via +// CRIU_REQ_TYPE__FEATURE_CHECK +type CriuFeatures struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MemTrack *bool `protobuf:"varint,1,opt,name=mem_track,json=memTrack" json:"mem_track,omitempty"` + LazyPages *bool `protobuf:"varint,2,opt,name=lazy_pages,json=lazyPages" json:"lazy_pages,omitempty"` + PidfdStore *bool `protobuf:"varint,3,opt,name=pidfd_store,json=pidfdStore" json:"pidfd_store,omitempty"` +} + +func (x *CriuFeatures) Reset() { + *x = CriuFeatures{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuFeatures) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuFeatures) ProtoMessage() {} + +func (x *CriuFeatures) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuFeatures.ProtoReflect.Descriptor instead. +func (*CriuFeatures) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{11} +} + +func (x *CriuFeatures) GetMemTrack() bool { + if x != nil && x.MemTrack != nil { + return *x.MemTrack + } + return false +} + +func (x *CriuFeatures) GetLazyPages() bool { + if x != nil && x.LazyPages != nil { + return *x.LazyPages + } + return false +} + +func (x *CriuFeatures) GetPidfdStore() bool { + if x != nil && x.PidfdStore != nil { + return *x.PidfdStore + } + return false +} + +type CriuReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type *CriuReqType `protobuf:"varint,1,req,name=type,enum=CriuReqType" json:"type,omitempty"` + Opts *CriuOpts `protobuf:"bytes,2,opt,name=opts" json:"opts,omitempty"` + NotifySuccess *bool `protobuf:"varint,3,opt,name=notify_success,json=notifySuccess" json:"notify_success,omitempty"` + // When set service won't close the connection but + // will wait for more req-s to appear. Works not + // for all request types. + KeepOpen *bool `protobuf:"varint,4,opt,name=keep_open,json=keepOpen" json:"keep_open,omitempty"` + // 'features' can be used to query which features + // are supported by the installed criu/kernel + // via RPC. + Features *CriuFeatures `protobuf:"bytes,5,opt,name=features" json:"features,omitempty"` + // 'pid' is used for WAIT_PID + Pid *uint32 `protobuf:"varint,6,opt,name=pid" json:"pid,omitempty"` +} + +func (x *CriuReq) Reset() { + *x = CriuReq{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuReq) ProtoMessage() {} + +func (x *CriuReq) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuReq.ProtoReflect.Descriptor instead. +func (*CriuReq) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{12} +} + +func (x *CriuReq) GetType() CriuReqType { + if x != nil && x.Type != nil { + return *x.Type + } + return CriuReqType_EMPTY +} + +func (x *CriuReq) GetOpts() *CriuOpts { + if x != nil { + return x.Opts + } + return nil +} + +func (x *CriuReq) GetNotifySuccess() bool { + if x != nil && x.NotifySuccess != nil { + return *x.NotifySuccess + } + return false +} + +func (x *CriuReq) GetKeepOpen() bool { + if x != nil && x.KeepOpen != nil { + return *x.KeepOpen + } + return false +} + +func (x *CriuReq) GetFeatures() *CriuFeatures { + if x != nil { + return x.Features + } + return nil +} + +func (x *CriuReq) GetPid() uint32 { + if x != nil && x.Pid != nil { + return *x.Pid + } + return 0 +} + +type CriuResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type *CriuReqType `protobuf:"varint,1,req,name=type,enum=CriuReqType" json:"type,omitempty"` + Success *bool `protobuf:"varint,2,req,name=success" json:"success,omitempty"` + Dump *CriuDumpResp `protobuf:"bytes,3,opt,name=dump" json:"dump,omitempty"` + Restore *CriuRestoreResp `protobuf:"bytes,4,opt,name=restore" json:"restore,omitempty"` + Notify *CriuNotify `protobuf:"bytes,5,opt,name=notify" json:"notify,omitempty"` + Ps *CriuPageServerInfo `protobuf:"bytes,6,opt,name=ps" json:"ps,omitempty"` + CrErrno *int32 `protobuf:"varint,7,opt,name=cr_errno,json=crErrno" json:"cr_errno,omitempty"` + Features *CriuFeatures `protobuf:"bytes,8,opt,name=features" json:"features,omitempty"` + CrErrmsg *string `protobuf:"bytes,9,opt,name=cr_errmsg,json=crErrmsg" json:"cr_errmsg,omitempty"` + Version *CriuVersion `protobuf:"bytes,10,opt,name=version" json:"version,omitempty"` + Status *int32 `protobuf:"varint,11,opt,name=status" json:"status,omitempty"` +} + +func (x *CriuResp) Reset() { + *x = CriuResp{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuResp) ProtoMessage() {} + +func (x *CriuResp) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuResp.ProtoReflect.Descriptor instead. +func (*CriuResp) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{13} +} + +func (x *CriuResp) GetType() CriuReqType { + if x != nil && x.Type != nil { + return *x.Type + } + return CriuReqType_EMPTY +} + +func (x *CriuResp) GetSuccess() bool { + if x != nil && x.Success != nil { + return *x.Success + } + return false +} + +func (x *CriuResp) GetDump() *CriuDumpResp { + if x != nil { + return x.Dump + } + return nil +} + +func (x *CriuResp) GetRestore() *CriuRestoreResp { + if x != nil { + return x.Restore + } + return nil +} + +func (x *CriuResp) GetNotify() *CriuNotify { + if x != nil { + return x.Notify + } + return nil +} + +func (x *CriuResp) GetPs() *CriuPageServerInfo { + if x != nil { + return x.Ps + } + return nil +} + +func (x *CriuResp) GetCrErrno() int32 { + if x != nil && x.CrErrno != nil { + return *x.CrErrno + } + return 0 +} + +func (x *CriuResp) GetFeatures() *CriuFeatures { + if x != nil { + return x.Features + } + return nil +} + +func (x *CriuResp) GetCrErrmsg() string { + if x != nil && x.CrErrmsg != nil { + return *x.CrErrmsg + } + return "" +} + +func (x *CriuResp) GetVersion() *CriuVersion { + if x != nil { + return x.Version + } + return nil +} + +func (x *CriuResp) GetStatus() int32 { + if x != nil && x.Status != nil { + return *x.Status + } + return 0 +} + +// Answer for criu_req_type.VERSION requests +type CriuVersion struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MajorNumber *int32 `protobuf:"varint,1,req,name=major_number,json=majorNumber" json:"major_number,omitempty"` + MinorNumber *int32 `protobuf:"varint,2,req,name=minor_number,json=minorNumber" json:"minor_number,omitempty"` + Gitid *string `protobuf:"bytes,3,opt,name=gitid" json:"gitid,omitempty"` + Sublevel *int32 `protobuf:"varint,4,opt,name=sublevel" json:"sublevel,omitempty"` + Extra *int32 `protobuf:"varint,5,opt,name=extra" json:"extra,omitempty"` + Name *string `protobuf:"bytes,6,opt,name=name" json:"name,omitempty"` +} + +func (x *CriuVersion) Reset() { + *x = CriuVersion{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_rpc_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CriuVersion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CriuVersion) ProtoMessage() {} + +func (x *CriuVersion) ProtoReflect() protoreflect.Message { + mi := &file_rpc_rpc_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CriuVersion.ProtoReflect.Descriptor instead. +func (*CriuVersion) Descriptor() ([]byte, []int) { + return file_rpc_rpc_proto_rawDescGZIP(), []int{14} +} + +func (x *CriuVersion) GetMajorNumber() int32 { + if x != nil && x.MajorNumber != nil { + return *x.MajorNumber + } + return 0 +} + +func (x *CriuVersion) GetMinorNumber() int32 { + if x != nil && x.MinorNumber != nil { + return *x.MinorNumber + } + return 0 +} + +func (x *CriuVersion) GetGitid() string { + if x != nil && x.Gitid != nil { + return *x.Gitid + } + return "" +} + +func (x *CriuVersion) GetSublevel() int32 { + if x != nil && x.Sublevel != nil { + return *x.Sublevel + } + return 0 +} + +func (x *CriuVersion) GetExtra() int32 { + if x != nil && x.Extra != nil { + return *x.Extra + } + return 0 +} + +func (x *CriuVersion) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +var File_rpc_rpc_proto protoreflect.FileDescriptor + +var file_rpc_rpc_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x67, 0x0a, 0x15, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x66, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x66, 0x64, 0x22, 0x3c, 0x0a, 0x0e, 0x63, 0x72, 0x69, 0x75, + 0x5f, 0x76, 0x65, 0x74, 0x68, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x12, 0x13, 0x0a, 0x05, 0x69, 0x66, + 0x5f, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x04, 0x69, 0x66, 0x49, 0x6e, 0x12, + 0x15, 0x0a, 0x06, 0x69, 0x66, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x02, 0x28, 0x09, 0x52, + 0x05, 0x69, 0x66, 0x4f, 0x75, 0x74, 0x22, 0x33, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x5f, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x02, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, + 0x18, 0x02, 0x20, 0x02, 0x28, 0x09, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x56, 0x0a, 0x0e, 0x6a, + 0x6f, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, + 0x02, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x02, 0x6e, 0x73, 0x12, 0x17, 0x0a, + 0x07, 0x6e, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x02, 0x28, 0x09, 0x52, 0x06, + 0x6e, 0x73, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, + 0x6f, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x74, 0x72, 0x61, + 0x4f, 0x70, 0x74, 0x22, 0x2e, 0x0a, 0x0a, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x5f, 0x66, + 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x66, 0x64, 0x18, 0x02, 0x20, 0x02, 0x28, 0x05, 0x52, + 0x02, 0x66, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x72, 0x6f, + 0x6f, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x74, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x63, 0x74, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, + 0x20, 0x02, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x1f, 0x0a, 0x07, 0x75, 0x6e, + 0x69, 0x78, 0x5f, 0x73, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, + 0x20, 0x02, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x22, 0xe4, 0x13, 0x0a, 0x09, + 0x63, 0x72, 0x69, 0x75, 0x5f, 0x6f, 0x70, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0d, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x73, 0x5f, 0x64, 0x69, 0x72, 0x5f, 0x66, 0x64, 0x18, 0x01, 0x20, 0x02, 0x28, 0x05, + 0x3a, 0x02, 0x2d, 0x31, 0x52, 0x0b, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x44, 0x69, 0x72, 0x46, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x64, 0x69, 0x72, 0x18, + 0x44, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x44, 0x69, 0x72, + 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, + 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x5f, 0x72, 0x75, 0x6e, 0x6e, + 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6c, 0x65, 0x61, 0x76, 0x65, + 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x1e, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x5f, 0x75, + 0x6e, 0x69, 0x78, 0x5f, 0x73, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x65, 0x78, + 0x74, 0x55, 0x6e, 0x69, 0x78, 0x53, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x63, 0x70, 0x5f, 0x65, + 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0e, 0x74, 0x63, 0x70, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x76, 0x61, 0x73, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x65, 0x76, 0x61, 0x73, 0x69, + 0x76, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x68, 0x65, + 0x6c, 0x6c, 0x5f, 0x6a, 0x6f, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x73, 0x68, + 0x65, 0x6c, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6c, + 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x69, 0x6c, 0x65, + 0x4c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x1e, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x3a, 0x01, 0x32, 0x52, 0x08, 0x6c, 0x6f, 0x67, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, + 0x12, 0x26, 0x0a, 0x02, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, + 0x72, 0x69, 0x75, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x69, 0x6e, 0x66, 0x6f, 0x52, 0x02, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, + 0x66, 0x79, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, + 0x6f, 0x6f, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6d, + 0x67, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, + 0x6d, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x5f, 0x6d, 0x65, 0x6d, 0x18, + 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x4d, 0x65, 0x6d, 0x12, + 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x64, 0x75, 0x70, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x44, 0x65, 0x64, 0x75, 0x70, 0x12, 0x1e, + 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x64, 0x69, 0x72, 0x5f, 0x66, 0x64, 0x18, 0x11, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x44, 0x69, 0x72, 0x46, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x70, 0x18, 0x12, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x52, 0x65, 0x6d, 0x61, 0x70, 0x12, 0x25, 0x0a, + 0x05, 0x76, 0x65, 0x74, 0x68, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, + 0x72, 0x69, 0x75, 0x5f, 0x76, 0x65, 0x74, 0x68, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x52, 0x05, 0x76, + 0x65, 0x74, 0x68, 0x73, 0x12, 0x23, 0x0a, 0x07, 0x63, 0x70, 0x75, 0x5f, 0x63, 0x61, 0x70, 0x18, + 0x14, 0x20, 0x01, 0x28, 0x0d, 0x3a, 0x0a, 0x34, 0x32, 0x39, 0x34, 0x39, 0x36, 0x37, 0x32, 0x39, + 0x35, 0x52, 0x06, 0x63, 0x70, 0x75, 0x43, 0x61, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6f, 0x72, + 0x63, 0x65, 0x5f, 0x69, 0x72, 0x6d, 0x61, 0x70, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x49, 0x72, 0x6d, 0x61, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x78, + 0x65, 0x63, 0x5f, 0x63, 0x6d, 0x64, 0x18, 0x16, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x65, 0x78, + 0x65, 0x63, 0x43, 0x6d, 0x64, 0x12, 0x27, 0x0a, 0x07, 0x65, 0x78, 0x74, 0x5f, 0x6d, 0x6e, 0x74, + 0x18, 0x17, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x78, 0x74, 0x5f, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x70, 0x52, 0x06, 0x65, 0x78, 0x74, 0x4d, 0x6e, 0x74, 0x12, 0x25, + 0x0a, 0x0e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x43, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x07, 0x63, 0x67, 0x5f, 0x72, 0x6f, 0x6f, 0x74, + 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, + 0x72, 0x6f, 0x6f, 0x74, 0x52, 0x06, 0x63, 0x67, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x72, 0x73, 0x74, 0x5f, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x1a, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0a, 0x72, 0x73, 0x74, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x2a, 0x0a, + 0x0a, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x5f, 0x66, 0x64, 0x18, 0x1b, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0b, 0x2e, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x5f, 0x66, 0x64, 0x52, 0x09, + 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x46, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x75, 0x74, + 0x6f, 0x5f, 0x65, 0x78, 0x74, 0x5f, 0x6d, 0x6e, 0x74, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x45, 0x78, 0x74, 0x4d, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, + 0x78, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0a, 0x65, 0x78, 0x74, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x0a, 0x0b, + 0x65, 0x78, 0x74, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x73, 0x18, 0x1e, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x73, 0x12, 0x19, 0x0a, + 0x08, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x6d, 0x6e, 0x74, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x07, 0x73, 0x6b, 0x69, 0x70, 0x4d, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x5f, 0x66, 0x73, 0x18, 0x20, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x46, 0x73, 0x12, 0x28, 0x0a, 0x0b, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x73, 0x6b, + 0x5f, 0x69, 0x6e, 0x6f, 0x18, 0x21, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x75, 0x6e, 0x69, + 0x78, 0x5f, 0x73, 0x6b, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x78, 0x53, 0x6b, 0x49, 0x6e, 0x6f, 0x12, + 0x3d, 0x0a, 0x13, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x73, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x63, + 0x72, 0x69, 0x75, 0x5f, 0x63, 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x52, 0x11, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x43, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x28, + 0x0a, 0x0b, 0x67, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x23, 0x20, + 0x01, 0x28, 0x0d, 0x3a, 0x07, 0x31, 0x30, 0x34, 0x38, 0x35, 0x37, 0x36, 0x52, 0x0a, 0x67, 0x68, + 0x6f, 0x73, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x72, 0x6d, 0x61, + 0x70, 0x5f, 0x73, 0x63, 0x61, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x24, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0e, 0x69, 0x72, 0x6d, 0x61, 0x70, 0x53, 0x63, 0x61, 0x6e, 0x50, 0x61, 0x74, + 0x68, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x25, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x19, + 0x0a, 0x08, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x6e, 0x73, 0x18, 0x26, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x07, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x4e, 0x73, 0x12, 0x28, 0x0a, 0x07, 0x6a, 0x6f, 0x69, + 0x6e, 0x5f, 0x6e, 0x73, 0x18, 0x27, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6a, 0x6f, 0x69, + 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x06, 0x6a, 0x6f, 0x69, + 0x6e, 0x4e, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x70, 0x72, + 0x6f, 0x70, 0x73, 0x18, 0x29, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x50, 0x72, 0x6f, 0x70, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x2a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x70, 0x73, 0x46, 0x69, + 0x6c, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x64, 0x75, 0x6d, + 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x2b, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x14, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x44, 0x75, 0x6d, 0x70, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, 0x65, 0x65, + 0x7a, 0x65, 0x5f, 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x66, 0x72, 0x65, 0x65, 0x7a, 0x65, 0x43, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x18, 0x0a, + 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x2b, 0x0a, 0x12, 0x74, 0x63, 0x70, 0x5f, 0x73, + 0x6b, 0x69, 0x70, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x18, 0x2e, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0f, 0x74, 0x63, 0x70, 0x53, 0x6b, 0x69, 0x70, 0x49, 0x6e, 0x46, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x65, 0x61, 0x6b, 0x5f, 0x73, 0x79, 0x73, + 0x63, 0x74, 0x6c, 0x73, 0x18, 0x2f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x65, 0x61, 0x6b, + 0x53, 0x79, 0x73, 0x63, 0x74, 0x6c, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x7a, 0x79, 0x5f, + 0x70, 0x61, 0x67, 0x65, 0x73, 0x18, 0x30, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6c, 0x61, 0x7a, + 0x79, 0x50, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x5f, 0x66, 0x64, 0x18, 0x31, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x46, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x74, + 0x73, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x18, 0x32, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, + 0x6f, 0x72, 0x70, 0x68, 0x61, 0x6e, 0x50, 0x74, 0x73, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x12, + 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x33, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x18, 0x34, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x63, 0x70, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x6c, 0x73, 0x6d, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x35, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x73, 0x6d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x74, 0x6c, 0x73, 0x5f, 0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x18, 0x36, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x74, 0x6c, 0x73, 0x43, 0x61, 0x63, 0x65, 0x72, 0x74, 0x12, 0x1b, 0x0a, + 0x09, 0x74, 0x6c, 0x73, 0x5f, 0x63, 0x61, 0x63, 0x72, 0x6c, 0x18, 0x37, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x74, 0x6c, 0x73, 0x43, 0x61, 0x63, 0x72, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6c, + 0x73, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x38, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x6c, + 0x73, 0x43, 0x65, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6c, 0x73, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x39, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6c, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x74, 0x6c, 0x73, 0x18, 0x3a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x74, 0x6c, 0x73, + 0x12, 0x27, 0x0a, 0x10, 0x74, 0x6c, 0x73, 0x5f, 0x6e, 0x6f, 0x5f, 0x63, 0x6e, 0x5f, 0x76, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x18, 0x3b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x74, 0x6c, 0x73, 0x4e, + 0x6f, 0x43, 0x6e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x5f, 0x79, 0x61, 0x72, 0x64, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x63, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x59, 0x61, 0x72, 0x64, 0x12, 0x3f, 0x0a, 0x0d, 0x70, 0x72, + 0x65, 0x5f, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x3d, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x13, 0x2e, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x5f, 0x64, 0x75, 0x6d, + 0x70, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x3a, 0x06, 0x53, 0x50, 0x4c, 0x49, 0x43, 0x45, 0x52, 0x0b, + 0x70, 0x72, 0x65, 0x44, 0x75, 0x6d, 0x70, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x70, + 0x69, 0x64, 0x66, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x6b, 0x18, 0x3e, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0c, 0x70, 0x69, 0x64, 0x66, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x53, + 0x6b, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x73, 0x6d, 0x5f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x3f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x73, + 0x6d, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x46, 0x0a, + 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x40, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x3a, 0x08, + 0x49, 0x50, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x53, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x6e, 0x74, 0x6e, 0x73, 0x5f, 0x63, + 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x41, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0f, 0x6d, 0x6e, 0x74, 0x6e, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x4d, 0x6f, 0x64, + 0x65, 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x72, + 0x77, 0x78, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x42, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, + 0x73, 0x6b, 0x69, 0x70, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x77, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x12, 0x22, 0x0a, 0x0c, 0x75, 0x6e, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x64, + 0x18, 0x43, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x6e, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, + 0x65, 0x67, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x5f, 0x73, 0x74, + 0x6f, 0x70, 0x70, 0x65, 0x64, 0x18, 0x45, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6c, 0x65, 0x61, + 0x76, 0x65, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x46, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, + 0x0a, 0x0d, 0x6c, 0x6f, 0x67, 0x5f, 0x74, 0x6f, 0x5f, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, + 0x47, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6c, 0x6f, 0x67, 0x54, 0x6f, 0x53, 0x74, 0x64, 0x65, + 0x72, 0x72, 0x22, 0x2c, 0x0a, 0x0e, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x64, 0x75, 0x6d, 0x70, 0x5f, + 0x72, 0x65, 0x73, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, + 0x22, 0x25, 0x0a, 0x11, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x5f, 0x72, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x02, + 0x28, 0x05, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22, 0x37, 0x0a, 0x0b, 0x63, 0x72, 0x69, 0x75, 0x5f, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x10, + 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, 0x69, 0x64, + 0x22, 0x6c, 0x0a, 0x0d, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6d, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x65, 0x6d, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x1d, + 0x0a, 0x0a, 0x6c, 0x61, 0x7a, 0x79, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x6c, 0x61, 0x7a, 0x79, 0x50, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1f, 0x0a, + 0x0b, 0x70, 0x69, 0x64, 0x66, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x70, 0x69, 0x64, 0x66, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x22, 0xd0, + 0x01, 0x0a, 0x08, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x72, 0x65, 0x71, 0x12, 0x22, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x63, 0x72, 0x69, 0x75, + 0x5f, 0x72, 0x65, 0x71, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, + 0x1e, 0x0a, 0x04, 0x6f, 0x70, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, + 0x63, 0x72, 0x69, 0x75, 0x5f, 0x6f, 0x70, 0x74, 0x73, 0x52, 0x04, 0x6f, 0x70, 0x74, 0x73, 0x12, + 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x53, + 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x6f, + 0x70, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6b, 0x65, 0x65, 0x70, 0x4f, + 0x70, 0x65, 0x6e, 0x12, 0x2a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, + 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, + 0x64, 0x22, 0x8f, 0x03, 0x0a, 0x09, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x12, + 0x22, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0e, 0x32, 0x0e, 0x2e, + 0x63, 0x72, 0x69, 0x75, 0x5f, 0x72, 0x65, 0x71, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, + 0x20, 0x02, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, + 0x04, 0x64, 0x75, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x72, + 0x69, 0x75, 0x5f, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x52, 0x04, 0x64, 0x75, + 0x6d, 0x70, 0x12, 0x2c, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x52, 0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x12, 0x24, 0x0a, 0x06, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0c, 0x2e, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x06, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x26, 0x0a, 0x02, 0x70, 0x73, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x52, 0x02, 0x70, 0x73, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x72, 0x5f, 0x65, 0x72, 0x72, 0x6e, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x07, 0x63, 0x72, 0x45, 0x72, 0x72, 0x6e, 0x6f, 0x12, 0x2a, 0x0a, 0x08, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x72, + 0x69, 0x75, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x08, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x72, 0x5f, 0x65, 0x72, 0x72, 0x6d, + 0x73, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x72, 0x45, 0x72, 0x72, 0x6d, + 0x73, 0x67, 0x12, 0x27, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x22, 0xb0, 0x01, 0x0a, 0x0c, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x6e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x02, 0x28, 0x05, 0x52, 0x0b, 0x6d, 0x61, 0x6a, 0x6f, + 0x72, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x6f, 0x72, + 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x02, 0x28, 0x05, 0x52, 0x0b, 0x6d, + 0x69, 0x6e, 0x6f, 0x72, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x69, + 0x74, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x69, 0x74, 0x69, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x75, 0x62, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x73, 0x75, 0x62, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x78, 0x74, 0x72, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x65, 0x78, 0x74, + 0x72, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x2a, 0x5f, 0x0a, 0x0c, 0x63, 0x72, 0x69, 0x75, 0x5f, 0x63, + 0x67, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, + 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x47, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12, + 0x09, 0x0a, 0x05, 0x50, 0x52, 0x4f, 0x50, 0x53, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x4f, + 0x46, 0x54, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x04, 0x12, 0x0a, + 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, + 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x06, 0x2a, 0x40, 0x0a, 0x18, 0x63, 0x72, 0x69, 0x75, 0x5f, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x12, 0x0c, 0x0a, 0x08, 0x49, 0x50, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x53, 0x10, + 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x46, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x53, 0x10, 0x02, 0x12, + 0x08, 0x0a, 0x04, 0x53, 0x4b, 0x49, 0x50, 0x10, 0x03, 0x2a, 0x2d, 0x0a, 0x12, 0x63, 0x72, 0x69, + 0x75, 0x5f, 0x70, 0x72, 0x65, 0x5f, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x12, + 0x0a, 0x0a, 0x06, 0x53, 0x50, 0x4c, 0x49, 0x43, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x56, + 0x4d, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x10, 0x02, 0x2a, 0xe5, 0x01, 0x0a, 0x0d, 0x63, 0x72, 0x69, + 0x75, 0x5f, 0x72, 0x65, 0x71, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x4d, + 0x50, 0x54, 0x59, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x55, 0x4d, 0x50, 0x10, 0x01, 0x12, + 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x53, 0x54, 0x4f, 0x52, 0x45, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, + 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52, 0x45, 0x5f, 0x44, + 0x55, 0x4d, 0x50, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x41, 0x47, 0x45, 0x5f, 0x53, 0x45, + 0x52, 0x56, 0x45, 0x52, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x59, + 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x50, 0x55, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x44, 0x55, + 0x4d, 0x50, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x50, 0x55, 0x49, 0x4e, 0x46, 0x4f, 0x5f, + 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x46, 0x45, 0x41, 0x54, 0x55, + 0x52, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x09, 0x12, 0x0b, 0x0a, 0x07, 0x56, 0x45, + 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x0a, 0x12, 0x0c, 0x0a, 0x08, 0x57, 0x41, 0x49, 0x54, 0x5f, + 0x50, 0x49, 0x44, 0x10, 0x0b, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x47, 0x45, 0x5f, 0x53, 0x45, + 0x52, 0x56, 0x45, 0x52, 0x5f, 0x43, 0x48, 0x4c, 0x44, 0x10, 0x0c, 0x12, 0x13, 0x0a, 0x0f, 0x53, + 0x49, 0x4e, 0x47, 0x4c, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x5f, 0x44, 0x55, 0x4d, 0x50, 0x10, 0x0d, +} + +var ( + file_rpc_rpc_proto_rawDescOnce sync.Once + file_rpc_rpc_proto_rawDescData = file_rpc_rpc_proto_rawDesc +) + +func file_rpc_rpc_proto_rawDescGZIP() []byte { + file_rpc_rpc_proto_rawDescOnce.Do(func() { + file_rpc_rpc_proto_rawDescData = protoimpl.X.CompressGZIP(file_rpc_rpc_proto_rawDescData) + }) + return file_rpc_rpc_proto_rawDescData +} + +var file_rpc_rpc_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_rpc_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_rpc_rpc_proto_goTypes = []interface{}{ + (CriuCgMode)(0), // 0: criu_cg_mode + (CriuNetworkLockMethod)(0), // 1: criu_network_lock_method + (CriuPreDumpMode)(0), // 2: criu_pre_dump_mode + (CriuReqType)(0), // 3: criu_req_type + (*CriuPageServerInfo)(nil), // 4: criu_page_server_info + (*CriuVethPair)(nil), // 5: criu_veth_pair + (*ExtMountMap)(nil), // 6: ext_mount_map + (*JoinNamespace)(nil), // 7: join_namespace + (*InheritFd)(nil), // 8: inherit_fd + (*CgroupRoot)(nil), // 9: cgroup_root + (*UnixSk)(nil), // 10: unix_sk + (*CriuOpts)(nil), // 11: criu_opts + (*CriuDumpResp)(nil), // 12: criu_dump_resp + (*CriuRestoreResp)(nil), // 13: criu_restore_resp + (*CriuNotify)(nil), // 14: criu_notify + (*CriuFeatures)(nil), // 15: criu_features + (*CriuReq)(nil), // 16: criu_req + (*CriuResp)(nil), // 17: criu_resp + (*CriuVersion)(nil), // 18: criu_version +} +var file_rpc_rpc_proto_depIdxs = []int32{ + 4, // 0: criu_opts.ps:type_name -> criu_page_server_info + 5, // 1: criu_opts.veths:type_name -> criu_veth_pair + 6, // 2: criu_opts.ext_mnt:type_name -> ext_mount_map + 9, // 3: criu_opts.cg_root:type_name -> cgroup_root + 8, // 4: criu_opts.inherit_fd:type_name -> inherit_fd + 10, // 5: criu_opts.unix_sk_ino:type_name -> unix_sk + 0, // 6: criu_opts.manage_cgroups_mode:type_name -> criu_cg_mode + 7, // 7: criu_opts.join_ns:type_name -> join_namespace + 2, // 8: criu_opts.pre_dump_mode:type_name -> criu_pre_dump_mode + 1, // 9: criu_opts.network_lock:type_name -> criu_network_lock_method + 3, // 10: criu_req.type:type_name -> criu_req_type + 11, // 11: criu_req.opts:type_name -> criu_opts + 15, // 12: criu_req.features:type_name -> criu_features + 3, // 13: criu_resp.type:type_name -> criu_req_type + 12, // 14: criu_resp.dump:type_name -> criu_dump_resp + 13, // 15: criu_resp.restore:type_name -> criu_restore_resp + 14, // 16: criu_resp.notify:type_name -> criu_notify + 4, // 17: criu_resp.ps:type_name -> criu_page_server_info + 15, // 18: criu_resp.features:type_name -> criu_features + 18, // 19: criu_resp.version:type_name -> criu_version + 20, // [20:20] is the sub-list for method output_type + 20, // [20:20] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name +} + +func init() { file_rpc_rpc_proto_init() } +func file_rpc_rpc_proto_init() { + if File_rpc_rpc_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_rpc_rpc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuPageServerInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuVethPair); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExtMountMap); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*JoinNamespace); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InheritFd); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CgroupRoot); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UnixSk); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuOpts); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuDumpResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuRestoreResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuNotify); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuFeatures); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_rpc_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CriuVersion); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_rpc_rpc_proto_rawDesc, + NumEnums: 4, + NumMessages: 15, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_rpc_rpc_proto_goTypes, + DependencyIndexes: file_rpc_rpc_proto_depIdxs, + EnumInfos: file_rpc_rpc_proto_enumTypes, + MessageInfos: file_rpc_rpc_proto_msgTypes, + }.Build() + File_rpc_rpc_proto = out.File + file_rpc_rpc_proto_rawDesc = nil + file_rpc_rpc_proto_goTypes = nil + file_rpc_rpc_proto_depIdxs = nil +} diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/rpc/rpc.proto b/vendor/github.com/checkpoint-restore/go-criu/v7/rpc/rpc.proto new file mode 100644 index 00000000..1a4722a9 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/rpc/rpc.proto @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: MIT + +syntax = "proto2"; + +message criu_page_server_info { + optional string address = 1; + optional int32 port = 2; + optional int32 pid = 3; + optional int32 fd = 4; +} + +message criu_veth_pair { + required string if_in = 1; + required string if_out = 2; +}; + +message ext_mount_map { + required string key = 1; + required string val = 2; +}; + +message join_namespace { + required string ns = 1; + required string ns_file = 2; + optional string extra_opt = 3; +} + +message inherit_fd { + required string key = 1; + required int32 fd = 2; +}; + +message cgroup_root { + optional string ctrl = 1; + required string path = 2; +}; + +message unix_sk { + required uint32 inode = 1; +}; + +enum criu_cg_mode { + IGNORE = 0; + CG_NONE = 1; + PROPS = 2; + SOFT = 3; + FULL = 4; + STRICT = 5; + DEFAULT = 6; +}; + +enum criu_network_lock_method { + IPTABLES = 1; + NFTABLES = 2; + SKIP = 3; +}; + +enum criu_pre_dump_mode { + SPLICE = 1; + VM_READ = 2; +}; + +message criu_opts { + required int32 images_dir_fd = 1 [default = -1]; + optional string images_dir = 68; /* used only if images_dir_fd == -1 */ + optional int32 pid = 2; /* if not set on dump, will dump requesting process */ + + optional bool leave_running = 3; + optional bool ext_unix_sk = 4; + optional bool tcp_established = 5; + optional bool evasive_devices = 6; + optional bool shell_job = 7; + optional bool file_locks = 8; + optional int32 log_level = 9 [default = 2]; + optional string log_file = 10; /* No subdirs are allowed. Consider using work-dir */ + + optional criu_page_server_info ps = 11; + + optional bool notify_scripts = 12; + + optional string root = 13; + optional string parent_img = 14; + optional bool track_mem = 15; + optional bool auto_dedup = 16; + + optional int32 work_dir_fd = 17; + optional bool link_remap = 18; + repeated criu_veth_pair veths = 19; /* DEPRECATED, use external instead */ + + optional uint32 cpu_cap = 20 [default = 0xffffffff]; + optional bool force_irmap = 21; + repeated string exec_cmd = 22; + + repeated ext_mount_map ext_mnt = 23; /* DEPRECATED, use external instead */ + optional bool manage_cgroups = 24; /* backward compatibility */ + repeated cgroup_root cg_root = 25; + + optional bool rst_sibling = 26; /* swrk only */ + repeated inherit_fd inherit_fd = 27; /* swrk only */ + + optional bool auto_ext_mnt = 28; + optional bool ext_sharing = 29; + optional bool ext_masters = 30; + + repeated string skip_mnt = 31; + repeated string enable_fs = 32; + + repeated unix_sk unix_sk_ino = 33; /* DEPRECATED, use external instead */ + + optional criu_cg_mode manage_cgroups_mode = 34; + optional uint32 ghost_limit = 35 [default = 0x100000]; + repeated string irmap_scan_paths = 36; + repeated string external = 37; + optional uint32 empty_ns = 38; + repeated join_namespace join_ns = 39; + + optional string cgroup_props = 41; + optional string cgroup_props_file = 42; + repeated string cgroup_dump_controller = 43; + + optional string freeze_cgroup = 44; + optional uint32 timeout = 45; + optional bool tcp_skip_in_flight = 46; + optional bool weak_sysctls = 47; + optional bool lazy_pages = 48; + optional int32 status_fd = 49; + optional bool orphan_pts_master = 50; + optional string config_file = 51; + optional bool tcp_close = 52; + optional string lsm_profile = 53; + optional string tls_cacert = 54; + optional string tls_cacrl = 55; + optional string tls_cert = 56; + optional string tls_key = 57; + optional bool tls = 58; + optional bool tls_no_cn_verify = 59; + optional string cgroup_yard = 60; + optional criu_pre_dump_mode pre_dump_mode = 61 [default = SPLICE]; + optional int32 pidfd_store_sk = 62; + optional string lsm_mount_context = 63; + optional criu_network_lock_method network_lock = 64 [default = IPTABLES]; + optional bool mntns_compat_mode = 65; + optional bool skip_file_rwx_check = 66; + optional bool unprivileged = 67; + optional bool leave_stopped = 69; + optional bool display_stats = 70; + optional bool log_to_stderr = 71; +/* optional bool check_mounts = 128; */ +} + +message criu_dump_resp { + optional bool restored = 1; +} + +message criu_restore_resp { + required int32 pid = 1; +} + +message criu_notify { + optional string script = 1; + optional int32 pid = 2; +} + +enum criu_req_type { + EMPTY = 0; + DUMP = 1; + RESTORE = 2; + CHECK = 3; + PRE_DUMP = 4; + PAGE_SERVER = 5; + + NOTIFY = 6; + + CPUINFO_DUMP = 7; + CPUINFO_CHECK = 8; + + FEATURE_CHECK = 9; + + VERSION = 10; + + WAIT_PID = 11; + PAGE_SERVER_CHLD = 12; + + SINGLE_PRE_DUMP = 13; +} + +/* + * List of features which can queried via + * CRIU_REQ_TYPE__FEATURE_CHECK + */ +message criu_features { + optional bool mem_track = 1; + optional bool lazy_pages = 2; + optional bool pidfd_store = 3; +} + +/* + * Request -- each type corresponds to must-be-there + * request arguments of respective type + */ + +message criu_req { + required criu_req_type type = 1; + + optional criu_opts opts = 2; + optional bool notify_success = 3; + + /* + * When set service won't close the connection but + * will wait for more req-s to appear. Works not + * for all request types. + */ + optional bool keep_open = 4; + /* + * 'features' can be used to query which features + * are supported by the installed criu/kernel + * via RPC. + */ + optional criu_features features = 5; + + /* 'pid' is used for WAIT_PID */ + optional uint32 pid = 6; +} + +/* + * Response -- it states whether the request was served + * and additional request-specific information + */ + +message criu_resp { + required criu_req_type type = 1; + required bool success = 2; + + optional criu_dump_resp dump = 3; + optional criu_restore_resp restore = 4; + optional criu_notify notify = 5; + optional criu_page_server_info ps = 6; + + optional int32 cr_errno = 7; + optional criu_features features = 8; + optional string cr_errmsg = 9; + optional criu_version version = 10; + + optional int32 status = 11; +} + +/* Answer for criu_req_type.VERSION requests */ +message criu_version { + required int32 major_number = 1; + required int32 minor_number = 2; + optional string gitid = 3; + optional int32 sublevel = 4; + optional int32 extra = 5; + optional string name = 6; +} diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/stats/stats.pb.go b/vendor/github.com/checkpoint-restore/go-criu/v7/stats/stats.pb.go new file mode 100644 index 00000000..ffec3809 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/stats/stats.pb.go @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: MIT + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v4.23.4 +// source: stats/stats.proto + +package stats + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This one contains statistics about dump/restore process +type DumpStatsEntry struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FreezingTime *uint32 `protobuf:"varint,1,req,name=freezing_time,json=freezingTime" json:"freezing_time,omitempty"` + FrozenTime *uint32 `protobuf:"varint,2,req,name=frozen_time,json=frozenTime" json:"frozen_time,omitempty"` + MemdumpTime *uint32 `protobuf:"varint,3,req,name=memdump_time,json=memdumpTime" json:"memdump_time,omitempty"` + MemwriteTime *uint32 `protobuf:"varint,4,req,name=memwrite_time,json=memwriteTime" json:"memwrite_time,omitempty"` + PagesScanned *uint64 `protobuf:"varint,5,req,name=pages_scanned,json=pagesScanned" json:"pages_scanned,omitempty"` + PagesSkippedParent *uint64 `protobuf:"varint,6,req,name=pages_skipped_parent,json=pagesSkippedParent" json:"pages_skipped_parent,omitempty"` + PagesWritten *uint64 `protobuf:"varint,7,req,name=pages_written,json=pagesWritten" json:"pages_written,omitempty"` + IrmapResolve *uint32 `protobuf:"varint,8,opt,name=irmap_resolve,json=irmapResolve" json:"irmap_resolve,omitempty"` + PagesLazy *uint64 `protobuf:"varint,9,req,name=pages_lazy,json=pagesLazy" json:"pages_lazy,omitempty"` + PagePipes *uint64 `protobuf:"varint,10,opt,name=page_pipes,json=pagePipes" json:"page_pipes,omitempty"` + PagePipeBufs *uint64 `protobuf:"varint,11,opt,name=page_pipe_bufs,json=pagePipeBufs" json:"page_pipe_bufs,omitempty"` + ShpagesScanned *uint64 `protobuf:"varint,12,opt,name=shpages_scanned,json=shpagesScanned" json:"shpages_scanned,omitempty"` + ShpagesSkippedParent *uint64 `protobuf:"varint,13,opt,name=shpages_skipped_parent,json=shpagesSkippedParent" json:"shpages_skipped_parent,omitempty"` + ShpagesWritten *uint64 `protobuf:"varint,14,opt,name=shpages_written,json=shpagesWritten" json:"shpages_written,omitempty"` +} + +func (x *DumpStatsEntry) Reset() { + *x = DumpStatsEntry{} + if protoimpl.UnsafeEnabled { + mi := &file_stats_stats_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DumpStatsEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DumpStatsEntry) ProtoMessage() {} + +func (x *DumpStatsEntry) ProtoReflect() protoreflect.Message { + mi := &file_stats_stats_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DumpStatsEntry.ProtoReflect.Descriptor instead. +func (*DumpStatsEntry) Descriptor() ([]byte, []int) { + return file_stats_stats_proto_rawDescGZIP(), []int{0} +} + +func (x *DumpStatsEntry) GetFreezingTime() uint32 { + if x != nil && x.FreezingTime != nil { + return *x.FreezingTime + } + return 0 +} + +func (x *DumpStatsEntry) GetFrozenTime() uint32 { + if x != nil && x.FrozenTime != nil { + return *x.FrozenTime + } + return 0 +} + +func (x *DumpStatsEntry) GetMemdumpTime() uint32 { + if x != nil && x.MemdumpTime != nil { + return *x.MemdumpTime + } + return 0 +} + +func (x *DumpStatsEntry) GetMemwriteTime() uint32 { + if x != nil && x.MemwriteTime != nil { + return *x.MemwriteTime + } + return 0 +} + +func (x *DumpStatsEntry) GetPagesScanned() uint64 { + if x != nil && x.PagesScanned != nil { + return *x.PagesScanned + } + return 0 +} + +func (x *DumpStatsEntry) GetPagesSkippedParent() uint64 { + if x != nil && x.PagesSkippedParent != nil { + return *x.PagesSkippedParent + } + return 0 +} + +func (x *DumpStatsEntry) GetPagesWritten() uint64 { + if x != nil && x.PagesWritten != nil { + return *x.PagesWritten + } + return 0 +} + +func (x *DumpStatsEntry) GetIrmapResolve() uint32 { + if x != nil && x.IrmapResolve != nil { + return *x.IrmapResolve + } + return 0 +} + +func (x *DumpStatsEntry) GetPagesLazy() uint64 { + if x != nil && x.PagesLazy != nil { + return *x.PagesLazy + } + return 0 +} + +func (x *DumpStatsEntry) GetPagePipes() uint64 { + if x != nil && x.PagePipes != nil { + return *x.PagePipes + } + return 0 +} + +func (x *DumpStatsEntry) GetPagePipeBufs() uint64 { + if x != nil && x.PagePipeBufs != nil { + return *x.PagePipeBufs + } + return 0 +} + +func (x *DumpStatsEntry) GetShpagesScanned() uint64 { + if x != nil && x.ShpagesScanned != nil { + return *x.ShpagesScanned + } + return 0 +} + +func (x *DumpStatsEntry) GetShpagesSkippedParent() uint64 { + if x != nil && x.ShpagesSkippedParent != nil { + return *x.ShpagesSkippedParent + } + return 0 +} + +func (x *DumpStatsEntry) GetShpagesWritten() uint64 { + if x != nil && x.ShpagesWritten != nil { + return *x.ShpagesWritten + } + return 0 +} + +type RestoreStatsEntry struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PagesCompared *uint64 `protobuf:"varint,1,req,name=pages_compared,json=pagesCompared" json:"pages_compared,omitempty"` + PagesSkippedCow *uint64 `protobuf:"varint,2,req,name=pages_skipped_cow,json=pagesSkippedCow" json:"pages_skipped_cow,omitempty"` + ForkingTime *uint32 `protobuf:"varint,3,req,name=forking_time,json=forkingTime" json:"forking_time,omitempty"` + RestoreTime *uint32 `protobuf:"varint,4,req,name=restore_time,json=restoreTime" json:"restore_time,omitempty"` + PagesRestored *uint64 `protobuf:"varint,5,opt,name=pages_restored,json=pagesRestored" json:"pages_restored,omitempty"` +} + +func (x *RestoreStatsEntry) Reset() { + *x = RestoreStatsEntry{} + if protoimpl.UnsafeEnabled { + mi := &file_stats_stats_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RestoreStatsEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RestoreStatsEntry) ProtoMessage() {} + +func (x *RestoreStatsEntry) ProtoReflect() protoreflect.Message { + mi := &file_stats_stats_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RestoreStatsEntry.ProtoReflect.Descriptor instead. +func (*RestoreStatsEntry) Descriptor() ([]byte, []int) { + return file_stats_stats_proto_rawDescGZIP(), []int{1} +} + +func (x *RestoreStatsEntry) GetPagesCompared() uint64 { + if x != nil && x.PagesCompared != nil { + return *x.PagesCompared + } + return 0 +} + +func (x *RestoreStatsEntry) GetPagesSkippedCow() uint64 { + if x != nil && x.PagesSkippedCow != nil { + return *x.PagesSkippedCow + } + return 0 +} + +func (x *RestoreStatsEntry) GetForkingTime() uint32 { + if x != nil && x.ForkingTime != nil { + return *x.ForkingTime + } + return 0 +} + +func (x *RestoreStatsEntry) GetRestoreTime() uint32 { + if x != nil && x.RestoreTime != nil { + return *x.RestoreTime + } + return 0 +} + +func (x *RestoreStatsEntry) GetPagesRestored() uint64 { + if x != nil && x.PagesRestored != nil { + return *x.PagesRestored + } + return 0 +} + +type StatsEntry struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Dump *DumpStatsEntry `protobuf:"bytes,1,opt,name=dump" json:"dump,omitempty"` + Restore *RestoreStatsEntry `protobuf:"bytes,2,opt,name=restore" json:"restore,omitempty"` +} + +func (x *StatsEntry) Reset() { + *x = StatsEntry{} + if protoimpl.UnsafeEnabled { + mi := &file_stats_stats_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatsEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatsEntry) ProtoMessage() {} + +func (x *StatsEntry) ProtoReflect() protoreflect.Message { + mi := &file_stats_stats_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatsEntry.ProtoReflect.Descriptor instead. +func (*StatsEntry) Descriptor() ([]byte, []int) { + return file_stats_stats_proto_rawDescGZIP(), []int{2} +} + +func (x *StatsEntry) GetDump() *DumpStatsEntry { + if x != nil { + return x.Dump + } + return nil +} + +func (x *StatsEntry) GetRestore() *RestoreStatsEntry { + if x != nil { + return x.Restore + } + return nil +} + +var File_stats_stats_proto protoreflect.FileDescriptor + +var file_stats_stats_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0xad, 0x04, 0x0a, 0x10, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, 0x65, 0x65, + 0x7a, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0d, 0x52, + 0x0c, 0x66, 0x72, 0x65, 0x65, 0x7a, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x02, + 0x28, 0x0d, 0x52, 0x0a, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x6d, 0x65, 0x6d, 0x64, 0x75, 0x6d, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x02, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x64, 0x75, 0x6d, 0x70, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x6d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x04, 0x20, 0x02, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65, 0x6d, 0x77, 0x72, 0x69, + 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x67, 0x65, 0x73, 0x5f, + 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x18, 0x05, 0x20, 0x02, 0x28, 0x04, 0x52, 0x0c, 0x70, + 0x61, 0x67, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x70, + 0x61, 0x67, 0x65, 0x73, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x02, 0x28, 0x04, 0x52, 0x12, 0x70, 0x61, 0x67, 0x65, 0x73, + 0x53, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, + 0x0d, 0x70, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x18, 0x07, + 0x20, 0x02, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x61, 0x67, 0x65, 0x73, 0x57, 0x72, 0x69, 0x74, 0x74, + 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x72, 0x6d, 0x61, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x6f, + 0x6c, 0x76, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x69, 0x72, 0x6d, 0x61, 0x70, + 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x73, + 0x5f, 0x6c, 0x61, 0x7a, 0x79, 0x18, 0x09, 0x20, 0x02, 0x28, 0x04, 0x52, 0x09, 0x70, 0x61, 0x67, + 0x65, 0x73, 0x4c, 0x61, 0x7a, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x70, + 0x69, 0x70, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x50, 0x69, 0x70, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x69, + 0x70, 0x65, 0x5f, 0x62, 0x75, 0x66, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, + 0x61, 0x67, 0x65, 0x50, 0x69, 0x70, 0x65, 0x42, 0x75, 0x66, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x73, + 0x68, 0x70, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x68, 0x70, 0x61, 0x67, 0x65, 0x73, 0x53, 0x63, 0x61, + 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x68, 0x70, 0x61, 0x67, 0x65, 0x73, 0x5f, + 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x73, 0x68, 0x70, 0x61, 0x67, 0x65, 0x73, 0x53, 0x6b, 0x69, + 0x70, 0x70, 0x65, 0x64, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x68, + 0x70, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x68, 0x70, 0x61, 0x67, 0x65, 0x73, 0x57, 0x72, 0x69, 0x74, + 0x74, 0x65, 0x6e, 0x22, 0xd5, 0x01, 0x0a, 0x13, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x70, + 0x61, 0x67, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x02, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x61, 0x67, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, + 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x73, 0x6b, 0x69, 0x70, + 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x02, 0x28, 0x04, 0x52, 0x0f, 0x70, + 0x61, 0x67, 0x65, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x77, 0x12, 0x21, + 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x02, 0x28, 0x0d, 0x52, 0x0b, 0x66, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x02, 0x28, 0x0d, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x72, 0x65, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x61, + 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x22, 0x64, 0x0a, 0x0b, 0x73, + 0x74, 0x61, 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x04, 0x64, 0x75, + 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x75, 0x6d, 0x70, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x64, 0x75, 0x6d, + 0x70, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x73, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, + 0x65, +} + +var ( + file_stats_stats_proto_rawDescOnce sync.Once + file_stats_stats_proto_rawDescData = file_stats_stats_proto_rawDesc +) + +func file_stats_stats_proto_rawDescGZIP() []byte { + file_stats_stats_proto_rawDescOnce.Do(func() { + file_stats_stats_proto_rawDescData = protoimpl.X.CompressGZIP(file_stats_stats_proto_rawDescData) + }) + return file_stats_stats_proto_rawDescData +} + +var file_stats_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_stats_stats_proto_goTypes = []interface{}{ + (*DumpStatsEntry)(nil), // 0: dump_stats_entry + (*RestoreStatsEntry)(nil), // 1: restore_stats_entry + (*StatsEntry)(nil), // 2: stats_entry +} +var file_stats_stats_proto_depIdxs = []int32{ + 0, // 0: stats_entry.dump:type_name -> dump_stats_entry + 1, // 1: stats_entry.restore:type_name -> restore_stats_entry + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_stats_stats_proto_init() } +func file_stats_stats_proto_init() { + if File_stats_stats_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_stats_stats_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DumpStatsEntry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_stats_stats_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RestoreStatsEntry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_stats_stats_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatsEntry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_stats_stats_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_stats_stats_proto_goTypes, + DependencyIndexes: file_stats_stats_proto_depIdxs, + MessageInfos: file_stats_stats_proto_msgTypes, + }.Build() + File_stats_stats_proto = out.File + file_stats_stats_proto_rawDesc = nil + file_stats_stats_proto_goTypes = nil + file_stats_stats_proto_depIdxs = nil +} diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/stats/stats.proto b/vendor/github.com/checkpoint-restore/go-criu/v7/stats/stats.proto new file mode 100644 index 00000000..64e46181 --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/stats/stats.proto @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +syntax = "proto2"; + +// This one contains statistics about dump/restore process +message dump_stats_entry { + required uint32 freezing_time = 1; + required uint32 frozen_time = 2; + required uint32 memdump_time = 3; + required uint32 memwrite_time = 4; + + required uint64 pages_scanned = 5; + required uint64 pages_skipped_parent = 6; + required uint64 pages_written = 7; + + optional uint32 irmap_resolve = 8; + + required uint64 pages_lazy = 9; + optional uint64 page_pipes = 10; + optional uint64 page_pipe_bufs = 11; + + optional uint64 shpages_scanned = 12; + optional uint64 shpages_skipped_parent = 13; + optional uint64 shpages_written = 14; +} + +message restore_stats_entry { + required uint64 pages_compared = 1; + required uint64 pages_skipped_cow = 2; + + required uint32 forking_time = 3; + required uint32 restore_time = 4; + + optional uint64 pages_restored = 5; +} + +message stats_entry { + optional dump_stats_entry dump = 1; + optional restore_stats_entry restore = 2; +} diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/stats/types.go b/vendor/github.com/checkpoint-restore/go-criu/v7/stats/types.go new file mode 100644 index 00000000..69d5b0ce --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/stats/types.go @@ -0,0 +1,14 @@ +package stats + +const ( + StatsDump = "stats-dump" + StatsRestore = "stats-restore" + + ImgServiceMagic = 0x55105940 /* Zlatoust */ + StatsMagic = 0x57093306 /* Ostashkov */ + + PrimaryMagicOffset = 0x0 + SecondaryMagicOffset = 0x4 + SizeOffset = 0x8 + PayloadOffset = 0xC +) diff --git a/vendor/github.com/checkpoint-restore/go-criu/v7/stats/utils.go b/vendor/github.com/checkpoint-restore/go-criu/v7/stats/utils.go new file mode 100644 index 00000000..2b65cc2c --- /dev/null +++ b/vendor/github.com/checkpoint-restore/go-criu/v7/stats/utils.go @@ -0,0 +1,52 @@ +package stats + +import ( + "encoding/binary" + "errors" + "os" + "path/filepath" + + "google.golang.org/protobuf/proto" +) + +func readStatisticsFile(imgDir *os.File, fileName string) (*StatsEntry, error) { + buf, err := os.ReadFile(filepath.Join(imgDir.Name(), fileName)) + if err != nil { + return nil, err + } + + if binary.LittleEndian.Uint32(buf[PrimaryMagicOffset:SecondaryMagicOffset]) != ImgServiceMagic { + return nil, errors.New("primary magic not found") + } + + if binary.LittleEndian.Uint32(buf[SecondaryMagicOffset:SizeOffset]) != StatsMagic { + return nil, errors.New("secondary magic not found") + } + + payloadSize := binary.LittleEndian.Uint32(buf[SizeOffset:PayloadOffset]) + + st := &StatsEntry{} + if err := proto.Unmarshal(buf[PayloadOffset:PayloadOffset+payloadSize], st); err != nil { + return nil, err + } + + return st, nil +} + +func CriuGetDumpStats(imgDir *os.File) (*DumpStatsEntry, error) { + st, err := readStatisticsFile(imgDir, StatsDump) + if err != nil { + return nil, err + } + + return st.GetDump(), nil +} + +func CriuGetRestoreStats(imgDir *os.File) (*RestoreStatsEntry, error) { + st, err := readStatisticsFile(imgDir, StatsRestore) + if err != nil { + return nil, err + } + + return st.GetRestore(), nil +} diff --git a/vendor/github.com/chzyer/readline/.gitignore b/vendor/github.com/chzyer/readline/.gitignore new file mode 100644 index 00000000..a3062bea --- /dev/null +++ b/vendor/github.com/chzyer/readline/.gitignore @@ -0,0 +1 @@ +.vscode/* diff --git a/vendor/github.com/chzyer/readline/.travis.yml b/vendor/github.com/chzyer/readline/.travis.yml new file mode 100644 index 00000000..9c359554 --- /dev/null +++ b/vendor/github.com/chzyer/readline/.travis.yml @@ -0,0 +1,8 @@ +language: go +go: + - 1.x +script: + - GOOS=windows go install github.com/chzyer/readline/example/... + - GOOS=linux go install github.com/chzyer/readline/example/... + - GOOS=darwin go install github.com/chzyer/readline/example/... + - go test -race -v diff --git a/vendor/github.com/chzyer/readline/CHANGELOG.md b/vendor/github.com/chzyer/readline/CHANGELOG.md new file mode 100644 index 00000000..14ff5be1 --- /dev/null +++ b/vendor/github.com/chzyer/readline/CHANGELOG.md @@ -0,0 +1,58 @@ +# ChangeLog + +### 1.4 - 2016-07-25 + +* [#60][60] Support dynamic autocompletion +* Fix ANSI parser on Windows +* Fix wrong column width in complete mode on Windows +* Remove dependent package "golang.org/x/crypto/ssh/terminal" + +### 1.3 - 2016-05-09 + +* [#38][38] add SetChildren for prefix completer interface +* [#42][42] improve multiple lines compatibility +* [#43][43] remove sub-package(runes) for gopkg compatibility +* [#46][46] Auto complete with space prefixed line +* [#48][48] support suspend process (ctrl+Z) +* [#49][49] fix bug that check equals with previous command +* [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty + +### 1.2 - 2016-03-05 + +* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib) +* [#23][23], support stdin remapping +* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM. +* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines. +* Supports performs even stdin/stdout is not a tty. +* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api. +* [#28][28], fixes the history is not working as expected. +* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)` + +### 1.1 - 2015-11-20 + +* [#12][12] Add support for key ``/``/`` +* Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`. +* Bugs fixed for `PrefixCompleter` +* Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience. +* Customable Interrupt/EOF prompt in `Config` +* [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices +* Provides a new password user experience(`readline.ReadPasswordEx()`). + +### 1.0 - 2015-10-14 + +* Initial public release. + +[12]: https://github.com/chzyer/readline/pull/12 +[17]: https://github.com/chzyer/readline/pull/17 +[23]: https://github.com/chzyer/readline/pull/23 +[27]: https://github.com/chzyer/readline/pull/27 +[28]: https://github.com/chzyer/readline/pull/28 +[33]: https://github.com/chzyer/readline/pull/33 +[38]: https://github.com/chzyer/readline/pull/38 +[42]: https://github.com/chzyer/readline/pull/42 +[43]: https://github.com/chzyer/readline/pull/43 +[46]: https://github.com/chzyer/readline/pull/46 +[48]: https://github.com/chzyer/readline/pull/48 +[49]: https://github.com/chzyer/readline/pull/49 +[53]: https://github.com/chzyer/readline/pull/53 +[60]: https://github.com/chzyer/readline/pull/60 diff --git a/vendor/github.com/fvbommel/sortorder/LICENSE b/vendor/github.com/chzyer/readline/LICENSE similarity index 96% rename from vendor/github.com/fvbommel/sortorder/LICENSE rename to vendor/github.com/chzyer/readline/LICENSE index 5c695fb5..c9afab3d 100644 --- a/vendor/github.com/fvbommel/sortorder/LICENSE +++ b/vendor/github.com/chzyer/readline/LICENSE @@ -1,13 +1,17 @@ The MIT License (MIT) -Copyright (c) 2015 Frits van Bommel + +Copyright (c) 2015 Chzyer + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -15,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/github.com/chzyer/readline/README.md b/vendor/github.com/chzyer/readline/README.md new file mode 100644 index 00000000..4b0a5ff5 --- /dev/null +++ b/vendor/github.com/chzyer/readline/README.md @@ -0,0 +1,114 @@ +[![Build Status](https://travis-ci.org/chzyer/readline.svg?branch=master)](https://travis-ci.org/chzyer/readline) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) +[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://github.com/chzyer/readline/releases) +[![GoDoc](https://godoc.org/github.com/chzyer/readline?status.svg)](https://godoc.org/github.com/chzyer/readline) +[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers) +[![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors) + +

+ + + +

+ +A powerful readline library in `Linux` `macOS` `Windows` `Solaris` `AIX` + +## Guide + +* [Demo](example/readline-demo/readline-demo.go) +* [Shortcut](doc/shortcut.md) + +## Repos using readline + +[![cockroachdb](https://img.shields.io/github/stars/cockroachdb/cockroach.svg?label=cockroachdb/cockroach)](https://github.com/cockroachdb/cockroach) +[![robertkrimen/otto](https://img.shields.io/github/stars/robertkrimen/otto.svg?label=robertkrimen/otto)](https://github.com/robertkrimen/otto) +[![empire](https://img.shields.io/github/stars/remind101/empire.svg?label=remind101/empire)](https://github.com/remind101/empire) +[![mehrdadrad/mylg](https://img.shields.io/github/stars/mehrdadrad/mylg.svg?label=mehrdadrad/mylg)](https://github.com/mehrdadrad/mylg) +[![knq/usql](https://img.shields.io/github/stars/knq/usql.svg?label=knq/usql)](https://github.com/knq/usql) +[![youtube/doorman](https://img.shields.io/github/stars/youtube/doorman.svg?label=youtube/doorman)](https://github.com/youtube/doorman) +[![bom-d-van/harp](https://img.shields.io/github/stars/bom-d-van/harp.svg?label=bom-d-van/harp)](https://github.com/bom-d-van/harp) +[![abiosoft/ishell](https://img.shields.io/github/stars/abiosoft/ishell.svg?label=abiosoft/ishell)](https://github.com/abiosoft/ishell) +[![Netflix/hal-9001](https://img.shields.io/github/stars/Netflix/hal-9001.svg?label=Netflix/hal-9001)](https://github.com/Netflix/hal-9001) +[![docker/go-p9p](https://img.shields.io/github/stars/docker/go-p9p.svg?label=docker/go-p9p)](https://github.com/docker/go-p9p) + + +## Feedback + +If you have any questions, please submit a github issue and any pull requests is welcomed :) + +* [https://twitter.com/chzyer](https://twitter.com/chzyer) +* [http://weibo.com/2145262190](http://weibo.com/2145262190) + + +## Backers + +Love Readline? Help me keep it alive by donating funds to cover project expenses!
+[[Become a backer](https://opencollective.com/readline#backer)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Sponsors + +Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/chzyer/readline/ansi_windows.go b/vendor/github.com/chzyer/readline/ansi_windows.go new file mode 100644 index 00000000..63b908c1 --- /dev/null +++ b/vendor/github.com/chzyer/readline/ansi_windows.go @@ -0,0 +1,249 @@ +// +build windows + +package readline + +import ( + "bufio" + "io" + "strconv" + "strings" + "sync" + "unicode/utf8" + "unsafe" +) + +const ( + _ = uint16(0) + COLOR_FBLUE = 0x0001 + COLOR_FGREEN = 0x0002 + COLOR_FRED = 0x0004 + COLOR_FINTENSITY = 0x0008 + + COLOR_BBLUE = 0x0010 + COLOR_BGREEN = 0x0020 + COLOR_BRED = 0x0040 + COLOR_BINTENSITY = 0x0080 + + COMMON_LVB_UNDERSCORE = 0x8000 + COMMON_LVB_BOLD = 0x0007 +) + +var ColorTableFg = []word{ + 0, // 30: Black + COLOR_FRED, // 31: Red + COLOR_FGREEN, // 32: Green + COLOR_FRED | COLOR_FGREEN, // 33: Yellow + COLOR_FBLUE, // 34: Blue + COLOR_FRED | COLOR_FBLUE, // 35: Magenta + COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan + COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White +} + +var ColorTableBg = []word{ + 0, // 40: Black + COLOR_BRED, // 41: Red + COLOR_BGREEN, // 42: Green + COLOR_BRED | COLOR_BGREEN, // 43: Yellow + COLOR_BBLUE, // 44: Blue + COLOR_BRED | COLOR_BBLUE, // 45: Magenta + COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan + COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White +} + +type ANSIWriter struct { + target io.Writer + wg sync.WaitGroup + ctx *ANSIWriterCtx + sync.Mutex +} + +func NewANSIWriter(w io.Writer) *ANSIWriter { + a := &ANSIWriter{ + target: w, + ctx: NewANSIWriterCtx(w), + } + return a +} + +func (a *ANSIWriter) Close() error { + a.wg.Wait() + return nil +} + +type ANSIWriterCtx struct { + isEsc bool + isEscSeq bool + arg []string + target *bufio.Writer + wantFlush bool +} + +func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx { + return &ANSIWriterCtx{ + target: bufio.NewWriter(target), + } +} + +func (a *ANSIWriterCtx) Flush() { + a.target.Flush() +} + +func (a *ANSIWriterCtx) process(r rune) bool { + if a.wantFlush { + if r == 0 || r == CharEsc { + a.wantFlush = false + a.target.Flush() + } + } + if a.isEscSeq { + a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg) + return true + } + + switch r { + case CharEsc: + a.isEsc = true + case '[': + if a.isEsc { + a.arg = nil + a.isEscSeq = true + a.isEsc = false + break + } + fallthrough + default: + a.target.WriteRune(r) + a.wantFlush = true + } + return true +} + +func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool { + arg := *argptr + var err error + + if r >= 'A' && r <= 'D' { + count := short(GetInt(arg, 1)) + info, err := GetConsoleScreenBufferInfo() + if err != nil { + return false + } + switch r { + case 'A': // up + info.dwCursorPosition.y -= count + case 'B': // down + info.dwCursorPosition.y += count + case 'C': // right + info.dwCursorPosition.x += count + case 'D': // left + info.dwCursorPosition.x -= count + } + SetConsoleCursorPosition(&info.dwCursorPosition) + return false + } + + switch r { + case 'J': + killLines() + case 'K': + eraseLine() + case 'm': + color := word(0) + for _, item := range arg { + var c int + c, err = strconv.Atoi(item) + if err != nil { + w.WriteString("[" + strings.Join(arg, ";") + "m") + break + } + if c >= 30 && c < 40 { + color ^= COLOR_FINTENSITY + color |= ColorTableFg[c-30] + } else if c >= 40 && c < 50 { + color ^= COLOR_BINTENSITY + color |= ColorTableBg[c-40] + } else if c == 4 { + color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7] + } else if c == 1 { + color |= COMMON_LVB_BOLD | COLOR_FINTENSITY + } else { // unknown code treat as reset + color = ColorTableFg[7] + } + } + if err != nil { + break + } + kernel.SetConsoleTextAttribute(stdout, uintptr(color)) + case '\007': // set title + case ';': + if len(arg) == 0 || arg[len(arg)-1] != "" { + arg = append(arg, "") + *argptr = arg + } + return true + default: + if len(arg) == 0 { + arg = append(arg, "") + } + arg[len(arg)-1] += string(r) + *argptr = arg + return true + } + *argptr = nil + return false +} + +func (a *ANSIWriter) Write(b []byte) (int, error) { + a.Lock() + defer a.Unlock() + + off := 0 + for len(b) > off { + r, size := utf8.DecodeRune(b[off:]) + if size == 0 { + return off, io.ErrShortWrite + } + off += size + a.ctx.process(r) + } + a.ctx.Flush() + return off, nil +} + +func killLines() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x + size += sbi.dwCursorPosition.x + + var written int + kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} + +func eraseLine() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := sbi.dwSize.x + sbi.dwCursorPosition.x = 0 + var written int + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} diff --git a/vendor/github.com/chzyer/readline/complete.go b/vendor/github.com/chzyer/readline/complete.go new file mode 100644 index 00000000..c08c9941 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete.go @@ -0,0 +1,285 @@ +package readline + +import ( + "bufio" + "bytes" + "fmt" + "io" +) + +type AutoCompleter interface { + // Readline will pass the whole line and current offset to it + // Completer need to pass all the candidates, and how long they shared the same characters in line + // Example: + // [go, git, git-shell, grep] + // Do("g", 1) => ["o", "it", "it-shell", "rep"], 1 + // Do("gi", 2) => ["t", "t-shell"], 2 + // Do("git", 3) => ["", "-shell"], 3 + Do(line []rune, pos int) (newLine [][]rune, length int) +} + +type TabCompleter struct{} + +func (t *TabCompleter) Do([]rune, int) ([][]rune, int) { + return [][]rune{[]rune("\t")}, 0 +} + +type opCompleter struct { + w io.Writer + op *Operation + width int + + inCompleteMode bool + inSelectMode bool + candidate [][]rune + candidateSource []rune + candidateOff int + candidateChoise int + candidateColNum int +} + +func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter { + return &opCompleter{ + w: w, + op: op, + width: width, + } +} + +func (o *opCompleter) doSelect() { + if len(o.candidate) == 1 { + o.op.buf.WriteRunes(o.candidate[0]) + o.ExitCompleteMode(false) + return + } + o.nextCandidate(1) + o.CompleteRefresh() +} + +func (o *opCompleter) nextCandidate(i int) { + o.candidateChoise += i + o.candidateChoise = o.candidateChoise % len(o.candidate) + if o.candidateChoise < 0 { + o.candidateChoise = len(o.candidate) + o.candidateChoise + } +} + +func (o *opCompleter) OnComplete() bool { + if o.width == 0 { + return false + } + if o.IsInCompleteSelectMode() { + o.doSelect() + return true + } + + buf := o.op.buf + rs := buf.Runes() + + if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) { + o.EnterCompleteSelectMode() + o.doSelect() + return true + } + + o.ExitCompleteSelectMode() + o.candidateSource = rs + newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) + if len(newLines) == 0 { + o.ExitCompleteMode(false) + return true + } + + // only Aggregate candidates in non-complete mode + if !o.IsInCompleteMode() { + if len(newLines) == 1 { + buf.WriteRunes(newLines[0]) + o.ExitCompleteMode(false) + return true + } + + same, size := runes.Aggregate(newLines) + if size > 0 { + buf.WriteRunes(same) + o.ExitCompleteMode(false) + return true + } + } + + o.EnterCompleteMode(offset, newLines) + return true +} + +func (o *opCompleter) IsInCompleteSelectMode() bool { + return o.inSelectMode +} + +func (o *opCompleter) IsInCompleteMode() bool { + return o.inCompleteMode +} + +func (o *opCompleter) HandleCompleteSelect(r rune) bool { + next := true + switch r { + case CharEnter, CharCtrlJ: + next = false + o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise]) + o.ExitCompleteMode(false) + case CharLineStart: + num := o.candidateChoise % o.candidateColNum + o.nextCandidate(-num) + case CharLineEnd: + num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1 + o.candidateChoise += num + if o.candidateChoise >= len(o.candidate) { + o.candidateChoise = len(o.candidate) - 1 + } + case CharBackspace: + o.ExitCompleteSelectMode() + next = false + case CharTab, CharForward: + o.doSelect() + case CharBell, CharInterrupt: + o.ExitCompleteMode(true) + next = false + case CharNext: + tmpChoise := o.candidateChoise + o.candidateColNum + if tmpChoise >= o.getMatrixSize() { + tmpChoise -= o.getMatrixSize() + } else if tmpChoise >= len(o.candidate) { + tmpChoise += o.candidateColNum + tmpChoise -= o.getMatrixSize() + } + o.candidateChoise = tmpChoise + case CharBackward: + o.nextCandidate(-1) + case CharPrev: + tmpChoise := o.candidateChoise - o.candidateColNum + if tmpChoise < 0 { + tmpChoise += o.getMatrixSize() + if tmpChoise >= len(o.candidate) { + tmpChoise -= o.candidateColNum + } + } + o.candidateChoise = tmpChoise + default: + next = false + o.ExitCompleteSelectMode() + } + if next { + o.CompleteRefresh() + return true + } + return false +} + +func (o *opCompleter) getMatrixSize() int { + line := len(o.candidate) / o.candidateColNum + if len(o.candidate)%o.candidateColNum != 0 { + line++ + } + return line * o.candidateColNum +} + +func (o *opCompleter) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opCompleter) CompleteRefresh() { + if !o.inCompleteMode { + return + } + lineCnt := o.op.buf.CursorLineCount() + colWidth := 0 + for _, c := range o.candidate { + w := runes.WidthAll(c) + if w > colWidth { + colWidth = w + } + } + colWidth += o.candidateOff + 1 + same := o.op.buf.RuneSlice(-o.candidateOff) + + // -1 to avoid reach the end of line + width := o.width - 1 + colNum := width / colWidth + if colNum != 0 { + colWidth += (width - (colWidth * colNum)) / colNum + } + + o.candidateColNum = colNum + buf := bufio.NewWriter(o.w) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + + colIdx := 0 + lines := 1 + buf.WriteString("\033[J") + for idx, c := range o.candidate { + inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode() + if inSelect { + buf.WriteString("\033[30;47m") + } + buf.WriteString(string(same)) + buf.WriteString(string(c)) + buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same))) + + if inSelect { + buf.WriteString("\033[0m") + } + + colIdx++ + if colIdx == colNum { + buf.WriteString("\n") + lines++ + colIdx = 0 + } + } + + // move back + fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines) + fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen()) + buf.Flush() +} + +func (o *opCompleter) aggCandidate(candidate [][]rune) int { + offset := 0 + for i := 0; i < len(candidate[0]); i++ { + for j := 0; j < len(candidate)-1; j++ { + if i > len(candidate[j]) { + goto aggregate + } + if candidate[j][i] != candidate[j+1][i] { + goto aggregate + } + } + offset = i + } +aggregate: + return offset +} + +func (o *opCompleter) EnterCompleteSelectMode() { + o.inSelectMode = true + o.candidateChoise = -1 + o.CompleteRefresh() +} + +func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) { + o.inCompleteMode = true + o.candidate = candidate + o.candidateOff = offset + o.CompleteRefresh() +} + +func (o *opCompleter) ExitCompleteSelectMode() { + o.inSelectMode = false + o.candidate = nil + o.candidateChoise = -1 + o.candidateOff = -1 + o.candidateSource = nil +} + +func (o *opCompleter) ExitCompleteMode(revent bool) { + o.inCompleteMode = false + o.ExitCompleteSelectMode() +} diff --git a/vendor/github.com/chzyer/readline/complete_helper.go b/vendor/github.com/chzyer/readline/complete_helper.go new file mode 100644 index 00000000..58d72487 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_helper.go @@ -0,0 +1,165 @@ +package readline + +import ( + "bytes" + "strings" +) + +// Caller type for dynamic completion +type DynamicCompleteFunc func(string) []string + +type PrefixCompleterInterface interface { + Print(prefix string, level int, buf *bytes.Buffer) + Do(line []rune, pos int) (newLine [][]rune, length int) + GetName() []rune + GetChildren() []PrefixCompleterInterface + SetChildren(children []PrefixCompleterInterface) +} + +type DynamicPrefixCompleterInterface interface { + PrefixCompleterInterface + IsDynamic() bool + GetDynamicNames(line []rune) [][]rune +} + +type PrefixCompleter struct { + Name []rune + Dynamic bool + Callback DynamicCompleteFunc + Children []PrefixCompleterInterface +} + +func (p *PrefixCompleter) Tree(prefix string) string { + buf := bytes.NewBuffer(nil) + p.Print(prefix, 0, buf) + return buf.String() +} + +func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) { + if strings.TrimSpace(string(p.GetName())) != "" { + buf.WriteString(prefix) + if level > 0 { + buf.WriteString("├") + buf.WriteString(strings.Repeat("─", (level*4)-2)) + buf.WriteString(" ") + } + buf.WriteString(string(p.GetName()) + "\n") + level++ + } + for _, ch := range p.GetChildren() { + ch.Print(prefix, level, buf) + } +} + +func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) { + Print(p, prefix, level, buf) +} + +func (p *PrefixCompleter) IsDynamic() bool { + return p.Dynamic +} + +func (p *PrefixCompleter) GetName() []rune { + return p.Name +} + +func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune { + var names = [][]rune{} + for _, name := range p.Callback(string(line)) { + names = append(names, []rune(name+" ")) + } + return names +} + +func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface { + return p.Children +} + +func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) { + p.Children = children +} + +func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter { + return PcItem("", pc...) +} + +func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter { + name += " " + return &PrefixCompleter{ + Name: []rune(name), + Dynamic: false, + Children: pc, + } +} + +func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter { + return &PrefixCompleter{ + Callback: callback, + Dynamic: true, + Children: pc, + } +} + +func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) { + line = runes.TrimSpaceLeft(line[:pos]) + goNext := false + var lineCompleter PrefixCompleterInterface + for _, child := range p.GetChildren() { + childNames := make([][]rune, 1) + + childDynamic, ok := child.(DynamicPrefixCompleterInterface) + if ok && childDynamic.IsDynamic() { + childNames = childDynamic.GetDynamicNames(origLine) + } else { + childNames[0] = child.GetName() + } + + for _, childName := range childNames { + if len(line) >= len(childName) { + if runes.HasPrefix(line, childName) { + if len(line) == len(childName) { + newLine = append(newLine, []rune{' '}) + } else { + newLine = append(newLine, childName) + } + offset = len(childName) + lineCompleter = child + goNext = true + } + } else { + if runes.HasPrefix(childName, line) { + newLine = append(newLine, childName[len(line):]) + offset = len(line) + lineCompleter = child + } + } + } + } + + if len(newLine) != 1 { + return + } + + tmpLine := make([]rune, 0, len(line)) + for i := offset; i < len(line); i++ { + if line[i] == ' ' { + continue + } + + tmpLine = append(tmpLine, line[i:]...) + return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine) + } + + if goNext { + return doInternal(lineCompleter, nil, 0, origLine) + } + return +} diff --git a/vendor/github.com/chzyer/readline/complete_segment.go b/vendor/github.com/chzyer/readline/complete_segment.go new file mode 100644 index 00000000..5ceadd80 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_segment.go @@ -0,0 +1,82 @@ +package readline + +type SegmentCompleter interface { + // a + // |- a1 + // |--- a11 + // |- a2 + // b + // input: + // DoTree([], 0) [a, b] + // DoTree([a], 1) [a] + // DoTree([a, ], 0) [a1, a2] + // DoTree([a, a], 1) [a1, a2] + // DoTree([a, a1], 2) [a1] + // DoTree([a, a1, ], 0) [a11] + // DoTree([a, a1, a], 1) [a11] + DoSegment([][]rune, int) [][]rune +} + +type dumpSegmentCompleter struct { + f func([][]rune, int) [][]rune +} + +func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune { + return d.f(segment, n) +} + +func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter { + return &SegmentComplete{&dumpSegmentCompleter{f}} +} + +func SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete { + return &SegmentComplete{ + SegmentCompleter: completer, + } +} + +type SegmentComplete struct { + SegmentCompleter +} + +func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) { + ret := make([][]rune, 0, len(cands)) + lastSegment := segments[len(segments)-1] + for _, cand := range cands { + if !runes.HasPrefix(cand, lastSegment) { + continue + } + ret = append(ret, cand[len(lastSegment):]) + } + return ret, idx +} + +func SplitSegment(line []rune, pos int) ([][]rune, int) { + segs := [][]rune{} + lastIdx := -1 + line = line[:pos] + pos = 0 + for idx, l := range line { + if l == ' ' { + pos = 0 + segs = append(segs, line[lastIdx+1:idx]) + lastIdx = idx + } else { + pos++ + } + } + segs = append(segs, line[lastIdx+1:]) + return segs, pos +} + +func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) { + + segment, idx := SplitSegment(line, pos) + + cands := c.DoSegment(segment, idx) + newLine, offset = RetSegment(segment, cands, idx) + for idx := range newLine { + newLine[idx] = append(newLine[idx], ' ') + } + return newLine, offset +} diff --git a/vendor/github.com/chzyer/readline/history.go b/vendor/github.com/chzyer/readline/history.go new file mode 100644 index 00000000..6b17c464 --- /dev/null +++ b/vendor/github.com/chzyer/readline/history.go @@ -0,0 +1,330 @@ +package readline + +import ( + "bufio" + "container/list" + "fmt" + "os" + "strings" + "sync" +) + +type hisItem struct { + Source []rune + Version int64 + Tmp []rune +} + +func (h *hisItem) Clean() { + h.Source = nil + h.Tmp = nil +} + +type opHistory struct { + cfg *Config + history *list.List + historyVer int64 + current *list.Element + fd *os.File + fdLock sync.Mutex + enable bool +} + +func newOpHistory(cfg *Config) (o *opHistory) { + o = &opHistory{ + cfg: cfg, + history: list.New(), + enable: true, + } + return o +} + +func (o *opHistory) Reset() { + o.history = list.New() + o.current = nil +} + +func (o *opHistory) IsHistoryClosed() bool { + o.fdLock.Lock() + defer o.fdLock.Unlock() + return o.fd.Fd() == ^(uintptr(0)) +} + +func (o *opHistory) Init() { + if o.IsHistoryClosed() { + o.initHistory() + } +} + +func (o *opHistory) initHistory() { + if o.cfg.HistoryFile != "" { + o.historyUpdatePath(o.cfg.HistoryFile) + } +} + +// only called by newOpHistory +func (o *opHistory) historyUpdatePath(path string) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + return + } + o.fd = f + r := bufio.NewReader(o.fd) + total := 0 + for ; ; total++ { + line, err := r.ReadString('\n') + if err != nil { + break + } + // ignore the empty line + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + o.Push([]rune(line)) + o.Compact() + } + if total > o.cfg.HistoryLimit { + o.rewriteLocked() + } + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Compact() { + for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 { + o.history.Remove(o.history.Front()) + } +} + +func (o *opHistory) Rewrite() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + o.rewriteLocked() +} + +func (o *opHistory) rewriteLocked() { + if o.cfg.HistoryFile == "" { + return + } + + tmpFile := o.cfg.HistoryFile + ".tmp" + fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666) + if err != nil { + return + } + + buf := bufio.NewWriter(fd) + for elem := o.history.Front(); elem != nil; elem = elem.Next() { + buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n") + } + buf.Flush() + + // replace history file + if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil { + fd.Close() + return + } + + if o.fd != nil { + o.fd.Close() + } + // fd is write only, just satisfy what we need. + o.fd = fd +} + +func (o *opHistory) Close() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + if o.fd != nil { + o.fd.Close() + } +} + +func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Prev() { + item := o.showItem(elem.Value) + if isNewSearch { + start += len(rs) + } + if elem == o.current { + if len(item) >= start { + item = item[:start] + } + } + idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Next() { + item := o.showItem(elem.Value) + if isNewSearch { + start -= len(rs) + if start < 0 { + start = 0 + } + } + if elem == o.current { + if len(item)-1 >= start { + item = item[start:] + } else { + continue + } + } + idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + if elem == o.current { + idx += start + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) showItem(obj interface{}) []rune { + item := obj.(*hisItem) + if item.Version == o.historyVer { + return item.Tmp + } + return item.Source +} + +func (o *opHistory) Prev() []rune { + if o.current == nil { + return nil + } + current := o.current.Prev() + if current == nil { + return nil + } + o.current = current + return runes.Copy(o.showItem(current.Value)) +} + +func (o *opHistory) Next() ([]rune, bool) { + if o.current == nil { + return nil, false + } + current := o.current.Next() + if current == nil { + return nil, false + } + + o.current = current + return runes.Copy(o.showItem(current.Value)), true +} + +// Disable the current history +func (o *opHistory) Disable() { + o.enable = false +} + +// Enable the current history +func (o *opHistory) Enable() { + o.enable = true +} + +func (o *opHistory) debug() { + Debug("-------") + for item := o.history.Front(); item != nil; item = item.Next() { + Debug(fmt.Sprintf("%+v", item.Value)) + } +} + +// save history +func (o *opHistory) New(current []rune) (err error) { + + // history deactivated + if !o.enable { + return nil + } + + current = runes.Copy(current) + + // if just use last command without modify + // just clean lastest history + if back := o.history.Back(); back != nil { + prev := back.Prev() + if prev != nil { + if runes.Equal(current, prev.Value.(*hisItem).Source) { + o.current = o.history.Back() + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + } + + if len(current) == 0 { + o.current = o.history.Back() + if o.current != nil { + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + + if o.current != o.history.Back() { + // move history item to current command + currentItem := o.current.Value.(*hisItem) + // set current to last item + o.current = o.history.Back() + + current = runes.Copy(currentItem.Tmp) + } + + // err only can be a IO error, just report + err = o.Update(current, true) + + // push a new one to commit current command + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Revert() { + o.historyVer++ + o.current = o.history.Back() +} + +func (o *opHistory) Update(s []rune, commit bool) (err error) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + s = runes.Copy(s) + if o.current == nil { + o.Push(s) + o.Compact() + return + } + r := o.current.Value.(*hisItem) + r.Version = o.historyVer + if commit { + r.Source = s + if o.fd != nil { + // just report the error + _, err = o.fd.Write([]byte(string(r.Source) + "\n")) + } + } else { + r.Tmp = append(r.Tmp[:0], s...) + } + o.current.Value = r + o.Compact() + return +} + +func (o *opHistory) Push(s []rune) { + s = runes.Copy(s) + elem := o.history.PushBack(&hisItem{Source: s}) + o.current = elem +} diff --git a/vendor/github.com/chzyer/readline/operation.go b/vendor/github.com/chzyer/readline/operation.go new file mode 100644 index 00000000..b60939a9 --- /dev/null +++ b/vendor/github.com/chzyer/readline/operation.go @@ -0,0 +1,537 @@ +package readline + +import ( + "errors" + "io" + "sync" +) + +var ( + ErrInterrupt = errors.New("Interrupt") +) + +type InterruptError struct { + Line []rune +} + +func (*InterruptError) Error() string { + return "Interrupted" +} + +type Operation struct { + m sync.Mutex + cfg *Config + t *Terminal + buf *RuneBuffer + outchan chan []rune + errchan chan error + w io.Writer + + history *opHistory + *opSearch + *opCompleter + *opPassword + *opVim +} + +func (o *Operation) SetBuffer(what string) { + o.buf.Set([]rune(what)) +} + +type wrapWriter struct { + r *Operation + t *Terminal + target io.Writer +} + +func (w *wrapWriter) Write(b []byte) (int, error) { + if !w.t.IsReading() { + return w.target.Write(b) + } + + var ( + n int + err error + ) + w.r.buf.Refresh(func() { + n, err = w.target.Write(b) + }) + + if w.r.IsSearchMode() { + w.r.SearchRefresh(-1) + } + if w.r.IsInCompleteMode() { + w.r.CompleteRefresh() + } + return n, err +} + +func NewOperation(t *Terminal, cfg *Config) *Operation { + width := cfg.FuncGetWidth() + op := &Operation{ + t: t, + buf: NewRuneBuffer(t, cfg.Prompt, cfg, width), + outchan: make(chan []rune), + errchan: make(chan error, 1), + } + op.w = op.buf.w + op.SetConfig(cfg) + op.opVim = newVimMode(op) + op.opCompleter = newOpCompleter(op.buf.w, op, width) + op.opPassword = newOpPassword(op) + op.cfg.FuncOnWidthChanged(func() { + newWidth := cfg.FuncGetWidth() + op.opCompleter.OnWidthChange(newWidth) + op.opSearch.OnWidthChange(newWidth) + op.buf.OnWidthChange(newWidth) + }) + go op.ioloop() + return op +} + +func (o *Operation) SetPrompt(s string) { + o.buf.SetPrompt(s) +} + +func (o *Operation) SetMaskRune(r rune) { + o.buf.SetMask(r) +} + +func (o *Operation) GetConfig() *Config { + o.m.Lock() + cfg := *o.cfg + o.m.Unlock() + return &cfg +} + +func (o *Operation) ioloop() { + for { + keepInSearchMode := false + keepInCompleteMode := false + r := o.t.ReadRune() + + if o.GetConfig().FuncFilterInputRune != nil { + var process bool + r, process = o.GetConfig().FuncFilterInputRune(r) + if !process { + o.t.KickRead() + o.buf.Refresh(nil) // to refresh the line + continue // ignore this rune + } + } + + if r == 0 { // io.EOF + if o.buf.Len() == 0 { + o.buf.Clean() + select { + case o.errchan <- io.EOF: + } + break + } else { + // if stdin got io.EOF and there is something left in buffer, + // let's flush them by sending CharEnter. + // And we will got io.EOF int next loop. + r = CharEnter + } + } + isUpdateHistory := true + + if o.IsInCompleteSelectMode() { + keepInCompleteMode = o.HandleCompleteSelect(r) + if keepInCompleteMode { + continue + } + + o.buf.Refresh(nil) + switch r { + case CharEnter, CharCtrlJ: + o.history.Update(o.buf.Runes(), false) + fallthrough + case CharInterrupt: + o.t.KickRead() + fallthrough + case CharBell: + continue + } + } + + if o.IsEnableVimMode() { + r = o.HandleVim(r, o.t.ReadRune) + if r == 0 { + continue + } + } + + switch r { + case CharBell: + if o.IsSearchMode() { + o.ExitSearchMode(true) + o.buf.Refresh(nil) + } + if o.IsInCompleteMode() { + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + } + case CharTab: + if o.GetConfig().AutoComplete == nil { + o.t.Bell() + break + } + if o.OnComplete() { + keepInCompleteMode = true + } else { + o.t.Bell() + break + } + + case CharBckSearch: + if !o.SearchMode(S_DIR_BCK) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharCtrlU: + o.buf.KillFront() + case CharFwdSearch: + if !o.SearchMode(S_DIR_FWD) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharKill: + o.buf.Kill() + keepInCompleteMode = true + case MetaForward: + o.buf.MoveToNextWord() + case CharTranspose: + o.buf.Transpose() + case MetaBackward: + o.buf.MoveToPrevWord() + case MetaDelete: + o.buf.DeleteWord() + case CharLineStart: + o.buf.MoveToLineStart() + case CharLineEnd: + o.buf.MoveToLineEnd() + case CharBackspace, CharCtrlH: + if o.IsSearchMode() { + o.SearchBackspace() + keepInSearchMode = true + break + } + + if o.buf.Len() == 0 { + o.t.Bell() + break + } + o.buf.Backspace() + if o.IsInCompleteMode() { + o.OnComplete() + } + case CharCtrlZ: + o.buf.Clean() + o.t.SleepToResume() + o.Refresh() + case CharCtrlL: + ClearScreen(o.w) + o.Refresh() + case MetaBackspace, CharCtrlW: + o.buf.BackEscapeWord() + case CharCtrlY: + o.buf.Yank() + case CharEnter, CharCtrlJ: + if o.IsSearchMode() { + o.ExitSearchMode(false) + } + o.buf.MoveToLineEnd() + var data []rune + if !o.GetConfig().UniqueEditLine { + o.buf.WriteRune('\n') + data = o.buf.Reset() + data = data[:len(data)-1] // trim \n + } else { + o.buf.Clean() + data = o.buf.Reset() + } + o.outchan <- data + if !o.GetConfig().DisableAutoSaveHistory { + // ignore IO error + _ = o.history.New(data) + } else { + isUpdateHistory = false + } + case CharBackward: + o.buf.MoveBackward() + case CharForward: + o.buf.MoveForward() + case CharPrev: + buf := o.history.Prev() + if buf != nil { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharNext: + buf, ok := o.history.Next() + if ok { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharDelete: + if o.buf.Len() > 0 || !o.IsNormalMode() { + o.t.KickRead() + if !o.buf.Delete() { + o.t.Bell() + } + break + } + + // treat as EOF + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(o.GetConfig().EOFPrompt + "\n") + } + o.buf.Reset() + isUpdateHistory = false + o.history.Revert() + o.errchan <- io.EOF + if o.GetConfig().UniqueEditLine { + o.buf.Clean() + } + case CharInterrupt: + if o.IsSearchMode() { + o.t.KickRead() + o.ExitSearchMode(true) + break + } + if o.IsInCompleteMode() { + o.t.KickRead() + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + break + } + o.buf.MoveToLineEnd() + o.buf.Refresh(nil) + hint := o.GetConfig().InterruptPrompt + "\n" + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(hint) + } + remain := o.buf.Reset() + if !o.GetConfig().UniqueEditLine { + remain = remain[:len(remain)-len([]rune(hint))] + } + isUpdateHistory = false + o.history.Revert() + o.errchan <- &InterruptError{remain} + default: + if o.IsSearchMode() { + o.SearchChar(r) + keepInSearchMode = true + break + } + o.buf.WriteRune(r) + if o.IsInCompleteMode() { + o.OnComplete() + keepInCompleteMode = true + } + } + + listener := o.GetConfig().Listener + if listener != nil { + newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r) + if ok { + o.buf.SetWithIdx(newPos, newLine) + } + } + + o.m.Lock() + if !keepInSearchMode && o.IsSearchMode() { + o.ExitSearchMode(false) + o.buf.Refresh(nil) + } else if o.IsInCompleteMode() { + if !keepInCompleteMode { + o.ExitCompleteMode(false) + o.Refresh() + } else { + o.buf.Refresh(nil) + o.CompleteRefresh() + } + } + if isUpdateHistory && !o.IsSearchMode() { + // it will cause null history + o.history.Update(o.buf.Runes(), false) + } + o.m.Unlock() + } +} + +func (o *Operation) Stderr() io.Writer { + return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t} +} + +func (o *Operation) Stdout() io.Writer { + return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t} +} + +func (o *Operation) String() (string, error) { + r, err := o.Runes() + return string(r), err +} + +func (o *Operation) Runes() ([]rune, error) { + o.t.EnterRawMode() + defer o.t.ExitRawMode() + + listener := o.GetConfig().Listener + if listener != nil { + listener.OnChange(nil, 0, 0) + } + + o.buf.Refresh(nil) // print prompt + o.t.KickRead() + select { + case r := <-o.outchan: + return r, nil + case err := <-o.errchan: + if e, ok := err.(*InterruptError); ok { + return e.Line, ErrInterrupt + } + return nil, err + } +} + +func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) { + cfg := o.GenPasswordConfig() + cfg.Prompt = prompt + cfg.Listener = l + return o.PasswordWithConfig(cfg) +} + +func (o *Operation) GenPasswordConfig() *Config { + return o.opPassword.PasswordConfig() +} + +func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) { + if err := o.opPassword.EnterPasswordMode(cfg); err != nil { + return nil, err + } + defer o.opPassword.ExitPasswordMode() + return o.Slice() +} + +func (o *Operation) Password(prompt string) ([]byte, error) { + return o.PasswordEx(prompt, nil) +} + +func (o *Operation) SetTitle(t string) { + o.w.Write([]byte("\033[2;" + t + "\007")) +} + +func (o *Operation) Slice() ([]byte, error) { + r, err := o.Runes() + if err != nil { + return nil, err + } + return []byte(string(r)), nil +} + +func (o *Operation) Close() { + select { + case o.errchan <- io.EOF: + default: + } + o.history.Close() +} + +func (o *Operation) SetHistoryPath(path string) { + if o.history != nil { + o.history.Close() + } + o.cfg.HistoryFile = path + o.history = newOpHistory(o.cfg) +} + +func (o *Operation) IsNormalMode() bool { + return !o.IsInCompleteMode() && !o.IsSearchMode() +} + +func (op *Operation) SetConfig(cfg *Config) (*Config, error) { + op.m.Lock() + defer op.m.Unlock() + if op.cfg == cfg { + return op.cfg, nil + } + if err := cfg.Init(); err != nil { + return op.cfg, err + } + old := op.cfg + op.cfg = cfg + op.SetPrompt(cfg.Prompt) + op.SetMaskRune(cfg.MaskRune) + op.buf.SetConfig(cfg) + width := op.cfg.FuncGetWidth() + + if cfg.opHistory == nil { + op.SetHistoryPath(cfg.HistoryFile) + cfg.opHistory = op.history + cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width) + } + op.history = cfg.opHistory + + // SetHistoryPath will close opHistory which already exists + // so if we use it next time, we need to reopen it by `InitHistory()` + op.history.Init() + + if op.cfg.AutoComplete != nil { + op.opCompleter = newOpCompleter(op.buf.w, op, width) + } + + op.opSearch = cfg.opSearch + return old, nil +} + +func (o *Operation) ResetHistory() { + o.history.Reset() +} + +// if err is not nil, it just mean it fail to write to file +// other things goes fine. +func (o *Operation) SaveHistory(content string) error { + return o.history.New([]rune(content)) +} + +func (o *Operation) Refresh() { + if o.t.IsReading() { + o.buf.Refresh(nil) + } +} + +func (o *Operation) Clean() { + o.buf.Clean() +} + +func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener { + return &DumpListener{f: f} +} + +type DumpListener struct { + f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { + return d.f(line, pos, key) +} + +type Listener interface { + OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +type Painter interface { + Paint(line []rune, pos int) []rune +} + +type defaultPainter struct{} + +func (p *defaultPainter) Paint(line []rune, _ int) []rune { + return line +} diff --git a/vendor/github.com/chzyer/readline/password.go b/vendor/github.com/chzyer/readline/password.go new file mode 100644 index 00000000..414288c2 --- /dev/null +++ b/vendor/github.com/chzyer/readline/password.go @@ -0,0 +1,33 @@ +package readline + +type opPassword struct { + o *Operation + backupCfg *Config +} + +func newOpPassword(o *Operation) *opPassword { + return &opPassword{o: o} +} + +func (o *opPassword) ExitPasswordMode() { + o.o.SetConfig(o.backupCfg) + o.backupCfg = nil +} + +func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) { + o.backupCfg, err = o.o.SetConfig(cfg) + return +} + +func (o *opPassword) PasswordConfig() *Config { + return &Config{ + EnableMask: true, + InterruptPrompt: "\n", + EOFPrompt: "\n", + HistoryLimit: -1, + Painter: &defaultPainter{}, + + Stdout: o.o.cfg.Stdout, + Stderr: o.o.cfg.Stderr, + } +} diff --git a/vendor/github.com/chzyer/readline/rawreader_windows.go b/vendor/github.com/chzyer/readline/rawreader_windows.go new file mode 100644 index 00000000..073ef150 --- /dev/null +++ b/vendor/github.com/chzyer/readline/rawreader_windows.go @@ -0,0 +1,125 @@ +// +build windows + +package readline + +import "unsafe" + +const ( + VK_CANCEL = 0x03 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_ESCAPE = 0x1B + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_DELETE = 0x2E + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 +) + +// RawReader translate input record to ANSI escape sequence. +// To provides same behavior as unix terminal. +type RawReader struct { + ctrlKey bool + altKey bool +} + +func NewRawReader() *RawReader { + r := new(RawReader) + return r +} + +// only process one action in one read +func (r *RawReader) Read(buf []byte) (int, error) { + ir := new(_INPUT_RECORD) + var read int + var err error +next: + err = kernel.ReadConsoleInputW(stdin, + uintptr(unsafe.Pointer(ir)), + 1, + uintptr(unsafe.Pointer(&read)), + ) + if err != nil { + return 0, err + } + if ir.EventType != EVENT_KEY { + goto next + } + ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0])) + if ker.bKeyDown == 0 { // keyup + if r.ctrlKey || r.altKey { + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = false + case VK_MENU: //alt + r.altKey = false + } + } + goto next + } + + if ker.unicodeChar == 0 { + var target rune + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = true + case VK_MENU: //alt + r.altKey = true + case VK_LEFT: + target = CharBackward + case VK_RIGHT: + target = CharForward + case VK_UP: + target = CharPrev + case VK_DOWN: + target = CharNext + } + if target != 0 { + return r.write(buf, target) + } + goto next + } + char := rune(ker.unicodeChar) + if r.ctrlKey { + switch char { + case 'A': + char = CharLineStart + case 'E': + char = CharLineEnd + case 'R': + char = CharBckSearch + case 'S': + char = CharFwdSearch + } + } else if r.altKey { + switch char { + case VK_BACK: + char = CharBackspace + } + return r.writeEsc(buf, char) + } + return r.write(buf, char) +} + +func (r *RawReader) writeEsc(b []byte, char rune) (int, error) { + b[0] = '\033' + n := copy(b[1:], []byte(string(char))) + return n + 1, nil +} + +func (r *RawReader) write(b []byte, char rune) (int, error) { + n := copy(b, []byte(string(char))) + return n, nil +} + +func (r *RawReader) Close() error { + return nil +} diff --git a/vendor/github.com/chzyer/readline/readline.go b/vendor/github.com/chzyer/readline/readline.go new file mode 100644 index 00000000..63b91710 --- /dev/null +++ b/vendor/github.com/chzyer/readline/readline.go @@ -0,0 +1,338 @@ +// Readline is a pure go implementation for GNU-Readline kind library. +// +// example: +// rl, err := readline.New("> ") +// if err != nil { +// panic(err) +// } +// defer rl.Close() +// +// for { +// line, err := rl.Readline() +// if err != nil { // io.EOF +// break +// } +// println(line) +// } +// +package readline + +import ( + "io" +) + +type Instance struct { + Config *Config + Terminal *Terminal + Operation *Operation +} + +type Config struct { + // prompt supports ANSI escape sequence, so we can color some characters even in windows + Prompt string + + // readline will persist historys to file where HistoryFile specified + HistoryFile string + // specify the max length of historys, it's 500 by default, set it to -1 to disable history + HistoryLimit int + DisableAutoSaveHistory bool + // enable case-insensitive history searching + HistorySearchFold bool + + // AutoCompleter will called once user press TAB + AutoComplete AutoCompleter + + // Any key press will pass to Listener + // NOTE: Listener will be triggered by (nil, 0, 0) immediately + Listener Listener + + Painter Painter + + // If VimMode is true, readline will in vim.insert mode by default + VimMode bool + + InterruptPrompt string + EOFPrompt string + + FuncGetWidth func() int + + Stdin io.ReadCloser + StdinWriter io.Writer + Stdout io.Writer + Stderr io.Writer + + EnableMask bool + MaskRune rune + + // erase the editing line after user submited it + // it use in IM usually. + UniqueEditLine bool + + // filter input runes (may be used to disable CtrlZ or for translating some keys to different actions) + // -> output = new (translated) rune and true/false if continue with processing this one + FuncFilterInputRune func(rune) (rune, bool) + + // force use interactive even stdout is not a tty + FuncIsTerminal func() bool + FuncMakeRaw func() error + FuncExitRaw func() error + FuncOnWidthChanged func(func()) + ForceUseInteractive bool + + // private fields + inited bool + opHistory *opHistory + opSearch *opSearch +} + +func (c *Config) useInteractive() bool { + if c.ForceUseInteractive { + return true + } + return c.FuncIsTerminal() +} + +func (c *Config) Init() error { + if c.inited { + return nil + } + c.inited = true + if c.Stdin == nil { + c.Stdin = NewCancelableStdin(Stdin) + } + + c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin) + + if c.Stdout == nil { + c.Stdout = Stdout + } + if c.Stderr == nil { + c.Stderr = Stderr + } + if c.HistoryLimit == 0 { + c.HistoryLimit = 500 + } + + if c.InterruptPrompt == "" { + c.InterruptPrompt = "^C" + } else if c.InterruptPrompt == "\n" { + c.InterruptPrompt = "" + } + if c.EOFPrompt == "" { + c.EOFPrompt = "^D" + } else if c.EOFPrompt == "\n" { + c.EOFPrompt = "" + } + + if c.AutoComplete == nil { + c.AutoComplete = &TabCompleter{} + } + if c.FuncGetWidth == nil { + c.FuncGetWidth = GetScreenWidth + } + if c.FuncIsTerminal == nil { + c.FuncIsTerminal = DefaultIsTerminal + } + rm := new(RawMode) + if c.FuncMakeRaw == nil { + c.FuncMakeRaw = rm.Enter + } + if c.FuncExitRaw == nil { + c.FuncExitRaw = rm.Exit + } + if c.FuncOnWidthChanged == nil { + c.FuncOnWidthChanged = DefaultOnWidthChanged + } + + return nil +} + +func (c Config) Clone() *Config { + c.opHistory = nil + c.opSearch = nil + return &c +} + +func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) { + c.Listener = FuncListener(f) +} + +func (c *Config) SetPainter(p Painter) { + c.Painter = p +} + +func NewEx(cfg *Config) (*Instance, error) { + t, err := NewTerminal(cfg) + if err != nil { + return nil, err + } + rl := t.Readline() + if cfg.Painter == nil { + cfg.Painter = &defaultPainter{} + } + return &Instance{ + Config: cfg, + Terminal: t, + Operation: rl, + }, nil +} + +func New(prompt string) (*Instance, error) { + return NewEx(&Config{Prompt: prompt}) +} + +func (i *Instance) ResetHistory() { + i.Operation.ResetHistory() +} + +func (i *Instance) SetPrompt(s string) { + i.Operation.SetPrompt(s) +} + +func (i *Instance) SetMaskRune(r rune) { + i.Operation.SetMaskRune(r) +} + +// change history persistence in runtime +func (i *Instance) SetHistoryPath(p string) { + i.Operation.SetHistoryPath(p) +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stdout() io.Writer { + return i.Operation.Stdout() +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stderr() io.Writer { + return i.Operation.Stderr() +} + +// switch VimMode in runtime +func (i *Instance) SetVimMode(on bool) { + i.Operation.SetVimMode(on) +} + +func (i *Instance) IsVimMode() bool { + return i.Operation.IsEnableVimMode() +} + +func (i *Instance) GenPasswordConfig() *Config { + return i.Operation.GenPasswordConfig() +} + +// we can generate a config by `i.GenPasswordConfig()` +func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) { + return i.Operation.PasswordWithConfig(cfg) +} + +func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) { + return i.Operation.PasswordEx(prompt, l) +} + +func (i *Instance) ReadPassword(prompt string) ([]byte, error) { + return i.Operation.Password(prompt) +} + +type Result struct { + Line string + Error error +} + +func (l *Result) CanContinue() bool { + return len(l.Line) != 0 && l.Error == ErrInterrupt +} + +func (l *Result) CanBreak() bool { + return !l.CanContinue() && l.Error != nil +} + +func (i *Instance) Line() *Result { + ret, err := i.Readline() + return &Result{ret, err} +} + +// err is one of (nil, io.EOF, readline.ErrInterrupt) +func (i *Instance) Readline() (string, error) { + return i.Operation.String() +} + +func (i *Instance) ReadlineWithDefault(what string) (string, error) { + i.Operation.SetBuffer(what) + return i.Operation.String() +} + +func (i *Instance) SaveHistory(content string) error { + return i.Operation.SaveHistory(content) +} + +// same as readline +func (i *Instance) ReadSlice() ([]byte, error) { + return i.Operation.Slice() +} + +// we must make sure that call Close() before process exit. +// if there has a pending reading operation, that reading will be interrupted. +// so you can capture the signal and call Instance.Close(), it's thread-safe. +func (i *Instance) Close() error { + i.Config.Stdin.Close() + i.Operation.Close() + if err := i.Terminal.Close(); err != nil { + return err + } + return nil +} + +// call CaptureExitSignal when you want readline exit gracefully. +func (i *Instance) CaptureExitSignal() { + CaptureExitSignal(func() { + i.Close() + }) +} + +func (i *Instance) Clean() { + i.Operation.Clean() +} + +func (i *Instance) Write(b []byte) (int, error) { + return i.Stdout().Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +// ie : +// i := readline.New() +// i.WriteStdin([]byte("test")) +// _, _= i.Readline() +// +// gives +// +// > test[cursor] +func (i *Instance) WriteStdin(val []byte) (int, error) { + return i.Terminal.WriteStdin(val) +} + +func (i *Instance) SetConfig(cfg *Config) *Config { + if i.Config == cfg { + return cfg + } + old := i.Config + i.Config = cfg + i.Operation.SetConfig(cfg) + i.Terminal.SetConfig(cfg) + return old +} + +func (i *Instance) Refresh() { + i.Operation.Refresh() +} + +// HistoryDisable the save of the commands into the history +func (i *Instance) HistoryDisable() { + i.Operation.history.Disable() +} + +// HistoryEnable the save of the commands into the history (default on) +func (i *Instance) HistoryEnable() { + i.Operation.history.Enable() +} diff --git a/vendor/github.com/chzyer/readline/remote.go b/vendor/github.com/chzyer/readline/remote.go new file mode 100644 index 00000000..74dbf569 --- /dev/null +++ b/vendor/github.com/chzyer/readline/remote.go @@ -0,0 +1,475 @@ +package readline + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "os" + "sync" + "sync/atomic" +) + +type MsgType int16 + +const ( + T_DATA = MsgType(iota) + T_WIDTH + T_WIDTH_REPORT + T_ISTTY_REPORT + T_RAW + T_ERAW // exit raw + T_EOF +) + +type RemoteSvr struct { + eof int32 + closed int32 + width int32 + reciveChan chan struct{} + writeChan chan *writeCtx + conn net.Conn + isTerminal bool + funcWidthChan func() + stopChan chan struct{} + + dataBufM sync.Mutex + dataBuf bytes.Buffer +} + +type writeReply struct { + n int + err error +} + +type writeCtx struct { + msg *Message + reply chan *writeReply +} + +func newWriteCtx(msg *Message) *writeCtx { + return &writeCtx{ + msg: msg, + reply: make(chan *writeReply), + } +} + +func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) { + rs := &RemoteSvr{ + width: -1, + conn: conn, + writeChan: make(chan *writeCtx), + reciveChan: make(chan struct{}), + stopChan: make(chan struct{}), + } + buf := bufio.NewReader(rs.conn) + + if err := rs.init(buf); err != nil { + return nil, err + } + + go rs.readLoop(buf) + go rs.writeLoop() + return rs, nil +} + +func (r *RemoteSvr) init(buf *bufio.Reader) error { + m, err := ReadMessage(buf) + if err != nil { + return err + } + // receive isTerminal + if m.Type != T_ISTTY_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotIsTerminal(m.Data) + + // receive width + m, err = ReadMessage(buf) + if err != nil { + return err + } + if m.Type != T_WIDTH_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotReportWidth(m.Data) + + return nil +} + +func (r *RemoteSvr) HandleConfig(cfg *Config) { + cfg.Stderr = r + cfg.Stdout = r + cfg.Stdin = r + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncIsTerminal = r.IsTerminal + cfg.FuncMakeRaw = r.EnterRawMode + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncGetWidth = r.GetWidth + cfg.FuncOnWidthChanged = func(f func()) { + r.funcWidthChan = f + } +} + +func (r *RemoteSvr) IsTerminal() bool { + return r.isTerminal +} + +func (r *RemoteSvr) checkEOF() error { + if atomic.LoadInt32(&r.eof) == 1 { + return io.EOF + } + return nil +} + +func (r *RemoteSvr) Read(b []byte) (int, error) { + r.dataBufM.Lock() + n, err := r.dataBuf.Read(b) + r.dataBufM.Unlock() + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + if n == 0 && err == io.EOF { + <-r.reciveChan + r.dataBufM.Lock() + n, err = r.dataBuf.Read(b) + r.dataBufM.Unlock() + } + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + return n, err +} + +func (r *RemoteSvr) writeMsg(m *Message) error { + ctx := newWriteCtx(m) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.err +} + +func (r *RemoteSvr) Write(b []byte) (int, error) { + ctx := newWriteCtx(NewMessage(T_DATA, b)) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.n, reply.err +} + +func (r *RemoteSvr) EnterRawMode() error { + return r.writeMsg(NewMessage(T_RAW, nil)) +} + +func (r *RemoteSvr) ExitRawMode() error { + return r.writeMsg(NewMessage(T_ERAW, nil)) +} + +func (r *RemoteSvr) writeLoop() { + defer r.Close() + +loop: + for { + select { + case ctx, ok := <-r.writeChan: + if !ok { + break + } + n, err := ctx.msg.WriteTo(r.conn) + ctx.reply <- &writeReply{n, err} + case <-r.stopChan: + break loop + } + } +} + +func (r *RemoteSvr) Close() error { + if atomic.CompareAndSwapInt32(&r.closed, 0, 1) { + close(r.stopChan) + r.conn.Close() + } + return nil +} + +func (r *RemoteSvr) readLoop(buf *bufio.Reader) { + defer r.Close() + for { + m, err := ReadMessage(buf) + if err != nil { + break + } + switch m.Type { + case T_EOF: + atomic.StoreInt32(&r.eof, 1) + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_DATA: + r.dataBufM.Lock() + r.dataBuf.Write(m.Data) + r.dataBufM.Unlock() + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_WIDTH_REPORT: + r.GotReportWidth(m.Data) + case T_ISTTY_REPORT: + r.GotIsTerminal(m.Data) + } + } +} + +func (r *RemoteSvr) GotIsTerminal(data []byte) { + if binary.BigEndian.Uint16(data) == 0 { + r.isTerminal = false + } else { + r.isTerminal = true + } +} + +func (r *RemoteSvr) GotReportWidth(data []byte) { + atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data))) + if r.funcWidthChan != nil { + r.funcWidthChan() + } +} + +func (r *RemoteSvr) GetWidth() int { + return int(atomic.LoadInt32(&r.width)) +} + +// ----------------------------------------------------------------------------- + +type Message struct { + Type MsgType + Data []byte +} + +func ReadMessage(r io.Reader) (*Message, error) { + m := new(Message) + var length int32 + if err := binary.Read(r, binary.BigEndian, &length); err != nil { + return nil, err + } + if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil { + return nil, err + } + m.Data = make([]byte, int(length)-2) + if _, err := io.ReadFull(r, m.Data); err != nil { + return nil, err + } + return m, nil +} + +func NewMessage(t MsgType, data []byte) *Message { + return &Message{t, data} +} + +func (m *Message) WriteTo(w io.Writer) (int, error) { + buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4)) + binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2)) + binary.Write(buf, binary.BigEndian, m.Type) + buf.Write(m.Data) + n, err := buf.WriteTo(w) + return int(n), err +} + +// ----------------------------------------------------------------------------- + +type RemoteCli struct { + conn net.Conn + raw RawMode + receiveChan chan struct{} + inited int32 + isTerminal *bool + + data bytes.Buffer + dataM sync.Mutex +} + +func NewRemoteCli(conn net.Conn) (*RemoteCli, error) { + r := &RemoteCli{ + conn: conn, + receiveChan: make(chan struct{}), + } + return r, nil +} + +func (r *RemoteCli) MarkIsTerminal(is bool) { + r.isTerminal = &is +} + +func (r *RemoteCli) init() error { + if !atomic.CompareAndSwapInt32(&r.inited, 0, 1) { + return nil + } + + if err := r.reportIsTerminal(); err != nil { + return err + } + + if err := r.reportWidth(); err != nil { + return err + } + + // register sig for width changed + DefaultOnWidthChanged(func() { + r.reportWidth() + }) + return nil +} + +func (r *RemoteCli) writeMsg(m *Message) error { + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return err +} + +func (r *RemoteCli) Write(b []byte) (int, error) { + m := NewMessage(T_DATA, b) + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return len(b), err +} + +func (r *RemoteCli) reportWidth() error { + screenWidth := GetScreenWidth() + data := make([]byte, 2) + binary.BigEndian.PutUint16(data, uint16(screenWidth)) + msg := NewMessage(T_WIDTH_REPORT, data) + + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) reportIsTerminal() error { + var isTerminal bool + if r.isTerminal != nil { + isTerminal = *r.isTerminal + } else { + isTerminal = DefaultIsTerminal() + } + data := make([]byte, 2) + if isTerminal { + binary.BigEndian.PutUint16(data, 1) + } else { + binary.BigEndian.PutUint16(data, 0) + } + msg := NewMessage(T_ISTTY_REPORT, data) + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) readLoop() { + buf := bufio.NewReader(r.conn) + for { + msg, err := ReadMessage(buf) + if err != nil { + break + } + switch msg.Type { + case T_ERAW: + r.raw.Exit() + case T_RAW: + r.raw.Enter() + case T_DATA: + os.Stdout.Write(msg.Data) + } + } +} + +func (r *RemoteCli) ServeBy(source io.Reader) error { + if err := r.init(); err != nil { + return err + } + + go func() { + defer r.Close() + for { + n, _ := io.Copy(r, source) + if n == 0 { + break + } + } + }() + defer r.raw.Exit() + r.readLoop() + return nil +} + +func (r *RemoteCli) Close() { + r.writeMsg(NewMessage(T_EOF, nil)) +} + +func (r *RemoteCli) Serve() error { + return r.ServeBy(os.Stdin) +} + +func ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error { + ln, err := net.Listen(n, addr) + if err != nil { + return err + } + if len(onListen) > 0 { + if err := onListen[0](ln); err != nil { + return err + } + } + for { + conn, err := ln.Accept() + if err != nil { + break + } + go func() { + defer conn.Close() + rl, err := HandleConn(*cfg, conn) + if err != nil { + return + } + h(rl) + }() + } + return nil +} + +func HandleConn(cfg Config, conn net.Conn) (*Instance, error) { + r, err := NewRemoteSvr(conn) + if err != nil { + return nil, err + } + r.HandleConfig(&cfg) + + rl, err := NewEx(&cfg) + if err != nil { + return nil, err + } + return rl, nil +} + +func DialRemote(n, addr string) error { + conn, err := net.Dial(n, addr) + if err != nil { + return err + } + defer conn.Close() + + cli, err := NewRemoteCli(conn) + if err != nil { + return err + } + return cli.Serve() +} diff --git a/vendor/github.com/chzyer/readline/runebuf.go b/vendor/github.com/chzyer/readline/runebuf.go new file mode 100644 index 00000000..d95df1e3 --- /dev/null +++ b/vendor/github.com/chzyer/readline/runebuf.go @@ -0,0 +1,629 @@ +package readline + +import ( + "bufio" + "bytes" + "io" + "strconv" + "strings" + "sync" +) + +type runeBufferBck struct { + buf []rune + idx int +} + +type RuneBuffer struct { + buf []rune + idx int + prompt []rune + w io.Writer + + hadClean bool + interactive bool + cfg *Config + + width int + + bck *runeBufferBck + + offset string + + lastKill []rune + + sync.Mutex +} + +func (r *RuneBuffer) pushKill(text []rune) { + r.lastKill = append([]rune{}, text...) +} + +func (r *RuneBuffer) OnWidthChange(newWidth int) { + r.Lock() + r.width = newWidth + r.Unlock() +} + +func (r *RuneBuffer) Backup() { + r.Lock() + r.bck = &runeBufferBck{r.buf, r.idx} + r.Unlock() +} + +func (r *RuneBuffer) Restore() { + r.Refresh(func() { + if r.bck == nil { + return + } + r.buf = r.bck.buf + r.idx = r.bck.idx + }) +} + +func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer { + rb := &RuneBuffer{ + w: w, + interactive: cfg.useInteractive(), + cfg: cfg, + width: width, + } + rb.SetPrompt(prompt) + return rb +} + +func (r *RuneBuffer) SetConfig(cfg *Config) { + r.Lock() + r.cfg = cfg + r.interactive = cfg.useInteractive() + r.Unlock() +} + +func (r *RuneBuffer) SetMask(m rune) { + r.Lock() + r.cfg.MaskRune = m + r.Unlock() +} + +func (r *RuneBuffer) CurrentWidth(x int) int { + r.Lock() + defer r.Unlock() + return runes.WidthAll(r.buf[:x]) +} + +func (r *RuneBuffer) PromptLen() int { + r.Lock() + width := r.promptLen() + r.Unlock() + return width +} + +func (r *RuneBuffer) promptLen() int { + return runes.WidthAll(runes.ColorFilter(r.prompt)) +} + +func (r *RuneBuffer) RuneSlice(i int) []rune { + r.Lock() + defer r.Unlock() + + if i > 0 { + rs := make([]rune, i) + copy(rs, r.buf[r.idx:r.idx+i]) + return rs + } + rs := make([]rune, -i) + copy(rs, r.buf[r.idx+i:r.idx]) + return rs +} + +func (r *RuneBuffer) Runes() []rune { + r.Lock() + newr := make([]rune, len(r.buf)) + copy(newr, r.buf) + r.Unlock() + return newr +} + +func (r *RuneBuffer) Pos() int { + r.Lock() + defer r.Unlock() + return r.idx +} + +func (r *RuneBuffer) Len() int { + r.Lock() + defer r.Unlock() + return len(r.buf) +} + +func (r *RuneBuffer) MoveToLineStart() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx = 0 + }) +} + +func (r *RuneBuffer) MoveBackward() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx-- + }) +} + +func (r *RuneBuffer) WriteString(s string) { + r.WriteRunes([]rune(s)) +} + +func (r *RuneBuffer) WriteRune(s rune) { + r.WriteRunes([]rune{s}) +} + +func (r *RuneBuffer) WriteRunes(s []rune) { + r.Refresh(func() { + tail := append(s, r.buf[r.idx:]...) + r.buf = append(r.buf[:r.idx], tail...) + r.idx += len(s) + }) +} + +func (r *RuneBuffer) MoveForward() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.idx++ + }) +} + +func (r *RuneBuffer) IsCursorInEnd() bool { + r.Lock() + defer r.Unlock() + return r.idx == len(r.buf) +} + +func (r *RuneBuffer) Replace(ch rune) { + r.Refresh(func() { + r.buf[r.idx] = ch + }) +} + +func (r *RuneBuffer) Erase() { + r.Refresh(func() { + r.idx = 0 + r.pushKill(r.buf[:]) + r.buf = r.buf[:0] + }) +} + +func (r *RuneBuffer) Delete() (success bool) { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.pushKill(r.buf[r.idx : r.idx+1]) + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + success = true + }) + return +} + +func (r *RuneBuffer) DeleteWord() { + if r.idx == len(r.buf) { + return + } + init := r.idx + for init < len(r.buf) && IsWordBreak(r.buf[init]) { + init++ + } + for i := init + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[r.idx : i-1]) + r.Refresh(func() { + r.buf = append(r.buf[:r.idx], r.buf[i-1:]...) + }) + return + } + } + r.Kill() +} + +func (r *RuneBuffer) MoveToPrevWord() (success bool) { + r.Refresh(func() { + if r.idx == 0 { + return + } + + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + success = true + return + } + } + r.idx = 0 + success = true + }) + return +} + +func (r *RuneBuffer) KillFront() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + length := len(r.buf) - r.idx + r.pushKill(r.buf[:r.idx]) + copy(r.buf[:length], r.buf[r.idx:]) + r.idx = 0 + r.buf = r.buf[:length] + }) +} + +func (r *RuneBuffer) Kill() { + r.Refresh(func() { + r.pushKill(r.buf[r.idx:]) + r.buf = r.buf[:r.idx] + }) +} + +func (r *RuneBuffer) Transpose() { + r.Refresh(func() { + if len(r.buf) == 1 { + r.idx++ + } + + if len(r.buf) < 2 { + return + } + + if r.idx == 0 { + r.idx = 1 + } else if r.idx >= len(r.buf) { + r.idx = len(r.buf) - 1 + } + r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx] + r.idx++ + }) +} + +func (r *RuneBuffer) MoveToNextWord() { + r.Refresh(func() { + for i := r.idx + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + return + } + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) MoveToEndWord() { + r.Refresh(func() { + // already at the end, so do nothing + if r.idx == len(r.buf) { + return + } + // if we are at the end of a word already, go to next + if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) { + r.idx++ + } + + // keep going until at the end of a word + for i := r.idx + 1; i < len(r.buf); i++ { + if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) { + r.idx = i - 1 + return + } + } + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) BackEscapeWord() { + r.Refresh(func() { + if r.idx == 0 { + return + } + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[i:r.idx]) + r.buf = append(r.buf[:i], r.buf[r.idx:]...) + r.idx = i + return + } + } + + r.buf = r.buf[:0] + r.idx = 0 + }) +} + +func (r *RuneBuffer) Yank() { + if len(r.lastKill) == 0 { + return + } + r.Refresh(func() { + buf := make([]rune, 0, len(r.buf)+len(r.lastKill)) + buf = append(buf, r.buf[:r.idx]...) + buf = append(buf, r.lastKill...) + buf = append(buf, r.buf[r.idx:]...) + r.buf = buf + r.idx += len(r.lastKill) + }) +} + +func (r *RuneBuffer) Backspace() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + r.idx-- + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + }) +} + +func (r *RuneBuffer) MoveToLineEnd() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) LineCount(width int) int { + if width == -1 { + width = r.width + } + return LineCount(width, + runes.WidthAll(r.buf)+r.PromptLen()) +} + +func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) { + r.Refresh(func() { + if reverse { + for i := r.idx - 1; i >= 0; i-- { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx++ + } + success = true + return + } + } + return + } + for i := r.idx + 1; i < len(r.buf); i++ { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx-- + } + success = true + return + } + } + }) + return +} + +func (r *RuneBuffer) isInLineEdge() bool { + if isWindows { + return false + } + sp := r.getSplitByLine(r.buf) + return len(sp[len(sp)-1]) == 0 +} + +func (r *RuneBuffer) getSplitByLine(rs []rune) []string { + return SplitByLine(r.promptLen(), r.width, rs) +} + +func (r *RuneBuffer) IdxLine(width int) int { + r.Lock() + defer r.Unlock() + return r.idxLine(width) +} + +func (r *RuneBuffer) idxLine(width int) int { + if width == 0 { + return 0 + } + sp := r.getSplitByLine(r.buf[:r.idx]) + return len(sp) - 1 +} + +func (r *RuneBuffer) CursorLineCount() int { + return r.LineCount(r.width) - r.IdxLine(r.width) +} + +func (r *RuneBuffer) Refresh(f func()) { + r.Lock() + defer r.Unlock() + + if !r.interactive { + if f != nil { + f() + } + return + } + + r.clean() + if f != nil { + f() + } + r.print() +} + +func (r *RuneBuffer) SetOffset(offset string) { + r.Lock() + r.offset = offset + r.Unlock() +} + +func (r *RuneBuffer) print() { + r.w.Write(r.output()) + r.hadClean = false +} + +func (r *RuneBuffer) output() []byte { + buf := bytes.NewBuffer(nil) + buf.WriteString(string(r.prompt)) + if r.cfg.EnableMask && len(r.buf) > 0 { + buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1))) + if r.buf[len(r.buf)-1] == '\n' { + buf.Write([]byte{'\n'}) + } else { + buf.Write([]byte(string(r.cfg.MaskRune))) + } + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + + } else { + for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) { + if e == '\t' { + buf.WriteString(strings.Repeat(" ", TabWidth)) + } else { + buf.WriteRune(e) + } + } + if r.isInLineEdge() { + buf.Write([]byte(" \b")) + } + } + // cursor position + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + return buf.Bytes() +} + +func (r *RuneBuffer) getBackspaceSequence() []byte { + var sep = map[int]bool{} + + var i int + for { + if i >= runes.WidthAll(r.buf) { + break + } + + if i == 0 { + i -= r.promptLen() + } + i += r.width + + sep[i] = true + } + var buf []byte + for i := len(r.buf); i > r.idx; i-- { + // move input to the left of one + buf = append(buf, '\b') + if sep[i] { + // up one line, go to the start of the line and move cursor right to the end (r.width) + buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...) + } + } + + return buf + +} + +func (r *RuneBuffer) Reset() []rune { + ret := runes.Copy(r.buf) + r.buf = r.buf[:0] + r.idx = 0 + return ret +} + +func (r *RuneBuffer) calWidth(m int) int { + if m > 0 { + return runes.WidthAll(r.buf[r.idx : r.idx+m]) + } + return runes.WidthAll(r.buf[r.idx+m : r.idx]) +} + +func (r *RuneBuffer) SetStyle(start, end int, style string) { + if end < start { + panic("end < start") + } + + // goto start + move := start - r.idx + if move > 0 { + r.w.Write([]byte(string(r.buf[r.idx : r.idx+move]))) + } else { + r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move))) + } + r.w.Write([]byte("\033[" + style + "m")) + r.w.Write([]byte(string(r.buf[start:end]))) + r.w.Write([]byte("\033[0m")) + // TODO: move back +} + +func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) { + r.Refresh(func() { + r.buf = buf + r.idx = idx + }) +} + +func (r *RuneBuffer) Set(buf []rune) { + r.SetWithIdx(len(buf), buf) +} + +func (r *RuneBuffer) SetPrompt(prompt string) { + r.Lock() + r.prompt = []rune(prompt) + r.Unlock() +} + +func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { + buf := bufio.NewWriter(w) + + if r.width == 0 { + buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen())) + buf.Write([]byte("\033[J")) + } else { + buf.Write([]byte("\033[J")) // just like ^k :) + if idxLine == 0 { + buf.WriteString("\033[2K") + buf.WriteString("\r") + } else { + for i := 0; i < idxLine; i++ { + io.WriteString(buf, "\033[2K\r\033[A") + } + io.WriteString(buf, "\033[2K\r") + } + } + buf.Flush() + return +} + +func (r *RuneBuffer) Clean() { + r.Lock() + r.clean() + r.Unlock() +} + +func (r *RuneBuffer) clean() { + r.cleanWithIdxLine(r.idxLine(r.width)) +} + +func (r *RuneBuffer) cleanWithIdxLine(idxLine int) { + if r.hadClean || !r.interactive { + return + } + r.hadClean = true + r.cleanOutput(r.w, idxLine) +} diff --git a/vendor/github.com/chzyer/readline/runes.go b/vendor/github.com/chzyer/readline/runes.go new file mode 100644 index 00000000..a669bc48 --- /dev/null +++ b/vendor/github.com/chzyer/readline/runes.go @@ -0,0 +1,223 @@ +package readline + +import ( + "bytes" + "unicode" + "unicode/utf8" +) + +var runes = Runes{} +var TabWidth = 4 + +type Runes struct{} + +func (Runes) EqualRune(a, b rune, fold bool) bool { + if a == b { + return true + } + if !fold { + return false + } + if a > b { + a, b = b, a + } + if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' { + if b == a+'a'-'A' { + return true + } + } + return false +} + +func (r Runes) EqualRuneFold(a, b rune) bool { + return r.EqualRune(a, b, true) +} + +func (r Runes) EqualFold(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if r.EqualRuneFold(a[i], b[i]) { + continue + } + return false + } + + return true +} + +func (Runes) Equal(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int { + for i := len(r) - len(sub); i >= 0; i-- { + found := true + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +// Search in runes from end to front +func (rs Runes) IndexAllBck(r, sub []rune) int { + return rs.IndexAllBckEx(r, sub, false) +} + +// Search in runes from front to end +func (rs Runes) IndexAll(r, sub []rune) int { + return rs.IndexAllEx(r, sub, false) +} + +func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int { + for i := 0; i < len(r); i++ { + found := true + if len(r[i:]) < len(sub) { + return -1 + } + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +func (Runes) Index(r rune, rs []rune) int { + for i := 0; i < len(rs); i++ { + if rs[i] == r { + return i + } + } + return -1 +} + +func (Runes) ColorFilter(r []rune) []rune { + newr := make([]rune, 0, len(r)) + for pos := 0; pos < len(r); pos++ { + if r[pos] == '\033' && r[pos+1] == '[' { + idx := runes.Index('m', r[pos+2:]) + if idx == -1 { + continue + } + pos += idx + 2 + continue + } + newr = append(newr, r[pos]) + } + return newr +} + +var zeroWidth = []*unicode.RangeTable{ + unicode.Mn, + unicode.Me, + unicode.Cc, + unicode.Cf, +} + +var doubleWidth = []*unicode.RangeTable{ + unicode.Han, + unicode.Hangul, + unicode.Hiragana, + unicode.Katakana, +} + +func (Runes) Width(r rune) int { + if r == '\t' { + return TabWidth + } + if unicode.IsOneOf(zeroWidth, r) { + return 0 + } + if unicode.IsOneOf(doubleWidth, r) { + return 2 + } + return 1 +} + +func (Runes) WidthAll(r []rune) (length int) { + for i := 0; i < len(r); i++ { + length += runes.Width(r[i]) + } + return +} + +func (Runes) Backspace(r []rune) []byte { + return bytes.Repeat([]byte{'\b'}, runes.WidthAll(r)) +} + +func (Runes) Copy(r []rune) []rune { + n := make([]rune, len(r)) + copy(n, r) + return n +} + +func (Runes) HasPrefixFold(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.EqualFold(r[:len(prefix)], prefix) +} + +func (Runes) HasPrefix(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.Equal(r[:len(prefix)], prefix) +} + +func (Runes) Aggregate(candicate [][]rune) (same []rune, size int) { + for i := 0; i < len(candicate[0]); i++ { + for j := 0; j < len(candicate)-1; j++ { + if i >= len(candicate[j]) || i >= len(candicate[j+1]) { + goto aggregate + } + if candicate[j][i] != candicate[j+1][i] { + goto aggregate + } + } + size = i + 1 + } +aggregate: + if size > 0 { + same = runes.Copy(candicate[0][:size]) + for i := 0; i < len(candicate); i++ { + n := runes.Copy(candicate[i]) + copy(n, n[size:]) + candicate[i] = n[:len(n)-size] + } + } + return +} + +func (Runes) TrimSpaceLeft(in []rune) []rune { + firstIndex := len(in) + for i, r := range in { + if unicode.IsSpace(r) == false { + firstIndex = i + break + } + } + return in[firstIndex:] +} diff --git a/vendor/github.com/chzyer/readline/search.go b/vendor/github.com/chzyer/readline/search.go new file mode 100644 index 00000000..52e8ff09 --- /dev/null +++ b/vendor/github.com/chzyer/readline/search.go @@ -0,0 +1,164 @@ +package readline + +import ( + "bytes" + "container/list" + "fmt" + "io" +) + +const ( + S_STATE_FOUND = iota + S_STATE_FAILING +) + +const ( + S_DIR_BCK = iota + S_DIR_FWD +) + +type opSearch struct { + inMode bool + state int + dir int + source *list.Element + w io.Writer + buf *RuneBuffer + data []rune + history *opHistory + cfg *Config + markStart int + markEnd int + width int +} + +func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch { + return &opSearch{ + w: w, + buf: buf, + cfg: cfg, + history: history, + width: width, + } +} + +func (o *opSearch) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opSearch) IsSearchMode() bool { + return o.inMode +} + +func (o *opSearch) SearchBackspace() { + if len(o.data) > 0 { + o.data = o.data[:len(o.data)-1] + o.search(true) + } +} + +func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) { + if o.dir == S_DIR_BCK { + return o.history.FindBck(isNewSearch, o.data, o.buf.idx) + } + return o.history.FindFwd(isNewSearch, o.data, o.buf.idx) +} + +func (o *opSearch) search(isChange bool) bool { + if len(o.data) == 0 { + o.state = S_STATE_FOUND + o.SearchRefresh(-1) + return true + } + idx, elem := o.findHistoryBy(isChange) + if elem == nil { + o.SearchRefresh(-2) + return false + } + o.history.current = elem + + item := o.history.showItem(o.history.current.Value) + start, end := 0, 0 + if o.dir == S_DIR_BCK { + start, end = idx, idx+len(o.data) + } else { + start, end = idx, idx+len(o.data) + idx += len(o.data) + } + o.buf.SetWithIdx(idx, item) + o.markStart, o.markEnd = start, end + o.SearchRefresh(idx) + return true +} + +func (o *opSearch) SearchChar(r rune) { + o.data = append(o.data, r) + o.search(true) +} + +func (o *opSearch) SearchMode(dir int) bool { + if o.width == 0 { + return false + } + alreadyInMode := o.inMode + o.inMode = true + o.dir = dir + o.source = o.history.current + if alreadyInMode { + o.search(false) + } else { + o.SearchRefresh(-1) + } + return true +} + +func (o *opSearch) ExitSearchMode(revert bool) { + if revert { + o.history.current = o.source + o.buf.Set(o.history.showItem(o.history.current.Value)) + } + o.markStart, o.markEnd = 0, 0 + o.state = S_STATE_FOUND + o.inMode = false + o.source = nil + o.data = nil +} + +func (o *opSearch) SearchRefresh(x int) { + if x == -2 { + o.state = S_STATE_FAILING + } else if x >= 0 { + o.state = S_STATE_FOUND + } + if x < 0 { + x = o.buf.idx + } + x = o.buf.CurrentWidth(x) + x += o.buf.PromptLen() + x = x % o.width + + if o.markStart > 0 { + o.buf.SetStyle(o.markStart, o.markEnd, "4") + } + + lineCnt := o.buf.CursorLineCount() + buf := bytes.NewBuffer(nil) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + buf.WriteString("\033[J") + if o.state == S_STATE_FAILING { + buf.WriteString("failing ") + } + if o.dir == S_DIR_BCK { + buf.WriteString("bck") + } else if o.dir == S_DIR_FWD { + buf.WriteString("fwd") + } + buf.WriteString("-i-search: ") + buf.WriteString(string(o.data)) // keyword + buf.WriteString("\033[4m \033[0m") // _ + fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev + if x > 0 { + fmt.Fprintf(buf, "\033[%dC", x) // move forward + } + o.w.Write(buf.Bytes()) +} diff --git a/vendor/github.com/chzyer/readline/std.go b/vendor/github.com/chzyer/readline/std.go new file mode 100644 index 00000000..61d44b75 --- /dev/null +++ b/vendor/github.com/chzyer/readline/std.go @@ -0,0 +1,197 @@ +package readline + +import ( + "io" + "os" + "sync" + "sync/atomic" +) + +var ( + Stdin io.ReadCloser = os.Stdin + Stdout io.WriteCloser = os.Stdout + Stderr io.WriteCloser = os.Stderr +) + +var ( + std *Instance + stdOnce sync.Once +) + +// global instance will not submit history automatic +func getInstance() *Instance { + stdOnce.Do(func() { + std, _ = NewEx(&Config{ + DisableAutoSaveHistory: true, + }) + }) + return std +} + +// let readline load history from filepath +// and try to persist history into disk +// set fp to "" to prevent readline persisting history to disk +// so the `AddHistory` will return nil error forever. +func SetHistoryPath(fp string) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.HistoryFile = fp + ins.SetConfig(cfg) +} + +// set auto completer to global instance +func SetAutoComplete(completer AutoCompleter) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.AutoComplete = completer + ins.SetConfig(cfg) +} + +// add history to global instance manually +// raise error only if `SetHistoryPath` is set with a non-empty path +func AddHistory(content string) error { + ins := getInstance() + return ins.SaveHistory(content) +} + +func Password(prompt string) ([]byte, error) { + ins := getInstance() + return ins.ReadPassword(prompt) +} + +// readline with global configs +func Line(prompt string) (string, error) { + ins := getInstance() + ins.SetPrompt(prompt) + return ins.Readline() +} + +type CancelableStdin struct { + r io.Reader + mutex sync.Mutex + stop chan struct{} + closed int32 + notify chan struct{} + data []byte + read int + err error +} + +func NewCancelableStdin(r io.Reader) *CancelableStdin { + c := &CancelableStdin{ + r: r, + notify: make(chan struct{}), + stop: make(chan struct{}), + } + go c.ioloop() + return c +} + +func (c *CancelableStdin) ioloop() { +loop: + for { + select { + case <-c.notify: + c.read, c.err = c.r.Read(c.data) + select { + case c.notify <- struct{}{}: + case <-c.stop: + break loop + } + case <-c.stop: + break loop + } + } +} + +func (c *CancelableStdin) Read(b []byte) (n int, err error) { + c.mutex.Lock() + defer c.mutex.Unlock() + if atomic.LoadInt32(&c.closed) == 1 { + return 0, io.EOF + } + + c.data = b + select { + case c.notify <- struct{}{}: + case <-c.stop: + return 0, io.EOF + } + select { + case <-c.notify: + return c.read, c.err + case <-c.stop: + return 0, io.EOF + } +} + +func (c *CancelableStdin) Close() error { + if atomic.CompareAndSwapInt32(&c.closed, 0, 1) { + close(c.stop) + } + return nil +} + +// FillableStdin is a stdin reader which can prepend some data before +// reading into the real stdin +type FillableStdin struct { + sync.Mutex + stdin io.Reader + stdinBuffer io.ReadCloser + buf []byte + bufErr error +} + +// NewFillableStdin gives you FillableStdin +func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) { + r, w := io.Pipe() + s := &FillableStdin{ + stdinBuffer: r, + stdin: stdin, + } + s.ioloop() + return s, w +} + +func (s *FillableStdin) ioloop() { + go func() { + for { + bufR := make([]byte, 100) + var n int + n, s.bufErr = s.stdinBuffer.Read(bufR) + if s.bufErr != nil { + if s.bufErr == io.ErrClosedPipe { + break + } + } + s.Lock() + s.buf = append(s.buf, bufR[:n]...) + s.Unlock() + } + }() +} + +// Read will read from the local buffer and if no data, read from stdin +func (s *FillableStdin) Read(p []byte) (n int, err error) { + s.Lock() + i := len(s.buf) + if len(p) < i { + i = len(p) + } + if i > 0 { + n := copy(p, s.buf) + s.buf = s.buf[:0] + cerr := s.bufErr + s.bufErr = nil + s.Unlock() + return n, cerr + } + s.Unlock() + n, err = s.stdin.Read(p) + return n, err +} + +func (s *FillableStdin) Close() error { + s.stdinBuffer.Close() + return nil +} diff --git a/vendor/github.com/chzyer/readline/std_windows.go b/vendor/github.com/chzyer/readline/std_windows.go new file mode 100644 index 00000000..b10f91bc --- /dev/null +++ b/vendor/github.com/chzyer/readline/std_windows.go @@ -0,0 +1,9 @@ +// +build windows + +package readline + +func init() { + Stdin = NewRawReader() + Stdout = NewANSIWriter(Stdout) + Stderr = NewANSIWriter(Stderr) +} diff --git a/vendor/github.com/chzyer/readline/term.go b/vendor/github.com/chzyer/readline/term.go new file mode 100644 index 00000000..ea5db934 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term.go @@ -0,0 +1,123 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 solaris + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" +) + +// State contains the state of a terminal. +type State struct { + termios Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := getTermios(fd) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var oldState State + + if termios, err := getTermios(fd); err != nil { + return nil, err + } else { + oldState.termios = *termios + } + + newState := oldState.termios + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage. + newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + // newState.Oflag &^= syscall.OPOST + newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newState.Cflag &^= syscall.CSIZE | syscall.PARENB + newState.Cflag |= syscall.CS8 + + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + + return &oldState, setTermios(fd, &newState) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := getTermios(fd) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + return setTermios(fd, &state.termios) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + oldState, err := getTermios(fd) + if err != nil { + return nil, err + } + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + if err := setTermios(fd, newState); err != nil { + return nil, err + } + + defer func() { + setTermios(fd, oldState) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/term_bsd.go b/vendor/github.com/chzyer/readline/term_bsd.go new file mode 100644 index 00000000..68b56ea6 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_bsd.go @@ -0,0 +1,29 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_linux.go b/vendor/github.com/chzyer/readline/term_linux.go new file mode 100644 index 00000000..e3392b4a --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_linux.go @@ -0,0 +1,33 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package readline + +import ( + "syscall" + "unsafe" +) + +// These constants are declared here, rather than importing +// them from the syscall package as some syscall packages, even +// on linux, for example gccgo, do not declare them. +const ioctlReadTermios = 0x5401 // syscall.TCGETS +const ioctlWriteTermios = 0x5402 // syscall.TCSETS + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_nosyscall6.go b/vendor/github.com/chzyer/readline/term_nosyscall6.go new file mode 100644 index 00000000..df923393 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_nosyscall6.go @@ -0,0 +1,32 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build aix os400 solaris + +package readline + +import "golang.org/x/sys/unix" + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} + +type Termios unix.Termios + +func getTermios(fd int) (*Termios, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + return (*Termios)(termios), nil +} + +func setTermios(fd int, termios *Termios) error { + return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios)) +} diff --git a/vendor/github.com/chzyer/readline/term_unix.go b/vendor/github.com/chzyer/readline/term_unix.go new file mode 100644 index 00000000..d3ea2424 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_unix.go @@ -0,0 +1,24 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +type Termios syscall.Termios + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + var dimensions [4]uint16 + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0) + if err != 0 { + return 0, 0, err + } + return int(dimensions[1]), int(dimensions[0]), nil +} diff --git a/vendor/github.com/chzyer/readline/term_windows.go b/vendor/github.com/chzyer/readline/term_windows.go new file mode 100644 index 00000000..1290e00b --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_windows.go @@ -0,0 +1,171 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" + "unsafe" +) + +const ( + enableLineInput = 2 + enableEchoInput = 4 + enableProcessedInput = 1 + enableWindowInput = 8 + enableMouseInput = 16 + enableInsertMode = 32 + enableQuickEditMode = 64 + enableExtendedFlags = 128 + enableAutoPosition = 256 + enableProcessedOutput = 1 + enableWrapAtEolOutput = 2 +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") +) + +type ( + coord struct { + x short + y short + } + smallRect struct { + left short + top short + right short + bottom short + } + consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord + } +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) + return err +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return 0, 0, error(e) + } + return int(info.size.x), int(info.size.y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + old := st + + st &^= (enableEchoInput) + st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + + defer func() { + syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(syscall.Handle(fd), buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + if n > 0 && buf[n-1] == '\r' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/terminal.go b/vendor/github.com/chzyer/readline/terminal.go new file mode 100644 index 00000000..38413d0c --- /dev/null +++ b/vendor/github.com/chzyer/readline/terminal.go @@ -0,0 +1,254 @@ +package readline + +import ( + "bufio" + "fmt" + "io" + "strings" + "sync" + "sync/atomic" +) + +type Terminal struct { + m sync.Mutex + cfg *Config + outchan chan rune + closed int32 + stopChan chan struct{} + kickChan chan struct{} + wg sync.WaitGroup + isReading int32 + sleeping int32 + + sizeChan chan string +} + +func NewTerminal(cfg *Config) (*Terminal, error) { + if err := cfg.Init(); err != nil { + return nil, err + } + t := &Terminal{ + cfg: cfg, + kickChan: make(chan struct{}, 1), + outchan: make(chan rune), + stopChan: make(chan struct{}, 1), + sizeChan: make(chan string, 1), + } + + go t.ioloop() + return t, nil +} + +// SleepToResume will sleep myself, and return only if I'm resumed. +func (t *Terminal) SleepToResume() { + if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) { + return + } + defer atomic.StoreInt32(&t.sleeping, 0) + + t.ExitRawMode() + ch := WaitForResume() + SuspendMe() + <-ch + t.EnterRawMode() +} + +func (t *Terminal) EnterRawMode() (err error) { + return t.cfg.FuncMakeRaw() +} + +func (t *Terminal) ExitRawMode() (err error) { + return t.cfg.FuncExitRaw() +} + +func (t *Terminal) Write(b []byte) (int, error) { + return t.cfg.Stdout.Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +func (t *Terminal) WriteStdin(b []byte) (int, error) { + return t.cfg.StdinWriter.Write(b) +} + +type termSize struct { + left int + top int +} + +func (t *Terminal) GetOffset(f func(offset string)) { + go func() { + f(<-t.sizeChan) + }() + t.Write([]byte("\033[6n")) +} + +func (t *Terminal) Print(s string) { + fmt.Fprintf(t.cfg.Stdout, "%s", s) +} + +func (t *Terminal) PrintRune(r rune) { + fmt.Fprintf(t.cfg.Stdout, "%c", r) +} + +func (t *Terminal) Readline() *Operation { + return NewOperation(t, t.cfg) +} + +// return rune(0) if meet EOF +func (t *Terminal) ReadRune() rune { + ch, ok := <-t.outchan + if !ok { + return rune(0) + } + return ch +} + +func (t *Terminal) IsReading() bool { + return atomic.LoadInt32(&t.isReading) == 1 +} + +func (t *Terminal) KickRead() { + select { + case t.kickChan <- struct{}{}: + default: + } +} + +func (t *Terminal) ioloop() { + t.wg.Add(1) + defer func() { + t.wg.Done() + close(t.outchan) + }() + + var ( + isEscape bool + isEscapeEx bool + isEscapeSS3 bool + expectNextChar bool + ) + + buf := bufio.NewReader(t.getStdin()) + for { + if !expectNextChar { + atomic.StoreInt32(&t.isReading, 0) + select { + case <-t.kickChan: + atomic.StoreInt32(&t.isReading, 1) + case <-t.stopChan: + return + } + } + expectNextChar = false + r, _, err := buf.ReadRune() + if err != nil { + if strings.Contains(err.Error(), "interrupted system call") { + expectNextChar = true + continue + } + break + } + + if isEscape { + isEscape = false + if r == CharEscapeEx { + // ^][ + expectNextChar = true + isEscapeEx = true + continue + } else if r == CharO { + // ^]O + expectNextChar = true + isEscapeSS3 = true + continue + } + r = escapeKey(r, buf) + } else if isEscapeEx { + isEscapeEx = false + if key := readEscKey(r, buf); key != nil { + r = escapeExKey(key) + // offset + if key.typ == 'R' { + if _, _, ok := key.Get2(); ok { + select { + case t.sizeChan <- key.attr: + default: + } + } + expectNextChar = true + continue + } + } + if r == 0 { + expectNextChar = true + continue + } + } else if isEscapeSS3 { + isEscapeSS3 = false + if key := readEscKey(r, buf); key != nil { + r = escapeSS3Key(key) + } + if r == 0 { + expectNextChar = true + continue + } + } + + expectNextChar = true + switch r { + case CharEsc: + if t.cfg.VimMode { + t.outchan <- r + break + } + isEscape = true + case CharInterrupt, CharEnter, CharCtrlJ, CharDelete: + expectNextChar = false + fallthrough + default: + t.outchan <- r + } + } + +} + +func (t *Terminal) Bell() { + fmt.Fprintf(t, "%c", CharBell) +} + +func (t *Terminal) Close() error { + if atomic.SwapInt32(&t.closed, 1) != 0 { + return nil + } + if closer, ok := t.cfg.Stdin.(io.Closer); ok { + closer.Close() + } + close(t.stopChan) + t.wg.Wait() + return t.ExitRawMode() +} + +func (t *Terminal) GetConfig() *Config { + t.m.Lock() + cfg := *t.cfg + t.m.Unlock() + return &cfg +} + +func (t *Terminal) getStdin() io.Reader { + t.m.Lock() + r := t.cfg.Stdin + t.m.Unlock() + return r +} + +func (t *Terminal) SetConfig(c *Config) error { + if err := c.Init(); err != nil { + return err + } + t.m.Lock() + t.cfg = c + t.m.Unlock() + return nil +} diff --git a/vendor/github.com/chzyer/readline/utils.go b/vendor/github.com/chzyer/readline/utils.go new file mode 100644 index 00000000..0706dd4e --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils.go @@ -0,0 +1,311 @@ +package readline + +import ( + "bufio" + "bytes" + "container/list" + "fmt" + "os" + "os/signal" + "strconv" + "strings" + "sync" + "syscall" + "time" + "unicode" +) + +var ( + isWindows = false +) + +const ( + CharLineStart = 1 + CharBackward = 2 + CharInterrupt = 3 + CharDelete = 4 + CharLineEnd = 5 + CharForward = 6 + CharBell = 7 + CharCtrlH = 8 + CharTab = 9 + CharCtrlJ = 10 + CharKill = 11 + CharCtrlL = 12 + CharEnter = 13 + CharNext = 14 + CharPrev = 16 + CharBckSearch = 18 + CharFwdSearch = 19 + CharTranspose = 20 + CharCtrlU = 21 + CharCtrlW = 23 + CharCtrlY = 25 + CharCtrlZ = 26 + CharEsc = 27 + CharO = 79 + CharEscapeEx = 91 + CharBackspace = 127 +) + +const ( + MetaBackward rune = -iota - 1 + MetaForward + MetaDelete + MetaBackspace + MetaTranspose +) + +// WaitForResume need to call before current process got suspend. +// It will run a ticker until a long duration is occurs, +// which means this process is resumed. +func WaitForResume() chan struct{} { + ch := make(chan struct{}) + var wg sync.WaitGroup + wg.Add(1) + go func() { + ticker := time.NewTicker(10 * time.Millisecond) + t := time.Now() + wg.Done() + for { + now := <-ticker.C + if now.Sub(t) > 100*time.Millisecond { + break + } + t = now + } + ticker.Stop() + ch <- struct{}{} + }() + wg.Wait() + return ch +} + +func Restore(fd int, state *State) error { + err := restoreTerm(fd, state) + if err != nil { + // errno 0 means everything is ok :) + if err.Error() == "errno 0" { + return nil + } else { + return err + } + } + return nil +} + +func IsPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// translate Esc[X +func escapeExKey(key *escapeKeyPair) rune { + var r rune + switch key.typ { + case 'D': + r = CharBackward + case 'C': + r = CharForward + case 'A': + r = CharPrev + case 'B': + r = CharNext + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + case '~': + if key.attr == "3" { + r = CharDelete + } + default: + } + return r +} + +// translate EscOX SS3 codes for up/down/etc. +func escapeSS3Key(key *escapeKeyPair) rune { + var r rune + switch key.typ { + case 'D': + r = CharBackward + case 'C': + r = CharForward + case 'A': + r = CharPrev + case 'B': + r = CharNext + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + default: + } + return r +} + +type escapeKeyPair struct { + attr string + typ rune +} + +func (e *escapeKeyPair) Get2() (int, int, bool) { + sp := strings.Split(e.attr, ";") + if len(sp) < 2 { + return -1, -1, false + } + s1, err := strconv.Atoi(sp[0]) + if err != nil { + return -1, -1, false + } + s2, err := strconv.Atoi(sp[1]) + if err != nil { + return -1, -1, false + } + return s1, s2, true +} + +func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair { + p := escapeKeyPair{} + buf := bytes.NewBuffer(nil) + for { + if r == ';' { + } else if unicode.IsNumber(r) { + } else { + p.typ = r + break + } + buf.WriteRune(r) + r, _, _ = reader.ReadRune() + } + p.attr = buf.String() + return &p +} + +// translate EscX to Meta+X +func escapeKey(r rune, reader *bufio.Reader) rune { + switch r { + case 'b': + r = MetaBackward + case 'f': + r = MetaForward + case 'd': + r = MetaDelete + case CharTranspose: + r = MetaTranspose + case CharBackspace: + r = MetaBackspace + case 'O': + d, _, _ := reader.ReadRune() + switch d { + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + default: + reader.UnreadRune() + } + case CharEsc: + + } + return r +} + +func SplitByLine(start, screenWidth int, rs []rune) []string { + var ret []string + buf := bytes.NewBuffer(nil) + currentWidth := start + for _, r := range rs { + w := runes.Width(r) + currentWidth += w + buf.WriteRune(r) + if currentWidth >= screenWidth { + ret = append(ret, buf.String()) + buf.Reset() + currentWidth = 0 + } + } + ret = append(ret, buf.String()) + return ret +} + +// calculate how many lines for N character +func LineCount(screenWidth, w int) int { + r := w / screenWidth + if w%screenWidth != 0 { + r++ + } + return r +} + +func IsWordBreak(i rune) bool { + switch { + case i >= 'a' && i <= 'z': + case i >= 'A' && i <= 'Z': + case i >= '0' && i <= '9': + default: + return true + } + return false +} + +func GetInt(s []string, def int) int { + if len(s) == 0 { + return def + } + c, err := strconv.Atoi(s[0]) + if err != nil { + return def + } + return c +} + +type RawMode struct { + state *State +} + +func (r *RawMode) Enter() (err error) { + r.state, err = MakeRaw(GetStdin()) + return err +} + +func (r *RawMode) Exit() error { + if r.state == nil { + return nil + } + return Restore(GetStdin(), r.state) +} + +// ----------------------------------------------------------------------------- + +func sleep(n int) { + Debug(n) + time.Sleep(2000 * time.Millisecond) +} + +// print a linked list to Debug() +func debugList(l *list.List) { + idx := 0 + for e := l.Front(); e != nil; e = e.Next() { + Debug(idx, fmt.Sprintf("%+v", e.Value)) + idx++ + } +} + +// append log info to another file +func Debug(o ...interface{}) { + f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + fmt.Fprintln(f, o...) + f.Close() +} + +func CaptureExitSignal(f func()) { + cSignal := make(chan os.Signal, 1) + signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM) + go func() { + for range cSignal { + f() + } + }() +} diff --git a/vendor/github.com/chzyer/readline/utils_unix.go b/vendor/github.com/chzyer/readline/utils_unix.go new file mode 100644 index 00000000..fc494923 --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_unix.go @@ -0,0 +1,83 @@ +// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 solaris + +package readline + +import ( + "io" + "os" + "os/signal" + "sync" + "syscall" +) + +type winsize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} + +// SuspendMe use to send suspend signal to myself, when we in the raw mode. +// For OSX it need to send to parent's pid +// For Linux it need to send to myself +func SuspendMe() { + p, _ := os.FindProcess(os.Getppid()) + p.Signal(syscall.SIGTSTP) + p, _ = os.FindProcess(os.Getpid()) + p.Signal(syscall.SIGTSTP) +} + +// get width of the terminal +func getWidth(stdoutFd int) int { + cols, _, err := GetSize(stdoutFd) + if err != nil { + return -1 + } + return cols +} + +func GetScreenWidth() int { + w := getWidth(syscall.Stdout) + if w < 0 { + w = getWidth(syscall.Stderr) + } + return w +} + +// ClearScreen clears the console screen +func ClearScreen(w io.Writer) (int, error) { + return w.Write([]byte("\033[H")) +} + +func DefaultIsTerminal() bool { + return IsTerminal(syscall.Stdin) && (IsTerminal(syscall.Stdout) || IsTerminal(syscall.Stderr)) +} + +func GetStdin() int { + return syscall.Stdin +} + +// ----------------------------------------------------------------------------- + +var ( + widthChange sync.Once + widthChangeCallback func() +) + +func DefaultOnWidthChanged(f func()) { + widthChangeCallback = f + widthChange.Do(func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGWINCH) + + go func() { + for { + _, ok := <-ch + if !ok { + break + } + widthChangeCallback() + } + }() + }) +} diff --git a/vendor/github.com/chzyer/readline/utils_windows.go b/vendor/github.com/chzyer/readline/utils_windows.go new file mode 100644 index 00000000..5bfa55dc --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_windows.go @@ -0,0 +1,41 @@ +// +build windows + +package readline + +import ( + "io" + "syscall" +) + +func SuspendMe() { +} + +func GetStdin() int { + return int(syscall.Stdin) +} + +func init() { + isWindows = true +} + +// get width of the terminal +func GetScreenWidth() int { + info, _ := GetConsoleScreenBufferInfo() + if info == nil { + return -1 + } + return int(info.dwSize.x) +} + +// ClearScreen clears the console screen +func ClearScreen(_ io.Writer) error { + return SetConsoleCursorPosition(&_COORD{0, 0}) +} + +func DefaultIsTerminal() bool { + return true +} + +func DefaultOnWidthChanged(func()) { + +} diff --git a/vendor/github.com/chzyer/readline/vim.go b/vendor/github.com/chzyer/readline/vim.go new file mode 100644 index 00000000..bedf2c1a --- /dev/null +++ b/vendor/github.com/chzyer/readline/vim.go @@ -0,0 +1,176 @@ +package readline + +const ( + VIM_NORMAL = iota + VIM_INSERT + VIM_VISUAL +) + +type opVim struct { + cfg *Config + op *Operation + vimMode int +} + +func newVimMode(op *Operation) *opVim { + ov := &opVim{ + cfg: op.cfg, + op: op, + } + ov.SetVimMode(ov.cfg.VimMode) + return ov +} + +func (o *opVim) SetVimMode(on bool) { + if o.cfg.VimMode && !on { // turn off + o.ExitVimMode() + } + o.cfg.VimMode = on + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) IsEnableVimMode() bool { + return o.cfg.VimMode +} + +func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'h': + t = CharBackward + case 'j': + t = CharNext + case 'k': + t = CharPrev + case 'l': + t = CharForward + case '0', '^': + rb.MoveToLineStart() + case '$': + rb.MoveToLineEnd() + case 'x': + rb.Delete() + if rb.IsCursorInEnd() { + rb.MoveBackward() + } + case 'r': + rb.Replace(readNext()) + case 'd': + next := readNext() + switch next { + case 'd': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + case 'p': + rb.Yank() + case 'b', 'B': + rb.MoveToPrevWord() + case 'w', 'W': + rb.MoveToNextWord() + case 'e', 'E': + rb.MoveToEndWord() + case 'f', 'F', 't', 'T': + next := readNext() + prevChar := r == 't' || r == 'T' + reverse := r == 'F' || r == 'T' + switch next { + case CharEsc: + default: + rb.MoveTo(next, prevChar, reverse) + } + default: + return r, false + } + return t, true +} + +func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'i': + case 'I': + rb.MoveToLineStart() + case 'a': + rb.MoveForward() + case 'A': + rb.MoveToLineEnd() + case 's': + rb.Delete() + case 'S': + rb.Erase() + case 'c': + next := readNext() + switch next { + case 'c': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + default: + return r, false + } + + o.EnterVimInsertMode() + return +} + +func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) { + switch r { + case CharEnter, CharInterrupt: + o.ExitVimMode() + return r + } + + if r, handled := o.handleVimNormalMovement(r, readNext); handled { + return r + } + + if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled { + return r + } + + // invalid operation + o.op.t.Bell() + return 0 +} + +func (o *opVim) EnterVimInsertMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimInsertMode() { + o.vimMode = VIM_NORMAL +} + +func (o *opVim) HandleVim(r rune, readNext func() rune) rune { + if o.vimMode == VIM_NORMAL { + return o.HandleVimNormal(r, readNext) + } + if r == CharEsc { + o.ExitVimInsertMode() + return 0 + } + + switch o.vimMode { + case VIM_INSERT: + return r + case VIM_VISUAL: + } + return r +} diff --git a/vendor/github.com/chzyer/readline/windows_api.go b/vendor/github.com/chzyer/readline/windows_api.go new file mode 100644 index 00000000..63f4f7b7 --- /dev/null +++ b/vendor/github.com/chzyer/readline/windows_api.go @@ -0,0 +1,152 @@ +// +build windows + +package readline + +import ( + "reflect" + "syscall" + "unsafe" +) + +var ( + kernel = NewKernel() + stdout = uintptr(syscall.Stdout) + stdin = uintptr(syscall.Stdin) +) + +type Kernel struct { + SetConsoleCursorPosition, + SetConsoleTextAttribute, + FillConsoleOutputCharacterW, + FillConsoleOutputAttribute, + ReadConsoleInputW, + GetConsoleScreenBufferInfo, + GetConsoleCursorInfo, + GetStdHandle CallFunc +} + +type short int16 +type word uint16 +type dword uint32 +type wchar uint16 + +type _COORD struct { + x short + y short +} + +func (c *_COORD) ptr() uintptr { + return uintptr(*(*int32)(unsafe.Pointer(c))) +} + +const ( + EVENT_KEY = 0x0001 + EVENT_MOUSE = 0x0002 + EVENT_WINDOW_BUFFER_SIZE = 0x0004 + EVENT_MENU = 0x0008 + EVENT_FOCUS = 0x0010 +) + +type _KEY_EVENT_RECORD struct { + bKeyDown int32 + wRepeatCount word + wVirtualKeyCode word + wVirtualScanCode word + unicodeChar wchar + dwControlKeyState dword +} + +// KEY_EVENT_RECORD KeyEvent; +// MOUSE_EVENT_RECORD MouseEvent; +// WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; +// MENU_EVENT_RECORD MenuEvent; +// FOCUS_EVENT_RECORD FocusEvent; +type _INPUT_RECORD struct { + EventType word + Padding uint16 + Event [16]byte +} + +type _CONSOLE_SCREEN_BUFFER_INFO struct { + dwSize _COORD + dwCursorPosition _COORD + wAttributes word + srWindow _SMALL_RECT + dwMaximumWindowSize _COORD +} + +type _SMALL_RECT struct { + left short + top short + right short + bottom short +} + +type _CONSOLE_CURSOR_INFO struct { + dwSize dword + bVisible bool +} + +type CallFunc func(u ...uintptr) error + +func NewKernel() *Kernel { + k := &Kernel{} + kernel32 := syscall.NewLazyDLL("kernel32.dll") + v := reflect.ValueOf(k).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + name := t.Field(i).Name + f := kernel32.NewProc(name) + v.Field(i).Set(reflect.ValueOf(k.Wrap(f))) + } + return k +} + +func (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc { + return func(args ...uintptr) error { + var r0 uintptr + var e1 syscall.Errno + size := uintptr(len(args)) + if len(args) <= 3 { + buf := make([]uintptr, 3) + copy(buf, args) + r0, _, e1 = syscall.Syscall(p.Addr(), size, + buf[0], buf[1], buf[2]) + } else { + buf := make([]uintptr, 6) + copy(buf, args) + r0, _, e1 = syscall.Syscall6(p.Addr(), size, + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], + ) + } + + if int(r0) == 0 { + if e1 != 0 { + return error(e1) + } else { + return syscall.EINVAL + } + } + return nil + } + +} + +func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) { + t := new(_CONSOLE_SCREEN_BUFFER_INFO) + err := kernel.GetConsoleScreenBufferInfo( + stdout, + uintptr(unsafe.Pointer(t)), + ) + return t, err +} + +func GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) { + t := new(_CONSOLE_CURSOR_INFO) + err := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t))) + return t, err +} + +func SetConsoleCursorPosition(c *_COORD) error { + return kernel.SetConsoleCursorPosition(stdout, c.ptr()) +} diff --git a/vendor/github.com/containerd/platforms/.gitattributes b/vendor/github.com/containerd/platforms/.gitattributes new file mode 100644 index 00000000..a0717e4b --- /dev/null +++ b/vendor/github.com/containerd/platforms/.gitattributes @@ -0,0 +1 @@ +*.go text eol=lf \ No newline at end of file diff --git a/vendor/github.com/containerd/platforms/.golangci.yml b/vendor/github.com/containerd/platforms/.golangci.yml new file mode 100644 index 00000000..d574fe11 --- /dev/null +++ b/vendor/github.com/containerd/platforms/.golangci.yml @@ -0,0 +1,32 @@ +linters: + enable: + - copyloopvar + - gofmt + - goimports + - gosec + - ineffassign + - misspell + - nolintlint + - revive + - staticcheck + - tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17 + - unconvert + - unused + - govet + - dupword # Checks for duplicate words in the source code + disable: + - errcheck + +run: + timeout: 5m + +issues: + exclude-dirs: + - api + - cluster + - design + - docs + - docs/man + - releases + - reports + - test # e2e scripts diff --git a/vendor/github.com/docker/cli/LICENSE b/vendor/github.com/containerd/platforms/LICENSE similarity index 99% rename from vendor/github.com/docker/cli/LICENSE rename to vendor/github.com/containerd/platforms/LICENSE index 9c8e20ab..584149b6 100644 --- a/vendor/github.com/docker/cli/LICENSE +++ b/vendor/github.com/containerd/platforms/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright 2013-2017 Docker, Inc. + Copyright The containerd Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/vendor/github.com/containerd/platforms/README.md b/vendor/github.com/containerd/platforms/README.md new file mode 100644 index 00000000..2059de77 --- /dev/null +++ b/vendor/github.com/containerd/platforms/README.md @@ -0,0 +1,32 @@ +# platforms + +A Go package for formatting, normalizing and matching container platforms. + +This package is based on the Open Containers Image Spec definition of a [platform](https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/descriptor.go#L52). + +## Platform Specifier + +While the OCI platform specifications provide a tool for components to +specify structured information, user input typically doesn't need the full +context and much can be inferred. To solve this problem, this package introduces +"specifiers". A specifier has the format +`||/[/]`. The user can provide either the +operating system or the architecture or both. + +An example of a common specifier is `linux/amd64`. If the host has a default +runtime that matches this, the user can simply provide the component that +matters. For example, if an image provides `amd64` and `arm64` support, the +operating system, `linux` can be inferred, so they only have to provide +`arm64` or `amd64`. Similar behavior is implemented for operating systems, +where the architecture may be known but a runtime may support images from +different operating systems. + +## Project details + +**platforms** is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). +As a containerd sub-project, you will find the: + * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), + * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), + * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) + +information in our [`containerd/project`](https://github.com/containerd/project) repository. \ No newline at end of file diff --git a/vendor/github.com/containerd/platforms/compare.go b/vendor/github.com/containerd/platforms/compare.go new file mode 100644 index 00000000..24403f3b --- /dev/null +++ b/vendor/github.com/containerd/platforms/compare.go @@ -0,0 +1,260 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "strconv" + "strings" + + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +// MatchComparer is able to match and compare platforms to +// filter and sort platforms. +type MatchComparer interface { + Matcher + + Less(specs.Platform, specs.Platform) bool +} + +type platformVersions struct { + major []int + minor []int +} + +var arm64variantToVersion = map[string]platformVersions{ + "v8": {[]int{8}, []int{0}}, + "v8.0": {[]int{8}, []int{0}}, + "v8.1": {[]int{8}, []int{1}}, + "v8.2": {[]int{8}, []int{2}}, + "v8.3": {[]int{8}, []int{3}}, + "v8.4": {[]int{8}, []int{4}}, + "v8.5": {[]int{8}, []int{5}}, + "v8.6": {[]int{8}, []int{6}}, + "v8.7": {[]int{8}, []int{7}}, + "v8.8": {[]int{8}, []int{8}}, + "v8.9": {[]int{8}, []int{9}}, + "v9": {[]int{9, 8}, []int{0, 5}}, + "v9.0": {[]int{9, 8}, []int{0, 5}}, + "v9.1": {[]int{9, 8}, []int{1, 6}}, + "v9.2": {[]int{9, 8}, []int{2, 7}}, + "v9.3": {[]int{9, 8}, []int{3, 8}}, + "v9.4": {[]int{9, 8}, []int{4, 9}}, + "v9.5": {[]int{9, 8}, []int{5, 9}}, + "v9.6": {[]int{9, 8}, []int{6, 9}}, + "v9.7": {[]int{9, 8}, []int{7, 9}}, +} + +// platformVector returns an (ordered) vector of appropriate specs.Platform +// objects to try matching for the given platform object (see platforms.Only). +func platformVector(platform specs.Platform) []specs.Platform { + vector := []specs.Platform{platform} + + switch platform.Architecture { + case "amd64": + if amd64Version, err := strconv.Atoi(strings.TrimPrefix(platform.Variant, "v")); err == nil && amd64Version > 1 { + for amd64Version--; amd64Version >= 1; amd64Version-- { + vector = append(vector, specs.Platform{ + Architecture: platform.Architecture, + OS: platform.OS, + OSVersion: platform.OSVersion, + OSFeatures: platform.OSFeatures, + Variant: "v" + strconv.Itoa(amd64Version), + }) + } + } + vector = append(vector, specs.Platform{ + Architecture: "386", + OS: platform.OS, + OSVersion: platform.OSVersion, + OSFeatures: platform.OSFeatures, + }) + case "arm": + if armVersion, err := strconv.Atoi(strings.TrimPrefix(platform.Variant, "v")); err == nil && armVersion > 5 { + for armVersion--; armVersion >= 5; armVersion-- { + vector = append(vector, specs.Platform{ + Architecture: platform.Architecture, + OS: platform.OS, + OSVersion: platform.OSVersion, + OSFeatures: platform.OSFeatures, + Variant: "v" + strconv.Itoa(armVersion), + }) + } + } + case "arm64": + variant := platform.Variant + if variant == "" { + variant = "v8" + } + + vector = []specs.Platform{} // Reset vector, the first variant will be added in loop. + arm64Versions, ok := arm64variantToVersion[variant] + if !ok { + break + } + for i, major := range arm64Versions.major { + for minor := arm64Versions.minor[i]; minor >= 0; minor-- { + arm64Variant := "v" + strconv.Itoa(major) + "." + strconv.Itoa(minor) + if minor == 0 { + arm64Variant = "v" + strconv.Itoa(major) + } + vector = append(vector, specs.Platform{ + Architecture: "arm64", + OS: platform.OS, + OSVersion: platform.OSVersion, + OSFeatures: platform.OSFeatures, + Variant: arm64Variant, + }) + } + } + + // All arm64/v8.x and arm64/v9.x are compatible with arm/v8 (32-bits) and below. + // There's no arm64 v9 variant, so it's normalized to v8. + if strings.HasPrefix(variant, "v8") || strings.HasPrefix(variant, "v9") { + variant = "v8" + } + vector = append(vector, platformVector(specs.Platform{ + Architecture: "arm", + OS: platform.OS, + OSVersion: platform.OSVersion, + OSFeatures: platform.OSFeatures, + Variant: variant, + })...) + } + + return vector +} + +// Only returns a match comparer for a single platform +// using default resolution logic for the platform. +// +// For arm64/v9.x, will also match arm64/v9.{0..x-1} and arm64/v8.{0..x+5} +// For arm64/v8.x, will also match arm64/v8.{0..x-1} +// For arm/v8, will also match arm/v7, arm/v6 and arm/v5 +// For arm/v7, will also match arm/v6 and arm/v5 +// For arm/v6, will also match arm/v5 +// For amd64, will also match 386 +func Only(platform specs.Platform) MatchComparer { + return Ordered(platformVector(Normalize(platform))...) +} + +// OnlyStrict returns a match comparer for a single platform. +// +// Unlike Only, OnlyStrict does not match sub platforms. +// So, "arm/vN" will not match "arm/vM" where M < N, +// and "amd64" will not also match "386". +// +// OnlyStrict matches non-canonical forms. +// So, "arm64" matches "arm/64/v8". +func OnlyStrict(platform specs.Platform) MatchComparer { + return Ordered(Normalize(platform)) +} + +// Ordered returns a platform MatchComparer which matches any of the platforms +// but orders them in order they are provided. +func Ordered(platforms ...specs.Platform) MatchComparer { + matchers := make([]Matcher, len(platforms)) + for i := range platforms { + matchers[i] = NewMatcher(platforms[i]) + } + return orderedPlatformComparer{ + matchers: matchers, + } +} + +// Any returns a platform MatchComparer which matches any of the platforms +// with no preference for ordering. +func Any(platforms ...specs.Platform) MatchComparer { + matchers := make([]Matcher, len(platforms)) + for i := range platforms { + matchers[i] = NewMatcher(platforms[i]) + } + return anyPlatformComparer{ + matchers: matchers, + } +} + +// All is a platform MatchComparer which matches all platforms +// with preference for ordering. +var All MatchComparer = allPlatformComparer{} + +type orderedPlatformComparer struct { + matchers []Matcher +} + +func (c orderedPlatformComparer) Match(platform specs.Platform) bool { + for _, m := range c.matchers { + if m.Match(platform) { + return true + } + } + return false +} + +func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool { + for _, m := range c.matchers { + p1m := m.Match(p1) + p2m := m.Match(p2) + if p1m && !p2m { + return true + } + if p1m || p2m { + return false + } + } + return false +} + +type anyPlatformComparer struct { + matchers []Matcher +} + +func (c anyPlatformComparer) Match(platform specs.Platform) bool { + for _, m := range c.matchers { + if m.Match(platform) { + return true + } + } + return false +} + +func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool { + var p1m, p2m bool + for _, m := range c.matchers { + if !p1m && m.Match(p1) { + p1m = true + } + if !p2m && m.Match(p2) { + p2m = true + } + if p1m && p2m { + return false + } + } + // If one matches, and the other does, sort match first + return p1m && !p2m +} + +type allPlatformComparer struct{} + +func (allPlatformComparer) Match(specs.Platform) bool { + return true +} + +func (allPlatformComparer) Less(specs.Platform, specs.Platform) bool { + return false +} diff --git a/vendor/github.com/containerd/platforms/cpuinfo.go b/vendor/github.com/containerd/platforms/cpuinfo.go new file mode 100644 index 00000000..91f50e8c --- /dev/null +++ b/vendor/github.com/containerd/platforms/cpuinfo.go @@ -0,0 +1,43 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "runtime" + "sync" + + "github.com/containerd/log" +) + +// Present the ARM instruction set architecture, eg: v7, v8 +// Don't use this value directly; call cpuVariant() instead. +var cpuVariantValue string + +var cpuVariantOnce sync.Once + +func cpuVariant() string { + cpuVariantOnce.Do(func() { + if isArmArch(runtime.GOARCH) { + var err error + cpuVariantValue, err = getCPUVariant() + if err != nil { + log.L.Errorf("Error getCPUVariant for OS %s: %v", runtime.GOOS, err) + } + } + }) + return cpuVariantValue +} diff --git a/vendor/github.com/containerd/platforms/cpuinfo_linux.go b/vendor/github.com/containerd/platforms/cpuinfo_linux.go new file mode 100644 index 00000000..98c7001f --- /dev/null +++ b/vendor/github.com/containerd/platforms/cpuinfo_linux.go @@ -0,0 +1,160 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "os" + "runtime" + "strings" + + "golang.org/x/sys/unix" +) + +// getMachineArch retrieves the machine architecture through system call +func getMachineArch() (string, error) { + var uname unix.Utsname + err := unix.Uname(&uname) + if err != nil { + return "", err + } + + arch := string(uname.Machine[:bytes.IndexByte(uname.Machine[:], 0)]) + + return arch, nil +} + +// For Linux, the kernel has already detected the ABI, ISA and Features. +// So we don't need to access the ARM registers to detect platform information +// by ourselves. We can just parse these information from /proc/cpuinfo +func getCPUInfo(pattern string) (info string, err error) { + + cpuinfo, err := os.Open("/proc/cpuinfo") + if err != nil { + return "", err + } + defer cpuinfo.Close() + + // Start to Parse the Cpuinfo line by line. For SMP SoC, we parse + // the first core is enough. + scanner := bufio.NewScanner(cpuinfo) + for scanner.Scan() { + newline := scanner.Text() + list := strings.Split(newline, ":") + + if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) { + return strings.TrimSpace(list[1]), nil + } + } + + // Check whether the scanner encountered errors + err = scanner.Err() + if err != nil { + return "", err + } + + return "", fmt.Errorf("getCPUInfo for pattern %s: %w", pattern, errNotFound) +} + +// getCPUVariantFromArch get CPU variant from arch through a system call +func getCPUVariantFromArch(arch string) (string, error) { + + var variant string + + arch = strings.ToLower(arch) + + if arch == "aarch64" { + variant = "8" + } else if arch[0:4] == "armv" && len(arch) >= 5 { + // Valid arch format is in form of armvXx + switch arch[3:5] { + case "v8": + variant = "8" + case "v7": + variant = "7" + case "v6": + variant = "6" + case "v5": + variant = "5" + case "v4": + variant = "4" + case "v3": + variant = "3" + default: + variant = "unknown" + } + } else { + return "", fmt.Errorf("getCPUVariantFromArch invalid arch: %s, %w", arch, errInvalidArgument) + } + return variant, nil +} + +// getCPUVariant returns cpu variant for ARM +// We first try reading "Cpu architecture" field from /proc/cpuinfo +// If we can't find it, then fall back using a system call +// This is to cover running ARM in emulated environment on x86 host as this field in /proc/cpuinfo +// was not present. +func getCPUVariant() (string, error) { + variant, err := getCPUInfo("Cpu architecture") + if err != nil { + if errors.Is(err, errNotFound) { + // Let's try getting CPU variant from machine architecture + arch, err := getMachineArch() + if err != nil { + return "", fmt.Errorf("failure getting machine architecture: %v", err) + } + + variant, err = getCPUVariantFromArch(arch) + if err != nil { + return "", fmt.Errorf("failure getting CPU variant from machine architecture: %v", err) + } + } else { + return "", fmt.Errorf("failure getting CPU variant: %v", err) + } + } + + // handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7") + // https://www.raspberrypi.org/forums/viewtopic.php?t=12614 + if runtime.GOARCH == "arm" && variant == "7" { + model, err := getCPUInfo("model name") + if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") { + variant = "6" + } + } + + switch strings.ToLower(variant) { + case "8", "aarch64": + variant = "v8" + case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)": + variant = "v7" + case "6", "6tej": + variant = "v6" + case "5", "5t", "5te", "5tej": + variant = "v5" + case "4", "4t": + variant = "v4" + case "3": + variant = "v3" + default: + variant = "unknown" + } + + return variant, nil +} diff --git a/vendor/github.com/containerd/platforms/cpuinfo_other.go b/vendor/github.com/containerd/platforms/cpuinfo_other.go new file mode 100644 index 00000000..97a1fe8a --- /dev/null +++ b/vendor/github.com/containerd/platforms/cpuinfo_other.go @@ -0,0 +1,55 @@ +//go:build !linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "fmt" + "runtime" +) + +func getCPUVariant() (string, error) { + + var variant string + + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + // Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use + // runtime.GOARCH to determine the variants + switch runtime.GOARCH { + case "arm64": + variant = "v8" + case "arm": + variant = "v7" + default: + variant = "unknown" + } + } else if runtime.GOOS == "freebsd" { + // FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated) + // detecting those variants is currently unimplemented + switch runtime.GOARCH { + case "arm64": + variant = "v8" + default: + variant = "unknown" + } + } else { + return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented) + } + + return variant, nil +} diff --git a/vendor/github.com/containerd/platforms/database.go b/vendor/github.com/containerd/platforms/database.go new file mode 100644 index 00000000..7a6f0d98 --- /dev/null +++ b/vendor/github.com/containerd/platforms/database.go @@ -0,0 +1,111 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "runtime" + "strings" +) + +// These function are generated from https://golang.org/src/go/build/syslist.go. +// +// We use switch statements because they are slightly faster than map lookups +// and use a little less memory. + +// isKnownOS returns true if we know about the operating system. +// +// The OS value should be normalized before calling this function. +func isKnownOS(os string) bool { + switch os { + case "aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos": + return true + } + return false +} + +// isArmArch returns true if the architecture is ARM. +// +// The arch value should be normalized before being passed to this function. +func isArmArch(arch string) bool { + switch arch { + case "arm", "arm64": + return true + } + return false +} + +// isKnownArch returns true if we know about the architecture. +// +// The arch value should be normalized before being passed to this function. +func isKnownArch(arch string) bool { + switch arch { + case "386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be", "ppc64", "ppc64le", "loong64", "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le", "ppc", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm": + return true + } + return false +} + +func normalizeOS(os string) string { + if os == "" { + return runtime.GOOS + } + os = strings.ToLower(os) + + switch os { + case "macos": + os = "darwin" + } + return os +} + +// normalizeArch normalizes the architecture. +func normalizeArch(arch, variant string) (string, string) { + arch, variant = strings.ToLower(arch), strings.ToLower(variant) + switch arch { + case "i386": + arch = "386" + variant = "" + case "x86_64", "x86-64", "amd64": + arch = "amd64" + if variant == "v1" { + variant = "" + } + case "aarch64", "arm64": + arch = "arm64" + switch variant { + case "8", "v8", "v8.0": + variant = "" + case "9", "9.0", "v9.0": + variant = "v9" + } + case "armhf": + arch = "arm" + variant = "v7" + case "armel": + arch = "arm" + variant = "v6" + case "arm": + switch variant { + case "", "7": + variant = "v7" + case "5", "6", "8": + variant = "v" + variant + } + } + + return arch, variant +} diff --git a/vendor/github.com/containerd/platforms/defaults.go b/vendor/github.com/containerd/platforms/defaults.go new file mode 100644 index 00000000..9d898d60 --- /dev/null +++ b/vendor/github.com/containerd/platforms/defaults.go @@ -0,0 +1,29 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +// DefaultString returns the default string specifier for the platform, +// with [PR#6](https://github.com/containerd/platforms/pull/6) the result +// may now also include the OSVersion from the provided platform specification. +func DefaultString() string { + return FormatAll(DefaultSpec()) +} + +// DefaultStrict returns strict form of Default. +func DefaultStrict() MatchComparer { + return OnlyStrict(DefaultSpec()) +} diff --git a/vendor/github.com/containerd/platforms/defaults_darwin.go b/vendor/github.com/containerd/platforms/defaults_darwin.go new file mode 100644 index 00000000..72355ca8 --- /dev/null +++ b/vendor/github.com/containerd/platforms/defaults_darwin.go @@ -0,0 +1,44 @@ +//go:build darwin + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "runtime" + + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +// DefaultSpec returns the current platform's default platform specification. +func DefaultSpec() specs.Platform { + return specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + // The Variant field will be empty if arch != ARM. + Variant: cpuVariant(), + } +} + +// Default returns the default matcher for the platform. +func Default() MatchComparer { + return Ordered(DefaultSpec(), specs.Platform{ + // darwin runtime also supports Linux binary via runu/LKL + OS: "linux", + Architecture: runtime.GOARCH, + }) +} diff --git a/vendor/github.com/containerd/platforms/defaults_freebsd.go b/vendor/github.com/containerd/platforms/defaults_freebsd.go new file mode 100644 index 00000000..d3fe89e0 --- /dev/null +++ b/vendor/github.com/containerd/platforms/defaults_freebsd.go @@ -0,0 +1,43 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "runtime" + + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +// DefaultSpec returns the current platform's default platform specification. +func DefaultSpec() specs.Platform { + return specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + // The Variant field will be empty if arch != ARM. + Variant: cpuVariant(), + } +} + +// Default returns the default matcher for the platform. +func Default() MatchComparer { + return Ordered(DefaultSpec(), specs.Platform{ + OS: "linux", + Architecture: runtime.GOARCH, + // The Variant field will be empty if arch != ARM. + Variant: cpuVariant(), + }) +} diff --git a/vendor/github.com/containerd/platforms/defaults_unix.go b/vendor/github.com/containerd/platforms/defaults_unix.go new file mode 100644 index 00000000..44acc47e --- /dev/null +++ b/vendor/github.com/containerd/platforms/defaults_unix.go @@ -0,0 +1,40 @@ +//go:build !windows && !darwin && !freebsd + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "runtime" + + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +// DefaultSpec returns the current platform's default platform specification. +func DefaultSpec() specs.Platform { + return specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + // The Variant field will be empty if arch != ARM. + Variant: cpuVariant(), + } +} + +// Default returns the default matcher for the platform. +func Default() MatchComparer { + return Only(DefaultSpec()) +} diff --git a/vendor/github.com/containerd/platforms/defaults_windows.go b/vendor/github.com/containerd/platforms/defaults_windows.go new file mode 100644 index 00000000..0165adea --- /dev/null +++ b/vendor/github.com/containerd/platforms/defaults_windows.go @@ -0,0 +1,42 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "fmt" + "runtime" + + specs "github.com/opencontainers/image-spec/specs-go/v1" + "golang.org/x/sys/windows" +) + +// DefaultSpec returns the current platform's default platform specification. +func DefaultSpec() specs.Platform { + major, minor, build := windows.RtlGetNtVersionNumbers() + return specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + OSVersion: fmt.Sprintf("%d.%d.%d", major, minor, build), + // The Variant field will be empty if arch != ARM. + Variant: cpuVariant(), + } +} + +// Default returns the current platform's default platform specification. +func Default() MatchComparer { + return Only(DefaultSpec()) +} diff --git a/vendor/github.com/containerd/platforms/errors.go b/vendor/github.com/containerd/platforms/errors.go new file mode 100644 index 00000000..5ad721e7 --- /dev/null +++ b/vendor/github.com/containerd/platforms/errors.go @@ -0,0 +1,30 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import "errors" + +// These errors mirror the errors defined in [github.com/containerd/containerd/errdefs], +// however, they are not exported as they are not expected to be used as sentinel +// errors by consumers of this package. +// +//nolint:unused // not all errors are used on all platforms. +var ( + errNotFound = errors.New("not found") + errInvalidArgument = errors.New("invalid argument") + errNotImplemented = errors.New("not implemented") +) diff --git a/vendor/github.com/containerd/platforms/platform_windows_compat.go b/vendor/github.com/containerd/platforms/platform_windows_compat.go new file mode 100644 index 00000000..7f3d9966 --- /dev/null +++ b/vendor/github.com/containerd/platforms/platform_windows_compat.go @@ -0,0 +1,156 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package platforms + +import ( + "strconv" + "strings" + + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +// windowsOSVersion is a wrapper for Windows version information +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx +type windowsOSVersion struct { + Version uint32 + MajorVersion uint8 + MinorVersion uint8 + Build uint16 +} + +// Windows Client and Server build numbers. +// +// See: +// https://learn.microsoft.com/en-us/windows/release-health/release-information +// https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info +// https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information +const ( + // rs5 (version 1809, codename "Redstone 5") corresponds to Windows Server + // 2019 (ltsc2019), and Windows 10 (October 2018 Update). + rs5 = 17763 + + // v21H2Server corresponds to Windows Server 2022 (ltsc2022). + v21H2Server = 20348 + + // v22H2Win11 corresponds to Windows 11 (2022 Update). + v22H2Win11 = 22621 +) + +// List of stable ABI compliant ltsc releases +// Note: List must be sorted in ascending order +var compatLTSCReleases = []uint16{ + v21H2Server, +} + +// CheckHostAndContainerCompat checks if given host and container +// OS versions are compatible. +// It includes support for stable ABI compliant versions as well. +// Every release after WS 2022 will support the previous ltsc +// container image. Stable ABI is in preview mode for windows 11 client. +// Refer: https://learn.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility?tabs=windows-server-2022%2Cwindows-10#windows-server-host-os-compatibility +func checkWindowsHostAndContainerCompat(host, ctr windowsOSVersion) bool { + // check major minor versions of host and guest + if host.MajorVersion != ctr.MajorVersion || + host.MinorVersion != ctr.MinorVersion { + return false + } + + // If host is < WS 2022, exact version match is required + if host.Build < v21H2Server { + return host.Build == ctr.Build + } + + var supportedLtscRelease uint16 + for i := len(compatLTSCReleases) - 1; i >= 0; i-- { + if host.Build >= compatLTSCReleases[i] { + supportedLtscRelease = compatLTSCReleases[i] + break + } + } + return ctr.Build >= supportedLtscRelease && ctr.Build <= host.Build +} + +func getWindowsOSVersion(osVersionPrefix string) windowsOSVersion { + if strings.Count(osVersionPrefix, ".") < 2 { + return windowsOSVersion{} + } + + major, extra, _ := strings.Cut(osVersionPrefix, ".") + minor, extra, _ := strings.Cut(extra, ".") + build, _, _ := strings.Cut(extra, ".") + + majorVersion, err := strconv.ParseUint(major, 10, 8) + if err != nil { + return windowsOSVersion{} + } + + minorVersion, err := strconv.ParseUint(minor, 10, 8) + if err != nil { + return windowsOSVersion{} + } + buildNumber, err := strconv.ParseUint(build, 10, 16) + if err != nil { + return windowsOSVersion{} + } + + return windowsOSVersion{ + MajorVersion: uint8(majorVersion), + MinorVersion: uint8(minorVersion), + Build: uint16(buildNumber), + } +} + +func winRevision(v string) int { + parts := strings.Split(v, ".") + if len(parts) < 4 { + return 0 + } + r, err := strconv.Atoi(parts[3]) + if err != nil { + return 0 + } + return r +} + +type windowsVersionMatcher struct { + windowsOSVersion +} + +func (m windowsVersionMatcher) Match(v string) bool { + if m.isEmpty() || v == "" { + return true + } + osv := getWindowsOSVersion(v) + return checkWindowsHostAndContainerCompat(m.windowsOSVersion, osv) +} + +func (m windowsVersionMatcher) isEmpty() bool { + return m.MajorVersion == 0 && m.MinorVersion == 0 && m.Build == 0 +} + +type windowsMatchComparer struct { + Matcher +} + +func (c *windowsMatchComparer) Less(p1, p2 specs.Platform) bool { + m1, m2 := c.Match(p1), c.Match(p2) + if m1 && m2 { + r1, r2 := winRevision(p1.OSVersion), winRevision(p2.OSVersion) + return r1 > r2 + } + return m1 && !m2 +} diff --git a/vendor/github.com/containerd/platforms/platforms.go b/vendor/github.com/containerd/platforms/platforms.go new file mode 100644 index 00000000..14d65abd --- /dev/null +++ b/vendor/github.com/containerd/platforms/platforms.go @@ -0,0 +1,341 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package platforms provides a toolkit for normalizing, matching and +// specifying container platforms. +// +// Centered around OCI platform specifications, we define a string-based +// specifier syntax that can be used for user input. With a specifier, users +// only need to specify the parts of the platform that are relevant to their +// context, providing an operating system or architecture or both. +// +// How do I use this package? +// +// The vast majority of use cases should simply use the match function with +// user input. The first step is to parse a specifier into a matcher: +// +// m, err := Parse("linux") +// if err != nil { ... } +// +// Once you have a matcher, use it to match against the platform declared by a +// component, typically from an image or runtime. Since extracting an images +// platform is a little more involved, we'll use an example against the +// platform default: +// +// if ok := m.Match(Default()); !ok { /* doesn't match */ } +// +// This can be composed in loops for resolving runtimes or used as a filter for +// fetch and select images. +// +// More details of the specifier syntax and platform spec follow. +// +// # Declaring Platform Support +// +// Components that have strict platform requirements should use the OCI +// platform specification to declare their support. Typically, this will be +// images and runtimes that should make these declaring which platform they +// support specifically. This looks roughly as follows: +// +// type Platform struct { +// Architecture string +// OS string +// Variant string +// } +// +// Most images and runtimes should at least set Architecture and OS, according +// to their GOARCH and GOOS values, respectively (follow the OCI image +// specification when in doubt). ARM should set variant under certain +// discussions, which are outlined below. +// +// # Platform Specifiers +// +// While the OCI platform specifications provide a tool for components to +// specify structured information, user input typically doesn't need the full +// context and much can be inferred. To solve this problem, we introduced +// "specifiers". A specifier has the format +// `||/[/]`. The user can provide either the +// operating system or the architecture or both. +// +// An example of a common specifier is `linux/amd64`. If the host has a default +// of runtime that matches this, the user can simply provide the component that +// matters. For example, if a image provides amd64 and arm64 support, the +// operating system, `linux` can be inferred, so they only have to provide +// `arm64` or `amd64`. Similar behavior is implemented for operating systems, +// where the architecture may be known but a runtime may support images from +// different operating systems. +// +// # Normalization +// +// Because not all users are familiar with the way the Go runtime represents +// platforms, several normalizations have been provided to make this package +// easier to user. +// +// The following are performed for architectures: +// +// Value Normalized +// aarch64 arm64 +// armhf arm +// armel arm/v6 +// i386 386 +// x86_64 amd64 +// x86-64 amd64 +// +// We also normalize the operating system `macos` to `darwin`. +// +// # ARM Support +// +// To qualify ARM architecture, the Variant field is used to qualify the arm +// version. The most common arm version, v7, is represented without the variant +// unless it is explicitly provided. This is treated as equivalent to armhf. A +// previous architecture, armel, will be normalized to arm/v6. +// +// Similarly, the most common arm64 version v8, and most common amd64 version v1 +// are represented without the variant. +// +// While these normalizations are provided, their support on arm platforms has +// not yet been fully implemented and tested. +package platforms + +import ( + "fmt" + "path" + "regexp" + "runtime" + "strconv" + "strings" + + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +var ( + specifierRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`) + osAndVersionRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.-]*)\))?$`) +) + +const osAndVersionFormat = "%s(%s)" + +// Platform is a type alias for convenience, so there is no need to import image-spec package everywhere. +type Platform = specs.Platform + +// Matcher matches platforms specifications, provided by an image or runtime. +type Matcher interface { + Match(platform specs.Platform) bool +} + +// NewMatcher returns a simple matcher based on the provided platform +// specification. The returned matcher only looks for equality based on os, +// architecture and variant. +// +// One may implement their own matcher if this doesn't provide the required +// functionality. +// +// Applications should opt to use `Match` over directly parsing specifiers. +func NewMatcher(platform specs.Platform) Matcher { + m := &matcher{ + Platform: Normalize(platform), + } + + if platform.OS == "windows" { + m.osvM = &windowsVersionMatcher{ + windowsOSVersion: getWindowsOSVersion(platform.OSVersion), + } + // In prior versions, on windows, the returned matcher implements a + // MatchComprarer interface. + // This preserves that behavior for backwards compatibility. + // + // TODO: This isn't actually used in this package, except for a test case, + // which may have been an unintended side of some refactor. + // It was likely intended to be used in `Ordered` but it is not since + // `Less` that is implemented here ends up getting masked due to wrapping. + if runtime.GOOS == "windows" { + return &windowsMatchComparer{m} + } + } + return m +} + +type osVerMatcher interface { + Match(string) bool +} + +type matcher struct { + specs.Platform + osvM osVerMatcher +} + +func (m *matcher) Match(platform specs.Platform) bool { + normalized := Normalize(platform) + return m.OS == normalized.OS && + m.Architecture == normalized.Architecture && + m.Variant == normalized.Variant && + m.matchOSVersion(platform) +} + +func (m *matcher) matchOSVersion(platform specs.Platform) bool { + if m.osvM != nil { + return m.osvM.Match(platform.OSVersion) + } + return true +} + +func (m *matcher) String() string { + return FormatAll(m.Platform) +} + +// ParseAll parses a list of platform specifiers into a list of platform. +func ParseAll(specifiers []string) ([]specs.Platform, error) { + platforms := make([]specs.Platform, len(specifiers)) + for i, s := range specifiers { + p, err := Parse(s) + if err != nil { + return nil, fmt.Errorf("invalid platform %s: %w", s, err) + } + platforms[i] = p + } + return platforms, nil +} + +// Parse parses the platform specifier syntax into a platform declaration. +// +// Platform specifiers are in the format `[()]||[()]/[/]`. +// The minimum required information for a platform specifier is the operating +// system or architecture. The OSVersion can be part of the OS like `windows(10.0.17763)` +// When an OSVersion is specified, then specs.Platform.OSVersion is populated with that value, +// and an empty string otherwise. +// If there is only a single string (no slashes), the +// value will be matched against the known set of operating systems, then fall +// back to the known set of architectures. The missing component will be +// inferred based on the local environment. +func Parse(specifier string) (specs.Platform, error) { + if strings.Contains(specifier, "*") { + // TODO(stevvooe): need to work out exact wildcard handling + return specs.Platform{}, fmt.Errorf("%q: wildcards not yet supported: %w", specifier, errInvalidArgument) + } + + // Limit to 4 elements to prevent unbounded split + parts := strings.SplitN(specifier, "/", 4) + + var p specs.Platform + for i, part := range parts { + if i == 0 { + // First element is [()] + osVer := osAndVersionRe.FindStringSubmatch(part) + if osVer == nil { + return specs.Platform{}, fmt.Errorf("%q is an invalid OS component of %q: OSAndVersion specifier component must match %q: %w", part, specifier, osAndVersionRe.String(), errInvalidArgument) + } + + p.OS = normalizeOS(osVer[1]) + p.OSVersion = osVer[2] + } else { + if !specifierRe.MatchString(part) { + return specs.Platform{}, fmt.Errorf("%q is an invalid component of %q: platform specifier component must match %q: %w", part, specifier, specifierRe.String(), errInvalidArgument) + } + } + } + + switch len(parts) { + case 1: + // in this case, we will test that the value might be an OS (with or + // without the optional OSVersion specified) and look it up. + // If it is not known, we'll treat it as an architecture. Since + // we have very little information about the platform here, we are + // going to be a little more strict if we don't know about the argument + // value. + if isKnownOS(p.OS) { + // picks a default architecture + p.Architecture = runtime.GOARCH + if p.Architecture == "arm" && cpuVariant() != "v7" { + p.Variant = cpuVariant() + } + + return p, nil + } + + p.Architecture, p.Variant = normalizeArch(parts[0], "") + if p.Architecture == "arm" && p.Variant == "v7" { + p.Variant = "" + } + if isKnownArch(p.Architecture) { + p.OS = runtime.GOOS + return p, nil + } + + return specs.Platform{}, fmt.Errorf("%q: unknown operating system or architecture: %w", specifier, errInvalidArgument) + case 2: + // In this case, we treat as a regular OS[(OSVersion)]/arch pair. We don't care + // about whether or not we know of the platform. + p.Architecture, p.Variant = normalizeArch(parts[1], "") + if p.Architecture == "arm" && p.Variant == "v7" { + p.Variant = "" + } + + return p, nil + case 3: + // we have a fully specified variant, this is rare + p.Architecture, p.Variant = normalizeArch(parts[1], parts[2]) + if p.Architecture == "arm64" && p.Variant == "" { + p.Variant = "v8" + } + + return p, nil + } + + return specs.Platform{}, fmt.Errorf("%q: cannot parse platform specifier: %w", specifier, errInvalidArgument) +} + +// MustParse is like Parses but panics if the specifier cannot be parsed. +// Simplifies initialization of global variables. +func MustParse(specifier string) specs.Platform { + p, err := Parse(specifier) + if err != nil { + panic("platform: Parse(" + strconv.Quote(specifier) + "): " + err.Error()) + } + return p +} + +// Format returns a string specifier from the provided platform specification. +func Format(platform specs.Platform) string { + if platform.OS == "" { + return "unknown" + } + + return path.Join(platform.OS, platform.Architecture, platform.Variant) +} + +// FormatAll returns a string specifier that also includes the OSVersion from the +// provided platform specification. +func FormatAll(platform specs.Platform) string { + if platform.OS == "" { + return "unknown" + } + + if platform.OSVersion != "" { + OSAndVersion := fmt.Sprintf(osAndVersionFormat, platform.OS, platform.OSVersion) + return path.Join(OSAndVersion, platform.Architecture, platform.Variant) + } + return path.Join(platform.OS, platform.Architecture, platform.Variant) +} + +// Normalize validates and translate the platform to the canonical value. +// +// For example, if "Aarch64" is encountered, we change it to "arm64" or if +// "x86_64" is encountered, it becomes "amd64". +func Normalize(platform specs.Platform) specs.Platform { + platform.OS = normalizeOS(platform.OS) + platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant) + + return platform +} diff --git a/vendor/github.com/moby/sys/atomicwriter/LICENSE b/vendor/github.com/containerd/stargz-snapshotter/estargz/LICENSE similarity index 100% rename from vendor/github.com/moby/sys/atomicwriter/LICENSE rename to vendor/github.com/containerd/stargz-snapshotter/estargz/LICENSE diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/build.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/build.go new file mode 100644 index 00000000..8b804b7d --- /dev/null +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/build.go @@ -0,0 +1,689 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright 2019 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +*/ + +package estargz + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "context" + "errors" + "fmt" + "io" + "os" + "path" + "runtime" + "strings" + "sync" + + "github.com/containerd/stargz-snapshotter/estargz/errorutil" + "github.com/klauspost/compress/zstd" + digest "github.com/opencontainers/go-digest" + "golang.org/x/sync/errgroup" +) + +type options struct { + chunkSize int + compressionLevel int + prioritizedFiles []string + missedPrioritizedFiles *[]string + compression Compression + ctx context.Context + minChunkSize int +} + +type Option func(o *options) error + +// WithChunkSize option specifies the chunk size of eStargz blob to build. +func WithChunkSize(chunkSize int) Option { + return func(o *options) error { + o.chunkSize = chunkSize + return nil + } +} + +// WithCompressionLevel option specifies the gzip compression level. +// The default is gzip.BestCompression. +// This option will be ignored if WithCompression option is used. +// See also: https://godoc.org/compress/gzip#pkg-constants +func WithCompressionLevel(level int) Option { + return func(o *options) error { + o.compressionLevel = level + return nil + } +} + +// WithPrioritizedFiles option specifies the list of prioritized files. +// These files must be complete paths that are absolute or relative to "/" +// For example, all of "foo/bar", "/foo/bar", "./foo/bar" and "../foo/bar" +// are treated as "/foo/bar". +func WithPrioritizedFiles(files []string) Option { + return func(o *options) error { + o.prioritizedFiles = files + return nil + } +} + +// WithAllowPrioritizeNotFound makes Build continue the execution even if some +// of prioritized files specified by WithPrioritizedFiles option aren't found +// in the input tar. Instead, this records all missed file names to the passed +// slice. +func WithAllowPrioritizeNotFound(missedFiles *[]string) Option { + return func(o *options) error { + if missedFiles == nil { + return fmt.Errorf("WithAllowPrioritizeNotFound: slice must be passed") + } + o.missedPrioritizedFiles = missedFiles + return nil + } +} + +// WithCompression specifies compression algorithm to be used. +// Default is gzip. +func WithCompression(compression Compression) Option { + return func(o *options) error { + o.compression = compression + return nil + } +} + +// WithContext specifies a context that can be used for clean canceleration. +func WithContext(ctx context.Context) Option { + return func(o *options) error { + o.ctx = ctx + return nil + } +} + +// WithMinChunkSize option specifies the minimal number of bytes of data +// must be written in one gzip stream. +// By increasing this number, one gzip stream can contain multiple files +// and it hopefully leads to smaller result blob. +// NOTE: This adds a TOC property that old reader doesn't understand. +func WithMinChunkSize(minChunkSize int) Option { + return func(o *options) error { + o.minChunkSize = minChunkSize + return nil + } +} + +// Blob is an eStargz blob. +type Blob struct { + io.ReadCloser + diffID digest.Digester + tocDigest digest.Digest +} + +// DiffID returns the digest of uncompressed blob. +// It is only valid to call DiffID after Close. +func (b *Blob) DiffID() digest.Digest { + return b.diffID.Digest() +} + +// TOCDigest returns the digest of uncompressed TOC JSON. +func (b *Blob) TOCDigest() digest.Digest { + return b.tocDigest +} + +// Build builds an eStargz blob which is an extended version of stargz, from a blob (gzip, zstd +// or plain tar) passed through the argument. If there are some prioritized files are listed in +// the option, these files are grouped as "prioritized" and can be used for runtime optimization +// (e.g. prefetch). This function builds a blob in parallel, with dividing that blob into several +// (at least the number of runtime.GOMAXPROCS(0)) sub-blobs. +func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) { + var opts options + opts.compressionLevel = gzip.BestCompression // BestCompression by default + for _, o := range opt { + if err := o(&opts); err != nil { + return nil, err + } + } + if opts.compression == nil { + opts.compression = newGzipCompressionWithLevel(opts.compressionLevel) + } + layerFiles := newTempFiles() + ctx := opts.ctx + if ctx == nil { + ctx = context.Background() + } + done := make(chan struct{}) + defer close(done) + go func() { + select { + case <-done: + // nop + case <-ctx.Done(): + layerFiles.CleanupAll() + } + }() + defer func() { + if rErr != nil { + if err := layerFiles.CleanupAll(); err != nil { + rErr = fmt.Errorf("failed to cleanup tmp files: %v: %w", err, rErr) + } + } + if cErr := ctx.Err(); cErr != nil { + rErr = fmt.Errorf("error from context %q: %w", cErr, rErr) + } + }() + tarBlob, err := decompressBlob(tarBlob, layerFiles) + if err != nil { + return nil, err + } + entries, err := sortEntries(tarBlob, opts.prioritizedFiles, opts.missedPrioritizedFiles) + if err != nil { + return nil, err + } + var tarParts [][]*entry + if opts.minChunkSize > 0 { + // Each entry needs to know the size of the current gzip stream so they + // cannot be processed in parallel. + tarParts = [][]*entry{entries} + } else { + tarParts = divideEntries(entries, runtime.GOMAXPROCS(0)) + } + writers := make([]*Writer, len(tarParts)) + payloads := make([]*os.File, len(tarParts)) + var mu sync.Mutex + var eg errgroup.Group + for i, parts := range tarParts { + i, parts := i, parts + // builds verifiable stargz sub-blobs + eg.Go(func() error { + esgzFile, err := layerFiles.TempFile("", "esgzdata") + if err != nil { + return err + } + sw := NewWriterWithCompressor(esgzFile, opts.compression) + sw.ChunkSize = opts.chunkSize + sw.MinChunkSize = opts.minChunkSize + if sw.needsOpenGzEntries == nil { + sw.needsOpenGzEntries = make(map[string]struct{}) + } + for _, f := range []string{PrefetchLandmark, NoPrefetchLandmark} { + sw.needsOpenGzEntries[f] = struct{}{} + } + if err := sw.AppendTar(readerFromEntries(parts...)); err != nil { + return err + } + mu.Lock() + writers[i] = sw + payloads[i] = esgzFile + mu.Unlock() + return nil + }) + } + if err := eg.Wait(); err != nil { + rErr = err + return nil, err + } + tocAndFooter, tocDgst, err := closeWithCombine(writers...) + if err != nil { + rErr = err + return nil, err + } + var rs []io.Reader + for _, p := range payloads { + fs, err := fileSectionReader(p) + if err != nil { + return nil, err + } + rs = append(rs, fs) + } + diffID := digest.Canonical.Digester() + pr, pw := io.Pipe() + go func() { + r, err := opts.compression.Reader(io.TeeReader(io.MultiReader(append(rs, tocAndFooter)...), pw)) + if err != nil { + pw.CloseWithError(err) + return + } + defer r.Close() + if _, err := io.Copy(diffID.Hash(), r); err != nil { + pw.CloseWithError(err) + return + } + pw.Close() + }() + return &Blob{ + ReadCloser: readCloser{ + Reader: pr, + closeFunc: layerFiles.CleanupAll, + }, + tocDigest: tocDgst, + diffID: diffID, + }, nil +} + +// closeWithCombine takes unclosed Writers and close them. This also returns the +// toc that combined all Writers into. +// Writers doesn't write TOC and footer to the underlying writers so they can be +// combined into a single eStargz and tocAndFooter returned by this function can +// be appended at the tail of that combined blob. +func closeWithCombine(ws ...*Writer) (tocAndFooterR io.Reader, tocDgst digest.Digest, err error) { + if len(ws) == 0 { + return nil, "", fmt.Errorf("at least one writer must be passed") + } + for _, w := range ws { + if w.closed { + return nil, "", fmt.Errorf("writer must be unclosed") + } + defer func(w *Writer) { w.closed = true }(w) + if err := w.closeGz(); err != nil { + return nil, "", err + } + if err := w.bw.Flush(); err != nil { + return nil, "", err + } + } + var ( + mtoc = new(JTOC) + currentOffset int64 + ) + mtoc.Version = ws[0].toc.Version + for _, w := range ws { + for _, e := range w.toc.Entries { + // Recalculate Offset of non-empty files/chunks + if (e.Type == "reg" && e.Size > 0) || e.Type == "chunk" { + e.Offset += currentOffset + } + mtoc.Entries = append(mtoc.Entries, e) + } + if w.toc.Version > mtoc.Version { + mtoc.Version = w.toc.Version + } + currentOffset += w.cw.n + } + + return tocAndFooter(ws[0].compressor, mtoc, currentOffset) +} + +func tocAndFooter(compressor Compressor, toc *JTOC, offset int64) (io.Reader, digest.Digest, error) { + buf := new(bytes.Buffer) + tocDigest, err := compressor.WriteTOCAndFooter(buf, offset, toc, nil) + if err != nil { + return nil, "", err + } + return buf, tocDigest, nil +} + +// divideEntries divides passed entries to the parts at least the number specified by the +// argument. +func divideEntries(entries []*entry, minPartsNum int) (set [][]*entry) { + var estimatedSize int64 + for _, e := range entries { + estimatedSize += e.header.Size + } + unitSize := estimatedSize / int64(minPartsNum) + var ( + nextEnd = unitSize + offset int64 + ) + set = append(set, []*entry{}) + for _, e := range entries { + set[len(set)-1] = append(set[len(set)-1], e) + offset += e.header.Size + if offset > nextEnd { + set = append(set, []*entry{}) + nextEnd += unitSize + } + } + return +} + +var errNotFound = errors.New("not found") + +// sortEntries reads the specified tar blob and returns a list of tar entries. +// If some of prioritized files are specified, the list starts from these +// files with keeping the order specified by the argument. +func sortEntries(in io.ReaderAt, prioritized []string, missedPrioritized *[]string) ([]*entry, error) { + + // Import tar file. + intar, err := importTar(in) + if err != nil { + return nil, fmt.Errorf("failed to sort: %w", err) + } + + // Sort the tar file respecting to the prioritized files list. + sorted := &tarFile{} + for _, l := range prioritized { + if err := moveRec(l, intar, sorted); err != nil { + if errors.Is(err, errNotFound) && missedPrioritized != nil { + *missedPrioritized = append(*missedPrioritized, l) + continue // allow not found + } + return nil, fmt.Errorf("failed to sort tar entries: %w", err) + } + } + if len(prioritized) == 0 { + sorted.add(&entry{ + header: &tar.Header{ + Name: NoPrefetchLandmark, + Typeflag: tar.TypeReg, + Size: int64(len([]byte{landmarkContents})), + }, + payload: bytes.NewReader([]byte{landmarkContents}), + }) + } else { + sorted.add(&entry{ + header: &tar.Header{ + Name: PrefetchLandmark, + Typeflag: tar.TypeReg, + Size: int64(len([]byte{landmarkContents})), + }, + payload: bytes.NewReader([]byte{landmarkContents}), + }) + } + + // Dump all entry and concatinate them. + return append(sorted.dump(), intar.dump()...), nil +} + +// readerFromEntries returns a reader of tar archive that contains entries passed +// through the arguments. +func readerFromEntries(entries ...*entry) io.Reader { + pr, pw := io.Pipe() + go func() { + tw := tar.NewWriter(pw) + defer tw.Close() + for _, entry := range entries { + if err := tw.WriteHeader(entry.header); err != nil { + pw.CloseWithError(fmt.Errorf("failed to write tar header: %v", err)) + return + } + if _, err := io.Copy(tw, entry.payload); err != nil { + pw.CloseWithError(fmt.Errorf("failed to write tar payload: %v", err)) + return + } + } + pw.Close() + }() + return pr +} + +func importTar(in io.ReaderAt) (*tarFile, error) { + tf := &tarFile{} + pw, err := newCountReadSeeker(in) + if err != nil { + return nil, fmt.Errorf("failed to make position watcher: %w", err) + } + tr := tar.NewReader(pw) + + // Walk through all nodes. + for { + // Fetch and parse next header. + h, err := tr.Next() + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("failed to parse tar file, %w", err) + } + switch cleanEntryName(h.Name) { + case PrefetchLandmark, NoPrefetchLandmark: + // Ignore existing landmark + continue + } + + // Add entry. If it already exists, replace it. + if _, ok := tf.get(h.Name); ok { + tf.remove(h.Name) + } + tf.add(&entry{ + header: h, + payload: io.NewSectionReader(in, pw.currentPos(), h.Size), + }) + } + + return tf, nil +} + +func moveRec(name string, in *tarFile, out *tarFile) error { + name = cleanEntryName(name) + if name == "" { // root directory. stop recursion. + if e, ok := in.get(name); ok { + // entry of the root directory exists. we should move it as well. + // this case will occur if tar entries are prefixed with "./", "/", etc. + out.add(e) + in.remove(name) + } + return nil + } + + _, okIn := in.get(name) + _, okOut := out.get(name) + if !okIn && !okOut { + return fmt.Errorf("file: %q: %w", name, errNotFound) + } + + parent, _ := path.Split(strings.TrimSuffix(name, "/")) + if err := moveRec(parent, in, out); err != nil { + return err + } + if e, ok := in.get(name); ok && e.header.Typeflag == tar.TypeLink { + if err := moveRec(e.header.Linkname, in, out); err != nil { + return err + } + } + if e, ok := in.get(name); ok { + out.add(e) + in.remove(name) + } + return nil +} + +type entry struct { + header *tar.Header + payload io.ReadSeeker +} + +type tarFile struct { + index map[string]*entry + stream []*entry +} + +func (f *tarFile) add(e *entry) { + if f.index == nil { + f.index = make(map[string]*entry) + } + f.index[cleanEntryName(e.header.Name)] = e + f.stream = append(f.stream, e) +} + +func (f *tarFile) remove(name string) { + name = cleanEntryName(name) + if f.index != nil { + delete(f.index, name) + } + var filtered []*entry + for _, e := range f.stream { + if cleanEntryName(e.header.Name) == name { + continue + } + filtered = append(filtered, e) + } + f.stream = filtered +} + +func (f *tarFile) get(name string) (e *entry, ok bool) { + if f.index == nil { + return nil, false + } + e, ok = f.index[cleanEntryName(name)] + return +} + +func (f *tarFile) dump() []*entry { + return f.stream +} + +type readCloser struct { + io.Reader + closeFunc func() error +} + +func (rc readCloser) Close() error { + return rc.closeFunc() +} + +func fileSectionReader(file *os.File) (*io.SectionReader, error) { + info, err := file.Stat() + if err != nil { + return nil, err + } + return io.NewSectionReader(file, 0, info.Size()), nil +} + +func newTempFiles() *tempFiles { + return &tempFiles{} +} + +type tempFiles struct { + files []*os.File + filesMu sync.Mutex + cleanupOnce sync.Once +} + +func (tf *tempFiles) TempFile(dir, pattern string) (*os.File, error) { + f, err := os.CreateTemp(dir, pattern) + if err != nil { + return nil, err + } + tf.filesMu.Lock() + tf.files = append(tf.files, f) + tf.filesMu.Unlock() + return f, nil +} + +func (tf *tempFiles) CleanupAll() (err error) { + tf.cleanupOnce.Do(func() { + err = tf.cleanupAll() + }) + return +} + +func (tf *tempFiles) cleanupAll() error { + tf.filesMu.Lock() + defer tf.filesMu.Unlock() + var allErr []error + for _, f := range tf.files { + if err := f.Close(); err != nil { + allErr = append(allErr, err) + } + if err := os.Remove(f.Name()); err != nil { + allErr = append(allErr, err) + } + } + tf.files = nil + return errorutil.Aggregate(allErr) +} + +func newCountReadSeeker(r io.ReaderAt) (*countReadSeeker, error) { + pos := int64(0) + return &countReadSeeker{r: r, cPos: &pos}, nil +} + +type countReadSeeker struct { + r io.ReaderAt + cPos *int64 + + mu sync.Mutex +} + +func (cr *countReadSeeker) Read(p []byte) (int, error) { + cr.mu.Lock() + defer cr.mu.Unlock() + + n, err := cr.r.ReadAt(p, *cr.cPos) + if err == nil { + *cr.cPos += int64(n) + } + return n, err +} + +func (cr *countReadSeeker) Seek(offset int64, whence int) (int64, error) { + cr.mu.Lock() + defer cr.mu.Unlock() + + switch whence { + default: + return 0, fmt.Errorf("unknown whence: %v", whence) + case io.SeekStart: + case io.SeekCurrent: + offset += *cr.cPos + case io.SeekEnd: + return 0, fmt.Errorf("unsupported whence: %v", whence) + } + + if offset < 0 { + return 0, fmt.Errorf("invalid offset") + } + *cr.cPos = offset + return offset, nil +} + +func (cr *countReadSeeker) currentPos() int64 { + cr.mu.Lock() + defer cr.mu.Unlock() + + return *cr.cPos +} + +func decompressBlob(org *io.SectionReader, tmp *tempFiles) (*io.SectionReader, error) { + if org.Size() < 4 { + return org, nil + } + src := make([]byte, 4) + if _, err := org.Read(src); err != nil && err != io.EOF { + return nil, err + } + var dR io.Reader + if bytes.Equal([]byte{0x1F, 0x8B, 0x08}, src[:3]) { + // gzip + dgR, err := gzip.NewReader(io.NewSectionReader(org, 0, org.Size())) + if err != nil { + return nil, err + } + defer dgR.Close() + dR = io.Reader(dgR) + } else if bytes.Equal([]byte{0x28, 0xb5, 0x2f, 0xfd}, src[:4]) { + // zstd + dzR, err := zstd.NewReader(io.NewSectionReader(org, 0, org.Size())) + if err != nil { + return nil, err + } + defer dzR.Close() + dR = io.Reader(dzR) + } else { + // uncompressed + return io.NewSectionReader(org, 0, org.Size()), nil + } + b, err := tmp.TempFile("", "uncompresseddata") + if err != nil { + return nil, err + } + if _, err := io.Copy(b, dR); err != nil { + return nil, err + } + return fileSectionReader(b) +} diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/errorutil/errors.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/errorutil/errors.go new file mode 100644 index 00000000..6de78b02 --- /dev/null +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/errorutil/errors.go @@ -0,0 +1,40 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package errorutil + +import ( + "errors" + "fmt" + "strings" +) + +// Aggregate combines a list of errors into a single new error. +func Aggregate(errs []error) error { + switch len(errs) { + case 0: + return nil + case 1: + return errs[0] + default: + points := make([]string, len(errs)+1) + points[0] = fmt.Sprintf("%d error(s) occurred:", len(errs)) + for i, err := range errs { + points[i+1] = fmt.Sprintf("* %s", err) + } + return errors.New(strings.Join(points, "\n\t")) + } +} diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/estargz.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/estargz.go new file mode 100644 index 00000000..f4d55465 --- /dev/null +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/estargz.go @@ -0,0 +1,1223 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright 2019 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +*/ + +package estargz + +import ( + "bufio" + "bytes" + "compress/gzip" + "crypto/sha256" + "errors" + "fmt" + "hash" + "io" + "os" + "path" + "sort" + "strings" + "sync" + "time" + + "github.com/containerd/stargz-snapshotter/estargz/errorutil" + digest "github.com/opencontainers/go-digest" + "github.com/vbatts/tar-split/archive/tar" +) + +// A Reader permits random access reads from a stargz file. +type Reader struct { + sr *io.SectionReader + toc *JTOC + tocDigest digest.Digest + + // m stores all non-chunk entries, keyed by name. + m map[string]*TOCEntry + + // chunks stores all TOCEntry values for regular files that + // are split up. For a file with a single chunk, it's only + // stored in m. + chunks map[string][]*TOCEntry + + decompressor Decompressor +} + +type openOpts struct { + tocOffset int64 + decompressors []Decompressor + telemetry *Telemetry +} + +// OpenOption is an option used during opening the layer +type OpenOption func(o *openOpts) error + +// WithTOCOffset option specifies the offset of TOC +func WithTOCOffset(tocOffset int64) OpenOption { + return func(o *openOpts) error { + o.tocOffset = tocOffset + return nil + } +} + +// WithDecompressors option specifies decompressors to use. +// Default is gzip-based decompressor. +func WithDecompressors(decompressors ...Decompressor) OpenOption { + return func(o *openOpts) error { + o.decompressors = decompressors + return nil + } +} + +// WithTelemetry option specifies the telemetry hooks +func WithTelemetry(telemetry *Telemetry) OpenOption { + return func(o *openOpts) error { + o.telemetry = telemetry + return nil + } +} + +// MeasureLatencyHook is a func which takes start time and records the diff +type MeasureLatencyHook func(time.Time) + +// Telemetry is a struct which defines telemetry hooks. By implementing these hooks you should be able to record +// the latency metrics of the respective steps of estargz open operation. To be used with estargz.OpenWithTelemetry(...) +type Telemetry struct { + GetFooterLatency MeasureLatencyHook // measure time to get stargz footer (in milliseconds) + GetTocLatency MeasureLatencyHook // measure time to GET TOC JSON (in milliseconds) + DeserializeTocLatency MeasureLatencyHook // measure time to deserialize TOC JSON (in milliseconds) +} + +// Open opens a stargz file for reading. +// The behavior is configurable using options. +// +// Note that each entry name is normalized as the path that is relative to root. +func Open(sr *io.SectionReader, opt ...OpenOption) (*Reader, error) { + var opts openOpts + for _, o := range opt { + if err := o(&opts); err != nil { + return nil, err + } + } + + gzipCompressors := []Decompressor{new(GzipDecompressor), new(LegacyGzipDecompressor)} + decompressors := append(gzipCompressors, opts.decompressors...) + + // Determine the size to fetch. Try to fetch as many bytes as possible. + fetchSize := maxFooterSize(sr.Size(), decompressors...) + if maybeTocOffset := opts.tocOffset; maybeTocOffset > fetchSize { + if maybeTocOffset > sr.Size() { + return nil, fmt.Errorf("blob size %d is smaller than the toc offset", sr.Size()) + } + fetchSize = sr.Size() - maybeTocOffset + } + + start := time.Now() // before getting layer footer + footer := make([]byte, fetchSize) + if _, err := sr.ReadAt(footer, sr.Size()-fetchSize); err != nil { + return nil, fmt.Errorf("error reading footer: %v", err) + } + if opts.telemetry != nil && opts.telemetry.GetFooterLatency != nil { + opts.telemetry.GetFooterLatency(start) + } + + var allErr []error + var found bool + var r *Reader + for _, d := range decompressors { + fSize := d.FooterSize() + fOffset := positive(int64(len(footer)) - fSize) + maybeTocBytes := footer[:fOffset] + _, tocOffset, tocSize, err := d.ParseFooter(footer[fOffset:]) + if err != nil { + allErr = append(allErr, err) + continue + } + if tocOffset >= 0 && tocSize <= 0 { + tocSize = sr.Size() - tocOffset - fSize + } + if tocOffset >= 0 && tocSize < int64(len(maybeTocBytes)) { + maybeTocBytes = maybeTocBytes[:tocSize] + } + r, err = parseTOC(d, sr, tocOffset, tocSize, maybeTocBytes, opts) + if err == nil { + found = true + break + } + allErr = append(allErr, err) + } + if !found { + return nil, errorutil.Aggregate(allErr) + } + if err := r.initFields(); err != nil { + return nil, fmt.Errorf("failed to initialize fields of entries: %v", err) + } + return r, nil +} + +// OpenFooter extracts and parses footer from the given blob. +// only supports gzip-based eStargz. +func OpenFooter(sr *io.SectionReader) (tocOffset int64, footerSize int64, rErr error) { + if sr.Size() < FooterSize && sr.Size() < legacyFooterSize { + return 0, 0, fmt.Errorf("blob size %d is smaller than the footer size", sr.Size()) + } + var footer [FooterSize]byte + if _, err := sr.ReadAt(footer[:], sr.Size()-FooterSize); err != nil { + return 0, 0, fmt.Errorf("error reading footer: %v", err) + } + var allErr []error + for _, d := range []Decompressor{new(GzipDecompressor), new(LegacyGzipDecompressor)} { + fSize := d.FooterSize() + fOffset := positive(int64(len(footer)) - fSize) + _, tocOffset, _, err := d.ParseFooter(footer[fOffset:]) + if err == nil { + return tocOffset, fSize, err + } + allErr = append(allErr, err) + } + return 0, 0, errorutil.Aggregate(allErr) +} + +// initFields populates the Reader from r.toc after decoding it from +// JSON. +// +// Unexported fields are populated and TOCEntry fields that were +// implicit in the JSON are populated. +func (r *Reader) initFields() error { + r.m = make(map[string]*TOCEntry, len(r.toc.Entries)) + r.chunks = make(map[string][]*TOCEntry) + var lastPath string + uname := map[int]string{} + gname := map[int]string{} + var lastRegEnt *TOCEntry + var chunkTopIndex int + for i, ent := range r.toc.Entries { + ent.Name = cleanEntryName(ent.Name) + switch ent.Type { + case "reg", "chunk": + if ent.Offset != r.toc.Entries[chunkTopIndex].Offset { + chunkTopIndex = i + } + ent.chunkTopIndex = chunkTopIndex + } + if ent.Type == "reg" { + lastRegEnt = ent + } + if ent.Type == "chunk" { + ent.Name = lastPath + r.chunks[ent.Name] = append(r.chunks[ent.Name], ent) + if ent.ChunkSize == 0 && lastRegEnt != nil { + ent.ChunkSize = lastRegEnt.Size - ent.ChunkOffset + } + } else { + lastPath = ent.Name + + if ent.Uname != "" { + uname[ent.UID] = ent.Uname + } else { + ent.Uname = uname[ent.UID] + } + if ent.Gname != "" { + gname[ent.GID] = ent.Gname + } else { + ent.Gname = uname[ent.GID] + } + + ent.modTime, _ = time.Parse(time.RFC3339, ent.ModTime3339) + + if ent.Type == "dir" { + ent.NumLink++ // Parent dir links to this directory + } + r.m[ent.Name] = ent + } + if ent.Type == "reg" && ent.ChunkSize > 0 && ent.ChunkSize < ent.Size { + r.chunks[ent.Name] = make([]*TOCEntry, 0, ent.Size/ent.ChunkSize+1) + r.chunks[ent.Name] = append(r.chunks[ent.Name], ent) + } + if ent.ChunkSize == 0 && ent.Size != 0 { + ent.ChunkSize = ent.Size + } + } + + // Populate children, add implicit directories: + for _, ent := range r.toc.Entries { + if ent.Type == "chunk" { + continue + } + // add "foo/": + // add "foo" child to "" (creating "" if necessary) + // + // add "foo/bar/": + // add "bar" child to "foo" (creating "foo" if necessary) + // + // add "foo/bar.txt": + // add "bar.txt" child to "foo" (creating "foo" if necessary) + // + // add "a/b/c/d/e/f.txt": + // create "a/b/c/d/e" node + // add "f.txt" child to "e" + + name := ent.Name + pdirName := parentDir(name) + if name == pdirName { + // This entry and its parent are the same. + // Ignore this for avoiding infinite loop of the reference. + // The example case where this can occur is when tar contains the root + // directory itself (e.g. "./", "/"). + continue + } + pdir := r.getOrCreateDir(pdirName) + ent.NumLink++ // at least one name(ent.Name) references this entry. + if ent.Type == "hardlink" { + org, err := r.getSource(ent) + if err != nil { + return err + } + org.NumLink++ // original entry is referenced by this ent.Name. + ent = org + } + pdir.addChild(path.Base(name), ent) + } + + lastOffset := r.sr.Size() + for i := len(r.toc.Entries) - 1; i >= 0; i-- { + e := r.toc.Entries[i] + if e.isDataType() { + e.nextOffset = lastOffset + } + if e.Offset != 0 && e.InnerOffset == 0 { + lastOffset = e.Offset + } + } + + return nil +} + +func (r *Reader) getSource(ent *TOCEntry) (_ *TOCEntry, err error) { + if ent.Type == "hardlink" { + org, ok := r.m[cleanEntryName(ent.LinkName)] + if !ok { + return nil, fmt.Errorf("%q is a hardlink but the linkname %q isn't found", ent.Name, ent.LinkName) + } + ent, err = r.getSource(org) + if err != nil { + return nil, err + } + } + return ent, nil +} + +func parentDir(p string) string { + dir, _ := path.Split(p) + return strings.TrimSuffix(dir, "/") +} + +func (r *Reader) getOrCreateDir(d string) *TOCEntry { + e, ok := r.m[d] + if !ok { + e = &TOCEntry{ + Name: d, + Type: "dir", + Mode: 0755, + NumLink: 2, // The directory itself(.) and the parent link to this directory. + } + r.m[d] = e + if d != "" { + pdir := r.getOrCreateDir(parentDir(d)) + pdir.addChild(path.Base(d), e) + } + } + return e +} + +func (r *Reader) TOCDigest() digest.Digest { + return r.tocDigest +} + +// VerifyTOC checks that the TOC JSON in the passed blob matches the +// passed digests and that the TOC JSON contains digests for all chunks +// contained in the blob. If the verification succceeds, this function +// returns TOCEntryVerifier which holds all chunk digests in the stargz blob. +func (r *Reader) VerifyTOC(tocDigest digest.Digest) (TOCEntryVerifier, error) { + // Verify the digest of TOC JSON + if r.tocDigest != tocDigest { + return nil, fmt.Errorf("invalid TOC JSON %q; want %q", r.tocDigest, tocDigest) + } + return r.Verifiers() +} + +// Verifiers returns TOCEntryVerifier of this chunk. Use VerifyTOC instead in most cases +// because this doesn't verify TOC. +func (r *Reader) Verifiers() (TOCEntryVerifier, error) { + chunkDigestMap := make(map[int64]digest.Digest) // map from chunk offset to the chunk digest + regDigestMap := make(map[int64]digest.Digest) // map from chunk offset to the reg file digest + var chunkDigestMapIncomplete bool + var regDigestMapIncomplete bool + var containsChunk bool + for _, e := range r.toc.Entries { + if e.Type != "reg" && e.Type != "chunk" { + continue + } + + // offset must be unique in stargz blob + _, dOK := chunkDigestMap[e.Offset] + _, rOK := regDigestMap[e.Offset] + if dOK || rOK { + return nil, fmt.Errorf("offset %d found twice", e.Offset) + } + + if e.Type == "reg" { + if e.Size == 0 { + continue // ignores empty file + } + + // record the digest of regular file payload + if e.Digest != "" { + d, err := digest.Parse(e.Digest) + if err != nil { + return nil, fmt.Errorf("failed to parse regular file digest %q: %w", e.Digest, err) + } + regDigestMap[e.Offset] = d + } else { + regDigestMapIncomplete = true + } + } else { + containsChunk = true // this layer contains "chunk" entries. + } + + // "reg" also can contain ChunkDigest (e.g. when "reg" is the first entry of + // chunked file) + if e.ChunkDigest != "" { + d, err := digest.Parse(e.ChunkDigest) + if err != nil { + return nil, fmt.Errorf("failed to parse chunk digest %q: %w", e.ChunkDigest, err) + } + chunkDigestMap[e.Offset] = d + } else { + chunkDigestMapIncomplete = true + } + } + + if chunkDigestMapIncomplete { + // Though some chunk digests are not found, if this layer doesn't contain + // "chunk"s and all digest of "reg" files are recorded, we can use them instead. + if !containsChunk && !regDigestMapIncomplete { + return &verifier{digestMap: regDigestMap}, nil + } + return nil, fmt.Errorf("some ChunkDigest not found in TOC JSON") + } + + return &verifier{digestMap: chunkDigestMap}, nil +} + +// verifier is an implementation of TOCEntryVerifier which holds verifiers keyed by +// offset of the chunk. +type verifier struct { + digestMap map[int64]digest.Digest + digestMapMu sync.Mutex +} + +// Verifier returns a content verifier specified by TOCEntry. +func (v *verifier) Verifier(ce *TOCEntry) (digest.Verifier, error) { + v.digestMapMu.Lock() + defer v.digestMapMu.Unlock() + d, ok := v.digestMap[ce.Offset] + if !ok { + return nil, fmt.Errorf("verifier for offset=%d,size=%d hasn't been registered", + ce.Offset, ce.ChunkSize) + } + return d.Verifier(), nil +} + +// ChunkEntryForOffset returns the TOCEntry containing the byte of the +// named file at the given offset within the file. +// Name must be absolute path or one that is relative to root. +func (r *Reader) ChunkEntryForOffset(name string, offset int64) (e *TOCEntry, ok bool) { + name = cleanEntryName(name) + e, ok = r.Lookup(name) + if !ok || !e.isDataType() { + return nil, false + } + ents := r.chunks[name] + if len(ents) < 2 { + if offset >= e.ChunkSize { + return nil, false + } + return e, true + } + i := sort.Search(len(ents), func(i int) bool { + e := ents[i] + return e.ChunkOffset >= offset || (offset > e.ChunkOffset && offset < e.ChunkOffset+e.ChunkSize) + }) + if i == len(ents) { + return nil, false + } + return ents[i], true +} + +// Lookup returns the Table of Contents entry for the given path. +// +// To get the root directory, use the empty string. +// Path must be absolute path or one that is relative to root. +func (r *Reader) Lookup(path string) (e *TOCEntry, ok bool) { + path = cleanEntryName(path) + if r == nil { + return + } + e, ok = r.m[path] + if ok && e.Type == "hardlink" { + var err error + e, err = r.getSource(e) + if err != nil { + return nil, false + } + } + return +} + +// OpenFile returns the reader of the specified file payload. +// +// Name must be absolute path or one that is relative to root. +func (r *Reader) OpenFile(name string) (*io.SectionReader, error) { + fr, err := r.newFileReader(name) + if err != nil { + return nil, err + } + return io.NewSectionReader(fr, 0, fr.size), nil +} + +func (r *Reader) newFileReader(name string) (*fileReader, error) { + name = cleanEntryName(name) + ent, ok := r.Lookup(name) + if !ok { + // TODO: come up with some error plan. This is lazy: + return nil, &os.PathError{ + Path: name, + Op: "OpenFile", + Err: os.ErrNotExist, + } + } + if ent.Type != "reg" { + return nil, &os.PathError{ + Path: name, + Op: "OpenFile", + Err: errors.New("not a regular file"), + } + } + return &fileReader{ + r: r, + size: ent.Size, + ents: r.getChunks(ent), + }, nil +} + +func (r *Reader) OpenFileWithPreReader(name string, preRead func(*TOCEntry, io.Reader) error) (*io.SectionReader, error) { + fr, err := r.newFileReader(name) + if err != nil { + return nil, err + } + fr.preRead = preRead + return io.NewSectionReader(fr, 0, fr.size), nil +} + +func (r *Reader) getChunks(ent *TOCEntry) []*TOCEntry { + if ents, ok := r.chunks[ent.Name]; ok { + return ents + } + return []*TOCEntry{ent} +} + +type fileReader struct { + r *Reader + size int64 + ents []*TOCEntry // 1 or more reg/chunk entries + preRead func(*TOCEntry, io.Reader) error +} + +func (fr *fileReader) ReadAt(p []byte, off int64) (n int, err error) { + if off >= fr.size { + return 0, io.EOF + } + if off < 0 { + return 0, errors.New("invalid offset") + } + var i int + if len(fr.ents) > 1 { + i = sort.Search(len(fr.ents), func(i int) bool { + return fr.ents[i].ChunkOffset >= off + }) + if i == len(fr.ents) { + i = len(fr.ents) - 1 + } + } + ent := fr.ents[i] + if ent.ChunkOffset > off { + if i == 0 { + return 0, errors.New("internal error; first chunk offset is non-zero") + } + ent = fr.ents[i-1] + } + + // If ent is a chunk of a large file, adjust the ReadAt + // offset by the chunk's offset. + off -= ent.ChunkOffset + + finalEnt := fr.ents[len(fr.ents)-1] + compressedOff := ent.Offset + // compressedBytesRemain is the number of compressed bytes in this + // file remaining, over 1+ chunks. + compressedBytesRemain := finalEnt.NextOffset() - compressedOff + + sr := io.NewSectionReader(fr.r.sr, compressedOff, compressedBytesRemain) + + const maxRead = 2 << 20 + var bufSize = maxRead + if compressedBytesRemain < maxRead { + bufSize = int(compressedBytesRemain) + } + + br := bufio.NewReaderSize(sr, bufSize) + if _, err := br.Peek(bufSize); err != nil { + return 0, fmt.Errorf("fileReader.ReadAt.peek: %v", err) + } + + dr, err := fr.r.decompressor.Reader(br) + if err != nil { + return 0, fmt.Errorf("fileReader.ReadAt.decompressor.Reader: %v", err) + } + defer dr.Close() + + if fr.preRead == nil { + if n, err := io.CopyN(io.Discard, dr, ent.InnerOffset+off); n != ent.InnerOffset+off || err != nil { + return 0, fmt.Errorf("discard of %d bytes != %v, %v", ent.InnerOffset+off, n, err) + } + return io.ReadFull(dr, p) + } + + var retN int + var retErr error + var found bool + var nr int64 + for _, e := range fr.r.toc.Entries[ent.chunkTopIndex:] { + if !e.isDataType() { + continue + } + if e.Offset != fr.r.toc.Entries[ent.chunkTopIndex].Offset { + break + } + if in, err := io.CopyN(io.Discard, dr, e.InnerOffset-nr); err != nil || in != e.InnerOffset-nr { + return 0, fmt.Errorf("discard of remaining %d bytes != %v, %v", e.InnerOffset-nr, in, err) + } + nr = e.InnerOffset + if e == ent { + found = true + if n, err := io.CopyN(io.Discard, dr, off); n != off || err != nil { + return 0, fmt.Errorf("discard of offset %d bytes != %v, %v", off, n, err) + } + retN, retErr = io.ReadFull(dr, p) + nr += off + int64(retN) + continue + } + cr := &countReader{r: io.LimitReader(dr, e.ChunkSize)} + if err := fr.preRead(e, cr); err != nil { + return 0, fmt.Errorf("failed to pre read: %w", err) + } + nr += cr.n + } + if !found { + return 0, fmt.Errorf("fileReader.ReadAt: target entry not found") + } + return retN, retErr +} + +// A Writer writes stargz files. +// +// Use NewWriter to create a new Writer. +type Writer struct { + bw *bufio.Writer + cw *countWriter + toc *JTOC + diffHash hash.Hash // SHA-256 of uncompressed tar + + closed bool + gz io.WriteCloser + lastUsername map[int]string + lastGroupname map[int]string + compressor Compressor + + uncompressedCounter *countWriteFlusher + + // ChunkSize optionally controls the maximum number of bytes + // of data of a regular file that can be written in one gzip + // stream before a new gzip stream is started. + // Zero means to use a default, currently 4 MiB. + ChunkSize int + + // MinChunkSize optionally controls the minimum number of bytes + // of data must be written in one gzip stream before a new gzip + // NOTE: This adds a TOC property that stargz snapshotter < v0.13.0 doesn't understand. + MinChunkSize int + + needsOpenGzEntries map[string]struct{} +} + +// currentCompressionWriter writes to the current w.gz field, which can +// change throughout writing a tar entry. +// +// Additionally, it updates w's SHA-256 of the uncompressed bytes +// of the tar file. +type currentCompressionWriter struct{ w *Writer } + +func (ccw currentCompressionWriter) Write(p []byte) (int, error) { + ccw.w.diffHash.Write(p) + if ccw.w.gz == nil { + if err := ccw.w.condOpenGz(); err != nil { + return 0, err + } + } + return ccw.w.gz.Write(p) +} + +func (w *Writer) chunkSize() int { + if w.ChunkSize <= 0 { + return 4 << 20 + } + return w.ChunkSize +} + +// Unpack decompresses the given estargz blob and returns a ReadCloser of the tar blob. +// TOC JSON and footer are removed. +func Unpack(sr *io.SectionReader, c Decompressor) (io.ReadCloser, error) { + footerSize := c.FooterSize() + if sr.Size() < footerSize { + return nil, fmt.Errorf("blob is too small; %d < %d", sr.Size(), footerSize) + } + footerOffset := sr.Size() - footerSize + footer := make([]byte, footerSize) + if _, err := sr.ReadAt(footer, footerOffset); err != nil { + return nil, err + } + blobPayloadSize, _, _, err := c.ParseFooter(footer) + if err != nil { + return nil, fmt.Errorf("failed to parse footer: %w", err) + } + if blobPayloadSize < 0 { + blobPayloadSize = sr.Size() + } + return c.Reader(io.LimitReader(sr, blobPayloadSize)) +} + +// NewWriter returns a new stargz writer (gzip-based) writing to w. +// +// The writer must be closed to write its trailing table of contents. +func NewWriter(w io.Writer) *Writer { + return NewWriterLevel(w, gzip.BestCompression) +} + +// NewWriterLevel returns a new stargz writer (gzip-based) writing to w. +// The compression level is configurable. +// +// The writer must be closed to write its trailing table of contents. +func NewWriterLevel(w io.Writer, compressionLevel int) *Writer { + return NewWriterWithCompressor(w, NewGzipCompressorWithLevel(compressionLevel)) +} + +// NewWriterWithCompressor returns a new stargz writer writing to w. +// The compression method is configurable. +// +// The writer must be closed to write its trailing table of contents. +func NewWriterWithCompressor(w io.Writer, c Compressor) *Writer { + bw := bufio.NewWriter(w) + cw := &countWriter{w: bw} + return &Writer{ + bw: bw, + cw: cw, + toc: &JTOC{Version: 1}, + diffHash: sha256.New(), + compressor: c, + uncompressedCounter: &countWriteFlusher{}, + } +} + +// Close writes the stargz's table of contents and flushes all the +// buffers, returning any error. +func (w *Writer) Close() (digest.Digest, error) { + if w.closed { + return "", nil + } + defer func() { w.closed = true }() + + if err := w.closeGz(); err != nil { + return "", err + } + + // Write the TOC index and footer. + tocDigest, err := w.compressor.WriteTOCAndFooter(w.cw, w.cw.n, w.toc, w.diffHash) + if err != nil { + return "", err + } + if err := w.bw.Flush(); err != nil { + return "", err + } + + return tocDigest, nil +} + +func (w *Writer) closeGz() error { + if w.closed { + return errors.New("write on closed Writer") + } + if w.gz != nil { + if err := w.gz.Close(); err != nil { + return err + } + w.gz = nil + } + return nil +} + +func (w *Writer) flushGz() error { + if w.closed { + return errors.New("flush on closed Writer") + } + if w.gz != nil { + if f, ok := w.gz.(interface { + Flush() error + }); ok { + return f.Flush() + } + } + return nil +} + +// nameIfChanged returns name, unless it was the already the value of (*mp)[id], +// in which case it returns the empty string. +func (w *Writer) nameIfChanged(mp *map[int]string, id int, name string) string { + if name == "" { + return "" + } + if *mp == nil { + *mp = make(map[int]string) + } + if (*mp)[id] == name { + return "" + } + (*mp)[id] = name + return name +} + +func (w *Writer) condOpenGz() (err error) { + if w.gz == nil { + w.gz, err = w.compressor.Writer(w.cw) + if w.gz != nil { + w.gz = w.uncompressedCounter.register(w.gz) + } + } + return +} + +// AppendTar reads the tar or tar.gz file from r and appends +// each of its contents to w. +// +// The input r can optionally be gzip compressed but the output will +// always be compressed by the specified compressor. +func (w *Writer) AppendTar(r io.Reader) error { + return w.appendTar(r, false) +} + +// AppendTarLossLess reads the tar or tar.gz file from r and appends +// each of its contents to w. +// +// The input r can optionally be gzip compressed but the output will +// always be compressed by the specified compressor. +// +// The difference of this func with AppendTar is that this writes +// the input tar stream into w without any modification (e.g. to header bytes). +// +// Note that if the input tar stream already contains TOC JSON, this returns +// error because w cannot overwrite the TOC JSON to the one generated by w without +// lossy modification. To avoid this error, if the input stream is known to be stargz/estargz, +// you shoud decompress it and remove TOC JSON in advance. +func (w *Writer) AppendTarLossLess(r io.Reader) error { + return w.appendTar(r, true) +} + +func (w *Writer) appendTar(r io.Reader, lossless bool) error { + var src io.Reader + br := bufio.NewReader(r) + if isGzip(br) { + zr, _ := gzip.NewReader(br) + src = zr + } else { + src = io.Reader(br) + } + dst := currentCompressionWriter{w} + var tw *tar.Writer + if !lossless { + tw = tar.NewWriter(dst) // use tar writer only when this isn't lossless mode. + } + tr := tar.NewReader(src) + if lossless { + tr.RawAccounting = true + } + prevOffset := w.cw.n + var prevOffsetUncompressed int64 + for { + h, err := tr.Next() + if err == io.EOF { + if lossless { + if remain := tr.RawBytes(); len(remain) > 0 { + // Collect the remaining null bytes. + // https://github.com/vbatts/tar-split/blob/80a436fd6164c557b131f7c59ed69bd81af69761/concept/main.go#L49-L53 + if _, err := dst.Write(remain); err != nil { + return err + } + } + } + break + } + if err != nil { + return fmt.Errorf("error reading from source tar: tar.Reader.Next: %v", err) + } + if cleanEntryName(h.Name) == TOCTarName { + // It is possible for a layer to be "stargzified" twice during the + // distribution lifecycle. So we reserve "TOCTarName" here to avoid + // duplicated entries in the resulting layer. + if lossless { + // We cannot handle this in lossless way. + return fmt.Errorf("existing TOC JSON is not allowed; decompress layer before append") + } + continue + } + + xattrs := make(map[string][]byte) + const xattrPAXRecordsPrefix = "SCHILY.xattr." + if h.PAXRecords != nil { + for k, v := range h.PAXRecords { + if strings.HasPrefix(k, xattrPAXRecordsPrefix) { + xattrs[k[len(xattrPAXRecordsPrefix):]] = []byte(v) + } + } + } + ent := &TOCEntry{ + Name: h.Name, + Mode: h.Mode, + UID: h.Uid, + GID: h.Gid, + Uname: w.nameIfChanged(&w.lastUsername, h.Uid, h.Uname), + Gname: w.nameIfChanged(&w.lastGroupname, h.Gid, h.Gname), + ModTime3339: formatModtime(h.ModTime), + Xattrs: xattrs, + } + if err := w.condOpenGz(); err != nil { + return err + } + if tw != nil { + if err := tw.WriteHeader(h); err != nil { + return err + } + } else { + if _, err := dst.Write(tr.RawBytes()); err != nil { + return err + } + } + switch h.Typeflag { + case tar.TypeLink: + ent.Type = "hardlink" + ent.LinkName = h.Linkname + case tar.TypeSymlink: + ent.Type = "symlink" + ent.LinkName = h.Linkname + case tar.TypeDir: + ent.Type = "dir" + case tar.TypeReg: + ent.Type = "reg" + ent.Size = h.Size + case tar.TypeChar: + ent.Type = "char" + ent.DevMajor = int(h.Devmajor) + ent.DevMinor = int(h.Devminor) + case tar.TypeBlock: + ent.Type = "block" + ent.DevMajor = int(h.Devmajor) + ent.DevMinor = int(h.Devminor) + case tar.TypeFifo: + ent.Type = "fifo" + default: + return fmt.Errorf("unsupported input tar entry %q", h.Typeflag) + } + + // We need to keep a reference to the TOC entry for regular files, so that we + // can fill the digest later. + var regFileEntry *TOCEntry + var payloadDigest digest.Digester + if h.Typeflag == tar.TypeReg { + regFileEntry = ent + payloadDigest = digest.Canonical.Digester() + } + + if h.Typeflag == tar.TypeReg && ent.Size > 0 { + var written int64 + totalSize := ent.Size // save it before we destroy ent + tee := io.TeeReader(tr, payloadDigest.Hash()) + for written < totalSize { + chunkSize := int64(w.chunkSize()) + remain := totalSize - written + if remain < chunkSize { + chunkSize = remain + } else { + ent.ChunkSize = chunkSize + } + + // We flush the underlying compression writer here to correctly calculate "w.cw.n". + if err := w.flushGz(); err != nil { + return err + } + if w.needsOpenGz(ent) || w.cw.n-prevOffset >= int64(w.MinChunkSize) { + if err := w.closeGz(); err != nil { + return err + } + ent.Offset = w.cw.n + prevOffset = ent.Offset + prevOffsetUncompressed = w.uncompressedCounter.n + } else { + ent.Offset = prevOffset + ent.InnerOffset = w.uncompressedCounter.n - prevOffsetUncompressed + } + + ent.ChunkOffset = written + chunkDigest := digest.Canonical.Digester() + + if err := w.condOpenGz(); err != nil { + return err + } + + teeChunk := io.TeeReader(tee, chunkDigest.Hash()) + var out io.Writer + if tw != nil { + out = tw + } else { + out = dst + } + if _, err := io.CopyN(out, teeChunk, chunkSize); err != nil { + return fmt.Errorf("error copying %q: %v", h.Name, err) + } + ent.ChunkDigest = chunkDigest.Digest().String() + w.toc.Entries = append(w.toc.Entries, ent) + written += chunkSize + ent = &TOCEntry{ + Name: h.Name, + Type: "chunk", + } + } + } else { + w.toc.Entries = append(w.toc.Entries, ent) + } + if payloadDigest != nil { + regFileEntry.Digest = payloadDigest.Digest().String() + } + if tw != nil { + if err := tw.Flush(); err != nil { + return err + } + } + } + remainDest := io.Discard + if lossless { + remainDest = dst // Preserve the remaining bytes in lossless mode + } + _, err := io.Copy(remainDest, src) + return err +} + +func (w *Writer) needsOpenGz(ent *TOCEntry) bool { + if ent.Type != "reg" { + return false + } + if w.needsOpenGzEntries == nil { + return false + } + _, ok := w.needsOpenGzEntries[ent.Name] + return ok +} + +// DiffID returns the SHA-256 of the uncompressed tar bytes. +// It is only valid to call DiffID after Close. +func (w *Writer) DiffID() string { + return fmt.Sprintf("sha256:%x", w.diffHash.Sum(nil)) +} + +func maxFooterSize(blobSize int64, decompressors ...Decompressor) (res int64) { + for _, d := range decompressors { + if s := d.FooterSize(); res < s && s <= blobSize { + res = s + } + } + return +} + +func parseTOC(d Decompressor, sr *io.SectionReader, tocOff, tocSize int64, tocBytes []byte, opts openOpts) (*Reader, error) { + if tocOff < 0 { + // This means that TOC isn't contained in the blob. + // We pass nil reader to ParseTOC and expect that ParseTOC acquire TOC from + // the external location. + start := time.Now() + toc, tocDgst, err := d.ParseTOC(nil) + if err != nil { + return nil, err + } + if opts.telemetry != nil && opts.telemetry.GetTocLatency != nil { + opts.telemetry.GetTocLatency(start) + } + if opts.telemetry != nil && opts.telemetry.DeserializeTocLatency != nil { + opts.telemetry.DeserializeTocLatency(start) + } + return &Reader{ + sr: sr, + toc: toc, + tocDigest: tocDgst, + decompressor: d, + }, nil + } + if len(tocBytes) > 0 { + start := time.Now() + toc, tocDgst, err := d.ParseTOC(bytes.NewReader(tocBytes)) + if err == nil { + if opts.telemetry != nil && opts.telemetry.DeserializeTocLatency != nil { + opts.telemetry.DeserializeTocLatency(start) + } + return &Reader{ + sr: sr, + toc: toc, + tocDigest: tocDgst, + decompressor: d, + }, nil + } + } + + start := time.Now() + tocBytes = make([]byte, tocSize) + if _, err := sr.ReadAt(tocBytes, tocOff); err != nil { + return nil, fmt.Errorf("error reading %d byte TOC targz: %v", len(tocBytes), err) + } + if opts.telemetry != nil && opts.telemetry.GetTocLatency != nil { + opts.telemetry.GetTocLatency(start) + } + start = time.Now() + toc, tocDgst, err := d.ParseTOC(bytes.NewReader(tocBytes)) + if err != nil { + return nil, err + } + if opts.telemetry != nil && opts.telemetry.DeserializeTocLatency != nil { + opts.telemetry.DeserializeTocLatency(start) + } + return &Reader{ + sr: sr, + toc: toc, + tocDigest: tocDgst, + decompressor: d, + }, nil +} + +func formatModtime(t time.Time) string { + if t.IsZero() || t.Unix() == 0 { + return "" + } + return t.UTC().Round(time.Second).Format(time.RFC3339) +} + +func cleanEntryName(name string) string { + // Use path.Clean to consistently deal with path separators across platforms. + return strings.TrimPrefix(path.Clean("/"+name), "/") +} + +// countWriter counts how many bytes have been written to its wrapped +// io.Writer. +type countWriter struct { + w io.Writer + n int64 +} + +func (cw *countWriter) Write(p []byte) (n int, err error) { + n, err = cw.w.Write(p) + cw.n += int64(n) + return +} + +type countWriteFlusher struct { + io.WriteCloser + n int64 +} + +func (wc *countWriteFlusher) register(w io.WriteCloser) io.WriteCloser { + wc.WriteCloser = w + return wc +} + +func (wc *countWriteFlusher) Write(p []byte) (n int, err error) { + n, err = wc.WriteCloser.Write(p) + wc.n += int64(n) + return +} + +func (wc *countWriteFlusher) Flush() error { + if f, ok := wc.WriteCloser.(interface { + Flush() error + }); ok { + return f.Flush() + } + return nil +} + +func (wc *countWriteFlusher) Close() error { + err := wc.WriteCloser.Close() + wc.WriteCloser = nil + return err +} + +// isGzip reports whether br is positioned right before an upcoming gzip stream. +// It does not consume any bytes from br. +func isGzip(br *bufio.Reader) bool { + const ( + gzipID1 = 0x1f + gzipID2 = 0x8b + gzipDeflate = 8 + ) + peek, _ := br.Peek(3) + return len(peek) >= 3 && peek[0] == gzipID1 && peek[1] == gzipID2 && peek[2] == gzipDeflate +} + +func positive(n int64) int64 { + if n < 0 { + return 0 + } + return n +} + +type countReader struct { + r io.Reader + n int64 +} + +func (cr *countReader) Read(p []byte) (n int, err error) { + n, err = cr.r.Read(p) + cr.n += int64(n) + return +} diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/gzip.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/gzip.go new file mode 100644 index 00000000..88fa13b1 --- /dev/null +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/gzip.go @@ -0,0 +1,237 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright 2019 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +*/ + +package estargz + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "encoding/binary" + "encoding/json" + "fmt" + "hash" + "io" + "strconv" + + digest "github.com/opencontainers/go-digest" +) + +type gzipCompression struct { + *GzipCompressor + *GzipDecompressor +} + +func newGzipCompressionWithLevel(level int) Compression { + return &gzipCompression{ + &GzipCompressor{level}, + &GzipDecompressor{}, + } +} + +func NewGzipCompressor() *GzipCompressor { + return &GzipCompressor{gzip.BestCompression} +} + +func NewGzipCompressorWithLevel(level int) *GzipCompressor { + return &GzipCompressor{level} +} + +type GzipCompressor struct { + compressionLevel int +} + +func (gc *GzipCompressor) Writer(w io.Writer) (WriteFlushCloser, error) { + return gzip.NewWriterLevel(w, gc.compressionLevel) +} + +func (gc *GzipCompressor) WriteTOCAndFooter(w io.Writer, off int64, toc *JTOC, diffHash hash.Hash) (digest.Digest, error) { + tocJSON, err := json.MarshalIndent(toc, "", "\t") + if err != nil { + return "", err + } + gz, _ := gzip.NewWriterLevel(w, gc.compressionLevel) + gw := io.Writer(gz) + if diffHash != nil { + gw = io.MultiWriter(gz, diffHash) + } + tw := tar.NewWriter(gw) + if err := tw.WriteHeader(&tar.Header{ + Typeflag: tar.TypeReg, + Name: TOCTarName, + Size: int64(len(tocJSON)), + }); err != nil { + return "", err + } + if _, err := tw.Write(tocJSON); err != nil { + return "", err + } + + if err := tw.Close(); err != nil { + return "", err + } + if err := gz.Close(); err != nil { + return "", err + } + if _, err := w.Write(gzipFooterBytes(off)); err != nil { + return "", err + } + return digest.FromBytes(tocJSON), nil +} + +// gzipFooterBytes returns the 51 bytes footer. +func gzipFooterBytes(tocOff int64) []byte { + buf := bytes.NewBuffer(make([]byte, 0, FooterSize)) + gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression) // MUST be NoCompression to keep 51 bytes + + // Extra header indicating the offset of TOCJSON + // https://tools.ietf.org/html/rfc1952#section-2.3.1.1 + header := make([]byte, 4) + header[0], header[1] = 'S', 'G' + subfield := fmt.Sprintf("%016xSTARGZ", tocOff) + binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952 + gz.Extra = append(header, []byte(subfield)...) + gz.Close() + if buf.Len() != FooterSize { + panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), FooterSize)) + } + return buf.Bytes() +} + +type GzipDecompressor struct{} + +func (gz *GzipDecompressor) Reader(r io.Reader) (io.ReadCloser, error) { + return gzip.NewReader(r) +} + +func (gz *GzipDecompressor) ParseTOC(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) { + return parseTOCEStargz(r) +} + +func (gz *GzipDecompressor) ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error) { + if len(p) != FooterSize { + return 0, 0, 0, fmt.Errorf("invalid length %d cannot be parsed", len(p)) + } + zr, err := gzip.NewReader(bytes.NewReader(p)) + if err != nil { + return 0, 0, 0, err + } + defer zr.Close() + extra := zr.Extra + si1, si2, subfieldlen, subfield := extra[0], extra[1], extra[2:4], extra[4:] + if si1 != 'S' || si2 != 'G' { + return 0, 0, 0, fmt.Errorf("invalid subfield IDs: %q, %q; want E, S", si1, si2) + } + if slen := binary.LittleEndian.Uint16(subfieldlen); slen != uint16(16+len("STARGZ")) { + return 0, 0, 0, fmt.Errorf("invalid length of subfield %d; want %d", slen, 16+len("STARGZ")) + } + if string(subfield[16:]) != "STARGZ" { + return 0, 0, 0, fmt.Errorf("STARGZ magic string must be included in the footer subfield") + } + tocOffset, err = strconv.ParseInt(string(subfield[:16]), 16, 64) + if err != nil { + return 0, 0, 0, fmt.Errorf("legacy: failed to parse toc offset: %w", err) + } + return tocOffset, tocOffset, 0, nil +} + +func (gz *GzipDecompressor) FooterSize() int64 { + return FooterSize +} + +func (gz *GzipDecompressor) DecompressTOC(r io.Reader) (tocJSON io.ReadCloser, err error) { + return decompressTOCEStargz(r) +} + +type LegacyGzipDecompressor struct{} + +func (gz *LegacyGzipDecompressor) Reader(r io.Reader) (io.ReadCloser, error) { + return gzip.NewReader(r) +} + +func (gz *LegacyGzipDecompressor) ParseTOC(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) { + return parseTOCEStargz(r) +} + +func (gz *LegacyGzipDecompressor) ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error) { + if len(p) != legacyFooterSize { + return 0, 0, 0, fmt.Errorf("legacy: invalid length %d cannot be parsed", len(p)) + } + zr, err := gzip.NewReader(bytes.NewReader(p)) + if err != nil { + return 0, 0, 0, fmt.Errorf("legacy: failed to get footer gzip reader: %w", err) + } + defer zr.Close() + extra := zr.Extra + if len(extra) != 16+len("STARGZ") { + return 0, 0, 0, fmt.Errorf("legacy: invalid stargz's extra field size") + } + if string(extra[16:]) != "STARGZ" { + return 0, 0, 0, fmt.Errorf("legacy: magic string STARGZ not found") + } + tocOffset, err = strconv.ParseInt(string(extra[:16]), 16, 64) + if err != nil { + return 0, 0, 0, fmt.Errorf("legacy: failed to parse toc offset: %w", err) + } + return tocOffset, tocOffset, 0, nil +} + +func (gz *LegacyGzipDecompressor) FooterSize() int64 { + return legacyFooterSize +} + +func (gz *LegacyGzipDecompressor) DecompressTOC(r io.Reader) (tocJSON io.ReadCloser, err error) { + return decompressTOCEStargz(r) +} + +func parseTOCEStargz(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) { + tr, err := decompressTOCEStargz(r) + if err != nil { + return nil, "", err + } + dgstr := digest.Canonical.Digester() + toc = new(JTOC) + if err := json.NewDecoder(io.TeeReader(tr, dgstr.Hash())).Decode(&toc); err != nil { + return nil, "", fmt.Errorf("error decoding TOC JSON: %v", err) + } + if err := tr.Close(); err != nil { + return nil, "", err + } + return toc, dgstr.Digest(), nil +} + +func decompressTOCEStargz(r io.Reader) (tocJSON io.ReadCloser, err error) { + zr, err := gzip.NewReader(r) + if err != nil { + return nil, fmt.Errorf("malformed TOC gzip header: %v", err) + } + zr.Multistream(false) + tr := tar.NewReader(zr) + h, err := tr.Next() + if err != nil { + return nil, fmt.Errorf("failed to find tar header in TOC gzip stream: %v", err) + } + if h.Name != TOCTarName { + return nil, fmt.Errorf("TOC tar entry had name %q; expected %q", h.Name, TOCTarName) + } + return readCloser{tr, zr.Close}, nil +} diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go new file mode 100644 index 00000000..a8dcdb86 --- /dev/null +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go @@ -0,0 +1,2370 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright 2019 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +*/ + +package estargz + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "crypto/rand" + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "os" + "path/filepath" + "reflect" + "sort" + "strings" + "testing" + "time" + + "github.com/containerd/stargz-snapshotter/estargz/errorutil" + "github.com/klauspost/compress/zstd" + digest "github.com/opencontainers/go-digest" +) + +// TestingController is Compression with some helper methods necessary for testing. +type TestingController interface { + Compression + TestStreams(t *testing.T, b []byte, streams []int64) + DiffIDOf(*testing.T, []byte) string + String() string +} + +// CompressionTestSuite tests this pkg with controllers can build valid eStargz blobs and parse them. +func CompressionTestSuite(t *testing.T, controllers ...TestingControllerFactory) { + t.Run("testBuild", func(t *testing.T) { t.Parallel(); testBuild(t, controllers...) }) + t.Run("testDigestAndVerify", func(t *testing.T) { t.Parallel(); testDigestAndVerify(t, controllers...) }) + t.Run("testWriteAndOpen", func(t *testing.T) { t.Parallel(); testWriteAndOpen(t, controllers...) }) +} + +type TestingControllerFactory func() TestingController + +const ( + uncompressedType int = iota + gzipType + zstdType +) + +var srcCompressions = []int{ + uncompressedType, + gzipType, + zstdType, +} + +var allowedPrefix = [4]string{"", "./", "/", "../"} + +// testBuild tests the resulting stargz blob built by this pkg has the same +// contents as the normal stargz blob. +func testBuild(t *testing.T, controllers ...TestingControllerFactory) { + tests := []struct { + name string + chunkSize int + minChunkSize []int + in []tarEntry + }{ + { + name: "regfiles and directories", + chunkSize: 4, + in: tarOf( + file("foo", "test1"), + dir("foo2/"), + file("foo2/bar", "test2", xAttr(map[string]string{"test": "sample"})), + ), + }, + { + name: "empty files", + chunkSize: 4, + in: tarOf( + file("foo", "tttttt"), + file("foo_empty", ""), + file("foo2", "tttttt"), + file("foo_empty2", ""), + file("foo3", "tttttt"), + file("foo_empty3", ""), + file("foo4", "tttttt"), + file("foo_empty4", ""), + file("foo5", "tttttt"), + file("foo_empty5", ""), + file("foo6", "tttttt"), + ), + }, + { + name: "various files", + chunkSize: 4, + minChunkSize: []int{0, 64000}, + in: tarOf( + file("baz.txt", "bazbazbazbazbazbazbaz"), + file("foo1.txt", "a"), + file("bar/foo2.txt", "b"), + file("foo3.txt", "c"), + symlink("barlink", "test/bar.txt"), + dir("test/"), + dir("dev/"), + blockdev("dev/testblock", 3, 4), + fifo("dev/testfifo"), + chardev("dev/testchar1", 5, 6), + file("test/bar.txt", "testbartestbar", xAttr(map[string]string{"test2": "sample2"})), + dir("test2/"), + link("test2/bazlink", "baz.txt"), + chardev("dev/testchar2", 1, 2), + ), + }, + { + name: "no contents", + chunkSize: 4, + in: tarOf( + file("baz.txt", ""), + symlink("barlink", "test/bar.txt"), + dir("test/"), + dir("dev/"), + blockdev("dev/testblock", 3, 4), + fifo("dev/testfifo"), + chardev("dev/testchar1", 5, 6), + file("test/bar.txt", "", xAttr(map[string]string{"test2": "sample2"})), + dir("test2/"), + link("test2/bazlink", "baz.txt"), + chardev("dev/testchar2", 1, 2), + ), + }, + } + for _, tt := range tests { + if len(tt.minChunkSize) == 0 { + tt.minChunkSize = []int{0} + } + for _, srcCompression := range srcCompressions { + srcCompression := srcCompression + for _, newCL := range controllers { + newCL := newCL + for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} { + srcTarFormat := srcTarFormat + for _, prefix := range allowedPrefix { + prefix := prefix + for _, minChunkSize := range tt.minChunkSize { + minChunkSize := minChunkSize + t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,src=%d,format=%s,minChunkSize=%d", newCL(), prefix, srcCompression, srcTarFormat, minChunkSize), func(t *testing.T) { + tarBlob := buildTar(t, tt.in, prefix, srcTarFormat) + // Test divideEntries() + entries, err := sortEntries(tarBlob, nil, nil) // identical order + if err != nil { + t.Fatalf("failed to parse tar: %v", err) + } + var merged []*entry + for _, part := range divideEntries(entries, 4) { + merged = append(merged, part...) + } + if !reflect.DeepEqual(entries, merged) { + for _, e := range entries { + t.Logf("Original: %v", e.header) + } + for _, e := range merged { + t.Logf("Merged: %v", e.header) + } + t.Errorf("divided entries couldn't be merged") + return + } + + // Prepare sample data + cl1 := newCL() + wantBuf := new(bytes.Buffer) + sw := NewWriterWithCompressor(wantBuf, cl1) + sw.MinChunkSize = minChunkSize + sw.ChunkSize = tt.chunkSize + if err := sw.AppendTar(tarBlob); err != nil { + t.Fatalf("failed to append tar to want stargz: %v", err) + } + if _, err := sw.Close(); err != nil { + t.Fatalf("failed to prepare want stargz: %v", err) + } + wantData := wantBuf.Bytes() + want, err := Open(io.NewSectionReader( + bytes.NewReader(wantData), 0, int64(len(wantData))), + WithDecompressors(cl1), + ) + if err != nil { + t.Fatalf("failed to parse the want stargz: %v", err) + } + + // Prepare testing data + var opts []Option + if minChunkSize > 0 { + opts = append(opts, WithMinChunkSize(minChunkSize)) + } + cl2 := newCL() + rc, err := Build(compressBlob(t, tarBlob, srcCompression), + append(opts, WithChunkSize(tt.chunkSize), WithCompression(cl2))...) + if err != nil { + t.Fatalf("failed to build stargz: %v", err) + } + defer rc.Close() + gotBuf := new(bytes.Buffer) + if _, err := io.Copy(gotBuf, rc); err != nil { + t.Fatalf("failed to copy built stargz blob: %v", err) + } + gotData := gotBuf.Bytes() + got, err := Open(io.NewSectionReader( + bytes.NewReader(gotBuf.Bytes()), 0, int64(len(gotData))), + WithDecompressors(cl2), + ) + if err != nil { + t.Fatalf("failed to parse the got stargz: %v", err) + } + + // Check DiffID is properly calculated + rc.Close() + diffID := rc.DiffID() + wantDiffID := cl2.DiffIDOf(t, gotData) + if diffID.String() != wantDiffID { + t.Errorf("DiffID = %q; want %q", diffID, wantDiffID) + } + + // Compare as stargz + if !isSameVersion(t, cl1, wantData, cl2, gotData) { + t.Errorf("built stargz hasn't same json") + return + } + if !isSameEntries(t, want, got) { + t.Errorf("built stargz isn't same as the original") + return + } + + // Compare as tar.gz + if !isSameTarGz(t, cl1, wantData, cl2, gotData) { + t.Errorf("built stargz isn't same tar.gz") + return + } + }) + } + } + } + } + } + } +} + +func isSameTarGz(t *testing.T, cla TestingController, a []byte, clb TestingController, b []byte) bool { + aGz, err := cla.Reader(bytes.NewReader(a)) + if err != nil { + t.Fatalf("failed to read A") + } + defer aGz.Close() + bGz, err := clb.Reader(bytes.NewReader(b)) + if err != nil { + t.Fatalf("failed to read B") + } + defer bGz.Close() + + // Same as tar's Next() method but ignores landmarks and TOCJSON file + next := func(r *tar.Reader) (h *tar.Header, err error) { + for { + if h, err = r.Next(); err != nil { + return + } + if h.Name != PrefetchLandmark && + h.Name != NoPrefetchLandmark && + h.Name != TOCTarName { + return + } + } + } + + aTar := tar.NewReader(aGz) + bTar := tar.NewReader(bGz) + for { + // Fetch and parse next header. + aH, aErr := next(aTar) + bH, bErr := next(bTar) + if aErr != nil || bErr != nil { + if aErr == io.EOF && bErr == io.EOF { + break + } + t.Fatalf("Failed to parse tar file: A: %v, B: %v", aErr, bErr) + } + if !reflect.DeepEqual(aH, bH) { + t.Logf("different header (A = %v; B = %v)", aH, bH) + return false + + } + aFile, err := io.ReadAll(aTar) + if err != nil { + t.Fatal("failed to read tar payload of A") + } + bFile, err := io.ReadAll(bTar) + if err != nil { + t.Fatal("failed to read tar payload of B") + } + if !bytes.Equal(aFile, bFile) { + t.Logf("different tar payload (A = %q; B = %q)", string(a), string(b)) + return false + } + } + + return true +} + +func isSameVersion(t *testing.T, cla TestingController, a []byte, clb TestingController, b []byte) bool { + aJTOC, _, err := parseStargz(io.NewSectionReader(bytes.NewReader(a), 0, int64(len(a))), cla) + if err != nil { + t.Fatalf("failed to parse A: %v", err) + } + bJTOC, _, err := parseStargz(io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))), clb) + if err != nil { + t.Fatalf("failed to parse B: %v", err) + } + t.Logf("A: TOCJSON: %v", dumpTOCJSON(t, aJTOC)) + t.Logf("B: TOCJSON: %v", dumpTOCJSON(t, bJTOC)) + return aJTOC.Version == bJTOC.Version +} + +func isSameEntries(t *testing.T, a, b *Reader) bool { + aroot, ok := a.Lookup("") + if !ok { + t.Fatalf("failed to get root of A") + } + broot, ok := b.Lookup("") + if !ok { + t.Fatalf("failed to get root of B") + } + aEntry := stargzEntry{aroot, a} + bEntry := stargzEntry{broot, b} + return contains(t, aEntry, bEntry) && contains(t, bEntry, aEntry) +} + +func compressBlob(t *testing.T, src *io.SectionReader, srcCompression int) *io.SectionReader { + buf := new(bytes.Buffer) + var w io.WriteCloser + var err error + switch srcCompression { + case gzipType: + w = gzip.NewWriter(buf) + case zstdType: + w, err = zstd.NewWriter(buf) + if err != nil { + t.Fatalf("failed to init zstd writer: %v", err) + } + default: + return src + } + src.Seek(0, io.SeekStart) + if _, err := io.Copy(w, src); err != nil { + t.Fatalf("failed to compress source") + } + if err := w.Close(); err != nil { + t.Fatalf("failed to finalize compress source") + } + data := buf.Bytes() + return io.NewSectionReader(bytes.NewReader(data), 0, int64(len(data))) + +} + +type stargzEntry struct { + e *TOCEntry + r *Reader +} + +// contains checks if all child entries in "b" are also contained in "a". +// This function also checks if the files/chunks contain the same contents among "a" and "b". +func contains(t *testing.T, a, b stargzEntry) bool { + ae, ar := a.e, a.r + be, br := b.e, b.r + t.Logf("Comparing: %q vs %q", ae.Name, be.Name) + if !equalEntry(ae, be) { + t.Logf("%q != %q: entry: a: %v, b: %v", ae.Name, be.Name, ae, be) + return false + } + if ae.Type == "dir" { + t.Logf("Directory: %q vs %q: %v vs %v", ae.Name, be.Name, + allChildrenName(ae), allChildrenName(be)) + iscontain := true + ae.ForeachChild(func(aBaseName string, aChild *TOCEntry) bool { + // Walk through all files on this stargz file. + + if aChild.Name == PrefetchLandmark || + aChild.Name == NoPrefetchLandmark { + return true // Ignore landmarks + } + + // Ignore a TOCEntry of "./" (formated as "" by stargz lib) on root directory + // because this points to the root directory itself. + if aChild.Name == "" && ae.Name == "" { + return true + } + + bChild, ok := be.LookupChild(aBaseName) + if !ok { + t.Logf("%q (base: %q): not found in b: %v", + ae.Name, aBaseName, allChildrenName(be)) + iscontain = false + return false + } + + childcontain := contains(t, stargzEntry{aChild, a.r}, stargzEntry{bChild, b.r}) + if !childcontain { + t.Logf("%q != %q: non-equal dir", ae.Name, be.Name) + iscontain = false + return false + } + return true + }) + return iscontain + } else if ae.Type == "reg" { + af, err := ar.OpenFile(ae.Name) + if err != nil { + t.Fatalf("failed to open file %q on A: %v", ae.Name, err) + } + bf, err := br.OpenFile(be.Name) + if err != nil { + t.Fatalf("failed to open file %q on B: %v", be.Name, err) + } + + var nr int64 + for nr < ae.Size { + abytes, anext, aok := readOffset(t, af, nr, a) + bbytes, bnext, bok := readOffset(t, bf, nr, b) + if !aok && !bok { + break + } else if !aok || !bok || anext != bnext { + t.Logf("%q != %q (offset=%d): chunk existence a=%v vs b=%v, anext=%v vs bnext=%v", + ae.Name, be.Name, nr, aok, bok, anext, bnext) + return false + } + nr = anext + if !bytes.Equal(abytes, bbytes) { + t.Logf("%q != %q: different contents %v vs %v", + ae.Name, be.Name, string(abytes), string(bbytes)) + return false + } + } + return true + } + + return true +} + +func allChildrenName(e *TOCEntry) (children []string) { + e.ForeachChild(func(baseName string, _ *TOCEntry) bool { + children = append(children, baseName) + return true + }) + return +} + +func equalEntry(a, b *TOCEntry) bool { + // Here, we selectively compare fileds that we are interested in. + return a.Name == b.Name && + a.Type == b.Type && + a.Size == b.Size && + a.ModTime3339 == b.ModTime3339 && + a.Stat().ModTime().Equal(b.Stat().ModTime()) && // modTime time.Time + a.LinkName == b.LinkName && + a.Mode == b.Mode && + a.UID == b.UID && + a.GID == b.GID && + a.Uname == b.Uname && + a.Gname == b.Gname && + (a.Offset >= 0) == (b.Offset >= 0) && + (a.NextOffset() > 0) == (b.NextOffset() > 0) && + a.DevMajor == b.DevMajor && + a.DevMinor == b.DevMinor && + a.NumLink == b.NumLink && + reflect.DeepEqual(a.Xattrs, b.Xattrs) && + // chunk-related infomations aren't compared in this function. + // ChunkOffset int64 `json:"chunkOffset,omitempty"` + // ChunkSize int64 `json:"chunkSize,omitempty"` + // children map[string]*TOCEntry + a.Digest == b.Digest +} + +func readOffset(t *testing.T, r *io.SectionReader, offset int64, e stargzEntry) ([]byte, int64, bool) { + ce, ok := e.r.ChunkEntryForOffset(e.e.Name, offset) + if !ok { + return nil, 0, false + } + data := make([]byte, ce.ChunkSize) + t.Logf("Offset: %v, NextOffset: %v", ce.Offset, ce.NextOffset()) + n, err := r.ReadAt(data, ce.ChunkOffset) + if err != nil { + t.Fatalf("failed to read file payload of %q (offset:%d,size:%d): %v", + e.e.Name, ce.ChunkOffset, ce.ChunkSize, err) + } + if int64(n) != ce.ChunkSize { + t.Fatalf("unexpected copied data size %d; want %d", + n, ce.ChunkSize) + } + return data[:n], offset + ce.ChunkSize, true +} + +func dumpTOCJSON(t *testing.T, tocJSON *JTOC) string { + jtocData, err := json.Marshal(*tocJSON) + if err != nil { + t.Fatalf("failed to marshal TOC JSON: %v", err) + } + buf := new(bytes.Buffer) + if _, err := io.Copy(buf, bytes.NewReader(jtocData)); err != nil { + t.Fatalf("failed to read toc json blob: %v", err) + } + return buf.String() +} + +const chunkSize = 3 + +// type check func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, compressionLevel int) +type check func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) + +// testDigestAndVerify runs specified checks against sample stargz blobs. +func testDigestAndVerify(t *testing.T, controllers ...TestingControllerFactory) { + tests := []struct { + name string + tarInit func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) + checks []check + minChunkSize []int + }{ + { + name: "no-regfile", + tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { + return tarOf( + dir("test/"), + ) + }, + checks: []check{ + checkStargzTOC, + checkVerifyTOC, + checkVerifyInvalidStargzFail(buildTar(t, tarOf( + dir("test2/"), // modified + ), allowedPrefix[0])), + }, + }, + { + name: "small-files", + tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { + return tarOf( + regDigest(t, "baz.txt", "", dgstMap), + regDigest(t, "foo.txt", "a", dgstMap), + dir("test/"), + regDigest(t, "test/bar.txt", "bbb", dgstMap), + ) + }, + minChunkSize: []int{0, 64000}, + checks: []check{ + checkStargzTOC, + checkVerifyTOC, + checkVerifyInvalidStargzFail(buildTar(t, tarOf( + file("baz.txt", ""), + file("foo.txt", "M"), // modified + dir("test/"), + file("test/bar.txt", "bbb"), + ), allowedPrefix[0])), + // checkVerifyInvalidTOCEntryFail("foo.txt"), // TODO + checkVerifyBrokenContentFail("foo.txt"), + }, + }, + { + name: "big-files", + tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { + return tarOf( + regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap), + regDigest(t, "foo.txt", "a", dgstMap), + dir("test/"), + regDigest(t, "test/bar.txt", "testbartestbar", dgstMap), + ) + }, + checks: []check{ + checkStargzTOC, + checkVerifyTOC, + checkVerifyInvalidStargzFail(buildTar(t, tarOf( + file("baz.txt", "bazbazbazMMMbazbazbaz"), // modified + file("foo.txt", "a"), + dir("test/"), + file("test/bar.txt", "testbartestbar"), + ), allowedPrefix[0])), + checkVerifyInvalidTOCEntryFail("test/bar.txt"), + checkVerifyBrokenContentFail("test/bar.txt"), + }, + }, + { + name: "with-non-regfiles", + minChunkSize: []int{0, 64000}, + tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { + return tarOf( + regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap), + regDigest(t, "foo.txt", "a", dgstMap), + regDigest(t, "bar/foo2.txt", "b", dgstMap), + regDigest(t, "foo3.txt", "c", dgstMap), + symlink("barlink", "test/bar.txt"), + dir("test/"), + regDigest(t, "test/bar.txt", "testbartestbar", dgstMap), + dir("test2/"), + link("test2/bazlink", "baz.txt"), + ) + }, + checks: []check{ + checkStargzTOC, + checkVerifyTOC, + checkVerifyInvalidStargzFail(buildTar(t, tarOf( + file("baz.txt", "bazbazbazbazbazbazbaz"), + file("foo.txt", "a"), + file("bar/foo2.txt", "b"), + file("foo3.txt", "c"), + symlink("barlink", "test/bar.txt"), + dir("test/"), + file("test/bar.txt", "testbartestbar"), + dir("test2/"), + link("test2/bazlink", "foo.txt"), // modified + ), allowedPrefix[0])), + checkVerifyInvalidTOCEntryFail("test/bar.txt"), + checkVerifyBrokenContentFail("test/bar.txt"), + }, + }, + } + + for _, tt := range tests { + if len(tt.minChunkSize) == 0 { + tt.minChunkSize = []int{0} + } + for _, srcCompression := range srcCompressions { + srcCompression := srcCompression + for _, newCL := range controllers { + newCL := newCL + for _, prefix := range allowedPrefix { + prefix := prefix + for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} { + srcTarFormat := srcTarFormat + for _, minChunkSize := range tt.minChunkSize { + minChunkSize := minChunkSize + t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,format=%s,minChunkSize=%d", newCL(), prefix, srcTarFormat, minChunkSize), func(t *testing.T) { + // Get original tar file and chunk digests + dgstMap := make(map[string]digest.Digest) + tarBlob := buildTar(t, tt.tarInit(t, dgstMap), prefix, srcTarFormat) + + cl := newCL() + rc, err := Build(compressBlob(t, tarBlob, srcCompression), + WithChunkSize(chunkSize), WithCompression(cl)) + if err != nil { + t.Fatalf("failed to convert stargz: %v", err) + } + tocDigest := rc.TOCDigest() + defer rc.Close() + buf := new(bytes.Buffer) + if _, err := io.Copy(buf, rc); err != nil { + t.Fatalf("failed to copy built stargz blob: %v", err) + } + newStargz := buf.Bytes() + // NoPrefetchLandmark is added during `Bulid`, which is expected behaviour. + dgstMap[chunkID(NoPrefetchLandmark, 0, int64(len([]byte{landmarkContents})))] = digest.FromBytes([]byte{landmarkContents}) + + for _, check := range tt.checks { + check(t, newStargz, tocDigest, dgstMap, cl, newCL) + } + }) + } + } + } + } + } + } +} + +// checkStargzTOC checks the TOC JSON of the passed stargz has the expected +// digest and contains valid chunks. It walks all entries in the stargz and +// checks all chunk digests stored to the TOC JSON match the actual contents. +func checkStargzTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { + sgz, err := Open( + io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), + WithDecompressors(controller), + ) + if err != nil { + t.Errorf("failed to parse converted stargz: %v", err) + return + } + digestMapTOC, err := listDigests(io.NewSectionReader( + bytes.NewReader(sgzData), 0, int64(len(sgzData))), + controller, + ) + if err != nil { + t.Fatalf("failed to list digest: %v", err) + } + found := make(map[string]bool) + for id := range dgstMap { + found[id] = false + } + zr, err := controller.Reader(bytes.NewReader(sgzData)) + if err != nil { + t.Fatalf("failed to decompress converted stargz: %v", err) + } + defer zr.Close() + tr := tar.NewReader(zr) + for { + h, err := tr.Next() + if err != nil { + if err != io.EOF { + t.Errorf("failed to read tar entry: %v", err) + return + } + break + } + if h.Name == TOCTarName { + // Check the digest of TOC JSON based on the actual contents + // It's sure that TOC JSON exists in this archive because + // Open succeeded. + dgstr := digest.Canonical.Digester() + if _, err := io.Copy(dgstr.Hash(), tr); err != nil { + t.Fatalf("failed to calculate digest of TOC JSON: %v", + err) + } + if dgstr.Digest() != tocDigest { + t.Errorf("invalid TOC JSON %q; want %q", tocDigest, dgstr.Digest()) + } + continue + } + if _, ok := sgz.Lookup(h.Name); !ok { + t.Errorf("lost stargz entry %q in the converted TOC", h.Name) + return + } + var n int64 + for n < h.Size { + ce, ok := sgz.ChunkEntryForOffset(h.Name, n) + if !ok { + t.Errorf("lost chunk %q(offset=%d) in the converted TOC", + h.Name, n) + return + } + + // Get the original digest to make sure the file contents are kept unchanged + // from the original tar, during the whole conversion steps. + id := chunkID(h.Name, n, ce.ChunkSize) + want, ok := dgstMap[id] + if !ok { + t.Errorf("Unexpected chunk %q(offset=%d,size=%d): %v", + h.Name, n, ce.ChunkSize, dgstMap) + return + } + found[id] = true + + // Check the file contents + dgstr := digest.Canonical.Digester() + if _, err := io.CopyN(dgstr.Hash(), tr, ce.ChunkSize); err != nil { + t.Fatalf("failed to calculate digest of %q (offset=%d,size=%d)", + h.Name, n, ce.ChunkSize) + } + if want != dgstr.Digest() { + t.Errorf("Invalid contents in converted stargz %q: %q; want %q", + h.Name, dgstr.Digest(), want) + return + } + + // Check the digest stored in TOC JSON + dgstTOC, ok := digestMapTOC[ce.Offset] + if !ok { + t.Errorf("digest of %q(offset=%d,size=%d,chunkOffset=%d) isn't registered", + h.Name, ce.Offset, ce.ChunkSize, ce.ChunkOffset) + } + if want != dgstTOC { + t.Errorf("Invalid digest in TOCEntry %q: %q; want %q", + h.Name, dgstTOC, want) + return + } + + n += ce.ChunkSize + } + } + + for id, ok := range found { + if !ok { + t.Errorf("required chunk %q not found in the converted stargz: %v", id, found) + } + } +} + +// checkVerifyTOC checks the verification works for the TOC JSON of the passed +// stargz. It walks all entries in the stargz and checks the verifications for +// all chunks work. +func checkVerifyTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { + sgz, err := Open( + io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), + WithDecompressors(controller), + ) + if err != nil { + t.Errorf("failed to parse converted stargz: %v", err) + return + } + ev, err := sgz.VerifyTOC(tocDigest) + if err != nil { + t.Errorf("failed to verify stargz: %v", err) + return + } + + found := make(map[string]bool) + for id := range dgstMap { + found[id] = false + } + zr, err := controller.Reader(bytes.NewReader(sgzData)) + if err != nil { + t.Fatalf("failed to decompress converted stargz: %v", err) + } + defer zr.Close() + tr := tar.NewReader(zr) + for { + h, err := tr.Next() + if err != nil { + if err != io.EOF { + t.Errorf("failed to read tar entry: %v", err) + return + } + break + } + if h.Name == TOCTarName { + continue + } + if _, ok := sgz.Lookup(h.Name); !ok { + t.Errorf("lost stargz entry %q in the converted TOC", h.Name) + return + } + var n int64 + for n < h.Size { + ce, ok := sgz.ChunkEntryForOffset(h.Name, n) + if !ok { + t.Errorf("lost chunk %q(offset=%d) in the converted TOC", + h.Name, n) + return + } + + v, err := ev.Verifier(ce) + if err != nil { + t.Errorf("failed to get verifier for %q(offset=%d)", h.Name, n) + } + + found[chunkID(h.Name, n, ce.ChunkSize)] = true + + // Check the file contents + if _, err := io.CopyN(v, tr, ce.ChunkSize); err != nil { + t.Fatalf("failed to get chunk of %q (offset=%d,size=%d)", + h.Name, n, ce.ChunkSize) + } + if !v.Verified() { + t.Errorf("Invalid contents in converted stargz %q (should be succeeded)", + h.Name) + return + } + n += ce.ChunkSize + } + } + + for id, ok := range found { + if !ok { + t.Errorf("required chunk %q not found in the converted stargz: %v", id, found) + } + } +} + +// checkVerifyInvalidTOCEntryFail checks if misconfigured TOC JSON can be +// detected during the verification and the verification returns an error. +func checkVerifyInvalidTOCEntryFail(filename string) check { + return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { + funcs := map[string]rewriteFunc{ + "lost digest in a entry": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) { + var found bool + for _, e := range toc.Entries { + if cleanEntryName(e.Name) == filename { + if e.Type != "reg" && e.Type != "chunk" { + t.Fatalf("entry %q to break must be regfile or chunk", filename) + } + if e.ChunkDigest == "" { + t.Fatalf("entry %q is already invalid", filename) + } + e.ChunkDigest = "" + found = true + } + } + if !found { + t.Fatalf("rewrite target not found") + } + }, + "duplicated entry offset": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) { + var ( + sampleEntry *TOCEntry + targetEntry *TOCEntry + ) + for _, e := range toc.Entries { + if e.Type == "reg" || e.Type == "chunk" { + if cleanEntryName(e.Name) == filename { + targetEntry = e + } else { + sampleEntry = e + } + } + } + if sampleEntry == nil { + t.Fatalf("TOC must contain at least one regfile or chunk entry other than the rewrite target") + return + } + if targetEntry == nil { + t.Fatalf("rewrite target not found") + return + } + targetEntry.Offset = sampleEntry.Offset + }, + } + + for name, rFunc := range funcs { + t.Run(name, func(t *testing.T) { + newSgz, newTocDigest := rewriteTOCJSON(t, io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), rFunc, controller) + buf := new(bytes.Buffer) + if _, err := io.Copy(buf, newSgz); err != nil { + t.Fatalf("failed to get converted stargz") + } + isgz := buf.Bytes() + + sgz, err := Open( + io.NewSectionReader(bytes.NewReader(isgz), 0, int64(len(isgz))), + WithDecompressors(controller), + ) + if err != nil { + t.Fatalf("failed to parse converted stargz: %v", err) + return + } + _, err = sgz.VerifyTOC(newTocDigest) + if err == nil { + t.Errorf("must fail for invalid TOC") + return + } + }) + } + } +} + +// checkVerifyInvalidStargzFail checks if the verification detects that the +// given stargz file doesn't match to the expected digest and returns error. +func checkVerifyInvalidStargzFail(invalid *io.SectionReader) check { + return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { + cl := newController() + rc, err := Build(invalid, WithChunkSize(chunkSize), WithCompression(cl)) + if err != nil { + t.Fatalf("failed to convert stargz: %v", err) + } + defer rc.Close() + buf := new(bytes.Buffer) + if _, err := io.Copy(buf, rc); err != nil { + t.Fatalf("failed to copy built stargz blob: %v", err) + } + mStargz := buf.Bytes() + + sgz, err := Open( + io.NewSectionReader(bytes.NewReader(mStargz), 0, int64(len(mStargz))), + WithDecompressors(cl), + ) + if err != nil { + t.Fatalf("failed to parse converted stargz: %v", err) + return + } + _, err = sgz.VerifyTOC(tocDigest) + if err == nil { + t.Errorf("must fail for invalid TOC") + return + } + } +} + +// checkVerifyBrokenContentFail checks if the verifier detects broken contents +// that doesn't match to the expected digest and returns error. +func checkVerifyBrokenContentFail(filename string) check { + return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { + // Parse stargz file + sgz, err := Open( + io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), + WithDecompressors(controller), + ) + if err != nil { + t.Fatalf("failed to parse converted stargz: %v", err) + return + } + ev, err := sgz.VerifyTOC(tocDigest) + if err != nil { + t.Fatalf("failed to verify stargz: %v", err) + return + } + + // Open the target file + sr, err := sgz.OpenFile(filename) + if err != nil { + t.Fatalf("failed to open file %q", filename) + } + ce, ok := sgz.ChunkEntryForOffset(filename, 0) + if !ok { + t.Fatalf("lost chunk %q(offset=%d) in the converted TOC", filename, 0) + return + } + if ce.ChunkSize == 0 { + t.Fatalf("file mustn't be empty") + return + } + data := make([]byte, ce.ChunkSize) + if _, err := sr.ReadAt(data, ce.ChunkOffset); err != nil { + t.Errorf("failed to get data of a chunk of %q(offset=%q)", + filename, ce.ChunkOffset) + } + + // Check the broken chunk (must fail) + v, err := ev.Verifier(ce) + if err != nil { + t.Fatalf("failed to get verifier for %q", filename) + } + broken := append([]byte{^data[0]}, data[1:]...) + if _, err := io.CopyN(v, bytes.NewReader(broken), ce.ChunkSize); err != nil { + t.Fatalf("failed to get chunk of %q (offset=%d,size=%d)", + filename, ce.ChunkOffset, ce.ChunkSize) + } + if v.Verified() { + t.Errorf("verification must fail for broken file chunk %q(org:%q,broken:%q)", + filename, data, broken) + } + } +} + +func chunkID(name string, offset, size int64) string { + return fmt.Sprintf("%s-%d-%d", cleanEntryName(name), offset, size) +} + +type rewriteFunc func(t *testing.T, toc *JTOC, sgz *io.SectionReader) + +func rewriteTOCJSON(t *testing.T, sgz *io.SectionReader, rewrite rewriteFunc, controller TestingController) (newSgz io.Reader, tocDigest digest.Digest) { + decodedJTOC, jtocOffset, err := parseStargz(sgz, controller) + if err != nil { + t.Fatalf("failed to extract TOC JSON: %v", err) + } + + rewrite(t, decodedJTOC, sgz) + + tocFooter, tocDigest, err := tocAndFooter(controller, decodedJTOC, jtocOffset) + if err != nil { + t.Fatalf("failed to create toc and footer: %v", err) + } + + // Reconstruct stargz file with the modified TOC JSON + if _, err := sgz.Seek(0, io.SeekStart); err != nil { + t.Fatalf("failed to reset the seek position of stargz: %v", err) + } + return io.MultiReader( + io.LimitReader(sgz, jtocOffset), // Original stargz (before TOC JSON) + tocFooter, // Rewritten TOC and footer + ), tocDigest +} + +func listDigests(sgz *io.SectionReader, controller TestingController) (map[int64]digest.Digest, error) { + decodedJTOC, _, err := parseStargz(sgz, controller) + if err != nil { + return nil, err + } + digestMap := make(map[int64]digest.Digest) + for _, e := range decodedJTOC.Entries { + if e.Type == "reg" || e.Type == "chunk" { + if e.Type == "reg" && e.Size == 0 { + continue // ignores empty file + } + if e.ChunkDigest == "" { + return nil, fmt.Errorf("ChunkDigest of %q(off=%d) not found in TOC JSON", + e.Name, e.Offset) + } + d, err := digest.Parse(e.ChunkDigest) + if err != nil { + return nil, err + } + digestMap[e.Offset] = d + } + } + return digestMap, nil +} + +func parseStargz(sgz *io.SectionReader, controller TestingController) (decodedJTOC *JTOC, jtocOffset int64, err error) { + fSize := controller.FooterSize() + footer := make([]byte, fSize) + if _, err := sgz.ReadAt(footer, sgz.Size()-fSize); err != nil { + return nil, 0, fmt.Errorf("error reading footer: %w", err) + } + _, tocOffset, _, err := controller.ParseFooter(footer[positive(int64(len(footer))-fSize):]) + if err != nil { + return nil, 0, fmt.Errorf("failed to parse footer: %w", err) + } + + // Decode the TOC JSON + var tocReader io.Reader + if tocOffset >= 0 { + tocReader = io.NewSectionReader(sgz, tocOffset, sgz.Size()-tocOffset-fSize) + } + decodedJTOC, _, err = controller.ParseTOC(tocReader) + if err != nil { + return nil, 0, fmt.Errorf("failed to parse TOC: %w", err) + } + return decodedJTOC, tocOffset, nil +} + +func testWriteAndOpen(t *testing.T, controllers ...TestingControllerFactory) { + const content = "Some contents" + invalidUtf8 := "\xff\xfe\xfd" + + xAttrFile := xAttr{"foo": "bar", "invalid-utf8": invalidUtf8} + sampleOwner := owner{uid: 50, gid: 100} + + data64KB := randomContents(64000) + + tests := []struct { + name string + chunkSize int + minChunkSize int + in []tarEntry + want []stargzCheck + wantNumGz int // expected number of streams + + wantNumGzLossLess int // expected number of streams (> 0) in lossless mode if it's different from wantNumGz + wantFailOnLossLess bool + wantTOCVersion int // default = 1 + }{ + { + name: "empty", + in: tarOf(), + wantNumGz: 2, // (empty tar) + TOC + footer + want: checks( + numTOCEntries(0), + ), + }, + { + name: "1dir_1empty_file", + in: tarOf( + dir("foo/"), + file("foo/bar.txt", ""), + ), + wantNumGz: 3, // dir, TOC, footer + want: checks( + numTOCEntries(2), + hasDir("foo/"), + hasFileLen("foo/bar.txt", 0), + entryHasChildren("foo", "bar.txt"), + hasFileDigest("foo/bar.txt", digestFor("")), + ), + }, + { + name: "1dir_1file", + in: tarOf( + dir("foo/"), + file("foo/bar.txt", content, xAttrFile), + ), + wantNumGz: 4, // var dir, foo.txt alone, TOC, footer + want: checks( + numTOCEntries(2), + hasDir("foo/"), + hasFileLen("foo/bar.txt", len(content)), + hasFileDigest("foo/bar.txt", digestFor(content)), + hasFileContentsRange("foo/bar.txt", 0, content), + hasFileContentsRange("foo/bar.txt", 1, content[1:]), + entryHasChildren("", "foo"), + entryHasChildren("foo", "bar.txt"), + hasFileXattrs("foo/bar.txt", "foo", "bar"), + hasFileXattrs("foo/bar.txt", "invalid-utf8", invalidUtf8), + ), + }, + { + name: "2meta_2file", + in: tarOf( + dir("bar/", sampleOwner), + dir("foo/", sampleOwner), + file("foo/bar.txt", content, sampleOwner), + ), + wantNumGz: 4, // both dirs, foo.txt alone, TOC, footer + want: checks( + numTOCEntries(3), + hasDir("bar/"), + hasDir("foo/"), + hasFileLen("foo/bar.txt", len(content)), + entryHasChildren("", "bar", "foo"), + entryHasChildren("foo", "bar.txt"), + hasChunkEntries("foo/bar.txt", 1), + hasEntryOwner("bar/", sampleOwner), + hasEntryOwner("foo/", sampleOwner), + hasEntryOwner("foo/bar.txt", sampleOwner), + ), + }, + { + name: "3dir", + in: tarOf( + dir("bar/"), + dir("foo/"), + dir("foo/bar/"), + ), + wantNumGz: 3, // 3 dirs, TOC, footer + want: checks( + hasDirLinkCount("bar/", 2), + hasDirLinkCount("foo/", 3), + hasDirLinkCount("foo/bar/", 2), + ), + }, + { + name: "symlink", + in: tarOf( + dir("foo/"), + symlink("foo/bar", "../../x"), + ), + wantNumGz: 3, // metas + TOC + footer + want: checks( + numTOCEntries(2), + hasSymlink("foo/bar", "../../x"), + entryHasChildren("", "foo"), + entryHasChildren("foo", "bar"), + ), + }, + { + name: "chunked_file", + chunkSize: 4, + in: tarOf( + dir("foo/"), + file("foo/big.txt", "This "+"is s"+"uch "+"a bi"+"g fi"+"le"), + ), + wantNumGz: 9, // dir + big.txt(6 chunks) + TOC + footer + want: checks( + numTOCEntries(7), // 1 for foo dir, 6 for the foo/big.txt file + hasDir("foo/"), + hasFileLen("foo/big.txt", len("This is such a big file")), + hasFileDigest("foo/big.txt", digestFor("This is such a big file")), + hasFileContentsRange("foo/big.txt", 0, "This is such a big file"), + hasFileContentsRange("foo/big.txt", 1, "his is such a big file"), + hasFileContentsRange("foo/big.txt", 2, "is is such a big file"), + hasFileContentsRange("foo/big.txt", 3, "s is such a big file"), + hasFileContentsRange("foo/big.txt", 4, " is such a big file"), + hasFileContentsRange("foo/big.txt", 5, "is such a big file"), + hasFileContentsRange("foo/big.txt", 6, "s such a big file"), + hasFileContentsRange("foo/big.txt", 7, " such a big file"), + hasFileContentsRange("foo/big.txt", 8, "such a big file"), + hasFileContentsRange("foo/big.txt", 9, "uch a big file"), + hasFileContentsRange("foo/big.txt", 10, "ch a big file"), + hasFileContentsRange("foo/big.txt", 11, "h a big file"), + hasFileContentsRange("foo/big.txt", 12, " a big file"), + hasFileContentsRange("foo/big.txt", len("This is such a big file")-1, ""), + hasChunkEntries("foo/big.txt", 6), + ), + }, + { + name: "recursive", + in: tarOf( + dir("/", sampleOwner), + dir("bar/", sampleOwner), + dir("foo/", sampleOwner), + file("foo/bar.txt", content, sampleOwner), + ), + wantNumGz: 4, // dirs, bar.txt alone, TOC, footer + want: checks( + maxDepth(2), // 0: root directory, 1: "foo/", 2: "bar.txt" + ), + }, + { + name: "block_char_fifo", + in: tarOf( + tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + return w.WriteHeader(&tar.Header{ + Name: prefix + "b", + Typeflag: tar.TypeBlock, + Devmajor: 123, + Devminor: 456, + Format: format, + }) + }), + tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + return w.WriteHeader(&tar.Header{ + Name: prefix + "c", + Typeflag: tar.TypeChar, + Devmajor: 111, + Devminor: 222, + Format: format, + }) + }), + tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + return w.WriteHeader(&tar.Header{ + Name: prefix + "f", + Typeflag: tar.TypeFifo, + Format: format, + }) + }), + ), + wantNumGz: 3, + want: checks( + lookupMatch("b", &TOCEntry{Name: "b", Type: "block", DevMajor: 123, DevMinor: 456, NumLink: 1}), + lookupMatch("c", &TOCEntry{Name: "c", Type: "char", DevMajor: 111, DevMinor: 222, NumLink: 1}), + lookupMatch("f", &TOCEntry{Name: "f", Type: "fifo", NumLink: 1}), + ), + }, + { + name: "modes", + in: tarOf( + dir("foo1/", 0755|os.ModeDir|os.ModeSetgid), + file("foo1/bar1", content, 0700|os.ModeSetuid), + file("foo1/bar2", content, 0755|os.ModeSetgid), + dir("foo2/", 0755|os.ModeDir|os.ModeSticky), + file("foo2/bar3", content, 0755|os.ModeSticky), + dir("foo3/", 0755|os.ModeDir), + file("foo3/bar4", content, os.FileMode(0700)), + file("foo3/bar5", content, os.FileMode(0755)), + ), + wantNumGz: 8, // dir, bar1 alone, bar2 alone + dir, bar3 alone + dir, bar4 alone, bar5 alone, TOC, footer + want: checks( + hasMode("foo1/", 0755|os.ModeDir|os.ModeSetgid), + hasMode("foo1/bar1", 0700|os.ModeSetuid), + hasMode("foo1/bar2", 0755|os.ModeSetgid), + hasMode("foo2/", 0755|os.ModeDir|os.ModeSticky), + hasMode("foo2/bar3", 0755|os.ModeSticky), + hasMode("foo3/", 0755|os.ModeDir), + hasMode("foo3/bar4", os.FileMode(0700)), + hasMode("foo3/bar5", os.FileMode(0755)), + ), + }, + { + name: "lossy", + in: tarOf( + dir("bar/", sampleOwner), + dir("foo/", sampleOwner), + file("foo/bar.txt", content, sampleOwner), + file(TOCTarName, "dummy"), // ignored by the writer. (lossless write returns error) + ), + wantNumGz: 4, // both dirs, foo.txt alone, TOC, footer + want: checks( + numTOCEntries(3), + hasDir("bar/"), + hasDir("foo/"), + hasFileLen("foo/bar.txt", len(content)), + entryHasChildren("", "bar", "foo"), + entryHasChildren("foo", "bar.txt"), + hasChunkEntries("foo/bar.txt", 1), + hasEntryOwner("bar/", sampleOwner), + hasEntryOwner("foo/", sampleOwner), + hasEntryOwner("foo/bar.txt", sampleOwner), + ), + wantFailOnLossLess: true, + }, + { + name: "hardlink should be replaced to the destination entry", + in: tarOf( + dir("foo/"), + file("foo/foo1", "test"), + link("foolink", "foo/foo1"), + ), + wantNumGz: 4, // dir, foo1 + link, TOC, footer + want: checks( + mustSameEntry("foo/foo1", "foolink"), + ), + }, + { + name: "several_files_in_chunk", + minChunkSize: 8000, + in: tarOf( + dir("foo/"), + file("foo/foo1", data64KB), + file("foo2", "bb"), + file("foo22", "ccc"), + dir("bar/"), + file("bar/bar.txt", "aaa"), + file("foo3", data64KB), + ), + // NOTE: we assume that the compressed "data64KB" is still larger than 8KB + wantNumGz: 4, // dir+foo1, foo2+foo22+dir+bar.txt+foo3, TOC, footer + want: checks( + numTOCEntries(7), // dir, foo1, foo2, foo22, dir, bar.txt, foo3 + hasDir("foo/"), + hasDir("bar/"), + hasFileLen("foo/foo1", len(data64KB)), + hasFileLen("foo2", len("bb")), + hasFileLen("foo22", len("ccc")), + hasFileLen("bar/bar.txt", len("aaa")), + hasFileLen("foo3", len(data64KB)), + hasFileDigest("foo/foo1", digestFor(data64KB)), + hasFileDigest("foo2", digestFor("bb")), + hasFileDigest("foo22", digestFor("ccc")), + hasFileDigest("bar/bar.txt", digestFor("aaa")), + hasFileDigest("foo3", digestFor(data64KB)), + hasFileContentsWithPreRead("foo22", 0, "ccc", chunkInfo{"foo2", "bb"}, chunkInfo{"bar/bar.txt", "aaa"}, chunkInfo{"foo3", data64KB}), + hasFileContentsRange("foo/foo1", 0, data64KB), + hasFileContentsRange("foo2", 0, "bb"), + hasFileContentsRange("foo2", 1, "b"), + hasFileContentsRange("foo22", 0, "ccc"), + hasFileContentsRange("foo22", 1, "cc"), + hasFileContentsRange("foo22", 2, "c"), + hasFileContentsRange("bar/bar.txt", 0, "aaa"), + hasFileContentsRange("bar/bar.txt", 1, "aa"), + hasFileContentsRange("bar/bar.txt", 2, "a"), + hasFileContentsRange("foo3", 0, data64KB), + hasFileContentsRange("foo3", 1, data64KB[1:]), + hasFileContentsRange("foo3", 2, data64KB[2:]), + hasFileContentsRange("foo3", len(data64KB)/2, data64KB[len(data64KB)/2:]), + hasFileContentsRange("foo3", len(data64KB)-1, data64KB[len(data64KB)-1:]), + ), + }, + { + name: "several_files_in_chunk_chunked", + minChunkSize: 8000, + chunkSize: 32000, + in: tarOf( + dir("foo/"), + file("foo/foo1", data64KB), + file("foo2", "bb"), + dir("bar/"), + file("foo3", data64KB), + ), + // NOTE: we assume that the compressed chunk of "data64KB" is still larger than 8KB + wantNumGz: 6, // dir+foo1(1), foo1(2), foo2+dir+foo3(1), foo3(2), TOC, footer + want: checks( + numTOCEntries(7), // dir, foo1(2 chunks), foo2, dir, foo3(2 chunks) + hasDir("foo/"), + hasDir("bar/"), + hasFileLen("foo/foo1", len(data64KB)), + hasFileLen("foo2", len("bb")), + hasFileLen("foo3", len(data64KB)), + hasFileDigest("foo/foo1", digestFor(data64KB)), + hasFileDigest("foo2", digestFor("bb")), + hasFileDigest("foo3", digestFor(data64KB)), + hasFileContentsWithPreRead("foo2", 0, "bb", chunkInfo{"foo3", data64KB[:32000]}), + hasFileContentsRange("foo/foo1", 0, data64KB), + hasFileContentsRange("foo/foo1", 1, data64KB[1:]), + hasFileContentsRange("foo/foo1", 2, data64KB[2:]), + hasFileContentsRange("foo/foo1", len(data64KB)/2, data64KB[len(data64KB)/2:]), + hasFileContentsRange("foo/foo1", len(data64KB)-1, data64KB[len(data64KB)-1:]), + hasFileContentsRange("foo2", 0, "bb"), + hasFileContentsRange("foo2", 1, "b"), + hasFileContentsRange("foo3", 0, data64KB), + hasFileContentsRange("foo3", 1, data64KB[1:]), + hasFileContentsRange("foo3", 2, data64KB[2:]), + hasFileContentsRange("foo3", len(data64KB)/2, data64KB[len(data64KB)/2:]), + hasFileContentsRange("foo3", len(data64KB)-1, data64KB[len(data64KB)-1:]), + ), + }, + } + + for _, tt := range tests { + for _, newCL := range controllers { + newCL := newCL + for _, prefix := range allowedPrefix { + prefix := prefix + for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} { + srcTarFormat := srcTarFormat + for _, lossless := range []bool{true, false} { + t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,lossless=%v,format=%s", newCL(), prefix, lossless, srcTarFormat), func(t *testing.T) { + var tr io.Reader = buildTar(t, tt.in, prefix, srcTarFormat) + origTarDgstr := digest.Canonical.Digester() + tr = io.TeeReader(tr, origTarDgstr.Hash()) + var stargzBuf bytes.Buffer + cl1 := newCL() + w := NewWriterWithCompressor(&stargzBuf, cl1) + w.ChunkSize = tt.chunkSize + w.MinChunkSize = tt.minChunkSize + if lossless { + err := w.AppendTarLossLess(tr) + if tt.wantFailOnLossLess { + if err != nil { + return // expected to fail + } + t.Fatalf("Append wanted to fail on lossless") + } + if err != nil { + t.Fatalf("Append(lossless): %v", err) + } + } else { + if err := w.AppendTar(tr); err != nil { + t.Fatalf("Append: %v", err) + } + } + if _, err := w.Close(); err != nil { + t.Fatalf("Writer.Close: %v", err) + } + b := stargzBuf.Bytes() + + if lossless { + // Check if the result blob reserves original tar metadata + rc, err := Unpack(io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))), cl1) + if err != nil { + t.Errorf("failed to decompress blob: %v", err) + return + } + defer rc.Close() + resultDgstr := digest.Canonical.Digester() + if _, err := io.Copy(resultDgstr.Hash(), rc); err != nil { + t.Errorf("failed to read result decompressed blob: %v", err) + return + } + if resultDgstr.Digest() != origTarDgstr.Digest() { + t.Errorf("lossy compression occurred: digest=%v; want %v", + resultDgstr.Digest(), origTarDgstr.Digest()) + return + } + } + + diffID := w.DiffID() + wantDiffID := cl1.DiffIDOf(t, b) + if diffID != wantDiffID { + t.Errorf("DiffID = %q; want %q", diffID, wantDiffID) + } + + telemetry, checkCalled := newCalledTelemetry() + sr := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))) + r, err := Open( + sr, + WithDecompressors(cl1), + WithTelemetry(telemetry), + ) + if err != nil { + t.Fatalf("stargz.Open: %v", err) + } + wantTOCVersion := 1 + if tt.wantTOCVersion > 0 { + wantTOCVersion = tt.wantTOCVersion + } + if r.toc.Version != wantTOCVersion { + t.Fatalf("invalid TOC Version %d; wanted %d", r.toc.Version, wantTOCVersion) + } + + footerSize := cl1.FooterSize() + footerOffset := sr.Size() - footerSize + footer := make([]byte, footerSize) + if _, err := sr.ReadAt(footer, footerOffset); err != nil { + t.Errorf("failed to read footer: %v", err) + } + _, tocOffset, _, err := cl1.ParseFooter(footer) + if err != nil { + t.Errorf("failed to parse footer: %v", err) + } + if err := checkCalled(tocOffset >= 0); err != nil { + t.Errorf("telemetry failure: %v", err) + } + + wantNumGz := tt.wantNumGz + if lossless && tt.wantNumGzLossLess > 0 { + wantNumGz = tt.wantNumGzLossLess + } + streamOffsets := []int64{0} + prevOffset := int64(-1) + streams := 0 + for _, e := range r.toc.Entries { + if e.Offset > prevOffset { + streamOffsets = append(streamOffsets, e.Offset) + prevOffset = e.Offset + streams++ + } + } + streams++ // TOC + if tocOffset >= 0 { + // toc is in the blob + streamOffsets = append(streamOffsets, tocOffset) + } + streams++ // footer + streamOffsets = append(streamOffsets, footerOffset) + if streams != wantNumGz { + t.Errorf("number of streams in TOC = %d; want %d", streams, wantNumGz) + } + + t.Logf("testing streams: %+v", streamOffsets) + cl1.TestStreams(t, b, streamOffsets) + + for _, want := range tt.want { + want.check(t, r) + } + }) + } + } + } + } + } +} + +type chunkInfo struct { + name string + data string +} + +func newCalledTelemetry() (telemetry *Telemetry, check func(needsGetTOC bool) error) { + var getFooterLatencyCalled bool + var getTocLatencyCalled bool + var deserializeTocLatencyCalled bool + return &Telemetry{ + func(time.Time) { getFooterLatencyCalled = true }, + func(time.Time) { getTocLatencyCalled = true }, + func(time.Time) { deserializeTocLatencyCalled = true }, + }, func(needsGetTOC bool) error { + var allErr []error + if !getFooterLatencyCalled { + allErr = append(allErr, fmt.Errorf("metrics GetFooterLatency isn't called")) + } + if needsGetTOC { + if !getTocLatencyCalled { + allErr = append(allErr, fmt.Errorf("metrics GetTocLatency isn't called")) + } + } + if !deserializeTocLatencyCalled { + allErr = append(allErr, fmt.Errorf("metrics DeserializeTocLatency isn't called")) + } + return errorutil.Aggregate(allErr) + } +} + +func digestFor(content string) string { + sum := sha256.Sum256([]byte(content)) + return fmt.Sprintf("sha256:%x", sum) +} + +type numTOCEntries int + +func (n numTOCEntries) check(t *testing.T, r *Reader) { + if r.toc == nil { + t.Fatal("nil TOC") + } + if got, want := len(r.toc.Entries), int(n); got != want { + t.Errorf("got %d TOC entries; want %d", got, want) + } + t.Logf("got TOC entries:") + for i, ent := range r.toc.Entries { + entj, _ := json.Marshal(ent) + t.Logf(" [%d]: %s\n", i, entj) + } + if t.Failed() { + t.FailNow() + } +} + +func checks(s ...stargzCheck) []stargzCheck { return s } + +type stargzCheck interface { + check(t *testing.T, r *Reader) +} + +type stargzCheckFn func(*testing.T, *Reader) + +func (f stargzCheckFn) check(t *testing.T, r *Reader) { f(t, r) } + +func maxDepth(max int) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + e, ok := r.Lookup("") + if !ok { + t.Fatal("root directory not found") + } + d, err := getMaxDepth(t, e, 0, 10*max) + if err != nil { + t.Errorf("failed to get max depth (wanted %d): %v", max, err) + return + } + if d != max { + t.Errorf("invalid depth %d; want %d", d, max) + return + } + }) +} + +func getMaxDepth(t *testing.T, e *TOCEntry, current, limit int) (max int, rErr error) { + if current > limit { + return -1, fmt.Errorf("walkMaxDepth: exceeds limit: current:%d > limit:%d", + current, limit) + } + max = current + e.ForeachChild(func(baseName string, ent *TOCEntry) bool { + t.Logf("%q(basename:%q) is child of %q\n", ent.Name, baseName, e.Name) + d, err := getMaxDepth(t, ent, current+1, limit) + if err != nil { + rErr = err + return false + } + if d > max { + max = d + } + return true + }) + return +} + +func hasFileLen(file string, wantLen int) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + for _, ent := range r.toc.Entries { + if ent.Name == file { + if ent.Type != "reg" { + t.Errorf("file type of %q is %q; want \"reg\"", file, ent.Type) + } else if ent.Size != int64(wantLen) { + t.Errorf("file size of %q = %d; want %d", file, ent.Size, wantLen) + } + return + } + } + t.Errorf("file %q not found", file) + }) +} + +func hasFileXattrs(file, name, value string) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + for _, ent := range r.toc.Entries { + if ent.Name == file { + if ent.Type != "reg" { + t.Errorf("file type of %q is %q; want \"reg\"", file, ent.Type) + } + if ent.Xattrs == nil { + t.Errorf("file %q has no xattrs", file) + return + } + valueFound, found := ent.Xattrs[name] + if !found { + t.Errorf("file %q has no xattr %q", file, name) + return + } + if string(valueFound) != value { + t.Errorf("file %q has xattr %q with value %q instead of %q", file, name, valueFound, value) + } + + return + } + } + t.Errorf("file %q not found", file) + }) +} + +func hasFileDigest(file string, digest string) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + ent, ok := r.Lookup(file) + if !ok { + t.Fatalf("didn't find TOCEntry for file %q", file) + } + if ent.Digest != digest { + t.Fatalf("Digest(%q) = %q, want %q", file, ent.Digest, digest) + } + }) +} + +func hasFileContentsWithPreRead(file string, offset int, want string, extra ...chunkInfo) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + extraMap := make(map[string]chunkInfo) + for _, e := range extra { + extraMap[e.name] = e + } + var extraNames []string + for n := range extraMap { + extraNames = append(extraNames, n) + } + f, err := r.OpenFileWithPreReader(file, func(e *TOCEntry, cr io.Reader) error { + t.Logf("On %q: got preread of %q", file, e.Name) + ex, ok := extraMap[e.Name] + if !ok { + t.Fatalf("fail on %q: unexpected entry %q: %+v, %+v", file, e.Name, e, extraNames) + } + got, err := io.ReadAll(cr) + if err != nil { + t.Fatalf("fail on %q: failed to read %q: %v", file, e.Name, err) + } + if ex.data != string(got) { + t.Fatalf("fail on %q: unexpected contents of %q: len=%d; want=%d", file, e.Name, len(got), len(ex.data)) + } + delete(extraMap, e.Name) + return nil + }) + if err != nil { + t.Fatal(err) + } + got := make([]byte, len(want)) + n, err := f.ReadAt(got, int64(offset)) + if err != nil { + t.Fatalf("ReadAt(len %d, offset %d, size %d) = %v, %v", len(got), offset, f.Size(), n, err) + } + if string(got) != want { + t.Fatalf("ReadAt(len %d, offset %d) = %q, want %q", len(got), offset, viewContent(got), viewContent([]byte(want))) + } + if len(extraMap) != 0 { + var exNames []string + for _, ex := range extraMap { + exNames = append(exNames, ex.name) + } + t.Fatalf("fail on %q: some entries aren't read: %+v", file, exNames) + } + }) +} + +func hasFileContentsRange(file string, offset int, want string) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + f, err := r.OpenFile(file) + if err != nil { + t.Fatal(err) + } + got := make([]byte, len(want)) + n, err := f.ReadAt(got, int64(offset)) + if err != nil { + t.Fatalf("ReadAt(len %d, offset %d) = %v, %v", len(got), offset, n, err) + } + if string(got) != want { + t.Fatalf("ReadAt(len %d, offset %d) = %q, want %q", len(got), offset, viewContent(got), viewContent([]byte(want))) + } + }) +} + +func hasChunkEntries(file string, wantChunks int) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + ent, ok := r.Lookup(file) + if !ok { + t.Fatalf("no file for %q", file) + } + if ent.Type != "reg" { + t.Fatalf("file %q has unexpected type %q; want reg", file, ent.Type) + } + chunks := r.getChunks(ent) + if len(chunks) != wantChunks { + t.Errorf("len(r.getChunks(%q)) = %d; want %d", file, len(chunks), wantChunks) + return + } + f := chunks[0] + + var gotChunks []*TOCEntry + var last *TOCEntry + for off := int64(0); off < f.Size; off++ { + e, ok := r.ChunkEntryForOffset(file, off) + if !ok { + t.Errorf("no ChunkEntryForOffset at %d", off) + return + } + if last != e { + gotChunks = append(gotChunks, e) + last = e + } + } + if !reflect.DeepEqual(chunks, gotChunks) { + t.Errorf("gotChunks=%d, want=%d; contents mismatch", len(gotChunks), wantChunks) + } + + // And verify the NextOffset + for i := 0; i < len(gotChunks)-1; i++ { + ci := gotChunks[i] + cnext := gotChunks[i+1] + if ci.NextOffset() != cnext.Offset { + t.Errorf("chunk %d NextOffset %d != next chunk's Offset of %d", i, ci.NextOffset(), cnext.Offset) + } + } + }) +} + +func entryHasChildren(dir string, want ...string) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + want := append([]string(nil), want...) + var got []string + ent, ok := r.Lookup(dir) + if !ok { + t.Fatalf("didn't find TOCEntry for dir node %q", dir) + } + for baseName := range ent.children { + got = append(got, baseName) + } + sort.Strings(got) + sort.Strings(want) + if !reflect.DeepEqual(got, want) { + t.Errorf("children of %q = %q; want %q", dir, got, want) + } + }) +} + +func hasDir(file string) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + for _, ent := range r.toc.Entries { + if ent.Name == cleanEntryName(file) { + if ent.Type != "dir" { + t.Errorf("file type of %q is %q; want \"dir\"", file, ent.Type) + } + return + } + } + t.Errorf("directory %q not found", file) + }) +} + +func hasDirLinkCount(file string, count int) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + for _, ent := range r.toc.Entries { + if ent.Name == cleanEntryName(file) { + if ent.Type != "dir" { + t.Errorf("file type of %q is %q; want \"dir\"", file, ent.Type) + return + } + if ent.NumLink != count { + t.Errorf("link count of %q = %d; want %d", file, ent.NumLink, count) + } + return + } + } + t.Errorf("directory %q not found", file) + }) +} + +func hasMode(file string, mode os.FileMode) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + for _, ent := range r.toc.Entries { + if ent.Name == cleanEntryName(file) { + if ent.Stat().Mode() != mode { + t.Errorf("invalid mode: got %v; want %v", ent.Stat().Mode(), mode) + return + } + return + } + } + t.Errorf("file %q not found", file) + }) +} + +func hasSymlink(file, target string) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + for _, ent := range r.toc.Entries { + if ent.Name == file { + if ent.Type != "symlink" { + t.Errorf("file type of %q is %q; want \"symlink\"", file, ent.Type) + } else if ent.LinkName != target { + t.Errorf("link target of symlink %q is %q; want %q", file, ent.LinkName, target) + } + return + } + } + t.Errorf("symlink %q not found", file) + }) +} + +func lookupMatch(name string, want *TOCEntry) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + e, ok := r.Lookup(name) + if !ok { + t.Fatalf("failed to Lookup entry %q", name) + } + if !reflect.DeepEqual(e, want) { + t.Errorf("entry %q mismatch.\n got: %+v\nwant: %+v\n", name, e, want) + } + + }) +} + +func hasEntryOwner(entry string, owner owner) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + ent, ok := r.Lookup(strings.TrimSuffix(entry, "/")) + if !ok { + t.Errorf("entry %q not found", entry) + return + } + if ent.UID != owner.uid || ent.GID != owner.gid { + t.Errorf("entry %q has invalid owner (uid:%d, gid:%d) instead of (uid:%d, gid:%d)", entry, ent.UID, ent.GID, owner.uid, owner.gid) + return + } + }) +} + +func mustSameEntry(files ...string) stargzCheck { + return stargzCheckFn(func(t *testing.T, r *Reader) { + var first *TOCEntry + for _, f := range files { + if first == nil { + var ok bool + first, ok = r.Lookup(f) + if !ok { + t.Errorf("unknown first file on Lookup: %q", f) + return + } + } + + // Test Lookup + e, ok := r.Lookup(f) + if !ok { + t.Errorf("unknown file on Lookup: %q", f) + return + } + if e != first { + t.Errorf("Lookup: %+v(%p) != %+v(%p)", e, e, first, first) + return + } + + // Test LookupChild + pe, ok := r.Lookup(filepath.Dir(filepath.Clean(f))) + if !ok { + t.Errorf("failed to get parent of %q", f) + return + } + e, ok = pe.LookupChild(filepath.Base(filepath.Clean(f))) + if !ok { + t.Errorf("failed to get %q as the child of %+v", f, pe) + return + } + if e != first { + t.Errorf("LookupChild: %+v(%p) != %+v(%p)", e, e, first, first) + return + } + + // Test ForeachChild + pe.ForeachChild(func(baseName string, e *TOCEntry) bool { + if baseName == filepath.Base(filepath.Clean(f)) { + if e != first { + t.Errorf("ForeachChild: %+v(%p) != %+v(%p)", e, e, first, first) + return false + } + } + return true + }) + } + }) +} + +func viewContent(c []byte) string { + if len(c) < 100 { + return string(c) + } + return string(c[:50]) + "...(omit)..." + string(c[50:100]) +} + +func tarOf(s ...tarEntry) []tarEntry { return s } + +type tarEntry interface { + appendTar(tw *tar.Writer, prefix string, format tar.Format) error +} + +type tarEntryFunc func(*tar.Writer, string, tar.Format) error + +func (f tarEntryFunc) appendTar(tw *tar.Writer, prefix string, format tar.Format) error { + return f(tw, prefix, format) +} + +func buildTar(t *testing.T, ents []tarEntry, prefix string, opts ...interface{}) *io.SectionReader { + format := tar.FormatUnknown + for _, opt := range opts { + switch v := opt.(type) { + case tar.Format: + format = v + default: + panic(fmt.Errorf("unsupported opt for buildTar: %v", opt)) + } + } + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + for _, ent := range ents { + if err := ent.appendTar(tw, prefix, format); err != nil { + t.Fatalf("building input tar: %v", err) + } + } + if err := tw.Close(); err != nil { + t.Errorf("closing write of input tar: %v", err) + } + data := append(buf.Bytes(), make([]byte, 100)...) // append empty bytes at the tail to see lossless works + return io.NewSectionReader(bytes.NewReader(data), 0, int64(len(data))) +} + +func dir(name string, opts ...interface{}) tarEntry { + return tarEntryFunc(func(tw *tar.Writer, prefix string, format tar.Format) error { + var o owner + mode := os.FileMode(0755) + for _, opt := range opts { + switch v := opt.(type) { + case owner: + o = v + case os.FileMode: + mode = v + default: + return errors.New("unsupported opt") + } + } + if !strings.HasSuffix(name, "/") { + panic(fmt.Sprintf("missing trailing slash in dir %q ", name)) + } + tm, err := fileModeToTarMode(mode) + if err != nil { + return err + } + return tw.WriteHeader(&tar.Header{ + Typeflag: tar.TypeDir, + Name: prefix + name, + Mode: tm, + Uid: o.uid, + Gid: o.gid, + Format: format, + }) + }) +} + +// xAttr are extended attributes to set on test files created with the file func. +type xAttr map[string]string + +// owner is owner ot set on test files and directories with the file and dir functions. +type owner struct { + uid int + gid int +} + +func file(name, contents string, opts ...interface{}) tarEntry { + return tarEntryFunc(func(tw *tar.Writer, prefix string, format tar.Format) error { + var xattrs xAttr + var o owner + mode := os.FileMode(0644) + for _, opt := range opts { + switch v := opt.(type) { + case xAttr: + xattrs = v + case owner: + o = v + case os.FileMode: + mode = v + default: + return errors.New("unsupported opt") + } + } + if strings.HasSuffix(name, "/") { + return fmt.Errorf("bogus trailing slash in file %q", name) + } + tm, err := fileModeToTarMode(mode) + if err != nil { + return err + } + if len(xattrs) > 0 { + format = tar.FormatPAX // only PAX supports xattrs + } + if err := tw.WriteHeader(&tar.Header{ + Typeflag: tar.TypeReg, + Name: prefix + name, + Mode: tm, + Xattrs: xattrs, + Size: int64(len(contents)), + Uid: o.uid, + Gid: o.gid, + Format: format, + }); err != nil { + return err + } + _, err = io.WriteString(tw, contents) + return err + }) +} + +func symlink(name, target string) tarEntry { + return tarEntryFunc(func(tw *tar.Writer, prefix string, format tar.Format) error { + return tw.WriteHeader(&tar.Header{ + Typeflag: tar.TypeSymlink, + Name: prefix + name, + Linkname: target, + Mode: 0644, + Format: format, + }) + }) +} + +func link(name string, linkname string) tarEntry { + now := time.Now() + return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + return w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeLink, + Name: prefix + name, + Linkname: linkname, + ModTime: now, + Format: format, + }) + }) +} + +func chardev(name string, major, minor int64) tarEntry { + now := time.Now() + return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + return w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeChar, + Name: prefix + name, + Devmajor: major, + Devminor: minor, + ModTime: now, + Format: format, + }) + }) +} + +func blockdev(name string, major, minor int64) tarEntry { + now := time.Now() + return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + return w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeBlock, + Name: prefix + name, + Devmajor: major, + Devminor: minor, + ModTime: now, + Format: format, + }) + }) +} +func fifo(name string) tarEntry { + now := time.Now() + return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + return w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeFifo, + Name: prefix + name, + ModTime: now, + Format: format, + }) + }) +} + +func prefetchLandmark() tarEntry { + return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + if err := w.WriteHeader(&tar.Header{ + Name: PrefetchLandmark, + Typeflag: tar.TypeReg, + Size: int64(len([]byte{landmarkContents})), + Format: format, + }); err != nil { + return err + } + contents := []byte{landmarkContents} + if _, err := io.CopyN(w, bytes.NewReader(contents), int64(len(contents))); err != nil { + return err + } + return nil + }) +} + +func noPrefetchLandmark() tarEntry { + return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + if err := w.WriteHeader(&tar.Header{ + Name: NoPrefetchLandmark, + Typeflag: tar.TypeReg, + Size: int64(len([]byte{landmarkContents})), + Format: format, + }); err != nil { + return err + } + contents := []byte{landmarkContents} + if _, err := io.CopyN(w, bytes.NewReader(contents), int64(len(contents))); err != nil { + return err + } + return nil + }) +} + +func regDigest(t *testing.T, name string, contentStr string, digestMap map[string]digest.Digest) tarEntry { + if digestMap == nil { + t.Fatalf("digest map mustn't be nil") + } + content := []byte(contentStr) + + var n int64 + for n < int64(len(content)) { + size := int64(chunkSize) + remain := int64(len(content)) - n + if remain < size { + size = remain + } + dgstr := digest.Canonical.Digester() + if _, err := io.CopyN(dgstr.Hash(), bytes.NewReader(content[n:n+size]), size); err != nil { + t.Fatalf("failed to calculate digest of %q (name=%q,offset=%d,size=%d)", + string(content[n:n+size]), name, n, size) + } + digestMap[chunkID(name, n, size)] = dgstr.Digest() + n += size + } + + return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { + if err := w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeReg, + Name: prefix + name, + Size: int64(len(content)), + Format: format, + }); err != nil { + return err + } + if _, err := io.CopyN(w, bytes.NewReader(content), int64(len(content))); err != nil { + return err + } + return nil + }) +} + +var runes = []rune("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +func randomContents(n int) string { + b := make([]rune, n) + for i := range b { + bi, err := rand.Int(rand.Reader, big.NewInt(int64(len(runes)))) + if err != nil { + panic(err) + } + b[i] = runes[int(bi.Int64())] + } + return string(b) +} + +func fileModeToTarMode(mode os.FileMode) (int64, error) { + h, err := tar.FileInfoHeader(fileInfoOnlyMode(mode), "") + if err != nil { + return 0, err + } + return h.Mode, nil +} + +// fileInfoOnlyMode is os.FileMode that populates only file mode. +type fileInfoOnlyMode os.FileMode + +func (f fileInfoOnlyMode) Name() string { return "" } +func (f fileInfoOnlyMode) Size() int64 { return 0 } +func (f fileInfoOnlyMode) Mode() os.FileMode { return os.FileMode(f) } +func (f fileInfoOnlyMode) ModTime() time.Time { return time.Now() } +func (f fileInfoOnlyMode) IsDir() bool { return os.FileMode(f).IsDir() } +func (f fileInfoOnlyMode) Sys() interface{} { return nil } + +func CheckGzipHasStreams(t *testing.T, b []byte, streams []int64) { + if len(streams) == 0 { + return // nop + } + + wants := map[int64]struct{}{} + for _, s := range streams { + wants[s] = struct{}{} + } + + len0 := len(b) + br := bytes.NewReader(b) + zr := new(gzip.Reader) + t.Logf("got gzip streams:") + numStreams := 0 + for { + zoff := len0 - br.Len() + if err := zr.Reset(br); err != nil { + if err == io.EOF { + return + } + t.Fatalf("countStreams(gzip), Reset: %v", err) + } + zr.Multistream(false) + n, err := io.Copy(io.Discard, zr) + if err != nil { + t.Fatalf("countStreams(gzip), Copy: %v", err) + } + var extra string + if len(zr.Extra) > 0 { + extra = fmt.Sprintf("; extra=%q", zr.Extra) + } + t.Logf(" [%d] at %d in stargz, uncompressed length %d%s", numStreams, zoff, n, extra) + delete(wants, int64(zoff)) + numStreams++ + } +} + +func GzipDiffIDOf(t *testing.T, b []byte) string { + h := sha256.New() + zr, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + t.Fatalf("diffIDOf(gzip): %v", err) + } + defer zr.Close() + if _, err := io.Copy(h, zr); err != nil { + t.Fatalf("diffIDOf(gzip).Copy: %v", err) + } + return fmt.Sprintf("sha256:%x", h.Sum(nil)) +} diff --git a/vendor/github.com/containerd/stargz-snapshotter/estargz/types.go b/vendor/github.com/containerd/stargz-snapshotter/estargz/types.go new file mode 100644 index 00000000..57e0aa61 --- /dev/null +++ b/vendor/github.com/containerd/stargz-snapshotter/estargz/types.go @@ -0,0 +1,342 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* + Copyright 2019 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +*/ + +package estargz + +import ( + "archive/tar" + "hash" + "io" + "os" + "path" + "time" + + digest "github.com/opencontainers/go-digest" +) + +const ( + // TOCTarName is the name of the JSON file in the tar archive in the + // table of contents gzip stream. + TOCTarName = "stargz.index.json" + + // FooterSize is the number of bytes in the footer + // + // The footer is an empty gzip stream with no compression and an Extra + // header of the form "%016xSTARGZ", where the 64 bit hex-encoded + // number is the offset to the gzip stream of JSON TOC. + // + // 51 comes from: + // + // 10 bytes gzip header + // 2 bytes XLEN (length of Extra field) = 26 (4 bytes header + 16 hex digits + len("STARGZ")) + // 2 bytes Extra: SI1 = 'S', SI2 = 'G' + // 2 bytes Extra: LEN = 22 (16 hex digits + len("STARGZ")) + // 22 bytes Extra: subfield = fmt.Sprintf("%016xSTARGZ", offsetOfTOC) + // 5 bytes flate header + // 8 bytes gzip footer + // (End of the eStargz blob) + // + // NOTE: For Extra fields, subfield IDs SI1='S' SI2='G' is used for eStargz. + FooterSize = 51 + + // legacyFooterSize is the number of bytes in the legacy stargz footer. + // + // 47 comes from: + // + // 10 byte gzip header + + // 2 byte (LE16) length of extra, encoding 22 (16 hex digits + len("STARGZ")) == "\x16\x00" + + // 22 bytes of extra (fmt.Sprintf("%016xSTARGZ", tocGzipOffset)) + // 5 byte flate header + // 8 byte gzip footer (two little endian uint32s: digest, size) + legacyFooterSize = 47 + + // TOCJSONDigestAnnotation is an annotation for an image layer. This stores the + // digest of the TOC JSON. + // This annotation is valid only when it is specified in `.[]layers.annotations` + // of an image manifest. + TOCJSONDigestAnnotation = "containerd.io/snapshot/stargz/toc.digest" + + // StoreUncompressedSizeAnnotation is an additional annotation key for eStargz to enable lazy + // pulling on containers/storage. Stargz Store is required to expose the layer's uncompressed size + // to the runtime but current OCI image doesn't ship this information by default. So we store this + // to the special annotation. + StoreUncompressedSizeAnnotation = "io.containers.estargz.uncompressed-size" + + // PrefetchLandmark is a file entry which indicates the end position of + // prefetch in the stargz file. + PrefetchLandmark = ".prefetch.landmark" + + // NoPrefetchLandmark is a file entry which indicates that no prefetch should + // occur in the stargz file. + NoPrefetchLandmark = ".no.prefetch.landmark" + + landmarkContents = 0xf +) + +// JTOC is the JSON-serialized table of contents index of the files in the stargz file. +type JTOC struct { + Version int `json:"version"` + Entries []*TOCEntry `json:"entries"` +} + +// TOCEntry is an entry in the stargz file's TOC (Table of Contents). +type TOCEntry struct { + // Name is the tar entry's name. It is the complete path + // stored in the tar file, not just the base name. + Name string `json:"name"` + + // Type is one of "dir", "reg", "symlink", "hardlink", "char", + // "block", "fifo", or "chunk". + // The "chunk" type is used for regular file data chunks past the first + // TOCEntry; the 2nd chunk and on have only Type ("chunk"), Offset, + // ChunkOffset, and ChunkSize populated. + Type string `json:"type"` + + // Size, for regular files, is the logical size of the file. + Size int64 `json:"size,omitempty"` + + // ModTime3339 is the modification time of the tar entry. Empty + // means zero or unknown. Otherwise it's in UTC RFC3339 + // format. Use the ModTime method to access the time.Time value. + ModTime3339 string `json:"modtime,omitempty"` + modTime time.Time + + // LinkName, for symlinks and hardlinks, is the link target. + LinkName string `json:"linkName,omitempty"` + + // Mode is the permission and mode bits. + Mode int64 `json:"mode,omitempty"` + + // UID is the user ID of the owner. + UID int `json:"uid,omitempty"` + + // GID is the group ID of the owner. + GID int `json:"gid,omitempty"` + + // Uname is the username of the owner. + // + // In the serialized JSON, this field may only be present for + // the first entry with the same UID. + Uname string `json:"userName,omitempty"` + + // Gname is the group name of the owner. + // + // In the serialized JSON, this field may only be present for + // the first entry with the same GID. + Gname string `json:"groupName,omitempty"` + + // Offset, for regular files, provides the offset in the + // stargz file to the file's data bytes. See ChunkOffset and + // ChunkSize. + Offset int64 `json:"offset,omitempty"` + + // InnerOffset is an optional field indicates uncompressed offset + // of this "reg" or "chunk" payload in a stream starts from Offset. + // This field enables to put multiple "reg" or "chunk" payloads + // in one chunk with having the same Offset but different InnerOffset. + InnerOffset int64 `json:"innerOffset,omitempty"` + + nextOffset int64 // the Offset of the next entry with a non-zero Offset + + // DevMajor is the major device number for "char" and "block" types. + DevMajor int `json:"devMajor,omitempty"` + + // DevMinor is the major device number for "char" and "block" types. + DevMinor int `json:"devMinor,omitempty"` + + // NumLink is the number of entry names pointing to this entry. + // Zero means one name references this entry. + // This field is calculated during runtime and not recorded in TOC JSON. + NumLink int `json:"-"` + + // Xattrs are the extended attribute for the entry. + Xattrs map[string][]byte `json:"xattrs,omitempty"` + + // Digest stores the OCI checksum for regular files payload. + // It has the form "sha256:abcdef01234....". + Digest string `json:"digest,omitempty"` + + // ChunkOffset is non-zero if this is a chunk of a large, + // regular file. If so, the Offset is where the gzip header of + // ChunkSize bytes at ChunkOffset in Name begin. + // + // In serialized form, a "chunkSize" JSON field of zero means + // that the chunk goes to the end of the file. After reading + // from the stargz TOC, though, the ChunkSize is initialized + // to a non-zero file for when Type is either "reg" or + // "chunk". + ChunkOffset int64 `json:"chunkOffset,omitempty"` + ChunkSize int64 `json:"chunkSize,omitempty"` + + // ChunkDigest stores an OCI digest of the chunk. This must be formed + // as "sha256:0123abcd...". + ChunkDigest string `json:"chunkDigest,omitempty"` + + children map[string]*TOCEntry + + // chunkTopIndex is index of the entry where Offset starts in the blob. + chunkTopIndex int +} + +// ModTime returns the entry's modification time. +func (e *TOCEntry) ModTime() time.Time { return e.modTime } + +// NextOffset returns the position (relative to the start of the +// stargz file) of the next gzip boundary after e.Offset. +func (e *TOCEntry) NextOffset() int64 { return e.nextOffset } + +func (e *TOCEntry) addChild(baseName string, child *TOCEntry) { + if e.children == nil { + e.children = make(map[string]*TOCEntry) + } + if child.Type == "dir" { + e.NumLink++ // Entry ".." in the subdirectory links to this directory + } + e.children[baseName] = child +} + +// isDataType reports whether TOCEntry is a regular file or chunk (something that +// contains regular file data). +func (e *TOCEntry) isDataType() bool { return e.Type == "reg" || e.Type == "chunk" } + +// Stat returns a FileInfo value representing e. +func (e *TOCEntry) Stat() os.FileInfo { return fileInfo{e} } + +// ForeachChild calls f for each child item. If f returns false, iteration ends. +// If e is not a directory, f is not called. +func (e *TOCEntry) ForeachChild(f func(baseName string, ent *TOCEntry) bool) { + for name, ent := range e.children { + if !f(name, ent) { + return + } + } +} + +// LookupChild returns the directory e's child by its base name. +func (e *TOCEntry) LookupChild(baseName string) (child *TOCEntry, ok bool) { + child, ok = e.children[baseName] + return +} + +// fileInfo implements os.FileInfo using the wrapped *TOCEntry. +type fileInfo struct{ e *TOCEntry } + +var _ os.FileInfo = fileInfo{} + +func (fi fileInfo) Name() string { return path.Base(fi.e.Name) } +func (fi fileInfo) IsDir() bool { return fi.e.Type == "dir" } +func (fi fileInfo) Size() int64 { return fi.e.Size } +func (fi fileInfo) ModTime() time.Time { return fi.e.ModTime() } +func (fi fileInfo) Sys() interface{} { return fi.e } +func (fi fileInfo) Mode() (m os.FileMode) { + // TOCEntry.Mode is tar.Header.Mode so we can understand the these bits using `tar` pkg. + m = (&tar.Header{Mode: fi.e.Mode}).FileInfo().Mode() & + (os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky) + switch fi.e.Type { + case "dir": + m |= os.ModeDir + case "symlink": + m |= os.ModeSymlink + case "char": + m |= os.ModeDevice | os.ModeCharDevice + case "block": + m |= os.ModeDevice + case "fifo": + m |= os.ModeNamedPipe + } + return m +} + +// TOCEntryVerifier holds verifiers that are usable for verifying chunks contained +// in a eStargz blob. +type TOCEntryVerifier interface { + + // Verifier provides a content verifier that can be used for verifying the + // contents of the specified TOCEntry. + Verifier(ce *TOCEntry) (digest.Verifier, error) +} + +// Compression provides the compression helper to be used creating and parsing eStargz. +// This package provides gzip-based Compression by default, but any compression +// algorithm (e.g. zstd) can be used as long as it implements Compression. +type Compression interface { + Compressor + Decompressor +} + +// Compressor represents the helper mothods to be used for creating eStargz. +type Compressor interface { + // Writer returns WriteCloser to be used for writing a chunk to eStargz. + // Everytime a chunk is written, the WriteCloser is closed and Writer is + // called again for writing the next chunk. + // + // The returned writer should implement "Flush() error" function that flushes + // any pending compressed data to the underlying writer. + Writer(w io.Writer) (WriteFlushCloser, error) + + // WriteTOCAndFooter is called to write JTOC to the passed Writer. + // diffHash calculates the DiffID (uncompressed sha256 hash) of the blob + // WriteTOCAndFooter can optionally write anything that affects DiffID calculation + // (e.g. uncompressed TOC JSON). + // + // This function returns tocDgst that represents the digest of TOC that will be used + // to verify this blob when it's parsed. + WriteTOCAndFooter(w io.Writer, off int64, toc *JTOC, diffHash hash.Hash) (tocDgst digest.Digest, err error) +} + +// Decompressor represents the helper mothods to be used for parsing eStargz. +type Decompressor interface { + // Reader returns ReadCloser to be used for decompressing file payload. + Reader(r io.Reader) (io.ReadCloser, error) + + // FooterSize returns the size of the footer of this blob. + FooterSize() int64 + + // ParseFooter parses the footer and returns the offset and (compressed) size of TOC. + // payloadBlobSize is the (compressed) size of the blob payload (i.e. the size between + // the top until the TOC JSON). + // + // If tocOffset < 0, we assume that TOC isn't contained in the blob and pass nil reader + // to ParseTOC. We expect that ParseTOC acquire TOC from the external location and return it. + // + // tocSize is optional. If tocSize <= 0, it's by default the size of the range from tocOffset until the beginning of the + // footer (blob size - tocOff - FooterSize). + // If blobPayloadSize < 0, blobPayloadSize become the blob size. + ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error) + + // ParseTOC parses TOC from the passed reader. The reader provides the partial contents + // of the underlying blob that has the range specified by ParseFooter method. + // + // This function returns tocDgst that represents the digest of TOC that will be used + // to verify this blob. This must match to the value returned from + // Compressor.WriteTOCAndFooter that is used when creating this blob. + // + // If tocOffset returned by ParseFooter is < 0, we assume that TOC isn't contained in the blob. + // Pass nil reader to ParseTOC then we expect that ParseTOC acquire TOC from the external location + // and return it. + ParseTOC(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) +} + +type WriteFlushCloser interface { + io.WriteCloser + Flush() error +} diff --git a/vendor/github.com/containerd/typeurl/v2/.gitignore b/vendor/github.com/containerd/typeurl/v2/.gitignore new file mode 100644 index 00000000..d5384677 --- /dev/null +++ b/vendor/github.com/containerd/typeurl/v2/.gitignore @@ -0,0 +1,2 @@ +*.test +coverage.txt diff --git a/vendor/github.com/containerd/typeurl/v2/LICENSE b/vendor/github.com/containerd/typeurl/v2/LICENSE new file mode 100644 index 00000000..584149b6 --- /dev/null +++ b/vendor/github.com/containerd/typeurl/v2/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright The containerd Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containerd/typeurl/v2/README.md b/vendor/github.com/containerd/typeurl/v2/README.md new file mode 100644 index 00000000..3098526a --- /dev/null +++ b/vendor/github.com/containerd/typeurl/v2/README.md @@ -0,0 +1,26 @@ +# typeurl + +[![PkgGoDev](https://pkg.go.dev/badge/github.com/containerd/typeurl)](https://pkg.go.dev/github.com/containerd/typeurl) +[![Build Status](https://github.com/containerd/typeurl/workflows/CI/badge.svg)](https://github.com/containerd/typeurl/actions?query=workflow%3ACI) +[![codecov](https://codecov.io/gh/containerd/typeurl/branch/main/graph/badge.svg)](https://codecov.io/gh/containerd/typeurl) +[![Go Report Card](https://goreportcard.com/badge/github.com/containerd/typeurl)](https://goreportcard.com/report/github.com/containerd/typeurl) + +A Go package for managing the registration, marshaling, and unmarshaling of encoded types. + +This package helps when types are sent over a ttrpc/GRPC API and marshaled as a protobuf [Any](https://pkg.go.dev/google.golang.org/protobuf@v1.27.1/types/known/anypb#Any) + +## Project details + +**typeurl** is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). +As a containerd sub-project, you will find the: + * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), + * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), + * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) + +information in our [`containerd/project`](https://github.com/containerd/project) repository. + +## Optional + +By default, support for gogoproto is available along side the standard Google +protobuf types. +You can choose to leave gogo support out by using the `!no_gogo` build tag. diff --git a/vendor/github.com/containerd/typeurl/v2/doc.go b/vendor/github.com/containerd/typeurl/v2/doc.go new file mode 100644 index 00000000..c0d0fd20 --- /dev/null +++ b/vendor/github.com/containerd/typeurl/v2/doc.go @@ -0,0 +1,83 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package typeurl + +// Package typeurl assists with managing the registration, marshaling, and +// unmarshaling of types encoded as protobuf.Any. +// +// A protobuf.Any is a proto message that can contain any arbitrary data. It +// consists of two components, a TypeUrl and a Value, and its proto definition +// looks like this: +// +// message Any { +// string type_url = 1; +// bytes value = 2; +// } +// +// The TypeUrl is used to distinguish the contents from other proto.Any +// messages. This typeurl library manages these URLs to enable automagic +// marshaling and unmarshaling of the contents. +// +// For example, consider this go struct: +// +// type Foo struct { +// Field1 string +// Field2 string +// } +// +// To use typeurl, types must first be registered. This is typically done in +// the init function +// +// func init() { +// typeurl.Register(&Foo{}, "Foo") +// } +// +// This will register the type Foo with the url path "Foo". The arguments to +// Register are variadic, and are used to construct a url path. Consider this +// example, from the github.com/containerd/containerd/client package: +// +// func init() { +// const prefix = "types.containerd.io" +// // register TypeUrls for commonly marshaled external types +// major := strconv.Itoa(specs.VersionMajor) +// typeurl.Register(&specs.Spec{}, prefix, "opencontainers/runtime-spec", major, "Spec") +// // this function has more Register calls, which are elided. +// } +// +// This registers several types under a more complex url, which ends up mapping +// to `types.containerd.io/opencontainers/runtime-spec/1/Spec` (or some other +// value for major). +// +// Once a type is registered, it can be marshaled to a proto.Any message simply +// by calling `MarshalAny`, like this: +// +// foo := &Foo{Field1: "value1", Field2: "value2"} +// anyFoo, err := typeurl.MarshalAny(foo) +// +// MarshalAny will resolve the correct URL for the type. If the type in +// question implements the proto.Message interface, then it will be marshaled +// as a proto message. Otherwise, it will be marshaled as json. This means that +// typeurl will work on any arbitrary data, whether or not it has a proto +// definition, as long as it can be serialized to json. +// +// To unmarshal, the process is simply inverse: +// +// iface, err := typeurl.UnmarshalAny(anyFoo) +// foo := iface.(*Foo) +// +// The correct type is automatically chosen from the type registry, and the +// returned interface can be cast straight to that type. diff --git a/vendor/github.com/containerd/typeurl/v2/types.go b/vendor/github.com/containerd/typeurl/v2/types.go new file mode 100644 index 00000000..9bf78104 --- /dev/null +++ b/vendor/github.com/containerd/typeurl/v2/types.go @@ -0,0 +1,309 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package typeurl + +import ( + "encoding/json" + "errors" + "fmt" + "path" + "reflect" + "sync" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/known/anypb" +) + +var ( + mu sync.RWMutex + registry = make(map[reflect.Type]string) + handlers []handler +) + +type handler interface { + Marshaller(interface{}) func() ([]byte, error) + Unmarshaller(interface{}) func([]byte) error + TypeURL(interface{}) string + GetType(url string) (reflect.Type, bool) +} + +// Definitions of common error types used throughout typeurl. +// +// These error types are used with errors.Wrap and errors.Wrapf to add context +// to an error. +// +// To detect an error class, use errors.Is() functions to tell whether an +// error is of this type. + +var ( + ErrNotFound = errors.New("not found") +) + +// Any contains an arbitrary protcol buffer message along with its type. +// +// While there is google.golang.org/protobuf/types/known/anypb.Any, +// we'd like to have our own to hide the underlying protocol buffer +// implementations from containerd clients. +// +// https://developers.google.com/protocol-buffers/docs/proto3#any +type Any interface { + // GetTypeUrl returns a URL/resource name that uniquely identifies + // the type of the serialized protocol buffer message. + GetTypeUrl() string + + // GetValue returns a valid serialized protocol buffer of the type that + // GetTypeUrl() indicates. + GetValue() []byte +} + +type anyType struct { + typeURL string + value []byte +} + +func (a *anyType) GetTypeUrl() string { + if a == nil { + return "" + } + return a.typeURL +} + +func (a *anyType) GetValue() []byte { + if a == nil { + return nil + } + return a.value +} + +// Register a type with a base URL for JSON marshaling. When the MarshalAny and +// UnmarshalAny functions are called they will treat the Any type value as JSON. +// To use protocol buffers for handling the Any value the proto.Register +// function should be used instead of this function. +func Register(v interface{}, args ...string) { + var ( + t = tryDereference(v) + p = path.Join(args...) + ) + mu.Lock() + defer mu.Unlock() + if et, ok := registry[t]; ok { + if et != p { + panic(fmt.Errorf("type registered with alternate path %q != %q", et, p)) + } + return + } + registry[t] = p +} + +// TypeURL returns the type url for a registered type. +func TypeURL(v interface{}) (string, error) { + mu.RLock() + u, ok := registry[tryDereference(v)] + mu.RUnlock() + if !ok { + switch t := v.(type) { + case proto.Message: + return string(t.ProtoReflect().Descriptor().FullName()), nil + default: + for _, h := range handlers { + if u := h.TypeURL(v); u != "" { + return u, nil + } + } + return "", fmt.Errorf("type %s: %w", reflect.TypeOf(v), ErrNotFound) + } + } + return u, nil +} + +// Is returns true if the type of the Any is the same as v. +func Is(any Any, v interface{}) bool { + if any == nil { + return false + } + // call to check that v is a pointer + tryDereference(v) + url, err := TypeURL(v) + if err != nil { + return false + } + return any.GetTypeUrl() == url +} + +// MarshalAny marshals the value v into an any with the correct TypeUrl. +// If the provided object is already a proto.Any message, then it will be +// returned verbatim. If it is of type proto.Message, it will be marshaled as a +// protocol buffer. Otherwise, the object will be marshaled to json. +func MarshalAny(v interface{}) (Any, error) { + var marshal func(v interface{}) ([]byte, error) + switch t := v.(type) { + case Any: + // avoid reserializing the type if we have an any. + return t, nil + case proto.Message: + marshal = func(v interface{}) ([]byte, error) { + return proto.Marshal(t) + } + default: + for _, h := range handlers { + if m := h.Marshaller(v); m != nil { + marshal = func(v interface{}) ([]byte, error) { + return m() + } + break + } + } + + if marshal == nil { + marshal = json.Marshal + } + } + + url, err := TypeURL(v) + if err != nil { + return nil, err + } + + data, err := marshal(v) + if err != nil { + return nil, err + } + return &anyType{ + typeURL: url, + value: data, + }, nil +} + +// UnmarshalAny unmarshals the any type into a concrete type. +func UnmarshalAny(any Any) (interface{}, error) { + return UnmarshalByTypeURL(any.GetTypeUrl(), any.GetValue()) +} + +// UnmarshalByTypeURL unmarshals the given type and value to into a concrete type. +func UnmarshalByTypeURL(typeURL string, value []byte) (interface{}, error) { + return unmarshal(typeURL, value, nil) +} + +// UnmarshalTo unmarshals the any type into a concrete type passed in the out +// argument. It is identical to UnmarshalAny, but lets clients provide a +// destination type through the out argument. +func UnmarshalTo(any Any, out interface{}) error { + return UnmarshalToByTypeURL(any.GetTypeUrl(), any.GetValue(), out) +} + +// UnmarshalToByTypeURL unmarshals the given type and value into a concrete type passed +// in the out argument. It is identical to UnmarshalByTypeURL, but lets clients +// provide a destination type through the out argument. +func UnmarshalToByTypeURL(typeURL string, value []byte, out interface{}) error { + _, err := unmarshal(typeURL, value, out) + return err +} + +// MarshalProto converts typeurl.Any to google.golang.org/protobuf/types/known/anypb.Any. +func MarshalProto(from Any) *anypb.Any { + if from == nil { + return nil + } + + if pbany, ok := from.(*anypb.Any); ok { + return pbany + } + + return &anypb.Any{ + TypeUrl: from.GetTypeUrl(), + Value: from.GetValue(), + } +} + +// MarshalAnyToProto converts an arbitrary interface to google.golang.org/protobuf/types/known/anypb.Any. +func MarshalAnyToProto(from interface{}) (*anypb.Any, error) { + anyType, err := MarshalAny(from) + if err != nil { + return nil, err + } + return MarshalProto(anyType), nil +} + +func unmarshal(typeURL string, value []byte, v interface{}) (interface{}, error) { + t, isProto, err := getTypeByUrl(typeURL) + if err != nil { + return nil, err + } + + if v == nil { + v = reflect.New(t).Interface() + } else { + // Validate interface type provided by client + vURL, err := TypeURL(v) + if err != nil { + return nil, err + } + if typeURL != vURL { + return nil, fmt.Errorf("can't unmarshal type %q to output %q", typeURL, vURL) + } + } + + if isProto { + pm, ok := v.(proto.Message) + if ok { + return v, proto.Unmarshal(value, pm) + } + + for _, h := range handlers { + if unmarshal := h.Unmarshaller(v); unmarshal != nil { + return v, unmarshal(value) + } + } + } + + // fallback to json unmarshaller + return v, json.Unmarshal(value, v) +} + +func getTypeByUrl(url string) (_ reflect.Type, isProto bool, _ error) { + mu.RLock() + for t, u := range registry { + if u == url { + mu.RUnlock() + return t, false, nil + } + } + mu.RUnlock() + mt, err := protoregistry.GlobalTypes.FindMessageByURL(url) + if err != nil { + if errors.Is(err, protoregistry.NotFound) { + for _, h := range handlers { + if t, isProto := h.GetType(url); t != nil { + return t, isProto, nil + } + } + } + return nil, false, fmt.Errorf("type with url %s: %w", url, ErrNotFound) + } + empty := mt.New().Interface() + return reflect.TypeOf(empty).Elem(), true, nil +} + +func tryDereference(v interface{}) reflect.Type { + t := reflect.TypeOf(v) + if t.Kind() == reflect.Ptr { + // require check of pointer but dereference to register + return t.Elem() + } + panic("v is not a pointer to a type") +} diff --git a/vendor/github.com/containerd/typeurl/v2/types_gogo.go b/vendor/github.com/containerd/typeurl/v2/types_gogo.go new file mode 100644 index 00000000..adb892ec --- /dev/null +++ b/vendor/github.com/containerd/typeurl/v2/types_gogo.go @@ -0,0 +1,68 @@ +//go:build !no_gogo + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package typeurl + +import ( + "reflect" + + gogoproto "github.com/gogo/protobuf/proto" +) + +func init() { + handlers = append(handlers, gogoHandler{}) +} + +type gogoHandler struct{} + +func (gogoHandler) Marshaller(v interface{}) func() ([]byte, error) { + pm, ok := v.(gogoproto.Message) + if !ok { + return nil + } + return func() ([]byte, error) { + return gogoproto.Marshal(pm) + } +} + +func (gogoHandler) Unmarshaller(v interface{}) func([]byte) error { + pm, ok := v.(gogoproto.Message) + if !ok { + return nil + } + + return func(dt []byte) error { + return gogoproto.Unmarshal(dt, pm) + } +} + +func (gogoHandler) TypeURL(v interface{}) string { + pm, ok := v.(gogoproto.Message) + if !ok { + return "" + } + return gogoproto.MessageName(pm) +} + +func (gogoHandler) GetType(url string) (reflect.Type, bool) { + t := gogoproto.MessageType(url) + if t == nil { + return nil, false + } + return t.Elem(), true +} diff --git a/vendor/github.com/containernetworking/cni/LICENSE b/vendor/github.com/containernetworking/cni/LICENSE new file mode 100644 index 00000000..8f71f43f --- /dev/null +++ b/vendor/github.com/containernetworking/cni/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/containernetworking/cni/libcni/api.go b/vendor/github.com/containernetworking/cni/libcni/api.go new file mode 100644 index 00000000..6ac26949 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/libcni/api.go @@ -0,0 +1,900 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package libcni + +// Note this is the actual implementation of the CNI specification, which +// is reflected in the SPEC.md file. +// it is typically bundled into runtime providers (i.e. containerd or cri-o would use this +// before calling runc or hcsshim). It is also bundled into CNI providers as well, for example, +// to add an IP to a container, to parse the configuration of the CNI and so on. + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/containernetworking/cni/pkg/invoke" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/create" + "github.com/containernetworking/cni/pkg/utils" + "github.com/containernetworking/cni/pkg/version" +) + +var ( + CacheDir = "/var/lib/cni" + // slightly awkward wording to preserve anyone matching on error strings + ErrorCheckNotSupp = fmt.Errorf("does not support the CHECK command") +) + +const ( + CNICacheV1 = "cniCacheV1" +) + +// A RuntimeConf holds the arguments to one invocation of a CNI plugin +// excepting the network configuration, with the nested exception that +// the `runtimeConfig` from the network configuration is included +// here. +type RuntimeConf struct { + ContainerID string + NetNS string + IfName string + Args [][2]string + // A dictionary of capability-specific data passed by the runtime + // to plugins as top-level keys in the 'runtimeConfig' dictionary + // of the plugin's stdin data. libcni will ensure that only keys + // in this map which match the capabilities of the plugin are passed + // to the plugin + CapabilityArgs map[string]interface{} + + // DEPRECATED. Will be removed in a future release. + CacheDir string +} + +// Use PluginConfig instead of NetworkConfig, the NetworkConfig +// backwards-compat alias will be removed in a future release. +type NetworkConfig = PluginConfig + +type PluginConfig struct { + Network *types.PluginConf + Bytes []byte +} + +type NetworkConfigList struct { + Name string + CNIVersion string + DisableCheck bool + DisableGC bool + LoadOnlyInlinedPlugins bool + Plugins []*PluginConfig + Bytes []byte +} + +type NetworkAttachment struct { + ContainerID string + Network string + IfName string + Config []byte + NetNS string + CniArgs [][2]string + CapabilityArgs map[string]interface{} +} + +type GCArgs struct { + ValidAttachments []types.GCAttachment +} + +type CNI interface { + AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) + CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error + DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error + GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) + GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) + + AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error) + CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error + DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error + GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error) + GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) + + ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error) + ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error) + + GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error + GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error + + GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) + + GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error) +} + +type CNIConfig struct { + Path []string + exec invoke.Exec + cacheDir string +} + +// CNIConfig implements the CNI interface +var _ CNI = &CNIConfig{} + +// NewCNIConfig returns a new CNIConfig object that will search for plugins +// in the given paths and use the given exec interface to run those plugins, +// or if the exec interface is not given, will use a default exec handler. +func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig { + return NewCNIConfigWithCacheDir(path, "", exec) +} + +// NewCNIConfigWithCacheDir returns a new CNIConfig object that will search for plugins +// in the given paths use the given exec interface to run those plugins, +// or if the exec interface is not given, will use a default exec handler. +// The given cache directory will be used for temporary data storage when needed. +func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec) *CNIConfig { + return &CNIConfig{ + Path: path, + cacheDir: cacheDir, + exec: exec, + } +} + +func buildOneConfig(name, cniVersion string, orig *PluginConfig, prevResult types.Result, rt *RuntimeConf) (*PluginConfig, error) { + var err error + + inject := map[string]interface{}{ + "name": name, + "cniVersion": cniVersion, + } + // Add previous plugin result + if prevResult != nil { + inject["prevResult"] = prevResult + } + + // Ensure every config uses the same name and version + orig, err = InjectConf(orig, inject) + if err != nil { + return nil, err + } + if rt != nil { + return injectRuntimeConfig(orig, rt) + } + + return orig, nil +} + +// This function takes a libcni RuntimeConf structure and injects values into +// a "runtimeConfig" dictionary in the CNI network configuration JSON that +// will be passed to the plugin on stdin. +// +// Only "capabilities arguments" passed by the runtime are currently injected. +// These capabilities arguments are filtered through the plugin's advertised +// capabilities from its config JSON, and any keys in the CapabilityArgs +// matching plugin capabilities are added to the "runtimeConfig" dictionary +// sent to the plugin via JSON on stdin. For example, if the plugin's +// capabilities include "portMappings", and the CapabilityArgs map includes a +// "portMappings" key, that key and its value are added to the "runtimeConfig" +// dictionary to be passed to the plugin's stdin. +func injectRuntimeConfig(orig *PluginConfig, rt *RuntimeConf) (*PluginConfig, error) { + var err error + + rc := make(map[string]interface{}) + for capability, supported := range orig.Network.Capabilities { + if !supported { + continue + } + if data, ok := rt.CapabilityArgs[capability]; ok { + rc[capability] = data + } + } + + if len(rc) > 0 { + orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc}) + if err != nil { + return nil, err + } + } + + return orig, nil +} + +// ensure we have a usable exec if the CNIConfig was not given one +func (c *CNIConfig) ensureExec() invoke.Exec { + if c.exec == nil { + c.exec = &invoke.DefaultExec{ + RawExec: &invoke.RawExec{Stderr: os.Stderr}, + PluginDecoder: version.PluginDecoder{}, + } + } + return c.exec +} + +type cachedInfo struct { + Kind string `json:"kind"` + ContainerID string `json:"containerId"` + Config []byte `json:"config"` + IfName string `json:"ifName"` + NetworkName string `json:"networkName"` + NetNS string `json:"netns,omitempty"` + CniArgs [][2]string `json:"cniArgs,omitempty"` + CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"` + RawResult map[string]interface{} `json:"result,omitempty"` + Result types.Result `json:"-"` +} + +// getCacheDir returns the cache directory in this order: +// 1) global cacheDir from CNIConfig object +// 2) deprecated cacheDir from RuntimeConf object +// 3) fall back to default cache directory +func (c *CNIConfig) getCacheDir(rt *RuntimeConf) string { + if c.cacheDir != "" { + return c.cacheDir + } + if rt.CacheDir != "" { + return rt.CacheDir + } + return CacheDir +} + +func (c *CNIConfig) getCacheFilePath(netName string, rt *RuntimeConf) (string, error) { + if netName == "" || rt.ContainerID == "" || rt.IfName == "" { + return "", fmt.Errorf("cache file path requires network name (%q), container ID (%q), and interface name (%q)", netName, rt.ContainerID, rt.IfName) + } + return filepath.Join(c.getCacheDir(rt), "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)), nil +} + +func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string, rt *RuntimeConf) error { + cached := cachedInfo{ + Kind: CNICacheV1, + ContainerID: rt.ContainerID, + Config: config, + IfName: rt.IfName, + NetworkName: netName, + NetNS: rt.NetNS, + CniArgs: rt.Args, + CapabilityArgs: rt.CapabilityArgs, + } + + // We need to get type.Result into cachedInfo as JSON map + // Marshal to []byte, then Unmarshal into cached.RawResult + data, err := json.Marshal(result) + if err != nil { + return err + } + + err = json.Unmarshal(data, &cached.RawResult) + if err != nil { + return err + } + + newBytes, err := json.Marshal(&cached) + if err != nil { + return err + } + + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(fname), 0o700); err != nil { + return err + } + + return os.WriteFile(fname, newBytes, 0o600) +} + +func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error { + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + // Ignore error + return nil + } + return os.Remove(fname) +} + +func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *RuntimeConf, error) { + var bytes []byte + + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + return nil, nil, err + } + bytes, err = os.ReadFile(fname) + if err != nil { + // Ignore read errors; the cached result may not exist on-disk + return nil, nil, nil + } + + unmarshaled := cachedInfo{} + if err := json.Unmarshal(bytes, &unmarshaled); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %w", netName, err) + } + if unmarshaled.Kind != CNICacheV1 { + return nil, nil, fmt.Errorf("read cached network %q config has wrong kind: %v", netName, unmarshaled.Kind) + } + + newRt := *rt + if unmarshaled.CniArgs != nil { + newRt.Args = unmarshaled.CniArgs + } + newRt.CapabilityArgs = unmarshaled.CapabilityArgs + + return unmarshaled.Config, &newRt, nil +} + +func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) { + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + return nil, err + } + data, err := os.ReadFile(fname) + if err != nil { + // Ignore read errors; the cached result may not exist on-disk + return nil, nil + } + + // Load the cached result + result, err := create.CreateFromBytes(data) + if err != nil { + return nil, err + } + + // Convert to the config version to ensure plugins get prevResult + // in the same version as the config. The cached result version + // should match the config version unless the config was changed + // while the container was running. + result, err = result.GetAsVersion(cniVersion) + if err != nil { + return nil, fmt.Errorf("failed to convert cached result to config version %q: %w", cniVersion, err) + } + return result, nil +} + +func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) { + fname, err := c.getCacheFilePath(netName, rt) + if err != nil { + return nil, err + } + fdata, err := os.ReadFile(fname) + if err != nil { + // Ignore read errors; the cached result may not exist on-disk + return nil, nil + } + + cachedInfo := cachedInfo{} + if err := json.Unmarshal(fdata, &cachedInfo); err != nil || cachedInfo.Kind != CNICacheV1 { + return c.getLegacyCachedResult(netName, cniVersion, rt) + } + + newBytes, err := json.Marshal(&cachedInfo.RawResult) + if err != nil { + return nil, fmt.Errorf("failed to marshal cached network %q config: %w", netName, err) + } + + // Load the cached result + result, err := create.CreateFromBytes(newBytes) + if err != nil { + return nil, err + } + + // Convert to the config version to ensure plugins get prevResult + // in the same version as the config. The cached result version + // should match the config version unless the config was changed + // while the container was running. + result, err = result.GetAsVersion(cniVersion) + if err != nil { + return nil, fmt.Errorf("failed to convert cached result to config version %q: %w", cniVersion, err) + } + return result, nil +} + +// GetNetworkListCachedResult returns the cached Result of the previous +// AddNetworkList() operation for a network list, or an error. +func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { + return c.getCachedResult(list.Name, list.CNIVersion, rt) +} + +// GetNetworkCachedResult returns the cached Result of the previous +// AddNetwork() operation for a network, or an error. +func (c *CNIConfig) GetNetworkCachedResult(net *PluginConfig, rt *RuntimeConf) (types.Result, error) { + return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) +} + +// GetNetworkListCachedConfig copies the input RuntimeConf to output +// RuntimeConf with fields updated with info from the cached Config. +func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) { + return c.getCachedConfig(list.Name, rt) +} + +// GetNetworkCachedConfig copies the input RuntimeConf to output +// RuntimeConf with fields updated with info from the cached Config. +func (c *CNIConfig) GetNetworkCachedConfig(net *PluginConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) { + return c.getCachedConfig(net.Network.Name, rt) +} + +// GetCachedAttachments returns a list of network attachments from the cache. +// The returned list will be filtered by the containerID if the value is not empty. +func (c *CNIConfig) GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) { + dirPath := filepath.Join(c.getCacheDir(&RuntimeConf{}), "results") + entries, err := os.ReadDir(dirPath) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + + fileNames := make([]string, 0, len(entries)) + for _, e := range entries { + fileNames = append(fileNames, e.Name()) + } + sort.Strings(fileNames) + + attachments := []*NetworkAttachment{} + for _, fname := range fileNames { + if len(containerID) > 0 { + part := fmt.Sprintf("-%s-", containerID) + pos := strings.Index(fname, part) + if pos <= 0 || pos+len(part) >= len(fname) { + continue + } + } + + cacheFile := filepath.Join(dirPath, fname) + bytes, err := os.ReadFile(cacheFile) + if err != nil { + continue + } + + cachedInfo := cachedInfo{} + + if err := json.Unmarshal(bytes, &cachedInfo); err != nil { + continue + } + if cachedInfo.Kind != CNICacheV1 { + continue + } + if len(containerID) > 0 && cachedInfo.ContainerID != containerID { + continue + } + if cachedInfo.IfName == "" || cachedInfo.NetworkName == "" { + continue + } + + attachments = append(attachments, &NetworkAttachment{ + ContainerID: cachedInfo.ContainerID, + Network: cachedInfo.NetworkName, + IfName: cachedInfo.IfName, + Config: cachedInfo.Config, + NetNS: cachedInfo.NetNS, + CniArgs: cachedInfo.CniArgs, + CapabilityArgs: cachedInfo.CapabilityArgs, + }) + } + return attachments, nil +} + +func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return nil, err + } + if err := utils.ValidateContainerID(rt.ContainerID); err != nil { + return nil, err + } + if err := utils.ValidateNetworkName(name); err != nil { + return nil, err + } + if err := utils.ValidateInterfaceName(rt.IfName); err != nil { + return nil, err + } + + newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) + if err != nil { + return nil, err + } + + return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec) +} + +// AddNetworkList executes a sequence of plugins with the ADD command +func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { + var err error + var result types.Result + for _, net := range list.Plugins { + result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt) + if err != nil { + return nil, fmt.Errorf("plugin %s failed (add): %w", pluginDescription(net.Network), err) + } + } + + if err = c.cacheAdd(result, list.Bytes, list.Name, rt); err != nil { + return nil, fmt.Errorf("failed to set network %q cached result: %w", list.Name, err) + } + + return result, nil +} + +func (c *CNIConfig) checkNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + + newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) + if err != nil { + return err + } + + return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("CHECK", rt), c.exec) +} + +// CheckNetworkList executes a sequence of plugins with the CHECK command +func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error { + // CHECK was added in CNI spec version 0.4.0 and higher + if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { + return err + } else if !gtet { + return fmt.Errorf("configuration version %q %w", list.CNIVersion, ErrorCheckNotSupp) + } + + if list.DisableCheck { + return nil + } + + cachedResult, err := c.getCachedResult(list.Name, list.CNIVersion, rt) + if err != nil { + return fmt.Errorf("failed to get network %q cached result: %w", list.Name, err) + } + + for _, net := range list.Plugins { + if err := c.checkNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil { + return err + } + } + + return nil +} + +func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *PluginConfig, prevResult types.Result, rt *RuntimeConf) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + + newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) + if err != nil { + return err + } + + return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec) +} + +// DelNetworkList executes a sequence of plugins with the DEL command +func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error { + var cachedResult types.Result + + // Cached result on DEL was added in CNI spec version 0.4.0 and higher + if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { + return err + } else if gtet { + if cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt); err != nil { + _ = c.cacheDel(list.Name, rt) + cachedResult = nil + } + } + + for i := len(list.Plugins) - 1; i >= 0; i-- { + net := list.Plugins[i] + if err := c.delNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil { + return fmt.Errorf("plugin %s failed (delete): %w", pluginDescription(net.Network), err) + } + } + + _ = c.cacheDel(list.Name, rt) + + return nil +} + +func pluginDescription(net *types.PluginConf) string { + if net == nil { + return "" + } + pluginType := net.Type + out := fmt.Sprintf("type=%q", pluginType) + name := net.Name + if name != "" { + out += fmt.Sprintf(" name=%q", name) + } + return out +} + +// AddNetwork executes the plugin with the ADD command +func (c *CNIConfig) AddNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) (types.Result, error) { + result, err := c.addNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, nil, rt) + if err != nil { + return nil, err + } + + if err = c.cacheAdd(result, net.Bytes, net.Network.Name, rt); err != nil { + return nil, fmt.Errorf("failed to set network %q cached result: %w", net.Network.Name, err) + } + + return result, nil +} + +// CheckNetwork executes the plugin with the CHECK command +func (c *CNIConfig) CheckNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error { + // CHECK was added in CNI spec version 0.4.0 and higher + if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { + return err + } else if !gtet { + return fmt.Errorf("configuration version %q %w", net.Network.CNIVersion, ErrorCheckNotSupp) + } + + cachedResult, err := c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) + if err != nil { + return fmt.Errorf("failed to get network %q cached result: %w", net.Network.Name, err) + } + return c.checkNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt) +} + +// DelNetwork executes the plugin with the DEL command +func (c *CNIConfig) DelNetwork(ctx context.Context, net *PluginConfig, rt *RuntimeConf) error { + var cachedResult types.Result + + // Cached result on DEL was added in CNI spec version 0.4.0 and higher + if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { + return err + } else if gtet { + cachedResult, err = c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) + if err != nil { + return fmt.Errorf("failed to get network %q cached result: %w", net.Network.Name, err) + } + } + + if err := c.delNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil { + return err + } + _ = c.cacheDel(net.Network.Name, rt) + return nil +} + +// ValidateNetworkList checks that a configuration is reasonably valid. +// - all the specified plugins exist on disk +// - every plugin supports the desired version. +// +// Returns a list of all capabilities supported by the configuration, or error +func (c *CNIConfig) ValidateNetworkList(ctx context.Context, list *NetworkConfigList) ([]string, error) { + version := list.CNIVersion + + // holding map for seen caps (in case of duplicates) + caps := map[string]interface{}{} + + errs := []error{} + for _, net := range list.Plugins { + if err := c.validatePlugin(ctx, net.Network.Type, version); err != nil { + errs = append(errs, err) + } + for c, enabled := range net.Network.Capabilities { + if !enabled { + continue + } + caps[c] = struct{}{} + } + } + + if len(errs) > 0 { + return nil, fmt.Errorf("%v", errs) + } + + // make caps list + cc := make([]string, 0, len(caps)) + for c := range caps { + cc = append(cc, c) + } + + return cc, nil +} + +// ValidateNetwork checks that a configuration is reasonably valid. +// It uses the same logic as ValidateNetworkList) +// Returns a list of capabilities +func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *PluginConfig) ([]string, error) { + caps := []string{} + for c, ok := range net.Network.Capabilities { + if ok { + caps = append(caps, c) + } + } + if err := c.validatePlugin(ctx, net.Network.Type, net.Network.CNIVersion); err != nil { + return nil, err + } + return caps, nil +} + +// validatePlugin checks that an individual plugin's configuration is sane +func (c *CNIConfig) validatePlugin(ctx context.Context, pluginName, expectedVersion string) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(pluginName, c.Path) + if err != nil { + return err + } + if expectedVersion == "" { + expectedVersion = "0.1.0" + } + + vi, err := invoke.GetVersionInfo(ctx, pluginPath, c.exec) + if err != nil { + return err + } + for _, vers := range vi.SupportedVersions() { + if vers == expectedVersion { + return nil + } + } + return fmt.Errorf("plugin %s does not support config version %q", pluginName, expectedVersion) +} + +// GetVersionInfo reports which versions of the CNI spec are supported by +// the given plugin. +func (c *CNIConfig) GetVersionInfo(ctx context.Context, pluginType string) (version.PluginInfo, error) { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(pluginType, c.Path) + if err != nil { + return nil, err + } + + return invoke.GetVersionInfo(ctx, pluginPath, c.exec) +} + +// GCNetworkList will do two things +// - dump the list of cached attachments, and issue deletes as necessary +// - issue a GC to the underlying plugins (if the version is high enough) +func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList, args *GCArgs) error { + // If DisableGC is set, then don't bother GCing at all. + if list.DisableGC { + return nil + } + + // First, get the list of cached attachments + cachedAttachments, err := c.GetCachedAttachments("") + if err != nil { + return nil + } + + var validAttachments map[types.GCAttachment]interface{} + if args != nil { + validAttachments = make(map[types.GCAttachment]interface{}, len(args.ValidAttachments)) + for _, a := range args.ValidAttachments { + validAttachments[a] = nil + } + } + + var errs []error + + for _, cachedAttachment := range cachedAttachments { + if cachedAttachment.Network != list.Name { + continue + } + // we found this attachment + gca := types.GCAttachment{ + ContainerID: cachedAttachment.ContainerID, + IfName: cachedAttachment.IfName, + } + if _, ok := validAttachments[gca]; ok { + continue + } + // otherwise, this attachment wasn't valid and we should issue a CNI DEL + rt := RuntimeConf{ + ContainerID: cachedAttachment.ContainerID, + NetNS: cachedAttachment.NetNS, + IfName: cachedAttachment.IfName, + Args: cachedAttachment.CniArgs, + CapabilityArgs: cachedAttachment.CapabilityArgs, + } + if err := c.DelNetworkList(ctx, list, &rt); err != nil { + errs = append(errs, fmt.Errorf("failed to delete stale attachment %s %s: %w", rt.ContainerID, rt.IfName, err)) + } + } + + // now, if the version supports it, issue a GC + if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); gt { + inject := map[string]interface{}{ + "name": list.Name, + "cniVersion": list.CNIVersion, + } + if args != nil { + inject["cni.dev/valid-attachments"] = args.ValidAttachments + // #1101: spec used incorrect variable name + inject["cni.dev/attachments"] = args.ValidAttachments + } + + for _, plugin := range list.Plugins { + // build config here + pluginConfig, err := InjectConf(plugin, inject) + if err != nil { + errs = append(errs, fmt.Errorf("failed to generate configuration to GC plugin %s: %w", plugin.Network.Type, err)) + } + if err := c.gcNetwork(ctx, pluginConfig); err != nil { + errs = append(errs, fmt.Errorf("failed to GC plugin %s: %w", plugin.Network.Type, err)) + } + } + } + + return errors.Join(errs...) +} + +func (c *CNIConfig) gcNetwork(ctx context.Context, net *PluginConfig) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + args := c.args("GC", &RuntimeConf{}) + + return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec) +} + +func (c *CNIConfig) GetStatusNetworkList(ctx context.Context, list *NetworkConfigList) error { + // If the version doesn't support status, abort. + if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); !gt { + return nil + } + + inject := map[string]interface{}{ + "name": list.Name, + "cniVersion": list.CNIVersion, + } + + for _, plugin := range list.Plugins { + // build config here + pluginConfig, err := InjectConf(plugin, inject) + if err != nil { + return fmt.Errorf("failed to generate configuration to get plugin STATUS %s: %w", plugin.Network.Type, err) + } + if err := c.getStatusNetwork(ctx, pluginConfig); err != nil { + return err // Don't collect errors here, so we return a clean error code. + } + } + return nil +} + +func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *PluginConfig) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + args := c.args("STATUS", &RuntimeConf{}) + + return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec) +} + +// ===== +func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args { + return &invoke.Args{ + Command: action, + ContainerID: rt.ContainerID, + NetNS: rt.NetNS, + PluginArgs: rt.Args, + IfName: rt.IfName, + Path: strings.Join(c.Path, string(os.PathListSeparator)), + } +} diff --git a/vendor/github.com/containernetworking/cni/libcni/conf.go b/vendor/github.com/containernetworking/cni/libcni/conf.go new file mode 100644 index 00000000..7f8482e7 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/libcni/conf.go @@ -0,0 +1,445 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package libcni + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "sort" + "strings" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/version" +) + +type NotFoundError struct { + Dir string + Name string +} + +func (e NotFoundError) Error() string { + return fmt.Sprintf(`no net configuration with name "%s" in %s`, e.Name, e.Dir) +} + +type NoConfigsFoundError struct { + Dir string +} + +func (e NoConfigsFoundError) Error() string { + return fmt.Sprintf(`no net configurations found in %s`, e.Dir) +} + +// This will not validate that the plugins actually belong to the netconfig by ensuring +// that they are loaded from a directory named after the networkName, relative to the network config. +// +// Since here we are just accepting raw bytes, the caller is responsible for ensuring that the plugin +// config provided here actually "belongs" to the networkconfig in question. +func NetworkPluginConfFromBytes(pluginConfBytes []byte) (*PluginConfig, error) { + // TODO why are we creating a struct that holds both the byte representation and the deserialized + // representation, and returning that, instead of just returning the deserialized representation? + conf := &PluginConfig{Bytes: pluginConfBytes, Network: &types.PluginConf{}} + if err := json.Unmarshal(pluginConfBytes, conf.Network); err != nil { + return nil, fmt.Errorf("error parsing configuration: %w", err) + } + if conf.Network.Type == "" { + return nil, fmt.Errorf("error parsing configuration: missing 'type'") + } + return conf, nil +} + +// Given a path to a directory containing a network configuration, and the name of a network, +// loads all plugin definitions found at path `networkConfPath/networkName/*.conf` +func NetworkPluginConfsFromFiles(networkConfPath, networkName string) ([]*PluginConfig, error) { + var pConfs []*PluginConfig + + pluginConfPath := filepath.Join(networkConfPath, networkName) + + pluginConfFiles, err := ConfFiles(pluginConfPath, []string{".conf"}) + if err != nil { + return nil, fmt.Errorf("failed to read plugin config files in %s: %w", pluginConfPath, err) + } + + for _, pluginConfFile := range pluginConfFiles { + pluginConfBytes, err := os.ReadFile(pluginConfFile) + if err != nil { + return nil, fmt.Errorf("error reading %s: %w", pluginConfFile, err) + } + pluginConf, err := NetworkPluginConfFromBytes(pluginConfBytes) + if err != nil { + return nil, err + } + pConfs = append(pConfs, pluginConf) + } + return pConfs, nil +} + +func NetworkConfFromBytes(confBytes []byte) (*NetworkConfigList, error) { + rawList := make(map[string]interface{}) + if err := json.Unmarshal(confBytes, &rawList); err != nil { + return nil, fmt.Errorf("error parsing configuration list: %w", err) + } + + rawName, ok := rawList["name"] + if !ok { + return nil, fmt.Errorf("error parsing configuration list: no name") + } + name, ok := rawName.(string) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid name type %T", rawName) + } + + var cniVersion string + rawVersion, ok := rawList["cniVersion"] + if ok { + cniVersion, ok = rawVersion.(string) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion type %T", rawVersion) + } + } + + rawVersions, ok := rawList["cniVersions"] + if ok { + // Parse the current package CNI version + rvs, ok := rawVersions.([]interface{}) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions: %T", rvs) + } + vs := make([]string, 0, len(rvs)) + for i, rv := range rvs { + v, ok := rv.(string) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid type for cniVersions index %d: %T", i, rv) + } + gt, err := version.GreaterThan(v, version.Current()) + if err != nil { + return nil, fmt.Errorf("error parsing configuration list: invalid cniVersions entry %s at index %d: %w", v, i, err) + } else if !gt { + // Skip versions "greater" than this implementation of the spec + vs = append(vs, v) + } + } + + // if cniVersion was already set, append it to the list for sorting. + if cniVersion != "" { + gt, err := version.GreaterThan(cniVersion, version.Current()) + if err != nil { + return nil, fmt.Errorf("error parsing configuration list: invalid cniVersion %s: %w", cniVersion, err) + } else if !gt { + // ignore any versions higher than the current implemented spec version + vs = append(vs, cniVersion) + } + } + slices.SortFunc[[]string](vs, func(v1, v2 string) int { + if v1 == v2 { + return 0 + } + if gt, _ := version.GreaterThan(v1, v2); gt { + return 1 + } + return -1 + }) + if len(vs) > 0 { + cniVersion = vs[len(vs)-1] + } + } + + readBool := func(key string) (bool, error) { + rawVal, ok := rawList[key] + if !ok { + return false, nil + } + if b, ok := rawVal.(bool); ok { + return b, nil + } + + s, ok := rawVal.(string) + if !ok { + return false, fmt.Errorf("error parsing configuration list: invalid type %T for %s", rawVal, key) + } + s = strings.ToLower(s) + switch s { + case "false": + return false, nil + case "true": + return true, nil + } + return false, fmt.Errorf("error parsing configuration list: invalid value %q for %s", s, key) + } + + disableCheck, err := readBool("disableCheck") + if err != nil { + return nil, err + } + + disableGC, err := readBool("disableGC") + if err != nil { + return nil, err + } + + loadOnlyInlinedPlugins, err := readBool("loadOnlyInlinedPlugins") + if err != nil { + return nil, err + } + + list := &NetworkConfigList{ + Name: name, + DisableCheck: disableCheck, + DisableGC: disableGC, + LoadOnlyInlinedPlugins: loadOnlyInlinedPlugins, + CNIVersion: cniVersion, + Bytes: confBytes, + } + + var plugins []interface{} + plug, ok := rawList["plugins"] + // We can have a `plugins` list key in the main conf, + // We can also have `loadOnlyInlinedPlugins == true` + // + // If `plugins` is there, then `loadOnlyInlinedPlugins` can be true + // + // If plugins is NOT there, then `loadOnlyInlinedPlugins` cannot be true + // + // We have to have at least some plugins. + if !ok && loadOnlyInlinedPlugins { + return nil, fmt.Errorf("error parsing configuration list: `loadOnlyInlinedPlugins` is true, and no 'plugins' key") + } else if !ok && !loadOnlyInlinedPlugins { + return list, nil + } + + plugins, ok = plug.([]interface{}) + if !ok { + return nil, fmt.Errorf("error parsing configuration list: invalid 'plugins' type %T", plug) + } + if len(plugins) == 0 { + return nil, fmt.Errorf("error parsing configuration list: no plugins in list") + } + + for i, conf := range plugins { + newBytes, err := json.Marshal(conf) + if err != nil { + return nil, fmt.Errorf("failed to marshal plugin config %d: %w", i, err) + } + netConf, err := ConfFromBytes(newBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse plugin config %d: %w", i, err) + } + list.Plugins = append(list.Plugins, netConf) + } + return list, nil +} + +func NetworkConfFromFile(filename string) (*NetworkConfigList, error) { + bytes, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("error reading %s: %w", filename, err) + } + + conf, err := NetworkConfFromBytes(bytes) + if err != nil { + return nil, err + } + + if !conf.LoadOnlyInlinedPlugins { + plugins, err := NetworkPluginConfsFromFiles(filepath.Dir(filename), conf.Name) + if err != nil { + return nil, err + } + conf.Plugins = append(conf.Plugins, plugins...) + } + + if len(conf.Plugins) == 0 { + // Having 0 plugins for a given network is not necessarily a problem, + // but return as error for caller to decide, since they tried to load + return nil, fmt.Errorf("no plugin configs found") + } + return conf, nil +} + +// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions +func ConfFromBytes(bytes []byte) (*NetworkConfig, error) { + return NetworkPluginConfFromBytes(bytes) +} + +// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions +func ConfFromFile(filename string) (*NetworkConfig, error) { + bytes, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("error reading %s: %w", filename, err) + } + return ConfFromBytes(bytes) +} + +func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) { + return NetworkConfFromBytes(bytes) +} + +func ConfListFromFile(filename string) (*NetworkConfigList, error) { + return NetworkConfFromFile(filename) +} + +// ConfFiles simply returns a slice of all files in the provided directory +// with extensions matching the provided set. +func ConfFiles(dir string, extensions []string) ([]string, error) { + // In part, adapted from rkt/networking/podenv.go#listFiles + files, err := os.ReadDir(dir) + switch { + case err == nil: // break + case os.IsNotExist(err): + // If folder not there, return no error - only return an + // error if we cannot read contents or there are no contents. + return nil, nil + default: + return nil, err + } + + confFiles := []string{} + for _, f := range files { + if f.IsDir() { + continue + } + fileExt := filepath.Ext(f.Name()) + for _, ext := range extensions { + if fileExt == ext { + confFiles = append(confFiles, filepath.Join(dir, f.Name())) + } + } + } + return confFiles, nil +} + +// Deprecated: This file format is no longer supported, use NetworkConfXXX and NetworkPluginXXX functions +func LoadConf(dir, name string) (*NetworkConfig, error) { + files, err := ConfFiles(dir, []string{".conf", ".json"}) + switch { + case err != nil: + return nil, err + case len(files) == 0: + return nil, NoConfigsFoundError{Dir: dir} + } + sort.Strings(files) + + for _, confFile := range files { + conf, err := ConfFromFile(confFile) + if err != nil { + return nil, err + } + if conf.Network.Name == name { + return conf, nil + } + } + return nil, NotFoundError{dir, name} +} + +func LoadConfList(dir, name string) (*NetworkConfigList, error) { + return LoadNetworkConf(dir, name) +} + +// LoadNetworkConf looks at all the network configs in a given dir, +// loads and parses them all, and returns the first one with an extension of `.conf` +// that matches the provided network name predicate. +func LoadNetworkConf(dir, name string) (*NetworkConfigList, error) { + // TODO this .conflist/.conf extension thing is confusing and inexact + // for implementors. We should pick one extension for everything and stick with it. + files, err := ConfFiles(dir, []string{".conflist"}) + if err != nil { + return nil, err + } + sort.Strings(files) + + for _, confFile := range files { + conf, err := NetworkConfFromFile(confFile) + if err != nil { + return nil, err + } + if conf.Name == name { + return conf, nil + } + } + + // Deprecated: Try and load a network configuration file (instead of list) + // from the same name, then upconvert. + singleConf, err := LoadConf(dir, name) + if err != nil { + // A little extra logic so the error makes sense + var ncfErr NoConfigsFoundError + if len(files) != 0 && errors.As(err, &ncfErr) { + // Config lists found but no config files found + return nil, NotFoundError{dir, name} + } + + return nil, err + } + return ConfListFromConf(singleConf) +} + +// InjectConf takes a PluginConfig and inserts additional values into it, ensuring the result is serializable. +func InjectConf(original *PluginConfig, newValues map[string]interface{}) (*PluginConfig, error) { + config := make(map[string]interface{}) + err := json.Unmarshal(original.Bytes, &config) + if err != nil { + return nil, fmt.Errorf("unmarshal existing network bytes: %w", err) + } + + for key, value := range newValues { + if key == "" { + return nil, fmt.Errorf("keys cannot be empty") + } + + if value == nil { + return nil, fmt.Errorf("key '%s' value must not be nil", key) + } + + config[key] = value + } + + newBytes, err := json.Marshal(config) + if err != nil { + return nil, err + } + + return NetworkPluginConfFromBytes(newBytes) +} + +// ConfListFromConf "upconverts" a network config in to a NetworkConfigList, +// with the single network as the only entry in the list. +// +// Deprecated: Non-conflist file formats are unsupported, use NetworkConfXXX and NetworkPluginXXX functions +func ConfListFromConf(original *PluginConfig) (*NetworkConfigList, error) { + // Re-deserialize the config's json, then make a raw map configlist. + // This may seem a bit strange, but it's to make the Bytes fields + // actually make sense. Otherwise, the generated json is littered with + // golang default values. + + rawConfig := make(map[string]interface{}) + if err := json.Unmarshal(original.Bytes, &rawConfig); err != nil { + return nil, err + } + + rawConfigList := map[string]interface{}{ + "name": original.Network.Name, + "cniVersion": original.Network.CNIVersion, + "plugins": []interface{}{rawConfig}, + } + + b, err := json.Marshal(rawConfigList) + if err != nil { + return nil, err + } + return ConfListFromBytes(b) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/args.go b/vendor/github.com/containernetworking/cni/pkg/invoke/args.go new file mode 100644 index 00000000..3cdb4bc8 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/args.go @@ -0,0 +1,128 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "fmt" + "os" + "strings" +) + +type CNIArgs interface { + // For use with os/exec; i.e., return nil to inherit the + // environment from this process + // For use in delegation; inherit the environment from this + // process and allow overrides + AsEnv() []string +} + +type inherited struct{} + +var inheritArgsFromEnv inherited + +func (*inherited) AsEnv() []string { + return nil +} + +func ArgsFromEnv() CNIArgs { + return &inheritArgsFromEnv +} + +type Args struct { + Command string + ContainerID string + NetNS string + PluginArgs [][2]string + PluginArgsStr string + IfName string + Path string +} + +// Args implements the CNIArgs interface +var _ CNIArgs = &Args{} + +func (args *Args) AsEnv() []string { + env := os.Environ() + pluginArgsStr := args.PluginArgsStr + if pluginArgsStr == "" { + pluginArgsStr = stringify(args.PluginArgs) + } + + // Duplicated values which come first will be overridden, so we must put the + // custom values in the end to avoid being overridden by the process environments. + env = append(env, + "CNI_COMMAND="+args.Command, + "CNI_CONTAINERID="+args.ContainerID, + "CNI_NETNS="+args.NetNS, + "CNI_ARGS="+pluginArgsStr, + "CNI_IFNAME="+args.IfName, + "CNI_PATH="+args.Path, + ) + return dedupEnv(env) +} + +// taken from rkt/networking/net_plugin.go +func stringify(pluginArgs [][2]string) string { + entries := make([]string, len(pluginArgs)) + + for i, kv := range pluginArgs { + entries[i] = strings.Join(kv[:], "=") + } + + return strings.Join(entries, ";") +} + +// DelegateArgs implements the CNIArgs interface +// used for delegation to inherit from environments +// and allow some overrides like CNI_COMMAND +var _ CNIArgs = &DelegateArgs{} + +type DelegateArgs struct { + Command string +} + +func (d *DelegateArgs) AsEnv() []string { + env := os.Environ() + + // The custom values should come in the end to override the existing + // process environment of the same key. + env = append(env, + "CNI_COMMAND="+d.Command, + ) + return dedupEnv(env) +} + +// dedupEnv returns a copy of env with any duplicates removed, in favor of later values. +// Items not of the normal environment "key=value" form are preserved unchanged. +func dedupEnv(env []string) []string { + out := make([]string, 0, len(env)) + envMap := map[string]string{} + + for _, kv := range env { + // find the first "=" in environment, if not, just keep it + eq := strings.Index(kv, "=") + if eq < 0 { + out = append(out, kv) + continue + } + envMap[kv[:eq]] = kv[eq+1:] + } + + for k, v := range envMap { + out = append(out, fmt.Sprintf("%s=%s", k, v)) + } + + return out +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go new file mode 100644 index 00000000..c8b548e7 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go @@ -0,0 +1,89 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "context" + "os" + "path/filepath" + + "github.com/containernetworking/cni/pkg/types" +) + +func delegateCommon(delegatePlugin string, exec Exec) (string, Exec, error) { + if exec == nil { + exec = defaultExec + } + + paths := filepath.SplitList(os.Getenv("CNI_PATH")) + pluginPath, err := exec.FindInPath(delegatePlugin, paths) + if err != nil { + return "", nil, err + } + + return pluginPath, exec, nil +} + +// DelegateAdd calls the given delegate plugin with the CNI ADD action and +// JSON configuration +func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) { + pluginPath, realExec, err := delegateCommon(delegatePlugin, exec) + if err != nil { + return nil, err + } + + // DelegateAdd will override the original "CNI_COMMAND" env from process with ADD + return ExecPluginWithResult(ctx, pluginPath, netconf, delegateArgs("ADD"), realExec) +} + +// DelegateCheck calls the given delegate plugin with the CNI CHECK action and +// JSON configuration +func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { + return delegateNoResult(ctx, delegatePlugin, netconf, exec, "CHECK") +} + +func delegateNoResult(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec, verb string) error { + pluginPath, realExec, err := delegateCommon(delegatePlugin, exec) + if err != nil { + return err + } + + return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs(verb), realExec) +} + +// DelegateDel calls the given delegate plugin with the CNI DEL action and +// JSON configuration +func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { + return delegateNoResult(ctx, delegatePlugin, netconf, exec, "DEL") +} + +// DelegateStatus calls the given delegate plugin with the CNI STATUS action and +// JSON configuration +func DelegateStatus(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { + return delegateNoResult(ctx, delegatePlugin, netconf, exec, "STATUS") +} + +// DelegateGC calls the given delegate plugin with the CNI GC action and +// JSON configuration +func DelegateGC(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error { + return delegateNoResult(ctx, delegatePlugin, netconf, exec, "GC") +} + +// return CNIArgs used by delegation +func delegateArgs(action string) *DelegateArgs { + return &DelegateArgs{ + Command: action, + } +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go new file mode 100644 index 00000000..a5e015fc --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go @@ -0,0 +1,187 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/create" + "github.com/containernetworking/cni/pkg/version" +) + +// Exec is an interface encapsulates all operations that deal with finding +// and executing a CNI plugin. Tests may provide a fake implementation +// to avoid writing fake plugins to temporary directories during the test. +type Exec interface { + ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) + FindInPath(plugin string, paths []string) (string, error) + Decode(jsonBytes []byte) (version.PluginInfo, error) +} + +// Plugin must return result in same version as specified in netconf; but +// for backwards compatibility reasons if the result version is empty use +// config version (rather than technically correct 0.1.0). +// https://github.com/containernetworking/cni/issues/895 +func fixupResultVersion(netconf, result []byte) (string, []byte, error) { + versionDecoder := &version.ConfigDecoder{} + confVersion, err := versionDecoder.Decode(netconf) + if err != nil { + return "", nil, err + } + + var rawResult map[string]interface{} + if err := json.Unmarshal(result, &rawResult); err != nil { + return "", nil, fmt.Errorf("failed to unmarshal raw result: %w", err) + } + + // plugin output of "null" is successfully unmarshalled, but results in a nil + // map which causes a panic when the confVersion is assigned below. + if rawResult == nil { + rawResult = make(map[string]interface{}) + } + + // Manually decode Result version; we need to know whether its cniVersion + // is empty, while built-in decoders (correctly) substitute 0.1.0 for an + // empty version per the CNI spec. + if resultVerRaw, ok := rawResult["cniVersion"]; ok { + resultVer, ok := resultVerRaw.(string) + if ok && resultVer != "" { + return resultVer, result, nil + } + } + + // If the cniVersion is not present or empty, assume the result is + // the same CNI spec version as the config + rawResult["cniVersion"] = confVersion + newBytes, err := json.Marshal(rawResult) + if err != nil { + return "", nil, fmt.Errorf("failed to remarshal fixed result: %w", err) + } + + return confVersion, newBytes, nil +} + +// For example, a testcase could pass an instance of the following fakeExec +// object to ExecPluginWithResult() to verify the incoming stdin and environment +// and provide a tailored response: +// +// import ( +// "encoding/json" +// "path" +// "strings" +// ) +// +// type fakeExec struct { +// version.PluginDecoder +// } +// +// func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { +// net := &types.NetConf{} +// err := json.Unmarshal(stdinData, net) +// if err != nil { +// return nil, fmt.Errorf("failed to unmarshal configuration: %v", err) +// } +// pluginName := path.Base(pluginPath) +// if pluginName != net.Type { +// return nil, fmt.Errorf("plugin name %q did not match config type %q", pluginName, net.Type) +// } +// for _, e := range environ { +// // Check environment for forced failure request +// parts := strings.Split(e, "=") +// if len(parts) > 0 && parts[0] == "FAIL" { +// return nil, fmt.Errorf("failed to execute plugin %s", pluginName) +// } +// } +// return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil +// } +// +// func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) { +// if len(paths) > 0 { +// return path.Join(paths[0], plugin), nil +// } +// return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths) +// } + +func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) { + if exec == nil { + exec = defaultExec + } + + stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv()) + if err != nil { + return nil, err + } + + resultVersion, fixedBytes, err := fixupResultVersion(netconf, stdoutBytes) + if err != nil { + return nil, err + } + + return create.Create(resultVersion, fixedBytes) +} + +func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error { + if exec == nil { + exec = defaultExec + } + _, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv()) + return err +} + +// GetVersionInfo returns the version information available about the plugin. +// For recent-enough plugins, it uses the information returned by the VERSION +// command. For older plugins which do not recognize that command, it reports +// version 0.1.0 +func GetVersionInfo(ctx context.Context, pluginPath string, exec Exec) (version.PluginInfo, error) { + if exec == nil { + exec = defaultExec + } + args := &Args{ + Command: "VERSION", + + // set fake values required by plugins built against an older version of skel + NetNS: "dummy", + IfName: "dummy", + Path: "dummy", + } + stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current())) + stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, stdin, args.AsEnv()) + if err != nil { + if err.Error() == "unknown CNI_COMMAND: VERSION" { + return version.PluginSupports("0.1.0"), nil + } + return nil, err + } + + return exec.Decode(stdoutBytes) +} + +// DefaultExec is an object that implements the Exec interface which looks +// for and executes plugins from disk. +type DefaultExec struct { + *RawExec + version.PluginDecoder +} + +// DefaultExec implements the Exec interface +var _ Exec = &DefaultExec{} + +var defaultExec = &DefaultExec{ + RawExec: &RawExec{Stderr: os.Stderr}, +} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/find.go b/vendor/github.com/containernetworking/cni/pkg/invoke/find.go new file mode 100644 index 00000000..e62029eb --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/find.go @@ -0,0 +1,48 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +// FindInPath returns the full path of the plugin by searching in the provided path +func FindInPath(plugin string, paths []string) (string, error) { + if plugin == "" { + return "", fmt.Errorf("no plugin name provided") + } + + if strings.ContainsRune(plugin, os.PathSeparator) { + return "", fmt.Errorf("invalid plugin name: %s", plugin) + } + + if len(paths) == 0 { + return "", fmt.Errorf("no paths provided") + } + + for _, path := range paths { + for _, fe := range ExecutableFileExtensions { + fullpath := filepath.Join(path, plugin) + fe + if fi, err := os.Stat(fullpath); err == nil && fi.Mode().IsRegular() { + return fullpath, nil + } + } + } + + return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths) +} diff --git a/vendor/github.com/petermattis/goid/goid_go1.5_amd64.s b/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go similarity index 50% rename from vendor/github.com/petermattis/goid/goid_go1.5_amd64.s rename to vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go index 416665dd..ed0999bd 100644 --- a/vendor/github.com/petermattis/goid/goid_go1.5_amd64.s +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/os_unix.go @@ -1,4 +1,4 @@ -// Copyright 2016 Peter Mattis. +// Copyright 2016 CNI authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -8,22 +8,14 @@ // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. See the AUTHORS file -// for names of contributors. +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -// Assembly to mimic runtime.getg. +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris -// +build amd64 amd64p32 -// +build gc,go1.5 +package invoke -#include "go_asm.h" -#include "textflag.h" - -// func Get() int64 -TEXT ·Get(SB),NOSPLIT,$0-8 - MOVQ (TLS), R14 - MOVQ g_goid(R14), R13 - MOVQ R13, ret+0(FP) - RET +// Valid file extensions for plugin executables. +var ExecutableFileExtensions = []string{""} diff --git a/vendor/github.com/petermattis/goid/goid_go1.5_amd64.go b/vendor/github.com/containernetworking/cni/pkg/invoke/os_windows.go similarity index 61% rename from vendor/github.com/petermattis/goid/goid_go1.5_amd64.go rename to vendor/github.com/containernetworking/cni/pkg/invoke/os_windows.go index 269abb3f..7665125b 100644 --- a/vendor/github.com/petermattis/goid/goid_go1.5_amd64.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/os_windows.go @@ -1,4 +1,4 @@ -// Copyright 2016 Peter Mattis. +// Copyright 2016 CNI authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -8,14 +8,11 @@ // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. See the AUTHORS file -// for names of contributors. +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -// +build amd64 amd64p32 -// +build gc,go1.5 +package invoke -package goid - -func Get() int64 +// Valid file extensions for plugin executables. +var ExecutableFileExtensions = []string{".exe", ""} diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go new file mode 100644 index 00000000..5ab5cc88 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go @@ -0,0 +1,88 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package invoke + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "os/exec" + "strings" + "time" + + "github.com/containernetworking/cni/pkg/types" +) + +type RawExec struct { + Stderr io.Writer +} + +func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) { + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + c := exec.CommandContext(ctx, pluginPath) + c.Env = environ + c.Stdin = bytes.NewBuffer(stdinData) + c.Stdout = stdout + c.Stderr = stderr + + // Retry the command on "text file busy" errors + for i := 0; i <= 5; i++ { + err := c.Run() + + // Command succeeded + if err == nil { + break + } + + // If the plugin is currently about to be written, then we wait a + // second and try it again + if strings.Contains(err.Error(), "text file busy") { + time.Sleep(time.Second) + continue + } + + // All other errors except than the busy text file + return nil, e.pluginErr(err, stdout.Bytes(), stderr.Bytes()) + } + + // Copy stderr to caller's buffer in case plugin printed to both + // stdout and stderr for some reason. Ignore failures as stderr is + // only informational. + if e.Stderr != nil && stderr.Len() > 0 { + _, _ = stderr.WriteTo(e.Stderr) + } + return stdout.Bytes(), nil +} + +func (e *RawExec) pluginErr(err error, stdout, stderr []byte) error { + emsg := types.Error{} + if len(stdout) == 0 { + if len(stderr) == 0 { + emsg.Msg = fmt.Sprintf("netplugin failed with no error message: %v", err) + } else { + emsg.Msg = fmt.Sprintf("netplugin failed: %q", string(stderr)) + } + } else if perr := json.Unmarshal(stdout, &emsg); perr != nil { + emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(stdout), perr) + } + return &emsg +} + +func (e *RawExec) FindInPath(plugin string, paths []string) (string, error) { + return FindInPath(plugin, paths) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/020/types.go b/vendor/github.com/containernetworking/cni/pkg/types/020/types.go new file mode 100644 index 00000000..99b151ff --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/020/types.go @@ -0,0 +1,189 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types020 + +import ( + "encoding/json" + "fmt" + "io" + "net" + "os" + + "github.com/containernetworking/cni/pkg/types" + convert "github.com/containernetworking/cni/pkg/types/internal" +) + +const ImplementedSpecVersion string = "0.2.0" + +var supportedVersions = []string{"", "0.1.0", ImplementedSpecVersion} + +// Register converters for all versions less than the implemented spec version +func init() { + convert.RegisterConverter("0.1.0", []string{ImplementedSpecVersion}, convertFrom010) + convert.RegisterConverter(ImplementedSpecVersion, []string{"0.1.0"}, convertTo010) + + // Creator + convert.RegisterCreator(supportedVersions, NewResult) +} + +// Compatibility types for CNI version 0.1.0 and 0.2.0 + +// NewResult creates a new Result object from JSON data. The JSON data +// must be compatible with the CNI versions implemented by this type. +func NewResult(data []byte) (types.Result, error) { + result := &Result{} + if err := json.Unmarshal(data, result); err != nil { + return nil, err + } + for _, v := range supportedVersions { + if result.CNIVersion == v { + if result.CNIVersion == "" { + result.CNIVersion = "0.1.0" + } + return result, nil + } + } + return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q", + supportedVersions, result.CNIVersion) +} + +// GetResult converts the given Result object to the ImplementedSpecVersion +// and returns the concrete type or an error +func GetResult(r types.Result) (*Result, error) { + result020, err := convert.Convert(r, ImplementedSpecVersion) + if err != nil { + return nil, err + } + result, ok := result020.(*Result) + if !ok { + return nil, fmt.Errorf("failed to convert result") + } + return result, nil +} + +func convertFrom010(from types.Result, toVersion string) (types.Result, error) { + if toVersion != "0.2.0" { + panic("only converts to version 0.2.0") + } + fromResult := from.(*Result) + return &Result{ + CNIVersion: ImplementedSpecVersion, + IP4: fromResult.IP4.Copy(), + IP6: fromResult.IP6.Copy(), + DNS: *fromResult.DNS.Copy(), + }, nil +} + +func convertTo010(from types.Result, toVersion string) (types.Result, error) { + if toVersion != "0.1.0" { + panic("only converts to version 0.1.0") + } + fromResult := from.(*Result) + return &Result{ + CNIVersion: "0.1.0", + IP4: fromResult.IP4.Copy(), + IP6: fromResult.IP6.Copy(), + DNS: *fromResult.DNS.Copy(), + }, nil +} + +// Result is what gets returned from the plugin (via stdout) to the caller +type Result struct { + CNIVersion string `json:"cniVersion,omitempty"` + IP4 *IPConfig `json:"ip4,omitempty"` + IP6 *IPConfig `json:"ip6,omitempty"` + DNS types.DNS `json:"dns,omitempty"` +} + +func (r *Result) Version() string { + return r.CNIVersion +} + +func (r *Result) GetAsVersion(version string) (types.Result, error) { + // If the creator of the result did not set the CNIVersion, assume it + // should be the highest spec version implemented by this Result + if r.CNIVersion == "" { + r.CNIVersion = ImplementedSpecVersion + } + return convert.Convert(r, version) +} + +func (r *Result) Print() error { + return r.PrintTo(os.Stdout) +} + +func (r *Result) PrintTo(writer io.Writer) error { + data, err := json.MarshalIndent(r, "", " ") + if err != nil { + return err + } + _, err = writer.Write(data) + return err +} + +// IPConfig contains values necessary to configure an interface +type IPConfig struct { + IP net.IPNet + Gateway net.IP + Routes []types.Route +} + +func (i *IPConfig) Copy() *IPConfig { + if i == nil { + return nil + } + + var routes []types.Route + for _, fromRoute := range i.Routes { + routes = append(routes, *fromRoute.Copy()) + } + return &IPConfig{ + IP: i.IP, + Gateway: i.Gateway, + Routes: routes, + } +} + +// net.IPNet is not JSON (un)marshallable so this duality is needed +// for our custom IPNet type + +// JSON (un)marshallable types +type ipConfig struct { + IP types.IPNet `json:"ip"` + Gateway net.IP `json:"gateway,omitempty"` + Routes []types.Route `json:"routes,omitempty"` +} + +func (c *IPConfig) MarshalJSON() ([]byte, error) { + ipc := ipConfig{ + IP: types.IPNet(c.IP), + Gateway: c.Gateway, + Routes: c.Routes, + } + + return json.Marshal(ipc) +} + +func (c *IPConfig) UnmarshalJSON(data []byte) error { + ipc := ipConfig{} + if err := json.Unmarshal(data, &ipc); err != nil { + return err + } + + c.IP = net.IPNet(ipc.IP) + c.Gateway = ipc.Gateway + c.Routes = ipc.Routes + return nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/040/types.go b/vendor/github.com/containernetworking/cni/pkg/types/040/types.go new file mode 100644 index 00000000..3633b0ea --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/040/types.go @@ -0,0 +1,306 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types040 + +import ( + "encoding/json" + "fmt" + "io" + "net" + "os" + + "github.com/containernetworking/cni/pkg/types" + types020 "github.com/containernetworking/cni/pkg/types/020" + convert "github.com/containernetworking/cni/pkg/types/internal" +) + +const ImplementedSpecVersion string = "0.4.0" + +var supportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion} + +// Register converters for all versions less than the implemented spec version +func init() { + // Up-converters + convert.RegisterConverter("0.1.0", supportedVersions, convertFrom02x) + convert.RegisterConverter("0.2.0", supportedVersions, convertFrom02x) + convert.RegisterConverter("0.3.0", supportedVersions, convertInternal) + convert.RegisterConverter("0.3.1", supportedVersions, convertInternal) + + // Down-converters + convert.RegisterConverter("0.4.0", []string{"0.3.0", "0.3.1"}, convertInternal) + convert.RegisterConverter("0.4.0", []string{"0.1.0", "0.2.0"}, convertTo02x) + convert.RegisterConverter("0.3.1", []string{"0.1.0", "0.2.0"}, convertTo02x) + convert.RegisterConverter("0.3.0", []string{"0.1.0", "0.2.0"}, convertTo02x) + + // Creator + convert.RegisterCreator(supportedVersions, NewResult) +} + +func NewResult(data []byte) (types.Result, error) { + result := &Result{} + if err := json.Unmarshal(data, result); err != nil { + return nil, err + } + for _, v := range supportedVersions { + if result.CNIVersion == v { + return result, nil + } + } + return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q", + supportedVersions, result.CNIVersion) +} + +func GetResult(r types.Result) (*Result, error) { + resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion) + if err != nil { + return nil, err + } + result, ok := resultCurrent.(*Result) + if !ok { + return nil, fmt.Errorf("failed to convert result") + } + return result, nil +} + +func NewResultFromResult(result types.Result) (*Result, error) { + newResult, err := convert.Convert(result, ImplementedSpecVersion) + if err != nil { + return nil, err + } + return newResult.(*Result), nil +} + +// Result is what gets returned from the plugin (via stdout) to the caller +type Result struct { + CNIVersion string `json:"cniVersion,omitempty"` + Interfaces []*Interface `json:"interfaces,omitempty"` + IPs []*IPConfig `json:"ips,omitempty"` + Routes []*types.Route `json:"routes,omitempty"` + DNS types.DNS `json:"dns,omitempty"` +} + +func convert020IPConfig(from *types020.IPConfig, ipVersion string) *IPConfig { + return &IPConfig{ + Version: ipVersion, + Address: from.IP, + Gateway: from.Gateway, + } +} + +func convertFrom02x(from types.Result, toVersion string) (types.Result, error) { + fromResult := from.(*types020.Result) + toResult := &Result{ + CNIVersion: toVersion, + DNS: *fromResult.DNS.Copy(), + Routes: []*types.Route{}, + } + if fromResult.IP4 != nil { + toResult.IPs = append(toResult.IPs, convert020IPConfig(fromResult.IP4, "4")) + for _, fromRoute := range fromResult.IP4.Routes { + toResult.Routes = append(toResult.Routes, fromRoute.Copy()) + } + } + + if fromResult.IP6 != nil { + toResult.IPs = append(toResult.IPs, convert020IPConfig(fromResult.IP6, "6")) + for _, fromRoute := range fromResult.IP6.Routes { + toResult.Routes = append(toResult.Routes, fromRoute.Copy()) + } + } + + return toResult, nil +} + +func convertInternal(from types.Result, toVersion string) (types.Result, error) { + fromResult := from.(*Result) + toResult := &Result{ + CNIVersion: toVersion, + DNS: *fromResult.DNS.Copy(), + Routes: []*types.Route{}, + } + for _, fromIntf := range fromResult.Interfaces { + toResult.Interfaces = append(toResult.Interfaces, fromIntf.Copy()) + } + for _, fromIPC := range fromResult.IPs { + toResult.IPs = append(toResult.IPs, fromIPC.Copy()) + } + for _, fromRoute := range fromResult.Routes { + toResult.Routes = append(toResult.Routes, fromRoute.Copy()) + } + return toResult, nil +} + +func convertTo02x(from types.Result, toVersion string) (types.Result, error) { + fromResult := from.(*Result) + toResult := &types020.Result{ + CNIVersion: toVersion, + DNS: *fromResult.DNS.Copy(), + } + + for _, fromIP := range fromResult.IPs { + // Only convert the first IP address of each version as 0.2.0 + // and earlier cannot handle multiple IP addresses + if fromIP.Version == "4" && toResult.IP4 == nil { + toResult.IP4 = &types020.IPConfig{ + IP: fromIP.Address, + Gateway: fromIP.Gateway, + } + } else if fromIP.Version == "6" && toResult.IP6 == nil { + toResult.IP6 = &types020.IPConfig{ + IP: fromIP.Address, + Gateway: fromIP.Gateway, + } + } + if toResult.IP4 != nil && toResult.IP6 != nil { + break + } + } + + for _, fromRoute := range fromResult.Routes { + is4 := fromRoute.Dst.IP.To4() != nil + if is4 && toResult.IP4 != nil { + toResult.IP4.Routes = append(toResult.IP4.Routes, types.Route{ + Dst: fromRoute.Dst, + GW: fromRoute.GW, + }) + } else if !is4 && toResult.IP6 != nil { + toResult.IP6.Routes = append(toResult.IP6.Routes, types.Route{ + Dst: fromRoute.Dst, + GW: fromRoute.GW, + }) + } + } + + // 0.2.0 and earlier require at least one IP address in the Result + if toResult.IP4 == nil && toResult.IP6 == nil { + return nil, fmt.Errorf("cannot convert: no valid IP addresses") + } + + return toResult, nil +} + +func (r *Result) Version() string { + return r.CNIVersion +} + +func (r *Result) GetAsVersion(version string) (types.Result, error) { + // If the creator of the result did not set the CNIVersion, assume it + // should be the highest spec version implemented by this Result + if r.CNIVersion == "" { + r.CNIVersion = ImplementedSpecVersion + } + return convert.Convert(r, version) +} + +func (r *Result) Print() error { + return r.PrintTo(os.Stdout) +} + +func (r *Result) PrintTo(writer io.Writer) error { + data, err := json.MarshalIndent(r, "", " ") + if err != nil { + return err + } + _, err = writer.Write(data) + return err +} + +// Interface contains values about the created interfaces +type Interface struct { + Name string `json:"name"` + Mac string `json:"mac,omitempty"` + Sandbox string `json:"sandbox,omitempty"` +} + +func (i *Interface) String() string { + return fmt.Sprintf("%+v", *i) +} + +func (i *Interface) Copy() *Interface { + if i == nil { + return nil + } + newIntf := *i + return &newIntf +} + +// Int returns a pointer to the int value passed in. Used to +// set the IPConfig.Interface field. +func Int(v int) *int { + return &v +} + +// IPConfig contains values necessary to configure an IP address on an interface +type IPConfig struct { + // IP version, either "4" or "6" + Version string + // Index into Result structs Interfaces list + Interface *int + Address net.IPNet + Gateway net.IP +} + +func (i *IPConfig) String() string { + return fmt.Sprintf("%+v", *i) +} + +func (i *IPConfig) Copy() *IPConfig { + if i == nil { + return nil + } + + ipc := &IPConfig{ + Version: i.Version, + Address: i.Address, + Gateway: i.Gateway, + } + if i.Interface != nil { + intf := *i.Interface + ipc.Interface = &intf + } + return ipc +} + +// JSON (un)marshallable types +type ipConfig struct { + Version string `json:"version"` + Interface *int `json:"interface,omitempty"` + Address types.IPNet `json:"address"` + Gateway net.IP `json:"gateway,omitempty"` +} + +func (c *IPConfig) MarshalJSON() ([]byte, error) { + ipc := ipConfig{ + Version: c.Version, + Interface: c.Interface, + Address: types.IPNet(c.Address), + Gateway: c.Gateway, + } + + return json.Marshal(ipc) +} + +func (c *IPConfig) UnmarshalJSON(data []byte) error { + ipc := ipConfig{} + if err := json.Unmarshal(data, &ipc); err != nil { + return err + } + + c.Version = ipc.Version + c.Interface = ipc.Interface + c.Address = net.IPNet(ipc.Address) + c.Gateway = ipc.Gateway + return nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/100/types.go b/vendor/github.com/containernetworking/cni/pkg/types/100/types.go new file mode 100644 index 00000000..f58b9120 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/100/types.go @@ -0,0 +1,352 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types100 + +import ( + "encoding/json" + "fmt" + "io" + "net" + "os" + + "github.com/containernetworking/cni/pkg/types" + types040 "github.com/containernetworking/cni/pkg/types/040" + convert "github.com/containernetworking/cni/pkg/types/internal" +) + +// The types did not change between v1.0 and v1.1 +const ImplementedSpecVersion string = "1.1.0" + +var supportedVersions = []string{"1.0.0", "1.1.0"} + +// Register converters for all versions less than the implemented spec version +func init() { + // Up-converters + convert.RegisterConverter("0.1.0", supportedVersions, convertFrom02x) + convert.RegisterConverter("0.2.0", supportedVersions, convertFrom02x) + convert.RegisterConverter("0.3.0", supportedVersions, convertFrom04x) + convert.RegisterConverter("0.3.1", supportedVersions, convertFrom04x) + convert.RegisterConverter("0.4.0", supportedVersions, convertFrom04x) + convert.RegisterConverter("1.0.0", []string{"1.1.0"}, convertFrom100) + + // Down-converters + convert.RegisterConverter("1.0.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x) + convert.RegisterConverter("1.0.0", []string{"0.1.0", "0.2.0"}, convertTo02x) + convert.RegisterConverter("1.1.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x) + convert.RegisterConverter("1.1.0", []string{"0.1.0", "0.2.0"}, convertTo02x) + convert.RegisterConverter("1.1.0", []string{"1.0.0"}, convertFrom100) + + // Creator + convert.RegisterCreator(supportedVersions, NewResult) +} + +func NewResult(data []byte) (types.Result, error) { + result := &Result{} + if err := json.Unmarshal(data, result); err != nil { + return nil, err + } + for _, v := range supportedVersions { + if result.CNIVersion == v { + return result, nil + } + } + return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q", + supportedVersions, result.CNIVersion) +} + +func GetResult(r types.Result) (*Result, error) { + resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion) + if err != nil { + return nil, err + } + result, ok := resultCurrent.(*Result) + if !ok { + return nil, fmt.Errorf("failed to convert result") + } + return result, nil +} + +func NewResultFromResult(result types.Result) (*Result, error) { + newResult, err := convert.Convert(result, ImplementedSpecVersion) + if err != nil { + return nil, err + } + return newResult.(*Result), nil +} + +// Result is what gets returned from the plugin (via stdout) to the caller +type Result struct { + CNIVersion string `json:"cniVersion,omitempty"` + Interfaces []*Interface `json:"interfaces,omitempty"` + IPs []*IPConfig `json:"ips,omitempty"` + Routes []*types.Route `json:"routes,omitempty"` + DNS types.DNS `json:"dns,omitempty"` +} + +// Note: DNS should be omit if DNS is empty but default Marshal function +// will output empty structure hence need to write a Marshal function +func (r *Result) MarshalJSON() ([]byte, error) { + // use type alias to escape recursion for json.Marshal() to MarshalJSON() + type fixObjType = Result + + bytes, err := json.Marshal(fixObjType(*r)) //nolint:all + if err != nil { + return nil, err + } + + fixupObj := make(map[string]interface{}) + if err := json.Unmarshal(bytes, &fixupObj); err != nil { + return nil, err + } + + if r.DNS.IsEmpty() { + delete(fixupObj, "dns") + } + + return json.Marshal(fixupObj) +} + +// convertFrom100 does nothing except set the version; the types are the same +func convertFrom100(from types.Result, toVersion string) (types.Result, error) { + fromResult := from.(*Result) + + result := &Result{ + CNIVersion: toVersion, + Interfaces: fromResult.Interfaces, + IPs: fromResult.IPs, + Routes: fromResult.Routes, + DNS: fromResult.DNS, + } + return result, nil +} + +func convertFrom02x(from types.Result, toVersion string) (types.Result, error) { + result040, err := convert.Convert(from, "0.4.0") + if err != nil { + return nil, err + } + result100, err := convertFrom04x(result040, toVersion) + if err != nil { + return nil, err + } + return result100, nil +} + +func convertIPConfigFrom040(from *types040.IPConfig) *IPConfig { + to := &IPConfig{ + Address: from.Address, + Gateway: from.Gateway, + } + if from.Interface != nil { + intf := *from.Interface + to.Interface = &intf + } + return to +} + +func convertInterfaceFrom040(from *types040.Interface) *Interface { + return &Interface{ + Name: from.Name, + Mac: from.Mac, + Sandbox: from.Sandbox, + } +} + +func convertFrom04x(from types.Result, toVersion string) (types.Result, error) { + fromResult := from.(*types040.Result) + toResult := &Result{ + CNIVersion: toVersion, + DNS: *fromResult.DNS.Copy(), + Routes: []*types.Route{}, + } + for _, fromIntf := range fromResult.Interfaces { + toResult.Interfaces = append(toResult.Interfaces, convertInterfaceFrom040(fromIntf)) + } + for _, fromIPC := range fromResult.IPs { + toResult.IPs = append(toResult.IPs, convertIPConfigFrom040(fromIPC)) + } + for _, fromRoute := range fromResult.Routes { + toResult.Routes = append(toResult.Routes, fromRoute.Copy()) + } + return toResult, nil +} + +func convertIPConfigTo040(from *IPConfig) *types040.IPConfig { + version := "6" + if from.Address.IP.To4() != nil { + version = "4" + } + to := &types040.IPConfig{ + Version: version, + Address: from.Address, + Gateway: from.Gateway, + } + if from.Interface != nil { + intf := *from.Interface + to.Interface = &intf + } + return to +} + +func convertInterfaceTo040(from *Interface) *types040.Interface { + return &types040.Interface{ + Name: from.Name, + Mac: from.Mac, + Sandbox: from.Sandbox, + } +} + +func convertTo04x(from types.Result, toVersion string) (types.Result, error) { + fromResult := from.(*Result) + toResult := &types040.Result{ + CNIVersion: toVersion, + DNS: *fromResult.DNS.Copy(), + Routes: []*types.Route{}, + } + for _, fromIntf := range fromResult.Interfaces { + toResult.Interfaces = append(toResult.Interfaces, convertInterfaceTo040(fromIntf)) + } + for _, fromIPC := range fromResult.IPs { + toResult.IPs = append(toResult.IPs, convertIPConfigTo040(fromIPC)) + } + for _, fromRoute := range fromResult.Routes { + toResult.Routes = append(toResult.Routes, fromRoute.Copy()) + } + return toResult, nil +} + +func convertTo02x(from types.Result, toVersion string) (types.Result, error) { + // First convert to 0.4.0 + result040, err := convertTo04x(from, "0.4.0") + if err != nil { + return nil, err + } + result02x, err := convert.Convert(result040, toVersion) + if err != nil { + return nil, err + } + return result02x, nil +} + +func (r *Result) Version() string { + return r.CNIVersion +} + +func (r *Result) GetAsVersion(version string) (types.Result, error) { + // If the creator of the result did not set the CNIVersion, assume it + // should be the highest spec version implemented by this Result + if r.CNIVersion == "" { + r.CNIVersion = ImplementedSpecVersion + } + return convert.Convert(r, version) +} + +func (r *Result) Print() error { + return r.PrintTo(os.Stdout) +} + +func (r *Result) PrintTo(writer io.Writer) error { + data, err := json.MarshalIndent(r, "", " ") + if err != nil { + return err + } + _, err = writer.Write(data) + return err +} + +// Interface contains values about the created interfaces +type Interface struct { + Name string `json:"name"` + Mac string `json:"mac,omitempty"` + Mtu int `json:"mtu,omitempty"` + Sandbox string `json:"sandbox,omitempty"` + SocketPath string `json:"socketPath,omitempty"` + PciID string `json:"pciID,omitempty"` +} + +func (i *Interface) String() string { + return fmt.Sprintf("%+v", *i) +} + +func (i *Interface) Copy() *Interface { + if i == nil { + return nil + } + newIntf := *i + return &newIntf +} + +// Int returns a pointer to the int value passed in. Used to +// set the IPConfig.Interface field. +func Int(v int) *int { + return &v +} + +// IPConfig contains values necessary to configure an IP address on an interface +type IPConfig struct { + // Index into Result structs Interfaces list + Interface *int + Address net.IPNet + Gateway net.IP +} + +func (i *IPConfig) String() string { + return fmt.Sprintf("%+v", *i) +} + +func (i *IPConfig) Copy() *IPConfig { + if i == nil { + return nil + } + + ipc := &IPConfig{ + Address: i.Address, + Gateway: i.Gateway, + } + if i.Interface != nil { + intf := *i.Interface + ipc.Interface = &intf + } + return ipc +} + +// JSON (un)marshallable types +type ipConfig struct { + Interface *int `json:"interface,omitempty"` + Address types.IPNet `json:"address"` + Gateway net.IP `json:"gateway,omitempty"` +} + +func (c *IPConfig) MarshalJSON() ([]byte, error) { + ipc := ipConfig{ + Interface: c.Interface, + Address: types.IPNet(c.Address), + Gateway: c.Gateway, + } + + return json.Marshal(ipc) +} + +func (c *IPConfig) UnmarshalJSON(data []byte) error { + ipc := ipConfig{} + if err := json.Unmarshal(data, &ipc); err != nil { + return err + } + + c.Interface = ipc.Interface + c.Address = net.IPNet(ipc.Address) + c.Gateway = ipc.Gateway + return nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/args.go b/vendor/github.com/containernetworking/cni/pkg/types/args.go new file mode 100644 index 00000000..68a602bf --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/args.go @@ -0,0 +1,122 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding" + "fmt" + "reflect" + "strings" +) + +// UnmarshallableBool typedef for builtin bool +// because builtin type's methods can't be declared +type UnmarshallableBool bool + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// Returns boolean true if the string is "1" or "true" or "True" +// Returns boolean false if the string is "0" or "false" or "False” +func (b *UnmarshallableBool) UnmarshalText(data []byte) error { + s := strings.ToLower(string(data)) + switch s { + case "1", "true": + *b = true + case "0", "false": + *b = false + default: + return fmt.Errorf("boolean unmarshal error: invalid input %s", s) + } + return nil +} + +// UnmarshallableString typedef for builtin string +type UnmarshallableString string + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// Returns the string +func (s *UnmarshallableString) UnmarshalText(data []byte) error { + *s = UnmarshallableString(data) + return nil +} + +// CommonArgs contains the IgnoreUnknown argument +// and must be embedded by all Arg structs +type CommonArgs struct { + IgnoreUnknown UnmarshallableBool `json:"ignoreunknown,omitempty"` +} + +// GetKeyField is a helper function to receive Values +// Values that represent a pointer to a struct +func GetKeyField(keyString string, v reflect.Value) reflect.Value { + return v.Elem().FieldByName(keyString) +} + +// UnmarshalableArgsError is used to indicate error unmarshalling args +// from the args-string in the form "K=V;K2=V2;..." +type UnmarshalableArgsError struct { + error +} + +// LoadArgs parses args from a string in the form "K=V;K2=V2;..." +func LoadArgs(args string, container interface{}) error { + if args == "" { + return nil + } + + containerValue := reflect.ValueOf(container) + + pairs := strings.Split(args, ";") + unknownArgs := []string{} + for _, pair := range pairs { + kv := strings.Split(pair, "=") + if len(kv) != 2 { + return fmt.Errorf("ARGS: invalid pair %q", pair) + } + keyString := kv[0] + valueString := kv[1] + keyField := GetKeyField(keyString, containerValue) + if !keyField.IsValid() { + unknownArgs = append(unknownArgs, pair) + continue + } + + var keyFieldInterface interface{} + switch { + case keyField.Kind() == reflect.Ptr: + keyField.Set(reflect.New(keyField.Type().Elem())) + keyFieldInterface = keyField.Interface() + case keyField.CanAddr() && keyField.Addr().CanInterface(): + keyFieldInterface = keyField.Addr().Interface() + default: + return UnmarshalableArgsError{fmt.Errorf("field '%s' has no valid interface", keyString)} + } + u, ok := keyFieldInterface.(encoding.TextUnmarshaler) + if !ok { + return UnmarshalableArgsError{fmt.Errorf( + "ARGS: cannot unmarshal into field '%s' - type '%s' does not implement encoding.TextUnmarshaler", + keyString, reflect.TypeOf(keyFieldInterface))} + } + err := u.UnmarshalText([]byte(valueString)) + if err != nil { + return fmt.Errorf("ARGS: error parsing value of pair %q: %w", pair, err) + } + } + + isIgnoreUnknown := GetKeyField("IgnoreUnknown", containerValue).Bool() + if len(unknownArgs) > 0 && !isIgnoreUnknown { + return fmt.Errorf("ARGS: unknown args %q", unknownArgs) + } + return nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/create/create.go b/vendor/github.com/containernetworking/cni/pkg/types/create/create.go new file mode 100644 index 00000000..452cb622 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/create/create.go @@ -0,0 +1,59 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package create + +import ( + "encoding/json" + "fmt" + + "github.com/containernetworking/cni/pkg/types" + _ "github.com/containernetworking/cni/pkg/types/020" + _ "github.com/containernetworking/cni/pkg/types/040" + _ "github.com/containernetworking/cni/pkg/types/100" + convert "github.com/containernetworking/cni/pkg/types/internal" +) + +// DecodeVersion returns the CNI version from CNI configuration or result JSON, +// or an error if the operation could not be performed. +func DecodeVersion(jsonBytes []byte) (string, error) { + var conf struct { + CNIVersion string `json:"cniVersion"` + } + err := json.Unmarshal(jsonBytes, &conf) + if err != nil { + return "", fmt.Errorf("decoding version from network config: %w", err) + } + if conf.CNIVersion == "" { + return "0.1.0", nil + } + return conf.CNIVersion, nil +} + +// Create creates a CNI Result using the given JSON with the expected +// version, or an error if the creation could not be performed +func Create(version string, bytes []byte) (types.Result, error) { + return convert.Create(version, bytes) +} + +// CreateFromBytes creates a CNI Result from the given JSON, automatically +// detecting the CNI spec version of the result. An error is returned if the +// operation could not be performed. +func CreateFromBytes(bytes []byte) (types.Result, error) { + version, err := DecodeVersion(bytes) + if err != nil { + return nil, err + } + return convert.Create(version, bytes) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/internal/convert.go b/vendor/github.com/containernetworking/cni/pkg/types/internal/convert.go new file mode 100644 index 00000000..bdbe4b0a --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/internal/convert.go @@ -0,0 +1,92 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "fmt" + + "github.com/containernetworking/cni/pkg/types" +) + +// ConvertFn should convert from the given arbitrary Result type into a +// Result implementing CNI specification version passed in toVersion. +// The function is guaranteed to be passed a Result type matching the +// fromVersion it was registered with, and is guaranteed to be +// passed a toVersion matching one of the toVersions it was registered with. +type ConvertFn func(from types.Result, toVersion string) (types.Result, error) + +type converter struct { + // fromVersion is the CNI Result spec version that convertFn accepts + fromVersion string + // toVersions is a list of versions that convertFn can convert to + toVersions []string + convertFn ConvertFn +} + +var converters []*converter + +func findConverter(fromVersion, toVersion string) *converter { + for _, c := range converters { + if c.fromVersion == fromVersion { + for _, v := range c.toVersions { + if v == toVersion { + return c + } + } + } + } + return nil +} + +// Convert converts a CNI Result to the requested CNI specification version, +// or returns an error if the conversion could not be performed or failed +func Convert(from types.Result, toVersion string) (types.Result, error) { + if toVersion == "" { + toVersion = "0.1.0" + } + + fromVersion := from.Version() + + // Shortcut for same version + if fromVersion == toVersion { + return from, nil + } + + // Otherwise find the right converter + c := findConverter(fromVersion, toVersion) + if c == nil { + return nil, fmt.Errorf("no converter for CNI result version %s to %s", + fromVersion, toVersion) + } + return c.convertFn(from, toVersion) +} + +// RegisterConverter registers a CNI Result converter. SHOULD NOT BE CALLED +// EXCEPT FROM CNI ITSELF. +func RegisterConverter(fromVersion string, toVersions []string, convertFn ConvertFn) { + // Make sure there is no converter already registered for these + // from and to versions + for _, v := range toVersions { + if findConverter(fromVersion, v) != nil { + panic(fmt.Sprintf("converter already registered for %s to %s", + fromVersion, v)) + } + } + converters = append(converters, &converter{ + fromVersion: fromVersion, + toVersions: toVersions, + convertFn: convertFn, + }) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/internal/create.go b/vendor/github.com/containernetworking/cni/pkg/types/internal/create.go new file mode 100644 index 00000000..96363091 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/internal/create.go @@ -0,0 +1,66 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "fmt" + + "github.com/containernetworking/cni/pkg/types" +) + +type ResultFactoryFunc func([]byte) (types.Result, error) + +type creator struct { + // CNI Result spec versions that createFn can create a Result for + versions []string + createFn ResultFactoryFunc +} + +var creators []*creator + +func findCreator(version string) *creator { + for _, c := range creators { + for _, v := range c.versions { + if v == version { + return c + } + } + } + return nil +} + +// Create creates a CNI Result using the given JSON, or an error if the creation +// could not be performed +func Create(version string, bytes []byte) (types.Result, error) { + if c := findCreator(version); c != nil { + return c.createFn(bytes) + } + return nil, fmt.Errorf("unsupported CNI result version %q", version) +} + +// RegisterCreator registers a CNI Result creator. SHOULD NOT BE CALLED +// EXCEPT FROM CNI ITSELF. +func RegisterCreator(versions []string, createFn ResultFactoryFunc) { + // Make sure there is no creator already registered for these versions + for _, v := range versions { + if findCreator(v) != nil { + panic(fmt.Sprintf("creator already registered for %s", v)) + } + } + creators = append(creators, &creator{ + versions: versions, + createFn: createFn, + }) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/types/types.go b/vendor/github.com/containernetworking/cni/pkg/types/types.go new file mode 100644 index 00000000..f4b3ce35 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/types/types.go @@ -0,0 +1,325 @@ +// Copyright 2015 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "fmt" + "io" + "net" + "os" +) + +// like net.IPNet but adds JSON marshalling and unmarshalling +type IPNet net.IPNet + +// ParseCIDR takes a string like "10.2.3.1/24" and +// return IPNet with "10.2.3.1" and /24 mask +func ParseCIDR(s string) (*net.IPNet, error) { + ip, ipn, err := net.ParseCIDR(s) + if err != nil { + return nil, err + } + + ipn.IP = ip + return ipn, nil +} + +func (n IPNet) MarshalJSON() ([]byte, error) { + return json.Marshal((*net.IPNet)(&n).String()) +} + +func (n *IPNet) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + + tmp, err := ParseCIDR(s) + if err != nil { + return err + } + + *n = IPNet(*tmp) + return nil +} + +// Use PluginConf instead of NetConf, the NetConf +// backwards-compat alias will be removed in a future release. +type NetConf = PluginConf + +// PluginConf describes a plugin configuration for a specific network. +type PluginConf struct { + CNIVersion string `json:"cniVersion,omitempty"` + + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Capabilities map[string]bool `json:"capabilities,omitempty"` + IPAM IPAM `json:"ipam,omitempty"` + DNS DNS `json:"dns,omitempty"` + + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult Result `json:"-"` + + // ValidAttachments is only supplied when executing a GC operation + ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"` +} + +// GCAttachment is the parameters to a GC call -- namely, +// the container ID and ifname pair that represents a +// still-valid attachment. +type GCAttachment struct { + ContainerID string `json:"containerID"` + IfName string `json:"ifname"` +} + +// Note: DNS should be omit if DNS is empty but default Marshal function +// will output empty structure hence need to write a Marshal function +func (n *PluginConf) MarshalJSON() ([]byte, error) { + bytes, err := json.Marshal(*n) + if err != nil { + return nil, err + } + + fixupObj := make(map[string]interface{}) + if err := json.Unmarshal(bytes, &fixupObj); err != nil { + return nil, err + } + + if n.DNS.IsEmpty() { + delete(fixupObj, "dns") + } + + return json.Marshal(fixupObj) +} + +type IPAM struct { + Type string `json:"type,omitempty"` +} + +// IsEmpty returns true if IPAM structure has no value, otherwise return false +func (i *IPAM) IsEmpty() bool { + return i.Type == "" +} + +// NetConfList describes an ordered list of networks. +type NetConfList struct { + CNIVersion string `json:"cniVersion,omitempty"` + + Name string `json:"name,omitempty"` + DisableCheck bool `json:"disableCheck,omitempty"` + DisableGC bool `json:"disableGC,omitempty"` + Plugins []*PluginConf `json:"plugins,omitempty"` +} + +// Result is an interface that provides the result of plugin execution +type Result interface { + // The highest CNI specification result version the result supports + // without having to convert + Version() string + + // Returns the result converted into the requested CNI specification + // result version, or an error if conversion failed + GetAsVersion(version string) (Result, error) + + // Prints the result in JSON format to stdout + Print() error + + // Prints the result in JSON format to provided writer + PrintTo(writer io.Writer) error +} + +func PrintResult(result Result, version string) error { + newResult, err := result.GetAsVersion(version) + if err != nil { + return err + } + return newResult.Print() +} + +// DNS contains values interesting for DNS resolvers +type DNS struct { + Nameservers []string `json:"nameservers,omitempty"` + Domain string `json:"domain,omitempty"` + Search []string `json:"search,omitempty"` + Options []string `json:"options,omitempty"` +} + +// IsEmpty returns true if DNS structure has no value, otherwise return false +func (d *DNS) IsEmpty() bool { + if len(d.Nameservers) == 0 && d.Domain == "" && len(d.Search) == 0 && len(d.Options) == 0 { + return true + } + return false +} + +func (d *DNS) Copy() *DNS { + if d == nil { + return nil + } + + to := &DNS{Domain: d.Domain} + to.Nameservers = append(to.Nameservers, d.Nameservers...) + to.Search = append(to.Search, d.Search...) + to.Options = append(to.Options, d.Options...) + return to +} + +type Route struct { + Dst net.IPNet + GW net.IP + MTU int + AdvMSS int + Priority int + Table *int + Scope *int +} + +func (r *Route) String() string { + table := "" + if r.Table != nil { + table = fmt.Sprintf("%d", *r.Table) + } + + scope := "" + if r.Scope != nil { + scope = fmt.Sprintf("%d", *r.Scope) + } + + return fmt.Sprintf("{Dst:%+v GW:%v MTU:%d AdvMSS:%d Priority:%d Table:%s Scope:%s}", r.Dst, r.GW, r.MTU, r.AdvMSS, r.Priority, table, scope) +} + +func (r *Route) Copy() *Route { + if r == nil { + return nil + } + + route := &Route{ + Dst: r.Dst, + GW: r.GW, + MTU: r.MTU, + AdvMSS: r.AdvMSS, + Priority: r.Priority, + Scope: r.Scope, + } + + if r.Table != nil { + table := *r.Table + route.Table = &table + } + + if r.Scope != nil { + scope := *r.Scope + route.Scope = &scope + } + + return route +} + +// Well known error codes +// see https://github.com/containernetworking/cni/blob/main/SPEC.md#well-known-error-codes +const ( + ErrUnknown uint = iota // 0 + ErrIncompatibleCNIVersion // 1 + ErrUnsupportedField // 2 + ErrUnknownContainer // 3 + ErrInvalidEnvironmentVariables // 4 + ErrIOFailure // 5 + ErrDecodingFailure // 6 + ErrInvalidNetworkConfig // 7 + ErrInvalidNetNS // 8 + ErrTryAgainLater uint = 11 + ErrInternal uint = 999 +) + +type Error struct { + Code uint `json:"code"` + Msg string `json:"msg"` + Details string `json:"details,omitempty"` +} + +func NewError(code uint, msg, details string) *Error { + return &Error{ + Code: code, + Msg: msg, + Details: details, + } +} + +func (e *Error) Error() string { + details := "" + if e.Details != "" { + details = fmt.Sprintf("; %v", e.Details) + } + return fmt.Sprintf("%v%v", e.Msg, details) +} + +func (e *Error) Print() error { + return prettyPrint(e) +} + +// net.IPNet is not JSON (un)marshallable so this duality is needed +// for our custom IPNet type + +// JSON (un)marshallable types +type route struct { + Dst IPNet `json:"dst"` + GW net.IP `json:"gw,omitempty"` + MTU int `json:"mtu,omitempty"` + AdvMSS int `json:"advmss,omitempty"` + Priority int `json:"priority,omitempty"` + Table *int `json:"table,omitempty"` + Scope *int `json:"scope,omitempty"` +} + +func (r *Route) UnmarshalJSON(data []byte) error { + rt := route{} + if err := json.Unmarshal(data, &rt); err != nil { + return err + } + + r.Dst = net.IPNet(rt.Dst) + r.GW = rt.GW + r.MTU = rt.MTU + r.AdvMSS = rt.AdvMSS + r.Priority = rt.Priority + r.Table = rt.Table + r.Scope = rt.Scope + + return nil +} + +func (r Route) MarshalJSON() ([]byte, error) { + rt := route{ + Dst: IPNet(r.Dst), + GW: r.GW, + MTU: r.MTU, + AdvMSS: r.AdvMSS, + Priority: r.Priority, + Table: r.Table, + Scope: r.Scope, + } + + return json.Marshal(rt) +} + +func prettyPrint(obj interface{}) error { + data, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return err + } + _, err = os.Stdout.Write(data) + return err +} diff --git a/vendor/github.com/containernetworking/cni/pkg/utils/utils.go b/vendor/github.com/containernetworking/cni/pkg/utils/utils.go new file mode 100644 index 00000000..1981d255 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/utils/utils.go @@ -0,0 +1,82 @@ +// Copyright 2019 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "bytes" + "fmt" + "regexp" + "unicode" + + "github.com/containernetworking/cni/pkg/types" +) + +const ( + // cniValidNameChars is the regexp used to validate valid characters in + // containerID and networkName + cniValidNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.\-]` + + // maxInterfaceNameLength is the length max of a valid interface name + maxInterfaceNameLength = 15 +) + +var cniReg = regexp.MustCompile(`^` + cniValidNameChars + `*$`) + +// ValidateContainerID will validate that the supplied containerID is not empty does not contain invalid characters +func ValidateContainerID(containerID string) *types.Error { + if containerID == "" { + return types.NewError(types.ErrUnknownContainer, "missing containerID", "") + } + if !cniReg.MatchString(containerID) { + return types.NewError(types.ErrInvalidEnvironmentVariables, "invalid characters in containerID", containerID) + } + return nil +} + +// ValidateNetworkName will validate that the supplied networkName does not contain invalid characters +func ValidateNetworkName(networkName string) *types.Error { + if networkName == "" { + return types.NewError(types.ErrInvalidNetworkConfig, "missing network name:", "") + } + if !cniReg.MatchString(networkName) { + return types.NewError(types.ErrInvalidNetworkConfig, "invalid characters found in network name", networkName) + } + return nil +} + +// ValidateInterfaceName will validate the interface name based on the four rules below +// 1. The name must not be empty +// 2. The name must be less than 16 characters +// 3. The name must not be "." or ".." +// 4. The name must not contain / or : or any whitespace characters +// ref to https://github.com/torvalds/linux/blob/master/net/core/dev.c#L1024 +func ValidateInterfaceName(ifName string) *types.Error { + if len(ifName) == 0 { + return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is empty", "") + } + if len(ifName) > maxInterfaceNameLength { + return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is too long", fmt.Sprintf("interface name should be less than %d characters", maxInterfaceNameLength+1)) + } + if ifName == "." || ifName == ".." { + return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name is . or ..", "") + } + for _, r := range bytes.Runes([]byte(ifName)) { + if r == '/' || r == ':' || unicode.IsSpace(r) { + return types.NewError(types.ErrInvalidEnvironmentVariables, "interface name contains / or : or whitespace characters", "") + } + } + + return nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/version/conf.go b/vendor/github.com/containernetworking/cni/pkg/version/conf.go new file mode 100644 index 00000000..808c33b8 --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/version/conf.go @@ -0,0 +1,26 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "github.com/containernetworking/cni/pkg/types/create" +) + +// ConfigDecoder can decode the CNI version available in network config data +type ConfigDecoder struct{} + +func (*ConfigDecoder) Decode(jsonBytes []byte) (string, error) { + return create.DecodeVersion(jsonBytes) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/version/plugin.go b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go new file mode 100644 index 00000000..e3bd375b --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go @@ -0,0 +1,168 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" +) + +// PluginInfo reports information about CNI versioning +type PluginInfo interface { + // SupportedVersions returns one or more CNI spec versions that the plugin + // supports. If input is provided in one of these versions, then the plugin + // promises to use the same CNI version in its response + SupportedVersions() []string + + // Encode writes this CNI version information as JSON to the given Writer + Encode(io.Writer) error +} + +type pluginInfo struct { + CNIVersion_ string `json:"cniVersion"` + SupportedVersions_ []string `json:"supportedVersions,omitempty"` +} + +// pluginInfo implements the PluginInfo interface +var _ PluginInfo = &pluginInfo{} + +func (p *pluginInfo) Encode(w io.Writer) error { + return json.NewEncoder(w).Encode(p) +} + +func (p *pluginInfo) SupportedVersions() []string { + return p.SupportedVersions_ +} + +// PluginSupports returns a new PluginInfo that will report the given versions +// as supported +func PluginSupports(supportedVersions ...string) PluginInfo { + if len(supportedVersions) < 1 { + panic("programmer error: you must support at least one version") + } + return &pluginInfo{ + CNIVersion_: Current(), + SupportedVersions_: supportedVersions, + } +} + +// PluginDecoder can decode the response returned by a plugin's VERSION command +type PluginDecoder struct{} + +func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) { + var info pluginInfo + err := json.Unmarshal(jsonBytes, &info) + if err != nil { + return nil, fmt.Errorf("decoding version info: %w", err) + } + if info.CNIVersion_ == "" { + return nil, fmt.Errorf("decoding version info: missing field cniVersion") + } + if len(info.SupportedVersions_) == 0 { + if info.CNIVersion_ == "0.2.0" { + return PluginSupports("0.1.0", "0.2.0"), nil + } + return nil, fmt.Errorf("decoding version info: missing field supportedVersions") + } + return &info, nil +} + +// ParseVersion parses a version string like "3.0.1" or "0.4.5" into major, +// minor, and micro numbers or returns an error +func ParseVersion(version string) (int, int, int, error) { + var major, minor, micro int + if version == "" { // special case: no version declared == v0.1.0 + return 0, 1, 0, nil + } + + parts := strings.Split(version, ".") + if len(parts) >= 4 { + return -1, -1, -1, fmt.Errorf("invalid version %q: too many parts", version) + } + + major, err := strconv.Atoi(parts[0]) + if err != nil { + return -1, -1, -1, fmt.Errorf("failed to convert major version part %q: %w", parts[0], err) + } + + if len(parts) >= 2 { + minor, err = strconv.Atoi(parts[1]) + if err != nil { + return -1, -1, -1, fmt.Errorf("failed to convert minor version part %q: %w", parts[1], err) + } + } + + if len(parts) >= 3 { + micro, err = strconv.Atoi(parts[2]) + if err != nil { + return -1, -1, -1, fmt.Errorf("failed to convert micro version part %q: %w", parts[2], err) + } + } + + return major, minor, micro, nil +} + +// GreaterThanOrEqualTo takes two string versions, parses them into major/minor/micro +// numbers, and compares them to determine whether the first version is greater +// than or equal to the second +func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) { + firstMajor, firstMinor, firstMicro, err := ParseVersion(version) + if err != nil { + return false, err + } + + secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion) + if err != nil { + return false, err + } + + if firstMajor > secondMajor { + return true, nil + } else if firstMajor == secondMajor { + if firstMinor > secondMinor { + return true, nil + } else if firstMinor == secondMinor && firstMicro >= secondMicro { + return true, nil + } + } + return false, nil +} + +// GreaterThan returns true if the first version is greater than the second +func GreaterThan(version, otherVersion string) (bool, error) { + firstMajor, firstMinor, firstMicro, err := ParseVersion(version) + if err != nil { + return false, err + } + + secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion) + if err != nil { + return false, err + } + + if firstMajor > secondMajor { + return true, nil + } else if firstMajor == secondMajor { + if firstMinor > secondMinor { + return true, nil + } else if firstMinor == secondMinor && firstMicro > secondMicro { + return true, nil + } + } + return false, nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go b/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go new file mode 100644 index 00000000..25c3810b --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/version/reconcile.go @@ -0,0 +1,49 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import "fmt" + +type ErrorIncompatible struct { + Config string + Supported []string +} + +func (e *ErrorIncompatible) Details() string { + return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Supported) +} + +func (e *ErrorIncompatible) Error() string { + return fmt.Sprintf("incompatible CNI versions: %s", e.Details()) +} + +type Reconciler struct{} + +func (r *Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible { + return r.CheckRaw(configVersion, pluginInfo.SupportedVersions()) +} + +func (*Reconciler) CheckRaw(configVersion string, supportedVersions []string) *ErrorIncompatible { + for _, supportedVersion := range supportedVersions { + if configVersion == supportedVersion { + return nil + } + } + + return &ErrorIncompatible{ + Config: configVersion, + Supported: supportedVersions, + } +} diff --git a/vendor/github.com/containernetworking/cni/pkg/version/version.go b/vendor/github.com/containernetworking/cni/pkg/version/version.go new file mode 100644 index 00000000..cfb6a12f --- /dev/null +++ b/vendor/github.com/containernetworking/cni/pkg/version/version.go @@ -0,0 +1,90 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "encoding/json" + "fmt" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/create" +) + +// Current reports the version of the CNI spec implemented by this library +func Current() string { + return "1.1.0" +} + +// Legacy PluginInfo describes a plugin that is backwards compatible with the +// CNI spec version 0.1.0. In particular, a runtime compiled against the 0.1.0 +// library ought to work correctly with a plugin that reports support for +// Legacy versions. +// +// Any future CNI spec versions which meet this definition should be added to +// this list. +var ( + Legacy = PluginSupports("0.1.0", "0.2.0") + All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0") +) + +// VersionsFrom returns a list of versions starting from min, inclusive +func VersionsStartingFrom(min string) PluginInfo { + out := []string{} + // cheat, just assume ordered + ok := false + for _, v := range All.SupportedVersions() { + if !ok && v == min { + ok = true + } + if ok { + out = append(out, v) + } + } + return PluginSupports(out...) +} + +// Finds a Result object matching the requested version (if any) and asks +// that object to parse the plugin result, returning an error if parsing failed. +func NewResult(version string, resultBytes []byte) (types.Result, error) { + return create.Create(version, resultBytes) +} + +// ParsePrevResult parses a prevResult in a NetConf structure and sets +// the NetConf's PrevResult member to the parsed Result object. +func ParsePrevResult(conf *types.PluginConf) error { + if conf.RawPrevResult == nil { + return nil + } + + // Prior to 1.0.0, Result types may not marshal a CNIVersion. Since the + // result version must match the config version, if the Result's version + // is empty, inject the config version. + if ver, ok := conf.RawPrevResult["CNIVersion"]; !ok || ver == "" { + conf.RawPrevResult["CNIVersion"] = conf.CNIVersion + } + + resultBytes, err := json.Marshal(conf.RawPrevResult) + if err != nil { + return fmt.Errorf("could not serialize prevResult: %w", err) + } + + conf.RawPrevResult = nil + conf.PrevResult, err = create.Create(conf.CNIVersion, resultBytes) + if err != nil { + return fmt.Errorf("could not parse prevResult: %w", err) + } + + return nil +} diff --git a/vendor/github.com/containernetworking/plugins/LICENSE b/vendor/github.com/containernetworking/plugins/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containernetworking/plugins/pkg/ns/README.md b/vendor/github.com/containernetworking/plugins/pkg/ns/README.md new file mode 100644 index 00000000..e5fef2db --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/ns/README.md @@ -0,0 +1,41 @@ +### Namespaces, Threads, and Go +On Linux each OS thread can have a different network namespace. Go's thread scheduling model switches goroutines between OS threads based on OS thread load and whether the goroutine would block other goroutines. This can result in a goroutine switching network namespaces without notice and lead to errors in your code. + +### Namespace Switching +Switching namespaces with the `ns.Set()` method is not recommended without additional strategies to prevent unexpected namespace changes when your goroutines switch OS threads. + +Go provides the `runtime.LockOSThread()` function to ensure a specific goroutine executes on its current OS thread and prevents any other goroutine from running in that thread until the locked one exits. Careful usage of `LockOSThread()` and goroutines can provide good control over which network namespace a given goroutine executes in. + +For example, you cannot rely on the `ns.Set()` namespace being the current namespace after the `Set()` call unless you do two things. First, the goroutine calling `Set()` must have previously called `LockOSThread()`. Second, you must ensure `runtime.UnlockOSThread()` is not called somewhere in-between. You also cannot rely on the initial network namespace remaining the current network namespace if any other code in your program switches namespaces, unless you have already called `LockOSThread()` in that goroutine. Note that `LockOSThread()` prevents the Go scheduler from optimally scheduling goroutines for best performance, so `LockOSThread()` should only be used in small, isolated goroutines that release the lock quickly. + +### Do() The Recommended Thing +The `ns.Do()` method provides **partial** control over network namespaces for you by implementing these strategies. All code dependent on a particular network namespace (including the root namespace) should be wrapped in the `ns.Do()` method to ensure the correct namespace is selected for the duration of your code. For example: + +```go +err = targetNs.Do(func(hostNs ns.NetNS) error { + linkAttrs := netlink.NewLinkAttrs() + linkAttrs.Name = "dummy0" + dummy := &netlink.Dummy{ + LinkAttrs: linkAttrs, + } + return netlink.LinkAdd(dummy) +}) +``` + +Note this requirement to wrap every network call is very onerous - any libraries you call might call out to network services such as DNS, and all such calls need to be protected after you call `ns.Do()`. All goroutines spawned from within the `ns.Do` will not inherit the new namespace. The CNI plugins all exit very soon after calling `ns.Do()` which helps to minimize the problem. + +When a new thread is spawned in Linux, it inherits the namespace of its parent. In versions of go **prior to 1.10**, if the runtime spawns a new OS thread, it picks the parent randomly. If the chosen parent thread has been moved to a new namespace (even temporarily), the new OS thread will be permanently "stuck in the wrong namespace", and goroutines will non-deterministically switch namespaces as they are rescheduled. + +In short, **there was no safe way to change network namespaces, even temporarily, from within a long-lived, multithreaded Go process**. If you wish to do this, you must use go 1.10 or greater. + + +### Creating network namespaces +Earlier versions of this library managed namespace creation, but as CNI does not actually utilize this feature (and it was essentially unmaintained), it was removed. If you're writing a container runtime, you should implement namespace management yourself. However, there are some gotchas when doing so, especially around handling `/var/run/netns`. A reasonably correct reference implementation, borrowed from `rkt`, can be found in `pkg/testutils/netns_linux.go` if you're in need of a source of inspiration. + + +### Further Reading + - https://github.com/golang/go/wiki/LockOSThread + - http://morsmachine.dk/go-scheduler + - https://github.com/containernetworking/cni/issues/262 + - https://golang.org/pkg/runtime/ + - https://www.weave.works/blog/linux-namespaces-and-go-don-t-mix diff --git a/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go new file mode 100644 index 00000000..5a6aaa33 --- /dev/null +++ b/vendor/github.com/containernetworking/plugins/pkg/ns/ns_linux.go @@ -0,0 +1,286 @@ +// Copyright 2015-2017 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ns + +import ( + "fmt" + "os" + "runtime" + "sync" + "syscall" + + "golang.org/x/sys/unix" +) + +// Returns an object representing the current OS thread's network namespace +func GetCurrentNS() (NetNS, error) { + // Lock the thread in case other goroutine executes in it and changes its + // network namespace after getCurrentThreadNetNSPath(), otherwise it might + // return an unexpected network namespace. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + return getCurrentNSNoLock() +} + +func getCurrentNSNoLock() (NetNS, error) { + return GetNS(getCurrentThreadNetNSPath()) +} + +func getCurrentThreadNetNSPath() string { + // /proc/self/ns/net returns the namespace of the main thread, not + // of whatever thread this goroutine is running on. Make sure we + // use the thread's net namespace since the thread is switching around + return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid()) +} + +func (ns *netNS) Close() error { + if err := ns.errorIfClosed(); err != nil { + return err + } + + if err := ns.file.Close(); err != nil { + return fmt.Errorf("Failed to close %q: %v", ns.file.Name(), err) + } + ns.closed = true + + return nil +} + +func (ns *netNS) Set() error { + if err := ns.errorIfClosed(); err != nil { + return err + } + + if err := unix.Setns(int(ns.Fd()), unix.CLONE_NEWNET); err != nil { + return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err) + } + + return nil +} + +type NetNS interface { + // Executes the passed closure in this object's network namespace, + // attempting to restore the original namespace before returning. + // However, since each OS thread can have a different network namespace, + // and Go's thread scheduling is highly variable, callers cannot + // guarantee any specific namespace is set unless operations that + // require that namespace are wrapped with Do(). Also, no code called + // from Do() should call runtime.UnlockOSThread(), or the risk + // of executing code in an incorrect namespace will be greater. See + // https://github.com/golang/go/wiki/LockOSThread for further details. + Do(toRun func(NetNS) error) error + + // Sets the current network namespace to this object's network namespace. + // Note that since Go's thread scheduling is highly variable, callers + // cannot guarantee the requested namespace will be the current namespace + // after this function is called; to ensure this wrap operations that + // require the namespace with Do() instead. + Set() error + + // Returns the filesystem path representing this object's network namespace + Path() string + + // Returns a file descriptor representing this object's network namespace + Fd() uintptr + + // Cleans up this instance of the network namespace; if this instance + // is the last user the namespace will be destroyed + Close() error +} + +type netNS struct { + file *os.File + closed bool +} + +// netNS implements the NetNS interface +var _ NetNS = &netNS{} + +const ( + // https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h + NSFS_MAGIC = unix.NSFS_MAGIC + PROCFS_MAGIC = unix.PROC_SUPER_MAGIC +) + +type NSPathNotExistErr struct{ msg string } + +func (e NSPathNotExistErr) Error() string { return e.msg } + +type NSPathNotNSErr struct{ msg string } + +func (e NSPathNotNSErr) Error() string { return e.msg } + +func IsNSorErr(nspath string) error { + stat := syscall.Statfs_t{} + if err := syscall.Statfs(nspath, &stat); err != nil { + if os.IsNotExist(err) { + err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)} + } else { + err = fmt.Errorf("failed to Statfs %q: %v", nspath, err) + } + return err + } + + switch stat.Type { + case PROCFS_MAGIC, NSFS_MAGIC: + return nil + default: + return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)} + } +} + +// Returns an object representing the namespace referred to by @path +func GetNS(nspath string) (NetNS, error) { + err := IsNSorErr(nspath) + if err != nil { + return nil, err + } + + fd, err := os.Open(nspath) + if err != nil { + return nil, err + } + + return &netNS{file: fd}, nil +} + +// Returns a new empty NetNS. +// Calling Close() let the kernel garbage collect the network namespace. +func TempNetNS() (NetNS, error) { + var tempNS NetNS + var err error + var wg sync.WaitGroup + wg.Add(1) + + // Create the new namespace in a new goroutine so that if we later fail + // to switch the namespace back to the original one, we can safely + // leave the thread locked to die without a risk of the current thread + // left lingering with incorrect namespace. + go func() { + defer wg.Done() + runtime.LockOSThread() + + var threadNS NetNS + // save a handle to current network namespace + threadNS, err = getCurrentNSNoLock() + if err != nil { + err = fmt.Errorf("failed to open current namespace: %v", err) + return + } + defer threadNS.Close() + + // create the temporary network namespace + err = unix.Unshare(unix.CLONE_NEWNET) + if err != nil { + return + } + + // get a handle to the temporary network namespace + tempNS, err = getCurrentNSNoLock() + + err2 := threadNS.Set() + if err2 == nil { + // Unlock the current thread only when we successfully switched back + // to the original namespace; otherwise leave the thread locked which + // will force the runtime to scrap the current thread, that is maybe + // not as optimal but at least always safe to do. + runtime.UnlockOSThread() + } + }() + + wg.Wait() + return tempNS, err +} + +func (ns *netNS) Path() string { + return ns.file.Name() +} + +func (ns *netNS) Fd() uintptr { + return ns.file.Fd() +} + +func (ns *netNS) errorIfClosed() error { + if ns.closed { + return fmt.Errorf("%q has already been closed", ns.file.Name()) + } + return nil +} + +func (ns *netNS) Do(toRun func(NetNS) error) error { + if err := ns.errorIfClosed(); err != nil { + return err + } + + containedCall := func(hostNS NetNS) error { + threadNS, err := getCurrentNSNoLock() + if err != nil { + return fmt.Errorf("failed to open current netns: %v", err) + } + defer threadNS.Close() + + // switch to target namespace + if err = ns.Set(); err != nil { + return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err) + } + defer func() { + err := threadNS.Set() // switch back + if err == nil { + // Unlock the current thread only when we successfully switched back + // to the original namespace; otherwise leave the thread locked which + // will force the runtime to scrap the current thread, that is maybe + // not as optimal but at least always safe to do. + runtime.UnlockOSThread() + } + }() + + return toRun(hostNS) + } + + // save a handle to current network namespace + hostNS, err := GetCurrentNS() + if err != nil { + return fmt.Errorf("Failed to open current namespace: %v", err) + } + defer hostNS.Close() + + var wg sync.WaitGroup + wg.Add(1) + + // Start the callback in a new green thread so that if we later fail + // to switch the namespace back to the original one, we can safely + // leave the thread locked to die without a risk of the current thread + // left lingering with incorrect namespace. + var innerError error + go func() { + defer wg.Done() + runtime.LockOSThread() + innerError = containedCall(hostNS) + }() + wg.Wait() + + return innerError +} + +// WithNetNSPath executes the passed closure under the given network +// namespace, restoring the original namespace afterwards. +func WithNetNSPath(nspath string, toRun func(NetNS) error) error { + ns, err := GetNS(nspath) + if err != nil { + return err + } + defer ns.Close() + return ns.Do(toRun) +} diff --git a/vendor/github.com/containers/buildah/.cirrus.yml b/vendor/github.com/containers/buildah/.cirrus.yml new file mode 100644 index 00000000..66b95024 --- /dev/null +++ b/vendor/github.com/containers/buildah/.cirrus.yml @@ -0,0 +1,456 @@ +--- + +# Main collection of env. vars to set for all tasks and scripts. +env: + #### + #### Global variables used for all tasks + #### + # Name of the ultimate destination branch for this CI run, PR or post-merge. + DEST_BRANCH: "main" + GOPATH: "/var/tmp/go" + GOSRC: "${GOPATH}/src/github.com/containers/buildah" + GOCACHE: "/tmp/go-build" + # Overrides default location (/tmp/cirrus) for repo clone + CIRRUS_WORKING_DIR: "${GOSRC}" + # Shell used to execute all script commands + CIRRUS_SHELL: "/bin/bash" + # Automation script path relative to $CIRRUS_WORKING_DIR) + SCRIPT_BASE: "./contrib/cirrus" + # No need to go crazy, but grab enough to cover most PRs + CIRRUS_CLONE_DEPTH: 50 + # Unless set by in_podman.sh, default to operating outside of a podman container + IN_PODMAN: 'false' + # root or rootless + PRIV_NAME: root + # default "mention the $BUILDAH_RUNTIME in the task alias, with initial whitespace" value + RUNTIME_N: "" + + #### + #### Cache-image names to test with + #### + # GCE project where images live + IMAGE_PROJECT: "libpod-218412" + FEDORA_NAME: "fedora-42" + PRIOR_FEDORA_NAME: "fedora-41" + RAWHIDE_NAME: "rawhide" + DEBIAN_NAME: "debian-13" + + # Image identifiers + IMAGE_SUFFIX: "c20250910t092246z-f42f41d13" + FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" + PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}" + RAWHIDE_CACHE_IMAGE_NAME: "rawhide-${IMAGE_SUFFIX}" # Used temporarily for rust-podman-sequoia. After that RPM is available in stable Fedora releases, we can stop testing against Rawhide again. + DEBIAN_CACHE_IMAGE_NAME: "debian-${IMAGE_SUFFIX}" + + IN_PODMAN_IMAGE: "quay.io/libpod/fedora_podman:${IMAGE_SUFFIX}" + + #### + #### Command variables to help avoid duplication + #### + # Command to prefix every output line with a timestamp + # (can't do inline awk script, Cirrus-CI or YAML mangles quoting) + _TIMESTAMP: 'awk -f ${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/timestamp.awk' + +gcp_credentials: ENCRYPTED[ae0bf7370f0b6e446bc61d0865a2c55d3e166b3fab9466eb0393e38e1c66a31ca4c71ddc7e0139d47d075c36dd6d3fd7] + +# Default timeout for each task +timeout_in: 30m + +# Default VM to use unless set or modified by task +gce_instance: &standardvm + image_project: "${IMAGE_PROJECT}" + zone: "us-central1-c" # Required by Cirrus for the time being + cpu: 2 + memory: "4G" + disk: 200 # Gigabytes, do not set less than 200 per obscure GCE docs re: I/O performance + image_name: "${FEDORA_CACHE_IMAGE_NAME}" + + +# Update metadata on VM images referenced by this repository state +meta_task: + name: "VM img. keepalive" + alias: meta + + container: + image: "quay.io/libpod/imgts:latest" + cpu: 1 + memory: "1G" + + env: + # Space-separated list of images used by this repository state + IMGNAMES: |- + ${FEDORA_CACHE_IMAGE_NAME} + ${PRIOR_FEDORA_CACHE_IMAGE_NAME} + ${RAWHIDE_CACHE_IMAGE_NAME} + ${DEBIAN_CACHE_IMAGE_NAME} + build-push-${IMAGE_SUFFIX} + BUILDID: "${CIRRUS_BUILD_ID}" + REPOREF: "${CIRRUS_CHANGE_IN_REPO}" + GCPJSON: ENCRYPTED[d3614d6f5cc0e66be89d4252b3365fd84f14eee0259d4eb47e25fc0bc2842c7937f5ee8c882b7e547b4c5ec4b6733b14] + GCPNAME: ENCRYPTED[8509e6a681b859479ce6aa275bd3c4ac82de5beec6df6057925afc4cd85b7ef2e879066ae8baaa2d453b82958e434578] + GCPPROJECT: ENCRYPTED[cc09b62d0ec6746a3df685e663ad25d9d5af95ef5fd843c96f3d0ec9d7f065dc63216b9c685c9f43a776a1d403991494] + + clone_script: 'true' + script: '/usr/local/bin/entrypoint.sh' + + +smoke_task: + alias: 'smoke' + name: "Smoke Test" + + gce_instance: + memory: "12G" + cpu: 8 + + # Don't bother running on branches (including cron), or for tags. + skip: $CIRRUS_PR == '' + + timeout_in: 10m + + setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' + build_script: '${SCRIPT_BASE}/build.sh |& ${_TIMESTAMP}' + validate_test_script: '${SCRIPT_BASE}/test.sh validate |& ${_TIMESTAMP}' + + binary_artifacts: + path: ./bin/* + +# Check that all included go modules from other sources match +# # what is expected in `vendor/modules.txt` vs `go.mod`. +vendor_task: + name: "Test Vendoring" + alias: vendor + + env: + CIRRUS_WORKING_DIR: "/var/tmp/go/src/github.com/containers/buildah" + GOPATH: "/var/tmp/go" + GOSRC: "/var/tmp/go/src/github.com/containers/buildah" + + # Runs within Cirrus's "community cluster" + container: + image: docker.io/library/golang:1.24.2 + cpu: 1 + memory: 1 + + timeout_in: 5m + + vendor_script: + - './hack/check_vendor_toolchain.sh Try updating the image used by the vendor_task in .cirrus.yml.' + - 'make vendor' + - './hack/tree_status.sh' + + +# Confirm cross-compile ALL architectures on a Mac OS-X VM. +cross_build_task: + name: "Cross Compile" + gce_instance: + cpu: 8 + memory: "24G" + alias: cross_build + skip: >- + $CIRRUS_CHANGE_TITLE =~ '.*CI:DOCS.*' + env: + HOME: /root + script: + - go version + - make -j cross CGO_ENABLED=0 + binary_artifacts: + path: ./bin/* + + +unit_task: + name: 'Unit tests w/ $STORAGE_DRIVER' + gce_instance: + cpu: 4 + alias: unit + skip: ¬_build_docs >- + $CIRRUS_CHANGE_TITLE =~ '.*CI:DOCS.*' || + $CIRRUS_CHANGE_TITLE =~ '.*CI:BUILD.*' + depends_on: &smoke_vendor + - smoke + - vendor + + matrix: + - env: + STORAGE_DRIVER: 'vfs' + - env: + STORAGE_DRIVER: 'overlay' + + setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' + unit_test_script: '${SCRIPT_BASE}/test.sh unit |& ${_TIMESTAMP}' + + +conformance_task: + name: 'Debian Conformance w/ $STORAGE_DRIVER' + alias: conformance + skip: *not_build_docs + depends_on: *smoke_vendor + + gce_instance: + cpu: 4 + image_name: "${DEBIAN_CACHE_IMAGE_NAME}" + + matrix: + - env: + STORAGE_DRIVER: 'vfs' + TMPDIR: '/var/tmp' + - env: + STORAGE_DRIVER: 'overlay' + + setup_script: '${SCRIPT_BASE}/setup.sh conformance |& ${_TIMESTAMP}' + conformance_test_script: '${SCRIPT_BASE}/test.sh conformance |& ${_TIMESTAMP}' + + +integration_task: + name: "Integration $DISTRO_NV$RUNTIME_N w/ $STORAGE_DRIVER" + alias: integration + skip: *not_build_docs + depends_on: *smoke_vendor + + matrix: + # VFS + - env: + DISTRO_NV: "${FEDORA_NAME}" + IMAGE_NAME: "${FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'vfs' + BUILDAH_RUNTIME: crun + RUNTIME_N: " using crun" + - env: + DISTRO_NV: "${PRIOR_FEDORA_NAME}" + IMAGE_NAME: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'vfs' + BUILDAH_RUNTIME: crun + RUNTIME_N: " using crun" + - env: + DISTRO_NV: "${DEBIAN_NAME}" + IMAGE_NAME: "${DEBIAN_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'vfs' + # OVERLAY + - env: + DISTRO_NV: "${FEDORA_NAME}" + IMAGE_NAME: "${FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + BUILDAH_RUNTIME: crun + RUNTIME_N: " using crun" + - env: + DISTRO_NV: "${PRIOR_FEDORA_NAME}" + IMAGE_NAME: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + BUILDAH_RUNTIME: crun + RUNTIME_N: " using crun" + - env: + DISTRO_NV: "${DEBIAN_NAME}" + IMAGE_NAME: "${DEBIAN_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + - env: + DISTRO_NV: "${RAWHIDE_NAME}" + IMAGE_NAME: "${RAWHIDE_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + TEST_BUILD_TAGS: 'containers_image_sequoia' + + gce_instance: &integration_gce_instance + image_name: "$IMAGE_NAME" + cpu: 8 + memory: "8G" + + # Separate scripts for separate outputs, makes debugging easier. + setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' + build_script: '${SCRIPT_BASE}/build.sh |& ${_TIMESTAMP}' + integration_test_script: '${SCRIPT_BASE}/test.sh integration |& ${_TIMESTAMP}' + + binary_artifacts: + path: ./bin/* + + always: &standardlogs + audit_log_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh audit' + df_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh df' + journal_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh journal' + podman_system_info_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh podman' + buildah_version_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh buildah_version' + buildah_info_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh buildah_info' + package_versions_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh packages' + golang_version_script: '$GOSRC/$SCRIPT_BASE/logcollector.sh golang' + +non_blocking_integration_task: + name: "Integration $DISTRO_NV$RUNTIME_N w/ $STORAGE_DRIVER (non-blocking)" + alias: non_blocking_integration + skip: *not_build_docs + depends_on: *smoke_vendor + + matrix: + # VFS + - env: + DISTRO_NV: "${FEDORA_NAME}" + IMAGE_NAME: "${FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'vfs' + BUILDAH_RUNTIME: runc + RUNTIME_N: " using runc" + - env: + DISTRO_NV: "${PRIOR_FEDORA_NAME}" + IMAGE_NAME: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'vfs' + BUILDAH_RUNTIME: runc + RUNTIME_N: " using runc" + # OVERLAY + - env: + DISTRO_NV: "${FEDORA_NAME}" + IMAGE_NAME: "${FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + BUILDAH_RUNTIME: runc + RUNTIME_N: " using runc" + - env: + DISTRO_NV: "${PRIOR_FEDORA_NAME}" + IMAGE_NAME: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + BUILDAH_RUNTIME: runc + RUNTIME_N: " using runc" + + gce_instance: + <<: *integration_gce_instance + + # Separate scripts for separate outputs, makes debugging easier. + setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' + build_script: '${SCRIPT_BASE}/build.sh |& ${_TIMESTAMP}' + integration_test_script: '${SCRIPT_BASE}/test.sh integration |& ${_TIMESTAMP}' + + binary_artifacts: + path: ./bin/* + + always: *standardlogs + +integration_rootless_task: + name: "Integration rootless $DISTRO_NV$RUNTIME_N w/ $STORAGE_DRIVER" + alias: integration_rootless + skip: *not_build_docs + depends_on: *smoke_vendor + + matrix: + # Running rootless tests on overlay + # OVERLAY + - env: + DISTRO_NV: "${FEDORA_NAME}" + IMAGE_NAME: "${FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + PRIV_NAME: rootless + BUILDAH_RUNTIME: crun + RUNTIME_N: " using crun" + - env: + DISTRO_NV: "${PRIOR_FEDORA_NAME}" + IMAGE_NAME: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + PRIV_NAME: rootless + BUILDAH_RUNTIME: crun + RUNTIME_N: " using crun" + - env: + DISTRO_NV: "${DEBIAN_NAME}" + IMAGE_NAME: "${DEBIAN_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + PRIV_NAME: rootless + + gce_instance: + <<: *integration_gce_instance + + # Separate scripts for separate outputs, makes debugging easier. + setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' + build_script: '${SCRIPT_BASE}/build.sh |& ${_TIMESTAMP}' + integration_test_script: '${SCRIPT_BASE}/test.sh integration |& ${_TIMESTAMP}' + + binary_artifacts: + path: ./bin/* + + always: + <<: *standardlogs + +non_blocking_integration_rootless_task: + name: "Integration rootless $DISTRO_NV$RUNTIME_N w/ $STORAGE_DRIVER (non-blocking)" + alias: non_blocking_integration_rootless + skip: *not_build_docs + depends_on: *smoke_vendor + + matrix: + - env: + DISTRO_NV: "${FEDORA_NAME}" + IMAGE_NAME: "${FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + PRIV_NAME: rootless + BUILDAH_RUNTIME: runc + RUNTIME_N: " using runc" + - env: + DISTRO_NV: "${PRIOR_FEDORA_NAME}" + IMAGE_NAME: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" + STORAGE_DRIVER: 'overlay' + PRIV_NAME: rootless + BUILDAH_RUNTIME: runc + RUNTIME_N: " using runc" + + gce_instance: + <<: *integration_gce_instance + + # Separate scripts for separate outputs, makes debugging easier. + setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' + build_script: '${SCRIPT_BASE}/build.sh |& ${_TIMESTAMP}' + integration_test_script: '${SCRIPT_BASE}/test.sh integration |& ${_TIMESTAMP}' + + binary_artifacts: + path: ./bin/* + + always: + <<: *standardlogs + +in_podman_task: + name: "Containerized Integration" + alias: in_podman + skip: *not_build_docs + depends_on: *smoke_vendor + + gce_instance: + cpu: 8 + memory: "8G" + + env: + # This is key, cause the scripts to re-execute themselves inside a container. + IN_PODMAN: 'true' + BUILDAH_ISOLATION: 'chroot' + STORAGE_DRIVER: 'vfs' + + # Separate scripts for separate outputs, makes debugging easier. + setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' + build_script: '${SCRIPT_BASE}/build.sh |& ${_TIMESTAMP}' + integration_test_script: '${SCRIPT_BASE}/test.sh integration |& ${_TIMESTAMP}' + + binary_artifacts: + path: ./bin/* + + always: + <<: *standardlogs + + +# Status aggregator for all tests. This task simply ensures a defined +# set of tasks all passed, and allows confirming that based on the status +# of this task. +success_task: + # N/B: The prow merge-bot (tide) is sensitized to this exact name, DO NOT CHANGE IT. + # Ref: https://github.com/openshift/release/pull/48909 + name: "Total Success" + alias: success + + depends_on: + - meta + - smoke + - unit + - conformance + - vendor + - cross_build + - integration + - integration_rootless + - in_podman + + container: + image: "quay.io/libpod/alpine:latest" + cpu: 1 + memory: 1 + + env: + CIRRUS_SHELL: direct # execute command directly + + clone_script: mkdir -p $CIRRUS_WORKING_DIR + script: /bin/true diff --git a/vendor/github.com/containers/buildah/.codespellrc b/vendor/github.com/containers/buildah/.codespellrc new file mode 100644 index 00000000..64a29fe6 --- /dev/null +++ b/vendor/github.com/containers/buildah/.codespellrc @@ -0,0 +1,3 @@ +[codespell] +skip = ./vendor,./.git,./go.sum,./docs/*.1,./docker/AUTHORS,./CHANGELOG.md,./changelog.txt,./tests/tools/vendor,./tests/tools/go.mod,./tests/tools/go.sum +ignore-words-list = fo,passt,secon,erro diff --git a/vendor/github.com/containers/buildah/.gitignore b/vendor/github.com/containers/buildah/.gitignore new file mode 100644 index 00000000..6884c670 --- /dev/null +++ b/vendor/github.com/containers/buildah/.gitignore @@ -0,0 +1,14 @@ +docs/buildah*.1 +docs/*.5 +/bin +/buildah +/imgtype +/build/ +/tests/tools/build +Dockerfile* +!/tests/bud/*/Dockerfile* +!/tests/conformance/**/Dockerfile* +*.swp +/result/ +internal/mkcw/embed/entrypoint_amd64.o +internal/mkcw/embed/entrypoint_amd64 diff --git a/vendor/github.com/containers/buildah/.golangci.yml b/vendor/github.com/containers/buildah/.golangci.yml new file mode 100644 index 00000000..c4d2eaed --- /dev/null +++ b/vendor/github.com/containers/buildah/.golangci.yml @@ -0,0 +1,29 @@ +version: "2" + +run: + build-tags: + - apparmor + - seccomp + - selinux + +formatters: + enable: + - gofumpt + +linters: + enable: + - nolintlint + - revive + - unconvert + - unparam + - unused + - whitespace + exclusions: + presets: + - comments + - std-error-handling + settings: + staticcheck: + checks: + - all + - -QF1008 # https://staticcheck.dev/docs/checks/#QF1008 Omit embedded fields from selector expression. diff --git a/vendor/github.com/containers/buildah/.packit.yaml b/vendor/github.com/containers/buildah/.packit.yaml new file mode 100644 index 00000000..032e9e13 --- /dev/null +++ b/vendor/github.com/containers/buildah/.packit.yaml @@ -0,0 +1,149 @@ +--- +# See the documentation for more information: +# https://packit.dev/docs/configuration/ + +downstream_package_name: buildah +upstream_tag_template: v{version} + +# These files get synced from upstream to downstream (Fedora / CentOS Stream) on every +# propose-downstream job. This is done so tests maintained upstream can be run +# downstream in Zuul CI and Bodhi. +# Ref: https://packit.dev/docs/configuration#files_to_sync +files_to_sync: + - src: rpm/gating.yaml + dest: gating.yaml + delete: true + - src: plans/ + dest: plans/ + delete: true + mkpath: true + - src: tests/tmt/ + dest: tests/tmt/ + delete: true + mkpath: true + - src: .fmf/ + dest: .fmf/ + delete: true + - .packit.yaml + +packages: + buildah-fedora: + pkg_tool: fedpkg + specfile_path: rpm/buildah.spec + buildah-centos: + pkg_tool: centpkg + specfile_path: rpm/buildah.spec + buildah-eln: + specfile_path: rpm/buildah.spec + +srpm_build_deps: + - make + +jobs: + - job: copr_build + trigger: pull_request + packages: [buildah-fedora] + notifications: &copr_build_failure_notification + failure_comment: + message: "Ephemeral COPR build failed. @containers/packit-build please check." + # Fedora aliases documentation: https://packit.dev/docs/configuration#aliases + # python3-fedora-distro-aliases provides `resolve-fedora-aliases` command + targets: &fedora_copr_targets + - fedora-all-x86_64 + - fedora-all-aarch64 + enable_net: true + # Disable osh diff scan until Go support is available + # Ref: https://github.com/openscanhub/known-false-positives/pull/30#issuecomment-2858698495 + osh_diff_scan_after_copr_build: false + + # Ignore until golang is updated in distro buildroot to 1.23.3+ + - job: copr_build + trigger: ignore + packages: [buildah-eln] + notifications: *copr_build_failure_notification + targets: + fedora-eln-x86_64: + additional_repos: + - "https://kojipkgs.fedoraproject.org/repos/eln-build/latest/x86_64/" + fedora-eln-aarch64: + additional_repos: + - "https://kojipkgs.fedoraproject.org/repos/eln-build/latest/aarch64/" + enable_net: true + + # Ignore until golang is updated in distro buildroot to 1.23.3+ + - job: copr_build + trigger: ignore + packages: [buildah-centos] + notifications: *copr_build_failure_notification + targets: ¢os_copr_targets + - centos-stream-9-x86_64 + - centos-stream-9-aarch64 + - centos-stream-10-x86_64 + - centos-stream-10-aarch64 + enable_net: true + + # Run on commit to main branch + - job: copr_build + trigger: commit + packages: [buildah-fedora] + notifications: + failure_comment: + message: "podman-next COPR build failed. @containers/packit-build please check." + branch: main + owner: rhcontainerbot + project: podman-next + enable_net: true + + # Tests on Fedora for main branch PRs + - job: tests + trigger: pull_request + packages: [buildah-fedora] + targets: + - fedora-all-x86_64 + tf_extra_params: + environments: + - artifacts: + - type: repository-file + id: https://copr.fedorainfracloud.org/coprs/rhcontainerbot/podman-next/repo/fedora-$releasever/rhcontainerbot-podman-next-fedora-$releasever.repo + + # Ignore until golang is updated in distro buildroot to 1.23.3+ + # Tests on CentOS Stream for main branch PRs + - job: tests + trigger: ignore + packages: [buildah-centos] + targets: + - centos-stream-9-x86_64 + - centos-stream-10-x86_64 + tf_extra_params: + environments: + - artifacts: + - type: repository-file + id: https://copr.fedorainfracloud.org/coprs/rhcontainerbot/podman-next/repo/centos-stream-$releasever/rhcontainerbot-podman-next-centos-stream-$releasever.repo + + # Sync to Fedora + - job: propose_downstream + trigger: release + packages: [buildah-fedora] + update_release: false + dist_git_branches: &fedora_targets + - fedora-all + + # Sync to CentOS Stream + - job: propose_downstream + trigger: release + packages: [buildah-centos] + update_release: false + dist_git_branches: + - c10s + + # Fedora Koji build + - job: koji_build + trigger: commit + packages: [buildah-fedora] + sidetag_group: podman-releases + # Dependents are not rpm dependencies, but the package whose bodhi update + # should include this package. + # Ref: https://packit.dev/docs/fedora-releases-guide/releasing-multiple-packages + dependents: + - podman + dist_git_branches: *fedora_targets diff --git a/vendor/github.com/containers/buildah/CHANGELOG.md b/vendor/github.com/containers/buildah/CHANGELOG.md new file mode 100644 index 00000000..207d47bd --- /dev/null +++ b/vendor/github.com/containers/buildah/CHANGELOG.md @@ -0,0 +1,3833 @@ +![buildah logo](https://cdn.rawgit.com/containers/buildah/main/logos/buildah-logo_large.png) + +# Changelog + +## v1.42.2 (2025-12-02) + + [release-1.42] Bump runc to v1.3.4 + +## v1.42.1 (2025-11-06) + + [release-1.42] bump runc to v1.3.3 - CVE-2025-52881 + RPM: build with sequoia on F43+ + +## v1.42.0 (2025-10-17) + + Bump to storage v1.61.0, image v5.38.0, common v0.66.0 + fix(deps): update module github.com/openshift/imagebuilder to v1.2.19 + fix(deps): update module github.com/openshift/imagebuilder to v1.2.18 + copier: ignore user.overlay.* xattrs + commit: always return the config digest as the image ID + fix(deps): update module golang.org/x/crypto to v0.43.0 + fix(deps): update module golang.org/x/sys to v0.37.0 + fix(deps): update module github.com/docker/docker to v28.5.1+incompatible + fix(deps): update module github.com/moby/buildkit to v0.25.1 + fix(deps): update module github.com/opencontainers/runc to v1.3.2 + fix(deps): update module github.com/docker/docker to v28.5.0+incompatible + fix(deps): update module github.com/moby/buildkit to v0.25.0 + fix(deps): update github.com/containers/luksy digest to 2cf5bc9 + Make some test files different from each other + Revert "fix(deps): update module github.com/cyphar/filepath-securejoin to v0.5.0" + Also run integration tests with the Sequoia backend + Allow users to build against podman-sequoia in non-default locations + fix(deps): update module github.com/cyphar/filepath-securejoin to v0.5.0 + .cirrus.yml: Test Vendoring bump golang + vendor: bump go.podman.io/{common,image,storage} to main + fix(deps): update module golang.org/x/crypto to v0.42.0 + fix(deps): update module github.com/docker/docker to v28.4.0+incompatible + fix(deps): update module github.com/moby/buildkit to v0.24.0 + fix(deps): update module github.com/spf13/pflag to v1.0.10 + fix(deps): update module github.com/fsouza/go-dockerclient to v1.12.2 + fix(deps): update module github.com/opencontainers/runc to v1.3.1 + fix(deps): update module github.com/opencontainers/cgroups to v0.0.5 + fix(deps): update module golang.org/x/sync to v0.17.0 + tests/run.bats: "run masks" test: accept "unreadable" masked directories + Run: create parent directories of mount targets with mode 0755 + tests/run.bats: "run masks" test: accept "unreadable" masked directories + New VM images + Suppress a linter warning + modernize: JSON doesn't do "omitempty" structs, so stop asking + modernize: use maps.Copy() instead of iterating over a map to copy it + modernize: use strings.CutPrefix/SplitSeq/FieldsSeq + Update expected/minimum version of Go to 1.24 + chroot: use $PATH when finding commands + [skip-ci] Update actions/stale action to v10 + Update module github.com/ulikunitz/xz to v0.5.15 [SECURITY] + Update go.sum + New VM images + Update module github.com/openshift/imagebuilder to v1 + Update module github.com/spf13/cobra to v1.10.1 + Switch common, storage and image to monorepo. + Update module github.com/stretchr/testify to v1.11.1 + Update module go.etcd.io/bbolt to v1.4.3 + Handle tagged+digested references when processing --all-platforms + Update module github.com/stretchr/testify to v1.11.0 + Add --transient-store global option + Support "--imagestore" global flags + Commit: don't depend on MountImage(), because .imagestore + Adding mohanboddu as community manager to MAINTAINERS.md + Rework how we decide what to filter out of layer diffs + Note that we have to build `true` first for the sake of its tests + copier.Stat(): return owner UID and GID if available + copier.Get(): ensure that directory entries end in "/" + copier.Get(): strip user and group names from entries + imagebuildah.Executor/StageExecutor: check numeric --from= values + Losen the dependency on go-connections/tlsconfig + fix(deps): update module golang.org/x/crypto to v0.41.0 + fix(deps): update module golang.org/x/term to v0.34.0 + fix(deps): update module github.com/docker/go-connections to v0.6.0 + fix(deps): update module golang.org/x/sys to v0.35.0 + copy: assume a destination with basename "." is a directory + generatePathChecksum: ignore ModTime, AccessTime and ChangeTime + fix(deps): update module github.com/seccomp/libseccomp-golang to v0.11.1 + fix(deps): update module github.com/containers/common to v0.64.1 + History should note unset-label, timestamp, and rewrite-timestamp + pkg/cli.GenBuildOptions(): don't hardwire optional bools + fix(deps): update module github.com/containers/image/v5 to v5.36.1 + imagebuildah.StageExecutor.Execute: commit more "no instructions" cases + fix(deps): update module github.com/containers/storage to v1.59.1 + Only suppress "noted" items when not squashing + Reap stray processes + fix(deps): update github.com/containers/luksy digest to 8fccf78 + fix(deps): update module github.com/docker/docker to v28.3.3+incompatible + Restore the default meaning of `--pull` (should be `always`). + Test that pulled up parent directories are excluded at commit + Exclude pulled up parent directories at commit-time + copier.Ensure(): also return parent directories + copier.MkdirOptions: add ModTimeNew + fix(deps): update module github.com/containers/common to v0.64.0 + Bump to Buildah v1.42.0-dev + fix(deps): update module github.com/spf13/pflag to v1.0.7 + CI: make runc tests non-blocking + build,add: add support for corporate proxies + +## v1.41.0 (2025-07-16) + + Bump to c/storage v1.59.0, c/image v5.36.0, ... c/common v0.64.0 + stage_executor: check platform of cache candidates + fix(deps): update module golang.org/x/crypto to v0.40.0 + fix(deps): update module golang.org/x/term to v0.33.0 + fix(deps): update module golang.org/x/sync to v0.16.0 + fix(deps): update module github.com/docker/docker to v28.3.2+incompatible + ADD/COPY --link support added + RPM/TMT: account for passwd binary moving to tests + buildah: move passwd command to tests + Update "bud with --cpu-shares" test, and rename it + Remove BUILDTAG btrfs_noversion as no longer effective + fix(deps): update module github.com/docker/docker to v28.3.1+incompatible + fix(deps): update module github.com/moby/buildkit to v0.23.2 + fix(deps): update github.com/containers/luksy digest to bc60f96 + chore(typos): fix typos + vendor: update c/{common,image,storage} to main + chore(deps): update module github.com/go-viper/mapstructure/v2 to v2.3.0 [security] + fix(deps): update module go.etcd.io/bbolt to v1.4.2 + Update Neil Smith's GitHub username in MAINTAINERS.md + Accept SOURCE_DATE_EPOCH as a build-arg + fix(deps): update module github.com/docker/docker to v28.3.0+incompatible + Add conditional release-checking system test + info,inspect: use the "formats" package to get some builtins + Use containers/common's formats package instead of our own + build, commit: set the OCI ...created annotation on OCI images + commit: exclude parents of mount targets, too + run: clean up parents of mount targets, too + tarFilterer: always flush after writing + Builder: drop the TempVolumes field + Update module github.com/moby/buildkit to v0.23.1 + Update module github.com/opencontainers/cgroups to v0.0.3 + Add CommitOptions.OmitLayerHistoryEntry, for skipping the new bits + Update module github.com/fsouza/go-dockerclient to v1.12.1 + conformance: use mirrored frontend and base images + commit-with-extra-files test: use $TEST_SCRATCH_DIR + fix(deps): update module github.com/moby/buildkit to v0.23.0 + "root fs only mounted once" test: accept root with only the rw option + Run with --device /dev/fuse and not just -v /dev/fuse:/dev/fuse + CI: pass $BUILDAH_RUNTIME through to in-container test runs + CI: ensure rootless groups aren't duplicates + build: add support for --inherit-annotations + CI: give the rootless test user some supplemental groups + bud,run: runc does not support keep-groups + Fix lint issue in TestCommitCompression + Add a unit test for compression types in OCI images + Support zstd compression in image commit + fix(deps): update module go.etcd.io/bbolt to v1.4.1 + rpm: build rpm with libsqlite3 tag + Makefile: use libsqlite3 build when possible + commit,build: --source-date-epoch/--timestamp omit identity label + docs: add --setopt "*.countme=false" to dnf examples + Builder.sbomScan(): don't break non-root scanners + build: --source-date-epoch/--timestamp use static hostname/cid + fix(deps): update module golang.org/x/crypto to v0.39.0 + fix(deps): update module golang.org/x/sync to v0.15.0 + build: add --source-date-epoch and --rewrite-timestamp flags + build,config: add support for --unsetannotation + commit: add --source-date-epoch and --rewrite-timestamp flags + fix(deps): update module github.com/openshift/imagebuilder to v1.2.16 + vendor latest c/{common,image,storage} + Tweak our handling of variant values, again + Don't BuildRequires: ostree-devel + parse, validateExtraHost: honor Hostgateway in format + remove static nix build + Ensure extendedGlob returns paths in lexical order + CI: run integration tests on Fedora with both crun and runc + buildah-build(1): clarify that --cgroup-parent affects RUN instructions + runUsingRuntime: use named constants for runtime states + Add a dummy "runtime" that just dumps its config file + run: handle relabeling bind mounts ourselves + fix link to Maintainers file + Update to avoid deprecated types + fix(deps): update module github.com/docker/docker to v28.2.0+incompatible + [skip-ci] Packit: cleanup redundant targets and unused anchors + [skip-ci] Packit: set fedora-all after F40 EOL + Use Fedora 42 instead of 41 in that one conformance test + [CI:DOCS] README.md: add openssf passing badge + fix(deps): update module github.com/moby/buildkit to v0.22.0 + copier: add Ensure and ConditionalRemove + [CI:DOCS] update a couple of lists in the build man page + build: allow --output to be specified multiple times + add: add a new --timestamp flag + tests/helpers.bash: add some helpers for parsing images + pkg/parse.GetBuildOutput(): use strings.Cut() + [skip-ci] Packit: Disable osh_diff_scan + internal/util.SetHas(): handle maps of [generic]generic + Refactor NewImageSource to add a manifest type abstraction (#5743) + [skip-ci] Packit: Ignore ELN and CentOS Stream jobs + imagebuildah: select most recent layer for cache + [CI:DOCS] Add CNCF roadmap, touchup other CNCF files + fix(deps): update module golang.org/x/crypto to v0.38.0 + Fix typo in comment (#6167) + Support label_users in buildah + fix(deps): update module golang.org/x/sync to v0.14.0 + fix(deps): update github.com/containers/luksy digest to 4bb4c3f + test/serve: fix a descriptor leak, add preliminary directory support + fix(deps): update module github.com/opencontainers/cgroups to v0.0.2 + fix(deps): update module github.com/moby/buildkit to v0.21.1 + Update to avoid deprecated types + fix(deps): update module github.com/opencontainers/runc to v1.3.0 + Only filter if containerImageRef.created != nil + Drop superfluous cast + Remove UID/GID scrubbing. + fix(deps): update module github.com/seccomp/libseccomp-golang to v0.11.0 + cirrus: turn prior fedora testing back on + chore(deps): update dependency containers/automation_images to v20250422 + fix(deps): update module github.com/docker/docker to v28.1.1+incompatible + Bump to Buildah v1.41.0-dev + CI vendor_task: pin to go 1.23.3 for now + fix(deps): update module github.com/containers/common to v0.63.0 + +## v1.40.0 (2025-04-17) + + Bump c/storage to v1.58.0, c/image v5.35.0, c/common v0.63.0 + fix(deps): update module github.com/docker/docker to v28.1.0+incompatible + fix(deps): update module github.com/containers/storage to v1.58.0 + cirrus: make Total Success wait for rootless integration + chroot: use symbolic names when complaining about mount() errors + cli: hide the `completion` command instead of disabling it outright + Document rw and src options for --mount flag in buildah-run(1) + fix(deps): update module github.com/moby/buildkit to v0.21.0 + build: add support for inherit-labels + chore(deps): update dependency golangci/golangci-lint to v2.1.0 + .github: check_cirrus_cron work around github bug + stage_executor,getCreatedBy: expand buildArgs for sources correctly + Add a link to project governance and MAINTAINERS file + fix(deps): update github.com/containers/storage digest to b1d1b45 + generateHostname: simplify + Use maps.Copy + Use slices.Concat + Use slices.Clone + Use slices.Contains + Use for range over integers + tests/testreport: don't copy os.Environ + Use any instead of interface{} + ci: add golangci-lint run with --tests=false + ci: add nolintlint, fix found issues + copier: rm nolint:unparam annotation + .golangci.yml: add unused linter + chroot: fix unused warnings + copier: fix unused warnings + tests/conformance: fix unused warning + ci: switch to golangci-lint v2 + internal/mkcw: disable ST1003 warnings + tests/conformance: do not double import (fix ST1019) + cmd/buildah: don't double import (fix ST1019) + Do not capitalize error strings + cmd/buildah: do not capitalize error strings + tests/conformance: fix QF1012 warnings + tests/serve: fix QF1012 warning + Use strings.ReplaceAll to fix QF1004 warnings + Use switch to fix QF1003 warnings + Apply De Morgan's law to fix QF1001 warnings + Fix QF1007 staticcheck warnings + imagebuildah: fix revive warning + Rename max variable + tests/tools: install lint from binary, use renovate + fix(deps): update module github.com/containernetworking/cni to v1.3.0 + Update Buildah issue template to new version and support podman build + fix(deps): update module golang.org/x/crypto to v0.37.0 + stage_executor: reset platform in systemcontext for stages + fix(deps): update github.com/opencontainers/runtime-tools digest to 260e151 + cmd/buildah: rm unused containerOutputUsingTemplate + cmd/buildah: rm unused getDateAndDigestAndSize + build: return ExecErrorCodeGeneric when git operation fails + add: report error while creating dir for URL source. + createPlatformContainer: drop MS_REMOUNT|MS_BIND + fix(deps): update module github.com/docker/docker to v28.0.3+incompatible + fix: bats won't fail on ! without cleverness + feat: use HistoryTimestamp, if set, for oci-archive entries + Allow extendedGlob to work with Windows paths + fix(deps): update module github.com/moby/buildkit to v0.20.2 + fix(deps): update github.com/openshift/imagebuilder digest to e87e4e1 + fix(deps): update module github.com/docker/docker to v28.0.2+incompatible + fix(deps): update module tags.cncf.io/container-device-interface to v1.0.1 + chore(deps): update dependency containers/automation_images to v20250324 + vendor: update github.com/opencontainers/selinux to v1.12.0 + replace deprecated selinux/label calls + vendor: bump c/common to dbeb17e40c80 + Use builtin arg defaults from imagebuilder + linux: accept unmask paths as glob values + vendor: update containers/common + Add --parents option for COPY in Dockerfiles + fix(deps): update module github.com/opencontainers/runc to v1.2.6 + update go.sum from the previous commit + fix(deps): update module tags.cncf.io/container-device-interface to v1 + chore(deps): update module golang.org/x/net to v0.36.0 [security] + packit: remove f40 from copr builds + cirrus: update to go 1.23 image + vendor bump to golang.org/x/crypto v0.36.0 + cirrus: update PRIOR_FEDORA comment + github: remove cirrus rerun action + fix(deps): update module github.com/containers/common to v0.62.2 + fix(deps): update module github.com/containers/image/v5 to v5.34.2 + fix: close files properly when BuildDockerfiles exits + fix(deps): update module github.com/containers/storage to v1.57.2 + stage_executor: history should include heredoc summary correctly + fix(deps): update module github.com/containers/common to v0.62.1 + github: disable cron rerun action + fix(deps): update module github.com/moby/buildkit to v0.20.1 + internal/mkcw.Archive(): use github.com/containers/storage/pkg/ioutils + [skip-ci] TMT: system tests + buildah-build.1.md: secret examples + fix(deps): update github.com/containers/luksy digest to 40bd943 + fix(deps): update module github.com/opencontainers/image-spec to v1.1.1 + fix(deps): update module github.com/containers/image/v5 to v5.34.1 + Use UnparsedInstance.Manifest instead of ImageSource.GetManifest + fix(deps): update module github.com/opencontainers/runtime-spec to v1.2.1 + tests/conformance/testdata/Dockerfile.add: update some URLs + Vendor imagebuilder + Fix source of OS, architecture and variant + chore(deps): update module github.com/go-jose/go-jose/v4 to v4.0.5 [security] + fix(deps): update module tags.cncf.io/container-device-interface to v0.8.1 + fix(deps): update module github.com/moby/buildkit to v0.20.0 + chroot createPlatformContainer: use MS_REMOUNT + conformance: make TestCommit and TestConformance parallel + cirrus: reduce task timeout + mkcw: mkcw_check_image use bats run_with_log + test: use /tmp as TMPDIR + heredoc: create temp subdirs for each build + test: heredoc remove python dependency from test + Support the containers.conf container_name_as_hostname option + fix(deps): update module github.com/opencontainers/runc to v1.2.5 + fix(deps): update module github.com/spf13/cobra to v1.9.0 + .cirrus: use more cores for smoke + Switch to the CNCF Code of Conduct + .cirrus: bump ci resources + fix(deps): update module golang.org/x/crypto to v0.33.0 + Distinguish --mount=type=cache locations by ownership, too + fix(deps): update module golang.org/x/term to v0.29.0 + .cirrus: run -race only on non-PR branch + unit: deparallize some tests + .cirrus: use multiple cpu for unit tests + Makefile: use -parallel for go test + unit_test: use Parallel test where possible + Update module golang.org/x/sys to v0.30.0 + Update module golang.org/x/sync to v0.11.0 + Update dependency containers/automation_images to v20250131 + Bump to Buildah v1.40.0-dev + +## v1.39.0 (2025-01-31) + + Bump c/storage v1.57.1, c/image 5.34.0, c/common v0.62.0 + Update module github.com/containers/storage to v1.57.0 + CI, .cirrus: parallelize containerized integration + ed's comment: cleanup + use seperate blobinfocache for flaky test + bump CI VMs to 4 CPUs (was: 2) for integration tests + cleanup, debug, and disable parallel in blobcache tests + bats tests - parallelize + pkg/overlay: cleanups + RPM: include check section to silence rpmlint + RPM: use default gobuild macro on RHEL + tests: remove masked /sys/dev/block check + vendor to latest c/{common,image,storage} + build, run: record hash or digest in image history + Accept image names as sources for cache mounts + Run(): always clean up options.ExternalImageMounts + refactor: replace golang.org/x/exp with stdlib + Update to c/image @main + fix broken doc link + run_freebsd.go: only import runtime-spec once + fix(deps): update module github.com/docker/docker to v27.5.1+incompatible + bump github.com/vbatts/tar-split + Add more checks to the --mount flag parsing logic + chroot mount flags integration test: copy binaries + fix(deps): update module github.com/moby/buildkit to v0.19.0 + relabel(): correct a misleading parameter name + Fix TOCTOU error when bind and cache mounts use "src" values + define.TempDirForURL(): always use an intermediate subdirectory + internal/volume.GetBindMount(): discard writes in bind mounts + pkg/overlay: add a MountLabel flag to Options + pkg/overlay: add a ForceMount flag to Options + Add internal/volumes.bindFromChroot() + Add an internal/open package + fix(deps): update module github.com/containers/common to v0.61.1 + fix(deps): update module github.com/containers/image/v5 to v5.33.1 + [CI:DOCS] Touch up changelogs + fix(deps): update module github.com/docker/docker to v27.5.0+incompatible + copy-preserving-extended-attributes: use a different base image + fix(deps): update github.com/containers/luksy digest to a3a812d + chore(deps): update module golang.org/x/net to v0.33.0 [security] + fix(deps): update module golang.org/x/crypto to v0.32.0 + New VM Images + fix(deps): update module github.com/opencontainers/runc to v1.2.4 + fix(deps): update module github.com/docker/docker to v27.4.1+incompatible + fix(deps): update module github.com/containers/ocicrypt to v1.2.1 + Add support for --security-opt mask and unmask + Allow cache mounts to be stages or additional build contexts + [skip-ci] RPM: cleanup changelog conditionals + fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.6 + fix(deps): update module github.com/moby/buildkit to v0.18.2 + Fix an error message in the chroot unit test + copier: use .PAXRecords instead of .Xattrs + chroot: on Linux, try to pivot_root before falling back to chroot + manifest add: add --artifact-annotation + Add context to an error message + Update module golang.org/x/crypto to v0.31.0 + Update module github.com/opencontainers/runc to v1.2.3 + Update module github.com/docker/docker to v27.4.0+incompatible + Update module github.com/cyphar/filepath-securejoin to v0.3.5 + CI: don't build a binary in the unit tests task + CI: use /tmp for $GOCACHE + CI: remove dependencies on the cross-build task + CI: run cross-compile task with make -j + Update module github.com/docker/docker to v27.4.0-rc.4+incompatible + Update module github.com/moby/buildkit to v0.18.1 + Update module golang.org/x/crypto to v0.30.0 + Update golang.org/x/exp digest to 2d47ceb + Update github.com/opencontainers/runtime-tools digest to f7e3563 + [skip-ci] Packit: remove rhel copr build jobs + [skip-ci] Packit: switch to fedora-all for copr + Update module github.com/stretchr/testify to v1.10.0 + Update module github.com/moby/buildkit to v0.17.2 + Makefile: use `find` to detect source files + Tests: make _prefetch() parallel-safe + Update module github.com/opencontainers/runc to v1.2.2 + executor: allow to specify --no-pivot-root + Update module github.com/moby/sys/capability to v0.4.0 + Makefile: mv codespell config to .codespellrc + Fix some codespell errors + Makefile,install.md: rm gopath stuff + Makefile: rm targets working on .. + build: rm exclude_graphdriver_devicemapper tag + Makefile: rm unused var + Finish updating to go 1.22 + CI VMs: bump again + Bump to Buidah v1.39.0-dev + stage_executor: set avoidLookingCache only if mounting stage + imagebuildah: additionalContext is not a local built stage + +## v1.38.0 (2024-11-08) + + Bump to c/common v0.61.0, c/image v5.33.0, c/storage v1.56.0 + fix(deps): update module golang.org/x/crypto to v0.29.0 + fix(deps): update module github.com/moby/buildkit to v0.17.1 + fix(deps): update module github.com/containers/storage to v1.56.0 + tests: skip two ulimit tests + CI VMs: bump f40 -> f41 + tests/tools: rebuild tools when we change versions + tests/tools: update golangci-lint to v1.61.0 + fix(deps): update module github.com/moby/buildkit to v0.17.0 + Handle RUN --mount with relative targets and no configured workdir + tests: bud: make parallel-safe + fix(deps): update module github.com/opencontainers/runc to v1.2.1 + fix(deps): update golang.org/x/exp digest to f66d83c + fix(deps): update github.com/opencontainers/runtime-tools digest to 6c9570a + tests: blobcache: use unique image name + tests: sbom: never write to cwd + tests: mkcw: bug fixes, refactor + deps: bump runc to v1.2.0 + deps: switch to moby/sys/userns + tests/test_runner.sh: remove some redundancies + Integration tests: run git daemon on a random-but-bind()able port + fix(deps): update module github.com/opencontainers/selinux to v1.11.1 + go.mod: remove unnecessary replace + Document more buildah build --secret options + Add support for COPY --exclude and ADD --exclude options + fix(deps): update github.com/containers/luksy digest to e2530d6 + chore(deps): update dependency containers/automation_images to v20241010 + fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.4 + Properly validate cache IDs and sources + [skip-ci] Packit: constrain koji job to fedora package to avoid dupes + Audit and tidy OWNERS + fix(deps): update module golang.org/x/crypto to v0.28.0 + tests: add quotes to names + vendor: update c/common to latest + CVE-2024-9407: validate "bind-propagation" flag settings + vendor: switch to moby/sys/capability + Don't set ambient capabilities + Document that zstd:chunked is downgraded to zstd when encrypting + fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.3 + buildah-manifest-create.1: Fix manpage section + chore(deps): update dependency ubuntu to v24 + Make `buildah manifest push --all` true by default + chroot: add newlines at the end of printed error messages + Do not error on trying to write IMA xattr as rootless + fix: remove duplicate conditions + fix(deps): update module github.com/moby/buildkit to v0.16.0 + fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.2 + Document how entrypoint is configured in buildah config + In a container, try to register binfmt_misc + imagebuildah.StageExecutor: clean up volumes/volumeCache + build: fall back to parsing a TARGETPLATFORM build-arg + `manifest add --artifact`: handle multiple values + Packit: split out ELN jobs and reuse fedora downstream targets + Packit: Enable sidetags for bodhi updates + fix(deps): update module github.com/docker/docker to v27.2.1+incompatible + tests/bud.bats: add git source + add: add support for git source + Add support for the new c/common pasta options + vendor latest c/common + fix(deps): update module golang.org/x/term to v0.24.0 + fix(deps): update module github.com/fsouza/go-dockerclient to v1.12.0 + packit: update fedora and epel targets + cirrus: disable f39 testing + cirrus: fix fedora names + update to go 1.22 + Vendor c/common:9d025e4cb348 + copier: handle globbing with "**" path components + fix(deps): update golang.org/x/exp digest to 9b4947d + fix(deps): update github.com/containers/luksy digest to 2e7307c + imagebuildah: make scratch config handling toggleable + fix(deps): update module github.com/docker/docker to v27.2.0+incompatible + Add a validation script for Makefile $(SOURCES) + fix(deps): update module github.com/openshift/imagebuilder to v1.2.15 + New VMs + Update some godocs, use 0o to prefix an octal in a comment + buildah-build.1.md: expand the --layer-label description + fix(deps): update module github.com/containers/common to v0.60.2 + run: fix a nil pointer dereference on FreeBSD + CI: enable the whitespace linter + Fix some govet linter warnings + Commit(): retry committing to local storage on storage.LayerUnknown + CI: enable the gofumpt linter + conformance: move weirdly-named files out of the repository + fix(deps): update module github.com/docker/docker to v27.1.2+incompatible + fix(deps): update module github.com/containers/common to v0.60.1 + *: use gofmt -s, add gofmt linter + *: fix build tags + fix(deps): update module github.com/containers/image/v5 to v5.32.1 + Add(): re-escape any globbed items that included escapes + conformance tests: use mirror.gcr.io for most images + unit tests: use test-specific policy.json and registries.conf + fix(deps): update module golang.org/x/sys to v0.24.0 + Update to spun-out "github.com/containerd/platforms" + Bump github.com/containerd/containerd + test/tools/Makefile: duplicate the vendor-in-container target + linters: unchecked error + linters: don't end loop iterations with "else" when "then" would + linters: unused arguments shouldn't have names + linters: rename checkIdsGreaterThan5() to checkIDsGreaterThan5() + linters: don't name variables "cap" + `make lint`: use --timeout instead of --deadline + Drop the e2e test suite + fix(deps): update module golang.org/x/crypto to v0.26.0 + fix(deps): update module github.com/onsi/gomega to v1.34.1 + `make vendor-in-container`: use the caller's Go cache if it exists + fix(deps): fix test/tools ginkgo typo + fix(deps): update module github.com/onsi/ginkgo/v2 to v2.19.1 + Update to keep up with API changes in storage + fix(deps): update github.com/containers/luksy digest to 1f482a9 + install: On Debian/Ubuntu, add installation of libbtrfs-dev + fix(deps): update module golang.org/x/sys to v0.23.0 + fix(deps): update golang.org/x/exp digest to 8a7402a + fix(deps): update module github.com/fsouza/go-dockerclient to v1.11.2 + Use Epoch: 2 and respect the epoch in dependencies. + Bump to Buildah v1.38.0-dev + AddAndCopyOptions: add CertPath, InsecureSkipTLSVerify, Retry fields + Add PrependedLinkedLayers/AppendedLinkedLayers to CommitOptions + integration tests: teach starthttpd() about TLS and pid files + +## v1.37.0 (2024-07-26) + + Bump c/storage, c/image, c/common for v1.37.0 + "build with basename resolving user arg" tests: correct ARG use + bud-multiple-platform-no-run test: correct ARG use + imagebuildah: always have default values for $TARGET... args ready + bump github.com/openshift/imagebuilder to v1.2.14 + fix(deps): update module github.com/docker/docker to v27.1.1+incompatible + fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.1 + fix(deps): update module github.com/docker/docker to v27.1.0+incompatible + CI: use local registry, part 2 of 2 + CI: use local registry, part 1 of 2 + fix(deps): update module github.com/fsouza/go-dockerclient to v1.11.1 + Revert "fix(deps): update github.com/containers/image/v5 to v5.31.1" + Replace libimage.LookupReferenceFunc with the manifests version + conformance tests: enable testing CompatVolumes + conformance tests: add a test that tries to chown a volume + imagebuildah: make traditional volume handling not the default + StageExecutor.prepare(): mark base image volumes for preservation + fix(deps): update module github.com/containers/image/v5 to v5.31.1 + Vendor in latest containers/(common, storage, image) + fix(deps): update module golang.org/x/term to v0.22.0 + fix(deps): update module golang.org/x/sys to v0.22.0 + fix(deps): update golang.org/x/exp digest to 7f521ea + fix(deps): update github.com/containers/luksy digest to a8846e2 + imagebuildah.StageExecutor.Copy(): reject new flags for now + bump github.com/openshift/imagebuilder to v1.2.11 + Rework parsing of --pull flags + fix(deps): update module github.com/containers/image/v5 to v5.31.1 + imagebuildah.StageExecutor.prepare(): log the --platform flag + CI VMs: bump + buildah copy: preserve owner info with --from= a container or image + conformance tests: enable testing CompatSetParent + containerImageRef.NewImageSource(): move the FROM comment to first + commit: set "parent" for docker format only when requested + Update godoc for Builder.EnsureContainerPathAs + fix(deps): update module github.com/spf13/cobra to v1.8.1 + fix(deps): update module github.com/containernetworking/cni to v1.2.0 + fix(deps): update module github.com/opencontainers/runc to v1.1.13 + Change default for podman build to --pull missing + fix(deps): update module github.com/containers/common to v0.59.1 + Clarify definition of --pull options + buildah: fix a nil pointer reference on FreeBSD + Use /var/tmp for $TMPDIR for vfs conformance jobs + Cirrus: run `df` during job setup + conformance: use quay.io/libpod/centos:7 instead of centos:8 + Stop setting "parent" in docker format + conformance: check if workdir trims path separator suffixes + push integration test: pass password to docker login via stdin + Re-enable the "copy with chown" conformance test + healthcheck: Add support for `--start-interval` + fix(deps): update module github.com/docker/docker to v26.1.4+incompatible + fix(deps): update module github.com/containerd/containerd to v1.7.18 + tests: set _CONTAINERS_USERNS_CONFIGURED=done for libnetwork + Cross-build on Fedora + Drop copyStringSlice() and copyStringStringMap() + fix(deps): update module golang.org/x/crypto to v0.24.0 + fix(deps): update module github.com/openshift/imagebuilder to v1.2.10 + Provide an uptime_netbsd.go + Spell unix as "!windows" + Add netbsd to lists-of-OSes + fix(deps): update golang.org/x/exp digest to fd00a4e + [skip-ci] Packit: enable c10s downstream sync + CI VMs: bump, to debian with cgroups v2 + Document when BlobDirectory is overridden + fix secret mounts for env vars when using chroot isolation + Change to take a types.ImageReference arg + imagebuildah: Support custom image reference lookup for cache push/pull + fix(deps): update module github.com/onsi/ginkgo/v2 to v2.19.0 + Bump to v1.37.0-dev + CI: Clarify Debian use for conformance tests + +## v1.36.0 (2024-05-23) + + build: be more selective about specifying the default OS + Bump to c/common v0.59.0 + Fix buildah prune --help showing the same example twice + fix(deps): update module github.com/onsi/ginkgo/v2 to v2.18.0 + fix(deps): update module github.com/containers/image/v5 to v5.31.0 + bud tests: fix breakage when vendoring into podman + Integration tests: fake up a replacement for nixery.dev/shell + copierWithSubprocess(): try to capture stderr on io.ErrClosedPipe + Don't expand RUN heredocs ourselves, let the shell do it + Don't leak temp files on failures + Add release note template to split dependency chores + fix CentOS/RHEL build - no BATS there + fix(deps): update module github.com/containers/luksy to v0.0.0-20240506205542-84b50f50f3ee + Address CVE-2024-3727 + chore(deps): update module github.com/opencontainers/runtime-spec to v1.2.0 + Builder.cdiSetupDevicesInSpecdefConfig(): use configured CDI dirs + Setting --arch should set the TARGETARCH build arg + fix(deps): update module golang.org/x/exp to v0.0.0-20240416160154-fe59bbe5cc7f + [CI:DOCS] Add link to Buildah image page to README.md + Don't set GOTOOLCHAIN=local + fix(deps): update module github.com/cyphar/filepath-securejoin to v0.2.5 + Makefile: set GOTOOLCHAIN=local + Integration tests: switch some base images + containerImageRef.NewImageSource: merge the tar filters + fix(deps): update module github.com/onsi/ginkgo/v2 to v2.17.2 + fix(deps): update module github.com/containers/luksy to v0.0.0-20240408185936-afd8e7619947 + Disable packit builds for centos-stream+epel-next-8 + Makefile: add missing files to $(SOURCES) + CI VMs: bump to new versions with tmpfs /tmp + chore(deps): update module golang.org/x/net to v0.23.0 [security] + integration test: handle new labels in "bud and test --unsetlabel" + Switch packit configuration to use epel-9-$arch ... + Give unit tests a bit more time + Integration tests: remove a couple of duplicated tests + Integration tests: whitespace tweaks + Integration tests: don't remove images at start or end of test + Integration tests: use cached images more + Integration tests _prefetch: use registry configs + internal: use fileutils.(Le|E)xists + pkg/parse: use fileutils.(Le|E)xists + buildah: use fileutils.(Le|E)xists + chroot: use fileutils.(Le|E)xists + vendor: update containers/(common|storage) + Fix issue/pr lock workflow + [CI:DOCS] Add golang 1.21 update warning + heredoc: honor inline COPY irrespective of ignorefiles + Update install.md + source-push: add support for --digestfile + Fix caching when mounting a cached stage with COPY/ADD + fix(deps): update github.com/containers/luksy digest to 3d2cf0e + Makefile: softcode `strip`, use it from env var + Man page updates + Add support for passing CDI specs to --device + Update comments on some API objects + pkg/parse.DeviceFromPath(): dereference src symlinks + fix(deps): update module github.com/onsi/ginkgo/v2 to v2.17.1 + +## v1.35.0 (2024-03-06) + + fix(deps): update module github.com/stretchr/testify to v1.9.0 + cgroups: reuse version check from c/common + Update vendor of containers/(common,image) + fix(deps): update github.com/containers/storage digest to eadc620 + fix(deps): update github.com/containers/luksy digest to ceb12d4 + fix(deps): update github.com/containers/image/v5 digest to cdc6802 + manifest add: complain if we get artifact flags without --artifact + Use retry logic from containers/common + Vendor in containers/(storage,image,common) + Update module golang.org/x/crypto to v0.20.0 + Add comment re: Total Success task name + tests: skip_if_no_unshare(): check for --setuid + Properly handle build --pull=false + [skip-ci] Update tim-actions/get-pr-commits action to v1.3.1 + Update module go.etcd.io/bbolt to v1.3.9 + Revert "Reduce official image size" + Update module github.com/opencontainers/image-spec to v1.1.0 + Reduce official image size + Build with CNI support on FreeBSD + build --all-platforms: skip some base "image" platforms + Bump main to v1.35.0-dev + Vendor in latest containers/(storage,image,common) + Split up error messages for missing --sbom related flags + `buildah manifest`: add artifact-related options + cmd/buildah/manifest.go: lock lists before adding/annotating/pushing + cmd/buildah/manifest.go: don't make struct declarations aliases + Use golang.org/x/exp/slices.Contains + Disable loong64 again + Fix a couple of typos in one-line comments + egrep is obsolescent; use grep -E + Try Cirrus with a newer VM version + Set CONTAINERS_CONF in the chroot-mount-flags integration test + Update to match dependency API update + Update github.com/openshift/imagebuilder and containers/common + docs: correct default authfile path + fix(deps): update module github.com/containerd/containerd to v1.7.13 + tests: retrofit test for heredoc summary + build, heredoc: show heredoc summary in build output + manifest, push: add support for --retry and --retry-delay + fix(deps): update github.com/openshift/imagebuilder digest to b767bc3 + imagebuildah: fix crash with empty RUN + fix(deps): update github.com/containers/luksy digest to b62d551 + fix(deps): update module github.com/opencontainers/runc to v1.1.12 [security] + fix(deps): update module github.com/moby/buildkit to v0.12.5 [security] + Make buildah match podman for handling of ulimits + docs: move footnotes to where they're applicable + Allow users to specify no-dereference + Run codespell on code + Fix FreeBSD version parsing + Fix a build break on FreeBSD + Remove a bad FROM line + fix(deps): update module github.com/onsi/gomega to v1.31.1 + fix(deps): update module github.com/opencontainers/image-spec to v1.1.0-rc6 + docs: use reversed logo for dark theme in README + build,commit: add --sbom to scan and produce SBOMs when committing + commit: force omitHistory if the parent has layers but no history + docs: fix a couple of typos + internal/mkcw.Archive(): handle extra image content + stage_executor,heredoc: honor interpreter in heredoc + stage_executor,layers: burst cache if heredoc content is changed + fix(deps): update module golang.org/x/crypto to v0.18.0 + Replace map[K]bool with map[K]struct{} where it makes sense + fix(deps): update module golang.org/x/sync to v0.6.0 + fix(deps): update module golang.org/x/term to v0.16.0 + Bump CI VMs + Replace strings.SplitN with strings.Cut + fix(deps): update github.com/containers/storage digest to ef81e9b + fix(deps): update github.com/containers/image/v5 digest to 1b221d4 + fix(deps): update module github.com/fsouza/go-dockerclient to v1.10.1 + Document use of containers-transports values in buildah + fix(deps): update module golang.org/x/crypto to v0.17.0 [security] + chore(deps): update dependency containers/automation_images to v20231208 + manifest: addCompression use default from containers.conf + commit: add a --add-file flag + mkcw: populate the rootfs using an overlay + chore(deps): update dependency containers/automation_images to v20230517 + [skip-ci] Update actions/stale action to v9 + fix(deps): update module github.com/containernetworking/plugins to v1.4.0 + fix(deps): update github.com/containers/image/v5 digest to 7a40fee + Bump to v1.34.1-dev + Ignore errors if label.Relabel returns ENOSUP + +## v1.34.0 (2023-12-11) + + vendor: update c/{common,image,storage} + run: Allow using just one jail per container on FreeBSD + Remove makefile targets entrypoint{,.gz} for non x86_64 + +## v1.33.2 (2023-11-22) + + Update minimum to golang 1.20 + fix(deps): update module github.com/fsouza/go-dockerclient to v1.10.0 + fix(deps): update module github.com/moby/buildkit to v0.12.3 + Bump to v1.33.2-dev + +## v1.33.1 (2023-11-18) + + fix(deps): update module github.com/moby/buildkit to v0.11.4 [security] + test,heredoc: use fedora instead of docker.io/library/python:latest + Bump to v1.33.1-dev + +## v1.33.0 (2023-11-17) + + Never omit layers for emptyLayer instructions when squashing/cwing + Add OverrideChanges and OverrideConfig to CommitOptions + buildah: add heredoc support for RUN, COPY and ADD + vendor: bump imagebuilder to v1.2.6-0.20231110114814-35a50d57f722 + conformance tests: archive the context directory as 0:0 (#5171) + blobcacheinfo,test: blobs must be resued when pushing across registry + Bump c/storage v1.51.0, c/image v5.29.0, c/common v0.57.0 + pkg/util.MirrorToTempFileIfPathIsDescriptor(): don't leak an fd + StageExecutor.Execute: force a commit for --unsetenv, too + Increase a copier+chroot test timeout + Add support for --compat-auth-file in login/logout + Update existing tests for error message change + Update c/image and c/common to latest + fix(deps): update module github.com/containerd/containerd to v1.7.9 + build: downgrade to go 1.20 + Add godoc for pkg/parse.GetTempDir + conformance tests: use go-dockerclient for BuildKit builds + Make TEE types case-insensitive + fix(deps): update module golang.org/x/crypto to v0.15.0 + Tweak some help descriptions + Stop using DefaultNetworkSysctl and use containers.conf only + Implement ADD checksum flag #5135 + vendor of openshift/imagebuilder #5135 + Pass secrets from the host down to internal podman containers + Update cirrus and version of golang + image: replace GetStoreImage with ResolveReference + vendor: bump c/image to 373c52a9466f + pkg/parse.Platform(): minor simplification + createConfigsAndManifests: clear history before cw-specific logic + Use a constant definition instead of "scratch" + conformance: use require.NoErrorf() more + fix(deps): update module golang.org/x/term to v0.14.0 + fix(deps): update module golang.org/x/sync to v0.5.0 + fix(deps): update module github.com/spf13/cobra to v1.8.0 + fix(deps): update module golang.org/x/sys to v0.14.0 + fix(deps): update github.com/containers/common digest to 8354404 + fix(deps): update module github.com/opencontainers/runc to v1.1.10 + fix(deps): update github.com/containers/luksy digest to b5a7f79 + Log the platform for build errors during multi-platform builds + Use mask definitions from containers/common + Vendor in latest containers/common + fix(deps): update module github.com/containerd/containerd to v1.7.8 + fix(deps): update module go.etcd.io/bbolt to v1.3.8 + container.conf: support attributed string slices + fix(deps): update module sigs.k8s.io/yaml to v1.4.0 + Use cutil.StringInSlice rather then contains + Add --no-hostname option to buildah containers + vendor c/common: appendable containers.conf strings, Part 1 + fix(deps): update module github.com/onsi/gomega to v1.28.1 + chroot.setupChrootBindMounts: pay more attention to flags + chore(deps): update dependency containers/automation_images to v20231004 + Vendor containers/common + chore(deps): update module golang.org/x/net to v0.17.0 [security] + run: use internal.GetTempDir with os.MkdirTemp + fix(deps): update module github.com/containerd/containerd to v1.7.7 + imagebuildah,multi-stage: do not remove base images + gitignore: add mkcw binary + mkcw: remove entrypoint binaries + fix(deps): update module golang.org/x/crypto to v0.14.0 + fix(deps): update module golang.org/x/sys to v0.13.0 + fix(deps): update module golang.org/x/sync to v0.4.0 + Update some comments related to confidential workload + Use the parent's image ID in the config that we pass to imagebuilder + fix(deps): update github.com/containers/common digest to 8892536 + fix(deps): update github.com/containers/luksy digest to 6df88cb + bug: Ensure the mount type is always BindMount by default + Protocol can be specified with --port. Ex. --port 514/udp + fix(deps): update module github.com/onsi/gomega to v1.28.0 + build,config: add support for --unsetlabel + tests/bud: add tests + [CI:BUILD] Packit: tag @containers/packit-build on copr build failures + stage_executor: allow images without layers + vendor of containers/common + Removing selinux_tag.sh as no longer needed after 580356f [NO NEW TESTS NEEDED] + add/copy: make sure we handle relative path names correctly + fix(deps): update module github.com/opencontainers/image-spec to v1.1.0-rc5 + Bump to v1.33.0-dev + imagebuildah: consider ignorefile with --build-context + +## v1.32.0 (2023-09-14) + + GetTmpDir is not using ImageCopyTmpdir correctly + Run codespell on code + Bump vendor containers/(common, storage, image) + Cirrus: Remove multi-arch buildah image builds + fix(deps): update module github.com/containerd/containerd to v1.7.6 + Split GetTempDir from internal/util + Move most of internal/parse to internal/volumes + copier: remove libimage dependency via util package + Add some docs for `build --cw`, `commit --cw`, and `mkcw` + Add `buildah mkcw`, add `--cw` to `buildah commit` and `buildah build` + Make sure that pathnames picked up from the environment are absolute + fix(deps): update module github.com/cyphar/filepath-securejoin to v0.2.4 + fix(deps): update module github.com/docker/docker to v24.0.6+incompatible + Don't try to look up names when committing images + fix(deps): update module golang.org/x/crypto to v0.13.0 + docs: use valid github repo + fix(deps): update module golang.org/x/sys to v0.12.0 + vendor containers/common@12405381ff45 + push: --force-compression should be true with --compression-format + Update module github.com/containerd/containerd to v1.7.5 + [skip-ci] Update tim-actions/commit-message-checker-with-regex action to v0.3.2 + docs: add reference to oci-hooks + Support passing of ULimits as -1 to mean max + GHA: Attempt to fix discussion_lock workflow + Fixing the owner of the storage.conf. + pkg/chrootuser: Ignore comments when parsing /etc/group on FreeBSD + Use buildah repo rather then podman repo + GHA: Closed issue/PR comment-lock test + fix(deps): update module github.com/containers/storage to v1.49.0 + chore(deps): update dependency containers/automation_images to v20230816 + Replace troff code with markdown in buildah-{copy,add}.1.md + [CI:BUILD] rpm: spdx compatible license field + executor: build-arg warnings must honor global args + fix(deps): update module github.com/containers/ocicrypt to v1.1.8 + chroot: `setSeccomp` add support for `ArchPARISC(64)` and `ArchRISCV64` + make,cross: restore loong64 + Clear CommonBuildOpts when loading Builder status + buildah/push/manifest-push: add support for --force-compression + vendor: bump c/common to v0.55.1-0.20230811093040-524b4d5c12f9 + chore(deps): update dependency containers/automation_images to v20230809 + [CI:BUILD] RPM: fix buildtags + fix(deps): update module github.com/opencontainers/runc to v1.1.9 + chore(deps): update dependency ubuntu to v22 + chore(deps): update dependency containers/automation_images to v20230807 + [CI:BUILD] Packit: add fedora-eln targets + [CI:BUILD] RPM: build docs with vendored go-md2man + packit: Build PRs into default packit COPRs + Update install.md + Update install.md changes current Debian stable version name + fix(deps): update module golang.org/x/term to v0.11.0 + fix(deps): update module golang.org/x/crypto to v0.12.0 + tests: fix layer-label tests + buildah: add --layer-label for setting labels on layers + Cirrus: container/rootless env. var. passthrough + Cirrus: Remove duplicate env. var. definitions + fix(deps): update github.com/containers/storage digest to c3da76f + Add a missing .Close() call on an ImageSource + Create only a reference when that's all we need + Add a missing .Close() call on an ImageDestination + CI:BUILD] RPM: define gobuild macro for rhel/centos stream + manifest/push: add support for --add-compression + manifest/inspect: add support for tls-verify and authfile + vendor: bump c/common to v0.55.1-0.20230727095721-647ed1d4d79a + vendor: bump c/image to v5.26.1-0.20230726142307-8c387a14f4ac + fix(deps): update module github.com/containerd/containerd to v1.7.3 + fix(deps): update module github.com/onsi/gomega to v1.27.10 + fix(deps): update module github.com/docker/docker to v24.0.5+incompatible + fix(deps): update module github.com/containers/image/v5 to v5.26.1 + fix(deps): update module github.com/opencontainers/runtime-spec to v1.1.0 + Update vendor of containers/(storage,image,common) + fix(deps): update module github.com/opencontainers/runc to v1.1.8 + [CI:BUILD] Packit: remove pre-sync action + fix(deps): update module github.com/containers/common to v0.55.2 + [CI:BUILD] Packit: downstream task script needs GOPATH + Vendor in containers/(common, image, storage) + fix(deps): update module golang.org/x/term to v0.10.0 + [CI:BUILD] Packit: fix pre-sync action for downstream tasks + contrib/buildahimage: set config correctly for rootless build user + fix(deps): update module github.com/opencontainers/image-spec to v1.1.0-rc4 + Bump to v1.32.0-dev + Update debian install instructions + pkg/overlay: add limited support for FreeBSD + +## v1.31.0 (2023-06-30) + + Bump c/common to 0.55.1 and c/image to 5.26.1 + Bump c/image to 5.26.0 and c/common to 0.54.0 + vendor: update c/{common,image,storage} to latest + chore: pkg imported more than once + buildah: add pasta(1) support + use slirp4netns package from c/common + update c/common to latest + add hostname to /etc/hosts when running with host network + vendor: update c/common to latest + [CI:BUILD] Packit: add jobs for downstream Fedora package builds + fix(deps): update module golang.org/x/sync to v0.3.0 + fix(deps): update module golang.org/x/crypto to v0.10.0 + Add smoke tests for encryption CLI helpers + fix(deps): update module golang.org/x/term to v0.9.0 + fix(deps): update module github.com/opencontainers/runtime-spec to v1.1.0-rc.3 + Remove device mapper support + Remove use of deprecated tar.TypeRegA + Update tooling to support newer golangci-lint + Make cli.EncryptConfig,DecryptConfig, GetFormat public + Don't decrypt images by default + fix(deps): update module github.com/onsi/gomega to v1.27.8 + fix(deps): update github.com/containers/storage digest to 3f3fb2f + Renovate: Don't touch fragile test stuffs + [CI:DOCS] Update comment to remove ambiguity + fix(deps): update github.com/containers/image/v5 digest to abe5133 + fix(deps): update module github.com/sirupsen/logrus to v1.9.3 + fix(deps): update module github.com/containerd/containerd to v1.7.2 + Explicitly ref. quay images for CI + At startup, log the effective capabilities for debugging + parse: use GetTempDir from internal utils + GetTmpDir: honor image_copy_tmp_dir from containers.conf + docs/Makefile: don't show sed invocations + CI: Support testing w/ podman-next COPR packages + intermediate-images inherit-label test: make it debuggable + fix(deps): update github.com/containers/common digest to 462ccdd + Add a warning to `--secret` docs + vendor: bump c/storage to v1.46.2-0.20230526114421-55ee2d19292f + executor: apply label to only final stage + remove registry.centos.org + Go back to setting SysProcAttr.Pdeathsig for child processes + Fix auth.json path (validated on Fedora 38) wq Signed-off-by: Andreas Mack + fix(deps): update module github.com/stretchr/testify to v1.8.3 + CI: fix test broken by renovatebot + chore(deps): update quay.io/libpod/testimage docker tag to v20221018 + fix(deps): update module github.com/onsi/gomega to v1.27.7 + test: use debian instead of docker.io/library/debian:testing-slim + vendor: bump logrus to 1.9.2 + [skip-ci] Update tim-actions/get-pr-commits action to v1.3.0 + Revert "Proof of concept: nightly dependency treadmill" + fix(deps): update module github.com/sirupsen/logrus to v1.9.1 + vendor in containers/(common,storage,image) + fix(deps): update module github.com/docker/distribution to v2.8.2+incompatible + run: drop Pdeathsig + chroot: lock thread before setPdeathsig + tests: add a case for required=false + fix(deps): update module github.com/openshift/imagebuilder to v1.2.5 + build: validate volumes on backend + secret: accept required flag w/o value + fix(deps): update module github.com/containerd/containerd to v1.7.1 + fix(deps): update module golang.org/x/crypto to v0.9.0 + Update the demos README file to fix minor typos + fix(deps): update module golang.org/x/sync to v0.2.0 + fix(deps): update module golang.org/x/term to v0.8.0 + manifest, push: use source as destination if not specified + run,mount: remove path only if they didnt pre-exist + Cirrus: Fix meta task failing to find commit + parse: filter edge-case for podman-remote + fix(deps): update module github.com/opencontainers/runc to v1.1.7 + fix(deps): update module github.com/docker/docker to v23.0.5+incompatible + build: --platform must accept only arch + fix(deps): update module github.com/containers/common to v0.53.0 + makefile: increase conformance timeout + Cap suffixDigitsModulo to a 9-digits suffix. + Rename conflict to suffixDigitsModulo + fix(deps): update module github.com/opencontainers/runtime-spec to v1.1.0-rc.2 + fix(deps): update module github.com/opencontainers/runc to v1.1.6 + chore(deps): update centos docker tag to v8 + Clarify the need for qemu-user-static package + chore(deps): update quay.io/centos/centos docker tag to v8 + Renovate: Ensure test/tools/go.mod is managed + Revert "buildah image should not enable fuse-overlayfs for rootful mode" + Bump to v1.31.0-dev + parse: add support for relabel bind mount option + +## v1.30.0 (2023-04-06) + + fix(deps): update module github.com/opencontainers/runc to v1.1.5 + fix(deps): update module github.com/fsouza/go-dockerclient to v1.9.7 + buildah image should not enable fuse-overlayfs for rootful mode + stage_executor: inline network add default string + fix(deps): update module github.com/containers/common to v0.51.2 + chore(deps): update dependency containers/automation_images to v20230330 + fix(deps): update module github.com/docker/docker to v23.0.2+incompatible + chore(deps): update dependency containers/automation_images to v20230320 + fix(deps): update module github.com/onsi/gomega to v1.27.6 + fix(deps): update github.com/opencontainers/runtime-tools digest to e931285 + [skip-ci] Update actions/stale action to v8 + test: don't allow to override io.buildah.version + executor: only apply label on the final stage + Update docs/buildah-build.1.md + update build instruction for Ubuntu + code review + build: accept arguments from file with --build-arg-file + run_linux: Update heuristic for mounting /sys + [CI:BUILD] Packit: Enable Copr builds on PR and commit to main + fix(deps): update module github.com/fsouza/go-dockerclient to v1.9.6 + Update to Go 1.18 + Disable dependabot in favor of renovate + chore(deps): update dependency containers/automation_images to v20230314 + Fix requiring tests on Makefile changes + Vendor in latest containers/(storage, common, image) + imagebuildah: set len(short_image_id) to 12 + Re-enable conformance tests + Skip conformance test failures with Docker 23.0.1 + Cirrus: Replace Ubuntu -> Debian SID + run: add support for inline --network in RUN stmt + vendor: bump imagebuilder to a3c3f8358ca31b1e4daa6 + stage_executor: attempt to push cache only when cacheKey is valid + Add "ifnewer" as option in help message for pull command + build: document behaviour of buildah's distributed cache + fix(deps): update module golang.org/x/term to v0.6.0 + Add default list of capabilities required to run buildah in a container + executor,copy: honor default ARG value while eval stage + sshagent: use ExtendedAgent instead of Agent + tests/bud: remove unwated test + executor: do not warn on builtin default args + executor: don't warn about unused TARGETARCH,TARGETOS,TARGETPLATFORM + Fix tutorial for rootless mode + Vendor in latest containers/(common, storage, image) + Ignore the base image's base image annotations + fix(deps): update module github.com/fsouza/go-dockerclient to v1.9.5 + build(deps): bump github.com/containers/storage from 1.45.3 to 1.45.4 + Vendor in latest containers/common + docs/tutorials/04: add defaults for Run() + imagebuildah.StageExecutor: suppress bogus "Pushing cache []:..." + executor: also add stage with no children to cleanupStages + [CI:BUILD] copr: fix el8 builds + Fix documentation on which Capabilities are allowed by default + Skip subject-length validation for renovate PRs + Temporarily hard-skip bud-multiple-platform-values test + fix(deps): update github.com/openshift/imagebuilder digest to 86828bf + build(deps): bump github.com/containerd/containerd from 1.6.16 to 1.6.17 + build(deps): bump tim-actions/get-pr-commits from 1.1.0 to 1.2.0 + build(deps): bump github.com/containers/image/v5 from 5.24.0 to 5.24.1 + [skip-ci] Update tim-actions/get-pr-commits digest to 55b867b + build(deps): bump github.com/opencontainers/selinux + build(deps): bump golang.org/x/crypto from 0.5.0 to 0.6.0 + Add renovate configuration + Run codespell on codebase + login: support interspersed args for password + conformance: use scratch for minimal test + pkg/parse: expose public CleanCacheMount API + build(deps): bump go.etcd.io/bbolt from 1.3.6 to 1.3.7 + build(deps): bump github.com/containerd/containerd from 1.6.15 to 1.6.16 + docs: specify order preference for FROM + Bump to v1.30.0-dev + +## v1.29.0 (2023-01-25) + + tests: improve build-with-network-test + Bump c/storagev1.45.3, c/imagev5.24.0, c/commonv0.51.0 + build(deps): bump github.com/onsi/gomega from 1.25.0 to 1.26.0 + Flake 3710 has been closed. Reenable the test. + [CI:DOCS] Fix two diversity issues in a tutorial + build(deps): bump github.com/fsouza/go-dockerclient from 1.9.2 to 1.9.3 + vendor in latests containers/(storage, common, image) + fix bud-multiple-platform-with-base-as-default-arg flake + stage_executor: while mounting stages use freshly built stage + build(deps): bump github.com/fsouza/go-dockerclient from 1.9.0 to 1.9.2 + build(deps): bump github.com/onsi/gomega from 1.24.2 to 1.25.0 + vendor in latests containers/(storage, common, image, ocicyrpt) + [Itests: change the runtime-flag test for crun + [CI:DOCS] README: drop sudo + Fix multi-arch manifest-list build timeouts + Cirrus: Update VM Images + bud: Consolidate multiple synthetic LABEL instructions + build, secret: allow realtive mountpoints wrt to work dir + fixed squash documentation + build(deps): bump github.com/containerd/containerd from 1.6.14 to 1.6.15 + Correct minor comment + Vendor in latest containers/(common, image, storage) + system tests: remove unhelpful assertions + buildah: add prune command and expose CleanCacheMount API + vendor: bump c/storage to a747b27 + Add support for --group-add to buildah from + build(deps): bump actions/stale from 6 to 7 + Add documentation for buildah build --pull=missing + build(deps): bump github.com/containerd/containerd from 1.6.12 to 1.6.14 + build(deps): bump github.com/docker/docker + parse: default ignorefile must not point to symlink outside context + buildah: wrap network setup errors + build, mount: allow realtive mountpoints wrt to work dir + Update to F37 CI VM Images, re-enable prior-fedora + Update vendor or containers/(image, storage, common) + build(deps): bump golang.org/x/crypto from 0.3.0 to 0.4.0 + Update contact information + build(deps): bump golang.org/x/term from 0.2.0 to 0.3.0 + Replace io/ioutil calls with os calls + [skip-ci] GHA/Cirrus-cron: Fix execution order + Vendor in containers/common + build(deps): bump golang.org/x/sys from 0.2.0 to 0.3.0 + remote-cache: support multiple sources and destinations + Update c/storage after https://github.com/containers/storage/pull/1436 + util.SortMounts(): make the returned order more stable + version: Bump to 1.29.0-dev + [CI:BUILD] Cirrus: Migrate OSX task to M1 + Update vendor of containers/(common, storage, image) + mount=type=cache: seperate cache parent on host for each user + Fix installation instructions for Gentoo Linux + build(deps): bump github.com/containerd/containerd from 1.6.9 to 1.6.10 + GHA: Reuse both cirrus rerun and check workflows + Vendor in latest containers/(common,image,storage) + build(deps): bump github.com/onsi/gomega from 1.24.0 to 1.24.1 + copier.Put(): clear up os/syscall mode bit confusion + build(deps): bump golang.org/x/sys from 0.1.0 to 0.2.0 + Use TypeBind consistently to name bind/nullfs mounts + Add no-new-privileges flag + Update vendor of containers/(common, image, storage) + imagebuildah:build with --all-platforms must honor args for base images + codespell code + Expand args and env when using --all-platforms + build(deps): bump github.com/onsi/gomega from 1.23.0 to 1.24.0 + GHA: Simplify Cirrus-Cron check slightly + Stop using ubi8 + remove unnecessary (hence misleading) rmi + chroot: fix mounting of ro bind mounts + executor: honor default ARG value while eval base name + userns: add arbitrary steps/stage to --userns=auto test + Don't set allow.mount in the vnet jail on Freebsd + copier: Preserve file flags when copying archives on FreeBSD + Remove quiet flag, so that it works in podman-remote + test: fix preserve rootfs with --mount for podman-remote + test: fix prune logic for cache-from after adding content summary + vendor in latest containers/(storage, common, image) + Fix RUN --mount=type=bind,from= not preserving rootfs of stage + Define and use a safe, reliable test image + Fix word missing in Container Tools Guide + Makefile: Use $(MAKE) to start sub-makes in install.tools + imagebuildah: pull cache from remote repo after adding content summary + Makefile: Fix install on FreeBSD + Ensure the cache volume locks are unlocked on all paths + Vendor in latest containers/(common,storage) + Simplify the interface of GetCacheMount and getCacheMount + Fix cache locks with multiple mounts + Remove calls to Lockfile.Locked() + Maintain cache mount locks as lock objects instead of paths + test: cleaning cache must not clean lockfiles + run: honor lockfiles for multiple --mount instruction + mount,cache: lockfiles must not be part of users cache content + Update vendor containers/(common,image,storage) + [CI:BUILD] copr: buildah rpm should depend on containers-common-extra + pr-should-include-tests: allow specfile, golangci + build(deps): bump dawidd6/action-send-mail from 3.7.0 to 3.7.1 + build(deps): bump github.com/docker/docker + build(deps): bump github.com/fsouza/go-dockerclient from 1.8.3 to 1.9.0 + Update vendor containers/(common,image,storage) + build(deps): bump actions/upload-artifact from 2 to 3 + build(deps): bump actions/checkout from 2 to 3 + build(deps): bump actions/stale from 1 to 6 + build(deps): bump dawidd6/action-send-mail from 2.2.2 to 3.7.0 + build(deps): bump tim-actions/get-pr-commits from 1.1.0 to 1.2.0 + sshagent: LockOSThread before setting SocketLabel + Update tests for error message changes + Update c/image after https://github.com/containers/image/pull/1299 + Fix ident for dependabot gha block + build(deps): bump github.com/containers/ocicrypt from 1.1.5 to 1.1.6 + Fix man pages to match latest cobra settings + build(deps): bump github.com/spf13/cobra from 1.5.0 to 1.6.0 + build(deps): bump github.com/onsi/gomega from 1.20.2 to 1.22.1 + test: retrofit 'bud with undefined build arg directory' + imagebuildah: warnOnUnsetBuildArgs while processing stages from executor + Update contrib/buildahimage/Containerfile + Cirrus CI add flavor parameter + Correction - `FLAVOR` not `FLAVOUR` + Changed build argument from `RELEASE` to `FLAVOUR` + Combine buildahimage Containerfiles + bud.bats refactoring: $TEST_SCRATCH_DIR, part 2 of 2 + bud.bats refactoring: $TEST_SCRATCH_DIR, part 1 of 2 + System test cleanup: document, clarify, fix + test: removing unneeded/expensive COPY + test: warning behaviour for unset/set TARGETOS,TARGETARCH,TARGETPLATFORM + Bump to v1.28.1-dev + +## v1.28.0 (2022-09-30) + + Update vendor containers/(common,image) + [CI:DOCS] Add quay-description update reminder + vendor: bump c/common to v0.49.2-0.20220929111928-2d1b45ae2423 + build(deps): bump github.com/opencontainers/selinux + Vendor in latest containers/storage + Changing shell list operators from `;` to `&&` + Fix buildahimage container.conf permissions regression + Set sysctls from containers.conf + refactor: stop using Normalize directly from containerd package + config,builder: process variant while populating image spec + Proof of concept: nightly dependency treadmill + Run codespell on code + Check for unset build args after TARGET args + pkg/cli: improve completion test + vendor in latest containers/(common,storage,image) + copier: work around freebsd bug for "mkdir /" + vendor: update c/image + test: run in the host cgroup namespace + vendor: update c/storage + vendor: update c/common + cmd: check for user UID instead of privileges + run,build: conflict --isolation=chroot and --network + Fix broken dns test (from merge collision) + Fix stutters + Fix broken command completion + buildah bud --network=none should have no network + build: support --skip-unused-stages for multi-stage builds + Prevent use of --dns* options with --net=none + buildah: make --cache-ttl=0s equivalent to --no-cache + parse: make processing flags in --mount order agnostic + Minor test fix for podman-remote + build: honor .containerignore as ignore file + Update install.md: Debian 11 (Bullseye) is stable + build(deps): bump github.com/docker/docker + Use constants from containers/common for finding seccomp.json + Don't call os.Exit(1) from manifest exist + manifest: add support for buildah manifest exists + Buildah should ignore /etc/crio/seccomp.json + chroot: Fix cross build break + chroot: Move isDevNull to run_common.go + chroot: Fix setRlimit build on FreeBSD + chroot: Move parseRLimits and setRlimits to run_common.go + chroot: Fix runUsingChrootExecMain on FreeBSD + chroot: Move runUsingChrootExecMain to run_common.go + chroot: Factor out Linux-specific unshare options from runUsingChroot + chroot: Move runUsingChroot to run_common.go + chroot: Move RunUsingChroot and runUsingChrootMain to run_common.go + chroot: Factor out /dev/ptmx pty implementation + chroot: Add FreeBSD support for run with chroot isolation + build(deps): bump github.com/docker/go-units from 0.4.0 to 0.5.0 + Replace k8s.gcr.io/pause in tests with registry.k8s.io/pause + build(deps): bump github.com/onsi/gomega from 1.20.0 to 1.20.1 + Cirrus: use image with fewer downloaded dependencies + build(deps): bump github.com/opencontainers/runc from 1.1.3 to 1.1.4 + run: add container gid to additional groups + buildah: support for --retry and --retry-delay for push/pull failures + Makefile: always call $(GO) instead of `go` + build(deps): bump github.com/fsouza/go-dockerclient from 1.8.2 to 1.8.3 + test: use `T.TempDir` to create temporary test directory + mount,cache: enable SElinux shared content label option by default + commit: use race-free RemoveNames instead of SetNames + Drop util/util.Cause() + cmd/buildah: add "manifest create --amend" + build(deps): bump github.com/fsouza/go-dockerclient from 1.8.1 to 1.8.2 + docs: specify git protocol is not supported for github hosted repo + Scrub user and group names from layer diffs + build(deps): bump github.com/containerd/containerd from 1.6.6 to 1.6.8 + version: bump to 1.28.0-dev + +## v1.27.0 (2022-08-01) + + build: support filtering cache by duration using `--cache-ttl`. + build: support building from commit when using git repo as build context. + build: clean up git repos correctly when using subdirs. + build: add support for distributing cache to remote sources using `--cache-to` and `--cache-from`. + imagebuildah: optimize cache hits for `COPY` and `ADD` instructions. + build: support OCI hooks for ephemeral build containers. + build: add support for `--userns=auto`. + copier: add NoOverwriteNonDirDir option . + add initial support for building images using Buildah on FreeBSD. + multistage: this now skips the computing of unwanted stages to improve performance. + multiarch: support splitting build logs for `--platform` using `--logsplit`. + build: add support for building images where the base image has no history. + commit: allow disabling image history with `--omit-history`. + build: add support for renaming a device in rootless setups. + build: now supports additionalBuildContext in builds via the `--build-context` option. + build: `--output` produces artifacts even if the build container is not committed. + build: now accepts `-cpp-flag`, allowing users to pass in CPP flags when processing a Containerfile with C Preprocessor-like syntax. + build: now accepts a branch and a subdirectory when the build context is a git repository. + build: output now shows a progress bar while pushing and pulling images + build: now errors out if the path to Containerfile is a directory. + build: support building container images on environments that are rootless and without any valid login sessions. + fix: `--output` now generates artifacts even if the entire build is cached. + fix: `--output` generates artifacts only for the target stage in multi-stage builds. + fix,add: now fails on a bad HTTP response instead of writing to container + fix,squash: never use build cache when computing the last step of the last stage + fix,build,run: allow reusing secret more than once in different RUN steps + fix: compatibility with Docker build by making its --label and --annotate options set empty labels and annotations when given a name but no `=` or label value. + +## v1.26.0 (2022-05-04) + + imagebuildah,build: move deepcopy of args before we spawn goroutine + Vendor in containers/storage v1.40.2 + buildah.BuilderOptions.DefaultEnv is ignored, so mark it as deprecated + help output: get more consistent about option usage text + Handle OS version and features flags + buildah build: --annotation and --label should remove values + buildah build: add a --env + buildah: deep copy options.Args before performing concurrent build/stage + test: inline platform and builtinargs behaviour + vendor: bump imagebuilder to master/009dbc6 + build: automatically set correct TARGETPLATFORM where expected + build(deps): bump github.com/fsouza/go-dockerclient + Vendor in containers/(common, storage, image) + imagebuildah, executor: process arg variables while populating baseMap + buildkit: add support for custom build output with --output + Cirrus: Update CI VMs to F36 + fix staticcheck linter warning for deprecated function + Fix docs build on FreeBSD + build(deps): bump github.com/containernetworking/cni from 1.0.1 to 1.1.0 + copier.unwrapError(): update for Go 1.16 + copier.PutOptions: add StripSetuidBit/StripSetgidBit/StripStickyBit + copier.Put(): write to read-only directories + build(deps): bump github.com/cpuguy83/go-md2man/v2 in /tests/tools + Rename $TESTSDIR (the plural one), step 4 of 3 + Rename $TESTSDIR (the plural one), step 3 of 3 + Rename $TESTSDIR (the plural one), step 2 of 3 + Rename $TESTSDIR (the plural one), step 1 of 3 + build(deps): bump github.com/containerd/containerd from 1.6.2 to 1.6.3 + Ed's periodic test cleanup + using consistent lowercase 'invalid' word in returned err msg + Update vendor of containers/(common,storage,image) + use etchosts package from c/common + run: set actual hostname in /etc/hostname to match docker parity + update c/common to latest main + Update vendor of containers/(common,storage,image) + Stop littering + manifest-create: allow creating manifest list from local image + Update vendor of storage,common,image + Bump golang.org/x/crypto to 7b82a4e + Initialize network backend before first pull + oci spec: change special mount points for namespaces + tests/helpers.bash: assert handle corner cases correctly + buildah: actually use containers.conf settings + integration tests: learn to start a dummy registry + Fix error check to work on Podman + buildah build should accept at most one arg + tests: reduce concurrency for flaky bud-multiple-platform-no-run + vendor in latest containers/common,image,storage + manifest-add: allow override arch,variant while adding image + Remove a stray `\` from .containerenv + Vendor in latest opencontainers/selinux v1.10.1 + build, commit: allow removing default identity labels + Create shorter names for containers based on image IDs + test: skip rootless on cgroupv2 in root env + fix hang when oci runtime fails + Set permissions for GitHub actions + copier test: use correct UID/GID in test archives + run: set parent-death signals and forward SIGHUP/SIGINT/SIGTERM + Bump back to v1.26.0-dev + build(deps): bump github.com/opencontainers/runc from 1.1.0 to 1.1.1 + Included the URL to check the SHA + +## v1.25.1 (2022-03-30) + + buildah: create WORKDIR with USER permissions + vendor: update github.com/openshift/imagebuilder + copier: attempt to open the dir before adding it + Updated dependabot to get updates for GitHub actions. + Switch most calls to filepath.Walk to filepath.WalkDir + build: allow --no-cache and --layers so build cache can be overrided + build(deps): bump github.com/onsi/gomega from 1.18.1 to 1.19.0 + Bump to v1.26.0-dev + build(deps): bump github.com/golangci/golangci-lint in /tests/tools + +## v1.25.0 (2022-03-25) + + install: drop RHEL/CentOS 7 doc + build(deps): bump github.com/containers/common from 0.47.4 to 0.47.5 + Bump c/storage to v1.39.0 in main + Add a test for CVE-2022-27651 + build(deps): bump github.com/docker/docker + Bump github.com/prometheus/client_golang to v1.11.1 + [CI:DOCS] man pages: sort flags, and keep them that way + build(deps): bump github.com/containerd/containerd from 1.6.1 to 1.6.2 + Don't pollute + network setup: increase timeout to 4 minutes + do not set the inheritable capabilities + build(deps): bump github.com/golangci/golangci-lint in /tests/tools + build(deps): bump github.com/containers/ocicrypt from 1.1.2 to 1.1.3 + parse: convert exposed GetVolumes to internal only + buildkit: mount=type=cache support locking external cache store + .in support: improve error message when cpp is not installed + buildah image: install cpp + build(deps): bump github.com/stretchr/testify from 1.7.0 to 1.7.1 + build(deps): bump github.com/spf13/cobra from 1.3.0 to 1.4.0 + build(deps): bump github.com/docker/docker + Add --no-hosts flag to eliminate use of /etc/hosts within containers + test: remove skips for rootless users + test: unshare mount/umount if test is_rootless + tests/copy: read correct containers.conf + build(deps): bump github.com/docker/distribution + cirrus: add seperate task and matrix for rootless + tests: skip tests for rootless which need unshare + buildah: test rootless integration + vendor: bump c/storage to main/93ce26691863 + build(deps): bump github.com/fsouza/go-dockerclient from 1.7.9 to 1.7.10 + tests/copy: initialize the network, too + [CI:DOCS] remove references to Kubic for CentOS and Ubuntu + build(deps): bump github.com/containerd/containerd from 1.6.0 to 1.6.1 + use c/image/pkg/blobcache + vendor c/image/v5@v5.20.0 + add: ensure the context directory is an absolute path + executor: docker builds must inherit healthconfig from base if any + docs: Remove Containerfile and containeringore + build(deps): bump github.com/fsouza/go-dockerclient from 1.7.8 to 1.7.9 + helpers.bash: Use correct syntax + speed up combination-namespaces test + build(deps): bump github.com/golangci/golangci-lint in /tests/tools + Bump back to 1.25.0-dev + build(deps): bump github.com/containerd/containerd from 1.5.9 to 1.6.0 + +## v1.24.2 (2022-02-16) + + Increase subuid/subgid to 65535 + history: only add proxy vars to history if specified + run_linux: use --systemd-cgroup + buildah: new global option --cgroup-manager + Makefile: build with systemd when available + build(deps): bump github.com/fsouza/go-dockerclient from 1.7.7 to 1.7.8 + Bump c/common to v0.47.4 + Cirrus: Use updated VM images + conformance: add a few "replace-directory-with-symlink" tests + Bump back to v1.25.0-dev + +## v1.24.1 (2022-02-03) + + executor: Add support for inline --platform within Dockerfile + caps: fix buildah run --cap-add=all + Update vendor of openshift/imagebuilder + Bump version of containers/image and containers/common + Update vendor of containers/common + System tests: fix accidental vandalism of source dir + build(deps): bump github.com/containers/storage from 1.38.1 to 1.38.2 + imagebuildah.BuildDockerfiles(): create the jobs semaphore + build(deps): bump github.com/onsi/gomega from 1.18.0 to 1.18.1 + overlay: always honor mountProgram + overlay: move mount program invocation to separate function + overlay: move mount program lookup to separate function + Bump to v1.25.0-dev [NO TESTS NEEDED] + +## v1.24.0 (2022-01-26) + + Update vendor of containers/common + build(deps): bump github.com/golangci/golangci-lint in /tests/tools + Github-workflow: Report both failures and errors. + build(deps): bump github.com/containers/image/v5 from 5.18.0 to 5.19.0 + Update docs/buildah-build.1.md + [CI:DOCS] Fix typos and improve language + buildah bud --network add support for custom networks + Make pull commands be consistent + docs/buildah-build.1.md: don't imply that -v isn't just a RUN thing + build(deps): bump github.com/onsi/gomega from 1.17.0 to 1.18.0 + Vendor in latest containers/image + Run codespell on code + .github/dependabot.yml: add tests/tools go.mod + CI: rm git-validation, add GHA job to validate PRs + tests/tools: bump go-md2man to v2.0.1 + tests/tools/Makefile: simplify + tests/tools: bump onsi/ginkgo to v1.16.5 + vendor: bump c/common and others + mount: add support for custom upper and workdir with overlay mounts + linux: fix lookup for runtime + overlay: add MountWithOptions to API which extends support for advanced overlay + Allow processing of SystemContext from FlagSet + .golangci.yml: enable unparam linter + util/resolveName: rm bool return + tests/tools: bump golangci-lint + .gitignore: fixups + all: fix capabilities.NewPid deprecation warnings + bind/mount.go: fix linter comment + all: fix gosimple warning S1039 + tests/e2e/buildah_suite_test.go: fix gosimple warnings + imagebuildah/executor.go: fix gosimple warning + util.go: fix gosimple warning + build(deps): bump github.com/opencontainers/runc from 1.0.3 to 1.1.0 + Enable git-daemon tests + Allow processing of id options from FlagSet + Cirrus: Re-order tasks for more parallelism + Cirrus: Freshen VM images + Fix platform handling for empty os/arch values + Allow processing of network options from FlagSet + Fix permissions on secrets directory + Update containers/image and containers/common + bud.bats: use a local git daemon for the git protocol test + Allow processing of common options from FlagSet + Cirrus: Run int. tests in parallel with unit + vendor c/common + Fix default CNI paths + build(deps): bump github.com/fsouza/go-dockerclient from 1.7.6 to 1.7.7 + multi-stage: enable mounting stages across each other with selinux enabled + executor: Share selinux label of first stage with other stages in a build + buildkit: add from field to bind and cache mounts so images can be used as source + Use config.ProxyEnv from containers/common + use libnetwork from c/common for networking + setup the netns in the buildah parent process + build(deps): bump github.com/containerd/containerd from 1.5.8 to 1.5.9 + build(deps): bump github.com/fsouza/go-dockerclient from 1.7.4 to 1.7.6 + build: fix libsubid test + Allow callers to replace the ContainerSuffix + parse: allow parsing anomaly non-human value for memory control group + .cirrus: remove static_build from ci + stage_executor: re-use all possible layers from cache for squashed builds + build(deps): bump github.com/spf13/cobra from 1.2.1 to 1.3.0 + Allow rootless buildah to set resource limits on cgroup V2 + build(deps): bump github.com/docker/docker + tests: move buildkit mount tests files from TESTSDIR to TESTDIR before modification + build(deps): bump github.com/opencontainers/runc from 1.0.2 to 1.0.3 + Wire logger through to config + copier.Put: check for is-not-a-directory using lstat, not stat + Turn on rootless cgroupv2 tests + Grab all of the containers.conf settings for namespaces. + image: set MediaType in OCI manifests + copier: RemoveAll possibly-directories + Simple README fix + images: accept multiple filter with logical AND + build(deps): bump github.com/containernetworking/cni from 0.8.1 to 1.0.1 + UPdate vendor of container/storage + build(deps): bump github.com/onsi/gomega from 1.16.0 to 1.17.0 + build(deps): bump github.com/containers/image/v5 from 5.16.1 to 5.17.0 + Make LocalIP public function so Podman can use it + Fix UnsetEnv for buildah bud + Tests should rely only on static/unchanging images + run: ensure that stdio pipes are labeled correctly + build(deps): bump github.com/docker/docker + Cirrus: Bump up to Fedora 35 & Ubuntu 21.10 + chroot: don't use the generate default seccomp filter for unit tests + build(deps): bump github.com/containerd/containerd from 1.5.7 to 1.5.8 + ssh-agent: Increase timeout before we explicitly close connection + docs/tutorials: update + Clarify that manifest defaults to localhost as the registry name + "config": remove a stray bit of debug output + "commit": fix a flag typo + Fix an error message: unlocking vs locking + Expand the godoc for CommonBuildOptions.Secrets + chroot: accept an "rw" option + Add --unsetenv option to buildah commit and build + define.TempDirForURL(): show CombinedOutput when a command fails + config: support the variant field + rootless: do not bind mount /sys if not needed + Fix tutorial to specify command on buildah run line + build: history should not contain ARG values + docs: Use guaranteed path for go-md2man + run: honor --network=none from builder if nothing specified + networkpolicy: Should be enabled instead of default when explictly set + Add support for env var secret sources + build(deps): bump github.com/docker/docker + fix: another non-portable shebang + Rootless containers users should use additional groups + Support overlayfs path contains colon + Report ignorefile location when no content added + Add support for host.containers.internal in the /etc/hosts + build(deps): bump github.com/onsi/ginkgo from 1.16.4 to 1.16.5 + imagebuildah: fix nil deref + buildkit: add support for mount=type=cache + Default secret mode to 400 + [CI:DOCS] Include manifest example usage + docs: update buildah-from, buildah-pull 'platform' option compatibility notes + docs: update buildah-build 'platform' option compatibility notes + De-dockerize the man page as much as possible + [CI:DOCS] Touch up Containerfile man page to show ARG can be 1st + docs: Fix and Update Containerfile man page with supported mount types + mount: add tmpcopyup to tmpfs mount option + buildkit: Add support for --mount=type=tmpfs + build(deps): bump github.com/opencontainers/selinux from 1.8.5 to 1.9.1 + Fix command doc links in README.md + build(deps): bump github.com/containers/image/v5 from 5.16.0 to 5.16.1 + build: Add support for buildkit like --mount=type=bind + Bump containerd to v1.5.7 + build(deps): bump github.com/docker/docker + tests: stop pulling php, composer + Fix .containerignore link file + Cirrus: Fix defunct package metadata breaking cache + build(deps): bump github.com/containers/storage from 1.36.0 to 1.37.0 + buildah build: add --all-platforms + Add man page for Containerfile and .containerignore + Plumb the remote logger throughut Buildah + Replace fmt.Sprintf("%d", x) with strconv.Itoa(x) + Run: Cleanup run directory after every RUN step + build(deps): bump github.com/containers/common from 0.45.0 to 0.46.0 + Makefile: adjust -ldflags/-gcflags/-gccgoflags depending on the go implementation + Makefile: check for `-race` using `-mod=vendor` + imagebuildah: fix an attempt to write to a nil map + push: support to specify the compression format + conformance: allow test cases to specify dockerUseBuildKit + build(deps): bump github.com/containers/common from 0.44.1 to 0.45.0 + build(deps): bump github.com/containers/common from 0.44.0 to 0.44.1 + unmarshalConvertedConfig(): handle zstd compression + tests/copy/copy: wire up compression options + Update to github.com/vbauerster/mpb v7.1.5 + Add flouthoc to OWNERS + build: Add additional step nodes when labels are modified + Makefile: turn on race detection whenever it's available + conformance: add more tests for exclusion short-circuiting + Update VM Images + Drop prior-ubuntu testing + Bump to v1.24.0-dev + +## v1.23.0 (2021-09-13) + + Vendor in containers/common v0.44.0 + build(deps): bump github.com/containers/storage from 1.35.0 to 1.36.0 + Update 05-openshift-rootless-build.md + build(deps): bump github.com/opencontainers/selinux from 1.8.4 to 1.8.5 + .cirrus.yml: run cross_build_task on Big Sur + Makefile: update cross targets + Add support for rootless overlay mounts + Cirrus: Increase unit-test timeout + Docs: Clarify rmi w/ manifest/index use + build: mirror --authfile to filesystem if pointing to FD instead of file + Fix build with .git url with branch + manifest: rm should remove only manifests not referenced images. + vendor: bump c/common to v0.43.3-0.20210902095222-a7acc160fb25 + Avoid rehashing and noop compression writer + corrected man page section; .conf file to mention its man page + copy: add --max-parallel-downloads to tune that copy option + copier.Get(): try to avoid descending into directories + tag: Support tagging manifest list instead of resolving to images + Install new manpages to correct sections + conformance: tighten up exception specifications + Add support for libsubid + Add epoch time field to buildah images + Fix ownership of /home/build/.local/share/containers + build(deps): bump github.com/containers/image/v5 from 5.15.2 to 5.16.0 + Rename bud to build, while keeping an alias for to bud. + Replace golang.org/x/crypto/ssh/terminal with golang.org/x/term + build(deps): bump github.com/opencontainers/runc from 1.0.1 to 1.0.2 + build(deps): bump github.com/onsi/gomega from 1.15.0 to 1.16.0 + build(deps): bump github.com/fsouza/go-dockerclient from 1.7.3 to 1.7.4 + build(deps): bump github.com/containers/common from 0.43.1 to 0.43.2 + Move DiscoverContainerfile to pkg/util directory + build(deps): bump github.com/containers/image/v5 from 5.15.1 to 5.15.2 + Remove some references to Docker + build(deps): bump github.com/containers/image/v5 from 5.15.0 to 5.15.1 + imagebuildah: handle --manifest directly + build(deps): bump github.com/containers/common from 0.42.1 to 0.43.1 + build(deps): bump github.com/opencontainers/selinux from 1.8.3 to 1.8.4 + executor: make sure imageMap is updated with terminatedStage + tests/serve/serve.go: use a kernel-assigned port + Bump go for vendor-in-container from 1.13 to 1.16 + imagebuildah: move multiple-platform building internal + Adds GenerateStructure helper function to support rootfs-overlay. + Run codespell to fix spelling + Implement SSH RUN mount + build(deps): bump github.com/onsi/gomega from 1.14.0 to 1.15.0 + Fix resolv.conf content with run --net=private + run: fix nil deref using the option's logger + build(deps): bump github.com/containerd/containerd from 1.5.1 to 1.5.5 + make vendor-in-container + bud: teach --platform to take a list + set base-image annotations + build(deps): bump github.com/opencontainers/selinux from 1.8.2 to 1.8.3 + [CI:DOCS] Fix CHANGELOG.md + Bump to v1.23.0-dev [NO TESTS NEEDED] + Accept repositories on login/logout + +## v1.22.0 (2021-08-02) + c/image, c/storage, c/common vendor before Podman 3.3 release + WIP: tests: new assert() + Proposed patch for 3399 (shadowutils) + Fix handling of --restore shadow-utils + build(deps): bump github.com/containers/image/v5 from 5.13.2 to 5.14.0 + runtime-flag (debug) test: handle old & new runc + build(deps): bump github.com/containers/storage from 1.32.6 to 1.33.0 + Allow dst and destination for target in secret mounts + Multi-arch: Always push updated version-tagged img + Add a few tests on cgroups V2 + imagebuildah.stageExecutor.prepare(): remove pseudonym check + refine dangling filter + Chown with environment variables not set should fail + Just restore protections of shadow-utils + build(deps): bump github.com/opencontainers/runc from 1.0.0 to 1.0.1 + Remove specific kernel version number requirement from install.md + Multi-arch image workflow: Make steps generic + chroot: fix environment value leakage to intermediate processes + Update nix pin with `make nixpkgs` + buildah source - create and manage source images + Update cirrus-cron notification GH workflow + Reuse code from containers/common/pkg/parse + Cirrus: Freshen VM images + build(deps): bump github.com/containers/storage from 1.32.5 to 1.32.6 + Fix excludes exception begining with / or ./ + Fix syntax for --manifest example + build(deps): bump github.com/onsi/gomega from 1.13.0 to 1.14.0 + vendor containers/common@main + Cirrus: Drop dependence on fedora-minimal + Adjust conformance-test error-message regex + Workaround appearance of differing debug messages + Cirrus: Install docker from package cache + build(deps): bump github.com/containers/ocicrypt from 1.1.1 to 1.1.2 + Switch rusagelogfile to use options.Out + build(deps): bump github.com/containers/storage from 1.32.4 to 1.32.5 + Turn stdio back to blocking when command finishes + Add support for default network creation + Cirrus: Updates for master->main rename + Change references from master to main + Add `--env` and `--workingdir` flags to run command + build(deps): bump github.com/opencontainers/runc + [CI:DOCS] buildah bud: spelling --ignore-file requires parameter + [CI:DOCS] push/pull: clarify supported transports + Remove unused function arguments + Create mountOptions for mount command flags + Extract version command implementation to function + Add --json flags to `mount` and `version` commands + build(deps): bump github.com/containers/storage from 1.32.2 to 1.32.3 + build(deps): bump github.com/containers/common from 0.40.0 to 0.40.1 + copier.Put(): set xattrs after ownership + buildah add/copy: spelling + build(deps): bump github.com/containers/common from 0.39.0 to 0.40.0 + buildah copy and buildah add should support .containerignore + Remove unused util.StartsWithValidTransport + Fix documentation of the --format option of buildah push + Don't use alltransports.ParseImageName with known transports + build(deps): bump github.com/containers/image/v5 from 5.13.0 to 5.13.1 + man pages: clarify `rmi` removes dangling parents + tests: make it easer to override the location of the copy helper + build(deps): bump github.com/containers/image/v5 from 5.12.0 to 5.13.0 + [CI:DOCS] Fix links to c/image master branch + imagebuildah: use the specified logger for logging preprocessing warnings + Fix copy into workdir for a single file + Fix docs links due to branch rename + Update nix pin with `make nixpkgs` + build(deps): bump github.com/fsouza/go-dockerclient from 1.7.2 to 1.7.3 + build(deps): bump github.com/opencontainers/selinux from 1.8.1 to 1.8.2 + build(deps): bump go.etcd.io/bbolt from 1.3.5 to 1.3.6 + build(deps): bump github.com/containers/storage from 1.32.1 to 1.32.2 + build(deps): bump github.com/mattn/go-shellwords from 1.0.11 to 1.0.12 + build(deps): bump github.com/onsi/ginkgo from 1.16.3 to 1.16.4 + fix(docs): typo + Move to v1.22.0-dev + Fix handling of auth.json file while in a user namespace + Add rusage-logfile flag to optionally send rusage to a file + imagebuildah: redo step logging + build(deps): bump github.com/onsi/ginkgo from 1.16.2 to 1.16.3 + build(deps): bump github.com/containers/storage from 1.32.0 to 1.32.1 + Add volumes to make running buildah within a container easier + build(deps): bump github.com/onsi/gomega from 1.12.0 to 1.13.0 + Add and use a "copy" helper instead of podman load/save + Bump github.com/containers/common from 0.38.4 to 0.39.0 + containerImageRef/containerImageSource: don't buffer uncompressed layers + containerImageRef(): squashed images have no parent images + Sync. workflow across skopeo, buildah, and podman + Bump github.com/containers/storage from 1.31.1 to 1.31.2 + Bump github.com/opencontainers/runc from 1.0.0-rc94 to 1.0.0-rc95 + Bump to v1.21.1-dev [NO TESTS NEEDED] + +## v1.21.0 (2021-05-19) + Don't blow up if cpp detects errors + Vendor in containers/common v0.38.4 + Remove 'buildah run --security-opt' from completion + update c/common + Fix handling of --default-mounts-file + update vendor of containers/storage v1.31.1 + Bump github.com/containers/storage from 1.30.3 to 1.31.0 + Send logrus messages back to caller when building + github: Fix bad repo. ref in workflow config + Check earlier for bad image tags name + buildah bud: fix containers/podman/issues/10307 + Bump github.com/containers/storage from 1.30.1 to 1.30.3 + Cirrus: Support [CI:DOCS] test skipping + Notification email for cirrus-cron build failures + Bump github.com/opencontainers/runc from 1.0.0-rc93 to 1.0.0-rc94 + Fix race condition + Fix copy race while walking paths + Preserve ownership of lower directory when doing an overlay mount + Bump github.com/onsi/gomega from 1.11.0 to 1.12.0 + Update nix pin with `make nixpkgs` + codespell cleanup + Multi-arch github-action workflow unification + Bump github.com/containers/image/v5 from 5.11.1 to 5.12.0 + Bump github.com/onsi/ginkgo from 1.16.1 to 1.16.2 + imagebuildah: ignore signatures when tagging images + update to latest libimage + Bump github.com/containers/common from 0.37.0 to 0.37.1 + Bump github.com/containers/storage from 1.30.0 to 1.30.1 + Upgrade to GitHub-native Dependabot + Document location of auth.json file if XDG_RUNTIME_DIR is not set + run.bats: fix flake in run-user test + Cirrus: Update F34beta -> F34 + pr-should-include-tests: try to make work in buildah + runUsingRuntime: when relaying error from the runtime, mention that + Run(): avoid Mkdir() into the rootfs + imagebuildah: replace archive with chrootarchive + imagebuildah.StageExecutor.volumeCacheSaveVFS(): set up bind mounts + conformance: use :Z with transient mounts when SELinux is enabled + bud.bats: fix a bats warning + imagebuildah: create volume directories when using overlays + imagebuildah: drop resolveSymlink() + namespaces test - refactoring and cleanup + Refactor 'idmapping' system test + Cirrus: Update Ubuntu images to 21.04 + Tiny fixes in bud system tests + Add compabitility wrappers for removed packages + Fix expected message at pulling image + Fix system tests of 'bud' subcommand + [CI:DOCS] Update steps for CentOS runc users + Add support for secret mounts + Add buildah manifest rm command + restore push/pull and util API + [CI:DOCS] Remove older distro docs + Rename rhel secrets to subscriptions + vendor in openshift/imagebuilder + Remove buildah bud --loglevel ... + use new containers/common/libimage package + Fix copier when using globs + Test namespace flags of 'bud' subcommand + Add system test of 'bud' subcommand + Output names of multiple tags in buildah bud + push to docker test: don't get fooled by podman + copier: add Remove() + build(deps): bump github.com/containers/image/v5 from 5.10.5 to 5.11.1 + Restore log timestamps + Add system test of 'buildah help' with a tiny fix + tests: copy.bats: fix infinite hang + Do not force hard code to crun in rootless mode + build(deps): bump github.com/openshift/imagebuilder from 1.2.0 to 1.2.1 + build(deps): bump github.com/containers/ocicrypt from 1.1.0 to 1.1.1 + build(deps): bump github.com/containers/common from 0.35.4 to 0.36.0 + Fix arg missing warning in bud + Check without flag in 'from --cgroup-parent' test + Minor fixes to Buildah as a library tutorial documentation + Add system test of 'buildah version' for packaged buildah + Add a few system tests of 'buildah from' + Log the final error with %+v at logging level "trace" + copier: add GetOptions.NoCrossDevice + Update nix pin with `make nixpkgs` + Bump to v1.20.2-dev + +## v1.20.1 (2021-04-13) + Run container with isolation type set at 'from' + bats helpers.bash - minor refactoring + Bump containers/storage vendor to v1.29.0 + build(deps): bump github.com/onsi/ginkgo from 1.16.0 to 1.16.1 + Cirrus: Update VMs w/ F34beta + CLI add/copy: add a --from option + build(deps): bump github.com/onsi/ginkgo from 1.15.2 to 1.16.0 + Add authentication system tests for 'commit' and 'bud' + fix local image lookup for custom platform + Double-check existence of OCI runtimes + Cirrus: Make use of shared get_ci_vm container + Add system tests of "buildah run" + Update nix pin with `make nixpkgs` + Remove some stuttering on returns errors + Setup alias for --tty to --terminal + Add conformance tests for COPY /... + Put a few more minutes on the clock for the CI conformance test + Add a conformance test for COPY --from $symlink + Add conformance tests for COPY "" + Check for symlink in builtin volume + Sort all mounts by destination directory + System-test cleanup + Export parse.Platform string to be used by podman-remote + blobcache: fix sequencing error + build(deps): bump github.com/containers/common from 0.35.3 to 0.35.4 + Fix URL in demos/buildah_multi_stage.sh + Add a few system tests + [NO TESTS NEEDED] Use --recurse-modules when building git context + Bump to v1.20.1-dev + +## v1.20.0 (2021-03-25) + * vendor in containers/storage v1.28.1 + * build(deps): bump github.com/containers/common from 0.35.2 to 0.35.3 + * tests: prefetch: use buildah, not podman, for pulls + * Use faster way to check image tag existence during multi-arch build + * Add information about multi-arch images to the Readme + * COPY --chown: expand the conformance test + * pkg/chrootuser: use a bufio.Scanner + * [CI:DOCS] Fix rootful typo in docs + * build(deps): bump github.com/onsi/ginkgo from 1.15.1 to 1.15.2 + * Add documentation and testing for .containerignore + * build(deps): bump github.com/sirupsen/logrus from 1.8.0 to 1.8.1 + * build(deps): bump github.com/hashicorp/go-multierror from 1.1.0 to 1.1.1 + * Lookup Containerfile if user specifies a directory + * Add Tag format placeholder to docs + * copier: ignore sockets + * image: propagate errors from extractRootfs + * Remove system test of 'buildah containers -a' + * Clarify userns options are usable only as root in man pages + * Fix system test of 'containers -a' + * Remove duplicated code in addcopy + * build(deps): bump github.com/onsi/ginkgo from 1.15.0 to 1.15.1 + * build(deps): bump github.com/onsi/gomega from 1.10.5 to 1.11.0 + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.1 to 1.7.2 + * Update multi-arch buildah build setup with new logic + * Update nix pin with `make nixpkgs` + * overlay.bats: fix the "overlay source permissions" test + * imagebuildah: use overlay for volumes when using overlay + * Make PolicyMap and PullPolicy names align + * copier: add GetOptions.IgnoreUnreadable + * Check local image to match system context + * fix: Containerfiles - smaller set of userns u/gids + * Set upperdir permissions based on source + * Shrink the vendoring size of pkc/cli + * Clarify image name match failure message + * ADD/COPY: create the destination directory first, chroot to it + * copier.GetOptions: add NoDerefSymLinks + * copier: add an Eval function + * Update system test for 'from --cap-add/drop' + * copier: fix a renaming bug + * copier: return child process stderr if we can't JSON decode the response + * Add some system tests + * build(deps): bump github.com/containers/storage from 1.26.0 to 1.27.0 + * complement add/copy --chmod documentation + * buildah login and logout, do not need to enter user namespace + * Add multi-arch image build + * chmod/chown added/fixed in bash completions + * OWNERS: add @lsm5 + * buildah add/copy --chmod dockerfile implementation + * bump github.com/openshift/imagebuilder from 1.1.8 to 1.2.0 + * buildah add/copy --chmod cli implementation for files and urls + * Make sure we set the buildah version label + * Isolation strings, should match user input + * [CI:DOCS] buildah-from.md: remove dup arch,os + * build(deps): bump github.com/containers/image/v5 from 5.10.2 to 5.10.3 + * Cirrus: Temp. disable prior-fedora (F32) testing + * pr-should-include-tests: recognized "renamed" tests + * build(deps): bump github.com/sirupsen/logrus from 1.7.0 to 1.8.0 + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.0 to 1.7.1 + * build(deps): bump github.com/containers/common from 0.34.2 to 0.35.0 + * Fix reaping of stages with no instructions + * add stale bot + * Add base image name to comment + * build(deps): bump github.com/spf13/cobra from 1.1.1 to 1.1.3 + * Don't fail copy to emptydir + * buildah: use volatile containers + * vendor: update containers/storage + * Eliminate the use of containers/building import in pkg subdirs + * Add more support for removing config + * Improve messages about --cache-from not being supported + * Revert patch to allow COPY/ADD of empty dirs. + * Don't fail copy to emptydir + * Fix tutorial for rootless mode + * Fix caching layers with build args + * Vendor in containers/image v5.10.2 + * build(deps): bump github.com/containers/common from 0.34.0 to 0.34.2 + * build(deps): bump github.com/onsi/ginkgo from 1.14.2 to 1.15.0 + * 'make validate': require PRs to include tests + * build(deps): bump github.com/onsi/gomega from 1.10.4 to 1.10.5 + * build(deps): bump github.com/containers/storage from 1.24.5 to 1.25.0 + * Use chown function for U volume flag from containers/common repository + * --iidfile: print hash prefix + * bump containernetworking/cni to v0.8.1 - fix for CVE-2021-20206 + * run: fix check for host pid namespace + * Finish plumbing for buildah bud --manifest + * buildah manifest add localimage should work + * Stop testing directory permissions with latest docker + * Fix build arg check + * build(deps): bump github.com/containers/ocicrypt from 1.0.3 to 1.1.0 + * [ci:docs] Fix man page for buildah push + * Update nix pin with `make nixpkgs` + * Bump to containers/image v5.10.1 + * Rebuild layer if a change in ARG is detected + * Bump golang.org/x/crypto to the latest + * Add Ashley and Urvashi to Approvers + * local image lookup by digest + * Use build-arg ENV val from local environment if set + * Pick default OCI Runtime from containers.conf + * Added required devel packages + * Cirrus: Native OSX Build + * Cirrus: Two minor cleanup items + * Workaround for RHEL gating test failure + * build(deps): bump github.com/stretchr/testify from 1.6.1 to 1.7.0 + * build(deps): bump github.com/mattn/go-shellwords from 1.0.10 to 1.0.11 + * Reset upstream branch to dev version + * If destination does not exists, do not throw error + +## v1.19.0 (2021-01-08) + Update vendor of containers/storage and containers/common + Buildah inspect should be able to inspect manifests + Make buildah push support pushing manifests lists and digests + Fix handling of TMPDIR environment variable + Add support for --manifest flags + Upper directory should match mode of destination directory + Only grab the OS, Arch if the user actually specified them + Use --arch and --os and --variant options to select architecture and os + Cirrus: Track libseccomp and golang version + copier.PutOptions: add an "IgnoreDevices" flag + fix: `rmi --prune` when parent image is in store. + build(deps): bump github.com/containers/storage from 1.24.3 to 1.24.4 + build(deps): bump github.com/containers/common from 0.31.1 to 0.31.2 + Allow users to specify stdin into containers + Drop log message on failure to mount on /sys file systems to info + Spelling + SELinux no longer requires a tag. + build(deps): bump github.com/opencontainers/selinux from 1.6.0 to 1.8.0 + build(deps): bump github.com/containers/common from 0.31.0 to 0.31.1 + Update nix pin with `make nixpkgs` + Switch references of /var/run -> /run + Allow FROM to be overriden with from option + copier: don't assume we can chroot() on Unixy systems + copier: add PutOptions.NoOverwriteDirNonDir, Get/PutOptions.Rename + copier: handle replacing directories with not-directories + copier: Put: skip entries with zero-length names + build(deps): bump github.com/containers/storage from 1.24.2 to 1.24.3 + Add U volume flag to chown source volumes + Turn off PRIOR_UBUNTU Test until vm is updated + pkg, cli: rootless uses correct isolation + build(deps): bump github.com/onsi/gomega from 1.10.3 to 1.10.4 + update installation doc to reflect current status + Move away from using docker.io + enable short-name aliasing + build(deps): bump github.com/containers/storage from 1.24.1 to 1.24.2 + build(deps): bump github.com/containers/common from 0.30.0 to 0.31.0 + Throw errors when using bogus --network flags + pkg/supplemented test: replace our null blobinfocache + build(deps): bump github.com/containers/common from 0.29.0 to 0.30.0 + inserts forgotten quotation mark + Not prefer use local image create/add manifest + Add container information to .containerenv + Add --ignorefile flag to use alternate .dockerignore flags + Add a source debug build + Fix crash on invalid filter commands + build(deps): bump github.com/containers/common from 0.27.0 to 0.29.0 + Switch to using containers/common pkg's + fix: non-portable shebang #2812 + Remove copy/paste errors that leaked `Podman` into man pages. + Add suggests cpp to spec file + Apply suggestions from code review + update docs for debian testing and unstable + imagebuildah: disable pseudo-terminals for RUN + Compute diffID for mapped-layer at creating image source + intermediateImageExists: ignore images whose history we can't read + Bump to v1.19.0-dev + build(deps): bump github.com/containers/common from 0.26.3 to 0.27.0 + +## v1.18.0 (2020-11-16) + Fix testing error caused by simultanious merge + Vendor in containers/storage v1.24.0 + short-names aliasing + Add --policy flag to buildah pull + Stop overwrapping and stuttering + copier.Get(): ignore ENOTSUP/ENOSYS when listing xattrs + Run: don't forcibly disable UTS namespaces in rootless mode + test: ensure non-directory in a Dockerfile path is handled correctly + Add a few tests for `pull` command + Fix buildah config --cmd to handle array + build(deps): bump github.com/containers/storage from 1.23.8 to 1.23.9 + Fix NPE when Dockerfile path contains non-directory entries + Update buildah bud man page from podman build man page + Move declaration of decryption-keys to common cli + Run: correctly call copier.Mkdir + util: digging UID/GID out of os.FileInfo should work on Unix + imagebuildah.getImageTypeAndHistoryAndDiffIDs: cache results + Verify userns-uid-map and userns-gid-map input + Use CPP, CC and flags in dep check scripts + Avoid overriding LDFLAGS in Makefile + ADD: handle --chown on URLs + Update nix pin with `make nixpkgs` + (*Builder).Run: MkdirAll: handle EEXIST error + copier: try to force loading of nsswitch modules before chroot() + fix MkdirAll usage + build(deps): bump github.com/containers/common from 0.26.2 to 0.26.3 + build(deps): bump github.com/containers/storage from 1.23.7 to 1.23.8 + Use osusergo build tag for static build + imagebuildah: cache should take image format into account + Bump to v1.18.0-dev + +## v1.17.0 (2020-10-29) + Handle cases where other tools mount/unmount containers + overlay.MountReadOnly: support RO overlay mounts + overlay: use fusermount for rootless umounts + overlay: fix umount + Switch default log level of Buildah to Warn. Users need to see these messages + Drop error messages about OCI/Docker format to Warning level + build(deps): bump github.com/containers/common from 0.26.0 to 0.26.2 + tests/testreport: adjust for API break in storage v1.23.6 + build(deps): bump github.com/containers/storage from 1.23.5 to 1.23.7 + build(deps): bump github.com/fsouza/go-dockerclient from 1.6.5 to 1.6.6 + copier: put: ignore Typeflag="g" + Use curl to get repo file (fix #2714) + build(deps): bump github.com/containers/common from 0.25.0 to 0.26.0 + build(deps): bump github.com/spf13/cobra from 1.0.0 to 1.1.1 + Remove docs that refer to bors, since we're not using it + Buildah bud should not use stdin by default + bump containerd, docker, and golang.org/x/sys + Makefile: cross: remove windows.386 target + copier.copierHandlerPut: don't check length when there are errors + Stop excessive wrapping + CI: require that conformance tests pass + bump(github.com/openshift/imagebuilder) to v1.1.8 + Skip tlsVerify insecure BUILD_REGISTRY_SOURCES + Fix build path wrong https://github.com/containers/podman/issues/7993 + refactor pullpolicy to avoid deps + build(deps): bump github.com/containers/common from 0.24.0 to 0.25.0 + CI: run gating tasks with a lot more memory + ADD and COPY: descend into excluded directories, sometimes + copier: add more context to a couple of error messages + copier: check an error earlier + copier: log stderr output as debug on success + Update nix pin with `make nixpkgs` + Set directory ownership when copied with ID mapping + build(deps): bump github.com/sirupsen/logrus from 1.6.0 to 1.7.0 + build(deps): bump github.com/containers/common from 0.23.0 to 0.24.0 + Cirrus: Remove bors artifacts + Sort build flag definitions alphabetically + ADD: only expand archives at the right time + Remove configuration for bors + Shell Completion for podman build flags + Bump c/common to v0.24.0 + New CI check: xref --help vs man pages + CI: re-enable several linters + Move --userns-uid-map/--userns-gid-map description into buildah man page + add: preserve ownerships and permissions on ADDed archives + Makefile: tweak the cross-compile target + Bump containers/common to v0.23.0 + chroot: create bind mount targets 0755 instead of 0700 + Change call to Split() to safer SplitN() + chroot: fix handling of errno seccomp rules + build(deps): bump github.com/containers/image/v5 from 5.5.2 to 5.6.0 + Add In Progress section to contributing + integration tests: make sure tests run in ${topdir}/tests + Run(): ignore containers.conf's environment configuration + Warn when setting healthcheck in OCI format + Cirrus: Skip git-validate on branches + tools: update git-validation to the latest commit + tools: update golangci-lint to v1.18.0 + Add a few tests of push command + Add(): fix handling of relative paths with no ContextDir + build(deps): bump github.com/containers/common from 0.21.0 to 0.22.0 + Lint: Use same linters as podman + Validate: reference HEAD + Fix buildah mount to display container names not ids + Update nix pin with `make nixpkgs` + Add missing --format option in buildah from man page + Fix up code based on codespell + build(deps): bump github.com/openshift/imagebuilder from 1.1.6 to 1.1.7 + build(deps): bump github.com/containers/storage from 1.23.4 to 1.23.5 + Improve buildah completions + Cirrus: Fix validate commit epoch + Fix bash completion of manifest flags + Uniform some man pages + Update Buildah Tutorial to address BZ1867426 + Update bash completion of `manifest add` sub command + copier.Get(): hard link targets shouldn't be relative paths + build(deps): bump github.com/onsi/gomega from 1.10.1 to 1.10.2 + Pass timestamp down to history lines + Timestamp gets updated everytime you inspect an image + bud.bats: use absolute paths in newly-added tests + contrib/cirrus/lib.sh: don't use CN for the hostname + tests: Add some tests + Update `manifest add` man page + Extend flags of `manifest add` + build(deps): bump github.com/containers/storage from 1.23.3 to 1.23.4 + build(deps): bump github.com/onsi/ginkgo from 1.14.0 to 1.14.1 + Bump to v1.17.0-dev + CI: expand cross-compile checks + +## v1.16.0 (2020-09-03) + fix build on 32bit arches + containerImageRef.NewImageSource(): don't always force timestamps + Add fuse module warning to image readme + Heed our retry delay option values when retrying commit/pull/push + Switch to containers/common for seccomp + Use --timestamp rather then --omit-timestamp + docs: remove outdated notice + docs: remove outdated notice + build-using-dockerfile: add a hidden --log-rusage flag + build(deps): bump github.com/containers/image/v5 from 5.5.1 to 5.5.2 + Discard ReportWriter if user sets options.Quiet + build(deps): bump github.com/containers/common from 0.19.0 to 0.20.3 + Fix ownership of content copied using COPY --from + newTarDigester: zero out timestamps in tar headers + Update nix pin with `make nixpkgs` + bud.bats: correct .dockerignore integration tests + Use pipes for copying + run: include stdout in error message + run: use the correct error for errors.Wrapf + copier: un-export internal types + copier: add Mkdir() + in_podman: don't get tripped up by $CIRRUS_CHANGE_TITLE + docs/buildah-commit.md: tweak some wording, add a --rm example + imagebuildah: don’t blank out destination names when COPYing + Replace retry functions with common/pkg/retry + StageExecutor.historyMatches: compare timestamps using .Equal + Update vendor of containers/common + Fix errors found in coverity scan + Change namespace handling flags to better match podman commands + conformance testing: ignore buildah.BuilderIdentityAnnotation labels + Vendor in containers/storage v1.23.0 + Add buildah.IsContainer interface + Avoid feeding run_buildah to pipe + fix(buildahimage): add xz dependency in buildah image + Bump github.com/containers/common from 0.15.2 to 0.18.0 + Howto for rootless image building from OpenShift + Add --omit-timestamp flag to buildah bud + Update nix pin with `make nixpkgs` + Shutdown storage on failures + Handle COPY --from when an argument is used + Bump github.com/seccomp/containers-golang from 0.5.0 to 0.6.0 + Cirrus: Use newly built VM images + Bump github.com/opencontainers/runc from 1.0.0-rc91 to 1.0.0-rc92 + Enhance the .dockerignore man pages + conformance: add a test for COPY from subdirectory + fix bug manifest inspct + Add documentation for .dockerignore + Add BuilderIdentityAnnotation to identify buildah version + DOC: Add quay.io/containers/buildah image to README.md + Update buildahimages readme + fix spelling mistake in "info" command result display + Don't bind /etc/host and /etc/resolv.conf if network is not present + blobcache: avoid an unnecessary NewImage() + Build static binary with `buildGoModule` + copier: split StripSetidBits into StripSetuidBit/StripSetgidBit/StripStickyBit + tarFilterer: handle multiple archives + Fix a race we hit during conformance tests + Rework conformance testing + Update 02-registries-repositories.md + test-unit: invoke cmd/buildah tests with --flags + parse: fix a type mismatch in a test + Fix compilation of tests/testreport/testreport + build.sh: log the version of Go that we're using + test-unit: increase the test timeout to 40/45 minutes + Add the "copier" package + Fix & add notes regarding problematic language in codebase + Add dependency on github.com/stretchr/testify/require + CompositeDigester: add the ability to filter tar streams + BATS tests: make more robust + vendor golang.org/x/text@v0.3.3 + Switch golang 1.12 to golang 1.13 + imagebuildah: wait for stages that might not have even started yet + chroot, run: not fail on bind mounts from /sys + chroot: do not use setgroups if it is blocked + Set engine env from containers.conf + imagebuildah: return the right stage's image as the "final" image + Fix a help string + Deduplicate environment variables + switch containers/libpod to containers/podman + Bump github.com/containers/ocicrypt from 1.0.2 to 1.0.3 + Bump github.com/opencontainers/selinux from 1.5.2 to 1.6.0 + Mask out /sys/dev to prevent information leak + linux: skip errors from the runtime kill + Mask over the /sys/fs/selinux in mask branch + Add VFS additional image store to container + tests: add auth tests + Allow "readonly" as alias to "ro" in mount options + Ignore OS X specific consistency mount option + Bump github.com/onsi/ginkgo from 1.13.0 to 1.14.0 + Bump github.com/containers/common from 0.14.0 to 0.15.2 + Rootless Buildah should default to IsolationOCIRootless + imagebuildah: fix inheriting multi-stage builds + Make imagebuildah.BuildOptions.Architecture/OS optional + Make imagebuildah.BuildOptions.Jobs optional + Resolve a possible race in imagebuildah.Executor.startStage() + Switch scripts to use containers.conf + Bump openshift/imagebuilder to v1.1.6 + Bump go.etcd.io/bbolt from 1.3.4 to 1.3.5 + buildah, bud: support --jobs=N for parallel execution + executor: refactor build code inside new function + Add bud regression tests + Cirrus: Fix missing htpasswd in registry img + docs: clarify the 'triples' format + CHANGELOG.md: Fix markdown formatting + Add nix derivation for static builds + Bump to v1.16.0-dev + version centos7 for compatible + +## v1.15.0 (2020-06-17) + Bump github.com/containers/common from 0.12.0 to 0.13.1 + Bump github.com/containers/storage from 1.20.1 to 1.20.2 + Bump github.com/seccomp/containers-golang from 0.4.1 to 0.5.0 + Bump github.com/stretchr/testify from 1.6.0 to 1.6.1 + Bump github.com/opencontainers/runc from 1.0.0-rc9 to 1.0.0-rc90 + Add CVE-2020-10696 to CHANGELOG.md and changelog.txt + Bump github.com/stretchr/testify from 1.5.1 to 1.6.0 + Bump github.com/onsi/ginkgo from 1.12.2 to 1.12.3 + Vendor in containers/common v0.12.0 + fix lighttpd example + Vendor in new go.etcd.io/bbolt + Bump github.com/onsi/ginkgo from 1.12.1 to 1.12.2 + Bump imagebuilder for ARG fix + Bump github.com/containers/common from 0.11.2 to 0.11.4 + remove dependency on openshift struct + Warn on unset build arguments + vendor: update seccomp/containers-golang to v0.4.1 + Ammended docs + Updated docs + clean up comments + update exit code for tests + Implement commit for encryption + implementation of encrypt/decrypt push/pull/bud/from + fix resolve docker image name as transport + Bump github.com/opencontainers/go-digest from 1.0.0-rc1 to 1.0.0 + Bump github.com/onsi/ginkgo from 1.12.0 to 1.12.1 + Bump github.com/containers/storage from 1.19.1 to 1.19.2 + Bump github.com/containers/image/v5 from 5.4.3 to 5.4.4 + Add preliminary profiling support to the CLI + Bump github.com/containers/common from 0.10.0 to 0.11.2 + Evaluate symlinks in build context directory + fix error info about get signatures for containerImageSource + Add Security Policy + Cirrus: Fixes from review feedback + Bump github.com/containers/storage from 1.19.0 to 1.19.1 + Bump github.com/sirupsen/logrus from 1.5.0 to 1.6.0 + imagebuildah: stages shouldn't count as their base images + Update containers/common v0.10.0 + Bump github.com/fsouza/go-dockerclient from 1.6.4 to 1.6.5 + Add registry to buildahimage Dockerfiles + Cirrus: Use pre-installed VM packages + F32 + Cirrus: Re-enable all distro versions + Cirrus: Update to F31 + Use cache images + golangci-lint: Disable gosimple + Lower number of golangci-lint threads + Fix permissions on containers.conf + Don't force tests to use runc + Bump github.com/containers/common from 0.9.1 to 0.9.5 + Return exit code from failed containers + Bump github.com/containers/storage from 1.18.2 to 1.19.0 + Bump github.com/containers/common from 0.9.0 to 0.9.1 + cgroup_manager should be under [engine] + Use c/common/pkg/auth in login/logout + Cirrus: Temporarily disable Ubuntu 19 testing + Add containers.conf to stablebyhand build + Update gitignore to exclude test Dockerfiles + Bump github.com/fsouza/go-dockerclient from 1.6.3 to 1.6.4 + Bump github.com/containers/common from 0.8.1 to 0.9.0 + Bump back to v1.15.0-dev + Remove warning for systemd inside of container + +## v1.14.8 (2020-04-09) + Run (make vendor) + Run (make -C tests/tools vendor) + Run (go mod tidy) before (go mod vendor) again + Fix (make vendor) + Bump validation + Bump back to v1.15.0-dev + +## v1.14.7 (2020-04-07) + Bump github.com/containers/image/v5 from 5.3.1 to 5.4.3 + make vendor: run `tidy` after `vendor` + Do not skip the directory when the ignore pattern matches + Bump github.com/containers/common from 0.7.0 to 0.8.1 + Downgrade siruspen/logrus from 1.4.2 + Fix errorf conventions + dockerignore tests : remove symlinks, rework + Bump back to v1.15.0-dev + +## v1.14.6 (2020-04-02) + bud.bats - cleanup, refactoring + vendor in latest containers/storage 1.18.0 and containers/common v0.7.0 + Bump github.com/spf13/cobra from 0.0.6 to 0.0.7 + Bump github.com/containers/storage from 1.16.5 to 1.17.0 + Bump github.com/containers/image/v5 from 5.2.1 to 5.3.1 + Fix Amazon install step + Bump back to v1.15.0-dev + Fix bud-build-arg-cache test + Make image history work correctly with new args handling + Don't add args to the RUN environment from the Builder + Update github.com/openshift/imagebuilder to v1.1.4 + Add .swp files to .gitignore + +## v1.14.5 (2020-03-26) + revert #2246 FIPS mode change + Bump back to v1.15.0-dev + image with dup layers: we now have one on quay + digest test : make more robust + +## v1.14.4 (2020-03-25) + Fix fips-mode check for RHEL8 boxes + Fix potential CVE in tarfile w/ symlink (Edit 02-Jun-2020: Addresses CVE-2020-10696) + Fix .dockerignore with globs and ! commands + update install steps for Amazon Linux 2 + Bump github.com/openshift/imagebuilder from 1.1.2 to 1.1.3 + Add comment for RUN command in volume ownership test + Run stat command directly for volume ownership test + vendor in containers/common v0.6.1 + Cleanup go.sum + Bump back to v1.15.0-dev + +## v1.14.3 (2020-03-17) + Update containers/storage to v1.16.5 + Bump github.com/containers/storage from 1.16.2 to 1.16.4 + Bump github.com/openshift/imagebuilder from 1.1.1 to 1.1.2 + Update github.com/openshift/imagebuilder vendoring + Update unshare man page to fix script example + Fix compilation errors on non linux platforms + Bump containers/common and opencontainers/selinux versions + Add tests for volume ownership + Preserve volume uid and gid through subsequent commands + Fix FORWARD_NULL errors found by Coverity + Bump github.com/containers/storage from 1.16.1 to 1.16.2 + Fix errors found by codespell + Bump back to v1.15.0-dev + Add Pull Request Template + +## v1.14.2 (2020-03-03) + Add Buildah pull request template + Bump to containers/storage v1.16.1 + run_linux: fix tight loop if file is not pollable + Bump github.com/opencontainers/selinux from 1.3.2 to 1.3.3 + Bump github.com/containers/common from 0.4.1 to 0.4.2 + Bump back to v1.15.0-dev + Add Containerfile to build a versioned stable image on quay.io + +## v1.14.1 (2020-02-27) + Search for local runtime per values in containers.conf + Set correct ownership on working directory + BATS : in teardown, umount stale mounts + Bump github.com/spf13/cobra from 0.0.5 to 0.0.6 + Bump github.com/fsouza/go-dockerclient from 1.6.1 to 1.6.3 + Bump github.com/stretchr/testify from 1.4.0 to 1.5.1 + Replace unix with syscall to allow vendoring into libpod + Update to containers/common v0.4.1 + Improve remote manifest retrieval + Fix minor spelling errors in containertools README + Clear the right variable in buildahimage + Correct a couple of incorrect format specifiers + Update to containers/common v0.3.0 + manifest push --format: force an image type, not a list type + run: adjust the order in which elements are added to $PATH + getDateAndDigestAndSize(): handle creation time not being set + Bump github.com/containers/common from 0.2.0 to 0.2.1 + include installation steps for CentOS 8 and Stream + include installation steps for CentOS7 and forks + Adjust Ubuntu install info to also work on Pop!_OS + Make the commit id clear like Docker + Show error on copied file above context directory in build + Bump github.com/containers/image/v5 from 5.2.0 to 5.2.1 + pull/from/commit/push: retry on most failures + Makefile: fix install.cni.sudo + Repair buildah so it can use containers.conf on the server side + Bump github.com/mattn/go-shellwords from 1.0.9 to 1.0.10 + Bump github.com/fsouza/go-dockerclient from 1.6.0 to 1.6.1 + Fixing formatting & build instructions + Add Code of Conduct + Bors: Fix no. req. github reviews + Cirrus+Bors: Simplify temp branch skipping + Bors-ng: Add documentation and status-icon + Bump github.com/onsi/ginkgo from 1.11.0 to 1.12.0 + fix XDG_RUNTIME_DIR for authfile + Cirrus: Disable F29 testing + Cirrus: Add jq package + Cirrus: Fix lint + validation using wrong epoch + Stop using fedorproject registry + Bors: Workaround ineffective required statuses + Bors: Enable app + Disable Travis + Cirrus: Add standardized log-collection + Cirrus: Improve automated lint + validation + Allow passing options to golangci-lint + Cirrus: Fixes from review feedback + Cirrus: Temporarily ignore VM testing failures + Cirrus: Migrate off papr + implement VM testing + Cirrus: Update packages + fixes for get_ci_vm.sh + Show validation command-line + Skip overlay test w/ vfs driver + use alpine, not centos, for various tests + Flake handling: cache and prefetch images + Bump to v1.15.0-dev + +## v1.14.0 (2020-02-05) + bump github.com/mtrmac/gpgme + Update containers/common to v0.1.4 + manifest push: add --format option + Bump github.com/onsi/gomega from 1.8.1 to 1.9.0 + vendor github.com/containers/image/v5@v5.2.0 + info test: deal with random key order + Bump back to v1.14.0-dev + +## v1.13.2 (2020-01-29) + sign.bats: set GPG_TTY=/dev/null + Fix parse_unsupported.go + getDateAndDigestAndSize(): use manifest.Digest + Bump github.com/opencontainers/selinux from 1.3.0 to 1.3.1 + Bump github.com/containers/common from 0.1.0 to 0.1.2 + Touch up os/arch doc + chroot: handle slightly broken seccomp defaults + buildahimage: specify fuse-overlayfs mount options + Bump github.com/mattn/go-shellwords from 1.0.7 to 1.0.9 + copy.bats: make sure we detect failures due to missing source + parse: don't complain about not being able to rename something to itself + Makefile: use a $(GO_TEST) macro, fix a typo + manifests: unit test fix + Fix build for 32bit platforms + Allow users to set OS and architecture on bud + Fix COPY in containerfile with envvar + Bump c/storage to v1.15.7 + add --sign-by to bud/commit/push, --remove-signatures for pull/push + Remove cut/paste error in CHANGELOG.md + Update vendor of containers/common to v0.1.0 + update install instructions for Debian, Raspbian and Ubuntu + Add support for containers.conf + Bump back to v1.14.0-dev + +## v1.13.1 (2020-01-14) + Bump github.com/containers/common from 0.0.5 to 0.0.7 + Bump github.com/onsi/ginkgo from 1.10.3 to 1.11.0 + Bump github.com/pkg/errors from 0.8.1 to 0.9.0 + Bump github.com/onsi/gomega from 1.7.1 to 1.8.1 + Add codespell support + copyFileWithTar: close source files at the right time + copy: don't digest files that we ignore + Check for .dockerignore specifically + Travis: rm go 1.12.x + Don't setup excludes, if their is only one pattern to match + set HOME env to /root on chroot-isolation by default + docs: fix references to containers-*.5 + update openshift/api + fix bug Add check .dockerignore COPY file + buildah bud --volume: run from tmpdir, not source dir + Fix imageNamePrefix to give consistent names in buildah-from + cpp: use -traditional and -undef flags + Fix image reference in tutorial 4 + discard outputs coming from onbuild command on buildah-from --quiet + make --format columnizing consistent with buildah images + Bump to v1.14.0-dev + +## v1.13.0 (2019-12-27) + Bump to c/storage v1.15.5 + Update container/storage to v1.15.4 + Fix option handling for volumes in build + Rework overlay pkg for use with libpod + Fix buildahimage builds for buildah + Add support for FIPS-Mode backends + Set the TMPDIR for pulling/pushing image to $TMPDIR + WIP: safer test for pull --all-tags + BATS major cleanup: blobcache.bats: refactor + BATS major cleanup: part 4: manual stuff + BATS major cleanup, step 3: yet more run_buildah + BATS major cleanup, part 2: use more run_buildah + BATS major cleanup, part 1: log-level + Bump github.com/containers/image/v5 from 5.0.0 to 5.1.0 + Bump github.com/containers/common from 0.0.3 to 0.0.5 + Bump to v1.13.0-dev + +## v1.12.0 (2019-12-13) + Allow ADD to use http src + Bump to c/storage v.1.15.3 + install.md: update golang dependency + imgtype: reset storage opts if driver overridden + Start using containers/common + overlay.bats typo: fuse-overlays should be fuse-overlayfs + chroot: Unmount with MNT_DETACH instead of UnmountMountpoints() + bind: don't complain about missing mountpoints + imgtype: check earlier for expected manifest type + Vendor containers/storage fix + Vendor containers/storage v1.15.1 + Add history names support + PR takeover of #1966 + Tests: Add inspect test check steps + Tests: Add container name and id check in containers test steps + Test: Get permission in add test + Tests: Add a test for tag by id + Tests: Add test cases for push test + Tests: Add image digest test + Tests: Add some buildah from tests + Tests: Add two commit test + Tests: Add buildah bud with --quiet test + Tests: Add two test for buildah add + Bump back to v1.12.0-dev + +## v1.11.6 (2019-12-03) + Handle missing equal sign in --from and --chown flags for COPY/ADD + bud COPY does not download URL + Bump github.com/onsi/gomega from 1.7.0 to 1.7.1 + Fix .dockerignore exclude regression + Ran buildah through codespell + commit(docker): always set ContainerID and ContainerConfig + Touch up commit man page image parameter + Add builder identity annotations. + info: use util.Runtime() + Bump github.com/onsi/ginkgo from 1.10.2 to 1.10.3 + Bump back to v1.12.0-dev + +## v1.11.5 (2019-11-11) + Enhance error on unsafe symbolic link targets + Add OCIRuntime to info + Check nonexsit authfile + Only output image id if running buildah bud --quiet + Fix --pull=true||false and add --pull-never to bud and from (retry) + cgroups v2: tweak or skip tests + Prepwork: new 'skip' helpers for tests + Handle configuration blobs for manifest lists + unmarshalConvertedConfig: avoid using the updated image's ref + Add completions for Manifest commands + Add disableFips option to secrets pkg + Update bud.bats test archive test + Add test for caching based on content digest + Builder.untarPath(): always evaluate b.ContentDigester.Hash() + Bump github.com/onsi/ginkgo from 1.10.1 to 1.10.2 + Fix another broken test: copy-url-mtime + yet more fixes + Actual bug fix for 'add' test: fix the expected mode + BATS tests - lots of mostly minor cleanup + build: drop support for ostree + Add support for make vendor-in-container + imgtype: exit with error if storage fails + remove XDG_RUNTIME_DIR from default authfile path + fix troubleshooting redirect instructions + Bump back to v1.12.0-dev + +## v1.11.4 (2019-10-28) + buildah: add a "manifest" command + manifests: add the module + pkg/supplemented: add a package for grouping images together + pkg/manifests: add a manifest list build/manipulation API + Update for ErrUnauthorizedForCredentials API change in containers/image + Update for manifest-lists API changes in containers/image + version: also note the version of containers/image + Move to containers/image v5.0.0 + Enable --device directory as src device + Fix git build with branch specified + Bump github.com/openshift/imagebuilder from 1.1.0 to 1.1.1 + Bump github.com/fsouza/go-dockerclient from 1.4.4 to 1.5.0 + Add clarification to the Tutorial for new users + Silence "using cache" to ensure -q is fully quiet + Add OWNERS File to Buildah + Bump github.com/containers/storage from 1.13.4 to 1.13.5 + Move runtime flag to bud from common + Commit: check for storage.ErrImageUnknown using errors.Cause() + Fix crash when invalid COPY --from flag is specified. + Bump back to v1.12.0-dev + +## v1.11.3 (2019-10-04) + Update c/image to v4.0.1 + Bump github.com/spf13/pflag from 1.0.3 to 1.0.5 + Fix --build-args handling + Bump github.com/spf13/cobra from 0.0.3 to 0.0.5 + Bump github.com/cyphar/filepath-securejoin from 0.2.1 to 0.2.2 + Bump github.com/onsi/ginkgo from 1.8.0 to 1.10.1 + Bump github.com/fsouza/go-dockerclient from 1.3.0 to 1.4.4 + Add support for retrieving context from stdin "-" + Ensure bud remote context cleans up on error + info: add cgroups2 + Bump github.com/seccomp/libseccomp-golang from 0.9.0 to 0.9.1 + Bump github.com/mattn/go-shellwords from 1.0.5 to 1.0.6 + Bump github.com/stretchr/testify from 1.3.0 to 1.4.0 + Bump github.com/opencontainers/selinux from 1.2.2 to 1.3.0 + Bump github.com/etcd-io/bbolt from 1.3.2 to 1.3.3 + Bump github.com/onsi/gomega from 1.5.0 to 1.7.0 + update c/storage to v1.13.4 + Print build 'STEP' line to stdout, not stderr + Fix travis-ci on forks + Vendor c/storage v1.13.3 + Use Containerfile by default + Added tutorial on how to include Buildah as library + util/util: Fix "configuraitno" -> "configuration" log typo + Bump back to v1.12.0-dev + +## v1.11.2 (2019-09-13) + Add some cleanup code + Move devices code to unit specific directory. + Bump back to v1.12.0-dev + +## v1.11.1 (2019-09-11) + Add --devices flag to bud and from + Downgrade .papr to highest atomic verion + Add support for /run/.containerenv + Truncate output of too long image names + Preserve file and directory mount permissions + Bump fedora version from 28 to 30 + makeImageRef: ignore EmptyLayer if Squash is set + Set TMPDIR to /var/tmp by default + replace --debug=false with --log-level=error + Allow mounts.conf entries for equal source and destination paths + fix label and annotation for 1-line Dockerfiles + Enable interfacer linter and fix lints + install.md: mention goproxy + Makefile: use go proxy + Bump to v1.12.0-dev + +## v1.11.0 (2019-08-29) + tests/bud.bats: add --signature-policy to some tests + Vendor github.com/openshift/api + pull/commit/push: pay attention to $BUILD_REGISTRY_SOURCES + Add `--log-level` command line option and deprecate `--debug` + add support for cgroupsV2 + Correctly detect ExitError values from Run() + Disable empty logrus timestamps to reduce logger noise + Remove outdated deps Makefile target + Remove gofmt.sh in favor of golangci-lint + Remove govet.sh in favor of golangci-lint + Allow to override build date with SOURCE_DATE_EPOCH + Update shebangs to take env into consideration + Fix directory pull image names + Add --digestfile and Re-add push statement as debug + README: mention that Podman uses Buildah's API + Use content digests in ADD/COPY history entries + add: add a DryRun flag to AddAndCopyOptions + Fix possible runtime panic on bud + Add security-related volume options to validator + use correct path for ginkgo + Add bud 'without arguments' integration tests + Update documentation about bud + add: handle hard links when copying with .dockerignore + add: teach copyFileWithTar() about symlinks and directories + Allow buildah bud to be called without arguments + imagebuilder: fix detection of referenced stage roots + Touch up go mod instructions in install + run_linux: fix mounting /sys in a userns + Vendor Storage v1.13.2 + Cirrus: Update VM images + Fix handling of /dev/null masked devices + Update `bud`/`from` help to contain indicator for `--dns=none` + Bump back to v1.11.0-dev + +## v1.10.1 (2019-08-08) + Bump containers/image to v3.0.2 to fix keyring issue + Bug fix for volume minus syntax + Bump container/storage v1.13.1 and containers/image v3.0.1 + bump github.com/containernetworking/cni to v0.7.1 + Add overlayfs to fuse-overlayfs tip + Add automatic apparmor tag discovery + Fix bug whereby --get-login has no effect + Bump to v1.11.0-dev + +## v1.10.0 (2019-08-02) + vendor github.com/containers/image@v3.0.0 + Remove GO111MODULE in favor of `-mod=vendor` + Vendor in containers/storage v1.12.16 + Add '-' minus syntax for removal of config values + tests: enable overlay tests for rootless + rootless, overlay: use fuse-overlayfs + vendor github.com/containers/image@v2.0.1 + Added '-' syntax to remove volume config option + delete `successfully pushed` message + Add golint linter and apply fixes + vendor github.com/containers/storage@v1.12.15 + Change wait to sleep in buildahimage readme + Handle ReadOnly images when deleting images + Add support for listing read/only images + +## v1.9.2 (2019-07-19) + from/import: record the base image's digest, if it has one + Fix CNI version retrieval to not require network connection + Add misspell linter and apply fixes + Add goimports linter and apply fixes + Add stylecheck linter and apply fixes + Add unconvert linter and apply fixes + image: make sure we don't try to use zstd compression + run.bats: skip the "z" flag when testing --mount + Update to runc v1.0.0-rc8 + Update to match updated runtime-tools API + bump github.com/opencontainers/runtime-tools to v0.9.0 + Build e2e tests using the proper build tags + Add unparam linter and apply fixes + Run: correct a typo in the --cap-add help text + unshare: add a --mount flag + fix push check image name is not empty + Bump to v1.9.2-dev + +## v1.9.1 (2019-07-12) + add: fix slow copy with no excludes + Add errcheck linter and fix missing error check + Improve tests/tools/Makefile parallelism and abstraction + Fix response body not closed resource leak + Switch to golangci-lint + Add gomod instructions and mailing list links + On Masked path, check if /dev/null already mounted before mounting + Update to containers/storage v1.12.13 + Refactor code in package imagebuildah + Add rootless podman with NFS issue in documentation + Add --mount for buildah run + import method ValidateVolumeOpts from libpod + Fix typo + Makefile: set GO111MODULE=off + rootless: add the built-in slirp DNS server + Update docker/libnetwork to get rid of outdated sctp package + Update buildah-login.md + migrate to go modules + install.md: mention go modules + tests/tools: go module for test binaries + fix --volume splits comma delimited option + Add bud test for RUN with a priv'd command + vendor logrus v1.4.2 + pkg/cli: panic when flags can't be hidden + pkg/unshare: check all errors + pull: check error during report write + run_linux.go: ignore unchecked errors + conformance test: catch copy error + chroot/run_test.go: export funcs to actually be executed + tests/imgtype: ignore error when shutting down the store + testreport: check json error + bind/util.go: remove unused func + rm chroot/util.go + imagebuildah: remove unused `dedupeStringSlice` + StageExecutor: EnsureContainerPath: catch error from SecureJoin() + imagebuildah/build.go: return instead of branching + rmi: avoid redundant branching + conformance tests: nilness: allocate map + imagebuildah/build.go: avoid redundant `filepath.Join()` + imagebuildah/build.go: avoid redundant `os.Stat()` + imagebuildah: omit comparison to bool + fix "ineffectual assignment" lint errors + docker: ignore "repeats json tag" lint error + pkg/unshare: use `...` instead of iterating a slice + conformance: bud test: use raw strings for regexes + conformance suite: remove unused func/var + buildah test suite: remove unused vars/funcs + testreport: fix golangci-lint errors + util: remove redundant `return` statement + chroot: only log clean-up errors + images_test: ignore golangci-lint error + blobcache: log error when draining the pipe + imagebuildah: check errors in deferred calls + chroot: fix error handling in deferred funcs + cmd: check all errors + chroot/run_test.go: check errors + chroot/run.go: check errors in deferred calls + imagebuildah.Executor: remove unused onbuild field + docker/types.go: remove unused struct fields + util: use strings.ContainsRune instead of index check + Cirrus: Initial implementation + Bump to v1.9.1-dev + +## v1.9.0 (2019-06-15) + buildah-run: fix-out-of-range panic (2) + Bump back to v1.9.0-dev + + + +## v1.8.4 (2019-06-13) + Update containers/image to v2.0.0 + run: fix hang with run and --isolation=chroot + run: fix hang when using run + chroot: drop unused function call + remove --> before imgageID on build + Always close stdin pipe + Write deny to setgroups when doing single user mapping + Avoid including linux/memfd.h + Add a test for the symlink pointing to a directory + Add missing continue + Fix the handling of symlinks to absolute paths + Only set default network sysctls if not rootless + Support --dns=none like podman + fix bug --cpu-shares parsing typo + Fix validate complaint + Update vendor on containers/storage to v1.12.10 + Create directory paths for COPY thereby ensuring correct perms + imagebuildah: use a stable sort for comparing build args + imagebuildah: tighten up cache checking + bud.bats: add a test verying the order of --build-args + add -t to podman run + imagebuildah: simplify screening by top layers + imagebuildah: handle ID mappings for COPY --from + imagebuildah: apply additionalTags ourselves + bud.bats: test additional tags with cached images + bud.bats: add a test for WORKDIR and COPY with absolute destinations + Cleanup Overlay Mounts content + +## v1.8.3 (2019-06-04) + Add support for file secret mounts + Add ability to skip secrets in mounts file + allow 32bit builds + fix tutorial instructions + imagebuilder: pass the right contextDir to Add() + add: use fileutils.PatternMatcher for .dockerignore + bud.bats: add another .dockerignore test + unshare: fallback to single usermapping + addHelperSymlink: clear the destination on os.IsExist errors + bud.bats: test replacing symbolic links + imagebuildah: fix handling of destinations that end with '/' + bud.bats: test COPY with a final "/" in the destination + linux: add check for sysctl before using it + unshare: set _CONTAINERS_ROOTLESS_GID + Rework buildahimamges + build context: support https git repos + Add a test for ENV special chars behaviour + Check in new Dockerfiles + Apply custom SHELL during build time + config: expand variables only at the command line + SetEnv: we only need to expand v once + Add default /root if empty on chroot iso + Add support for Overlay volumes into the container. + Export buildah validate volume functions so it can share code with libpod + Bump baseline test to F30 + Fix rootless handling of /dev/shm size + Avoid fmt.Printf() in the library + imagebuildah: tighten cache checking back up + Handle WORKDIR with dangling target + Default Authfile to proper path + Make buildah run --isolation follow BUILDAH_ISOLATION environment + Vendor in latest containers/storage and containers/image + getParent/getChildren: handle layerless images + imagebuildah: recognize cache images for layerless images + bud.bats: test scratch images with --layers caching + Get CHANGELOG.md updates + Add some symlinks to test our .dockerignore logic + imagebuildah: addHelper: handle symbolic links + commit/push: use an everything-allowed policy + Correct manpage formatting in files section + Remove must be root statement from buildah doc + Change image names to stable, testing and upstream + Bump back to v1.9.0-dev + +## v1.8.2 (2019-05-02) + Vendor Storage 1.12.6 + Create scratch file in TESTDIR + Test bud-copy-dot with --layers picks up changed file + Bump back to 1.9.0-dev + +## v1.8.1 (2019-05-01) + Don't create directory on container + Replace kubernetes/pause in tests with k8s.gcr.io/pause + imagebuildah: don't remove intermediate images if we need them + Rework buildahimagegit to buildahimageupstream + Fix Transient Mounts + Handle WORKDIRs that are symlinks + allow podman to build a client for windows + Touch up 1.9-dev to 1.9.0-dev + Bump to 1.9-dev + +## v1.8.0 (2019-04-26) + Resolve symlink when checking container path + commit: commit on every instruction, but not always with layers + CommitOptions: drop the unused OnBuild field + makeImageRef: pass in the whole CommitOptions structure + cmd: API cleanup: stores before images + run: check if SELinux is enabled + Fix buildahimages Dockerfiles to include support for additionalimages mounted from host. + Detect changes in rootdir + Fix typo in buildah-pull(1) + Vendor in latest containers/storage + Keep track of any build-args used during buildah bud --layers + commit: always set a parent ID + imagebuildah: rework unused-argument detection + fix bug dest path when COPY .dockerignore + Move Host IDMAppings code from util to unshare + Add BUILDAH_ISOLATION rootless back + Travis CI: fail fast, upon error in any step + imagebuildah: only commit images for intermediate stages if we have to + Use errors.Cause() when checking for IsNotExist errors + auto pass http_proxy to container + Bump back to 1.8-dev + +## v1.7.3 (2019-04-16) + imagebuildah: don't leak image structs + Add Dockerfiles for buildahimages + Bump to Replace golang 1.10 with 1.12 + add --dns* flags to buildah bud + Add hack/build_speed.sh test speeds on building container images + Create buildahimage Dockerfile for Quay + rename 'is' to 'expect_output' + squash.bats: test squashing in multi-layered builds + bud.bats: test COPY --from in a Dockerfile while using the cache + commit: make target image names optional + Fix bud-args to allow comma separation + oops, missed some tests in commit.bats + new helper: expect_line_count + New tests for #1467 (string slices in cmdline opts) + Workarounds for dealing with travis; review feedback + BATS tests - extensive but minor cleanup + imagebuildah: defer pulling images for COPY --from + imagebuildah: centralize COMMIT and image ID output + Travis: do not use traviswait + imagebuildah: only initialize imagebuilder configuration once per stage + Make cleaner error on Dockerfile build errors + unshare: move to pkg/ + unshare: move some code from cmd/buildah/unshare + Fix handling of Slices versus Arrays + imagebuildah: reorganize stage and per-stage logic + imagebuildah: add empty layers for instructions + Add missing step in installing into Ubuntu + fix bug in .dockerignore support + imagebuildah: deduplicate prepended "FROM" instructions + Touch up intro + commit: set created-by to the shell if it isn't set + commit: check that we always set a "created-by" + docs/buildah.md: add "containers-" prefixes under "SEE ALSO" + Bump back to 1.8-dev + +## v1.7.2 (2019-03-28) + mount: do not create automatically a namespace + buildah: correctly create the userns if euid!=0 + imagebuildah.Build: consolidate cleanup logic + CommitOptions: drop the redundant Store field + Move pkg/chrootuser from libpod to buildah. + imagebuildah: record image IDs and references more often + vendor imagebuilder v1.1.0 + imagebuildah: fix requiresStart/noRunsRemaining confusion + imagebuildah: check for unused args across stages + bump github.com/containernetworking/cni to v0.7.0-rc2 + imagebuildah: use "useCache" instead of "noCache" + imagebuildah.resolveNameToImageRef(): take name as a parameter + Export fields of the DokcerIgnore struct + imagebuildah: drop the duplicate containerIDs list + rootless: by default use the host network namespace + imagebuildah: split Executor and per-stage execution + imagebuildah: move some fields around + golint: make golint happy + docs: 01-intro.md: add missing . in Dockerfile examples + fix bug using .dockerignore + Do not create empty mounts.conf file + images: suppress a spurious blank line with no images + from: distinguish between ADD and COPY + fix bug to not separate each --label value with comma + buildah-bud.md: correct a typo, note a default + Remove mistaken code that got merged in other PR + add sample registries.conf to docs + escape shell variables in README example + slirp4netns: set mtu to 65520 + images: imageReposToMap() already adds : + imagebuildah.ReposToMap: move to cmd + Build: resolve copyFrom references earlier + Allow rootless users to use the cache directory in homedir + bud.bats: use the per-test temp directory + bud.bats: log output before counting length + Simplify checks for leftover args + Print commitID with --layers + fix bug images use the template to print results + rootless: honor --net host + onsi/gomeage add missing files + vendor latest openshift/imagebuilder + Remove noop from squash help + Prepend a comment to files setup in container + imagebuildah resolveSymlink: fix handling of relative links + Errors should be printed to stderr + Add recommends for slirp4netns and fuse-overlay + Update pull and pull-always flags + Hide from users command options that we don't want them to use. + Update secrets fipsmode patch to work on rootless containers + fix unshare option handling and documentation + Vendor in latest containers/storage + Hard-code docker.Transport use in pull --all-tags + Use a types.ImageReference instead of (transport, name) strings in pullImage etc. + Move the computation of srcRef before first pullAndFindImage + Don't throw away user-specified tag for pull --all-tags + CHANGES BEHAVIOR: Remove the string format input to localImageNameForReference + Don't try to parse imageName as transport:image in pullImage + Use reference.WithTag instead of manual string manipulation in Pull + Don't pass image = transport:repo:tag, transport=transport to pullImage + Fix confusing variable naming in Pull + Don't try to parse image name as a transport:image + Fix error reporting when parsing trans+image + Remove 'transport == ""' handling from the pull path + Clean up "pulls" of local image IDs / ID prefixes + Simplify ExpandNames + Document the semantics of transport+name returned by ResolveName + UPdate gitvalidation epoch + Bump back to 1.8-dev + +## v1.7.1 (2019-02-26) + vendor containers/image v1.5 + Move secrets code from libpod into buildah + Update CHANGELOG.md with the past changes + README.md: fix typo + Fix a few issues found by tests/validate/gometalinter.sh + Neutralize buildah/unshare on non-Linux platforms + Explicitly specify a directory to find(1) + README.md: rephrase Buildah description + Stop printing default twice in cli --help + install.md: add section about vendoring + Bump to 1.8-dev + +## v1.7 (2019-02-21) + vendor containers/image v1.4 + Make "images --all" faster + Remove a misleading comment + Remove quiet option from pull options + Make sure buildah pull --all-tags only works with docker transport + Support oci layout format + Fix pulling of images within buildah + Fix tls-verify polarity + Travis: execute make vendor and hack/tree_status.sh + vendor.conf: remove unused dependencies + add missing vendor/github.com/containers/libpod/vendor.conf + vendor.conf: remove github.com/inconshreveable/mousetrap + make vendor: always fetch the latest vndr + add hack/tree_status.sh script + Bump c/Storage to 1.10 + Add --all-tags test to pull + mount: make error clearer + Remove global flags from cli help + Set --disable-compression to true as documented + Help document using buildah mount in rootless mode + healthcheck start-period: update documentation + Vendor in latest c/storage and c/image + dumpbolt: handle nested buckets + Fix buildah commit compress by default + Test on xenial, not trusty + unshare: reexec using a memfd copy instead of the binary + Add --target to bud command + Fix example for setting multiple environment variables + main: fix rootless mode + buildah: force umask 022 + pull.bats: specify registry config when using registries + pull.bats: use the temporary directory, not /tmp + unshare: do not set rootless mode if euid=0 + Touch up cli help examples and a few nits + Add an undocumented dumpbolt command + Move tar commands into containers/storage + Fix bud issue with 2 line Dockerfile + Add package install descriptions + Note configuration file requirements + Replace urfave/cli with cobra + cleanup vendor.conf + Vendor in latest containers/storage + Add Quiet to PullOptions and PushOptions + cmd/commit: add flag omit-timestamp to allow for deterministic builds + Add options for empty-layer history entries + Make CLI help descriptions and usage a bit more consistent + vndr opencontainers/selinux + Bump baseline test Fedora to 29 + Bump to v1.7-dev-1 + Bump to v1.6-1 + Add support for ADD --chown + imagebuildah: make EnsureContainerPath() check/create the right one + Bump 1.7-dev + Fix contrib/rpm/bulidah.spec changelog date + +## v1.6-1 (2019-01-18) + Add support for ADD --chown + imagebuildah: make EnsureContainerPath() check/create the right one + Fix contrib/rpm/bulidah.spec changelog date + Vendor in latest containers/storage + Revendor everything + Revendor in latest code by release + unshare: do not set USER=root + run: ignore EIO when flushing at the end, avoid double log + build-using-dockerfile,commit: disable compression by default + Update some comments + Make rootless work under no_pivot_root + Add CreatedAtRaw date field for use with Format + Properly format images JSON output + pull: add all-tags option + Fix support for multiple Short options + pkg/blobcache: add synchronization + Skip empty files in file check of conformance test + Use NoPivot also for RUN, not only for run + Remove no longer used isReferenceInsecure / isRegistryInsecure + Do not set OCIInsecureSkipTLSVerify based on registries.conf + Remove duplicate entries from images JSON output + vendor parallel-copy from containers/image + blobcache.bats: adjust explicit push tests + Handle one line Dockerfile with layers + We should only warn if user actually requests Hostname be set in image + Fix compiler Warning about comparing different size types + imagebuildah: don't walk if rootdir and path are equal + Add aliases for buildah containers, so buildah list, ls and ps work + vendor: use faster version instead compress/gzip + vendor: update libpod + Properly handle Hostname inside of RUN command + docs: mention how to mount in rootless mode + tests: use fully qualified name for centos image + travis.yml: use the fully qualified name for alpine + mount: allow mount only when using vfs + Add some tests for buildah pull + Touch up images -q processing + Refactor: Use library shared idtools.ParseIDMap() instead of bundling it + bump GITVALIDATE_EPOCH + cli.BudFlags: add `--platform` nop + Makefile: allow packagers to more easily add tags + Makefile: soften the requirement on git + tests: add containers json test + Inline blobCache.putBlob into blobCacheDestination.PutBlob + Move saveStream and putBlob near blobCacheDestination.PutBlob + Remove BlobCache.PutBlob + Update for API changes + Vendor c/image after merging c/image#536 + Handle 'COPY --from' in Dockerfile + Vendor in latest content from github.com/containers/storage + Clarify docker.io default in push with docker-daemon + Test blob caching + Wire in a hidden --blob-cache option + Use a blob cache when we're asked to use one + Add --disable-compression to 'build-using-dockerfile' + Add a blob cache implementation + vendor: update containers/storage + Update for sysregistriesv2 API changes + Update containers/image to 63a1cbdc5e6537056695cf0d627c0a33b334df53 + clean up makefile variables + Fix file permission + Complete the instructions for the command + Show warning when a build arg not used + Assume user 0 group 0, if /etc/passwd file in container. + Add buildah info command + Enable -q when --filter is used for images command + Add v1.5 Release Announcement + Fix dangling filter for images command + Fix completions to print Names as well as IDs + tests: Fix file permissions + Bump 1.6-dev + +## v1.5-1 (2018-11-21) + Bump min go to 1.10 in install.md + vendor: update ostree-go + Update docker build command line in conformance test + Print command in SystemExec as debug information + Add some skip word for inspect check in conformance test + Update regex for multi stage base test + Sort CLI flags + vendor: update containers/storage + Add note to install about non-root on RHEL/CentOS + Update imagebuild depdency to support heading ARGs in Dockerfile + rootless: do not specify --rootless to the OCI runtime + Export resolvesymlink function + Exclude --force-rm from common bud cli flags + run: bind mount /etc/hosts and /etc/resolv.conf if not in a volume + rootless: use slirp4netns to setup the network namespace + Instructions for completing the pull command + Fix travis to not run environment variable patch + rootless: only discard network configuration names + run: only set up /etc/hosts or /etc/resolv.conf with network + common: getFormat: match entire string not only the prefix + vendor: update libpod + Change validation EPOCH + Fixing broken link for container-registries.conf + Restore rootless isolation test for from volume ro test + ostree: fix tag for build constraint + Handle directories better in bud -f + vndr in latest containers/storage + Fix unshare gofmt issue + runSetupBuiltinVolumes(): break up volume setup + common: support a per-user registries conf file + unshare: do not override the configuration + common: honor the rootless configuration file + unshare: create a new mount namespace + unshare: support libpod rootless pkg + Use libpod GetDefaultStorage to report proper storage config + Allow container storage to manage the SELinux labels + Resolve image names with default transport in from command + run: When the value of isolation is set, use the set value instead of the default value. + Vendor in latest containers/storage and opencontainers/selinux + Remove no longer valid todo + Check for empty buildTime in version + Change gofmt so it runs on all but 1.10 + Run gofmt only on Go 1.11 + Walk symlinks when checking cached images for copied/added files + ReserveSELinuxLabels(): handle wrapped errors from OpenBuilder + Set WorkingDir to empty, not / for conformance + Update calls in e2e to addres 1101 + imagebuilder.BuildDockerfiles: return the image ID + Update for changes in the containers/image API + bump(github.com/containers/image) + Allow setting --no-pivot default with an env var + Add man page and bash completion, for --no-pivot + Add the --no-pivot flag to the run command + Improve reporting about individual pull failures + Move the "short name but no search registries" error handling to resolveImage + Return a "search registries were needed but empty" indication in util.ResolveName + Simplify handling of the "tried to pull an image but found nothing" case in newBuilder + Don't even invoke the pull loop if options.FromImage == "" + Eliminate the long-running ref and img variables in resolveImage + In resolveImage, return immediately on success + Fix From As in Dockerfile + Vendor latest containers/image + Vendor in latest libpod + Sort CLI flags of buildah bud + Change from testing with golang 1.9 to 1.11. + unshare: detect when unprivileged userns are disabled + Optimize redundant code + fix missing format param + chroot: fix the args check + imagebuildah: make ResolveSymLink public + Update copy chown test + buildah: use the same logic for XDG_RUNTIME_DIR as podman + V1.4 Release Announcement + Podman --privileged selinux is broken + papr: mount source at gopath + parse: Modify the return value + parse: modify the verification of the isolation value + Make sure we log or return every error + pullImage(): when completing an image name, try docker:// + Fix up Tutorial 3 to account for format + Vendor in latest containers/storage and containers/image + docs/tutorials/01-intro.md: enhanced installation instructions + Enforce "blocked" for registries for the "docker" transport + Correctly set DockerInsecureSkipTLSVerify when pulling images + chroot: set up seccomp and capabilities after supplemental groups + chroot: fix capabilities list setup and application + .papr.yml: log the podman version + namespaces.bats: fix handling of uidmap/gidmap options in pairs + chroot: only create user namespaces when we know we need them + Check /proc/sys/user/max_user_namespaces on unshare(NEWUSERNS) + bash/buildah: add isolation option to the from command + +## v1.4 (2018-10-02) + from: fix isolation option + Touchup pull manpage + Export buildah ReserveSELinuxLables so podman can use it + Add buildah.io to README.md and doc fixes + Update rmi man for prune changes + Ignore file not found removal error in bud + bump(github.com/containers/{storage,image}) + NewImageSource(): only create one Diff() at a time + Copy ExposedPorts from base image into the config + tests: run conformance test suite in Travis + Change rmi --prune to not accept an imageID + Clear intermediate container IDs after each stage + Request podman version for build issues + unshare: keep the additional groups of the user + Builtin volumes should be owned by the UID/GID of the container + Get rid of dangling whitespace in markdown files + Move buildah from projecatatomic/buildah to containers/buildah + nitpick: parse.validateFlags loop in bud cli + bash: Completion options + Add signature policy to push tests + vendor in latest containers/image + Fix grammar in Container Tools Guide + Don't build btrfs if it is not installed + new: Return image-pulling errors from resolveImage + pull: Return image-pulling errors from pullImage + Add more volume mount tests + chroot: create missing parent directories for volume mounts + Push: Allow an empty destination + Add Podman relationship to readme, create container tools guide + Fix arg usage in buildah-tag + Add flags/arguments order verification to other commands + Handle ErrDuplicateName errors from store.CreateContainer() + Evaluate symbolic links on Add/Copy Commands + Vendor in latest containers/image and containers/storage + Retain bounding set when running containers as non root + run container-diff tests in Travis + buildah-images.md: Fix option contents + push: show image digest after push succeed + Vendor in latest containers/storage,image,libpod and runc + Change references to cri-o to point at new repository + Exclude --layers from the common bug cli flags + demos: Increase the executable permissions + run: clear default seccomp filter if not enabled + Bump maximum cyclomatic complexity to 45 + stdin: on HUP, read everything + nitpick: use tabs in tests/helpers.bash + Add flags/arguments order verification to one arg commands + nitpick: decrease cognitive complexity in buildah-bud + rename: Avoid renaming the same name as other containers + chroot isolation: chroot() before setting up seccomp + Small nitpick at the "if" condition in tag.go + cmd/images: Modify json option + cmd/images: Disallow the input of image when using the -a option + Fix examples to include context directory + Update containers/image to fix commit layer issue + cmd/containers: End loop early when using the json option + Make buildah-from error message clear when flags are after arg + Touch up README.md for conformance tests + Update container/storage for lock fix + cmd/rm: restore the correct containerID display + Remove debug lines + Remove docker build image after each test + Add README for conformance test + Update the MakeOptions to accept all command options for buildah + Update regrex to fit the docker output in test "run with JSON" + cmd/buildah: Remove redundant variable declarations + Warn about using Commands in Dockerfile that are not supported by OCI. + Add buildah bud conformance test + Fix rename to also change container name in builder + Makefile: use $(GO) env-var everywhere + Cleanup code to more closely match Docker Build images + Document BUILDAH_* environment variables in buildah bud --help output + Return error immediately if error occurs in Prepare step + Fix --layers ADD from url issue + Add "Sign your PRs" TOC item to contributing.md. + Display the correct ID after deleting image + rmi: Modify the handling of errors + Let util.ResolveName() return parsing errors + Explain Open Container Initiative (OCI) acronym, add link + Update vendor for urfave/cli back to master + Handle COPY --chown in Dockerfile + Switch to Recommends container-selinux + Update vendor for containernetworking, imagebuildah and podman + Document STORAGE_DRIVER and STORAGE_OPTS environment variable + Change references to projectatomic/libpod to containers/libpod + Add container PATH retrieval example + Expand variables names for --env + imagebuildah: provide a way to provide stdin for RUN + Remove an unused srcRef.NewImageSource in pullImage + chroot: correct a comment + chroot: bind mount an empty directory for masking + Don't bother with --no-pivot for rootless isolation + CentOS need EPEL repo + Export a Pull() function + Remove stream options, since docker build does not have it + release v1.3: mention openSUSE + Add Release Announcements directory + Bump to v1.4-dev + +## 1.3 (2018-08-4) + Revert pull error handling from 881 + bud should not search context directory for Dockerfile + Set BUILDAH_ISOLATION=rootless when running unprivileged + .papr.sh: Also test with BUILDAH_ISOLATION=rootless + Skip certain tests when we're using "rootless" isolation + .travis.yml: run integration tests with BUILDAH_ISOLATION=chroot + Add and implement IsolationOCIRootless + Add a value for IsolationOCIRootless + Fix rmi to remove intermediate images associated with an image + Return policy error on pull + Update containers/image to 216acb1bcd2c1abef736ee322e17147ee2b7d76c + Switch to github.com/containers/image/pkg/sysregistriesv2 + unshare: make adjusting the OOM score optional + Add flags validation + chroot: handle raising process limits + chroot: make the resource limits name map module-global + Remove rpm.bats, we need to run this manually + Set the default ulimits to match Docker + buildah: no args is out of bounds + unshare: error message missed the pid + preprocess ".in" suffixed Dockerfiles + Fix the the in buildah-config man page + Only test rpmbuild on latest fedora + Add support for multiple Short options + Update to latest urvave/cli + Add additional SELinux tests + Vendor in latest github.com/containers/{image;storage} + Stop testing with golang 1.8 + Fix volume cache issue with buildah bud --layers + Create buildah pull command + Increase the deadline for gometalinter during 'make validate' + .papr.sh: Also test with BUILDAH_ISOLATION=chroot + .travis.yml: run integration tests with BUILDAH_ISOLATION=chroot + Add a Dockerfile + Set BUILDAH_ISOLATION=chroot when running unprivileged + Add and implement IsolationChroot + Update github.com/opencontainers/runc + maybeReexecUsingUserNamespace: add a default for root + Allow ping command without NET_RAW Capabilities + rmi.storageImageID: fix Wrapf format warning + Allow Dockerfile content to come from stdin + Vendor latest container/storage to fix overlay mountopt + userns: assign additional IDs sequentially + Remove default dev/pts + Add OnBuild test to baseline test + tests/run.bats(volumes): use :z when SELinux is enabled + Avoid a stall in runCollectOutput() + Use manifest from container/image + Vendor in latest containers/image and containers/storage + add rename command + Completion command + Update CHANGELOG.md + Update vendor for runc to fix 32 bit builds + bash completion: remove shebang + Update vendor for runc to fix 32 bit builds + +## 1.2 (2018-07-14) + Vendor in lates containers/image + build-using-dockerfile: let -t include transports again + Block use of /proc/acpi and /proc/keys from inside containers + Fix handling of --registries-conf + Fix becoming a maintainer link + add optional CI test fo darwin + Don't pass a nil error to errors.Wrapf() + image filter test: use kubernetes/pause as a "since" + Add --cidfile option to from + vendor: update containers/storage + Contributors need to find the CONTRIBUTOR.md file easier + Add a --loglevel option to build-with-dockerfile + Create Development plan + cmd: Code improvement + allow buildah cross compile for a darwin target + Add unused function param lint check + docs: Follow man-pages(7) suggestions for SYNOPSIS + Start using github.com/seccomp/containers-golang + umount: add all option to umount all mounted containers + runConfigureNetwork(): remove an unused parameter + Update github.com/opencontainers/selinux + Fix buildah bud --layers + Force ownership of /etc/hosts and /etc/resolv.conf to 0:0 + main: if unprivileged, reexec in a user namespace + Vendor in latest imagebuilder + Reduce the complexity of the buildah.Run function + mount: output it before replacing lastError + Vendor in latest selinux-go code + Implement basic recognition of the "--isolation" option + Run(): try to resolve non-absolute paths using $PATH + Run(): don't include any default environment variables + build without seccomp + vendor in latest runtime-tools + bind/mount_unsupported.go: remove import errors + Update github.com/opencontainers/runc + Add Capabilities lists to BuilderInfo + Tweaks for commit tests + commit: recognize committing to second storage locations + Fix ARGS parsing for run commands + Add info on registries.conf to from manpage + Switch from using docker to podman for testing in .papr + buildah: set the HTTP User-Agent + ONBUILD tutorial + Add information about the configuration files to the install docs + Makefile: add uninstall + Add tilde info for push to troubleshooting + mount: support multiple inputs + Use the right formatting when adding entries to /etc/hosts + Vendor in latest go-selinux bindings + Allow --userns-uid-map/--userns-gid-map to be global options + bind: factor out UnmountMountpoints + Run(): simplify runCopyStdio() + Run(): handle POLLNVAL results + Run(): tweak terminal mode handling + Run(): rename 'copyStdio' to 'copyPipes' + Run(): don't set a Pdeathsig for the runtime + Run(): add options for adding and removing capabilities + Run(): don't use a callback when a slice will do + setupSeccomp(): refactor + Change RunOptions.Stdin/Stdout/Stderr to just be Reader/Writers + Escape use of '_' in .md docs + Break out getProcIDMappings() + Break out SetupIntermediateMountNamespace() + Add Multi From Demo + Use the c/image conversion code instead of converting configs manually + Don't throw away the manifest MIME type and guess again + Consolidate loading manifest and config in initConfig + Pass a types.Image to Builder.initConfig + Require an image ID in importBuilderDataFromImage + Use c/image/manifest.GuessMIMEType instead of a custom heuristic + Do not ignore any parsing errors in initConfig + Explicitly handle "from scratch" images in Builder.initConfig + Fix parsing of OCI images + Simplify dead but dangerous-looking error handling + Don't ignore v2s1 history if docker_version is not set + Add --rm and --force-rm to buildah bud + Add --all,-a flag to buildah images + Separate stdio buffering from writing + Remove tty check from images --format + Add environment variable BUILDAH_RUNTIME + Add --layers and --no-cache to buildah bud + Touch up images man + version.md: fix DESCRIPTION + tests: add containers test + tests: add images test + images: fix usage + fix make clean error + Change 'registries' to 'container registries' in man + add commit test + Add(): learn to record hashes of what we add + Minor update to buildah config documentation for entrypoint + Bump to v1.2-dev + Add registries.conf link to a few man pages + +## 1.1 (2018-06-08) + Drop capabilities if running container processes as non root + Print Warning message if cmd will not be used based on entrypoint + Update 01-intro.md + Shouldn't add insecure registries to list of search registries + Report errors on bad transports specification when pushing images + Move parsing code out of common for namespaces and into pkg/parse.go + Add disable-content-trust noop flag to bud + Change freenode chan to buildah + runCopyStdio(): don't close stdin unless we saw POLLHUP + Add registry errors for pull + runCollectOutput(): just read until the pipes are closed on us + Run(): provide redirection for stdio + rmi, rm: add test + add mount test + Add parameter judgment for commands that do not require parameters + Add context dir to bud command in baseline test + run.bats: check that we can run with symlinks in the bundle path + Give better messages to users when image can not be found + use absolute path for bundlePath + Add environment variable to buildah --format + rm: add validation to args and all option + Accept json array input for config entrypoint + Run(): process RunOptions.Mounts, and its flags + Run(): only collect error output from stdio pipes if we created some + Add OnBuild support for Dockerfiles + Quick fix on demo readme + run: fix validate flags + buildah bud should require a context directory or URL + Touchup tutorial for run changes + Validate common bud and from flags + images: Error if the specified imagename does not exist + inspect: Increase err judgments to avoid panic + add test to inspect + buildah bud picks up ENV from base image + Extend the amount of time travis_wait should wait + Add a make target for Installing CNI plugins + Add tests for namespace control flags + copy.bats: check ownerships in the container + Fix SELinux test errors when SELinux is enabled + Add example CNI configurations + Run: set supplemental group IDs + Run: use a temporary mount namespace + Use CNI to configure container networks + add/secrets/commit: Use mappings when setting permissions on added content + Add CLI options for specifying namespace and cgroup setup + Always set mappings when using user namespaces + Run(): break out creation of stdio pipe descriptors + Read UID/GID mapping information from containers and images + Additional bud CI tests + Run integration tests under travis_wait in Travis + build-using-dockerfile: add --annotation + Implement --squash for build-using-dockerfile and commit + Vendor in latest container/storage for devicemapper support + add test to inspect + Vendor github.com/onsi/ginkgo and github.com/onsi/gomega + Test with Go 1.10, too + Add console syntax highlighting to troubleshooting page + bud.bats: print "$output" before checking its contents + Manage "Run" containers more closely + Break Builder.Run()'s "run runc" bits out + util.ResolveName(): handle completion for tagged/digested image names + Handle /etc/hosts and /etc/resolv.conf properly in container + Documentation fixes + Make it easier to parse our temporary directory as an image name + Makefile: list new pkg/ subdirectoris as dependencies for buildah + containerImageSource: return more-correct errors + API cleanup: PullPolicy and TerminalPolicy should be types + Make "run --terminal" and "run -t" aliases for "run --tty" + Vendor github.com/containernetworking/cni v0.6.0 + Update github.com/containers/storage + Update github.com/containers/libpod + Add support for buildah bud --label + buildah push/from can push and pull images with no reference + Vendor in latest containers/image + Update gometalinter to fix install.tools error + Update troubleshooting with new run workaround + Added a bud demo and tidied up + Attempt to download file from url, if fails assume Dockerfile + Add buildah bud CI tests for ENV variables + Re-enable rpm .spec version check and new commit test + Update buildah scratch demo to support el7 + Added Docker compatibility demo + Update to F28 and new run format in baseline test + Touchup man page short options across man pages + Added demo dir and a demo. chged distrorlease + builder-inspect: fix format option + Add cpu-shares short flag (-c) and cpu-shares CI tests + Minor fixes to formatting in rpm spec changelog + Fix rpm .spec changelog formatting + CI tests and minor fix for cache related noop flags + buildah-from: add effective value to mount propagation + +## 1.0 (2018-05-06) + Declare Buildah 1.0 + Add cache-from and no-cache noops, and fix doco + Update option and documentation for --force-rm + Adding noop for --force-rm to match --rm + Add buildah bud ENTRYPOINT,CMD,RUN tests + Adding buildah bud RUN test scenarios + Extend tests for empty buildah run command + Fix formatting error in run.go + Update buildah run to make command required + Expanding buildah run cmd/entrypoint tests + Update test cases for buildah run behaviour + Remove buildah run cmd and entrypoint execution + Add Files section with registries.conf to pertinent man pages + tests/config: perfect test + tests/from: add name test + Do not print directly to stdout in Commit() + Touch up auth test commands + Force "localhost" as a default registry + Drop util.GetLocalTime() + Vendor in latest containers/image + Validate host and container paths passed to --volume + test/from: add add-host test + Add --compress, --rm, --squash flags as a noop for bud + Add FIPS mode secret to buildah run and bud + Add config --comment/--domainname/--history-comment/--hostname + 'buildah config': stop replacing Created-By whenever it's not specified + Modify man pages so they compile correctly in mandb + Add description on how to do --isolation to buildah-bud man page + Add support for --iidfile to bud and commit + Refactor buildah bud for vendoring + Fail if date or git not installed + Revert update of entrypoint behaviour to match docker + Vendor in latest imagebuilder code to fix multiple stage builds + Add /bin/sh -c to entrypoint in config + image_test: Improve the test + Fix README example of buildah config + buildah-image: add validation to 'format' + Simple changes to allow buildah to pass make validate + Clarify the use of buildah config options + containers_test: Perfect testing + buildah images and podman images are listing different sizes + buildah-containers: add tests and example to the man page + buildah-containers: add validation to 'format' + Clarify the use of buildah config options + Minor fix for lighttpd example in README + Add tls-verification to troubleshooting + Modify buildah rmi to account for changes in containers/storage + Vendor in latest containers/image and containers/storage + addcopy: add src validation + Remove tarball as an option from buildah push --help + Fix secrets patch + Update entrypoint behaviour to match docker + Display imageId after commit + config: add support for StopSignal + Fix docker login issue in travis.yml + Allow referencing stages as index and names + Add multi-stage builds tests + Add multi-stage builds support + Add accessor functions for comment and stop signal + Vendor in latest imagebuilder, to get mixed case AS support + Allow umount to have multi-containers + Update buildah push doc + buildah bud walks symlinks + Imagename is required for commit atm, update manpage + +## 0.16.0 (2018-04-08) + Bump to v0.16.0 + Remove requires for ostree-lib in rpm spec file + Add support for shell + buildah.spec should require ostree-libs + Vendor in latest containers/image + bash: prefer options + Change image time to locale, add troubleshooting.md, add logo to other mds + buildah-run.md: fix error SYNOPSIS + docs: fix error example + Allow --cmd parameter to have commands as values + Touchup README to re-enable logo + Clean up README.md + Make default-mounts-file a hidden option + Document the mounts.conf file + Fix man pages to format correctly + Add various transport support to buildah from + Add unit tests to run.go + If the user overrides the storage driver, the options should be dropped + Show Config/Manifest as JSON string in inspect when format is not set + Switch which for that in README.md + Remove COPR + Fix wrong order of parameters + Vendor in latest containers/image + Remove shallowCopy(), which shouldn't be saving us time any more + shallowCopy: avoid a second read of the container's layer + +## 0.5 - 2017-11-07 + Add secrets patch to buildah + Add proper SELinux labeling to buildah run + Add tls-verify to bud command + Make filtering by date use the image's date + images: don't list unnamed images twice + Fix timeout issue + Add further tty verbiage to buildah run + Make inspect try an image on failure if type not specified + Add support for `buildah run --hostname` + Tons of bug fixes and code cleanup + +## 0.4 - 2017-09-22 +### Added + Update buildah spec file to match new version + Bump to version 0.4 + Add default transport to push if not provided + Add authentication to commit and push + Remove --transport flag + Run: don't complain about missing volume locations + Add credentials to buildah from + Remove export command + Bump containers/storage and containers/image + +## 0.3 - 2017-07-20 +## 0.2 - 2017-07-18 +### Added + Vendor in latest containers/image and containers/storage + Update image-spec and runtime-spec to v1.0.0 + Add support for -- ending options parsing to buildah run + Add/Copy need to support glob syntax + Add flag to remove containers on commit + Add buildah export support + update 'buildah images' and 'buildah rmi' commands + buildah containers/image: Add JSON output option + Add 'buildah version' command + Handle "run" without an explicit command correctly + Ensure volume points get created, and with perms + Add a -a/--all option to "buildah containers" + +## 0.1 - 2017-06-14 +### Added + Vendor in latest container/storage container/image + Add a "push" command + Add an option to specify a Create date for images + Allow building a source image from another image + Improve buildah commit performance + Add a --volume flag to "buildah run" + Fix inspect/tag-by-truncated-image-ID + Include image-spec and runtime-spec versions + buildah mount command should list mounts when no arguments are given. + Make the output image format selectable + commit images in multiple formats + Also import configurations from V2S1 images + Add a "tag" command + Add an "inspect" command + Update reference comments for docker types origins + Improve configuration preservation in imagebuildah + Report pull/commit progress by default + Contribute buildah.spec + Remove --mount from buildah-from + Add a build-using-dockerfile command (alias: bud) + Create manpages for the buildah project + Add installation for buildah and bash completions + Rename "list"/"delete" to "containers"/"rm" + Switch `buildah list quiet` option to only list container id's + buildah delete should be able to delete multiple containers + Correctly set tags on the names of pulled images + Don't mix "config" in with "run" and "commit" + Add a "list" command, for listing active builders + Add "add" and "copy" commands + Add a "run" command, using runc + Massive refactoring + Make a note to distinguish compression of layers + +## 0.0 - 2017-01-26 +### Added + Initial version, needs work diff --git a/vendor/github.com/containers/buildah/CODE-OF-CONDUCT.md b/vendor/github.com/containers/buildah/CODE-OF-CONDUCT.md new file mode 100644 index 00000000..dd95fce2 --- /dev/null +++ b/vendor/github.com/containers/buildah/CODE-OF-CONDUCT.md @@ -0,0 +1,3 @@ +## The Buildah Project Community Code of Conduct + +The Buildah Project, as part of Podman Container Tools, follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). diff --git a/vendor/github.com/containers/buildah/CONTRIBUTING.md b/vendor/github.com/containers/buildah/CONTRIBUTING.md new file mode 100644 index 00000000..e6516577 --- /dev/null +++ b/vendor/github.com/containers/buildah/CONTRIBUTING.md @@ -0,0 +1,177 @@ +![buildah logo](https://cdn.rawgit.com/containers/buildah/main/logos/buildah-logo_large.png) + +# Contributing to Buildah + +We'd love to have you join the community! Below summarizes the processes +that we follow. + +## Topics + +* [Reporting Issues](#reporting-issues) +* [Working On Issues](#working-on-issues) +* [Submitting Pull Requests](#submitting-pull-requests) +* [Sign your PRs](#sign-your-prs) +* [Merge bot interaction](#merge-bot-interaction) +* [Communications](#communications) +* [Becoming a Maintainer](#becoming-a-maintainer) + +## Reporting Issues + +Before reporting an issue, check our backlog of +[open issues](https://github.com/containers/buildah/issues) +to see if someone else has already reported it. If so, feel free to add +your scenario, or additional information, to the discussion. Or simply +"subscribe" to it to be notified when it is updated. + +If you find a new issue with the project we'd love to hear about it! The most +important aspect of a bug report is that it includes enough information for +us to reproduce it. So, please include as much detail as possible and try +to remove the extra stuff that doesn't really relate to the issue itself. +The easier it is for us to reproduce it, the faster it'll be fixed! + +Please don't include any private/sensitive information in your issue! + +## Working On Issues + +Once you have decided to contribute to Buildah by working on an issue, check our +backlog of [open issues](https://github.com/containers/buildah/issues) looking +for any that do not have an "In Progress" label attached to it. Often issues +will be assigned to someone, to be worked on at a later time. If you have the +time to work on the issue now, add yourself as an assignee, and set the +"In Progress" label if you’re a member of the “Containers” GitHub organization. +If you can not set the label, just add a quick comment in the issue asking that +the “In Progress” label be set and a member will do so for you. + +## Submitting Pull Requests + +No Pull Request (PR) is too small! Typos, additional comments in the code, +new testcases, bug fixes, new features, more documentation, ... it's all +welcome! + +While bug fixes can first be identified via an "issue", that is not required. +It's ok to just open up a PR with the fix, but make sure you include the same +information you would have included in an issue - like how to reproduce it. + +PRs for new features should include some background on what use cases the +new code is trying to address. When possible and when it makes sense, try to break-up +larger PRs into smaller ones - it's easier to review smaller +code changes. But only if those smaller ones make sense as stand-alone PRs. + +Regardless of the type of PR, all PRs should include: +* well documented code changes +* additional testcases. Ideally, they should fail w/o your code change applied +* documentation changes + +Squash your commits into logical pieces of work that might want to be reviewed +separate from the rest of the PRs. But, squashing down to just one commit is ok +too since in the end the entire PR will be reviewed anyway. When in doubt, +squash. + +PRs that fix issues should include a reference like `Closes #XXXX` in the +commit message so that github will automatically close the referenced issue +when the PR is merged. + + + +### Sign your PRs + +The sign-off is a line at the end of the explanation for the patch. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as an open-source patch. The rules are simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. + +## Merge bot interaction + +Maintainers should never merge anything directly into upstream +branches. Instead, interact with the [openshift-ci-robot](https://github.com/openshift-ci-robot/) +through PR comments as summarized [here](https://prow.ci.openshift.org/command-help?repo=containers%2Fbuildah). +This ensures all upstream +branches contain commits in a predictable order, and that every commit +has passed automated testing at some point in the past. A +[Maintainer portal](https://prow.ci.openshift.org/pr?query=is%3Apr%20state%3Aopen%20repo%3Acontainers%2Fbuildah) +is available, showing all PRs awaiting review and approval. + +## Communications + +For general questions or discussions, please use the +IRC channel `#podman` on `irc.libera.chat`. If you are unfamiliar with IRC you can start a web client at https://web.libera.chat/#podman. + +Alternatively, [\[matrix\]](https://matrix.org) can be used to access the same channel via federation at https://matrix.to/#/#podman:chat.fedoraproject.org. + +### For discussions around issues/bugs and features: + +#### GitHub +You can also use GitHub +[issues](https://github.com/containers/buildah/issues) +and +[PRs](https://github.com/containers/buildah/pulls) +tracking system. + +#### Buildah Mailing List + + +You can join the Buildah mailing list by sending an email to `buildah-join@lists.buildah.io` with the word `subscribe` in the subject. You can also go to this [page](https://lists.podman.io/admin/lists/buildah.lists.buildah.io/), then scroll down to the bottom of the page and enter your email and optionally name, then click on the "Subscribe" button. + +## Becoming a Maintainer + +To become a maintainer you must first be nominated by an existing maintainer. +If a majority (>50%) of maintainers agree then the proposal is adopted and +you will be added to the list. + +Removing a maintainer requires at least 75% of the remaining maintainers +approval, or if the person requests to be removed then it is automatic. +Normally, a maintainer will only be removed if they are considered to be +inactive for a long period of time or are viewed as disruptive to the community. + +The current list of maintainers can be found in the +[MAINTAINERS](./MAINTAINERS.md) file. + diff --git a/vendor/github.com/containers/buildah/GOVERNANCE.md b/vendor/github.com/containers/buildah/GOVERNANCE.md new file mode 100644 index 00000000..94b838e3 --- /dev/null +++ b/vendor/github.com/containers/buildah/GOVERNANCE.md @@ -0,0 +1,12 @@ +## The Buildah Project Community Governance + +The Buildah project, as part of Podman Container Tools, follows the [Podman Project Governance](https://github.com/containers/podman/blob/main/GOVERNANCE.md) +except sections found in this document, which override those found in Podman's Governance. + +--- + +# Maintainers File + +The definitive source of truth for maintainers of this repository is the local [MAINTAINERS.md](./MAINTAINERS.md) file. The [MAINTAINERS.md](https://github.com/containers/podman/blob/main/MAINTAINERS.md) file in the main Podman repository is used for project-spanning roles, including Core Maintainer and Community Manager. Some repositories in the project will also have a local [OWNERS](./OWNERS) file, which the CI system uses to map users to roles. Any changes to the [OWNERS](./OWNERS) file must make a corresponding change to the [MAINTAINERS.md](./MAINTAINERS.md) file to ensure that the file remains up to date. Most changes to [MAINTAINERS.md](./MAINTAINERS.md) will require a change to the repository’s [OWNERS](.OWNERS) file (e.g., adding a Reviewer), but some will not (e.g., promoting a Maintainer to a Core Maintainer, which comes with no additional CI-related privileges). + +Any Core Maintainers listed in Podman’s [MAINTAINERS.md](https://github.com/containers/podman/blob/main/MAINTAINERS.md) file should also be added to the list of “approvers” in the local [OWNERS](./OWNERS) file and as a Core Maintainer in the list of “Maintainers” in the local [MAINTAINERS.md](./MAINTAINERS.md) file. diff --git a/vendor/github.com/containers/buildah/LICENSE b/vendor/github.com/containers/buildah/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/github.com/containers/buildah/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/buildah/MAINTAINERS.md b/vendor/github.com/containers/buildah/MAINTAINERS.md new file mode 100644 index 00000000..83fd6a71 --- /dev/null +++ b/vendor/github.com/containers/buildah/MAINTAINERS.md @@ -0,0 +1,36 @@ +# Buildah Maintainers + +[GOVERNANCE.md](GOVERNANCE.md) +describes the project's governance and the Project Roles used below. + +## Maintainers + +| Maintainer | GitHub ID | Project Roles | Affiliation | +|-------------------|----------------------------------------------------------|----------------------------------|----------------------------------------------| +| Brent Baude | [baude](https://github.com/baude) | Core Maintainer | [Red Hat](https://github.com/RedHatOfficial) | +| Nalin Dahyabhai | [nalind](https://github.com/nalind) | Core Maintainer | [Red Hat](https://github.com/RedHatOfficial) | +| Matthew Heon | [mheon](https://github.com/mheon) | Core Maintainer | [Red Hat](https://github.com/RedHatOfficial) | +| Paul Holzinger | [Luap99](https://github.com/Luap99) | Core Maintainer | [Red Hat](https://github.com/RedHatOfficial) | +| Giuseppe Scrivano | [giuseppe](https://github.com/giuseppe) | Core Maintainer | [Red Hat](https://github.com/RedHatOfficial) | +| Miloslav Trmač | [mtrmac](https://github.com/mtrmac) | Core Maintainer | [Red Hat](https://github.com/RedHatOfficial) | +| Mohan Boddu | [mohanboddu](https://github.com/mohanboddu) | Community Manager | [Red Hat](https://github.com/RedHatOfficial) | +| Neil Smith | [actionmancan](https://github.com/actionmancan) | Community Manager | [Red Hat](https://github.com/RedHatOfficial) | +| Tom Sweeney | [TomSweeneyRedHat](https://github.com/TomSweeneyRedHat/) | Maintainer and Community Manager | [Red Hat](https://github.com/RedHatOfficial) | +| Lokesh Mandvekar | [lsm5](https://github.com/lsm5) | Maintainer | [Red Hat](https://github.com/RedHatOfficial) | +| Aditya Rajan | [flouthoc](https://github.com/flouthoc) | Maintainer | [Red Hat](https://github.com/RedHatOfficial) | +| Dan Walsh | [rhatdan](https://github.com/rhatdan) | Maintainer | [Red Hat](https://github.com/RedHatOfficial) | +| Ashley Cui | [ashley-cui](https://github.com/ashley-cui) | Reviewer | [Red Hat](https://github.com/RedHatOfficial) | +| Jan Rodák | [Honny1](https://github.com/Honny1) | Reviewer | [Red Hat](https://github.com/RedHatOfficial) | +| Valentin Rothberg | [vrothberg](https://github.com/vrothberg) | Reviewer | [Red Hat](https://github.com/RedHatOfficial) | + +## Alumni + +None at present + +## Credits + +The structure of this document was based off of the equivalent one in the [CRI-O Project](https://github.com/cri-o/cri-o/blob/main/MAINTAINERS.md). + +## Note + +If there is a discrepancy between the [MAINTAINERS.md](https://github.com/containers/podman/blob/main/MAINTAINERS.md) file in the main Podman repository and this file regarding Core Maintainers or Community Managers, the file in the Podman Repository is considered the source of truth. diff --git a/vendor/github.com/containers/buildah/Makefile b/vendor/github.com/containers/buildah/Makefile new file mode 100644 index 00000000..42a25d01 --- /dev/null +++ b/vendor/github.com/containers/buildah/Makefile @@ -0,0 +1,212 @@ +export GOPROXY=https://proxy.golang.org + +APPARMORTAG := $(shell hack/apparmor_tag.sh) +STORAGETAGS := $(shell ./btrfs_installed_tag.sh) $(shell ./hack/libsubid_tag.sh) +SECURITYTAGS ?= seccomp $(APPARMORTAG) +TAGS ?= $(SECURITYTAGS) $(STORAGETAGS) $(shell ./hack/systemd_tag.sh) $(shell ./hack/sqlite_tag.sh) +ifeq ($(shell uname -s),FreeBSD) +# FreeBSD needs CNI until netavark is supported +TAGS += cni +endif +BUILDTAGS += $(TAGS) $(EXTRA_BUILD_TAGS) +PREFIX := /usr/local +BINDIR := $(PREFIX)/bin +BASHINSTALLDIR = $(PREFIX)/share/bash-completion/completions +BUILDFLAGS := -tags "$(BUILDTAGS)" +BUILDAH := buildah +SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z) +SELINUXTYPE=container_runtime_exec_t +AS ?= as +STRIP ?= strip + +GO := go +GO_LDFLAGS := $(shell if $(GO) version|grep -q gccgo; then echo "-gccgoflags"; else echo "-ldflags"; fi) +GO_GCFLAGS := $(shell if $(GO) version|grep -q gccgo; then echo "-gccgoflags"; else echo "-gcflags"; fi) +NPROCS := $(shell nproc) +export GO_BUILD=$(GO) build +export GO_TEST=$(GO) test -parallel=$(NPROCS) +RACEFLAGS ?= $(shell $(GO_TEST) -race ./pkg/dummy > /dev/null 2>&1 && echo -race) + +COMMIT_NO ?= $(shell git rev-parse HEAD 2> /dev/null || true) +GIT_COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),${COMMIT_NO}-dirty,${COMMIT_NO}) +SOURCE_DATE_EPOCH ?= $(if $(shell date +%s),$(shell date +%s),$(error "date failed")) + +# we get GNU make 3.x in MacOS build envs, which wants # to be escaped in +# strings, while the 4.x we have on Linux doesn't. this is the documented +# workaround +COMMENT := \# +CNI_COMMIT := $(shell sed -n 's;^$(COMMENT) github.com/containernetworking/cni \([^ \n]*\).*$$;\1;p' vendor/modules.txt) + +SEQUOIA_SONAME_DIR = +EXTRA_LDFLAGS ?= +BUILDAH_LDFLAGS := $(GO_LDFLAGS) '-X main.GitCommit=$(GIT_COMMIT) -X main.buildInfo=$(SOURCE_DATE_EPOCH) -X main.cniVersion=$(CNI_COMMIT) -X go.podman.io/image/v5/signature/internal/sequoia.sequoiaLibraryDir="$(SEQUOIA_SONAME_DIR)" $(EXTRA_LDFLAGS)' + +# This isn't what we actually build; it's a superset, used for target +# dependencies. Basically: all *.go and *.c files, except *_test.go, +# and except anything in a dot subdirectory. If any of these files is +# newer than our target (bin/buildah), a rebuild is triggered. +SOURCES=$(shell find . -path './.*' -prune -o \( \( -name '*.go' -o -name '*.c' \) -a ! -name '*_test.go' \) -print) + +LINTFLAGS ?= + +ifeq ($(BUILDDEBUG), 1) + override GOGCFLAGS += -N -l +endif + +# Managed by renovate. +export GOLANGCI_LINT_VERSION := 2.1.0 + +# make all BUILDDEBUG=1 +# Note: Uses the -N -l go compiler options to disable compiler optimizations +# and inlining. Using these build options allows you to subsequently +# use source debugging tools like delve. +all: bin/buildah bin/imgtype bin/copy bin/inet bin/tutorial bin/dumpspec bin/passwd bin/crash bin/wait docs + +bin/buildah: $(SOURCES) internal/mkcw/embed/entrypoint_amd64.gz + $(GO_BUILD) $(BUILDAH_LDFLAGS) $(GO_GCFLAGS) "$(GOGCFLAGS)" -o $@ $(BUILDFLAGS) ./cmd/buildah + test -z "${SELINUXOPT}" || chcon --verbose -t $(SELINUXTYPE) $@ + +ifneq ($(shell $(AS) --version | grep x86_64),) +internal/mkcw/embed/entrypoint_amd64.gz: internal/mkcw/embed/entrypoint_amd64 + gzip -k9nf $^ + +internal/mkcw/embed/entrypoint_amd64: internal/mkcw/embed/entrypoint_amd64.s + $(AS) -o $(patsubst %.s,%.o,$^) $^ + $(LD) -o $@ $(patsubst %.s,%.o,$^) + $(STRIP) $@ +endif + + +.PHONY: buildah +buildah: bin/buildah + +ALL_CROSS_TARGETS := $(addprefix bin/buildah.,$(subst /,.,$(shell $(GO) tool dist list))) +LINUX_CROSS_TARGETS := $(filter-out %.loong64,$(filter bin/buildah.linux.%,$(ALL_CROSS_TARGETS))) +DARWIN_CROSS_TARGETS := $(filter bin/buildah.darwin.%,$(ALL_CROSS_TARGETS)) +WINDOWS_CROSS_TARGETS := $(addsuffix .exe,$(filter bin/buildah.windows.%,$(ALL_CROSS_TARGETS))) +FREEBSD_CROSS_TARGETS := $(filter bin/buildah.freebsd.%,$(ALL_CROSS_TARGETS)) +.PHONY: cross +cross: $(LINUX_CROSS_TARGETS) $(DARWIN_CROSS_TARGETS) $(WINDOWS_CROSS_TARGETS) $(FREEBSD_CROSS_TARGETS) + +bin/buildah.%: $(SOURCES) + mkdir -p ./bin + GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ -tags "containers_image_openpgp" ./cmd/buildah + +bin/crash: $(SOURCES) + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/crash + +bin/wait: $(SOURCES) + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/wait + +bin/dumpspec: $(SOURCES) + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/dumpspec + +bin/imgtype: $(SOURCES) + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/imgtype/imgtype.go + +bin/copy: $(SOURCES) + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/copy/copy.go + +bin/tutorial: $(SOURCES) + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/tutorial/tutorial.go + +bin/inet: tests/inet/inet.go + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/inet/inet.go + +bin/passwd: tests/passwd/passwd.go + $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/passwd/passwd.go + +.PHONY: clean +clean: + $(RM) -r bin tests/testreport/testreport tests/conformance/testdata/mount-targets/true + $(MAKE) -C docs clean + +.PHONY: docs +docs: install.tools ## build the docs on the host + $(MAKE) -C docs + +codespell: + codespell -w + +.PHONY: validate +validate: install.tools + ./tests/validate/whitespace.sh + ./hack/xref-helpmsgs-manpages + ./tests/validate/pr-should-include-tests + +.PHONY: install.tools +install.tools: + $(MAKE) -C tests/tools + +.PHONY: install +install: + install -d -m 755 $(DESTDIR)/$(BINDIR) + install -m 755 bin/buildah $(DESTDIR)/$(BINDIR)/buildah + $(MAKE) -C docs install + +.PHONY: uninstall +uninstall: + rm -f $(DESTDIR)/$(BINDIR)/buildah + rm -f $(PREFIX)/share/man/man1/buildah*.1 + rm -f $(DESTDIR)/$(BASHINSTALLDIR)/buildah + +.PHONY: install.completions +install.completions: + install -m 755 -d $(DESTDIR)/$(BASHINSTALLDIR) + install -m 644 contrib/completions/bash/buildah $(DESTDIR)/$(BASHINSTALLDIR)/buildah + +.PHONY: test-conformance +test-conformance: tests/conformance/testdata/mount-targets/true + $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover -timeout 60m ./tests/conformance + +.PHONY: test-integration +test-integration: install.tools + cd tests; ./test_runner.sh + +tests/testreport/testreport: tests/testreport/testreport.go + $(GO_BUILD) $(GO_LDFLAGS) "-linkmode external -extldflags -static" -tags "$(STORAGETAGS) $(SECURITYTAGS)" -o tests/testreport/testreport ./tests/testreport/testreport.go + +tests/conformance/testdata/mount-targets/true: tests/conformance/testdata/mount-targets/true.go + $(GO_BUILD) $(GO_LDFLAGS) "-linkmode external -extldflags -static" -o tests/conformance/testdata/mount-targets/true tests/conformance/testdata/mount-targets/true.go + +.PHONY: test-unit +test-unit: tests/testreport/testreport + $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover $(RACEFLAGS) $(shell $(GO) list ./... | grep -v vendor | grep -v tests | grep -v cmd | grep -v chroot | grep -v copier) -timeout 45m + $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" $(RACEFLAGS) ./chroot ./copier -timeout 60m + tmp=$(shell mktemp -d) ; \ + mkdir -p $$tmp/root $$tmp/runroot; \ + $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover $(RACEFLAGS) ./cmd/buildah -args --root $$tmp/root --runroot $$tmp/runroot --storage-driver vfs --signature-policy $(shell pwd)/tests/policy.json --registries-conf $(shell pwd)/tests/registries.conf + +vendor-in-container: + goversion=$(shell sed -e '/^go /!d' -e '/^go /s,.* ,,g' go.mod) ; \ + if test -d `$(GO) env GOCACHE` && test -w `$(GO) env GOCACHE` ; then \ + podman run --privileged --rm --env HOME=/root -v `$(GO) env GOCACHE`:/root/.cache/go-build --env GOCACHE=/root/.cache/go-build -v `pwd`:/src -w /src docker.io/library/golang:$$goversion make vendor ; \ + else \ + podman run --privileged --rm --env HOME=/root -v `pwd`:/src -w /src docker.io/library/golang:$$goversion make vendor ; \ + fi + +.PHONY: vendor +vendor: + $(GO) mod tidy + $(GO) mod vendor + $(GO) mod verify + if test -n "$(strip $(shell $(GO) env GOTOOLCHAIN))"; then go mod edit -toolchain none ; fi + +.PHONY: lint +lint: install.tools + ./tests/tools/build/golangci-lint run $(LINTFLAGS) + ./tests/tools/build/golangci-lint run --tests=false $(LINTFLAGS) + +# CAUTION: This is not a replacement for RPMs provided by your distro. +# Only intended to build and test the latest unreleased changes. +.PHONY: rpm +rpm: ## Build rpm packages + $(MAKE) -C rpm + +# Remember that rpms install exec to /usr/bin/buildah while a `make install` +# installs them to /usr/local/bin/buildah which is likely before. Always use +# a full path to test installed buildah or you risk to call another executable. +.PHONY: rpm-install +rpm-install: package ## Install rpm packages + $(call err_if_empty,PKG_MANAGER) -y install rpm/RPMS/*/*.rpm + /usr/bin/buildah version diff --git a/vendor/github.com/containers/buildah/OWNERS b/vendor/github.com/containers/buildah/OWNERS new file mode 100644 index 00000000..4deab296 --- /dev/null +++ b/vendor/github.com/containers/buildah/OWNERS @@ -0,0 +1,25 @@ +approvers: + - baude + - flouthoc + - giuseppe + - lsm5 + - Luap99 + - mheon + - mtrmac + - nalind + - rhatdan + - TomSweeneyRedHat +reviewers: + - ashley-cui + - baude + - flouthoc + - giuseppe + - Honny1 + - lsm5 + - Luap99 + - mheon + - mtrmac + - nalind + - rhatdan + - TomSweeneyRedHat + - vrothberg diff --git a/vendor/github.com/containers/buildah/README.md b/vendor/github.com/containers/buildah/README.md new file mode 100644 index 00000000..04c79307 --- /dev/null +++ b/vendor/github.com/containers/buildah/README.md @@ -0,0 +1,136 @@ +![buildah logo (light)](logos/buildah-logo_large.png#gh-light-mode-only) +![buildah logo (dark)](logos/buildah-logo_reverse_large.png#gh-dark-mode-only) + +# [Buildah](https://www.youtube.com/embed/YVk5NgSiUw8) - a tool that facilitates building [Open Container Initiative (OCI)](https://www.opencontainers.org/) container images + +[![Go Report Card](https://goreportcard.com/badge/github.com/containers/buildah)](https://goreportcard.com/report/github.com/containers/buildah) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10579/badge)](https://www.bestpractices.dev/projects/10579) + + +The Buildah package provides a command line tool that can be used to +* create a working container, either from scratch or using an image as a starting point +* create an image, either from a working container or via the instructions in a Dockerfile +* images can be built in either the OCI image format or the traditional upstream docker image format +* mount a working container's root filesystem for manipulation +* unmount a working container's root filesystem +* use the updated contents of a container's root filesystem as a filesystem layer to create a new image +* delete a working container or an image +* rename a local container + +## Buildah Information for Developers + +For blogs, release announcements and more, please checkout the [buildah.io](https://buildah.io) website! + +**[Buildah Container Images](https://github.com/containers/image_build/blob/main/buildah/README.md)** + +**[Buildah Demos](demos)** + +**[Changelog](CHANGELOG.md)** + +**[Contributing](CONTRIBUTING.md)** + +**[Development Plan](developmentplan.md)** + +**[Installation notes](install.md)** + +**[Troubleshooting Guide](troubleshooting.md)** + +**[Tutorials](docs/tutorials)** + +## Buildah and Podman relationship + +Buildah and Podman are two complementary open-source projects that are +available on most Linux platforms and both projects reside at +[GitHub.com](https://github.com) with Buildah +[here](https://github.com/containers/buildah) and Podman +[here](https://github.com/containers/podman). Both, Buildah and Podman are +command line tools that work on Open Container Initiative (OCI) images and +containers. The two projects differentiate in their specialization. + +Buildah specializes in building OCI images. Buildah's commands replicate all +of the commands that are found in a Dockerfile. This allows building images +with and without Dockerfiles while not requiring any root privileges. +Buildah’s ultimate goal is to provide a lower-level coreutils interface to +build images. The flexibility of building images without Dockerfiles allows +for the integration of other scripting languages into the build process. +Buildah follows a simple fork-exec model and does not run as a daemon +but it is based on a comprehensive API in golang, which can be vendored +into other tools. + +Podman specializes in all of the commands and functions that help you to maintain and modify +OCI images, such as pulling and tagging. It also allows you to create, run, and maintain those containers +created from those images. For building container images via Dockerfiles, Podman uses Buildah's +golang API and can be installed independently from Buildah. + +A major difference between Podman and Buildah is their concept of a container. Podman +allows users to create "traditional containers" where the intent of these containers is +to be long lived. While Buildah containers are really just created to allow content +to be added back to the container image. An easy way to think of it is the +`buildah run` command emulates the RUN command in a Dockerfile while the `podman run` +command emulates the `docker run` command in functionality. Because of this and their underlying +storage differences, you can not see Podman containers from within Buildah or vice versa. + +In short, Buildah is an efficient way to create OCI images while Podman allows +you to manage and maintain those images and containers in a production environment using +familiar container cli commands. For more details, see the +[Container Tools Guide](https://github.com/containers/buildah/tree/main/docs/containertools). + +## Example + +From [`./examples/lighttpd.sh`](examples/lighttpd.sh): + +```bash +$ cat > lighttpd.sh <<"EOF" +#!/usr/bin/env bash + +set -x + +ctr1=$(buildah from "${1:-fedora}") + +## Get all updates and install our minimal httpd server +buildah run "$ctr1" -- dnf update -y +buildah run "$ctr1" -- dnf install -y lighttpd + +## Include some buildtime annotations +buildah config --annotation "com.example.build.host=$(uname -n)" "$ctr1" + +## Run our server and expose the port +buildah config --cmd "/usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf" "$ctr1" +buildah config --port 80 "$ctr1" + +## Commit this container to an image name +buildah commit "$ctr1" "${2:-$USER/lighttpd}" +EOF + +$ chmod +x lighttpd.sh +$ ./lighttpd.sh +``` + +## Commands +| Command | Description | +| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| [buildah-add(1)](/docs/buildah-add.1.md) | Add the contents of a file, URL, or a directory to the container. | +| [buildah-build(1)](/docs/buildah-build.1.md) | Build an image using instructions from Containerfiles or Dockerfiles. | +| [buildah-commit(1)](/docs/buildah-commit.1.md) | Create an image from a working container. | +| [buildah-config(1)](/docs/buildah-config.1.md) | Update image configuration settings. | +| [buildah-containers(1)](/docs/buildah-containers.1.md) | List the working containers and their base images. | +| [buildah-copy(1)](/docs/buildah-copy.1.md) | Copies the contents of a file, URL, or directory into a container's working directory. | +| [buildah-from(1)](/docs/buildah-from.1.md) | Creates a new working container, either from scratch or using a specified image as a starting point. | +| [buildah-images(1)](/docs/buildah-images.1.md) | List images in local storage. | +| [buildah-info(1)](/docs/buildah-info.1.md) | Display Buildah system information. | +| [buildah-inspect(1)](/docs/buildah-inspect.1.md) | Inspects the configuration of a container or image. | +| [buildah-mount(1)](/docs/buildah-mount.1.md) | Mount the working container's root filesystem. | +| [buildah-pull(1)](/docs/buildah-pull.1.md) | Pull an image from the specified location. | +| [buildah-push(1)](/docs/buildah-push.1.md) | Push an image from local storage to elsewhere. | +| [buildah-rename(1)](/docs/buildah-rename.1.md) | Rename a local container. | +| [buildah-rm(1)](/docs/buildah-rm.1.md) | Removes one or more working containers. | +| [buildah-rmi(1)](/docs/buildah-rmi.1.md) | Removes one or more images. | +| [buildah-run(1)](/docs/buildah-run.1.md) | Run a command inside of the container. | +| [buildah-tag(1)](/docs/buildah-tag.1.md) | Add an additional name to a local image. | +| [buildah-umount(1)](/docs/buildah-umount.1.md) | Unmount a working container's root file system. | +| [buildah-unshare(1)](/docs/buildah-unshare.1.md) | Launch a command in a user namespace with modified ID mappings. | +| [buildah-version(1)](/docs/buildah-version.1.md) | Display the Buildah Version Information | + +**Future goals include:** +* more CI tests +* additional CLI commands (?) diff --git a/vendor/github.com/containers/buildah/ROADMAP.md b/vendor/github.com/containers/buildah/ROADMAP.md new file mode 100644 index 00000000..ae67904d --- /dev/null +++ b/vendor/github.com/containers/buildah/ROADMAP.md @@ -0,0 +1,48 @@ +![buildah logo (light)](logos/buildah-logo_large.png#gh-light-mode-only) +![buildah logo (dark)](logos/buildah-logo_reverse_large.png#gh-dark-mode-only) + +# Buildah Roadmap + +The Buildah development team reviews feature requests from its various stakeholders for consideration +quarterly along with the Podman Development team. These features are then prioritized and the top +features are then assigned to one or more engineers. + + +## Future feature considerations + +The following features are of general importantance to Buildah. While these features have no timeline +associated with them yet, they will likely be on future quarterly milestones. + +* Ongoing work around partial pull support (zstd:chunked) +* Improved support for the BuildKit API. +* Performance and stability improvements. +* Reductions to the size of the Buildah binary. + +## Milestones and commitments by quarter + +This section is a historical account of what features were prioritized by quarter. Results of the prioritization will be added at start of each quarter (Jan, Apr, July, Oct). + +### 2025 Q2 #### + +#### Releases #### +- [ ] Buildah 1.40 + +#### Features #### +- [ ] Reduce binary size of Buildah +- [ ] Additional Containerfile command options + +#### CNCF #### +- [ ] Add and adhere to Governance model +- [ ] Update Maintainers file + +### 2025 Q1 #### + +#### Releases #### +- [x] Buildah 1.39 + +#### Features #### +- [x] Artifact add --options + +#### CNCF #### +- [x] Create Governance documentation +- [x] Create Maintainers file diff --git a/vendor/github.com/containers/buildah/SECURITY.md b/vendor/github.com/containers/buildah/SECURITY.md new file mode 100644 index 00000000..dfc531a5 --- /dev/null +++ b/vendor/github.com/containers/buildah/SECURITY.md @@ -0,0 +1,3 @@ +## Security and Disclosure Information Policy for the Buildah Project + +The Buildah Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/main/SECURITY.md) for the Containers Projects. diff --git a/vendor/github.com/containers/buildah/add.go b/vendor/github.com/containers/buildah/add.go new file mode 100644 index 00000000..88049015 --- /dev/null +++ b/vendor/github.com/containers/buildah/add.go @@ -0,0 +1,1021 @@ +package buildah + +import ( + "archive/tar" + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "slices" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "github.com/containers/buildah/copier" + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal/tmpdir" + "github.com/containers/buildah/pkg/chrootuser" + "github.com/docker/go-connections/tlsconfig" + "github.com/hashicorp/go-multierror" + "github.com/moby/sys/userns" + digest "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/retry" + "go.podman.io/image/v5/pkg/tlsclientconfig" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/regexp" +) + +// AddAndCopyOptions holds options for add and copy commands. +type AddAndCopyOptions struct { + // Chmod sets the access permissions of the destination content. + Chmod string + // Chown is a spec for the user who should be given ownership over the + // newly-added content, potentially overriding permissions which would + // otherwise be set to 0:0. + Chown string + // Checksum is a standard container digest string (e.g. :) + // and is the expected hash of the content being copied. + Checksum string + // PreserveOwnership, if Chown is not set, tells us to avoid setting + // ownership of copied items to 0:0, instead using whatever ownership + // information is already set. Not meaningful for remote sources or + // local archives that we extract. + PreserveOwnership bool + // All of the data being copied will pass through Hasher, if set. + // If the sources are URLs or files, their contents will be passed to + // Hasher. + // If the sources include directory trees, Hasher will be passed + // tar-format archives of the directory trees. + Hasher io.Writer + // Excludes is the contents of the .containerignore file. + Excludes []string + // IgnoreFile is the path to the .containerignore file. + IgnoreFile string + // ContextDir is the base directory for content being copied and + // Excludes patterns. + ContextDir string + // ID mapping options to use when contents to be copied are part of + // another container, and need ownerships to be mapped from the host to + // that container's values before copying them into the container. + IDMappingOptions *define.IDMappingOptions + // DryRun indicates that the content should be digested, but not actually + // copied into the container. + DryRun bool + // Clear the setuid bit on items being copied. Has no effect on + // archives being extracted, where the bit is always preserved. + StripSetuidBit bool + // Clear the setgid bit on items being copied. Has no effect on + // archives being extracted, where the bit is always preserved. + StripSetgidBit bool + // Clear the sticky bit on items being copied. Has no effect on + // archives being extracted, where the bit is always preserved. + StripStickyBit bool + // If not "", a directory containing a CA certificate (ending with + // ".crt"), a client certificate (ending with ".cert") and a client + // certificate key (ending with ".key") used when downloading sources + // from locations protected with TLS. + CertPath string + // Allow downloading sources from HTTPS where TLS verification fails. + InsecureSkipTLSVerify types.OptionalBool + // MaxRetries is the maximum number of attempts we'll make to retrieve + // contents from a remote location. + MaxRetries int + // RetryDelay is how long to wait before retrying attempts to retrieve + // remote contents. + RetryDelay time.Duration + // Parents specifies that we should preserve either all of the parent + // directories of source locations, or the ones which follow "/./" in + // the source paths for source locations which include such a + // component. + Parents bool + // Timestamp is a timestamp to override on all content as it is being read. + Timestamp *time.Time + // Link, when set to true, creates an independent layer containing the copied content + // that sits on top of existing layers. This layer can be cached and reused + // separately, and is not affected by filesystem changes from previous instructions. + Link bool + // BuildMetadata is consulted only when Link is true. Contains metadata used by + // imagebuildah for cache evaluation of linked layers (inheritLabels, unsetAnnotations, + // inheritAnnotations, newAnnotations). This field is internally managed and should + // not be set by external API users. + BuildMetadata string +} + +// gitURLFragmentSuffix matches fragments to use as Git reference and build +// context from the Git repository e.g. +// +// github.com/containers/buildah.git +// github.com/containers/buildah.git#main +// github.com/containers/buildah.git#v1.35.0 +var gitURLFragmentSuffix = regexp.Delayed(`\.git(?:#.+)?$`) + +// sourceIsGit returns true if "source" is a git location. +func sourceIsGit(source string) bool { + return isURL(source) && gitURLFragmentSuffix.MatchString(source) +} + +func isURL(url string) bool { + return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") +} + +// sourceIsRemote returns true if "source" is a remote location +// and *not* a git repo. Certain github urls such as raw.github.* are allowed. +func sourceIsRemote(source string) bool { + return isURL(source) && !gitURLFragmentSuffix.MatchString(source) +} + +// getURL writes a tar archive containing the named content +func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string, writer io.Writer, chmod *os.FileMode, srcDigest digest.Digest, certPath string, insecureSkipTLSVerify types.OptionalBool, timestamp *time.Time) error { + url, err := url.Parse(src) + if err != nil { + return err + } + tlsClientConfig := &tls.Config{ + // As of 2025-08, tlsconfig.ClientDefault() differs from Go 1.23 defaults only in CipherSuites; + // so, limit us to only using that value. If go-connections/tlsconfig changes its policy, we + // will want to consider that and make a decision whether to follow suit. + // There is some chance that eventually the Go default will be to require TLS 1.3, and that point + // we might want to drop the dependency on go-connections entirely. + CipherSuites: tlsconfig.ClientDefault().CipherSuites, + } + if err := tlsclientconfig.SetupCertificates(certPath, tlsClientConfig); err != nil { + return err + } + tlsClientConfig.InsecureSkipVerify = insecureSkipTLSVerify == types.OptionalBoolTrue + + tr := &http.Transport{ + TLSClientConfig: tlsClientConfig, + Proxy: http.ProxyFromEnvironment, + } + httpClient := &http.Client{Transport: tr} + response, err := httpClient.Get(src) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusBadRequest { + return fmt.Errorf("invalid response status %d", response.StatusCode) + } + + // Figure out what to name the new content. + name := renameTarget + if name == "" { + name = path.Base(url.Path) + } + // If there's a date on the content, use it. If not, use the Unix epoch + // or a specified value for compatibility. + date := time.Unix(0, 0).UTC() + if timestamp != nil { + date = timestamp.UTC() + } else { + lastModified := response.Header.Get("Last-Modified") + if lastModified != "" { + d, err := time.Parse(time.RFC1123, lastModified) + if err != nil { + return fmt.Errorf("parsing last-modified time %q: %w", lastModified, err) + } + date = d.UTC() + } + } + // Figure out the size of the content. + size := response.ContentLength + var responseBody io.Reader = response.Body + if size < 0 { + // Create a temporary file and copy the content to it, so that + // we can figure out how much content there is. + f, err := os.CreateTemp(mountpoint, "download") + if err != nil { + return fmt.Errorf("creating temporary file to hold %q: %w", src, err) + } + defer os.Remove(f.Name()) + defer f.Close() + size, err = io.Copy(f, response.Body) + if err != nil { + return fmt.Errorf("writing %q to temporary file %q: %w", src, f.Name(), err) + } + _, err = f.Seek(0, io.SeekStart) + if err != nil { + return fmt.Errorf("setting up to read %q from temporary file %q: %w", src, f.Name(), err) + } + responseBody = f + } + var digester digest.Digester + if srcDigest != "" { + digester = srcDigest.Algorithm().Digester() + responseBody = io.TeeReader(responseBody, digester.Hash()) + } + // Write the output archive. Set permissions for compatibility. + tw := tar.NewWriter(writer) + defer tw.Close() + uid := 0 + gid := 0 + if chown != nil { + uid = chown.UID + gid = chown.GID + } + var mode int64 = 0o600 + if chmod != nil { + mode = int64(*chmod) + } + hdr := tar.Header{ + Typeflag: tar.TypeReg, + Name: name, + Size: size, + Uid: uid, + Gid: gid, + Mode: mode, + ModTime: date, + } + err = tw.WriteHeader(&hdr) + if err != nil { + return fmt.Errorf("writing header: %w", err) + } + + if _, err := io.Copy(tw, responseBody); err != nil { + return fmt.Errorf("writing content from %q to tar stream: %w", src, err) + } + + if digester != nil { + if responseDigest := digester.Digest(); responseDigest != srcDigest { + return fmt.Errorf("unexpected response digest for %q: %s, want %s", src, responseDigest, srcDigest) + } + } + + return nil +} + +// includeDirectoryAnyway returns true if "path" is a prefix for an exception +// known to "pm". If "path" is a directory that "pm" claims matches its list +// of patterns, but "pm"'s list of exclusions contains a pattern for which +// "path" is a prefix, then IncludeDirectoryAnyway() will return true. +// This is not always correct, because it relies on the directory part of any +// exception paths to be specified without wildcards. +func includeDirectoryAnyway(path string, pm *fileutils.PatternMatcher) bool { + if !pm.Exclusions() { + return false + } + prefix := strings.TrimPrefix(path, string(os.PathSeparator)) + string(os.PathSeparator) + for _, pattern := range pm.Patterns() { + if !pattern.Exclusion() { + continue + } + spec := strings.TrimPrefix(pattern.String(), string(os.PathSeparator)) + if strings.HasPrefix(spec, prefix) { + return true + } + } + return false +} + +// globbedToGlobbable takes a pathname which might include the '[', *, or ? +// characters, and converts it into a glob pattern that matches itself by +// marking the '[' characters as _not_ the beginning of match ranges and +// escaping the * and ? characters. +func globbedToGlobbable(glob string) string { + result := glob + result = strings.ReplaceAll(result, "[", "[[]") + result = strings.ReplaceAll(result, "?", "\\?") + result = strings.ReplaceAll(result, "*", "\\*") + return result +} + +// getParentsPrefixToRemoveAndParentsToSkip gets from the pattern the prefix before the "pivot point", +// the location in the source path marked by the path component named "." +// (i.e. where "/./" occurs in the path). And list of parents to skip. +// In case "/./" is not present is returned "/". +func getParentsPrefixToRemoveAndParentsToSkip(pattern string, contextDir string) (string, []string) { + prefix, _, found := strings.Cut(strings.TrimPrefix(pattern, contextDir), "/./") + if !found { + return string(filepath.Separator), []string{} + } + prefix = strings.TrimPrefix(filepath.Clean(string(filepath.Separator)+prefix), string(filepath.Separator)) + out := []string{} + parentPath := prefix + for parentPath != "/" && parentPath != "." { + out = append(out, parentPath) + parentPath = filepath.Dir(parentPath) + } + return prefix, out +} + +// Add copies the contents of the specified sources into the container's root +// filesystem, optionally extracting contents of local files that look like +// non-empty archives. +func (b *Builder) Add(destination string, extract bool, options AddAndCopyOptions, sources ...string) error { + mountPoint, err := b.Mount(b.MountLabel) + if err != nil { + return err + } + defer func() { + if err2 := b.Unmount(); err2 != nil { + logrus.Errorf("error unmounting container: %v", err2) + } + }() + + contextDir := options.ContextDir + currentDir := options.ContextDir + if options.ContextDir == "" { + contextDir = string(os.PathSeparator) + currentDir, err = os.Getwd() + if err != nil { + return fmt.Errorf("determining current working directory: %w", err) + } + } else { + if !filepath.IsAbs(options.ContextDir) { + contextDir, err = filepath.Abs(options.ContextDir) + if err != nil { + return fmt.Errorf("converting context directory path %q to an absolute path: %w", options.ContextDir, err) + } + } + } + + // Figure out what sorts of sources we have. + var localSources, remoteSources, gitSources []string + for i, src := range sources { + if src == "" { + return errors.New("empty source location") + } + if sourceIsRemote(src) { + remoteSources = append(remoteSources, src) + continue + } + if sourceIsGit(src) { + gitSources = append(gitSources, src) + continue + } + if !filepath.IsAbs(src) && options.ContextDir == "" { + sources[i] = filepath.Join(currentDir, src) + } + localSources = append(localSources, sources[i]) + } + + // Treat git sources as a subset of remote sources + // differentiating only in how we fetch the two later on. + if len(gitSources) > 0 { + remoteSources = append(remoteSources, gitSources...) + } + + // Check how many items our local source specs matched. Each spec + // should have matched at least one item, otherwise we consider it an + // error. + var localSourceStats []*copier.StatsForGlob + if len(localSources) > 0 { + statOptions := copier.StatOptions{ + CheckForArchives: extract, + } + localSourceStats, err = copier.Stat(contextDir, contextDir, statOptions, localSources) + if err != nil { + return fmt.Errorf("checking on sources under %q: %w", contextDir, err) + } + } + numLocalSourceItems := 0 + for _, localSourceStat := range localSourceStats { + if localSourceStat.Error != "" { + errorText := localSourceStat.Error + rel, err := filepath.Rel(contextDir, localSourceStat.Glob) + if err != nil { + errorText = fmt.Sprintf("%v; %s", err, errorText) + } + if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + errorText = fmt.Sprintf("possible escaping context directory error: %s", errorText) + } + return fmt.Errorf("checking on sources under %q: %v", contextDir, errorText) + } + if len(localSourceStat.Globbed) == 0 { + return fmt.Errorf("checking source under %q: no glob matches: %w", contextDir, syscall.ENOENT) + } + numLocalSourceItems += len(localSourceStat.Globbed) + } + if numLocalSourceItems+len(remoteSources)+len(gitSources) == 0 { + return fmt.Errorf("no sources %v found: %w", sources, syscall.ENOENT) + } + + // Find out which user (and group) the destination should belong to. + var chownDirs, chownFiles *idtools.IDPair + var userUID, userGID uint32 + if options.Chown != "" { + userUID, userGID, err = b.userForCopy(mountPoint, options.Chown) + if err != nil { + return fmt.Errorf("looking up UID/GID for %q: %w", options.Chown, err) + } + } + var chmodDirsFiles *os.FileMode + if options.Chmod != "" { + p, err := strconv.ParseUint(options.Chmod, 8, 32) + if err != nil { + return fmt.Errorf("parsing chmod %q: %w", options.Chmod, err) + } + perm := os.FileMode(p) + chmodDirsFiles = &perm + } + + chownDirs = &idtools.IDPair{UID: int(userUID), GID: int(userGID)} + chownFiles = &idtools.IDPair{UID: int(userUID), GID: int(userGID)} + if options.Chown == "" && options.PreserveOwnership { + chownDirs = nil + chownFiles = nil + } + + // If we have a single source archive to extract, or more than one + // source item, or the destination has a path separator at the end of + // it, and it's not a remote URL, the destination needs to be a + // directory. + destMustBeDirectory := strings.HasSuffix(destination, string(os.PathSeparator)) || strings.HasSuffix(destination, string(os.PathSeparator)+".") // keep this in sync with github.com/openshift/imagebuilder.hasSlash() + destMustBeDirectory = destMustBeDirectory || destination == "" || (len(sources) > 1) + if destination == "" || !filepath.IsAbs(destination) { + tmpDestination := filepath.Join(string(os.PathSeparator)+b.WorkDir(), destination) + if destMustBeDirectory { + destination = tmpDestination + string(os.PathSeparator) + } else { + destination = tmpDestination + } + } + destMustBeDirectory = destMustBeDirectory || (filepath.Clean(destination) == filepath.Clean(b.WorkDir())) + destCanBeFile := false + if len(sources) == 1 { + if len(remoteSources) == 1 { + destCanBeFile = sourceIsRemote(sources[0]) + } + if len(localSources) == 1 { + item := localSourceStats[0].Results[localSourceStats[0].Globbed[0]] + if item.IsDir || (item.IsArchive && extract) { + destMustBeDirectory = true + } + if item.IsRegular { + destCanBeFile = true + } + } + if len(gitSources) > 0 { + destMustBeDirectory = true + } + } + + // We care if the destination either doesn't exist, or exists and is a + // file. If the source can be a single file, for those cases we treat + // the destination as a file rather than as a directory tree. + renameTarget := "" + extractDirectory := filepath.Join(mountPoint, destination) + statOptions := copier.StatOptions{ + CheckForArchives: extract, + } + destStats, err := copier.Stat(mountPoint, filepath.Join(mountPoint, b.WorkDir()), statOptions, []string{extractDirectory}) + if err != nil { + return fmt.Errorf("checking on destination %v: %w", extractDirectory, err) + } + if (len(destStats) == 0 || len(destStats[0].Globbed) == 0) && !destMustBeDirectory && destCanBeFile { + // destination doesn't exist - extract to parent and rename the incoming file to the destination's name + renameTarget = filepath.Base(extractDirectory) + extractDirectory = filepath.Dir(extractDirectory) + } + + // if the destination is a directory that doesn't yet exist, let's copy it. + newDestDirFound := (len(destStats) == 1 || len(destStats[0].Globbed) == 0) && destMustBeDirectory && !destCanBeFile + + if len(destStats) == 1 && len(destStats[0].Globbed) == 1 && destStats[0].Results[destStats[0].Globbed[0]].IsRegular { + if destMustBeDirectory { + return fmt.Errorf("destination %v already exists but is not a directory", destination) + } + // destination exists - it's a file, we need to extract to parent and rename the incoming file to the destination's name + renameTarget = filepath.Base(extractDirectory) + extractDirectory = filepath.Dir(extractDirectory) + } + + pm, err := fileutils.NewPatternMatcher(options.Excludes) + if err != nil { + return fmt.Errorf("processing excludes list %v: %w", options.Excludes, err) + } + + // Make sure that, if it's a symlink, we'll chroot to the target of the link; + // knowing that target requires that we resolve it within the chroot. + evalOptions := copier.EvalOptions{} + evaluated, err := copier.Eval(mountPoint, extractDirectory, evalOptions) + if err != nil { + return fmt.Errorf("checking on destination %v: %w", extractDirectory, err) + } + extractDirectory = evaluated + + // Set up ID maps. + var srcUIDMap, srcGIDMap []idtools.IDMap + if options.IDMappingOptions != nil { + srcUIDMap, srcGIDMap = convertRuntimeIDMaps(options.IDMappingOptions.UIDMap, options.IDMappingOptions.GIDMap) + } + destUIDMap, destGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) + + var putRoot, putDir, stagingDir string + var createdDirs []string + var latestTimestamp time.Time + + mkdirOptions := copier.MkdirOptions{ + UIDMap: destUIDMap, + GIDMap: destGIDMap, + ChownNew: chownDirs, + } + + // If --link is specified, we create a staging directory to hold the content + // that will then become an independent layer + if options.Link { + containerDir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return fmt.Errorf("getting container directory for %q: %w", b.ContainerID, err) + } + + stagingDir, err = os.MkdirTemp(containerDir, "link-stage-") + if err != nil { + return fmt.Errorf("creating staging directory for link %q: %w", b.ContainerID, err) + } + + putRoot = stagingDir + + cleanDest := filepath.Clean(destination) + + if strings.Contains(cleanDest, "..") { + return fmt.Errorf("invalid destination path %q: contains path traversal", destination) + } + + if renameTarget != "" { + putDir = filepath.Dir(filepath.Join(stagingDir, cleanDest)) + } else { + putDir = filepath.Join(stagingDir, cleanDest) + } + + putDirAbs, err := filepath.Abs(putDir) + if err != nil { + return fmt.Errorf("failed to resolve absolute path: %w", err) + } + + stagingDirAbs, err := filepath.Abs(stagingDir) + if err != nil { + return fmt.Errorf("failed to resolve staging directory absolute path: %w", err) + } + + if !strings.HasPrefix(putDirAbs, stagingDirAbs+string(os.PathSeparator)) && putDirAbs != stagingDirAbs { + return fmt.Errorf("destination path %q escapes staging directory", destination) + } + if err := copier.Mkdir(putRoot, putDirAbs, mkdirOptions); err != nil { + return fmt.Errorf("ensuring target directory exists: %w", err) + } + tempPath := putDir + for tempPath != stagingDir && tempPath != filepath.Dir(tempPath) { + if _, err := os.Stat(tempPath); err == nil { + createdDirs = append(createdDirs, tempPath) + } + tempPath = filepath.Dir(tempPath) + } + } else { + if err := copier.Mkdir(mountPoint, extractDirectory, mkdirOptions); err != nil { + return fmt.Errorf("ensuring target directory exists: %w", err) + } + + putRoot = extractDirectory + putDir = extractDirectory + } + + // Copy each source in turn. + for _, src := range sources { + var multiErr *multierror.Error + var getErr, closeErr, renameErr, putErr error + var wg sync.WaitGroup + if sourceIsRemote(src) || sourceIsGit(src) { + pipeReader, pipeWriter := io.Pipe() + var srcDigest digest.Digest + if options.Checksum != "" { + srcDigest, err = digest.Parse(options.Checksum) + if err != nil { + return fmt.Errorf("invalid checksum flag: %w", err) + } + } + + wg.Add(1) + if sourceIsGit(src) { + go func() { + defer wg.Done() + defer pipeWriter.Close() + var cloneDir, subdir string + cloneDir, subdir, getErr = define.TempDirForURL(tmpdir.GetTempDir(), "", src) + if getErr != nil { + return + } + getOptions := copier.GetOptions{ + UIDMap: srcUIDMap, + GIDMap: srcGIDMap, + Excludes: options.Excludes, + ExpandArchives: extract, + ChownDirs: chownDirs, + ChmodDirs: chmodDirsFiles, + ChownFiles: chownFiles, + ChmodFiles: chmodDirsFiles, + StripSetuidBit: options.StripSetuidBit, + StripSetgidBit: options.StripSetgidBit, + StripStickyBit: options.StripStickyBit, + Timestamp: options.Timestamp, + } + writer := io.WriteCloser(pipeWriter) + repositoryDir := filepath.Join(cloneDir, subdir) + getErr = copier.Get(repositoryDir, repositoryDir, getOptions, []string{"."}, writer) + }() + } else { + go func() { + getErr = retry.IfNecessary(context.TODO(), func() error { + return getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter, chmodDirsFiles, srcDigest, options.CertPath, options.InsecureSkipTLSVerify, options.Timestamp) + }, &retry.Options{ + MaxRetry: options.MaxRetries, + Delay: options.RetryDelay, + }) + pipeWriter.Close() + wg.Done() + }() + } + + wg.Add(1) + go func() { + b.ContentDigester.Start("") + hashCloser := b.ContentDigester.Hash() + hasher := io.Writer(hashCloser) + if options.Hasher != nil { + hasher = io.MultiWriter(hasher, options.Hasher) + } + if options.DryRun { + _, putErr = io.Copy(hasher, pipeReader) + } else { + putOptions := copier.PutOptions{ + UIDMap: destUIDMap, + GIDMap: destGIDMap, + ChownDirs: nil, + ChmodDirs: nil, + ChownFiles: nil, + ChmodFiles: nil, + IgnoreDevices: userns.RunningInUserNS(), + } + putErr = copier.Put(putRoot, putDir, putOptions, io.TeeReader(pipeReader, hasher)) + } + hashCloser.Close() + pipeReader.Close() + wg.Done() + }() + wg.Wait() + if getErr != nil { + getErr = fmt.Errorf("reading %q: %w", src, getErr) + } + if putErr != nil { + putErr = fmt.Errorf("storing %q: %w", src, putErr) + } + multiErr = multierror.Append(getErr, putErr) + if multiErr != nil && multiErr.ErrorOrNil() != nil { + if len(multiErr.Errors) > 1 { + return multiErr.ErrorOrNil() + } + return multiErr.Errors[0] + } + continue + } + + if options.Checksum != "" { + return fmt.Errorf("checksum flag is not supported for local sources") + } + + // Dig out the result of running glob+stat on this source spec. + var localSourceStat *copier.StatsForGlob + for _, st := range localSourceStats { + if st.Glob == src { + localSourceStat = st + break + } + } + if localSourceStat == nil { + continue + } + // Iterate through every item that matched the glob. + itemsCopied := 0 + for _, globbed := range localSourceStat.Globbed { + rel := globbed + if filepath.IsAbs(globbed) { + if rel, err = filepath.Rel(contextDir, globbed); err != nil { + return fmt.Errorf("computing path of %q relative to %q: %w", globbed, contextDir, err) + } + } + if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return fmt.Errorf("possible escaping context directory error: %q is outside of %q", globbed, contextDir) + } + // Check for dockerignore-style exclusion of this item. + if rel != "." { + excluded, err := pm.Matches(filepath.ToSlash(rel)) //nolint:staticcheck + if err != nil { + return fmt.Errorf("checking if %q(%q) is excluded: %w", globbed, rel, err) + } + if excluded { + // non-directories that are excluded are excluded, no question, but + // directories can only be skipped if we don't have to allow for the + // possibility of finding things to include under them + globInfo := localSourceStat.Results[globbed] + if !globInfo.IsDir || !includeDirectoryAnyway(rel, pm) { + continue + } + } else { + // if the destination is a directory that doesn't yet exist, and is not excluded, let's copy it. + if newDestDirFound { + itemsCopied++ + } + } + } else { + // Make sure we don't trigger a "copied nothing" error for an empty context + // directory if we were told to copy the context directory itself. We won't + // actually copy it, but we need to make sure that we don't produce an error + // due to potentially not having anything in the tarstream that we passed. + itemsCopied++ + } + st := localSourceStat.Results[globbed] + if options.Link && st.ModTime.After(latestTimestamp) { + latestTimestamp = st.ModTime + } + pipeReader, pipeWriter := io.Pipe() + wg.Add(1) + go func() { + renamedItems := 0 + writer := io.WriteCloser(pipeWriter) + if renameTarget != "" { + writer = newTarFilterer(writer, func(hdr *tar.Header) (bool, bool, io.Reader) { + hdr.Name = renameTarget + renamedItems++ + return false, false, nil + }) + } + + if options.Parents { + parentsPrefixToRemove, parentsToSkip := getParentsPrefixToRemoveAndParentsToSkip(src, options.ContextDir) + writer = newTarFilterer(writer, func(hdr *tar.Header) (bool, bool, io.Reader) { + if slices.Contains(parentsToSkip, hdr.Name) && hdr.Typeflag == tar.TypeDir { + return true, false, nil + } + hdr.Name = strings.TrimPrefix(hdr.Name, parentsPrefixToRemove) + hdr.Name = strings.TrimPrefix(hdr.Name, "/") + if hdr.Typeflag == tar.TypeLink { + hdr.Linkname = strings.TrimPrefix(hdr.Linkname, parentsPrefixToRemove) + hdr.Linkname = strings.TrimPrefix(hdr.Linkname, "/") + } + if hdr.Name == "" { + return true, false, nil + } + return false, false, nil + }) + } + writer = newTarFilterer(writer, func(_ *tar.Header) (bool, bool, io.Reader) { + itemsCopied++ + return false, false, nil + }) + getOptions := copier.GetOptions{ + UIDMap: srcUIDMap, + GIDMap: srcGIDMap, + Excludes: options.Excludes, + ExpandArchives: extract, + ChownDirs: chownDirs, + ChmodDirs: chmodDirsFiles, + ChownFiles: chownFiles, + ChmodFiles: chmodDirsFiles, + StripSetuidBit: options.StripSetuidBit, + StripSetgidBit: options.StripSetgidBit, + StripStickyBit: options.StripStickyBit, + Parents: options.Parents, + Timestamp: options.Timestamp, + } + getErr = copier.Get(contextDir, contextDir, getOptions, []string{globbedToGlobbable(globbed)}, writer) + closeErr = writer.Close() + if renameTarget != "" && renamedItems > 1 { + renameErr = fmt.Errorf("internal error: renamed %d items when we expected to only rename 1", renamedItems) + } + wg.Done() + }() + wg.Add(1) + go func() { + if st.IsDir { + b.ContentDigester.Start("dir") + } else { + b.ContentDigester.Start("file") + } + hashCloser := b.ContentDigester.Hash() + hasher := io.Writer(hashCloser) + if options.Hasher != nil { + hasher = io.MultiWriter(hasher, options.Hasher) + } + if options.DryRun { + _, putErr = io.Copy(hasher, pipeReader) + } else { + putOptions := copier.PutOptions{ + UIDMap: destUIDMap, + GIDMap: destGIDMap, + DefaultDirOwner: chownDirs, + DefaultDirMode: nil, + ChownDirs: nil, + ChmodDirs: nil, + ChownFiles: nil, + ChmodFiles: nil, + IgnoreDevices: userns.RunningInUserNS(), + } + putErr = copier.Put(putRoot, putDir, putOptions, io.TeeReader(pipeReader, hasher)) + } + hashCloser.Close() + pipeReader.Close() + wg.Done() + }() + + wg.Wait() + if getErr != nil { + getErr = fmt.Errorf("reading %q: %w", src, getErr) + } + if closeErr != nil { + closeErr = fmt.Errorf("closing %q: %w", src, closeErr) + } + if renameErr != nil { + renameErr = fmt.Errorf("renaming %q: %w", src, renameErr) + } + if putErr != nil { + putErr = fmt.Errorf("storing %q: %w", src, putErr) + } + multiErr = multierror.Append(getErr, closeErr, renameErr, putErr) + if multiErr != nil && multiErr.ErrorOrNil() != nil { + if len(multiErr.Errors) > 1 { + return multiErr.ErrorOrNil() + } + return multiErr.Errors[0] + } + } + if itemsCopied == 0 { + excludesFile := "" + if options.IgnoreFile != "" { + excludesFile = " using " + options.IgnoreFile + } + return fmt.Errorf("no items matching glob %q copied (%d filtered out%s): %w", localSourceStat.Glob, len(localSourceStat.Globbed), excludesFile, syscall.ENOENT) + } + } + + if options.Link { + if !latestTimestamp.IsZero() { + for _, dir := range createdDirs { + if err := os.Chtimes(dir, latestTimestamp, latestTimestamp); err != nil { + logrus.Warnf("failed to set timestamp on directory %q: %v", dir, err) + } + } + } + var created time.Time + if options.Timestamp != nil { + created = *options.Timestamp + } else if !latestTimestamp.IsZero() { + created = latestTimestamp + } else { + created = time.Unix(0, 0).UTC() + } + + command := "ADD" + if !extract { + command = "COPY" + } + + contentType, digest := b.ContentDigester.Digest() + summary := contentType + if digest != "" { + if summary != "" { + summary = summary + ":" + } + summary = summary + digest.Encoded() + logrus.Debugf("added content from --link %s", summary) + } + + createdBy := "/bin/sh -c #(nop) " + command + " --link " + summary + " in " + destination + " " + options.BuildMetadata + history := v1.History{ + Created: &created, + CreatedBy: createdBy, + Comment: b.HistoryComment(), + } + + linkedLayer := LinkedLayer{ + History: history, + BlobPath: stagingDir, + } + + b.AppendedLinkedLayers = append(b.AppendedLinkedLayers, linkedLayer) + + if err := b.Save(); err != nil { + return fmt.Errorf("saving builder state after queuing linked layer: %w", err) + } + } + + return nil +} + +// userForRun returns the user (and group) information which we should use for +// running commands +func (b *Builder) userForRun(mountPoint string, userspec string) (specs.User, string, error) { + if userspec == "" { + userspec = b.User() + } + + uid, gid, homeDir, err := chrootuser.GetUser(mountPoint, userspec) + u := specs.User{ + UID: uid, + GID: gid, + Username: userspec, + } + if !strings.Contains(userspec, ":") { + groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID)) + if err2 != nil { + if !errors.Is(err2, chrootuser.ErrNoSuchUser) && err == nil { + err = err2 + } + } else { + u.AdditionalGids = groups + } + } + return u, homeDir, err +} + +// userForCopy returns the user (and group) information which we should use for +// setting ownership of contents being copied. It's just like what +// userForRun() does, except for the case where we're passed a single numeric +// value, where we need to use that value for both the UID and the GID. +func (b *Builder) userForCopy(mountPoint string, userspec string) (uint32, uint32, error) { + var ( + user, group string + uid, gid uint64 + err error + ) + + split := strings.SplitN(userspec, ":", 2) + user = split[0] + if len(split) > 1 { + group = split[1] + } + + // If userspec did not specify any values for user or group, then fail + if user == "" && group == "" { + return 0, 0, fmt.Errorf("can't find uid for user %s", userspec) + } + + // If userspec specifies values for user or group, check for numeric values + // and return early. If not, then translate username/groupname + if user != "" { + uid, err = strconv.ParseUint(user, 10, 32) + } + if err == nil { + // default gid to uid + gid = uid + if group != "" { + gid, err = strconv.ParseUint(group, 10, 32) + } + } + // If err != nil, then user or group not numeric, check filesystem + if err == nil { + return uint32(uid), uint32(gid), nil + } + + owner, _, err := b.userForRun(mountPoint, userspec) + if err != nil { + return 0xffffffff, 0xffffffff, err + } + return owner.UID, owner.GID, nil +} + +// EnsureContainerPathAs creates the specified directory if it doesn't exist, +// setting a newly-created directory's owner to USER and its permissions to MODE. +func (b *Builder) EnsureContainerPathAs(path, user string, mode *os.FileMode) error { + mountPoint, err := b.Mount(b.MountLabel) + if err != nil { + return err + } + defer func() { + if err2 := b.Unmount(); err2 != nil { + logrus.Errorf("error unmounting container: %v", err2) + } + }() + + uid, gid := uint32(0), uint32(0) + if user != "" { + if uidForCopy, gidForCopy, err := b.userForCopy(mountPoint, user); err == nil { + uid = uidForCopy + gid = gidForCopy + } + } + + destUIDMap, destGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) + + idPair := &idtools.IDPair{UID: int(uid), GID: int(gid)} + opts := copier.MkdirOptions{ + ChmodNew: mode, + ChownNew: idPair, + UIDMap: destUIDMap, + GIDMap: destGIDMap, + } + return copier.Mkdir(mountPoint, filepath.Join(mountPoint, path), opts) +} diff --git a/vendor/github.com/containers/buildah/bind/mount.go b/vendor/github.com/containers/buildah/bind/mount.go new file mode 100644 index 00000000..5153dfc5 --- /dev/null +++ b/vendor/github.com/containers/buildah/bind/mount.go @@ -0,0 +1,303 @@ +//go:build linux + +package bind + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "syscall" + + "github.com/containers/buildah/util" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/mount" + "golang.org/x/sys/unix" +) + +// SetupIntermediateMountNamespace creates a new mount namespace and bind +// mounts all bind-mount sources into a subdirectory of bundlePath that can +// only be reached by the root user of the container's user namespace, except +// for Mounts which include the NoBindOption option in their options list. The +// NoBindOption will then merely be removed. +func SetupIntermediateMountNamespace(spec *specs.Spec, bundlePath string) (unmountAll func() error, err error) { + defer stripNoBindOption(spec) + + // We expect a root directory to be defined. + if spec.Root == nil { + return nil, errors.New("configuration has no root filesystem?") + } + rootPath := spec.Root.Path + + // Create a new mount namespace in which to do the things we're doing. + if err := unix.Unshare(unix.CLONE_NEWNS); err != nil { + return nil, fmt.Errorf("creating new mount namespace for %v: %w", spec.Process.Args, err) + } + + // Make all of our mounts private to our namespace. + if err := mount.MakeRPrivate("/"); err != nil { + return nil, fmt.Errorf("making mounts private to mount namespace for %v: %w", spec.Process.Args, err) + } + + // Make sure the bundle directory is searchable. We created it with + // TempDir(), so it should have started with permissions set to 0700. + info, err := os.Stat(bundlePath) + if err != nil { + return nil, fmt.Errorf("checking permissions on %q: %w", bundlePath, err) + } + if err = os.Chmod(bundlePath, info.Mode()|0o111); err != nil { + return nil, fmt.Errorf("loosening permissions on %q: %w", bundlePath, err) + } + + // Figure out who needs to be able to reach these bind mounts in order + // for the container to be started. + rootUID, rootGID, err := util.GetHostRootIDs(spec) + if err != nil { + return nil, err + } + + // Hand back a callback that the caller can use to clean up everything + // we're doing here. + unmount := []string{} + unmountAll = func() (err error) { + for _, mountpoint := range unmount { + // Unmount it and anything under it. + if err2 := UnmountMountpoints(mountpoint, nil); err2 != nil { + logrus.Warnf("pkg/bind: error unmounting %q: %v", mountpoint, err2) + if err == nil { + err = err2 + } + } + if err2 := unix.Unmount(mountpoint, unix.MNT_DETACH); err2 != nil { + if errno, ok := err2.(syscall.Errno); !ok || errno != syscall.EINVAL { + logrus.Warnf("pkg/bind: error detaching %q: %v", mountpoint, err2) + if err == nil { + err = err2 + } + } + } + // Remove just the mountpoint. + retry := 10 + remove := unix.Unlink + err2 := remove(mountpoint) + for err2 != nil && retry > 0 { + if errno, ok := err2.(syscall.Errno); ok { + switch errno { + default: + retry = 0 + continue + case syscall.EISDIR: + remove = unix.Rmdir + err2 = remove(mountpoint) + case syscall.EBUSY: + if err3 := unix.Unmount(mountpoint, unix.MNT_DETACH); err3 == nil { + err2 = remove(mountpoint) + } + } + retry-- + } + } + if err2 != nil { + logrus.Warnf("pkg/bind: error removing %q: %v", mountpoint, err2) + if err == nil { + err = err2 + } + } + } + return err + } + + // Create a top-level directory that the "root" user will be able to + // access, that "root" from containers which use different mappings, or + // other unprivileged users outside of containers, shouldn't be able to + // access. + mnt := filepath.Join(bundlePath, "mnt") + if err = idtools.MkdirAndChown(mnt, 0o100, idtools.IDPair{UID: int(rootUID), GID: int(rootGID)}); err != nil { + return unmountAll, fmt.Errorf("creating %q owned by the container's root user: %w", mnt, err) + } + + // Make that directory private, and add it to the list of locations we + // unmount at cleanup time. + if err = mount.MakeRPrivate(mnt); err != nil { + return unmountAll, fmt.Errorf("marking filesystem at %q as private: %w", mnt, err) + } + unmount = append([]string{mnt}, unmount...) + + // Create a bind mount for the root filesystem and add it to the list. + rootfs := filepath.Join(mnt, "rootfs") + if err = os.Mkdir(rootfs, 0o000); err != nil { + return unmountAll, fmt.Errorf("creating directory %q: %w", rootfs, err) + } + if err = unix.Mount(rootPath, rootfs, "", unix.MS_BIND|unix.MS_REC|unix.MS_PRIVATE, ""); err != nil { + return unmountAll, fmt.Errorf("bind mounting root filesystem from %q to %q: %w", rootPath, rootfs, err) + } + logrus.Debugf("bind mounted %q to %q", rootPath, rootfs) + unmount = append([]string{rootfs}, unmount...) + spec.Root.Path = rootfs + + // Do the same for everything we're binding in. + mounts := make([]specs.Mount, 0, len(spec.Mounts)) + for i := range spec.Mounts { + // If we're not using an intermediate, leave it in the list. + if leaveBindMountAlone(spec.Mounts[i]) { + mounts = append(mounts, spec.Mounts[i]) + continue + } + // Check if the source is a directory or something else. + info, err := os.Stat(spec.Mounts[i].Source) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + logrus.Warnf("couldn't find %q on host to bind mount into container", spec.Mounts[i].Source) + continue + } + return unmountAll, fmt.Errorf("checking if %q is a directory: %w", spec.Mounts[i].Source, err) + } + stage := filepath.Join(mnt, fmt.Sprintf("buildah-bind-target-%d", i)) + if info.IsDir() { + // If the source is a directory, make one to use as the + // mount target. + if err = os.Mkdir(stage, 0o000); err != nil { + return unmountAll, fmt.Errorf("creating directory %q: %w", stage, err) + } + } else { + // If the source is not a directory, create an empty + // file to use as the mount target. + file, err := os.OpenFile(stage, os.O_WRONLY|os.O_CREATE, 0o000) + if err != nil { + return unmountAll, fmt.Errorf("creating file %q: %w", stage, err) + } + file.Close() + } + // Bind mount the source from wherever it is to a place where + // we know the runtime helper will be able to get to it... + if err = unix.Mount(spec.Mounts[i].Source, stage, "", unix.MS_BIND|unix.MS_REC|unix.MS_PRIVATE, ""); err != nil { + return unmountAll, fmt.Errorf("bind mounting bind object from %q to %q: %w", spec.Mounts[i].Source, stage, err) + } + logrus.Debugf("bind mounted %q to %q", spec.Mounts[i].Source, stage) + spec.Mounts[i].Source = stage + // ... and update the source location that we'll pass to the + // runtime to our intermediate location. + mounts = append(mounts, spec.Mounts[i]) + unmount = append([]string{stage}, unmount...) + } + spec.Mounts = mounts + + return unmountAll, nil +} + +// Decide if the mount should not be redirected to an intermediate location first. +func leaveBindMountAlone(mount specs.Mount) bool { + // If we know we shouldn't do a redirection for this mount, skip it. + if slices.Contains(mount.Options, NoBindOption) { + return true + } + // If we're not bind mounting it in, we don't need to do anything for it. + if mount.Type != "bind" && !slices.Contains(mount.Options, "bind") && !slices.Contains(mount.Options, "rbind") { + return true + } + return false +} + +// UnmountMountpoints unmounts the given mountpoints and anything that's hanging +// off of them, rather aggressively. If a mountpoint also appears in the +// mountpointsToRemove slice, the mountpoints are removed after they are +// unmounted. +func UnmountMountpoints(mountpoint string, mountpointsToRemove []string) error { + mounts, err := mount.GetMounts() + if err != nil { + return fmt.Errorf("retrieving list of mounts: %w", err) + } + // getChildren returns the list of mount IDs that hang off of the + // specified ID. + getChildren := func(id int) []int { + var list []int + for _, info := range mounts { + if info.Parent == id { + list = append(list, info.ID) + } + } + return list + } + // getTree returns the list of mount IDs that hang off of the specified + // ID, and off of those mount IDs, etc. + getTree := func(id int) []int { + mounts := []int{id} + i := 0 + for i < len(mounts) { + children := getChildren(mounts[i]) + mounts = append(mounts, children...) + i++ + } + return mounts + } + // getMountByID looks up the mount info with the specified ID + getMountByID := func(id int) *mount.Info { + for i := range mounts { + if mounts[i].ID == id { + return mounts[i] + } + } + return nil + } + // getMountByPoint looks up the mount info with the specified mountpoint + getMountByPoint := func(mountpoint string) *mount.Info { + for i := range mounts { + if mounts[i].Mountpoint == mountpoint { + return mounts[i] + } + } + return nil + } + // find the top of the tree we're unmounting + top := getMountByPoint(mountpoint) + if top == nil { + if err != nil { + return fmt.Errorf("%q is not mounted: %w", mountpoint, err) + } + return nil + } + // add all of the mounts that are hanging off of it + tree := getTree(top.ID) + // unmount each mountpoint, working from the end of the list (leaf nodes) to the top + for i := range tree { + var st unix.Stat_t + id := tree[len(tree)-i-1] + mount := getMountByID(id) + // check if this mountpoint is mounted + if err := unix.Lstat(mount.Mountpoint, &st); err != nil { + if errors.Is(err, os.ErrNotExist) { + logrus.Debugf("mountpoint %q is not present(?), skipping", mount.Mountpoint) + continue + } + return fmt.Errorf("checking if %q is mounted: %w", mount.Mountpoint, err) + } + if uint64(mount.Major) != uint64(st.Dev) || uint64(mount.Minor) != uint64(st.Dev) { //nolint:unconvert // (required for some OS/arch combinations) + logrus.Debugf("%q is apparently not really mounted, skipping", mount.Mountpoint) + continue + } + // do the unmount + if err := unix.Unmount(mount.Mountpoint, 0); err != nil { + // if it was busy, detach it + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EBUSY { + err = unix.Unmount(mount.Mountpoint, unix.MNT_DETACH) + } + if err != nil { + // if it was invalid (not mounted), hide the error, else return it + if errno, ok := err.(syscall.Errno); !ok || errno != syscall.EINVAL { + logrus.Warnf("error unmounting %q: %v", mount.Mountpoint, err) + continue + } + } + } + // if we're also supposed to remove this thing, do that, too + if slices.Contains(mountpointsToRemove, mount.Mountpoint) { + if err := os.Remove(mount.Mountpoint); err != nil { + return fmt.Errorf("removing %q: %w", mount.Mountpoint, err) + } + } + } + return nil +} diff --git a/vendor/github.com/containers/buildah/bind/mount_unsupported.go b/vendor/github.com/containers/buildah/bind/mount_unsupported.go new file mode 100644 index 00000000..11cee0a4 --- /dev/null +++ b/vendor/github.com/containers/buildah/bind/mount_unsupported.go @@ -0,0 +1,13 @@ +//go:build !linux + +package bind + +import ( + "github.com/opencontainers/runtime-spec/specs-go" +) + +// SetupIntermediateMountNamespace returns a no-op unmountAll() and no error. +func SetupIntermediateMountNamespace(spec *specs.Spec, bundlePath string) (unmountAll func() error, err error) { + stripNoBindOption(spec) + return func() error { return nil }, nil +} diff --git a/vendor/github.com/containers/buildah/bind/util.go b/vendor/github.com/containers/buildah/bind/util.go new file mode 100644 index 00000000..c139dcbc --- /dev/null +++ b/vendor/github.com/containers/buildah/bind/util.go @@ -0,0 +1,28 @@ +package bind + +import ( + "slices" + + "github.com/opencontainers/runtime-spec/specs-go" +) + +const ( + // NoBindOption is an option which, if present in a Mount structure's + // options list, will cause SetupIntermediateMountNamespace to not + // redirect it through a bind mount. + NoBindOption = "nobuildahbind" +) + +func stripNoBindOption(spec *specs.Spec) { + for i := range spec.Mounts { + if slices.Contains(spec.Mounts[i].Options, NoBindOption) { + prunedOptions := make([]string, 0, len(spec.Mounts[i].Options)) + for _, option := range spec.Mounts[i].Options { + if option != NoBindOption { + prunedOptions = append(prunedOptions, option) + } + } + spec.Mounts[i].Options = prunedOptions + } + } +} diff --git a/vendor/github.com/containers/buildah/btrfs_installed_tag.sh b/vendor/github.com/containers/buildah/btrfs_installed_tag.sh new file mode 100644 index 00000000..f2f2b33c --- /dev/null +++ b/vendor/github.com/containers/buildah/btrfs_installed_tag.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +${CPP:-${CC:-cc} -E} ${CPPFLAGS} - > /dev/null 2> /dev/null << EOF +#include +EOF +if test $? -ne 0 ; then + echo exclude_graphdriver_btrfs +fi diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go new file mode 100644 index 00000000..27d67b63 --- /dev/null +++ b/vendor/github.com/containers/buildah/buildah.go @@ -0,0 +1,583 @@ +package buildah + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "time" + + "github.com/containers/buildah/define" + "github.com/containers/buildah/docker" + encconfig "github.com/containers/ocicrypt/config" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" + nettypes "go.podman.io/common/libnetwork/types" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/ioutils" +) + +const ( + // Package is the name of this package, used in help output and to + // identify working containers. + Package = define.Package + // Version for the Package. + Version = define.Version + // The value we use to identify what type of information, currently a + // serialized Builder structure, we are using as per-container state. + // This should only be changed when we make incompatible changes to + // that data structure, as it's used to distinguish containers which + // are "ours" from ones that aren't. + containerType = Package + " 0.0.1" + // The file in the per-container directory which we use to store our + // per-container state. If it isn't there, then the container isn't + // one of our build containers. + stateFile = Package + ".json" +) + +// PullPolicy takes the value PullIfMissing, PullAlways, PullIfNewer, or PullNever. +type PullPolicy = define.PullPolicy + +const ( + // PullIfMissing is one of the values that BuilderOptions.PullPolicy + // can take, signalling that the source image should be pulled from a + // registry if a local copy of it is not already present. + PullIfMissing = define.PullIfMissing + // PullAlways is one of the values that BuilderOptions.PullPolicy can + // take, signalling that a fresh, possibly updated, copy of the image + // should be pulled from a registry before the build proceeds. + PullAlways = define.PullAlways + // PullIfNewer is one of the values that BuilderOptions.PullPolicy + // can take, signalling that the source image should only be pulled + // from a registry if a local copy is not already present or if a + // newer version the image is present on the repository. + PullIfNewer = define.PullIfNewer + // PullNever is one of the values that BuilderOptions.PullPolicy can + // take, signalling that the source image should not be pulled from a + // registry if a local copy of it is not already present. + PullNever = define.PullNever +) + +// NetworkConfigurationPolicy takes the value NetworkDefault, NetworkDisabled, +// or NetworkEnabled. +type NetworkConfigurationPolicy = define.NetworkConfigurationPolicy + +const ( + // NetworkDefault is one of the values that BuilderOptions.ConfigureNetwork + // can take, signalling that the default behavior should be used. + NetworkDefault = define.NetworkDefault + // NetworkDisabled is one of the values that BuilderOptions.ConfigureNetwork + // can take, signalling that network interfaces should NOT be configured for + // newly-created network namespaces. + NetworkDisabled = define.NetworkDisabled + // NetworkEnabled is one of the values that BuilderOptions.ConfigureNetwork + // can take, signalling that network interfaces should be configured for + // newly-created network namespaces. + NetworkEnabled = define.NetworkEnabled +) + +// Builder objects are used to represent containers which are being used to +// build images. They also carry potential updates which will be applied to +// the image's configuration when the container's contents are used to build an +// image. +type Builder struct { + store storage.Store + + // Logger is the logrus logger to write log messages with + Logger *logrus.Logger `json:"-"` + + // Args define variables that users can pass at build-time to the builder. + Args map[string]string + // Type is used to help identify a build container's metadata. It + // should not be modified. + Type string `json:"type"` + // FromImage is the name of the source image which was used to create + // the container, if one was used. It should not be modified. + FromImage string `json:"image,omitempty"` + // FromImageID is the ID of the source image which was used to create + // the container, if one was used. It should not be modified. + FromImageID string `json:"image-id"` + // FromImageDigest is the digest of the source image which was used to + // create the container, if one was used. It should not be modified. + FromImageDigest string `json:"image-digest"` + // Config is the source image's configuration. It should not be + // modified. + Config []byte `json:"config,omitempty"` + // Manifest is the source image's manifest. It should not be modified. + Manifest []byte `json:"manifest,omitempty"` + + // Container is the name of the build container. It should not be modified. + Container string `json:"container-name,omitempty"` + // ContainerID is the ID of the build container. It should not be modified. + ContainerID string `json:"container-id,omitempty"` + // MountPoint is the last location where the container's root + // filesystem was mounted. It should not be modified. + MountPoint string `json:"mountpoint,omitempty"` + // ProcessLabel is the SELinux process label to use during subsequent Run() calls. + ProcessLabel string `json:"process-label,omitempty"` + // MountLabel is the SELinux mount label associated with the container + MountLabel string `json:"mount-label,omitempty"` + + // ImageAnnotations is a set of key-value pairs which is stored in the + // image's manifest. + ImageAnnotations map[string]string `json:"annotations,omitempty"` + // ImageCreatedBy is a description of how this container was built. + ImageCreatedBy string `json:"created-by,omitempty"` + // ImageHistoryComment is a description of how our added layers were built. + ImageHistoryComment string `json:"history-comment,omitempty"` + + // Image metadata and runtime settings, in multiple formats. + OCIv1 v1.Image `json:"ociv1"` + Docker docker.V2Image `json:"docker"` + // DefaultMountsFilePath is the file path holding the mounts to be mounted in "host-path:container-path" format. + DefaultMountsFilePath string `json:"defaultMountsFilePath,omitempty"` + + // Isolation controls how we handle "RUN" statements and the Run() method. + Isolation define.Isolation + // NamespaceOptions controls how we set up the namespaces for processes that we Run(). + NamespaceOptions define.NamespaceOptions + // ConfigureNetwork controls whether or not network interfaces and + // routing are configured for a new network namespace (i.e., when not + // joining another's namespace and not just using the host's + // namespace), effectively deciding whether or not the process has a + // usable network. + ConfigureNetwork define.NetworkConfigurationPolicy + // CNIPluginPath is the location of CNI plugin helpers, if they should be + // run from a location other than the default location. + CNIPluginPath string + // CNIConfigDir is the location of CNI configuration files, if the files in + // the default configuration directory shouldn't be used. + CNIConfigDir string + + // NetworkInterface is the libnetwork network interface used to setup CNI or netavark networks. + NetworkInterface nettypes.ContainerNetwork `json:"-"` + + // GroupAdd is a list of groups to add to the primary process when Run() is + // called. The magic 'keep-groups' value indicates that the process should + // be allowed to inherit the current set of supplementary groups. + GroupAdd []string + // ID mapping options to use when running processes with non-host user namespaces. + IDMappingOptions define.IDMappingOptions + // Capabilities is a list of capabilities to use when running commands in the container. + Capabilities []string + // PrependedEmptyLayers are history entries that we'll add to a + // committed image, after any history items that we inherit from a base + // image, but before the history item for the layer that we're + // committing. + PrependedEmptyLayers []v1.History + // AppendedEmptyLayers are history entries that we'll add to a + // committed image after the history item for the layer that we're + // committing. + AppendedEmptyLayers []v1.History + CommonBuildOpts *define.CommonBuildOptions + // TopLayer is the top layer of the image + TopLayer string + // Format to use for a container image we eventually commit, when we do. + Format string + // TempVolumes are temporary mount points created during Run() calls. + // Deprecated: do not use. + TempVolumes map[string]bool + // ContentDigester counts the digest of all Add()ed content since it was + // last restarted. + ContentDigester CompositeDigester + // Devices are parsed additional devices to provide to Run() calls. + Devices define.ContainerDevices + // DeviceSpecs are unparsed additional devices to provide to Run() calls. + DeviceSpecs []string + // CDIConfigDir is the location of CDI configuration files, if the files in + // the default configuration locations shouldn't be used. + CDIConfigDir string + // PrependedLinkedLayers and AppendedLinkedLayers are combinations of + // history entries and locations of either directory trees (if + // directories, per os.Stat()) or uncompressed layer blobs which should + // be added to the image at commit-time. The order of these relative + // to PrependedEmptyLayers and AppendedEmptyLayers in the committed + // image is not guaranteed. + PrependedLinkedLayers, AppendedLinkedLayers []LinkedLayer +} + +// BuilderInfo are used as objects to display container information +type BuilderInfo struct { + Type string + FromImage string + FromImageID string + FromImageDigest string + GroupAdd []string + Config string + Manifest string + Container string + ContainerID string + MountPoint string + ProcessLabel string + MountLabel string + ImageAnnotations map[string]string + ImageCreatedBy string + OCIv1 v1.Image + Docker docker.V2Image + DefaultMountsFilePath string + Isolation string + NamespaceOptions define.NamespaceOptions + Capabilities []string + ConfigureNetwork string + CNIPluginPath string + CNIConfigDir string + IDMappingOptions define.IDMappingOptions + History []v1.History + Devices define.ContainerDevices + DeviceSpecs []string + CDIConfigDir string +} + +// GetBuildInfo gets a pointer to a Builder object and returns a BuilderInfo object from it. +// This is used in the inspect command to display Manifest and Config as string and not []byte. +func GetBuildInfo(b *Builder) BuilderInfo { + history := copyHistory(b.OCIv1.History) + history = append(history, copyHistory(b.PrependedEmptyLayers)...) + history = append(history, copyHistory(b.AppendedEmptyLayers)...) + sort.Strings(b.Capabilities) + return BuilderInfo{ + Type: b.Type, + FromImage: b.FromImage, + FromImageID: b.FromImageID, + FromImageDigest: b.FromImageDigest, + Config: string(b.Config), + Manifest: string(b.Manifest), + Container: b.Container, + ContainerID: b.ContainerID, + GroupAdd: b.GroupAdd, + MountPoint: b.MountPoint, + ProcessLabel: b.ProcessLabel, + MountLabel: b.MountLabel, + ImageAnnotations: b.ImageAnnotations, + ImageCreatedBy: b.ImageCreatedBy, + OCIv1: b.OCIv1, + Docker: b.Docker, + DefaultMountsFilePath: b.DefaultMountsFilePath, + Isolation: b.Isolation.String(), + NamespaceOptions: b.NamespaceOptions, + ConfigureNetwork: fmt.Sprintf("%v", b.ConfigureNetwork), + CNIPluginPath: b.CNIPluginPath, + CNIConfigDir: b.CNIConfigDir, + IDMappingOptions: b.IDMappingOptions, + Capabilities: b.Capabilities, + History: history, + Devices: b.Devices, + DeviceSpecs: b.DeviceSpecs, + CDIConfigDir: b.CDIConfigDir, + } +} + +// CommonBuildOptions are resources that can be defined by flags for both buildah from and build +type CommonBuildOptions = define.CommonBuildOptions + +// BuilderOptions are used to initialize a new Builder. +type BuilderOptions struct { + // Args define variables that users can pass at build-time to the builder + Args map[string]string + // FromImage is the name of the image which should be used as the + // starting point for the container. It can be set to an empty value + // or "scratch" to indicate that the container should not be based on + // an image. + FromImage string + // ContainerSuffix is the suffix to add for generated container names + ContainerSuffix string + // Container is a desired name for the build container. + Container string + // PullPolicy decides whether or not we should pull the image that + // we're using as a base image. It should be PullIfMissing, + // PullAlways, or PullNever. + PullPolicy define.PullPolicy + // Registry is a value which is prepended to the image's name, if it + // needs to be pulled and the image name alone can not be resolved to a + // reference to a source image. No separator is implicitly added. + Registry string + // BlobDirectory is the name of a directory in which we'll attempt + // to store copies of layer blobs that we pull down, if any. It should + // already exist. + BlobDirectory string + GroupAdd []string + // Logger is the logrus logger to write log messages with + Logger *logrus.Logger `json:"-"` + // Mount signals to NewBuilder() that the container should be mounted + // immediately. + Mount bool + // SignaturePolicyPath specifies an override location for the signature + // policy which should be used for verifying the new image as it is + // being written. Except in specific circumstances, no value should be + // specified, indicating that the shared, system-wide default policy + // should be used. + SignaturePolicyPath string + // ReportWriter is an io.Writer which will be used to log the reading + // of the source image from a registry, if we end up pulling the image. + ReportWriter io.Writer + // github.com/containers/image/types SystemContext to hold credentials + // and other authentication/authorization information. + SystemContext *types.SystemContext + // DefaultMountsFilePath is the file path holding the mounts to be + // mounted in "host-path:container-path" format + DefaultMountsFilePath string + // Isolation controls how we handle "RUN" statements and the Run() + // method. + Isolation define.Isolation + // NamespaceOptions controls how we set up namespaces for processes that + // we might need to run using the container's root filesystem. + NamespaceOptions define.NamespaceOptions + // ConfigureNetwork controls whether or not network interfaces and + // routing are configured for a new network namespace (i.e., when not + // joining another's namespace and not just using the host's + // namespace), effectively deciding whether or not the process has a + // usable network. + ConfigureNetwork define.NetworkConfigurationPolicy + // CNIPluginPath is the location of CNI plugin helpers, if they should be + // run from a location other than the default location. + CNIPluginPath string + // CNIConfigDir is the location of CNI configuration files, if the files in + // the default configuration directory shouldn't be used. + CNIConfigDir string + + // NetworkInterface is the libnetwork network interface used to setup CNI or netavark networks. + NetworkInterface nettypes.ContainerNetwork `json:"-"` + + // ID mapping options to use if we're setting up our own user namespace. + IDMappingOptions *define.IDMappingOptions + // Capabilities is a list of capabilities to use when + // running commands for Run(). + Capabilities []string + CommonBuildOpts *define.CommonBuildOptions + // Format to use for a container image we eventually commit, when we do. + Format string + // Devices are additional parsed devices to provide for Run() calls. + Devices define.ContainerDevices + // DeviceSpecs are additional unparsed devices to provide for Run() calls. + DeviceSpecs []string + // DefaultEnv is deprecated and ignored. + DefaultEnv []string + // MaxPullRetries is the maximum number of attempts we'll make to pull + // any one image from the external registry if the first attempt fails. + MaxPullRetries int + // PullRetryDelay is how long to wait before retrying a pull attempt. + PullRetryDelay time.Duration + // OciDecryptConfig contains the config that can be used to decrypt an image if it is + // encrypted if non-nil. If nil, it does not attempt to decrypt an image. + OciDecryptConfig *encconfig.DecryptConfig + // ProcessLabel is the SELinux process label associated with commands we Run() + ProcessLabel string + // MountLabel is the SELinux mount label associated with the working container + MountLabel string + // PreserveBaseImageAnns indicates that we should preserve base + // image information (Annotations) that are present in our base image, + // rather than overwriting them with information about the base image + // itself. Useful as an internal implementation detail of multistage + // builds, and does not need to be set by most callers. + PreserveBaseImageAnns bool + // CDIConfigDir is the location of CDI configuration files, if the files in + // the default configuration locations shouldn't be used. + CDIConfigDir string + // CompatScratchConfig controls whether a "scratch" image is created + // with a truly empty configuration, as would have happened in the past + // (when set to true), or with a minimal initial configuration which + // has a working directory set in it. + CompatScratchConfig types.OptionalBool +} + +// ImportOptions are used to initialize a Builder from an existing container +// which was created elsewhere. +type ImportOptions struct { + // Container is the name of the build container. + Container string + // SignaturePolicyPath specifies an override location for the signature + // policy which should be used for verifying the new image as it is + // being written. Except in specific circumstances, no value should be + // specified, indicating that the shared, system-wide default policy + // should be used. + SignaturePolicyPath string +} + +// ImportFromImageOptions are used to initialize a Builder from an image. +type ImportFromImageOptions struct { + // Image is the name or ID of the image we'd like to examine. + Image string + // SignaturePolicyPath specifies an override location for the signature + // policy which should be used for verifying the new image as it is + // being written. Except in specific circumstances, no value should be + // specified, indicating that the shared, system-wide default policy + // should be used. + SignaturePolicyPath string + // github.com/containers/image/types SystemContext to hold information + // about which registries we should check for completing image names + // that don't include a domain portion. + SystemContext *types.SystemContext +} + +// ConfidentialWorkloadOptions encapsulates options which control whether or not +// we output an image whose rootfs contains a LUKS-compatibly-encrypted disk image +// instead of the usual rootfs contents. +type ConfidentialWorkloadOptions = define.ConfidentialWorkloadOptions + +// SBOMScanOptions encapsulates options which control whether or not we run a +// scanner on the rootfs that we're about to commit, and how. +type SBOMScanOptions = define.SBOMScanOptions + +// NewBuilder creates a new build container. +func NewBuilder(ctx context.Context, store storage.Store, options BuilderOptions) (*Builder, error) { + if options.CommonBuildOpts == nil { + options.CommonBuildOpts = &CommonBuildOptions{} + } + return newBuilder(ctx, store, options) +} + +// ImportBuilder creates a new build configuration using an already-present +// container. +func ImportBuilder(ctx context.Context, store storage.Store, options ImportOptions) (*Builder, error) { + return importBuilder(ctx, store, options) +} + +// ImportBuilderFromImage creates a new builder configuration using an image. +// The returned object can be modified and examined, but it can not be saved +// or committed because it is not associated with a working container. +func ImportBuilderFromImage(ctx context.Context, store storage.Store, options ImportFromImageOptions) (*Builder, error) { + return importBuilderFromImage(ctx, store, options) +} + +// OpenBuilder loads information about a build container given its name or ID. +func OpenBuilder(store storage.Store, container string) (*Builder, error) { + cdir, err := store.ContainerDirectory(container) + if err != nil { + return nil, err + } + buildstate, err := os.ReadFile(filepath.Join(cdir, stateFile)) + if err != nil { + return nil, err + } + b := &Builder{} + if err = json.Unmarshal(buildstate, &b); err != nil { + return nil, fmt.Errorf("parsing %q, read from %q: %w", string(buildstate), filepath.Join(cdir, stateFile), err) + } + if b.Type != containerType { + return nil, fmt.Errorf("container %q is not a %s container (is a %q container)", container, define.Package, b.Type) + } + + netInt, err := getNetworkInterface(store, b.CNIConfigDir, b.CNIPluginPath) + if err != nil { + return nil, err + } + b.NetworkInterface = netInt + b.store = store + b.fixupConfig(nil) + b.setupLogger() + if b.CommonBuildOpts == nil { + b.CommonBuildOpts = &CommonBuildOptions{} + } + return b, nil +} + +// OpenBuilderByPath loads information about a build container given a +// path to the container's root filesystem +func OpenBuilderByPath(store storage.Store, path string) (*Builder, error) { + containers, err := store.Containers() + if err != nil { + return nil, err + } + abs, err := filepath.Abs(path) + if err != nil { + return nil, err + } + builderMatchesPath := func(b *Builder, path string) bool { + return (b.MountPoint == path) + } + for _, container := range containers { + cdir, err := store.ContainerDirectory(container.ID) + if err != nil { + return nil, err + } + buildstate, err := os.ReadFile(filepath.Join(cdir, stateFile)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + logrus.Debugf("error reading %q: %v, ignoring container %q", filepath.Join(cdir, stateFile), err, container.ID) + continue + } + return nil, err + } + b := &Builder{} + err = json.Unmarshal(buildstate, &b) + if err == nil && b.Type == containerType && builderMatchesPath(b, abs) { + b.store = store + b.fixupConfig(nil) + b.setupLogger() + if b.CommonBuildOpts == nil { + b.CommonBuildOpts = &CommonBuildOptions{} + } + return b, nil + } + if err != nil { + logrus.Debugf("error parsing %q, read from %q: %v", string(buildstate), filepath.Join(cdir, stateFile), err) + } else if b.Type != containerType { + logrus.Debugf("container %q is not a %s container (is a %q container)", container.ID, define.Package, b.Type) + } + } + return nil, storage.ErrContainerUnknown +} + +// OpenAllBuilders loads all containers which have a state file that we use in +// their data directory, typically so that they can be listed. +func OpenAllBuilders(store storage.Store) (builders []*Builder, err error) { + containers, err := store.Containers() + if err != nil { + return nil, err + } + for _, container := range containers { + cdir, err := store.ContainerDirectory(container.ID) + if err != nil { + return nil, err + } + buildstate, err := os.ReadFile(filepath.Join(cdir, stateFile)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + logrus.Debugf("%v, ignoring container %q", err, container.ID) + continue + } + return nil, err + } + b := &Builder{} + err = json.Unmarshal(buildstate, &b) + if err == nil && b.Type == containerType { + b.store = store + b.setupLogger() + b.fixupConfig(nil) + if b.CommonBuildOpts == nil { + b.CommonBuildOpts = &CommonBuildOptions{} + } + builders = append(builders, b) + continue + } + if err != nil { + logrus.Debugf("error parsing %q, read from %q: %v", string(buildstate), filepath.Join(cdir, stateFile), err) + } else if b.Type != containerType { + logrus.Debugf("container %q is not a %s container (is a %q container)", container.ID, define.Package, b.Type) + } + } + return builders, nil +} + +// Save saves the builder's current state to the build container's metadata. +// This should not need to be called directly, as other methods of the Builder +// object take care of saving their state. +func (b *Builder) Save() error { + buildstate, err := json.Marshal(b) + if err != nil { + return err + } + cdir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return err + } + if err = ioutils.AtomicWriteFile(filepath.Join(cdir, stateFile), buildstate, 0o600); err != nil { + return fmt.Errorf("saving builder state to %q: %w", filepath.Join(cdir, stateFile), err) + } + return nil +} diff --git a/vendor/github.com/containers/buildah/changelog.txt b/vendor/github.com/containers/buildah/changelog.txt new file mode 100644 index 00000000..02857a42 --- /dev/null +++ b/vendor/github.com/containers/buildah/changelog.txt @@ -0,0 +1,3721 @@ +- Changelog for v1.42.2 (2025-12-02) + * [release-1.42] Bump runc to v1.3.4 + +- Changelog for v1.42.1 (2025-11-06) + * [release-1.42] bump runc to v1.3.3 - CVE-2025-52881 + * RPM: build with sequoia on F43+ + +- Changelog for v1.42.0 (2025-10-17) + * Bump to storage v1.61.0, image v5.38.0, common v0.66.0 + * fix(deps): update module github.com/openshift/imagebuilder to v1.2.19 + * fix(deps): update module github.com/openshift/imagebuilder to v1.2.18 + * copier: ignore user.overlay.* xattrs + * commit: always return the config digest as the image ID + * fix(deps): update module golang.org/x/crypto to v0.43.0 + * fix(deps): update module golang.org/x/sys to v0.37.0 + * fix(deps): update module github.com/docker/docker to v28.5.1+incompatible + * fix(deps): update module github.com/moby/buildkit to v0.25.1 + * fix(deps): update module github.com/opencontainers/runc to v1.3.2 + * fix(deps): update module github.com/docker/docker to v28.5.0+incompatible + * fix(deps): update module github.com/moby/buildkit to v0.25.0 + * fix(deps): update github.com/containers/luksy digest to 2cf5bc9 + * Make some test files different from each other + * Revert "fix(deps): update module github.com/cyphar/filepath-securejoin to v0.5.0" + * Also run integration tests with the Sequoia backend + * Allow users to build against podman-sequoia in non-default locations + * fix(deps): update module github.com/cyphar/filepath-securejoin to v0.5.0 + * .cirrus.yml: Test Vendoring bump golang + * vendor: bump go.podman.io/{common,image,storage} to main + * fix(deps): update module golang.org/x/crypto to v0.42.0 + * fix(deps): update module github.com/docker/docker to v28.4.0+incompatible + * fix(deps): update module github.com/moby/buildkit to v0.24.0 + * fix(deps): update module github.com/spf13/pflag to v1.0.10 + * fix(deps): update module github.com/fsouza/go-dockerclient to v1.12.2 + * fix(deps): update module github.com/opencontainers/runc to v1.3.1 + * fix(deps): update module github.com/opencontainers/cgroups to v0.0.5 + * fix(deps): update module golang.org/x/sync to v0.17.0 + * tests/run.bats: "run masks" test: accept "unreadable" masked directories + * Run: create parent directories of mount targets with mode 0755 + * tests/run.bats: "run masks" test: accept "unreadable" masked directories + * New VM images + * Suppress a linter warning + * modernize: JSON doesn't do "omitempty" structs, so stop asking + * modernize: use maps.Copy() instead of iterating over a map to copy it + * modernize: use strings.CutPrefix/SplitSeq/FieldsSeq + * Update expected/minimum version of Go to 1.24 + * chroot: use $PATH when finding commands + * [skip-ci] Update actions/stale action to v10 + * Update module github.com/ulikunitz/xz to v0.5.15 [SECURITY] + * Update go.sum + * New VM images + * Update module github.com/openshift/imagebuilder to v1 + * Update module github.com/spf13/cobra to v1.10.1 + * Switch common, storage and image to monorepo. + * Update module github.com/stretchr/testify to v1.11.1 + * Update module go.etcd.io/bbolt to v1.4.3 + * Handle tagged+digested references when processing --all-platforms + * Update module github.com/stretchr/testify to v1.11.0 + * Add --transient-store global option + * Support "--imagestore" global flags + * Commit: don't depend on MountImage(), because .imagestore + * Adding mohanboddu as community manager to MAINTAINERS.md + * Rework how we decide what to filter out of layer diffs + * Note that we have to build `true` first for the sake of its tests + * copier.Stat(): return owner UID and GID if available + * copier.Get(): ensure that directory entries end in "/" + * copier.Get(): strip user and group names from entries + * imagebuildah.Executor/StageExecutor: check numeric --from= values + * Losen the dependency on go-connections/tlsconfig + * fix(deps): update module golang.org/x/crypto to v0.41.0 + * fix(deps): update module golang.org/x/term to v0.34.0 + * fix(deps): update module github.com/docker/go-connections to v0.6.0 + * fix(deps): update module golang.org/x/sys to v0.35.0 + * copy: assume a destination with basename "." is a directory + * generatePathChecksum: ignore ModTime, AccessTime and ChangeTime + * fix(deps): update module github.com/seccomp/libseccomp-golang to v0.11.1 + * fix(deps): update module github.com/containers/common to v0.64.1 + * History should note unset-label, timestamp, and rewrite-timestamp + * pkg/cli.GenBuildOptions(): don't hardwire optional bools + * fix(deps): update module github.com/containers/image/v5 to v5.36.1 + * imagebuildah.StageExecutor.Execute: commit more "no instructions" cases + * fix(deps): update module github.com/containers/storage to v1.59.1 + * Only suppress "noted" items when not squashing + * Reap stray processes + * fix(deps): update github.com/containers/luksy digest to 8fccf78 + * fix(deps): update module github.com/docker/docker to v28.3.3+incompatible + * Restore the default meaning of `--pull` (should be `always`). + * Test that pulled up parent directories are excluded at commit + * Exclude pulled up parent directories at commit-time + * copier.Ensure(): also return parent directories + * copier.MkdirOptions: add ModTimeNew + * fix(deps): update module github.com/containers/common to v0.64.0 + * Bump to Buildah v1.42.0-dev + * fix(deps): update module github.com/spf13/pflag to v1.0.7 + * CI: make runc tests non-blocking + * build,add: add support for corporate proxies + +- Changelog for v1.41.0 (2025-07-16) + * Bump to c/storage v1.59.0, c/image v5.36.0, ... c/common v0.64.0 + * stage_executor: check platform of cache candidates + * fix(deps): update module golang.org/x/crypto to v0.40.0 + * fix(deps): update module golang.org/x/term to v0.33.0 + * fix(deps): update module golang.org/x/sync to v0.16.0 + * fix(deps): update module github.com/docker/docker to v28.3.2+incompatible + * ADD/COPY --link support added + * RPM/TMT: account for passwd binary moving to tests + * buildah: move passwd command to tests + * Update "bud with --cpu-shares" test, and rename it + * Remove BUILDTAG btrfs_noversion as no longer effective + * fix(deps): update module github.com/docker/docker to v28.3.1+incompatible + * fix(deps): update module github.com/moby/buildkit to v0.23.2 + * fix(deps): update github.com/containers/luksy digest to bc60f96 + * chore(typos): fix typos + * vendor: update c/{common,image,storage} to main + * chore(deps): update module github.com/go-viper/mapstructure/v2 to v2.3.0 [security] + * fix(deps): update module go.etcd.io/bbolt to v1.4.2 + * Update Neil Smith's GitHub username in MAINTAINERS.md + * Accept SOURCE_DATE_EPOCH as a build-arg + * fix(deps): update module github.com/docker/docker to v28.3.0+incompatible + * Add conditional release-checking system test + * info,inspect: use the "formats" package to get some builtins + * Use containers/common's formats package instead of our own + * build, commit: set the OCI ...created annotation on OCI images + * commit: exclude parents of mount targets, too + * run: clean up parents of mount targets, too + * tarFilterer: always flush after writing + * Builder: drop the TempVolumes field + * Update module github.com/moby/buildkit to v0.23.1 + * Update module github.com/opencontainers/cgroups to v0.0.3 + * Add CommitOptions.OmitLayerHistoryEntry, for skipping the new bits + * Update module github.com/fsouza/go-dockerclient to v1.12.1 + * conformance: use mirrored frontend and base images + * commit-with-extra-files test: use $TEST_SCRATCH_DIR + * fix(deps): update module github.com/moby/buildkit to v0.23.0 + * "root fs only mounted once" test: accept root with only the rw option + * Run with --device /dev/fuse and not just -v /dev/fuse:/dev/fuse + * CI: pass $BUILDAH_RUNTIME through to in-container test runs + * CI: ensure rootless groups aren't duplicates + * build: add support for --inherit-annotations + * CI: give the rootless test user some supplemental groups + * bud,run: runc does not support keep-groups + * Fix lint issue in TestCommitCompression + * Add a unit test for compression types in OCI images + * Support zstd compression in image commit + * fix(deps): update module go.etcd.io/bbolt to v1.4.1 + * rpm: build rpm with libsqlite3 tag + * Makefile: use libsqlite3 build when possible + * commit,build: --source-date-epoch/--timestamp omit identity label + * docs: add --setopt "*.countme=false" to dnf examples + * Builder.sbomScan(): don't break non-root scanners + * build: --source-date-epoch/--timestamp use static hostname/cid + * fix(deps): update module golang.org/x/crypto to v0.39.0 + * fix(deps): update module golang.org/x/sync to v0.15.0 + * build: add --source-date-epoch and --rewrite-timestamp flags + * build,config: add support for --unsetannotation + * commit: add --source-date-epoch and --rewrite-timestamp flags + * fix(deps): update module github.com/openshift/imagebuilder to v1.2.16 + * vendor latest c/{common,image,storage} + * Tweak our handling of variant values, again + * Don't BuildRequires: ostree-devel + * parse, validateExtraHost: honor Hostgateway in format + * remove static nix build + * Ensure extendedGlob returns paths in lexical order + * CI: run integration tests on Fedora with both crun and runc + * buildah-build(1): clarify that --cgroup-parent affects RUN instructions + * runUsingRuntime: use named constants for runtime states + * Add a dummy "runtime" that just dumps its config file + * run: handle relabeling bind mounts ourselves + * fix link to Maintainers file + * Update to avoid deprecated types + * fix(deps): update module github.com/docker/docker to v28.2.0+incompatible + * [skip-ci] Packit: cleanup redundant targets and unused anchors + * [skip-ci] Packit: set fedora-all after F40 EOL + * Use Fedora 42 instead of 41 in that one conformance test + * [CI:DOCS] README.md: add openssf passing badge + * fix(deps): update module github.com/moby/buildkit to v0.22.0 + * copier: add Ensure and ConditionalRemove + * [CI:DOCS] update a couple of lists in the build man page + * build: allow --output to be specified multiple times + * add: add a new --timestamp flag + * tests/helpers.bash: add some helpers for parsing images + * pkg/parse.GetBuildOutput(): use strings.Cut() + * [skip-ci] Packit: Disable osh_diff_scan + * internal/util.SetHas(): handle maps of [generic]generic + * Refactor NewImageSource to add a manifest type abstraction (#5743) + * [skip-ci] Packit: Ignore ELN and CentOS Stream jobs + * imagebuildah: select most recent layer for cache + * [CI:DOCS] Add CNCF roadmap, touchup other CNCF files + * fix(deps): update module golang.org/x/crypto to v0.38.0 + * Fix typo in comment (#6167) + * Support label_users in buildah + * fix(deps): update module golang.org/x/sync to v0.14.0 + * fix(deps): update github.com/containers/luksy digest to 4bb4c3f + * test/serve: fix a descriptor leak, add preliminary directory support + * fix(deps): update module github.com/opencontainers/cgroups to v0.0.2 + * fix(deps): update module github.com/moby/buildkit to v0.21.1 + * Update to avoid deprecated types + * fix(deps): update module github.com/opencontainers/runc to v1.3.0 + * Only filter if containerImageRef.created != nil + * Drop superfluous cast + * Remove UID/GID scrubbing. + * fix(deps): update module github.com/seccomp/libseccomp-golang to v0.11.0 + * cirrus: turn prior fedora testing back on + * chore(deps): update dependency containers/automation_images to v20250422 + * fix(deps): update module github.com/docker/docker to v28.1.1+incompatible + * Bump to Buildah v1.41.0-dev + * CI vendor_task: pin to go 1.23.3 for now + * fix(deps): update module github.com/containers/common to v0.63.0 + +- Changelog for v1.40.0 (2025-04-17) + * Bump c/storage to v1.58.0, c/image v5.35.0, c/common v0.63.0 + * fix(deps): update module github.com/docker/docker to v28.1.0+incompatible + * fix(deps): update module github.com/containers/storage to v1.58.0 + * cirrus: make Total Success wait for rootless integration + * chroot: use symbolic names when complaining about mount() errors + * cli: hide the `completion` command instead of disabling it outright + * Document rw and src options for --mount flag in buildah-run(1) + * fix(deps): update module github.com/moby/buildkit to v0.21.0 + * build: add support for inherit-labels + * chore(deps): update dependency golangci/golangci-lint to v2.1.0 + * .github: check_cirrus_cron work around github bug + * stage_executor,getCreatedBy: expand buildArgs for sources correctly + * Add a link to project governance and MAINTAINERS file + * fix(deps): update github.com/containers/storage digest to b1d1b45 + * generateHostname: simplify + * Use maps.Copy + * Use slices.Concat + * Use slices.Clone + * Use slices.Contains + * Use for range over integers + * tests/testreport: don't copy os.Environ + * Use any instead of interface{} + * ci: add golangci-lint run with --tests=false + * ci: add nolintlint, fix found issues + * copier: rm nolint:unparam annotation + * .golangci.yml: add unused linter + * chroot: fix unused warnings + * copier: fix unused warnings + * tests/conformance: fix unused warning + * ci: switch to golangci-lint v2 + * internal/mkcw: disable ST1003 warnings + * tests/conformance: do not double import (fix ST1019) + * cmd/buildah: don't double import (fix ST1019) + * Do not capitalize error strings + * cmd/buildah: do not capitalize error strings + * tests/conformance: fix QF1012 warnings + * tests/serve: fix QF1012 warning + * Use strings.ReplaceAll to fix QF1004 warnings + * Use switch to fix QF1003 warnings + * Apply De Morgan's law to fix QF1001 warnings + * Fix QF1007 staticcheck warnings + * imagebuildah: fix revive warning + * Rename max variable + * tests/tools: install lint from binary, use renovate + * fix(deps): update module github.com/containernetworking/cni to v1.3.0 + * Update Buildah issue template to new version and support podman build + * fix(deps): update module golang.org/x/crypto to v0.37.0 + * stage_executor: reset platform in systemcontext for stages + * fix(deps): update github.com/opencontainers/runtime-tools digest to 260e151 + * cmd/buildah: rm unused containerOutputUsingTemplate + * cmd/buildah: rm unused getDateAndDigestAndSize + * build: return ExecErrorCodeGeneric when git operation fails + * add: report error while creating dir for URL source. + * createPlatformContainer: drop MS_REMOUNT|MS_BIND + * fix(deps): update module github.com/docker/docker to v28.0.3+incompatible + * fix: bats won't fail on ! without cleverness + * feat: use HistoryTimestamp, if set, for oci-archive entries + * Allow extendedGlob to work with Windows paths + * fix(deps): update module github.com/moby/buildkit to v0.20.2 + * fix(deps): update github.com/openshift/imagebuilder digest to e87e4e1 + * fix(deps): update module github.com/docker/docker to v28.0.2+incompatible + * fix(deps): update module tags.cncf.io/container-device-interface to v1.0.1 + * chore(deps): update dependency containers/automation_images to v20250324 + * vendor: update github.com/opencontainers/selinux to v1.12.0 + * replace deprecated selinux/label calls + * vendor: bump c/common to dbeb17e40c80 + * Use builtin arg defaults from imagebuilder + * linux: accept unmask paths as glob values + * vendor: update containers/common + * Add --parents option for COPY in Dockerfiles + * fix(deps): update module github.com/opencontainers/runc to v1.2.6 + * update go.sum from the previous commit + * fix(deps): update module tags.cncf.io/container-device-interface to v1 + * chore(deps): update module golang.org/x/net to v0.36.0 [security] + * packit: remove f40 from copr builds + * cirrus: update to go 1.23 image + * vendor bump to golang.org/x/crypto v0.36.0 + * cirrus: update PRIOR_FEDORA comment + * github: remove cirrus rerun action + * fix(deps): update module github.com/containers/common to v0.62.2 + * fix(deps): update module github.com/containers/image/v5 to v5.34.2 + * fix: close files properly when BuildDockerfiles exits + * fix(deps): update module github.com/containers/storage to v1.57.2 + * stage_executor: history should include heredoc summary correctly + * fix(deps): update module github.com/containers/common to v0.62.1 + * github: disable cron rerun action + * fix(deps): update module github.com/moby/buildkit to v0.20.1 + * internal/mkcw.Archive(): use github.com/containers/storage/pkg/ioutils + * [skip-ci] TMT: system tests + * buildah-build.1.md: secret examples + * fix(deps): update github.com/containers/luksy digest to 40bd943 + * fix(deps): update module github.com/opencontainers/image-spec to v1.1.1 + * fix(deps): update module github.com/containers/image/v5 to v5.34.1 + * Use UnparsedInstance.Manifest instead of ImageSource.GetManifest + * fix(deps): update module github.com/opencontainers/runtime-spec to v1.2.1 + * tests/conformance/testdata/Dockerfile.add: update some URLs + * Vendor imagebuilder + * Fix source of OS, architecture and variant + * chore(deps): update module github.com/go-jose/go-jose/v4 to v4.0.5 [security] + * fix(deps): update module tags.cncf.io/container-device-interface to v0.8.1 + * fix(deps): update module github.com/moby/buildkit to v0.20.0 + * chroot createPlatformContainer: use MS_REMOUNT + * conformance: make TestCommit and TestConformance parallel + * cirrus: reduce task timeout + * mkcw: mkcw_check_image use bats run_with_log + * test: use /tmp as TMPDIR + * heredoc: create temp subdirs for each build + * test: heredoc remove python dependency from test + * Support the containers.conf container_name_as_hostname option + * fix(deps): update module github.com/opencontainers/runc to v1.2.5 + * fix(deps): update module github.com/spf13/cobra to v1.9.0 + * .cirrus: use more cores for smoke + * Switch to the CNCF Code of Conduct + * .cirrus: bump ci resources + * fix(deps): update module golang.org/x/crypto to v0.33.0 + * Distinguish --mount=type=cache locations by ownership, too + * fix(deps): update module golang.org/x/term to v0.29.0 + * .cirrus: run -race only on non-PR branch + * unit: deparallize some tests + * .cirrus: use multiple cpu for unit tests + * Makefile: use -parallel for go test + * unit_test: use Parallel test where possible + * Update module golang.org/x/sys to v0.30.0 + * Update module golang.org/x/sync to v0.11.0 + * Update dependency containers/automation_images to v20250131 + * Bump to Buildah v1.40.0-dev + +- Changelog for v1.39.0 (2025-01-31) + * Bump c/storage v1.57.1, c/image 5.34.0, c/common v0.62.0 + * Update module github.com/containers/storage to v1.57.0 + * CI, .cirrus: parallelize containerized integration + * ed's comment: cleanup + * use seperate blobinfocache for flaky test + * bump CI VMs to 4 CPUs (was: 2) for integration tests + * cleanup, debug, and disable parallel in blobcache tests + * bats tests - parallelize + * pkg/overlay: cleanups + * RPM: include check section to silence rpmlint + * RPM: use default gobuild macro on RHEL + * tests: remove masked /sys/dev/block check + * vendor to latest c/{common,image,storage} + * build, run: record hash or digest in image history + * Accept image names as sources for cache mounts + * Run(): always clean up options.ExternalImageMounts + * refactor: replace golang.org/x/exp with stdlib + * Update to c/image @main + * fix broken doc link + * run_freebsd.go: only import runtime-spec once + * fix(deps): update module github.com/docker/docker to v27.5.1+incompatible + * bump github.com/vbatts/tar-split + * Add more checks to the --mount flag parsing logic + * chroot mount flags integration test: copy binaries + * fix(deps): update module github.com/moby/buildkit to v0.19.0 + * relabel(): correct a misleading parameter name + * Fix TOCTOU error when bind and cache mounts use "src" values + * define.TempDirForURL(): always use an intermediate subdirectory + * internal/volume.GetBindMount(): discard writes in bind mounts + * pkg/overlay: add a MountLabel flag to Options + * pkg/overlay: add a ForceMount flag to Options + * Add internal/volumes.bindFromChroot() + * Add an internal/open package + * fix(deps): update module github.com/containers/common to v0.61.1 + * fix(deps): update module github.com/containers/image/v5 to v5.33.1 + * [CI:DOCS] Touch up changelogs + * fix(deps): update module github.com/docker/docker to v27.5.0+incompatible + * copy-preserving-extended-attributes: use a different base image + * fix(deps): update github.com/containers/luksy digest to a3a812d + * chore(deps): update module golang.org/x/net to v0.33.0 [security] + * fix(deps): update module golang.org/x/crypto to v0.32.0 + * New VM Images + * fix(deps): update module github.com/opencontainers/runc to v1.2.4 + * fix(deps): update module github.com/docker/docker to v27.4.1+incompatible + * fix(deps): update module github.com/containers/ocicrypt to v1.2.1 + * Add support for --security-opt mask and unmask + * Allow cache mounts to be stages or additional build contexts + * [skip-ci] RPM: cleanup changelog conditionals + * fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.6 + * fix(deps): update module github.com/moby/buildkit to v0.18.2 + * Fix an error message in the chroot unit test + * copier: use .PAXRecords instead of .Xattrs + * chroot: on Linux, try to pivot_root before falling back to chroot + * manifest add: add --artifact-annotation + * Add context to an error message + * Update module golang.org/x/crypto to v0.31.0 + * Update module github.com/opencontainers/runc to v1.2.3 + * Update module github.com/docker/docker to v27.4.0+incompatible + * Update module github.com/cyphar/filepath-securejoin to v0.3.5 + * CI: don't build a binary in the unit tests task + * CI: use /tmp for $GOCACHE + * CI: remove dependencies on the cross-build task + * CI: run cross-compile task with make -j + * Update module github.com/docker/docker to v27.4.0-rc.4+incompatible + * Update module github.com/moby/buildkit to v0.18.1 + * Update module golang.org/x/crypto to v0.30.0 + * Update golang.org/x/exp digest to 2d47ceb + * Update github.com/opencontainers/runtime-tools digest to f7e3563 + * [skip-ci] Packit: remove rhel copr build jobs + * [skip-ci] Packit: switch to fedora-all for copr + * Update module github.com/stretchr/testify to v1.10.0 + * Update module github.com/moby/buildkit to v0.17.2 + * Makefile: use `find` to detect source files + * Tests: make _prefetch() parallel-safe + * Update module github.com/opencontainers/runc to v1.2.2 + * executor: allow to specify --no-pivot-root + * Update module github.com/moby/sys/capability to v0.4.0 + * Makefile: mv codespell config to .codespellrc + * Fix some codespell errors + * Makefile,install.md: rm gopath stuff + * Makefile: rm targets working on .. + * build: rm exclude_graphdriver_devicemapper tag + * Makefile: rm unused var + * Finish updating to go 1.22 + * CI VMs: bump again + * Bump to Buidah v1.39.0-dev + * stage_executor: set avoidLookingCache only if mounting stage + * imagebuildah: additionalContext is not a local built stage + +- Changelog for v1.38.0 (2024-11-08) + * Bump to c/common v0.61.0, c/image v5.33.0, c/storage v1.56.0 + * fix(deps): update module golang.org/x/crypto to v0.29.0 + * fix(deps): update module github.com/moby/buildkit to v0.17.1 + * fix(deps): update module github.com/containers/storage to v1.56.0 + * tests: skip two ulimit tests + * CI VMs: bump f40 -> f41 + * tests/tools: rebuild tools when we change versions + * tests/tools: update golangci-lint to v1.61.0 + * fix(deps): update module github.com/moby/buildkit to v0.17.0 + * Handle RUN --mount with relative targets and no configured workdir + * tests: bud: make parallel-safe + * fix(deps): update module github.com/opencontainers/runc to v1.2.1 + * fix(deps): update golang.org/x/exp digest to f66d83c + * fix(deps): update github.com/opencontainers/runtime-tools digest to 6c9570a + * tests: blobcache: use unique image name + * tests: sbom: never write to cwd + * tests: mkcw: bug fixes, refactor + * deps: bump runc to v1.2.0 + * deps: switch to moby/sys/userns + * tests/test_runner.sh: remove some redundancies + * Integration tests: run git daemon on a random-but-bind()able port + * fix(deps): update module github.com/opencontainers/selinux to v1.11.1 + * go.mod: remove unnecessary replace + * Document more buildah build --secret options + * Add support for COPY --exclude and ADD --exclude options + * fix(deps): update github.com/containers/luksy digest to e2530d6 + * chore(deps): update dependency containers/automation_images to v20241010 + * fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.4 + * Properly validate cache IDs and sources + * [skip-ci] Packit: constrain koji job to fedora package to avoid dupes + * Audit and tidy OWNERS + * fix(deps): update module golang.org/x/crypto to v0.28.0 + * tests: add quotes to names + * vendor: update c/common to latest + * CVE-2024-9407: validate "bind-propagation" flag settings + * vendor: switch to moby/sys/capability + * Don't set ambient capabilities + * Document that zstd:chunked is downgraded to zstd when encrypting + * fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.3 + * buildah-manifest-create.1: Fix manpage section + * chore(deps): update dependency ubuntu to v24 + * Make `buildah manifest push --all` true by default + * chroot: add newlines at the end of printed error messages + * Do not error on trying to write IMA xattr as rootless + * fix: remove duplicate conditions + * fix(deps): update module github.com/moby/buildkit to v0.16.0 + * fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.2 + * Document how entrypoint is configured in buildah config + * In a container, try to register binfmt_misc + * imagebuildah.StageExecutor: clean up volumes/volumeCache + * build: fall back to parsing a TARGETPLATFORM build-arg + * `manifest add --artifact`: handle multiple values + * Packit: split out ELN jobs and reuse fedora downstream targets + * Packit: Enable sidetags for bodhi updates + * fix(deps): update module github.com/docker/docker to v27.2.1+incompatible + * tests/bud.bats: add git source + * add: add support for git source + * Add support for the new c/common pasta options + * vendor latest c/common + * fix(deps): update module golang.org/x/term to v0.24.0 + * fix(deps): update module github.com/fsouza/go-dockerclient to v1.12.0 + * packit: update fedora and epel targets + * cirrus: disable f39 testing + * cirrus: fix fedora names + * update to go 1.22 + * Vendor c/common:9d025e4cb348 + * copier: handle globbing with "**" path components + * fix(deps): update golang.org/x/exp digest to 9b4947d + * fix(deps): update github.com/containers/luksy digest to 2e7307c + * imagebuildah: make scratch config handling toggleable + * fix(deps): update module github.com/docker/docker to v27.2.0+incompatible + * Add a validation script for Makefile $(SOURCES) + * fix(deps): update module github.com/openshift/imagebuilder to v1.2.15 + * New VMs + * Update some godocs, use 0o to prefix an octal in a comment + * buildah-build.1.md: expand the --layer-label description + * fix(deps): update module github.com/containers/common to v0.60.2 + * run: fix a nil pointer dereference on FreeBSD + * CI: enable the whitespace linter + * Fix some govet linter warnings + * Commit(): retry committing to local storage on storage.LayerUnknown + * CI: enable the gofumpt linter + * conformance: move weirdly-named files out of the repository + * fix(deps): update module github.com/docker/docker to v27.1.2+incompatible + * fix(deps): update module github.com/containers/common to v0.60.1 + * *: use gofmt -s, add gofmt linter + * *: fix build tags + * fix(deps): update module github.com/containers/image/v5 to v5.32.1 + * Add(): re-escape any globbed items that included escapes + * conformance tests: use mirror.gcr.io for most images + * unit tests: use test-specific policy.json and registries.conf + * fix(deps): update module golang.org/x/sys to v0.24.0 + * Update to spun-out "github.com/containerd/platforms" + * Bump github.com/containerd/containerd + * test/tools/Makefile: duplicate the vendor-in-container target + * linters: unchecked error + * linters: don't end loop iterations with "else" when "then" would + * linters: unused arguments shouldn't have names + * linters: rename checkIdsGreaterThan5() to checkIDsGreaterThan5() + * linters: don't name variables "cap" + * `make lint`: use --timeout instead of --deadline + * Drop the e2e test suite + * fix(deps): update module golang.org/x/crypto to v0.26.0 + * fix(deps): update module github.com/onsi/gomega to v1.34.1 + * `make vendor-in-container`: use the caller's Go cache if it exists + * fix(deps): fix test/tools ginkgo typo + * fix(deps): update module github.com/onsi/ginkgo/v2 to v2.19.1 + * Update to keep up with API changes in storage + * fix(deps): update github.com/containers/luksy digest to 1f482a9 + * install: On Debian/Ubuntu, add installation of libbtrfs-dev + * fix(deps): update module golang.org/x/sys to v0.23.0 + * fix(deps): update golang.org/x/exp digest to 8a7402a + * fix(deps): update module github.com/fsouza/go-dockerclient to v1.11.2 + * Use Epoch: 2 and respect the epoch in dependencies. + * Bump to Buildah v1.38.0-dev + * AddAndCopyOptions: add CertPath, InsecureSkipTLSVerify, Retry fields + * Add PrependedLinkedLayers/AppendedLinkedLayers to CommitOptions + * integration tests: teach starthttpd() about TLS and pid files + +- Changelog for v1.37.0 (2024-07-26) + * Bump c/storage, c/image, c/common for v1.37.0 + * "build with basename resolving user arg" tests: correct ARG use + * bud-multiple-platform-no-run test: correct ARG use + * imagebuildah: always have default values for $TARGET... args ready + * bump github.com/openshift/imagebuilder to v1.2.14 + * fix(deps): update module github.com/docker/docker to v27.1.1+incompatible + * fix(deps): update module github.com/cyphar/filepath-securejoin to v0.3.1 + * fix(deps): update module github.com/docker/docker to v27.1.0+incompatible + * CI: use local registry, part 2 of 2 + * CI: use local registry, part 1 of 2 + * fix(deps): update module github.com/fsouza/go-dockerclient to v1.11.1 + * Revert "fix(deps): update github.com/containers/image/v5 to v5.31.1" + * Replace libimage.LookupReferenceFunc with the manifests version + * conformance tests: enable testing CompatVolumes + * conformance tests: add a test that tries to chown a volume + * imagebuildah: make traditional volume handling not the default + * StageExecutor.prepare(): mark base image volumes for preservation + * fix(deps): update module github.com/containers/image/v5 to v5.31.1 + * Vendor in latest containers/(common, storage, image) + * fix(deps): update module golang.org/x/term to v0.22.0 + * fix(deps): update module golang.org/x/sys to v0.22.0 + * fix(deps): update golang.org/x/exp digest to 7f521ea + * fix(deps): update github.com/containers/luksy digest to a8846e2 + * imagebuildah.StageExecutor.Copy(): reject new flags for now + * bump github.com/openshift/imagebuilder to v1.2.11 + * Rework parsing of --pull flags + * fix(deps): update module github.com/containers/image/v5 to v5.31.1 + * imagebuildah.StageExecutor.prepare(): log the --platform flag + * CI VMs: bump + * buildah copy: preserve owner info with --from= a container or image + * conformance tests: enable testing CompatSetParent + * containerImageRef.NewImageSource(): move the FROM comment to first + * commit: set "parent" for docker format only when requested + * Update godoc for Builder.EnsureContainerPathAs + * fix(deps): update module github.com/spf13/cobra to v1.8.1 + * fix(deps): update module github.com/containernetworking/cni to v1.2.0 + * fix(deps): update module github.com/opencontainers/runc to v1.1.13 + * Change default for podman build to --pull missing + * fix(deps): update module github.com/containers/common to v0.59.1 + * Clarify definition of --pull options + * buildah: fix a nil pointer reference on FreeBSD + * Use /var/tmp for $TMPDIR for vfs conformance jobs + * Cirrus: run `df` during job setup + * conformance: use quay.io/libpod/centos:7 instead of centos:8 + * Stop setting "parent" in docker format + * conformance: check if workdir trims path separator suffixes + * push integration test: pass password to docker login via stdin + * Re-enable the "copy with chown" conformance test + * healthcheck: Add support for `--start-interval` + * fix(deps): update module github.com/docker/docker to v26.1.4+incompatible + * fix(deps): update module github.com/containerd/containerd to v1.7.18 + * tests: set _CONTAINERS_USERNS_CONFIGURED=done for libnetwork + * Cross-build on Fedora + * Drop copyStringSlice() and copyStringStringMap() + * fix(deps): update module golang.org/x/crypto to v0.24.0 + * fix(deps): update module github.com/openshift/imagebuilder to v1.2.10 + * Provide an uptime_netbsd.go + * Spell unix as "!windows" + * Add netbsd to lists-of-OSes + * fix(deps): update golang.org/x/exp digest to fd00a4e + * [skip-ci] Packit: enable c10s downstream sync + * CI VMs: bump, to debian with cgroups v2 + * Document when BlobDirectory is overridden + * fix secret mounts for env vars when using chroot isolation + * Change to take a types.ImageReference arg + * imagebuildah: Support custom image reference lookup for cache push/pull + * fix(deps): update module github.com/onsi/ginkgo/v2 to v2.19.0 + * Bump to v1.37.0-dev + * CI: Clarify Debian use for conformance tests + +- Changelog for v1.36.0 (2024-05-23) + * build: be more selective about specifying the default OS + * Bump to c/common v0.59.0 + * Fix buildah prune --help showing the same example twice + * fix(deps): update module github.com/onsi/ginkgo/v2 to v2.18.0 + * fix(deps): update module github.com/containers/image/v5 to v5.31.0 + * bud tests: fix breakage when vendoring into podman + * Integration tests: fake up a replacement for nixery.dev/shell + * copierWithSubprocess(): try to capture stderr on io.ErrClosedPipe + * Don't expand RUN heredocs ourselves, let the shell do it + * Don't leak temp files on failures + * Add release note template to split dependency chores + * fix CentOS/RHEL build - no BATS there + * fix(deps): update module github.com/containers/luksy to v0.0.0-20240506205542-84b50f50f3ee + * Address CVE-2024-3727 + * chore(deps): update module github.com/opencontainers/runtime-spec to v1.2.0 + * Builder.cdiSetupDevicesInSpecdefConfig(): use configured CDI dirs + * Setting --arch should set the TARGETARCH build arg + * fix(deps): update module golang.org/x/exp to v0.0.0-20240416160154-fe59bbe5cc7f + * [CI:DOCS] Add link to Buildah image page to README.md + * Don't set GOTOOLCHAIN=local + * fix(deps): update module github.com/cyphar/filepath-securejoin to v0.2.5 + * Makefile: set GOTOOLCHAIN=local + * Integration tests: switch some base images + * containerImageRef.NewImageSource: merge the tar filters + * fix(deps): update module github.com/onsi/ginkgo/v2 to v2.17.2 + * fix(deps): update module github.com/containers/luksy to v0.0.0-20240408185936-afd8e7619947 + * Disable packit builds for centos-stream+epel-next-8 + * Makefile: add missing files to $(SOURCES) + * CI VMs: bump to new versions with tmpfs /tmp + * chore(deps): update module golang.org/x/net to v0.23.0 [security] + * integration test: handle new labels in "bud and test --unsetlabel" + * Switch packit configuration to use epel-9-$arch ... + * Give unit tests a bit more time + * Integration tests: remove a couple of duplicated tests + * Integration tests: whitespace tweaks + * Integration tests: don't remove images at start or end of test + * Integration tests: use cached images more + * Integration tests _prefetch: use registry configs + * internal: use fileutils.(Le|E)xists + * pkg/parse: use fileutils.(Le|E)xists + * buildah: use fileutils.(Le|E)xists + * chroot: use fileutils.(Le|E)xists + * vendor: update containers/(common|storage) + * Fix issue/pr lock workflow + * [CI:DOCS] Add golang 1.21 update warning + * heredoc: honor inline COPY irrespective of ignorefiles + * Update install.md + * source-push: add support for --digestfile + * Fix caching when mounting a cached stage with COPY/ADD + * fix(deps): update github.com/containers/luksy digest to 3d2cf0e + * Makefile: softcode `strip`, use it from env var + * Man page updates + * Add support for passing CDI specs to --device + * Update comments on some API objects + * pkg/parse.DeviceFromPath(): dereference src symlinks + * fix(deps): update module github.com/onsi/ginkgo/v2 to v2.17.1 + +- Changelog for v1.35.0 (2024-03-06) + * fix(deps): update module github.com/stretchr/testify to v1.9.0 + * cgroups: reuse version check from c/common + * Update vendor of containers/(common,image) + * fix(deps): update github.com/containers/storage digest to eadc620 + * fix(deps): update github.com/containers/luksy digest to ceb12d4 + * fix(deps): update github.com/containers/image/v5 digest to cdc6802 + * manifest add: complain if we get artifact flags without --artifact + * Use retry logic from containers/common + * Vendor in containers/(storage,image,common) + * Update module golang.org/x/crypto to v0.20.0 + * Add comment re: Total Success task name + * tests: skip_if_no_unshare(): check for --setuid + * Properly handle build --pull=false + * [skip-ci] Update tim-actions/get-pr-commits action to v1.3.1 + * Update module go.etcd.io/bbolt to v1.3.9 + * Revert "Reduce official image size" + * Update module github.com/opencontainers/image-spec to v1.1.0 + * Reduce official image size + * Build with CNI support on FreeBSD + * build --all-platforms: skip some base "image" platforms + * Bump main to v1.35.0-dev + * Vendor in latest containers/(storage,image,common) + * Split up error messages for missing --sbom related flags + * `buildah manifest`: add artifact-related options + * cmd/buildah/manifest.go: lock lists before adding/annotating/pushing + * cmd/buildah/manifest.go: don't make struct declarations aliases + * Use golang.org/x/exp/slices.Contains + * Disable loong64 again + * Fix a couple of typos in one-line comments + * egrep is obsolescent; use grep -E + * Try Cirrus with a newer VM version + * Set CONTAINERS_CONF in the chroot-mount-flags integration test + * Update to match dependency API update + * Update github.com/openshift/imagebuilder and containers/common + * docs: correct default authfile path + * fix(deps): update module github.com/containerd/containerd to v1.7.13 + * tests: retrofit test for heredoc summary + * build, heredoc: show heredoc summary in build output + * manifest, push: add support for --retry and --retry-delay + * fix(deps): update github.com/openshift/imagebuilder digest to b767bc3 + * imagebuildah: fix crash with empty RUN + * fix(deps): update github.com/containers/luksy digest to b62d551 + * fix(deps): update module github.com/opencontainers/runc to v1.1.12 [security] + * fix(deps): update module github.com/moby/buildkit to v0.12.5 [security] + * Make buildah match podman for handling of ulimits + * docs: move footnotes to where they're applicable + * Allow users to specify no-dereference + * Run codespell on code + * Fix FreeBSD version parsing + * Fix a build break on FreeBSD + * Remove a bad FROM line + * fix(deps): update module github.com/onsi/gomega to v1.31.1 + * fix(deps): update module github.com/opencontainers/image-spec to v1.1.0-rc6 + * docs: use reversed logo for dark theme in README + * build,commit: add --sbom to scan and produce SBOMs when committing + * commit: force omitHistory if the parent has layers but no history + * docs: fix a couple of typos + * internal/mkcw.Archive(): handle extra image content + * stage_executor,heredoc: honor interpreter in heredoc + * stage_executor,layers: burst cache if heredoc content is changed + * fix(deps): update module golang.org/x/crypto to v0.18.0 + * Replace map[K]bool with map[K]struct{} where it makes sense + * fix(deps): update module golang.org/x/sync to v0.6.0 + * fix(deps): update module golang.org/x/term to v0.16.0 + * Bump CI VMs + * Replace strings.SplitN with strings.Cut + * fix(deps): update github.com/containers/storage digest to ef81e9b + * fix(deps): update github.com/containers/image/v5 digest to 1b221d4 + * fix(deps): update module github.com/fsouza/go-dockerclient to v1.10.1 + * Document use of containers-transports values in buildah + * fix(deps): update module golang.org/x/crypto to v0.17.0 [security] + * chore(deps): update dependency containers/automation_images to v20231208 + * manifest: addCompression use default from containers.conf + * commit: add a --add-file flag + * mkcw: populate the rootfs using an overlay + * chore(deps): update dependency containers/automation_images to v20230517 + * [skip-ci] Update actions/stale action to v9 + * fix(deps): update module github.com/containernetworking/plugins to v1.4.0 + * fix(deps): update github.com/containers/image/v5 digest to 7a40fee + * Bump to v1.34.1-dev + * Ignore errors if label.Relabel returns ENOSUP + +- Changelog for v1.34.0 (2023-12-11) + * vendor: update c/{common,image,storage} + * run: Allow using just one jail per container on FreeBSD + * Remove makefile targets entrypoint{,.gz} for non x86_64 + +- Changelog for v1.33.2 (2023-11-22) + * Update minimum to golang 1.20 + * fix(deps): update module github.com/fsouza/go-dockerclient to v1.10.0 + * fix(deps): update module github.com/moby/buildkit to v0.12.3 + * Bump to v1.33.2-dev + +- Changelog for v1.33.1 (2023-11-18) + * fix(deps): update module github.com/moby/buildkit to v0.11.4 [security] + * test,heredoc: use fedora instead of docker.io/library/python:latest + * Bump to v1.33.1-dev + +- Changelog for v1.33.0 (2023-11-17) + * Never omit layers for emptyLayer instructions when squashing/cwing + * Add OverrideChanges and OverrideConfig to CommitOptions + * buildah: add heredoc support for RUN, COPY and ADD + * vendor: bump imagebuilder to v1.2.6-0.20231110114814-35a50d57f722 + * conformance tests: archive the context directory as 0:0 (#5171) + * blobcacheinfo,test: blobs must be resued when pushing across registry + * Bump c/storage v1.51.0, c/image v5.29.0, c/common v0.57.0 + * pkg/util.MirrorToTempFileIfPathIsDescriptor(): don't leak an fd + * StageExecutor.Execute: force a commit for --unsetenv, too + * Increase a copier+chroot test timeout + * Add support for --compat-auth-file in login/logout + * Update existing tests for error message change + * Update c/image and c/common to latest + * fix(deps): update module github.com/containerd/containerd to v1.7.9 + * build: downgrade to go 1.20 + * Add godoc for pkg/parse.GetTempDir + * conformance tests: use go-dockerclient for BuildKit builds + * Make TEE types case-insensitive + * fix(deps): update module golang.org/x/crypto to v0.15.0 + * Tweak some help descriptions + * Stop using DefaultNetworkSysctl and use containers.conf only + * Implement ADD checksum flag #5135 + * vendor of openshift/imagebuilder #5135 + * Pass secrets from the host down to internal podman containers + * Update cirrus and version of golang + * image: replace GetStoreImage with ResolveReference + * vendor: bump c/image to 373c52a9466f + * pkg/parse.Platform(): minor simplification + * createConfigsAndManifests: clear history before cw-specific logic + * Use a constant definition instead of "scratch" + * conformance: use require.NoErrorf() more + * fix(deps): update module golang.org/x/term to v0.14.0 + * fix(deps): update module golang.org/x/sync to v0.5.0 + * fix(deps): update module github.com/spf13/cobra to v1.8.0 + * fix(deps): update module golang.org/x/sys to v0.14.0 + * fix(deps): update github.com/containers/common digest to 8354404 + * fix(deps): update module github.com/opencontainers/runc to v1.1.10 + * fix(deps): update github.com/containers/luksy digest to b5a7f79 + * Log the platform for build errors during multi-platform builds + * Use mask definitions from containers/common + * Vendor in latest containers/common + * fix(deps): update module github.com/containerd/containerd to v1.7.8 + * fix(deps): update module go.etcd.io/bbolt to v1.3.8 + * container.conf: support attributed string slices + * fix(deps): update module sigs.k8s.io/yaml to v1.4.0 + * Use cutil.StringInSlice rather then contains + * Add --no-hostname option to buildah containers + * vendor c/common: appendable containers.conf strings, Part 1 + * fix(deps): update module github.com/onsi/gomega to v1.28.1 + * chroot.setupChrootBindMounts: pay more attention to flags + * chore(deps): update dependency containers/automation_images to v20231004 + * Vendor containers/common + * chore(deps): update module golang.org/x/net to v0.17.0 [security] + * run: use internal.GetTempDir with os.MkdirTemp + * fix(deps): update module github.com/containerd/containerd to v1.7.7 + * imagebuildah,multi-stage: do not remove base images + * gitignore: add mkcw binary + * mkcw: remove entrypoint binaries + * fix(deps): update module golang.org/x/crypto to v0.14.0 + * fix(deps): update module golang.org/x/sys to v0.13.0 + * fix(deps): update module golang.org/x/sync to v0.4.0 + * Update some comments related to confidential workload + * Use the parent's image ID in the config that we pass to imagebuilder + * fix(deps): update github.com/containers/common digest to 8892536 + * fix(deps): update github.com/containers/luksy digest to 6df88cb + * bug: Ensure the mount type is always BindMount by default + * Protocol can be specified with --port. Ex. --port 514/udp + * fix(deps): update module github.com/onsi/gomega to v1.28.0 + * build,config: add support for --unsetlabel + * tests/bud: add tests + * [CI:BUILD] Packit: tag @containers/packit-build on copr build failures + * stage_executor: allow images without layers + * vendor of containers/common + * Removing selinux_tag.sh as no longer needed after 580356f [NO NEW TESTS NEEDED] + * add/copy: make sure we handle relative path names correctly + * fix(deps): update module github.com/opencontainers/image-spec to v1.1.0-rc5 + * Bump to v1.33.0-dev + * imagebuildah: consider ignorefile with --build-context + +- Changelog for v1.32.0 (2023-09-14) + * GetTmpDir is not using ImageCopyTmpdir correctly + * Run codespell on code + * Bump vendor containers/(common, storage, image) + * Cirrus: Remove multi-arch buildah image builds + * fix(deps): update module github.com/containerd/containerd to v1.7.6 + * Split GetTempDir from internal/util + * Move most of internal/parse to internal/volumes + * copier: remove libimage dependency via util package + * Add some docs for `build --cw`, `commit --cw`, and `mkcw` + * Add `buildah mkcw`, add `--cw` to `buildah commit` and `buildah build` + * Make sure that pathnames picked up from the environment are absolute + * fix(deps): update module github.com/cyphar/filepath-securejoin to v0.2.4 + * fix(deps): update module github.com/docker/docker to v24.0.6+incompatible + * Don't try to look up names when committing images + * fix(deps): update module golang.org/x/crypto to v0.13.0 + * docs: use valid github repo + * fix(deps): update module golang.org/x/sys to v0.12.0 + * vendor containers/common@12405381ff45 + * push: --force-compression should be true with --compression-format + * Update module github.com/containerd/containerd to v1.7.5 + * [skip-ci] Update tim-actions/commit-message-checker-with-regex action to v0.3.2 + * docs: add reference to oci-hooks + * Support passing of ULimits as -1 to mean max + * GHA: Attempt to fix discussion_lock workflow + * Fixing the owner of the storage.conf. + * pkg/chrootuser: Ignore comments when parsing /etc/group on FreeBSD + * Use buildah repo rather then podman repo + * GHA: Closed issue/PR comment-lock test + * fix(deps): update module github.com/containers/storage to v1.49.0 + * chore(deps): update dependency containers/automation_images to v20230816 + * Replace troff code with markdown in buildah-{copy,add}.1.md + * [CI:BUILD] rpm: spdx compatible license field + * executor: build-arg warnings must honor global args + * fix(deps): update module github.com/containers/ocicrypt to v1.1.8 + * chroot: `setSeccomp` add support for `ArchPARISC(64)` and `ArchRISCV64` + * make,cross: restore loong64 + * Clear CommonBuildOpts when loading Builder status + * buildah/push/manifest-push: add support for --force-compression + * vendor: bump c/common to v0.55.1-0.20230811093040-524b4d5c12f9 + * chore(deps): update dependency containers/automation_images to v20230809 + * [CI:BUILD] RPM: fix buildtags + * fix(deps): update module github.com/opencontainers/runc to v1.1.9 + * chore(deps): update dependency ubuntu to v22 + * chore(deps): update dependency containers/automation_images to v20230807 + * [CI:BUILD] Packit: add fedora-eln targets + * [CI:BUILD] RPM: build docs with vendored go-md2man + * packit: Build PRs into default packit COPRs + * Update install.md + * Update install.md changes current Debian stable version name + * fix(deps): update module golang.org/x/term to v0.11.0 + * fix(deps): update module golang.org/x/crypto to v0.12.0 + * tests: fix layer-label tests + * buildah: add --layer-label for setting labels on layers + * Cirrus: container/rootless env. var. passthrough + * Cirrus: Remove duplicate env. var. definitions + * fix(deps): update github.com/containers/storage digest to c3da76f + * Add a missing .Close() call on an ImageSource + * Create only a reference when that's all we need + * Add a missing .Close() call on an ImageDestination + * CI:BUILD] RPM: define gobuild macro for rhel/centos stream + * manifest/push: add support for --add-compression + * manifest/inspect: add support for tls-verify and authfile + * vendor: bump c/common to v0.55.1-0.20230727095721-647ed1d4d79a + * vendor: bump c/image to v5.26.1-0.20230726142307-8c387a14f4ac + * fix(deps): update module github.com/containerd/containerd to v1.7.3 + * fix(deps): update module github.com/onsi/gomega to v1.27.10 + * fix(deps): update module github.com/docker/docker to v24.0.5+incompatible + * fix(deps): update module github.com/containers/image/v5 to v5.26.1 + * fix(deps): update module github.com/opencontainers/runtime-spec to v1.1.0 + * Update vendor of containers/(storage,image,common) + * fix(deps): update module github.com/opencontainers/runc to v1.1.8 + * [CI:BUILD] Packit: remove pre-sync action + * fix(deps): update module github.com/containers/common to v0.55.2 + * [CI:BUILD] Packit: downstream task script needs GOPATH + * Vendor in containers/(common, image, storage) + * fix(deps): update module golang.org/x/term to v0.10.0 + * [CI:BUILD] Packit: fix pre-sync action for downstream tasks + * contrib/buildahimage: set config correctly for rootless build user + * fix(deps): update module github.com/opencontainers/image-spec to v1.1.0-rc4 + * Bump to v1.32.0-dev + * Update debian install instructions + * pkg/overlay: add limited support for FreeBSD + +- Changelog for v1.31.0 (2023-06-30) + * Bump c/common to 0.55.1 and c/image to 5.26.1 + * Bump c/image to 5.26.0 and c/common to 0.54.0 + * vendor: update c/{common,image,storage} to latest + * chore: pkg imported more than once + * buildah: add pasta(1) support + * use slirp4netns package from c/common + * update c/common to latest + * add hostname to /etc/hosts when running with host network + * vendor: update c/common to latest + * [CI:BUILD] Packit: add jobs for downstream Fedora package builds + * fix(deps): update module golang.org/x/sync to v0.3.0 + * fix(deps): update module golang.org/x/crypto to v0.10.0 + * Add smoke tests for encryption CLI helpers + * fix(deps): update module golang.org/x/term to v0.9.0 + * fix(deps): update module github.com/opencontainers/runtime-spec to v1.1.0-rc.3 + * Remove device mapper support + * Remove use of deprecated tar.TypeRegA + * Update tooling to support newer golangci-lint + * Make cli.EncryptConfig,DecryptConfig, GetFormat public + * Don't decrypt images by default + * fix(deps): update module github.com/onsi/gomega to v1.27.8 + * fix(deps): update github.com/containers/storage digest to 3f3fb2f + * Renovate: Don't touch fragile test stuffs + * [CI:DOCS] Update comment to remove ambiguity + * fix(deps): update github.com/containers/image/v5 digest to abe5133 + * fix(deps): update module github.com/sirupsen/logrus to v1.9.3 + * fix(deps): update module github.com/containerd/containerd to v1.7.2 + * Explicitly ref. quay images for CI + * At startup, log the effective capabilities for debugging + * parse: use GetTempDir from internal utils + * GetTmpDir: honor image_copy_tmp_dir from containers.conf + * docs/Makefile: don't show sed invocations + * CI: Support testing w/ podman-next COPR packages + * intermediate-images inherit-label test: make it debuggable + * fix(deps): update github.com/containers/common digest to 462ccdd + * Add a warning to `--secret` docs + * vendor: bump c/storage to v1.46.2-0.20230526114421-55ee2d19292f + * executor: apply label to only final stage + * remove registry.centos.org + * Go back to setting SysProcAttr.Pdeathsig for child processes + * Fix auth.json path (validated on Fedora 38) wq Signed-off-by: Andreas Mack + * fix(deps): update module github.com/stretchr/testify to v1.8.3 + * CI: fix test broken by renovatebot + * chore(deps): update quay.io/libpod/testimage docker tag to v20221018 + * fix(deps): update module github.com/onsi/gomega to v1.27.7 + * test: use debian instead of docker.io/library/debian:testing-slim + * vendor: bump logrus to 1.9.2 + * [skip-ci] Update tim-actions/get-pr-commits action to v1.3.0 + * Revert "Proof of concept: nightly dependency treadmill" + * fix(deps): update module github.com/sirupsen/logrus to v1.9.1 + * vendor in containers/(common,storage,image) + * fix(deps): update module github.com/docker/distribution to v2.8.2+incompatible + * run: drop Pdeathsig + * chroot: lock thread before setPdeathsig + * tests: add a case for required=false + * fix(deps): update module github.com/openshift/imagebuilder to v1.2.5 + * build: validate volumes on backend + * secret: accept required flag w/o value + * fix(deps): update module github.com/containerd/containerd to v1.7.1 + * fix(deps): update module golang.org/x/crypto to v0.9.0 + * Update the demos README file to fix minor typos + * fix(deps): update module golang.org/x/sync to v0.2.0 + * fix(deps): update module golang.org/x/term to v0.8.0 + * manifest, push: use source as destination if not specified + * run,mount: remove path only if they didnt pre-exist + * Cirrus: Fix meta task failing to find commit + * parse: filter edge-case for podman-remote + * fix(deps): update module github.com/opencontainers/runc to v1.1.7 + * fix(deps): update module github.com/docker/docker to v23.0.5+incompatible + * build: --platform must accept only arch + * fix(deps): update module github.com/containers/common to v0.53.0 + * makefile: increase conformance timeout + * Cap suffixDigitsModulo to a 9-digits suffix. + * Rename conflict to suffixDigitsModulo + * fix(deps): update module github.com/opencontainers/runtime-spec to v1.1.0-rc.2 + * fix(deps): update module github.com/opencontainers/runc to v1.1.6 + * chore(deps): update centos docker tag to v8 + * Clarify the need for qemu-user-static package + * chore(deps): update quay.io/centos/centos docker tag to v8 + * Renovate: Ensure test/tools/go.mod is managed + * Revert "buildah image should not enable fuse-overlayfs for rootful mode" + * Bump to v1.31.0-dev + * parse: add support for relabel bind mount option + +- Changelog for v1.30.0 (2023-04-06) + * fix(deps): update module github.com/opencontainers/runc to v1.1.5 + * fix(deps): update module github.com/fsouza/go-dockerclient to v1.9.7 + * buildah image should not enable fuse-overlayfs for rootful mode + * stage_executor: inline network add default string + * fix(deps): update module github.com/containers/common to v0.51.2 + * chore(deps): update dependency containers/automation_images to v20230330 + * fix(deps): update module github.com/docker/docker to v23.0.2+incompatible + * chore(deps): update dependency containers/automation_images to v20230320 + * fix(deps): update module github.com/onsi/gomega to v1.27.6 + * fix(deps): update github.com/opencontainers/runtime-tools digest to e931285 + * [skip-ci] Update actions/stale action to v8 + * test: don't allow to override io.buildah.version + * executor: only apply label on the final stage + * Update docs/buildah-build.1.md + * update build instruction for Ubuntu + * code review + * build: accept arguments from file with --build-arg-file + * run_linux: Update heuristic for mounting /sys + * [CI:BUILD] Packit: Enable Copr builds on PR and commit to main + * fix(deps): update module github.com/fsouza/go-dockerclient to v1.9.6 + * Update to Go 1.18 + * Disable dependabot in favor of renovate + * chore(deps): update dependency containers/automation_images to v20230314 + * Fix requiring tests on Makefile changes + * Vendor in latest containers/(storage, common, image) + * imagebuildah: set len(short_image_id) to 12 + * Re-enable conformance tests + * Skip conformance test failures with Docker 23.0.1 + * Cirrus: Replace Ubuntu -> Debian SID + * run: add support for inline --network in RUN stmt + * vendor: bump imagebuilder to a3c3f8358ca31b1e4daa6 + * stage_executor: attempt to push cache only when cacheKey is valid + * Add "ifnewer" as option in help message for pull command + * build: document behaviour of buildah's distributed cache + * fix(deps): update module golang.org/x/term to v0.6.0 + * Add default list of capabilities required to run buildah in a container + * executor,copy: honor default ARG value while eval stage + * sshagent: use ExtendedAgent instead of Agent + * tests/bud: remove unwated test + * executor: do not warn on builtin default args + * executor: don't warn about unused TARGETARCH,TARGETOS,TARGETPLATFORM + * Fix tutorial for rootless mode + * Vendor in latest containers/(common, storage, image) + * Ignore the base image's base image annotations + * fix(deps): update module github.com/fsouza/go-dockerclient to v1.9.5 + * build(deps): bump github.com/containers/storage from 1.45.3 to 1.45.4 + * Vendor in latest containers/common + * docs/tutorials/04: add defaults for Run() + * imagebuildah.StageExecutor: suppress bogus "Pushing cache []:..." + * executor: also add stage with no children to cleanupStages + * [CI:BUILD] copr: fix el8 builds + * Fix documentation on which Capabilities are allowed by default + * Skip subject-length validation for renovate PRs + * Temporarily hard-skip bud-multiple-platform-values test + * fix(deps): update github.com/openshift/imagebuilder digest to 86828bf + * build(deps): bump github.com/containerd/containerd from 1.6.16 to 1.6.17 + * build(deps): bump tim-actions/get-pr-commits from 1.1.0 to 1.2.0 + * build(deps): bump github.com/containers/image/v5 from 5.24.0 to 5.24.1 + * [skip-ci] Update tim-actions/get-pr-commits digest to 55b867b + * build(deps): bump github.com/opencontainers/selinux + * build(deps): bump golang.org/x/crypto from 0.5.0 to 0.6.0 + * Add renovate configuration + * Run codespell on codebase + * login: support interspersed args for password + * conformance: use scratch for minimal test + * pkg/parse: expose public CleanCacheMount API + * build(deps): bump go.etcd.io/bbolt from 1.3.6 to 1.3.7 + * build(deps): bump github.com/containerd/containerd from 1.6.15 to 1.6.16 + * docs: specify order preference for FROM + * Bump to v1.30.0-dev + +- Changelog for v1.29.0 (2023-01-25) + * tests: improve build-with-network-test + * Bump c/storagev1.45.3, c/imagev5.24.0, c/commonv0.51.0 + * build(deps): bump github.com/onsi/gomega from 1.25.0 to 1.26.0 + * Flake 3710 has been closed. Reenable the test. + * [CI:DOCS] Fix two diversity issues in a tutorial + * build(deps): bump github.com/fsouza/go-dockerclient from 1.9.2 to 1.9.3 + * vendor in latests containers/(storage, common, image) + * fix bud-multiple-platform-with-base-as-default-arg flake + * stage_executor: while mounting stages use freshly built stage + * build(deps): bump github.com/fsouza/go-dockerclient from 1.9.0 to 1.9.2 + * build(deps): bump github.com/onsi/gomega from 1.24.2 to 1.25.0 + * vendor in latests containers/(storage, common, image, ocicyrpt) + * [Itests: change the runtime-flag test for crun + * [CI:DOCS] README: drop sudo + * Fix multi-arch manifest-list build timeouts + * Cirrus: Update VM Images + * bud: Consolidate multiple synthetic LABEL instructions + * build, secret: allow realtive mountpoints wrt to work dir + * fixed squash documentation + * build(deps): bump github.com/containerd/containerd from 1.6.14 to 1.6.15 + * Correct minor comment + * Vendor in latest containers/(common, image, storage) + * system tests: remove unhelpful assertions + * buildah: add prune command and expose CleanCacheMount API + * vendor: bump c/storage to a747b27 + * Add support for --group-add to buildah from + * build(deps): bump actions/stale from 6 to 7 + * Add documentation for buildah build --pull=missing + * build(deps): bump github.com/containerd/containerd from 1.6.12 to 1.6.14 + * build(deps): bump github.com/docker/docker + * parse: default ignorefile must not point to symlink outside context + * buildah: wrap network setup errors + * build, mount: allow realtive mountpoints wrt to work dir + * Update to F37 CI VM Images, re-enable prior-fedora + * Update vendor or containers/(image, storage, common) + * build(deps): bump golang.org/x/crypto from 0.3.0 to 0.4.0 + * Update contact information + * build(deps): bump golang.org/x/term from 0.2.0 to 0.3.0 + * Replace io/ioutil calls with os calls + * [skip-ci] GHA/Cirrus-cron: Fix execution order + * Vendor in containers/common + * build(deps): bump golang.org/x/sys from 0.2.0 to 0.3.0 + * remote-cache: support multiple sources and destinations + * Update c/storage after https://github.com/containers/storage/pull/1436 + * util.SortMounts(): make the returned order more stable + * version: Bump to 1.29.0-dev + * [CI:BUILD] Cirrus: Migrate OSX task to M1 + * Update vendor of containers/(common, storage, image) + * mount=type=cache: seperate cache parent on host for each user + * Fix installation instructions for Gentoo Linux + * build(deps): bump github.com/containerd/containerd from 1.6.9 to 1.6.10 + * GHA: Reuse both cirrus rerun and check workflows + * Vendor in latest containers/(common,image,storage) + * build(deps): bump github.com/onsi/gomega from 1.24.0 to 1.24.1 + * copier.Put(): clear up os/syscall mode bit confusion + * build(deps): bump golang.org/x/sys from 0.1.0 to 0.2.0 + * Use TypeBind consistently to name bind/nullfs mounts + * Add no-new-privileges flag + * Update vendor of containers/(common, image, storage) + * imagebuildah:build with --all-platforms must honor args for base images + * codespell code + * Expand args and env when using --all-platforms + * build(deps): bump github.com/onsi/gomega from 1.23.0 to 1.24.0 + * GHA: Simplify Cirrus-Cron check slightly + * Stop using ubi8 + * remove unnecessary (hence misleading) rmi + * chroot: fix mounting of ro bind mounts + * executor: honor default ARG value while eval base name + * userns: add arbitrary steps/stage to --userns=auto test + * Don't set allow.mount in the vnet jail on Freebsd + * copier: Preserve file flags when copying archives on FreeBSD + * Remove quiet flag, so that it works in podman-remote + * test: fix preserve rootfs with --mount for podman-remote + * test: fix prune logic for cache-from after adding content summary + * vendor in latest containers/(storage, common, image) + * Fix RUN --mount=type=bind,from= not preserving rootfs of stage + * Define and use a safe, reliable test image + * Fix word missing in Container Tools Guide + * Makefile: Use $(MAKE) to start sub-makes in install.tools + * imagebuildah: pull cache from remote repo after adding content summary + * Makefile: Fix install on FreeBSD + * Ensure the cache volume locks are unlocked on all paths + * Vendor in latest containers/(common,storage) + * Simplify the interface of GetCacheMount and getCacheMount + * Fix cache locks with multiple mounts + * Remove calls to Lockfile.Locked() + * Maintain cache mount locks as lock objects instead of paths + * test: cleaning cache must not clean lockfiles + * run: honor lockfiles for multiple --mount instruction + * mount,cache: lockfiles must not be part of users cache content + * Update vendor containers/(common,image,storage) + * [CI:BUILD] copr: buildah rpm should depend on containers-common-extra + * pr-should-include-tests: allow specfile, golangci + * build(deps): bump dawidd6/action-send-mail from 3.7.0 to 3.7.1 + * build(deps): bump github.com/docker/docker + * build(deps): bump github.com/fsouza/go-dockerclient from 1.8.3 to 1.9.0 + * Update vendor containers/(common,image,storage) + * build(deps): bump actions/upload-artifact from 2 to 3 + * build(deps): bump actions/checkout from 2 to 3 + * build(deps): bump actions/stale from 1 to 6 + * build(deps): bump dawidd6/action-send-mail from 2.2.2 to 3.7.0 + * build(deps): bump tim-actions/get-pr-commits from 1.1.0 to 1.2.0 + * sshagent: LockOSThread before setting SocketLabel + * Update tests for error message changes + * Update c/image after https://github.com/containers/image/pull/1299 + * Fix ident for dependabot gha block + * build(deps): bump github.com/containers/ocicrypt from 1.1.5 to 1.1.6 + * Fix man pages to match latest cobra settings + * build(deps): bump github.com/spf13/cobra from 1.5.0 to 1.6.0 + * build(deps): bump github.com/onsi/gomega from 1.20.2 to 1.22.1 + * test: retrofit 'bud with undefined build arg directory' + * imagebuildah: warnOnUnsetBuildArgs while processing stages from executor + * Update contrib/buildahimage/Containerfile + * Cirrus CI add flavor parameter + * Correction - `FLAVOR` not `FLAVOUR` + * Changed build argument from `RELEASE` to `FLAVOUR` + * Combine buildahimage Containerfiles + * bud.bats refactoring: $TEST_SCRATCH_DIR, part 2 of 2 + * bud.bats refactoring: $TEST_SCRATCH_DIR, part 1 of 2 + * System test cleanup: document, clarify, fix + * test: removing unneeded/expensive COPY + * test: warning behaviour for unset/set TARGETOS,TARGETARCH,TARGETPLATFORM + * Bump to v1.28.1-dev + +- Changelog for v1.28.0 (2022-09-30) + * Update vendor containers/(common,image) + * [CI:DOCS] Add quay-description update reminder + * vendor: bump c/common to v0.49.2-0.20220929111928-2d1b45ae2423 + * build(deps): bump github.com/opencontainers/selinux + * Vendor in latest containers/storage + * Changing shell list operators from `;` to `&&` + * Fix buildahimage container.conf permissions regression + * Set sysctls from containers.conf + * refactor: stop using Normalize directly from containerd package + * config,builder: process variant while populating image spec + * Proof of concept: nightly dependency treadmill + * Run codespell on code + * Check for unset build args after TARGET args + * pkg/cli: improve completion test + * vendor in latest containers/(common,storage,image) + * copier: work around freebsd bug for "mkdir /" + * vendor: update c/image + * test: run in the host cgroup namespace + * vendor: update c/storage + * vendor: update c/common + * cmd: check for user UID instead of privileges + * run,build: conflict --isolation=chroot and --network + * Fix broken dns test (from merge collision) + * Fix stutters + * Fix broken command completion + * buildah bud --network=none should have no network + * build: support --skip-unused-stages for multi-stage builds + * Prevent use of --dns* options with --net=none + * buildah: make --cache-ttl=0s equivalent to --no-cache + * parse: make processing flags in --mount order agnostic + * Minor test fix for podman-remote + * build: honor .containerignore as ignore file + * Update install.md: Debian 11 (Bullseye) is stable + * build(deps): bump github.com/docker/docker + * Use constants from containers/common for finding seccomp.json + * Don't call os.Exit(1) from manifest exist + * manifest: add support for buildah manifest exists + * Buildah should ignore /etc/crio/seccomp.json + * chroot: Fix cross build break + * chroot: Move isDevNull to run_common.go + * chroot: Fix setRlimit build on FreeBSD + * chroot: Move parseRLimits and setRlimits to run_common.go + * chroot: Fix runUsingChrootExecMain on FreeBSD + * chroot: Move runUsingChrootExecMain to run_common.go + * chroot: Factor out Linux-specific unshare options from runUsingChroot + * chroot: Move runUsingChroot to run_common.go + * chroot: Move RunUsingChroot and runUsingChrootMain to run_common.go + * chroot: Factor out /dev/ptmx pty implementation + * chroot: Add FreeBSD support for run with chroot isolation + * build(deps): bump github.com/docker/go-units from 0.4.0 to 0.5.0 + * Replace k8s.gcr.io/pause in tests with registry.k8s.io/pause + * build(deps): bump github.com/onsi/gomega from 1.20.0 to 1.20.1 + * Cirrus: use image with fewer downloaded dependencies + * build(deps): bump github.com/opencontainers/runc from 1.1.3 to 1.1.4 + * run: add container gid to additional groups + * buildah: support for --retry and --retry-delay for push/pull failures + * Makefile: always call $(GO) instead of `go` + * build(deps): bump github.com/fsouza/go-dockerclient from 1.8.2 to 1.8.3 + * test: use `T.TempDir` to create temporary test directory + * mount,cache: enable SElinux shared content label option by default + * commit: use race-free RemoveNames instead of SetNames + * Drop util/util.Cause() + * cmd/buildah: add "manifest create --amend" + * build(deps): bump github.com/fsouza/go-dockerclient from 1.8.1 to 1.8.2 + * docs: specify git protocol is not supported for github hosted repo + * Scrub user and group names from layer diffs + * build(deps): bump github.com/containerd/containerd from 1.6.6 to 1.6.8 + * version: bump to 1.28.0-dev + +- Changelog for v1.27.0 (2022-08-01) + * build: support filtering cache by duration using `--cache-ttl`. + * build: support building from commit when using git repo as build context. + * build: clean up git repos correctly when using subdirs. + * build: add support for distributing cache to remote sources using `--cache-to` and `--cache-from`. + * imagebuildah: optimize cache hits for `COPY` and `ADD` instructions. + * build: support OCI hooks for ephemeral build containers. + * build: add support for `--userns=auto`. + * copier: add NoOverwriteNonDirDir option . + * add initial support for building images using Buildah on FreeBSD. + * multistage: this now skips the computing of unwanted stages to improve performance. + * multiarch: support splitting build logs for `--platform` using `--logsplit`. + * build: add support for building images where the base image has no history. + * commit: allow disabling image history with `--omit-history`. + * build: add support for renaming a device in rootless setups. + * build: now supports additionalBuildContext in builds via the `--build-context` option. + * build: `--output` produces artifacts even if the build container is not committed. + * build: now accepts `-cpp-flag`, allowing users to pass in CPP flags when processing a Containerfile with C Preprocessor-like syntax. + * build: now accepts a branch and a subdirectory when the build context is a git repository. + * build: output now shows a progress bar while pushing and pulling images + * build: now errors out if the path to Containerfile is a directory. + * build: support building container images on environments that are rootless and without any valid login sessions. + * fix: `--output` now generates artifacts even if the entire build is cached. + * fix: `--output` generates artifacts only for the target stage in multi-stage builds. + * fix,add: now fails on a bad HTTP response instead of writing to container + * fix,squash: never use build cache when computing the last step of the last stage + * fix,build,run: allow reusing secret more than once in different RUN steps + * fix: compatibility with Docker build by making its --label and --annotate options set empty labels and annotations when given a name but no `=` or label value. + +- Changelog for v1.26.0 (2022-05-04) + * imagebuildah,build: move deepcopy of args before we spawn goroutine + * Vendor in containers/storage v1.40.2 + * buildah.BuilderOptions.DefaultEnv is ignored, so mark it as deprecated + * help output: get more consistent about option usage text + * Handle OS version and features flags + * buildah build: --annotation and --label should remove values + * buildah build: add a --env + * buildah: deep copy options.Args before performing concurrent build/stage + * test: inline platform and builtinargs behaviour + * vendor: bump imagebuilder to master/009dbc6 + * build: automatically set correct TARGETPLATFORM where expected + * build(deps): bump github.com/fsouza/go-dockerclient + * Vendor in containers/(common, storage, image) + * imagebuildah, executor: process arg variables while populating baseMap + * buildkit: add support for custom build output with --output + * Cirrus: Update CI VMs to F36 + * fix staticcheck linter warning for deprecated function + * Fix docs build on FreeBSD + * build(deps): bump github.com/containernetworking/cni from 1.0.1 to 1.1.0 + * copier.unwrapError(): update for Go 1.16 + * copier.PutOptions: add StripSetuidBit/StripSetgidBit/StripStickyBit + * copier.Put(): write to read-only directories + * build(deps): bump github.com/cpuguy83/go-md2man/v2 in /tests/tools + * Rename $TESTSDIR (the plural one), step 4 of 3 + * Rename $TESTSDIR (the plural one), step 3 of 3 + * Rename $TESTSDIR (the plural one), step 2 of 3 + * Rename $TESTSDIR (the plural one), step 1 of 3 + * build(deps): bump github.com/containerd/containerd from 1.6.2 to 1.6.3 + * Ed's periodic test cleanup + * using consistent lowercase 'invalid' word in returned err msg + * Update vendor of containers/(common,storage,image) + * use etchosts package from c/common + * run: set actual hostname in /etc/hostname to match docker parity + * update c/common to latest main + * Update vendor of containers/(common,storage,image) + * Stop littering + * manifest-create: allow creating manifest list from local image + * Update vendor of storage,common,image + * Bump golang.org/x/crypto to 7b82a4e + * Initialize network backend before first pull + * oci spec: change special mount points for namespaces + * tests/helpers.bash: assert handle corner cases correctly + * buildah: actually use containers.conf settings + * integration tests: learn to start a dummy registry + * Fix error check to work on Podman + * buildah build should accept at most one arg + * tests: reduce concurrency for flaky bud-multiple-platform-no-run + * vendor in latest containers/common,image,storage + * manifest-add: allow override arch,variant while adding image + * Remove a stray `\` from .containerenv + * Vendor in latest opencontainers/selinux v1.10.1 + * build, commit: allow removing default identity labels + * Create shorter names for containers based on image IDs + * test: skip rootless on cgroupv2 in root env + * fix hang when oci runtime fails + * Set permissions for GitHub actions + * copier test: use correct UID/GID in test archives + * run: set parent-death signals and forward SIGHUP/SIGINT/SIGTERM + * Bump back to v1.26.0-dev + * build(deps): bump github.com/opencontainers/runc from 1.1.0 to 1.1.1 + * Included the URL to check the SHA + +- Changelog for v1.25.1 (2022-03-30) + * buildah: create WORKDIR with USER permissions + * vendor: update github.com/openshift/imagebuilder + * copier: attempt to open the dir before adding it + * Updated dependabot to get updates for GitHub actions. + * Switch most calls to filepath.Walk to filepath.WalkDir + * build: allow --no-cache and --layers so build cache can be overrided + * build(deps): bump github.com/onsi/gomega from 1.18.1 to 1.19.0 + * Bump to v1.26.0-dev + * build(deps): bump github.com/golangci/golangci-lint in /tests/tools + +- Changelog for v1.25.0 (2022-03-25) + * install: drop RHEL/CentOS 7 doc + * build(deps): bump github.com/containers/common from 0.47.4 to 0.47.5 + * Bump c/storage to v1.39.0 in main + * Add a test for CVE-2022-27651 + * build(deps): bump github.com/docker/docker + * Bump github.com/prometheus/client_golang to v1.11.1 + * [CI:DOCS] man pages: sort flags, and keep them that way + * build(deps): bump github.com/containerd/containerd from 1.6.1 to 1.6.2 + * Don't pollute + * network setup: increase timeout to 4 minutes + * do not set the inheritable capabilities + * build(deps): bump github.com/golangci/golangci-lint in /tests/tools + * build(deps): bump github.com/containers/ocicrypt from 1.1.2 to 1.1.3 + * parse: convert exposed GetVolumes to internal only + * buildkit: mount=type=cache support locking external cache store + * .in support: improve error message when cpp is not installed + * buildah image: install cpp + * build(deps): bump github.com/stretchr/testify from 1.7.0 to 1.7.1 + * build(deps): bump github.com/spf13/cobra from 1.3.0 to 1.4.0 + * build(deps): bump github.com/docker/docker + * Add --no-hosts flag to eliminate use of /etc/hosts within containers + * test: remove skips for rootless users + * test: unshare mount/umount if test is_rootless + * tests/copy: read correct containers.conf + * build(deps): bump github.com/docker/distribution + * cirrus: add seperate task and matrix for rootless + * tests: skip tests for rootless which need unshare + * buildah: test rootless integration + * vendor: bump c/storage to main/93ce26691863 + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.9 to 1.7.10 + * tests/copy: initialize the network, too + * [CI:DOCS] remove references to Kubic for CentOS and Ubuntu + * build(deps): bump github.com/containerd/containerd from 1.6.0 to 1.6.1 + * use c/image/pkg/blobcache + * vendor c/image/v5@v5.20.0 + * add: ensure the context directory is an absolute path + * executor: docker builds must inherit healthconfig from base if any + * docs: Remove Containerfile and containeringore + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.8 to 1.7.9 + * helpers.bash: Use correct syntax + * speed up combination-namespaces test + * build(deps): bump github.com/golangci/golangci-lint in /tests/tools + * Bump back to 1.25.0-dev + * build(deps): bump github.com/containerd/containerd from 1.5.9 to 1.6.0 + +- Changelog for v1.24.2 (2022-02-16) + * Increase subuid/subgid to 65535 + * history: only add proxy vars to history if specified + * run_linux: use --systemd-cgroup + * buildah: new global option --cgroup-manager + * Makefile: build with systemd when available + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.7 to 1.7.8 + * Bump c/common to v0.47.4 + * Cirrus: Use updated VM images + * conformance: add a few "replace-directory-with-symlink" tests + * Bump back to v1.25.0-dev + +- Changelog for v1.24.1 (2022-02-03) + * executor: Add support for inline --platform within Dockerfile + * caps: fix buildah run --cap-add=all + * Update vendor of openshift/imagebuilder + * Bump version of containers/image and containers/common + * Update vendor of containers/common + * System tests: fix accidental vandalism of source dir + * build(deps): bump github.com/containers/storage from 1.38.1 to 1.38.2 + * imagebuildah.BuildDockerfiles(): create the jobs semaphore + * build(deps): bump github.com/onsi/gomega from 1.18.0 to 1.18.1 + * overlay: always honor mountProgram + * overlay: move mount program invocation to separate function + * overlay: move mount program lookup to separate function + * Bump to v1.25.0-dev [NO TESTS NEEDED] + +- Changelog for v1.24.0 (2022-01-26) + * Update vendor of containers/common + * build(deps): bump github.com/golangci/golangci-lint in /tests/tools + * Github-workflow: Report both failures and errors. + * build(deps): bump github.com/containers/image/v5 from 5.18.0 to 5.19.0 + * Update docs/buildah-build.1.md + * [CI:DOCS] Fix typos and improve language + * buildah bud --network add support for custom networks + * Make pull commands be consistent + * docs/buildah-build.1.md: don't imply that -v isn't just a RUN thing + * build(deps): bump github.com/onsi/gomega from 1.17.0 to 1.18.0 + * Vendor in latest containers/image + * Run codespell on code + * .github/dependabot.yml: add tests/tools go.mod + * CI: rm git-validation, add GHA job to validate PRs + * tests/tools: bump go-md2man to v2.0.1 + * tests/tools/Makefile: simplify + * tests/tools: bump onsi/ginkgo to v1.16.5 + * vendor: bump c/common and others + * mount: add support for custom upper and workdir with overlay mounts + * linux: fix lookup for runtime + * overlay: add MountWithOptions to API which extends support for advanced overlay + * Allow processing of SystemContext from FlagSet + * .golangci.yml: enable unparam linter + * util/resolveName: rm bool return + * tests/tools: bump golangci-lint + * .gitignore: fixups + * all: fix capabilities.NewPid deprecation warnings + * bind/mount.go: fix linter comment + * all: fix gosimple warning S1039 + * tests/e2e/buildah_suite_test.go: fix gosimple warnings + * imagebuildah/executor.go: fix gosimple warning + * util.go: fix gosimple warning + * build(deps): bump github.com/opencontainers/runc from 1.0.3 to 1.1.0 + * Enable git-daemon tests + * Allow processing of id options from FlagSet + * Cirrus: Re-order tasks for more parallelism + * Cirrus: Freshen VM images + * Fix platform handling for empty os/arch values + * Allow processing of network options from FlagSet + * Fix permissions on secrets directory + * Update containers/image and containers/common + * bud.bats: use a local git daemon for the git protocol test + * Allow processing of common options from FlagSet + * Cirrus: Run int. tests in parallel with unit + * vendor c/common + * Fix default CNI paths + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.6 to 1.7.7 + * multi-stage: enable mounting stages across each other with selinux enabled + * executor: Share selinux label of first stage with other stages in a build + * buildkit: add from field to bind and cache mounts so images can be used as source + * Use config.ProxyEnv from containers/common + * use libnetwork from c/common for networking + * setup the netns in the buildah parent process + * build(deps): bump github.com/containerd/containerd from 1.5.8 to 1.5.9 + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.4 to 1.7.6 + * build: fix libsubid test + * Allow callers to replace the ContainerSuffix + * parse: allow parsing anomaly non-human value for memory control group + * .cirrus: remove static_build from ci + * stage_executor: re-use all possible layers from cache for squashed builds + * build(deps): bump github.com/spf13/cobra from 1.2.1 to 1.3.0 + * Allow rootless buildah to set resource limits on cgroup V2 + * build(deps): bump github.com/docker/docker + * tests: move buildkit mount tests files from TESTSDIR to TESTDIR before modification + * build(deps): bump github.com/opencontainers/runc from 1.0.2 to 1.0.3 + * Wire logger through to config + * copier.Put: check for is-not-a-directory using lstat, not stat + * Turn on rootless cgroupv2 tests + * Grab all of the containers.conf settings for namespaces. + * image: set MediaType in OCI manifests + * copier: RemoveAll possibly-directories + * Simple README fix + * images: accept multiple filter with logical AND + * build(deps): bump github.com/containernetworking/cni from 0.8.1 to 1.0.1 + * UPdate vendor of container/storage + * build(deps): bump github.com/onsi/gomega from 1.16.0 to 1.17.0 + * build(deps): bump github.com/containers/image/v5 from 5.16.1 to 5.17.0 + * Make LocalIP public function so Podman can use it + * Fix UnsetEnv for buildah bud + * Tests should rely only on static/unchanging images + * run: ensure that stdio pipes are labeled correctly + * build(deps): bump github.com/docker/docker + * Cirrus: Bump up to Fedora 35 & Ubuntu 21.10 + * chroot: don't use the generate default seccomp filter for unit tests + * build(deps): bump github.com/containerd/containerd from 1.5.7 to 1.5.8 + * ssh-agent: Increase timeout before we explicitly close connection + * docs/tutorials: update + * Clarify that manifest defaults to localhost as the registry name + * "config": remove a stray bit of debug output + * "commit": fix a flag typo + * Fix an error message: unlocking vs locking + * Expand the godoc for CommonBuildOptions.Secrets + * chroot: accept an "rw" option + * Add --unsetenv option to buildah commit and build + * define.TempDirForURL(): show CombinedOutput when a command fails + * config: support the variant field + * rootless: do not bind mount /sys if not needed + * Fix tutorial to specify command on buildah run line + * build: history should not contain ARG values + * docs: Use guaranteed path for go-md2man + * run: honor --network=none from builder if nothing specified + * networkpolicy: Should be enabled instead of default when explictly set + * Add support for env var secret sources + * build(deps): bump github.com/docker/docker + * fix: another non-portable shebang + * Rootless containers users should use additional groups + * Support overlayfs path contains colon + * Report ignorefile location when no content added + * Add support for host.containers.internal in the /etc/hosts + * build(deps): bump github.com/onsi/ginkgo from 1.16.4 to 1.16.5 + * imagebuildah: fix nil deref + * buildkit: add support for mount=type=cache + * Default secret mode to 400 + * [CI:DOCS] Include manifest example usage + * docs: update buildah-from, buildah-pull 'platform' option compatibility notes + * docs: update buildah-build 'platform' option compatibility notes + * De-dockerize the man page as much as possible + * [CI:DOCS] Touch up Containerfile man page to show ARG can be 1st + * docs: Fix and Update Containerfile man page with supported mount types + * mount: add tmpcopyup to tmpfs mount option + * buildkit: Add support for --mount=type=tmpfs + * build(deps): bump github.com/opencontainers/selinux from 1.8.5 to 1.9.1 + * Fix command doc links in README.md + * build(deps): bump github.com/containers/image/v5 from 5.16.0 to 5.16.1 + * build: Add support for buildkit like --mount=type=bind + * Bump containerd to v1.5.7 + * build(deps): bump github.com/docker/docker + * tests: stop pulling php, composer + * Fix .containerignore link file + * Cirrus: Fix defunct package metadata breaking cache + * build(deps): bump github.com/containers/storage from 1.36.0 to 1.37.0 + * buildah build: add --all-platforms + * Add man page for Containerfile and .containerignore + * Plumb the remote logger throughut Buildah + * Replace fmt.Sprintf("%d", x) with strconv.Itoa(x) + * Run: Cleanup run directory after every RUN step + * build(deps): bump github.com/containers/common from 0.45.0 to 0.46.0 + * Makefile: adjust -ldflags/-gcflags/-gccgoflags depending on the go implementation + * Makefile: check for `-race` using `-mod=vendor` + * imagebuildah: fix an attempt to write to a nil map + * push: support to specify the compression format + * conformance: allow test cases to specify dockerUseBuildKit + * build(deps): bump github.com/containers/common from 0.44.1 to 0.45.0 + * build(deps): bump github.com/containers/common from 0.44.0 to 0.44.1 + * unmarshalConvertedConfig(): handle zstd compression + * tests/copy/copy: wire up compression options + * Update to github.com/vbauerster/mpb v7.1.5 + * Add flouthoc to OWNERS + * build: Add additional step nodes when labels are modified + * Makefile: turn on race detection whenever it's available + * conformance: add more tests for exclusion short-circuiting + * Update VM Images + Drop prior-ubuntu testing + * Bump to v1.24.0-dev + +- Changelog for v1.23.0 (2021-09-13) + * Vendor in containers/common v0.44.0 + * build(deps): bump github.com/containers/storage from 1.35.0 to 1.36.0 + * Update 05-openshift-rootless-build.md + * build(deps): bump github.com/opencontainers/selinux from 1.8.4 to 1.8.5 + * .cirrus.yml: run cross_build_task on Big Sur + * Makefile: update cross targets + * Add support for rootless overlay mounts + * Cirrus: Increase unit-test timeout + * Docs: Clarify rmi w/ manifest/index use + * build: mirror --authfile to filesystem if pointing to FD instead of file + * Fix build with .git url with branch + * manifest: rm should remove only manifests not referenced images. + * vendor: bump c/common to v0.43.3-0.20210902095222-a7acc160fb25 + * Avoid rehashing and noop compression writer + * corrected man page section; .conf file to mention its man page + * copy: add --max-parallel-downloads to tune that copy option + * copier.Get(): try to avoid descending into directories + * tag: Support tagging manifest list instead of resolving to images + * Install new manpages to correct sections + * conformance: tighten up exception specifications + * Add support for libsubid + * Add epoch time field to buildah images + * Fix ownership of /home/build/.local/share/containers + * build(deps): bump github.com/containers/image/v5 from 5.15.2 to 5.16.0 + * Rename bud to build, while keeping an alias for to bud. + * Replace golang.org/x/crypto/ssh/terminal with golang.org/x/term + * build(deps): bump github.com/opencontainers/runc from 1.0.1 to 1.0.2 + * build(deps): bump github.com/onsi/gomega from 1.15.0 to 1.16.0 + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.3 to 1.7.4 + * build(deps): bump github.com/containers/common from 0.43.1 to 0.43.2 + * Move DiscoverContainerfile to pkg/util directory + * build(deps): bump github.com/containers/image/v5 from 5.15.1 to 5.15.2 + * Remove some references to Docker + * build(deps): bump github.com/containers/image/v5 from 5.15.0 to 5.15.1 + * imagebuildah: handle --manifest directly + * build(deps): bump github.com/containers/common from 0.42.1 to 0.43.1 + * build(deps): bump github.com/opencontainers/selinux from 1.8.3 to 1.8.4 + * executor: make sure imageMap is updated with terminatedStage + * tests/serve/serve.go: use a kernel-assigned port + * Bump go for vendor-in-container from 1.13 to 1.16 + * imagebuildah: move multiple-platform building internal + * Adds GenerateStructure helper function to support rootfs-overlay. + * Run codespell to fix spelling + * Implement SSH RUN mount + * build(deps): bump github.com/onsi/gomega from 1.14.0 to 1.15.0 + * Fix resolv.conf content with run --net=private + * run: fix nil deref using the option's logger + * build(deps): bump github.com/containerd/containerd from 1.5.1 to 1.5.5 + * make vendor-in-container + * bud: teach --platform to take a list + * set base-image annotations + * build(deps): bump github.com/opencontainers/selinux from 1.8.2 to 1.8.3 + * [CI:DOCS] Fix CHANGELOG.md + * Bump to v1.23.0-dev [NO TESTS NEEDED] + * Accept repositories on login/logout + +- Changelog for v1.22.0 (2021-08-02) + * c/image, c/storage, c/common vendor before Podman 3.3 release + * WIP: tests: new assert() + * Proposed patch for 3399 (shadowutils) + * Fix handling of --restore shadow-utils + * build(deps): bump github.com/containers/image/v5 from 5.13.2 to 5.14.0 + * runtime-flag (debug) test: handle old & new runc + * build(deps): bump github.com/containers/storage from 1.32.6 to 1.33.0 + * Allow dst and destination for target in secret mounts + * Multi-arch: Always push updated version-tagged img + * Add a few tests on cgroups V2 + * imagebuildah.stageExecutor.prepare(): remove pseudonym check + * refine dangling filter + * Chown with environment variables not set should fail + * Just restore protections of shadow-utils + * build(deps): bump github.com/opencontainers/runc from 1.0.0 to 1.0.1 + * Remove specific kernel version number requirement from install.md + * Multi-arch image workflow: Make steps generic + * chroot: fix environment value leakage to intermediate processes + * Update nix pin with `make nixpkgs` + * buildah source - create and manage source images + * Update cirrus-cron notification GH workflow + * Reuse code from containers/common/pkg/parse + * Cirrus: Freshen VM images + * build(deps): bump github.com/containers/storage from 1.32.5 to 1.32.6 + * Fix excludes exception begining with / or ./ + * Fix syntax for --manifest example + * build(deps): bump github.com/onsi/gomega from 1.13.0 to 1.14.0 + * vendor containers/common@main + * Cirrus: Drop dependence on fedora-minimal + * Adjust conformance-test error-message regex + * Workaround appearance of differing debug messages + * Cirrus: Install docker from package cache + * build(deps): bump github.com/containers/ocicrypt from 1.1.1 to 1.1.2 + * Switch rusagelogfile to use options.Out + * build(deps): bump github.com/containers/storage from 1.32.4 to 1.32.5 + * Turn stdio back to blocking when command finishes + * Add support for default network creation + * Cirrus: Updates for master->main rename + * Change references from master to main + * Add `--env` and `--workingdir` flags to run command + * build(deps): bump github.com/opencontainers/runc + * [CI:DOCS] buildah bud: spelling --ignore-file requires parameter + * [CI:DOCS] push/pull: clarify supported transports + * Remove unused function arguments + * Create mountOptions for mount command flags + * Extract version command implementation to function + * Add --json flags to `mount` and `version` commands + * build(deps): bump github.com/containers/storage from 1.32.2 to 1.32.3 + * build(deps): bump github.com/containers/common from 0.40.0 to 0.40.1 + * copier.Put(): set xattrs after ownership + * buildah add/copy: spelling + * build(deps): bump github.com/containers/common from 0.39.0 to 0.40.0 + * buildah copy and buildah add should support .containerignore + * Remove unused util.StartsWithValidTransport + * Fix documentation of the --format option of buildah push + * Don't use alltransports.ParseImageName with known transports + * build(deps): bump github.com/containers/image/v5 from 5.13.0 to 5.13.1 + * man pages: clarify `rmi` removes dangling parents + * tests: make it easer to override the location of the copy helper + * build(deps): bump github.com/containers/image/v5 from 5.12.0 to 5.13.0 + * [CI:DOCS] Fix links to c/image master branch + * imagebuildah: use the specified logger for logging preprocessing warnings + * Fix copy into workdir for a single file + * Fix docs links due to branch rename + * Update nix pin with `make nixpkgs` + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.2 to 1.7.3 + * build(deps): bump github.com/opencontainers/selinux from 1.8.1 to 1.8.2 + * build(deps): bump go.etcd.io/bbolt from 1.3.5 to 1.3.6 + * build(deps): bump github.com/containers/storage from 1.32.1 to 1.32.2 + * build(deps): bump github.com/mattn/go-shellwords from 1.0.11 to 1.0.12 + * build(deps): bump github.com/onsi/ginkgo from 1.16.3 to 1.16.4 + * fix(docs): typo + * Move to v1.22.0-dev + * Fix handling of auth.json file while in a user namespace + * Add rusage-logfile flag to optionally send rusage to a file + * imagebuildah: redo step logging + * build(deps): bump github.com/onsi/ginkgo from 1.16.2 to 1.16.3 + * build(deps): bump github.com/containers/storage from 1.32.0 to 1.32.1 + * Add volumes to make running buildah within a container easier + * build(deps): bump github.com/onsi/gomega from 1.12.0 to 1.13.0 + * Add and use a "copy" helper instead of podman load/save + * Bump github.com/containers/common from 0.38.4 to 0.39.0 + * containerImageRef/containerImageSource: don't buffer uncompressed layers + * containerImageRef(): squashed images have no parent images + * Sync. workflow across skopeo, buildah, and podman + * Bump github.com/containers/storage from 1.31.1 to 1.31.2 + * Bump github.com/opencontainers/runc from 1.0.0-rc94 to 1.0.0-rc95 + * Bump to v1.21.1-dev [NO TESTS NEEDED] + +- Changelog for v1.21.0 (2021-05-19) + * Don't blow up if cpp detects errors + * Vendor in containers/common v0.38.4 + * Remove 'buildah run --security-opt' from completion + * update c/common + * Fix handling of --default-mounts-file + * update vendor of containers/storage v1.31.1 + * Bump github.com/containers/storage from 1.30.3 to 1.31.0 + * Send logrus messages back to caller when building + * github: Fix bad repo. ref in workflow config + * Check earlier for bad image tags name + * buildah bud: fix containers/podman/issues/10307 + * Bump github.com/containers/storage from 1.30.1 to 1.30.3 + * Cirrus: Support [CI:DOCS] test skipping + * Notification email for cirrus-cron build failures + * Bump github.com/opencontainers/runc from 1.0.0-rc93 to 1.0.0-rc94 + * Fix race condition + * Fix copy race while walking paths + * Preserve ownership of lower directory when doing an overlay mount + * Bump github.com/onsi/gomega from 1.11.0 to 1.12.0 + * Update nix pin with `make nixpkgs` + * codespell cleanup + * Multi-arch github-action workflow unification + * Bump github.com/containers/image/v5 from 5.11.1 to 5.12.0 + * Bump github.com/onsi/ginkgo from 1.16.1 to 1.16.2 + * imagebuildah: ignore signatures when tagging images + * update to latest libimage + * Bump github.com/containers/common from 0.37.0 to 0.37.1 + * Bump github.com/containers/storage from 1.30.0 to 1.30.1 + * Upgrade to GitHub-native Dependabot + * Document location of auth.json file if XDG_RUNTIME_DIR is not set + * run.bats: fix flake in run-user test + * Cirrus: Update F34beta -> F34 + * pr-should-include-tests: try to make work in buildah + * runUsingRuntime: when relaying error from the runtime, mention that + * Run(): avoid Mkdir() into the rootfs + * imagebuildah: replace archive with chrootarchive + * imagebuildah.StageExecutor.volumeCacheSaveVFS(): set up bind mounts + * conformance: use :Z with transient mounts when SELinux is enabled + * bud.bats: fix a bats warning + * imagebuildah: create volume directories when using overlays + * imagebuildah: drop resolveSymlink() + * namespaces test - refactoring and cleanup + * Refactor 'idmapping' system test + * Cirrus: Update Ubuntu images to 21.04 + * Tiny fixes in bud system tests + * Add compabitility wrappers for removed packages + * Fix expected message at pulling image + * Fix system tests of 'bud' subcommand + * [CI:DOCS] Update steps for CentOS runc users + * Add support for secret mounts + * Add buildah manifest rm command + * restore push/pull and util API + * [CI:DOCS] Remove older distro docs + * Rename rhel secrets to subscriptions + * vendor in openshift/imagebuilder + * Remove buildah bud --loglevel ... + * use new containers/common/libimage package + * Fix copier when using globs + * Test namespace flags of 'bud' subcommand + * Add system test of 'bud' subcommand + * Output names of multiple tags in buildah bud + * push to docker test: don't get fooled by podman + * copier: add Remove() + * build(deps): bump github.com/containers/image/v5 from 5.10.5 to 5.11.1 + * Restore log timestamps + * Add system test of 'buildah help' with a tiny fix + * tests: copy.bats: fix infinite hang + * Do not force hard code to crun in rootless mode + * build(deps): bump github.com/openshift/imagebuilder from 1.2.0 to 1.2.1 + * build(deps): bump github.com/containers/ocicrypt from 1.1.0 to 1.1.1 + * build(deps): bump github.com/containers/common from 0.35.4 to 0.36.0 + * Fix arg missing warning in bud + * Check without flag in 'from --cgroup-parent' test + * Minor fixes to Buildah as a library tutorial documentation + * Add system test of 'buildah version' for packaged buildah + * Add a few system tests of 'buildah from' + * Log the final error with %+v at logging level "trace" + * copier: add GetOptions.NoCrossDevice + * Update nix pin with `make nixpkgs` + * Bump to v1.20.2-dev + +- Changelog for v1.20.1 (2021-04-13) + * Run container with isolation type set at 'from' + * bats helpers.bash - minor refactoring + * Bump containers/storage vendor to v1.29.0 + * build(deps): bump github.com/onsi/ginkgo from 1.16.0 to 1.16.1 + * Cirrus: Update VMs w/ F34beta + * CLI add/copy: add a --from option + * build(deps): bump github.com/onsi/ginkgo from 1.15.2 to 1.16.0 + * Add authentication system tests for 'commit' and 'bud' + * fix local image lookup for custom platform + * Double-check existence of OCI runtimes + * Cirrus: Make use of shared get_ci_vm container + * Add system tests of "buildah run" + * Update nix pin with `make nixpkgs` + * Remove some stuttering on returns errors + * Setup alias for --tty to --terminal + * Add conformance tests for COPY /... + * Put a few more minutes on the clock for the CI conformance test + * Add a conformance test for COPY --from $symlink + * Add conformance tests for COPY "" + * Check for symlink in builtin volume + * Sort all mounts by destination directory + * System-test cleanup + * Export parse.Platform string to be used by podman-remote + * blobcache: fix sequencing error + * build(deps): bump github.com/containers/common from 0.35.3 to 0.35.4 + * Fix URL in demos/buildah_multi_stage.sh + * Add a few system tests + * [NO TESTS NEEDED] Use --recurse-modules when building git context + * Bump to v1.20.1-dev + +- Changelog for v1.20.0 (2021-03-25) + * vendor in containers/storage v1.28.1 + * build(deps): bump github.com/containers/common from 0.35.2 to 0.35.3 + * tests: prefetch: use buildah, not podman, for pulls + * Use faster way to check image tag existence during multi-arch build + * Add information about multi-arch images to the Readme + * COPY --chown: expand the conformance test + * pkg/chrootuser: use a bufio.Scanner + * [CI:DOCS] Fix rootful typo in docs + * build(deps): bump github.com/onsi/ginkgo from 1.15.1 to 1.15.2 + * Add documentation and testing for .containerignore + * build(deps): bump github.com/sirupsen/logrus from 1.8.0 to 1.8.1 + * build(deps): bump github.com/hashicorp/go-multierror from 1.1.0 to 1.1.1 + * Lookup Containerfile if user specifies a directory + * Add Tag format placeholder to docs + * copier: ignore sockets + * image: propagate errors from extractRootfs + * Remove system test of 'buildah containers -a' + * Clarify userns options are usable only as root in man pages + * Fix system test of 'containers -a' + * Remove duplicated code in addcopy + * build(deps): bump github.com/onsi/ginkgo from 1.15.0 to 1.15.1 + * build(deps): bump github.com/onsi/gomega from 1.10.5 to 1.11.0 + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.1 to 1.7.2 + * Update multi-arch buildah build setup with new logic + * Update nix pin with `make nixpkgs` + * overlay.bats: fix the "overlay source permissions" test + * imagebuildah: use overlay for volumes when using overlay + * Make PolicyMap and PullPolicy names align + * copier: add GetOptions.IgnoreUnreadable + * Check local image to match system context + * fix: Containerfiles - smaller set of userns u/gids + * Set upperdir permissions based on source + * Shrink the vendoring size of pkc/cli + * Clarify image name match failure message + * ADD/COPY: create the destination directory first, chroot to it + * copier.GetOptions: add NoDerefSymLinks + * copier: add an Eval function + * Update system test for 'from --cap-add/drop' + * copier: fix a renaming bug + * copier: return child process stderr if we can't JSON decode the response + * Add some system tests + * build(deps): bump github.com/containers/storage from 1.26.0 to 1.27.0 + * complement add/copy --chmod documentation + * buildah login and logout, do not need to enter user namespace + * Add multi-arch image build + * chmod/chown added/fixed in bash completions + * OWNERS: add @lsm5 + * buildah add/copy --chmod dockerfile implementation + * bump github.com/openshift/imagebuilder from 1.1.8 to 1.2.0 + * buildah add/copy --chmod cli implementation for files and urls + * Make sure we set the buildah version label + * Isolation strings, should match user input + * [CI:DOCS] buildah-from.md: remove dup arch,os + * build(deps): bump github.com/containers/image/v5 from 5.10.2 to 5.10.3 + * Cirrus: Temp. disable prior-fedora (F32) testing + * pr-should-include-tests: recognized "renamed" tests + * build(deps): bump github.com/sirupsen/logrus from 1.7.0 to 1.8.0 + * build(deps): bump github.com/fsouza/go-dockerclient from 1.7.0 to 1.7.1 + * build(deps): bump github.com/containers/common from 0.34.2 to 0.35.0 + * Fix reaping of stages with no instructions + * add stale bot + * Add base image name to comment + * build(deps): bump github.com/spf13/cobra from 1.1.1 to 1.1.3 + * Don't fail copy to emptydir + * buildah: use volatile containers + * vendor: update containers/storage + * Eliminate the use of containers/building import in pkg subdirs + * Add more support for removing config + * Improve messages about --cache-from not being supported + * Revert patch to allow COPY/ADD of empty dirs. + * Don't fail copy to emptydir + * Fix tutorial for rootless mode + * Fix caching layers with build args + * Vendor in containers/image v5.10.2 + * build(deps): bump github.com/containers/common from 0.34.0 to 0.34.2 + * build(deps): bump github.com/onsi/ginkgo from 1.14.2 to 1.15.0 + * 'make validate': require PRs to include tests + * build(deps): bump github.com/onsi/gomega from 1.10.4 to 1.10.5 + * build(deps): bump github.com/containers/storage from 1.24.5 to 1.25.0 + * Use chown function for U volume flag from containers/common repository + * --iidfile: print hash prefix + * bump containernetworking/cni to v0.8.1 - fix for CVE-2021-20206 + * run: fix check for host pid namespace + * Finish plumbing for buildah bud --manifest + * buildah manifest add localimage should work + * Stop testing directory permissions with latest docker + * Fix build arg check + * build(deps): bump github.com/containers/ocicrypt from 1.0.3 to 1.1.0 + * [ci:docs] Fix man page for buildah push + * Update nix pin with `make nixpkgs` + * Bump to containers/image v5.10.1 + * Rebuild layer if a change in ARG is detected + * Bump golang.org/x/crypto to the latest + * Add Ashley and Urvashi to Approvers + * local image lookup by digest + * Use build-arg ENV val from local environment if set + * Pick default OCI Runtime from containers.conf + * Added required devel packages + * Cirrus: Native OSX Build + * Cirrus: Two minor cleanup items + * Workaround for RHEL gating test failure + * build(deps): bump github.com/stretchr/testify from 1.6.1 to 1.7.0 + * build(deps): bump github.com/mattn/go-shellwords from 1.0.10 to 1.0.11 + * Reset upstream branch to dev version + * If destination does not exists, do not throw error + +- Changelog for v1.19.0 (2021-01-08) + * Update vendor of containers/storage and containers/common + * Buildah inspect should be able to inspect manifests + * Make buildah push support pushing manifests lists and digests + * Fix handling of TMPDIR environment variable + * Add support for --manifest flags + * Upper directory should match mode of destination directory + * Only grab the OS, Arch if the user actually specified them + * Use --arch and --os and --variant options to select architecture and os + * Cirrus: Track libseccomp and golang version + * copier.PutOptions: add an "IgnoreDevices" flag + * fix: `rmi --prune` when parent image is in store. + * build(deps): bump github.com/containers/storage from 1.24.3 to 1.24.4 + * build(deps): bump github.com/containers/common from 0.31.1 to 0.31.2 + * Allow users to specify stdin into containers + * Drop log message on failure to mount on /sys file systems to info + * Spelling + * SELinux no longer requires a tag. + * build(deps): bump github.com/opencontainers/selinux from 1.6.0 to 1.8.0 + * build(deps): bump github.com/containers/common from 0.31.0 to 0.31.1 + * Update nix pin with `make nixpkgs` + * Switch references of /var/run -> /run + * Allow FROM to be overriden with from option + * copier: don't assume we can chroot() on Unixy systems + * copier: add PutOptions.NoOverwriteDirNonDir, Get/PutOptions.Rename + * copier: handle replacing directories with not-directories + * copier: Put: skip entries with zero-length names + * build(deps): bump github.com/containers/storage from 1.24.2 to 1.24.3 + * Add U volume flag to chown source volumes + * Turn off PRIOR_UBUNTU Test until vm is updated + * pkg, cli: rootless uses correct isolation + * build(deps): bump github.com/onsi/gomega from 1.10.3 to 1.10.4 + * update installation doc to reflect current status + * Move away from using docker.io + * enable short-name aliasing + * build(deps): bump github.com/containers/storage from 1.24.1 to 1.24.2 + * build(deps): bump github.com/containers/common from 0.30.0 to 0.31.0 + * Throw errors when using bogus --network flags + * pkg/supplemented test: replace our null blobinfocache + * build(deps): bump github.com/containers/common from 0.29.0 to 0.30.0 + * inserts forgotten quotation mark + * Not prefer use local image create/add manifest + * Add container information to .containerenv + * Add --ignorefile flag to use alternate .dockerignore flags + * Add a source debug build + * Fix crash on invalid filter commands + * build(deps): bump github.com/containers/common from 0.27.0 to 0.29.0 + * Switch to using containers/common pkg's + * fix: non-portable shebang #2812 + * Remove copy/paste errors that leaked `Podman` into man pages. + * Add suggests cpp to spec file + * Apply suggestions from code review + * update docs for debian testing and unstable + * imagebuildah: disable pseudo-terminals for RUN + * Compute diffID for mapped-layer at creating image source + * intermediateImageExists: ignore images whose history we can't read + * Bump to v1.19.0-dev + * build(deps): bump github.com/containers/common from 0.26.3 to 0.27.0 + +- Changelog for v1.18.0 (2020-11-16) + * Fix testing error caused by simultanious merge + * Vendor in containers/storage v1.24.0 + * short-names aliasing + * Add --policy flag to buildah pull + * Stop overwrapping and stuttering + * copier.Get(): ignore ENOTSUP/ENOSYS when listing xattrs + * Run: don't forcibly disable UTS namespaces in rootless mode + * test: ensure non-directory in a Dockerfile path is handled correctly + * Add a few tests for `pull` command + * Fix buildah config --cmd to handle array + * build(deps): bump github.com/containers/storage from 1.23.8 to 1.23.9 + * Fix NPE when Dockerfile path contains non-directory entries + * Update buildah bud man page from podman build man page + * Move declaration of decryption-keys to common cli + * Run: correctly call copier.Mkdir + * util: digging UID/GID out of os.FileInfo should work on Unix + * imagebuildah.getImageTypeAndHistoryAndDiffIDs: cache results + * Verify userns-uid-map and userns-gid-map input + * Use CPP, CC and flags in dep check scripts + * Avoid overriding LDFLAGS in Makefile + * ADD: handle --chown on URLs + * Update nix pin with `make nixpkgs` + * (*Builder).Run: MkdirAll: handle EEXIST error + * copier: try to force loading of nsswitch modules before chroot() + * fix MkdirAll usage + * build(deps): bump github.com/containers/common from 0.26.2 to 0.26.3 + * build(deps): bump github.com/containers/storage from 1.23.7 to 1.23.8 + * Use osusergo build tag for static build + * imagebuildah: cache should take image format into account + * Bump to v1.18.0-dev + +- Changelog for v1.17.0 (2020-10-29) + * Handle cases where other tools mount/unmount containers + * overlay.MountReadOnly: support RO overlay mounts + * overlay: use fusermount for rootless umounts + * overlay: fix umount + * Switch default log level of Buildah to Warn. Users need to see these messages + * Drop error messages about OCI/Docker format to Warning level + * build(deps): bump github.com/containers/common from 0.26.0 to 0.26.2 + * tests/testreport: adjust for API break in storage v1.23.6 + * build(deps): bump github.com/containers/storage from 1.23.5 to 1.23.7 + * build(deps): bump github.com/fsouza/go-dockerclient from 1.6.5 to 1.6.6 + * copier: put: ignore Typeflag="g" + * Use curl to get repo file (fix #2714) + * build(deps): bump github.com/containers/common from 0.25.0 to 0.26.0 + * build(deps): bump github.com/spf13/cobra from 1.0.0 to 1.1.1 + * Remove docs that refer to bors, since we're not using it + * Buildah bud should not use stdin by default + * bump containerd, docker, and golang.org/x/sys + * Makefile: cross: remove windows.386 target + * copier.copierHandlerPut: don't check length when there are errors + * Stop excessive wrapping + * CI: require that conformance tests pass + * bump(github.com/openshift/imagebuilder) to v1.1.8 + * Skip tlsVerify insecure BUILD_REGISTRY_SOURCES + * Fix build path wrong https://github.com/containers/podman/issues/7993 + * refactor pullpolicy to avoid deps + * build(deps): bump github.com/containers/common from 0.24.0 to 0.25.0 + * CI: run gating tasks with a lot more memory + * ADD and COPY: descend into excluded directories, sometimes + * copier: add more context to a couple of error messages + * copier: check an error earlier + * copier: log stderr output as debug on success + * Update nix pin with `make nixpkgs` + * Set directory ownership when copied with ID mapping + * build(deps): bump github.com/sirupsen/logrus from 1.6.0 to 1.7.0 + * build(deps): bump github.com/containers/common from 0.23.0 to 0.24.0 + * Cirrus: Remove bors artifacts + * Sort build flag definitions alphabetically + * ADD: only expand archives at the right time + * Remove configuration for bors + * Shell Completion for podman build flags + * Bump c/common to v0.24.0 + * New CI check: xref --help vs man pages + * CI: re-enable several linters + * Move --userns-uid-map/--userns-gid-map description into buildah man page + * add: preserve ownerships and permissions on ADDed archives + * Makefile: tweak the cross-compile target + * Bump containers/common to v0.23.0 + * chroot: create bind mount targets 0755 instead of 0700 + * Change call to Split() to safer SplitN() + * chroot: fix handling of errno seccomp rules + * build(deps): bump github.com/containers/image/v5 from 5.5.2 to 5.6.0 + * Add In Progress section to contributing + * integration tests: make sure tests run in ${topdir}/tests + * Run(): ignore containers.conf's environment configuration + * Warn when setting healthcheck in OCI format + * Cirrus: Skip git-validate on branches + * tools: update git-validation to the latest commit + * tools: update golangci-lint to v1.18.0 + * Add a few tests of push command + * Add(): fix handling of relative paths with no ContextDir + * build(deps): bump github.com/containers/common from 0.21.0 to 0.22.0 + * Lint: Use same linters as podman + * Validate: reference HEAD + * Fix buildah mount to display container names not ids + * Update nix pin with `make nixpkgs` + * Add missing --format option in buildah from man page + * Fix up code based on codespell + * build(deps): bump github.com/openshift/imagebuilder from 1.1.6 to 1.1.7 + * build(deps): bump github.com/containers/storage from 1.23.4 to 1.23.5 + * Improve buildah completions + * Cirrus: Fix validate commit epoch + * Fix bash completion of manifest flags + * Uniform some man pages + * Update Buildah Tutorial to address BZ1867426 + * Update bash completion of `manifest add` sub command + * copier.Get(): hard link targets shouldn't be relative paths + * build(deps): bump github.com/onsi/gomega from 1.10.1 to 1.10.2 + * Pass timestamp down to history lines + * Timestamp gets updated everytime you inspect an image + * bud.bats: use absolute paths in newly-added tests + * contrib/cirrus/lib.sh: don't use CN for the hostname + * tests: Add some tests + * Update `manifest add` man page + * Extend flags of `manifest add` + * build(deps): bump github.com/containers/storage from 1.23.3 to 1.23.4 + * build(deps): bump github.com/onsi/ginkgo from 1.14.0 to 1.14.1 + * Bump to v1.17.0-dev + * CI: expand cross-compile checks + +- Changelog for v1.16.0 (2020-09-03) + * fix build on 32bit arches + * containerImageRef.NewImageSource(): don't always force timestamps + * Add fuse module warning to image readme + * Heed our retry delay option values when retrying commit/pull/push + * Switch to containers/common for seccomp + * Use --timestamp rather then --omit-timestamp + * docs: remove outdated notice + * docs: remove outdated notice + * build-using-dockerfile: add a hidden --log-rusage flag + * build(deps): bump github.com/containers/image/v5 from 5.5.1 to 5.5.2 + * Discard ReportWriter if user sets options.Quiet + * build(deps): bump github.com/containers/common from 0.19.0 to 0.20.3 + * Fix ownership of content copied using COPY --from + * newTarDigester: zero out timestamps in tar headers + * Update nix pin with `make nixpkgs` + * bud.bats: correct .dockerignore integration tests + * Use pipes for copying + * run: include stdout in error message + * run: use the correct error for errors.Wrapf + * copier: un-export internal types + * copier: add Mkdir() + * in_podman: don't get tripped up by $CIRRUS_CHANGE_TITLE + * docs/buildah-commit.md: tweak some wording, add a --rm example + * imagebuildah: don’t blank out destination names when COPYing + * Replace retry functions with common/pkg/retry + * StageExecutor.historyMatches: compare timestamps using .Equal + * Update vendor of containers/common + * Fix errors found in coverity scan + * Change namespace handling flags to better match podman commands + * conformance testing: ignore buildah.BuilderIdentityAnnotation labels + * Vendor in containers/storage v1.23.0 + * Add buildah.IsContainer interface + * Avoid feeding run_buildah to pipe + * fix(buildahimage): add xz dependency in buildah image + * Bump github.com/containers/common from 0.15.2 to 0.18.0 + * Howto for rootless image building from OpenShift + * Add --omit-timestamp flag to buildah bud + * Update nix pin with `make nixpkgs` + * Shutdown storage on failures + * Handle COPY --from when an argument is used + * Bump github.com/seccomp/containers-golang from 0.5.0 to 0.6.0 + * Cirrus: Use newly built VM images + * Bump github.com/opencontainers/runc from 1.0.0-rc91 to 1.0.0-rc92 + * Enhance the .dockerignore man pages + * conformance: add a test for COPY from subdirectory + * fix bug manifest inspct + * Add documentation for .dockerignore + * Add BuilderIdentityAnnotation to identify buildah version + * DOC: Add quay.io/containers/buildah image to README.md + * Update buildahimages readme + * fix spelling mistake in "info" command result display + * Don't bind /etc/host and /etc/resolv.conf if network is not present + * blobcache: avoid an unnecessary NewImage() + * Build static binary with `buildGoModule` + * copier: split StripSetidBits into StripSetuidBit/StripSetgidBit/StripStickyBit + * tarFilterer: handle multiple archives + * Fix a race we hit during conformance tests + * Rework conformance testing + * Update 02-registries-repositories.md + * test-unit: invoke cmd/buildah tests with --flags + * parse: fix a type mismatch in a test + * Fix compilation of tests/testreport/testreport + * build.sh: log the version of Go that we're using + * test-unit: increase the test timeout to 40/45 minutes + * Add the "copier" package + * Fix & add notes regarding problematic language in codebase + * Add dependency on github.com/stretchr/testify/require + * CompositeDigester: add the ability to filter tar streams + * BATS tests: make more robust + * vendor golang.org/x/text@v0.3.3 + * Switch golang 1.12 to golang 1.13 + * imagebuildah: wait for stages that might not have even started yet + * chroot, run: not fail on bind mounts from /sys + * chroot: do not use setgroups if it is blocked + * Set engine env from containers.conf + * imagebuildah: return the right stage's image as the "final" image + * Fix a help string + * Deduplicate environment variables + * switch containers/libpod to containers/podman + * Bump github.com/containers/ocicrypt from 1.0.2 to 1.0.3 + * Bump github.com/opencontainers/selinux from 1.5.2 to 1.6.0 + * Mask out /sys/dev to prevent information leak + * linux: skip errors from the runtime kill + * Mask over the /sys/fs/selinux in mask branch + * Add VFS additional image store to container + * tests: add auth tests + * Allow "readonly" as alias to "ro" in mount options + * Ignore OS X specific consistency mount option + * Bump github.com/onsi/ginkgo from 1.13.0 to 1.14.0 + * Bump github.com/containers/common from 0.14.0 to 0.15.2 + * Rootless Buildah should default to IsolationOCIRootless + * imagebuildah: fix inheriting multi-stage builds + * Make imagebuildah.BuildOptions.Architecture/OS optional + * Make imagebuildah.BuildOptions.Jobs optional + * Resolve a possible race in imagebuildah.Executor.startStage() + * Switch scripts to use containers.conf + * Bump openshift/imagebuilder to v1.1.6 + * Bump go.etcd.io/bbolt from 1.3.4 to 1.3.5 + * buildah, bud: support --jobs=N for parallel execution + * executor: refactor build code inside new function + * Add bud regression tests + * Cirrus: Fix missing htpasswd in registry img + * docs: clarify the 'triples' format + * CHANGELOG.md: Fix markdown formatting + * Add nix derivation for static builds + * Bump to v1.16.0-dev + * add version centos7 for compatible + +- Changelog for v1.15.0 (2020-06-17) + * Bump github.com/containers/common from 0.12.0 to 0.13.1 + * Bump github.com/containers/storage from 1.20.1 to 1.20.2 + * Bump github.com/seccomp/containers-golang from 0.4.1 to 0.5.0 + * Bump github.com/stretchr/testify from 1.6.0 to 1.6.1 + * Bump github.com/opencontainers/runc from 1.0.0-rc9 to 1.0.0-rc90 + * Add CVE-2020-10696 to CHANGELOG.md and changelog.txt + * Bump github.com/stretchr/testify from 1.5.1 to 1.6.0 + * Bump github.com/onsi/ginkgo from 1.12.2 to 1.12.3 + * Vendor in containers/common v0.12.0 + * fix lighttpd example + * Vendor in new go.etcd.io/bbolt + * Bump github.com/onsi/ginkgo from 1.12.1 to 1.12.2 + * Bump imagebuilder for ARG fix + * Bump github.com/containers/common from 0.11.2 to 0.11.4 + * remove dependency on openshift struct + * Warn on unset build arguments + * vendor: update seccomp/containers-golang to v0.4.1 + * Ammended docs + * Updated docs + * clean up comments + * update exit code for tests + * Implement commit for encryption + * implementation of encrypt/decrypt push/pull/bud/from + * fix resolve docker image name as transport + * Bump github.com/opencontainers/go-digest from 1.0.0-rc1 to 1.0.0 + * Bump github.com/onsi/ginkgo from 1.12.0 to 1.12.1 + * Bump github.com/containers/storage from 1.19.1 to 1.19.2 + * Bump github.com/containers/image/v5 from 5.4.3 to 5.4.4 + * Add preliminary profiling support to the CLI + * Bump github.com/containers/common from 0.10.0 to 0.11.2 + * Evaluate symlinks in build context directory + * fix error info about get signatures for containerImageSource + * Add Security Policy + * Cirrus: Fixes from review feedback + * Bump github.com/containers/storage from 1.19.0 to 1.19.1 + * Bump github.com/sirupsen/logrus from 1.5.0 to 1.6.0 + * imagebuildah: stages shouldn't count as their base images + * Update containers/common v0.10.0 + * Bump github.com/fsouza/go-dockerclient from 1.6.4 to 1.6.5 + * Add registry to buildahimage Dockerfiles + * Cirrus: Use pre-installed VM packages + F32 + * Cirrus: Re-enable all distro versions + * Cirrus: Update to F31 + Use cache images + * golangci-lint: Disable gosimple + * Lower number of golangci-lint threads + * Fix permissions on containers.conf + * Don't force tests to use runc + * Bump github.com/containers/common from 0.9.1 to 0.9.5 + * Return exit code from failed containers + * Bump github.com/containers/storage from 1.18.2 to 1.19.0 + * Bump github.com/containers/common from 0.9.0 to 0.9.1 + * cgroup_manager should be under [engine] + * Use c/common/pkg/auth in login/logout + * Cirrus: Temporarily disable Ubuntu 19 testing + * Add containers.conf to stablebyhand build + * Update gitignore to exclude test Dockerfiles + * Bump github.com/fsouza/go-dockerclient from 1.6.3 to 1.6.4 + * Bump github.com/containers/common from 0.8.1 to 0.9.0 + * Bump back to v1.15.0-dev + * Remove warning for systemd inside of container + +- Changelog for v1.14.8 (2020-04-09) + * Run (make vendor) + * Run (make -C tests/tools vendor) + * Run (go mod tidy) before (go mod vendor) again + * Fix (make vendor) + * Bump validation + * Bump back to v1.15.0-dev + +- Changelog for v1.14.7 (2020-04-07) + * Bump github.com/containers/image/v5 from 5.3.1 to 5.4.3 + * make vendor: run `tidy` after `vendor` + * Do not skip the directory when the ignore pattern matches + * Bump github.com/containers/common from 0.7.0 to 0.8.1 + * Downgrade siruspen/logrus from 1.4.2 + * Fix errorf conventions + * dockerignore tests : remove symlinks, rework + * Bump back to v1.15.0-dev + +- Changelog for v1.14.6 (2020-04-02) + * bud.bats - cleanup, refactoring + * vendor in latest containers/storage 1.18.0 and containers/common v0.7.0 + * Bump github.com/spf13/cobra from 0.0.6 to 0.0.7 + * Bump github.com/containers/storage from 1.16.5 to 1.17.0 + * Bump github.com/containers/image/v5 from 5.2.1 to 5.3.1 + * Fix Amazon install step + * Bump back to v1.15.0-dev + * Fix bud-build-arg-cache test + * Make image history work correctly with new args handling + * Don't add args to the RUN environment from the Builder + * Update github.com/openshift/imagebuilder to v1.1.4 + * Add .swp files to .gitignore + +- Changelog for v1.14.5 (2020-03-26) + * revert #2246 FIPS mode change + * Bump back to v1.15.0-dev + * image with dup layers: we now have one on quay + * digest test : make more robust + +- Changelog for v1.14.4 (2020-03-25) + * Fix fips-mode check for RHEL8 boxes + * Fix potential CVE in tarfile w/ symlink (Edit 02-Jun-2020: Addresses CVE-2020-10696) + * Fix .dockerignore with globs and ! commands + * update install steps for Amazon Linux 2 + * Bump github.com/openshift/imagebuilder from 1.1.2 to 1.1.3 + * Add comment for RUN command in volume ownership test + * Run stat command directly for volume ownership test + * vendor in containers/common v0.6.1 + * Cleanup go.sum + * Bump back to v1.15.0-dev + +- Changelog for v1.14.3 (2020-03-17) + * Update containers/storage to v1.16.5 + * Bump github.com/containers/storage from 1.16.2 to 1.16.4 + * Bump github.com/openshift/imagebuilder from 1.1.1 to 1.1.2 + * Update github.com/openshift/imagebuilder vendoring + * Update unshare man page to fix script example + * Fix compilation errors on non linux platforms + * Bump containers/common and opencontainers/selinux versions + * Add tests for volume ownership + * Preserve volume uid and gid through subsequent commands + * Fix FORWARD_NULL errors found by Coverity + * Bump github.com/containers/storage from 1.16.1 to 1.16.2 + * Fix errors found by codespell + * Bump back to v1.15.0-dev + * Add Pull Request Template + +- Changelog for v1.14.2 (2020-03-03) + * Add Buildah pull request template + * Bump to containers/storage v1.16.1 + * run_linux: fix tight loop if file is not pollable + * Bump github.com/opencontainers/selinux from 1.3.2 to 1.3.3 + * Bump github.com/containers/common from 0.4.1 to 0.4.2 + * Bump back to v1.15.0-dev + * Add Containerfile to build a versioned stable image on quay.io + +- Changelog for v1.14.1 (2020-02-27) + * Search for local runtime per values in containers.conf + * Set correct ownership on working directory + * BATS : in teardown, umount stale mounts + * Bump github.com/spf13/cobra from 0.0.5 to 0.0.6 + * Bump github.com/fsouza/go-dockerclient from 1.6.1 to 1.6.3 + * Bump github.com/stretchr/testify from 1.4.0 to 1.5.1 + * Replace unix with syscall to allow vendoring into libpod + * Update to containers/common v0.4.1 + * Improve remote manifest retrieval + * Fix minor spelling errors in containertools README + * Clear the right variable in buildahimage + * Correct a couple of incorrect format specifiers + * Update to containers/common v0.3.0 + * manifest push --format: force an image type, not a list type + * run: adjust the order in which elements are added to $PATH + * getDateAndDigestAndSize(): handle creation time not being set + * Bump github.com/containers/common from 0.2.0 to 0.2.1 + * include installation steps for CentOS 8 and Stream + * include installation steps for CentOS7 and forks + * Adjust Ubuntu install info to also work on Pop!_OS + * Make the commit id clear like Docker + * Show error on copied file above context directory in build + * Bump github.com/containers/image/v5 from 5.2.0 to 5.2.1 + * pull/from/commit/push: retry on most failures + * Makefile: fix install.cni.sudo + * Repair buildah so it can use containers.conf on the server side + * Bump github.com/mattn/go-shellwords from 1.0.9 to 1.0.10 + * Bump github.com/fsouza/go-dockerclient from 1.6.0 to 1.6.1 + * Fixing formatting & build instructions + * Add Code of Conduct + * Bors: Fix no. req. github reviews + * Cirrus+Bors: Simplify temp branch skipping + * Bors-ng: Add documentation and status-icon + * Bump github.com/onsi/ginkgo from 1.11.0 to 1.12.0 + * fix XDG_RUNTIME_DIR for authfile + * Cirrus: Disable F29 testing + * Cirrus: Add jq package + * Cirrus: Fix lint + validation using wrong epoch + * Stop using fedorproject registry + * Bors: Workaround ineffective required statuses + * Bors: Enable app + Disable Travis + * Cirrus: Add standardized log-collection + * Cirrus: Improve automated lint + validation + * Allow passing options to golangci-lint + * Cirrus: Fixes from review feedback + * Cirrus: Temporarily ignore VM testing failures + * Cirrus: Migrate off papr + implement VM testing + * Cirrus: Update packages + fixes for get_ci_vm.sh + * Show validation command-line + * Skip overlay test w/ vfs driver + * use alpine, not centos, for various tests + * Flake handling: cache and prefetch images + * Bump to v1.15.0-dev + +- Changelog for v1.14.0 (2020-02-05) + * bump github.com/mtrmac/gpgme + * Update containers/common to v0.1.4 + * manifest push: add --format option + * Bump github.com/onsi/gomega from 1.8.1 to 1.9.0 + * vendor github.com/containers/image/v5@v5.2.0 + * info test: deal with random key order + * Bump back to v1.14.0-dev + +- Changelog for v1.13.2 (2020-01-29) + * sign.bats: set GPG_TTY=/dev/null + * Fix parse_unsupported.go + * getDateAndDigestAndSize(): use manifest.Digest + * Bump github.com/opencontainers/selinux from 1.3.0 to 1.3.1 + * Bump github.com/containers/common from 0.1.0 to 0.1.2 + * Touch up os/arch doc + * chroot: handle slightly broken seccomp defaults + * buildahimage: specify fuse-overlayfs mount options + * Bump github.com/mattn/go-shellwords from 1.0.7 to 1.0.9 + * copy.bats: make sure we detect failures due to missing source + * parse: don't complain about not being able to rename something to itself + * Makefile: use a $(GO_TEST) macro, fix a typo + * manifests: unit test fix + * Fix build for 32bit platforms + * Allow users to set OS and architecture on bud + * Fix COPY in containerfile with envvar + * Bump c/storage to v1.15.7 + * add --sign-by to bud/commit/push, --remove-signatures for pull/push + * Remove cut/paste error in CHANGELOG.md + * Update vendor of containers/common to v0.1.0 + * update install instructions for Debian, Raspbian and Ubuntu + * Add support for containers.conf + * Bump back to v1.14.0-dev + +- Changelog for v1.13.1 (2020-01-14) + * Bump github.com/containers/common from 0.0.5 to 0.0.7 + * Bump github.com/onsi/ginkgo from 1.10.3 to 1.11.0 + * Bump github.com/pkg/errors from 0.8.1 to 0.9.0 + * Bump github.com/onsi/gomega from 1.7.1 to 1.8.1 + * Add codespell support + * copyFileWithTar: close source files at the right time + * copy: don't digest files that we ignore + * Check for .dockerignore specifically + * Travis: rm go 1.12.x + * Don't setup excludes, if their is only one pattern to match + * set HOME env to /root on chroot-isolation by default + * docs: fix references to containers-*.5 + * update openshift/api + * fix bug Add check .dockerignore COPY file + * buildah bud --volume: run from tmpdir, not source dir + * Fix imageNamePrefix to give consistent names in buildah-from + * cpp: use -traditional and -undef flags + * Fix image reference in tutorial 4 + * discard outputs coming from onbuild command on buildah-from --quiet + * make --format columnizing consistent with buildah images + * Bump to v1.14.0-dev + +- Changelog for v1.13.0 (2019-12-27) + * Bump to c/storage v1.15.5 + * Update container/storage to v1.15.4 + * Fix option handling for volumes in build + * Rework overlay pkg for use with libpod + * Fix buildahimage builds for buildah + * Add support for FIPS-Mode backends + * Set the TMPDIR for pulling/pushing image to $TMPDIR + * WIP: safer test for pull --all-tags + * BATS major cleanup: blobcache.bats: refactor + * BATS major cleanup: part 4: manual stuff + * BATS major cleanup, step 3: yet more run_buildah + * BATS major cleanup, part 2: use more run_buildah + * BATS major cleanup, part 1: log-level + * Bump github.com/containers/image/v5 from 5.0.0 to 5.1.0 + * Bump github.com/containers/common from 0.0.3 to 0.0.5 + * Bump to v1.13.0-dev + +- Changelog for v1.12.0 (2019-12-13) + * Allow ADD to use http src + * Bump to c/storage v.1.15.3 + * install.md: update golang dependency + * imgtype: reset storage opts if driver overridden + * Start using containers/common + * overlay.bats typo: fuse-overlays should be fuse-overlayfs + * chroot: Unmount with MNT_DETACH instead of UnmountMountpoints() + * bind: don't complain about missing mountpoints + * imgtype: check earlier for expected manifest type + * Vendor containers/storage fix + * Vendor containers/storage v1.15.1 + * Add history names support + * PR takeover of #1966 + * Tests: Add inspect test check steps + * Tests: Add container name and id check in containers test steps + * Test: Get permission in add test + * Tests: Add a test for tag by id + * Tests: Add test cases for push test + * Tests: Add image digest test + * Tests: Add some buildah from tests + * Tests: Add two commit test + * Tests: Add buildah bud with --quiet test + * Tests: Add two test for buildah add + * Bump back to v1.12.0-dev + +- Changelog for v1.11.6 (2019-12-03) + * Handle missing equal sign in --from and --chown flags for COPY/ADD + * bud COPY does not download URL + * Bump github.com/onsi/gomega from 1.7.0 to 1.7.1 + * Fix .dockerignore exclude regression + * Ran buildah through codespell + * commit(docker): always set ContainerID and ContainerConfig + * Touch up commit man page image parameter + * Add builder identity annotations. + * info: use util.Runtime() + * Bump github.com/onsi/ginkgo from 1.10.2 to 1.10.3 + * Bump back to v1.12.0-dev + +- Changelog for v1.11.5 (2019-11-11) + * Enhance error on unsafe symbolic link targets + * Add OCIRuntime to info + * Check nonexsit authfile + * Only output image id if running buildah bud --quiet + * Fix --pull=true||false and add --pull-never to bud and from (retry) + * cgroups v2: tweak or skip tests + * Prepwork: new 'skip' helpers for tests + * Handle configuration blobs for manifest lists + * unmarshalConvertedConfig: avoid using the updated image's ref + * Add completions for Manifest commands + * Add disableFips option to secrets pkg + * Update bud.bats test archive test + * Add test for caching based on content digest + * Builder.untarPath(): always evaluate b.ContentDigester.Hash() + * Bump github.com/onsi/ginkgo from 1.10.1 to 1.10.2 + * Fix another broken test: copy-url-mtime + * yet more fixes + * Actual bug fix for 'add' test: fix the expected mode + * BATS tests - lots of mostly minor cleanup + * build: drop support for ostree + * Add support for make vendor-in-container + * imgtype: exit with error if storage fails + * remove XDG_RUNTIME_DIR from default authfile path + * fix troubleshooting redirect instructions + * Bump back to v1.12.0-dev + +- Changelog for v1.11.4 (2019-10-28) + * buildah: add a "manifest" command + * manifests: add the module + * pkg/supplemented: add a package for grouping images together + * pkg/manifests: add a manifest list build/manipulation API + * Update for ErrUnauthorizedForCredentials API change in containers/image + * Update for manifest-lists API changes in containers/image + * version: also note the version of containers/image + * Move to containers/image v5.0.0 + * Enable --device directory as src device + * Fix git build with branch specified + * Bump github.com/openshift/imagebuilder from 1.1.0 to 1.1.1 + * Bump github.com/fsouza/go-dockerclient from 1.4.4 to 1.5.0 + * Add clarification to the Tutorial for new users + * Silence "using cache" to ensure -q is fully quiet + * Add OWNERS File to Buildah + * Bump github.com/containers/storage from 1.13.4 to 1.13.5 + * Move runtime flag to bud from common + * Commit: check for storage.ErrImageUnknown using errors.Cause() + * Fix crash when invalid COPY --from flag is specified. + * Bump back to v1.12.0-dev + +- Changelog for v1.11.3 (2019-10-04) + * Update c/image to v4.0.1 + * Bump github.com/spf13/pflag from 1.0.3 to 1.0.5 + * Fix --build-args handling + * Bump github.com/spf13/cobra from 0.0.3 to 0.0.5 + * Bump github.com/cyphar/filepath-securejoin from 0.2.1 to 0.2.2 + * Bump github.com/onsi/ginkgo from 1.8.0 to 1.10.1 + * Bump github.com/fsouza/go-dockerclient from 1.3.0 to 1.4.4 + * Add support for retrieving context from stdin "-" + * Ensure bud remote context cleans up on error + * info: add cgroups2 + * Bump github.com/seccomp/libseccomp-golang from 0.9.0 to 0.9.1 + * Bump github.com/mattn/go-shellwords from 1.0.5 to 1.0.6 + * Bump github.com/stretchr/testify from 1.3.0 to 1.4.0 + * Bump github.com/opencontainers/selinux from 1.2.2 to 1.3.0 + * Bump github.com/etcd-io/bbolt from 1.3.2 to 1.3.3 + * Bump github.com/onsi/gomega from 1.5.0 to 1.7.0 + * update c/storage to v1.13.4 + * Print build 'STEP' line to stdout, not stderr + * Fix travis-ci on forks + * Vendor c/storage v1.13.3 + * Use Containerfile by default + * Added tutorial on how to include Buildah as library + * util/util: Fix "configuraitno" -> "configuration" log typo + * Bump back to v1.12.0-dev + +- Changelog for v1.11.2 (2019-09-13) + * Add some cleanup code + * Move devices code to unit specific directory. + * Bump back to v1.12.0-dev + +- Changelog for v1.11.1 (2019-09-11) + * Add --devices flag to bud and from + * Downgrade .papr to highest atomic verion + * Add support for /run/.containerenv + * Truncate output of too long image names + * Preserve file and directory mount permissions + * Bump fedora version from 28 to 30 + * makeImageRef: ignore EmptyLayer if Squash is set + * Set TMPDIR to /var/tmp by default + * replace --debug=false with --log-level=error + * Allow mounts.conf entries for equal source and destination paths + * fix label and annotation for 1-line Dockerfiles + * Enable interfacer linter and fix lints + * install.md: mention goproxy + * Makefile: use go proxy + * Bump to v1.12.0-dev + +- Changelog for v1.11.0 (2019-08-29) + * tests/bud.bats: add --signature-policy to some tests + * Vendor github.com/openshift/api + * pull/commit/push: pay attention to $BUILD_REGISTRY_SOURCES + * Add `--log-level` command line option and deprecate `--debug` + * add support for cgroupsV2 + * Correctly detect ExitError values from Run() + * Disable empty logrus timestamps to reduce logger noise + * Remove outdated deps Makefile target + * Remove gofmt.sh in favor of golangci-lint + * Remove govet.sh in favor of golangci-lint + * Allow to override build date with SOURCE_DATE_EPOCH + * Update shebangs to take env into consideration + * Fix directory pull image names + * Add --digestfile and Re-add push statement as debug + * README: mention that Podman uses Buildah's API + * Use content digests in ADD/COPY history entries + * add: add a DryRun flag to AddAndCopyOptions + * Fix possible runtime panic on bud + * Add security-related volume options to validator + * use correct path for ginkgo + * Add bud 'without arguments' integration tests + * Update documentation about bud + * add: handle hard links when copying with .dockerignore + * add: teach copyFileWithTar() about symlinks and directories + * Allow buildah bud to be called without arguments + * imagebuilder: fix detection of referenced stage roots + * Touch up go mod instructions in install + * run_linux: fix mounting /sys in a userns + * Vendor Storage v1.13.2 + * Cirrus: Update VM images + * Fix handling of /dev/null masked devices + * Update `bud`/`from` help to contain indicator for `--dns=none` + * Bump back to v1.11.0-dev + +- Changelog for v1.10.1 (2019-08-08) + * Bump containers/image to v3.0.2 to fix keyring issue + * Bug fix for volume minus syntax + * Bump container/storage v1.13.1 and containers/image v3.0.1 + * bump github.com/containernetworking/cni to v0.7.1 + * Add overlayfs to fuse-overlayfs tip + * Add automatic apparmor tag discovery + * Fix bug whereby --get-login has no effect + * Bump to v1.11.0-dev + +- Changelog for v1.10.0 (2019-08-02) + * vendor github.com/containers/image@v3.0.0 + * Remove GO111MODULE in favor of `-mod=vendor` + * Vendor in containers/storage v1.12.16 + * Add '-' minus syntax for removal of config values + * tests: enable overlay tests for rootless + * rootless, overlay: use fuse-overlayfs + * vendor github.com/containers/image@v2.0.1 + * Added '-' syntax to remove volume config option + * delete `successfully pushed` message + * Add golint linter and apply fixes + * vendor github.com/containers/storage@v1.12.15 + * Change wait to sleep in buildahimage readme + * Handle ReadOnly images when deleting images + * Add support for listing read/only images + +- Changelog for v1.9.2 (2019-07-19) + * from/import: record the base image's digest, if it has one + * Fix CNI version retrieval to not require network connection + * Add misspell linter and apply fixes + * Add goimports linter and apply fixes + * Add stylecheck linter and apply fixes + * Add unconvert linter and apply fixes + * image: make sure we don't try to use zstd compression + * run.bats: skip the "z" flag when testing --mount + * Update to runc v1.0.0-rc8 + * Update to match updated runtime-tools API + * bump github.com/opencontainers/runtime-tools to v0.9.0 + * Build e2e tests using the proper build tags + * Add unparam linter and apply fixes + * Run: correct a typo in the --cap-add help text + * unshare: add a --mount flag + * fix push check image name is not empty + * Bump to v1.9.2-dev + +- Changelog for v1.9.1 (2019-07-12) + * add: fix slow copy with no excludes + * Add errcheck linter and fix missing error check + * Improve tests/tools/Makefile parallelism and abstraction + * Fix response body not closed resource leak + * Switch to golangci-lint + * Add gomod instructions and mailing list links + * On Masked path, check if /dev/null already mounted before mounting + * Update to containers/storage v1.12.13 + * Refactor code in package imagebuildah + * Add rootless podman with NFS issue in documentation + * Add --mount for buildah run + * import method ValidateVolumeOpts from libpod + * Fix typo + * Makefile: set GO111MODULE=off + * rootless: add the built-in slirp DNS server + * Update docker/libnetwork to get rid of outdated sctp package + * Update buildah-login.md + * migrate to go modules + * install.md: mention go modules + * tests/tools: go module for test binaries + * fix --volume splits comma delimited option + * Add bud test for RUN with a priv'd command + * vendor logrus v1.4.2 + * pkg/cli: panic when flags can't be hidden + * pkg/unshare: check all errors + * pull: check error during report write + * run_linux.go: ignore unchecked errors + * conformance test: catch copy error + * chroot/run_test.go: export funcs to actually be executed + * tests/imgtype: ignore error when shutting down the store + * testreport: check json error + * bind/util.go: remove unused func + * rm chroot/util.go + * imagebuildah: remove unused `dedupeStringSlice` + * StageExecutor: EnsureContainerPath: catch error from SecureJoin() + * imagebuildah/build.go: return instead of branching + * rmi: avoid redundant branching + * conformance tests: nilness: allocate map + * imagebuildah/build.go: avoid redundant `filepath.Join()` + * imagebuildah/build.go: avoid redundant `os.Stat()` + * imagebuildah: omit comparison to bool + * fix "ineffectual assignment" lint errors + * docker: ignore "repeats json tag" lint error + * pkg/unshare: use `...` instead of iterating a slice + * conformance: bud test: use raw strings for regexes + * conformance suite: remove unused func/var + * buildah test suite: remove unused vars/funcs + * testreport: fix golangci-lint errors + * util: remove redundant `return` statement + * chroot: only log clean-up errors + * images_test: ignore golangci-lint error + * blobcache: log error when draining the pipe + * imagebuildah: check errors in deferred calls + * chroot: fix error handling in deferred funcs + * cmd: check all errors + * chroot/run_test.go: check errors + * chroot/run.go: check errors in deferred calls + * imagebuildah.Executor: remove unused onbuild field + * docker/types.go: remove unused struct fields + * util: use strings.ContainsRune instead of index check + * Cirrus: Initial implementation + * Bump to v1.9.1-dev + +- Changelog for v1.9.0 (2019-06-15) + * buildah-run: fix-out-of-range panic (2) + * Bump back to v1.9.0-dev + +- Changelog for v1.8.4 (2019-06-13) + Update containers/image to v2.0.0 + run: fix hang with run and --isolation=chroot + run: fix hang when using run + chroot: drop unused function call + remove --> before imgageID on build + Always close stdin pipe + Write deny to setgroups when doing single user mapping + Avoid including linux/memfd.h + Add a test for the symlink pointing to a directory + Add missing continue + Fix the handling of symlinks to absolute paths + Only set default network sysctls if not rootless + Support --dns=none like podman + fix bug --cpu-shares parsing typo + Fix validate complaint + Update vendor on containers/storage to v1.12.10 + Create directory paths for COPY thereby ensuring correct perms + imagebuildah: use a stable sort for comparing build args + imagebuildah: tighten up cache checking + bud.bats: add a test verying the order of --build-args + add -t to podman run + imagebuildah: simplify screening by top layers + imagebuildah: handle ID mappings for COPY --from + imagebuildah: apply additionalTags ourselves + bud.bats: test additional tags with cached images + bud.bats: add a test for WORKDIR and COPY with absolute destinations + Cleanup Overlay Mounts content + +- Changelog for v1.8.3 (2019-06-04) + * Add support for file secret mounts + * Add ability to skip secrets in mounts file + * allow 32bit builds + * fix tutorial instructions + * imagebuilder: pass the right contextDir to Add() + * add: use fileutils.PatternMatcher for .dockerignore + * bud.bats: add another .dockerignore test + * unshare: fallback to single usermapping + * addHelperSymlink: clear the destination on os.IsExist errors + * bud.bats: test replacing symbolic links + * imagebuildah: fix handling of destinations that end with '/' + * bud.bats: test COPY with a final "/" in the destination + * linux: add check for sysctl before using it + * unshare: set _CONTAINERS_ROOTLESS_GID + * Rework buildahimamges + * build context: support https git repos + * Add a test for ENV special chars behaviour + * Check in new Dockerfiles + * Apply custom SHELL during build time + * config: expand variables only at the command line + * SetEnv: we only need to expand v once + * Add default /root if empty on chroot iso + * Add support for Overlay volumes into the container. + * Export buildah validate volume functions so it can share code with libpod + * Bump baseline test to F30 + * Fix rootless handling of /dev/shm size + * Avoid fmt.Printf() in the library + * imagebuildah: tighten cache checking back up + * Handle WORKDIR with dangling target + * Default Authfile to proper path + * Make buildah run --isolation follow BUILDAH_ISOLATION environment + * Vendor in latest containers/storage and containers/image + * getParent/getChildren: handle layerless images + * imagebuildah: recognize cache images for layerless images + * bud.bats: test scratch images with --layers caching + * Get CHANGELOG.md updates + * Add some symlinks to test our .dockerignore logic + * imagebuildah: addHelper: handle symbolic links + * commit/push: use an everything-allowed policy + * Correct manpage formatting in files section + * Remove must be root statement from buildah doc + * Change image names to stable, testing and upstream + * Bump back to v1.9.0-dev + +- Changelog for v1.8.2 (2019-05-02) + * Vendor Storage 1.12.6 + * Create scratch file in TESTDIR + * Test bud-copy-dot with --layers picks up changed file + * Bump back to 1.9.0-dev + +- Changelog for v1.8.1 (2019-05-01) + * Don't create directory on container + * Replace kubernetes/pause in tests with k8s.gcr.io/pause + * imagebuildah: don't remove intermediate images if we need them + * Rework buildahimagegit to buildahimageupstream + * Fix Transient Mounts + * Handle WORKDIRs that are symlinks + * allow podman to build a client for windows + * Touch up 1.9-dev to 1.9.0-dev + * Bump to 1.9-dev + +- Changelog for v1.8.0 (2019-04-26) + * Resolve symlink when checking container path + * commit: commit on every instruction, but not always with layers + * CommitOptions: drop the unused OnBuild field + * makeImageRef: pass in the whole CommitOptions structure + * cmd: API cleanup: stores before images + * run: check if SELinux is enabled + * Fix buildahimages Dockerfiles to include support for additionalimages mounted from host. + * Detect changes in rootdir + * Fix typo in buildah-pull(1) + * Vendor in latest containers/storage + * Keep track of any build-args used during buildah bud --layers + * commit: always set a parent ID + * imagebuildah: rework unused-argument detection + * fix bug dest path when COPY .dockerignore + * Move Host IDMAppings code from util to unshare + * Add BUILDAH_ISOLATION rootless back + * Travis CI: fail fast, upon error in any step + * imagebuildah: only commit images for intermediate stages if we have to + * Use errors.Cause() when checking for IsNotExist errors + * auto pass http_proxy to container + * Bump back to 1.8-dev + +- Changelog for v1.7.3 (2019-04-16) + * imagebuildah: don't leak image structs + * Add Dockerfiles for buildahimages + * Bump to Replace golang 1.10 with 1.12 + * add --dns* flags to buildah bud + * Add hack/build_speed.sh test speeds on building container images + * Create buildahimage Dockerfile for Quay + * rename 'is' to 'expect_output' + * squash.bats: test squashing in multi-layered builds + * bud.bats: test COPY --from in a Dockerfile while using the cache + * commit: make target image names optional + * Fix bud-args to allow comma separation + * oops, missed some tests in commit.bats + * new helper: expect_line_count + * New tests for #1467 (string slices in cmdline opts) + * Workarounds for dealing with travis; review feedback + * BATS tests - extensive but minor cleanup + * imagebuildah: defer pulling images for COPY --from + * imagebuildah: centralize COMMIT and image ID output + * Travis: do not use traviswait + * imagebuildah: only initialize imagebuilder configuration once per stage + * Make cleaner error on Dockerfile build errors + * unshare: move to pkg/ + * unshare: move some code from cmd/buildah/unshare + * Fix handling of Slices versus Arrays + * imagebuildah: reorganize stage and per-stage logic + * imagebuildah: add empty layers for instructions + * Add missing step in installing into Ubuntu + * fix bug in .dockerignore support + * imagebuildah: deduplicate prepended "FROM" instructions + * Touch up intro + * commit: set created-by to the shell if it isn't set + * commit: check that we always set a "created-by" + * docs/buildah.md: add "containers-" prefixes under "SEE ALSO" + * Bump back to 1.8-dev + +- Changelog for v1.7.2 (2019-03-28) + * mount: do not create automatically a namespace + * buildah: correctly create the userns if euid!=0 + * imagebuildah.Build: consolidate cleanup logic + * CommitOptions: drop the redundant Store field + * Move pkg/chrootuser from libpod to buildah. + * imagebuildah: record image IDs and references more often + * vendor imagebuilder v1.1.0 + * imagebuildah: fix requiresStart/noRunsRemaining confusion + * imagebuildah: check for unused args across stages + * bump github.com/containernetworking/cni to v0.7.0-rc2 + * imagebuildah: use "useCache" instead of "noCache" + * imagebuildah.resolveNameToImageRef(): take name as a parameter + * Export fields of the DokcerIgnore struct + * imagebuildah: drop the duplicate containerIDs list + * rootless: by default use the host network namespace + * imagebuildah: split Executor and per-stage execution + * imagebuildah: move some fields around + * golint: make golint happy + * docs: 01-intro.md: add missing . in Dockerfile examples + * fix bug using .dockerignore + * Do not create empty mounts.conf file + * images: suppress a spurious blank line with no images + * from: distinguish between ADD and COPY + * fix bug to not separate each --label value with comma + * buildah-bud.md: correct a typo, note a default + * Remove mistaken code that got merged in other PR + * add sample registries.conf to docs + * escape shell variables in README example + * slirp4netns: set mtu to 65520 + * images: imageReposToMap() already adds : + * imagebuildah.ReposToMap: move to cmd + * Build: resolve copyFrom references earlier + * Allow rootless users to use the cache directory in homedir + * bud.bats: use the per-test temp directory + * bud.bats: log output before counting length + * Simplify checks for leftover args + * Print commitID with --layers + * fix bug images use the template to print results + * rootless: honor --net host + * onsi/gomeage add missing files + * vendor latest openshift/imagebuilder + * Remove noop from squash help + * Prepend a comment to files setup in container + * imagebuildah resolveSymlink: fix handling of relative links + * Errors should be printed to stderr + * Add recommends for slirp4netns and fuse-overlay + * Update pull and pull-always flags + * Hide from users command options that we don't want them to use. + * Update secrets fipsmode patch to work on rootless containers + * fix unshare option handling and documentation + * Vendor in latest containers/storage + * Hard-code docker.Transport use in pull --all-tags + * Use a types.ImageReference instead of (transport, name) strings in pullImage etc. + * Move the computation of srcRef before first pullAndFindImage + * Don't throw away user-specified tag for pull --all-tags + * CHANGES BEHAVIOR: Remove the string format input to localImageNameForReference + * Don't try to parse imageName as transport:image in pullImage + * Use reference.WithTag instead of manual string manipulation in Pull + * Don't pass image = transport:repo:tag, transport=transport to pullImage + * Fix confusing variable naming in Pull + * Don't try to parse image name as a transport:image + * Fix error reporting when parsing trans+image + * Remove 'transport == ""' handling from the pull path + * Clean up "pulls" of local image IDs / ID prefixes + * Simplify ExpandNames + * Document the semantics of transport+name returned by ResolveName + * UPdate gitvalidation epoch + * Bump back to 1.8-dev + +- Changelog for v1.7.1 (2019-02-26) + * vendor containers/image v1.5 + * Move secrets code from libpod into buildah + * Update CHANGELOG.md with the past changes + * README.md: fix typo + * Fix a few issues found by tests/validate/gometalinter.sh + * Neutralize buildah/unshare on non-Linux platforms + * Explicitly specify a directory to find(1) + * README.md: rephrase Buildah description + * Stop printing default twice in cli --help + * install.md: add section about vendoring + * Bump to 1.8-dev + +- Changelog for v1.7 (2019-02-21) + * vendor containers/image v1.4 + * Make "images --all" faster + * Remove a misleading comment + * Remove quiet option from pull options + * Make sure buildah pull --all-tags only works with docker transport + * Support oci layout format + * Fix pulling of images within buildah + * Fix tls-verify polarity + * Travis: execute make vendor and hack/tree_status.sh + * vendor.conf: remove unused dependencies + * add missing vendor/github.com/containers/libpod/vendor.conf + * vendor.conf: remove github.com/inconshreveable/mousetrap + * make vendor: always fetch the latest vndr + * add hack/tree_status.sh script + * Bump c/Storage to 1.10 + * Add --all-tags test to pull + * mount: make error clearer + * Remove global flags from cli help + * Set --disable-compression to true as documented + * Help document using buildah mount in rootless mode + * healthcheck start-period: update documentation + * Vendor in latest c/storage and c/image + * dumpbolt: handle nested buckets + * Fix buildah commit compress by default + * Test on xenial, not trusty + * unshare: reexec using a memfd copy instead of the binary + * Add --target to bud command + * Fix example for setting multiple environment variables + * main: fix rootless mode + * buildah: force umask 022 + * pull.bats: specify registry config when using registries + * pull.bats: use the temporary directory, not /tmp + * unshare: do not set rootless mode if euid=0 + * Touch up cli help examples and a few nits + * Add an undocumented dumpbolt command + * Move tar commands into containers/storage + * Fix bud issue with 2 line Dockerfile + * Add package install descriptions + * Note configuration file requirements + * Replace urfave/cli with cobra + * cleanup vendor.conf + * Vendor in latest containers/storage + * Add Quiet to PullOptions and PushOptions + * cmd/commit: add flag omit-timestamp to allow for deterministic builds + * Add options for empty-layer history entries + * Make CLI help descriptions and usage a bit more consistent + * vndr opencontainers/selinux + * Bump baseline test Fedora to 29 + * Bump to v1.7-dev-1 + * Bump to v1.6-1 + * Add support for ADD --chown + * imagebuildah: make EnsureContainerPath() check/create the right one + * Bump 1.7-dev + * Fix contrib/rpm/bulidah.spec changelog date + +- Changelog for v1.6-1 (2019-01-18) + * Add support for ADD --chown + * imagebuildah: make EnsureContainerPath() check/create the right one + * Fix contrib/rpm/bulidah.spec changelog date + * Vendor in latest containers/storage + * Revendor everything + * Revendor in latest code by release + * unshare: do not set USER=root + * run: ignore EIO when flushing at the end, avoid double log + * build-using-dockerfile,commit: disable compression by default + * Update some comments + * Make rootless work under no_pivot_root + * Add CreatedAtRaw date field for use with Format + * Properly format images JSON output + * pull: add all-tags option + * Fix support for multiple Short options + * pkg/blobcache: add synchronization + * Skip empty files in file check of conformance test + * Use NoPivot also for RUN, not only for run + * Remove no longer used isReferenceInsecure / isRegistryInsecure + * Do not set OCIInsecureSkipTLSVerify based on registries.conf + * Remove duplicate entries from images JSON output + * vendor parallel-copy from containers/image + * blobcache.bats: adjust explicit push tests + * Handle one line Dockerfile with layers + * We should only warn if user actually requests Hostname be set in image + * Fix compiler Warning about comparing different size types + * imagebuildah: don't walk if rootdir and path are equal + * Add aliases for buildah containers, so buildah list, ls and ps work + * vendor: use faster version instead compress/gzip + * vendor: update libpod + * Properly handle Hostname inside of RUN command + * docs: mention how to mount in rootless mode + * tests: use fully qualified name for centos image + * travis.yml: use the fully qualified name for alpine + * mount: allow mount only when using vfs + * Add some tests for buildah pull + * Touch up images -q processing + * Refactor: Use library shared idtools.ParseIDMap() instead of bundling it + * bump GITVALIDATE_EPOCH + * cli.BudFlags: add `--platform` nop + * Makefile: allow packagers to more easily add tags + * Makefile: soften the requirement on git + * tests: add containers json test + * Inline blobCache.putBlob into blobCacheDestination.PutBlob + * Move saveStream and putBlob near blobCacheDestination.PutBlob + * Remove BlobCache.PutBlob + * Update for API changes + * Vendor c/image after merging c/image#536 + * Handle 'COPY --from' in Dockerfile + * Vendor in latest content from github.com/containers/storage + * Clarify docker.io default in push with docker-daemon + * Test blob caching + * Wire in a hidden --blob-cache option + * Use a blob cache when we're asked to use one + * Add --disable-compression to 'build-using-dockerfile' + * Add a blob cache implementation + * vendor: update containers/storage + * Update for sysregistriesv2 API changes + * Update containers/image to 63a1cbdc5e6537056695cf0d627c0a33b334df53 + * clean up makefile variables + * Fix file permission + * Complete the instructions for the command + * Show warning when a build arg not used + * Assume user 0 group 0, if /etc/passwd file in container. + * Add buildah info command + * Enable -q when --filter is used for images command + * Add v1.5 Release Announcement + * Fix dangling filter for images command + * Fix completions to print Names as well as IDs + * tests: Fix file permissions + * Bump 1.6-dev + +- Changelog for v1.5-1 (2018-11-21) + * Bump min go to 1.10 in install.md + * vendor: update ostree-go + * Update docker build command line in conformance test + * Print command in SystemExec as debug information + * Add some skip word for inspect check in conformance test + * Update regex for multi stage base test + * Sort CLI flags + * vendor: update containers/storage + * Add note to install about non-root on RHEL/CentOS + * Update imagebuild depdency to support heading ARGs in Dockerfile + * rootless: do not specify --rootless to the OCI runtime + * Export resolvesymlink function + * Exclude --force-rm from common bud cli flags + * run: bind mount /etc/hosts and /etc/resolv.conf if not in a volume + * rootless: use slirp4netns to setup the network namespace + * Instructions for completing the pull command + * Fix travis to not run environment variable patch + * rootless: only discard network configuration names + * run: only set up /etc/hosts or /etc/resolv.conf with network + * common: getFormat: match entire string not only the prefix + * vendor: update libpod + * Change validation EPOCH + * Fixing broken link for container-registries.conf + * Restore rootless isolation test for from volume ro test + * ostree: fix tag for build constraint + * Handle directories better in bud -f + * vndr in latest containers/storage + * Fix unshare gofmt issue + * runSetupBuiltinVolumes(): break up volume setup + * common: support a per-user registries conf file + * unshare: do not override the configuration + * common: honor the rootless configuration file + * unshare: create a new mount namespace + * unshare: support libpod rootless pkg + * Use libpod GetDefaultStorage to report proper storage config + * Allow container storage to manage the SELinux labels + * Resolve image names with default transport in from command + * run: When the value of isolation is set, use the set value instead of the default value. + * Vendor in latest containers/storage and opencontainers/selinux + * Remove no longer valid todo + * Check for empty buildTime in version + * Change gofmt so it runs on all but 1.10 + * Run gofmt only on Go 1.11 + * Walk symlinks when checking cached images for copied/added files + * ReserveSELinuxLabels(): handle wrapped errors from OpenBuilder + * Set WorkingDir to empty, not / for conformance + * Update calls in e2e to addres 1101 + * imagebuilder.BuildDockerfiles: return the image ID + * Update for changes in the containers/image API + * bump(github.com/containers/image) + * Allow setting --no-pivot default with an env var + * Add man page and bash completion, for --no-pivot + * Add the --no-pivot flag to the run command + * Improve reporting about individual pull failures + * Move the "short name but no search registries" error handling to resolveImage + * Return a "search registries were needed but empty" indication in util.ResolveName + * Simplify handling of the "tried to pull an image but found nothing" case in newBuilder + * Don't even invoke the pull loop if options.FromImage == "" + * Eliminate the long-running ref and img variables in resolveImage + * In resolveImage, return immediately on success + * Fix From As in Dockerfile + * Vendor latest containers/image + * Vendor in latest libpod + * Sort CLI flags of buildah bud + * Change from testing with golang 1.9 to 1.11. + * unshare: detect when unprivileged userns are disabled + * Optimize redundant code + * fix missing format param + * chroot: fix the args check + * imagebuildah: make ResolveSymLink public + * Update copy chown test + * buildah: use the same logic for XDG_RUNTIME_DIR as podman + * V1.4 Release Announcement + * Podman --privileged selinux is broken + * papr: mount source at gopath + * parse: Modify the return value + * parse: modify the verification of the isolation value + * Make sure we log or return every error + * pullImage(): when completing an image name, try docker:// + * Fix up Tutorial 3 to account for format + * Vendor in latest containers/storage and containers/image + * docs/tutorials/01-intro.md: enhanced installation instructions + * Enforce "blocked" for registries for the "docker" transport + * Correctly set DockerInsecureSkipTLSVerify when pulling images + * chroot: set up seccomp and capabilities after supplemental groups + * chroot: fix capabilities list setup and application + * .papr.yml: log the podman version + * namespaces.bats: fix handling of uidmap/gidmap options in pairs + * chroot: only create user namespaces when we know we need them + * Check /proc/sys/user/max_user_namespaces on unshare(NEWUSERNS) + * bash/buildah: add isolation option to the from command + +- Changelog for v1.4 (2018-10-02) + * from: fix isolation option + * Touchup pull manpage + * Export buildah ReserveSELinuxLables so podman can use it + * Add buildah.io to README.md and doc fixes + * Update rmi man for prune changes + * Ignore file not found removal error in bud + * bump(github.com/containers/{storage,image}) + * NewImageSource(): only create one Diff() at a time + * Copy ExposedPorts from base image into the config + * tests: run conformance test suite in Travis + * Change rmi --prune to not accept an imageID + * Clear intermediate container IDs after each stage + * Request podman version for build issues + * unshare: keep the additional groups of the user + * Builtin volumes should be owned by the UID/GID of the container + * Get rid of dangling whitespace in markdown files + * Move buildah from projecatatomic/buildah to containers/buildah + * nitpick: parse.validateFlags loop in bud cli + * bash: Completion options + * Add signature policy to push tests + * vendor in latest containers/image + * Fix grammar in Container Tools Guide + * Don't build btrfs if it is not installed + * new: Return image-pulling errors from resolveImage + * pull: Return image-pulling errors from pullImage + * Add more volume mount tests + * chroot: create missing parent directories for volume mounts + * Push: Allow an empty destination + * Add Podman relationship to readme, create container tools guide + * Fix arg usage in buildah-tag + * Add flags/arguments order verification to other commands + * Handle ErrDuplicateName errors from store.CreateContainer() + * Evaluate symbolic links on Add/Copy Commands + * Vendor in latest containers/image and containers/storage + * Retain bounding set when running containers as non root + * run container-diff tests in Travis + * buildah-images.md: Fix option contents + * push: show image digest after push succeed + * Vendor in latest containers/storage,image,libpod and runc + * Change references to cri-o to point at new repository + * Exclude --layers from the common bug cli flags + * demos: Increase the executable permissions + * run: clear default seccomp filter if not enabled + * Bump maximum cyclomatic complexity to 45 + * stdin: on HUP, read everything + * nitpick: use tabs in tests/helpers.bash + * Add flags/arguments order verification to one arg commands + * nitpick: decrease cognitive complexity in buildah-bud + * rename: Avoid renaming the same name as other containers + * chroot isolation: chroot() before setting up seccomp + * Small nitpick at the "if" condition in tag.go + * cmd/images: Modify json option + * cmd/images: Disallow the input of image when using the -a option + * Fix examples to include context directory + * Update containers/image to fix commit layer issue + * cmd/containers: End loop early when using the json option + * Make buildah-from error message clear when flags are after arg + * Touch up README.md for conformance tests + * Update container/storage for lock fix + * cmd/rm: restore the correct containerID display + * Remove debug lines + * Remove docker build image after each test + * Add README for conformance test + * Update the MakeOptions to accept all command options for buildah + * Update regrex to fit the docker output in test "run with JSON" + * cmd/buildah: Remove redundant variable declarations + * Warn about using Commands in Dockerfile that are not supported by OCI. + * Add buildah bud conformance test + * Fix rename to also change container name in builder + * Makefile: use $(GO) env-var everywhere + * Cleanup code to more closely match Docker Build images + * Document BUILDAH_* environment variables in buildah bud --help output + * Return error immediately if error occurs in Prepare step + * Fix --layers ADD from url issue + * Add "Sign your PRs" TOC item to contributing.md. + * Display the correct ID after deleting image + * rmi: Modify the handling of errors + * Let util.ResolveName() return parsing errors + * Explain Open Container Initiative (OCI) acronym, add link + * Update vendor for urfave/cli back to master + * Handle COPY --chown in Dockerfile + * Switch to Recommends container-selinux + * Update vendor for containernetworking, imagebuildah and podman + * Document STORAGE_DRIVER and STORAGE_OPTS environment variable + * Change references to projectatomic/libpod to containers/libpod + * Add container PATH retrieval example + * Expand variables names for --env + * imagebuildah: provide a way to provide stdin for RUN + * Remove an unused srcRef.NewImageSource in pullImage + * chroot: correct a comment + * chroot: bind mount an empty directory for masking + * Don't bother with --no-pivot for rootless isolation + * CentOS need EPEL repo + * Export a Pull() function + * Remove stream options, since docker build does not have it + * release v1.3: mention openSUSE + * Add Release Announcements directory + * Bump to v1.4-dev + +- Changelog for v1.3 (2018-08-04) + * Revert pull error handling from 881 + * bud should not search context directory for Dockerfile + * Set BUILDAH_ISOLATION=rootless when running unprivileged + * .papr.sh: Also test with BUILDAH_ISOLATION=rootless + * Skip certain tests when we're using "rootless" isolation + * .travis.yml: run integration tests with BUILDAH_ISOLATION=chroot + * Add and implement IsolationOCIRootless + * Add a value for IsolationOCIRootless + * Fix rmi to remove intermediate images associated with an image + * Return policy error on pull + * Update containers/image to 216acb1bcd2c1abef736ee322e17147ee2b7d76c + * Switch to github.com/containers/image/pkg/sysregistriesv2 + * unshare: make adjusting the OOM score optional + * Add flags validation + * chroot: handle raising process limits + * chroot: make the resource limits name map module-global + * Remove rpm.bats, we need to run this manually + * Set the default ulimits to match Docker + * buildah: no args is out of bounds + * unshare: error message missed the pid + * preprocess ".in" suffixed Dockerfiles + * Fix the the in buildah-config man page + * Only test rpmbuild on latest fedora + * Add support for multiple Short options + * Update to latest urvave/cli + * Add additional SELinux tests + * Vendor in latest github.com/containers/{image;storage} + * Stop testing with golang 1.8 + * Fix volume cache issue with buildah bud --layers + * Create buildah pull command + * Increase the deadline for gometalinter during 'make validate' + * .papr.sh: Also test with BUILDAH_ISOLATION=chroot + * .travis.yml: run integration tests with BUILDAH_ISOLATION=chroot + * Add a Dockerfile + * Set BUILDAH_ISOLATION=chroot when running unprivileged + * Add and implement IsolationChroot + * Update github.com/opencontainers/runc + * maybeReexecUsingUserNamespace: add a default for root + * Allow ping command without NET_RAW Capabilities + * rmi.storageImageID: fix Wrapf format warning + * Allow Dockerfile content to come from stdin + * Vendor latest container/storage to fix overlay mountopt + * userns: assign additional IDs sequentially + * Remove default dev/pts + * Add OnBuild test to baseline test + * tests/run.bats(volumes): use :z when SELinux is enabled + * Avoid a stall in runCollectOutput() + * Use manifest from container/image + * Vendor in latest containers/image and containers/storage + * add rename command + * Completion command + * Update CHANGELOG.md + * Update vendor for runc to fix 32 bit builds + * bash completion: remove shebang + * Update vendor for runc to fix 32 bit builds + +- Changelog for v1.2 (2018-07-14) + * Vendor in lates containers/image + * build-using-dockerfile: let -t include transports again + * Block use of /proc/acpi and /proc/keys from inside containers + * Fix handling of --registries-conf + * Fix becoming a maintainer link + * add optional CI test fo darwin + * Don't pass a nil error to errors.Wrapf() + * image filter test: use kubernetes/pause as a "since" + * Add --cidfile option to from + * vendor: update containers/storage + * Contributors need to find the CONTRIBUTOR.md file easier + * Add a --loglevel option to build-with-dockerfile + * Create Development plan + * cmd: Code improvement + * allow buildah cross compile for a darwin target + * Add unused function param lint check + * docs: Follow man-pages(7) suggestions for SYNOPSIS + * Start using github.com/seccomp/containers-golang + * umount: add all option to umount all mounted containers + * runConfigureNetwork(): remove an unused parameter + * Update github.com/opencontainers/selinux + * Fix buildah bud --layers + * Force ownership of /etc/hosts and /etc/resolv.conf to 0:0 + * main: if unprivileged, reexec in a user namespace + * Vendor in latest imagebuilder + * Reduce the complexity of the buildah.Run function + * mount: output it before replacing lastError + * Vendor in latest selinux-go code + * Implement basic recognition of the "--isolation" option + * Run(): try to resolve non-absolute paths using $PATH + * Run(): don't include any default environment variables + * build without seccomp + * vendor in latest runtime-tools + * bind/mount_unsupported.go: remove import errors + * Update github.com/opencontainers/runc + * Add Capabilities lists to BuilderInfo + * Tweaks for commit tests + * commit: recognize committing to second storage locations + * Fix ARGS parsing for run commands + * Add info on registries.conf to from manpage + * Switch from using docker to podman for testing in .papr + * buildah: set the HTTP User-Agent + * ONBUILD tutorial + * Add information about the configuration files to the install docs + * Makefile: add uninstall + * Add tilde info for push to troubleshooting + * mount: support multiple inputs + * Use the right formatting when adding entries to /etc/hosts + * Vendor in latest go-selinux bindings + * Allow --userns-uid-map/--userns-gid-map to be global options + * bind: factor out UnmountMountpoints + * Run(): simplify runCopyStdio() + * Run(): handle POLLNVAL results + * Run(): tweak terminal mode handling + * Run(): rename 'copyStdio' to 'copyPipes' + * Run(): don't set a Pdeathsig for the runtime + * Run(): add options for adding and removing capabilities + * Run(): don't use a callback when a slice will do + * setupSeccomp(): refactor + * Change RunOptions.Stdin/Stdout/Stderr to just be Reader/Writers + * Escape use of '_' in .md docs + * Break out getProcIDMappings() + * Break out SetupIntermediateMountNamespace() + * Add Multi From Demo + * Use the c/image conversion code instead of converting configs manually + * Don't throw away the manifest MIME type and guess again + * Consolidate loading manifest and config in initConfig + * Pass a types.Image to Builder.initConfig + * Require an image ID in importBuilderDataFromImage + * Use c/image/manifest.GuessMIMEType instead of a custom heuristic + * Do not ignore any parsing errors in initConfig + * Explicitly handle "from scratch" images in Builder.initConfig + * Fix parsing of OCI images + * Simplify dead but dangerous-looking error handling + * Don't ignore v2s1 history if docker_version is not set + * Add --rm and --force-rm to buildah bud + * Add --all,-a flag to buildah images + * Separate stdio buffering from writing + * Remove tty check from images --format + * Add environment variable BUILDAH_RUNTIME + * Add --layers and --no-cache to buildah bud + * Touch up images man + * version.md: fix DESCRIPTION + * tests: add containers test + * tests: add images test + * images: fix usage + * fix make clean error + * Change 'registries' to 'container registries' in man + * add commit test + * Add(): learn to record hashes of what we add + * Minor update to buildah config documentation for entrypoint + * Bump to v1.2-dev + * Add registries.conf link to a few man pages + +- Changelog for v1.1 (2018-06-08) + * Drop capabilities if running container processes as non root + * Print Warning message if cmd will not be used based on entrypoint + * Update 01-intro.md + * Shouldn't add insecure registries to list of search registries + * Report errors on bad transports specification when pushing images + * Move parsing code out of common for namespaces and into pkg/parse.go + * Add disable-content-trust noop flag to bud + * Change freenode chan to buildah + * runCopyStdio(): don't close stdin unless we saw POLLHUP + * Add registry errors for pull + * runCollectOutput(): just read until the pipes are closed on us + * Run(): provide redirection for stdio + * rmi, rm: add test + * add mount test + * Add parameter judgment for commands that do not require parameters + * Add context dir to bud command in baseline test + * run.bats: check that we can run with symlinks in the bundle path + * Give better messages to users when image can not be found + * use absolute path for bundlePath + * Add environment variable to buildah --format + * rm: add validation to args and all option + * Accept json array input for config entrypoint + * Run(): process RunOptions.Mounts, and its flags + * Run(): only collect error output from stdio pipes if we created some + * Add OnBuild support for Dockerfiles + * Quick fix on demo readme + * run: fix validate flags + * buildah bud should require a context directory or URL + * Touchup tutorial for run changes + * Validate common bud and from flags + * images: Error if the specified imagename does not exist + * inspect: Increase err judgments to avoid panic + * add test to inspect + * buildah bud picks up ENV from base image + * Extend the amount of time travis_wait should wait + * Add a make target for Installing CNI plugins + * Add tests for namespace control flags + * copy.bats: check ownerships in the container + * Fix SELinux test errors when SELinux is enabled + * Add example CNI configurations + * Run: set supplemental group IDs + * Run: use a temporary mount namespace + * Use CNI to configure container networks + * add/secrets/commit: Use mappings when setting permissions on added content + * Add CLI options for specifying namespace and cgroup setup + * Always set mappings when using user namespaces + * Run(): break out creation of stdio pipe descriptors + * Read UID/GID mapping information from containers and images + * Additional bud CI tests + * Run integration tests under travis_wait in Travis + * build-using-dockerfile: add --annotation + * Implement --squash for build-using-dockerfile and commit + * Vendor in latest container/storage for devicemapper support + * add test to inspect + * Vendor github.com/onsi/ginkgo and github.com/onsi/gomega + * Test with Go 1.10, too + * Add console syntax highlighting to troubleshooting page + * bud.bats: print "$output" before checking its contents + * Manage "Run" containers more closely + * Break Builder.Run()'s "run runc" bits out + * util.ResolveName(): handle completion for tagged/digested image names + * Handle /etc/hosts and /etc/resolv.conf properly in container + * Documentation fixes + * Make it easier to parse our temporary directory as an image name + * Makefile: list new pkg/ subdirectoris as dependencies for buildah + * containerImageSource: return more-correct errors + * API cleanup: PullPolicy and TerminalPolicy should be types + * Make "run --terminal" and "run -t" aliases for "run --tty" + * Vendor github.com/containernetworking/cni v0.6.0 + * Update github.com/containers/storage + * Update github.com/projectatomic/libpod + * Add support for buildah bud --label + * buildah push/from can push and pull images with no reference + * Vendor in latest containers/image + * Update gometalinter to fix install.tools error + * Update troubleshooting with new run workaround + * Added a bud demo and tidied up + * Attempt to download file from url, if fails assume Dockerfile + * Add buildah bud CI tests for ENV variables + * Re-enable rpm .spec version check and new commit test + * Update buildah scratch demo to support el7 + * Added Docker compatibility demo + * Update to F28 and new run format in baseline test + * Touchup man page short options across man pages + * Added demo dir and a demo. chged distrorlease + * builder-inspect: fix format option + * Add cpu-shares short flag (-c) and cpu-shares CI tests + * Minor fixes to formatting in rpm spec changelog + * Fix rpm .spec changelog formatting + * CI tests and minor fix for cache related noop flags + * buildah-from: add effective value to mount propagation + +- Changelog for v1.0 (2018-05-06) + * Declare Buildah 1.0 + * Add cache-from and no-cache noops, and fix doco + * Update option and documentation for --force-rm + * Adding noop for --force-rm to match --rm + * Add buildah bud ENTRYPOINT,CMD,RUN tests + * Adding buildah bud RUN test scenarios + * Extend tests for empty buildah run command + * Fix formatting error in run.go + * Update buildah run to make command required + * Expanding buildah run cmd/entrypoint tests + * Update test cases for buildah run behaviour + * Remove buildah run cmd and entrypoint execution + * Add Files section with registries.conf to pertinent man pages + * tests/config: perfect test + * tests/from: add name test + * Do not print directly to stdout in Commit() + * Touch up auth test commands + * Force "localhost" as a default registry + * Drop util.GetLocalTime() + * Vendor in latest containers/image + * Validate host and container paths passed to --volume + * test/from: add add-host test + * Add --compress, --rm, --squash flags as a noop for bud + * Add FIPS mode secret to buildah run and bud + * Add config --comment/--domainname/--history-comment/--hostname + * 'buildah config': stop replacing Created-By whenever it's not specified + * Modify man pages so they compile correctly in mandb + * Add description on how to do --isolation to buildah-bud man page + * Add support for --iidfile to bud and commit + * Refactor buildah bud for vendoring + * Fail if date or git not installed + * Revert update of entrypoint behaviour to match docker + * Vendor in latest imagebuilder code to fix multiple stage builds + * Add /bin/sh -c to entrypoint in config + * image_test: Improve the test + * Fix README example of buildah config + * buildah-image: add validation to 'format' + * Simple changes to allow buildah to pass make validate + * Clarify the use of buildah config options + * containers_test: Perfect testing + * buildah images and podman images are listing different sizes + * buildah-containers: add tests and example to the man page + * buildah-containers: add validation to 'format' + * Clarify the use of buildah config options + * Minor fix for lighttpd example in README + * Add tls-verification to troubleshooting + * Modify buildah rmi to account for changes in containers/storage + * Vendor in latest containers/image and containers/storage + * addcopy: add src validation + * Remove tarball as an option from buildah push --help + * Fix secrets patch + * Update entrypoint behaviour to match docker + * Display imageId after commit + * config: add support for StopSignal + * Fix docker login issue in travis.yml + * Allow referencing stages as index and names + * Add multi-stage builds tests + * Add multi-stage builds support + * Add accessor functions for comment and stop signal + * Vendor in latest imagebuilder, to get mixed case AS support + * Allow umount to have multi-containers + * Update buildah push doc + * buildah bud walks symlinks + * Imagename is required for commit atm, update manpage + +- Changelog for v0.16.0 (2018-04-08) + * Bump to v0.16.0 + * Remove requires for ostree-lib in rpm spec file + * Add support for shell + * buildah.spec should require ostree-libs + * Vendor in latest containers/image + * bash: prefer options + * Change image time to locale, add troubleshooting.md, add logo to other mds + * buildah-run.md: fix error SYNOPSIS + * docs: fix error example + * Allow --cmd parameter to have commands as values + * Touchup README to re-enable logo + * Clean up README.md + * Make default-mounts-file a hidden option + * Document the mounts.conf file + * Fix man pages to format correctly + * Add various transport support to buildah from + * Add unit tests to run.go + * If the user overrides the storage driver, the options should be dropped + * Show Config/Manifest as JSON string in inspect when format is not set + * Switch which for that in README.md + * Remove COPR + * Fix wrong order of parameters + * Vendor in latest containers/image + * Remove shallowCopy(), which shouldn't be saving us time any more + * shallowCopy: avoid a second read of the container's layer diff --git a/vendor/github.com/containers/buildah/chroot/run_common.go b/vendor/github.com/containers/buildah/chroot/run_common.go new file mode 100644 index 00000000..fbd0689f --- /dev/null +++ b/vendor/github.com/containers/buildah/chroot/run_common.go @@ -0,0 +1,854 @@ +//go:build linux || freebsd + +package chroot + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "os/exec" + "os/signal" + "path/filepath" + "runtime" + "slices" + "strconv" + "strings" + "sync" + "syscall" + + "github.com/containers/buildah/bind" + "github.com/containers/buildah/internal/pty" + "github.com/containers/buildah/util" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/ioutils" + "go.podman.io/storage/pkg/reexec" + "go.podman.io/storage/pkg/unshare" + "golang.org/x/sys/unix" + "golang.org/x/term" +) + +const ( + // runUsingChrootCommand is a command we use as a key for reexec + runUsingChrootCommand = "buildah-chroot-runtime" + // runUsingChrootExec is a command we use as a key for reexec + runUsingChrootExecCommand = "buildah-chroot-exec" + // containersConfEnv is an environment variable that we need to pass down except for the command itself + containersConfEnv = "CONTAINERS_CONF" +) + +func init() { + reexec.Register(runUsingChrootCommand, runUsingChrootMain) + reexec.Register(runUsingChrootExecCommand, runUsingChrootExecMain) + for limitName, limitNumber := range rlimitsMap { + rlimitsReverseMap[limitNumber] = limitName + } +} + +type runUsingChrootExecSubprocOptions struct { + Spec *specs.Spec + BundlePath string + NoPivot bool +} + +// RunUsingChroot runs a chrooted process, using some of the settings from the +// passed-in spec, and using the specified bundlePath to hold temporary files, +// directories, and mountpoints. +func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reader, stdout, stderr io.Writer, noPivot bool) (err error) { + var confwg sync.WaitGroup + var homeFound bool + for _, env := range spec.Process.Env { + if strings.HasPrefix(env, "HOME=") { + homeFound = true + break + } + } + if !homeFound { + spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("HOME=%s", homeDir)) + } + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + // Write the runtime configuration, mainly for debugging. + specbytes, err := json.Marshal(spec) + if err != nil { + return err + } + if err = ioutils.AtomicWriteFile(filepath.Join(bundlePath, "config.json"), specbytes, 0o600); err != nil { + return fmt.Errorf("storing runtime configuration: %w", err) + } + logrus.Debugf("config = %v", string(specbytes)) + + // Default to using stdin/stdout/stderr if we weren't passed objects to use. + if stdin == nil { + stdin = os.Stdin + } + if stdout == nil { + stdout = os.Stdout + } + if stderr == nil { + stderr = os.Stderr + } + + // Create a pipe for passing configuration down to the next process. + preader, pwriter, err := os.Pipe() + if err != nil { + return fmt.Errorf("creating configuration pipe: %w", err) + } + config, conferr := json.Marshal(runUsingChrootSubprocOptions{ + Spec: spec, + BundlePath: bundlePath, + NoPivot: noPivot, + }) + if conferr != nil { + return fmt.Errorf("encoding configuration for %q: %w", runUsingChrootCommand, conferr) + } + + // Set our terminal's mode to raw, to pass handling of special + // terminal input to the terminal in the container. + if spec.Process.Terminal && term.IsTerminal(unix.Stdin) { + state, err := term.MakeRaw(unix.Stdin) + if err != nil { + logrus.Warnf("error setting terminal state: %v", err) + } else { + defer func() { + if err = term.Restore(unix.Stdin, state); err != nil { + logrus.Errorf("unable to restore terminal state: %v", err) + } + }() + } + } + + // Raise any resource limits that are higher than they are now, before + // we drop any more privileges. + if err = setRlimits(spec, false, true); err != nil { + return err + } + + // Start the grandparent subprocess. + cmd := unshare.Command(runUsingChrootCommand) + setPdeathsig(cmd.Cmd) + cmd.Stdin, cmd.Stdout, cmd.Stderr = stdin, stdout, stderr + cmd.Dir = "/" + cmd.Env = []string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())} + if _, ok := os.LookupEnv(containersConfEnv); ok { + cmd.Env = append(cmd.Env, containersConfEnv+"="+os.Getenv(containersConfEnv)) + } + + interrupted := make(chan os.Signal, 100) + cmd.Hook = func(int) error { + signal.Notify(interrupted, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + go func() { + for receivedSignal := range interrupted { + if err := cmd.Process.Signal(receivedSignal); err != nil { + logrus.Infof("%v while attempting to forward %v to child process", err, receivedSignal) + } + } + }() + return nil + } + + logrus.Debugf("Running %#v in %#v", cmd.Cmd, cmd) + confwg.Add(1) + go func() { + _, conferr = io.Copy(pwriter, bytes.NewReader(config)) + pwriter.Close() + confwg.Done() + }() + cmd.ExtraFiles = append([]*os.File{preader}, cmd.ExtraFiles...) + err = cmd.Run() + confwg.Wait() + signal.Stop(interrupted) + close(interrupted) + if err == nil { + return conferr + } + return err +} + +// main() for grandparent subprocess. Its main job is to shuttle stdio back +// and forth, managing a pseudo-terminal if we want one, for our child, the +// parent subprocess. +func runUsingChrootMain() { + var options runUsingChrootSubprocOptions + + runtime.LockOSThread() + + // Set logging. + if level := os.Getenv("LOGLEVEL"); level != "" { + if ll, err := strconv.Atoi(level); err == nil { + logrus.SetLevel(logrus.Level(ll)) + } + os.Unsetenv("LOGLEVEL") + } + + // Unpack our configuration. + confPipe := os.NewFile(3, "confpipe") + if confPipe == nil { + fmt.Fprintf(os.Stderr, "error reading options pipe\n") + os.Exit(1) + } + defer confPipe.Close() + if err := json.NewDecoder(confPipe).Decode(&options); err != nil { + fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err) + os.Exit(1) + } + + if options.Spec == nil || options.Spec.Process == nil { + fmt.Fprintf(os.Stderr, "invalid options spec in runUsingChrootMain\n") + os.Exit(1) + } + noPivot := options.NoPivot + + // Prepare to shuttle stdio back and forth. + rootUID32, rootGID32, err := util.GetHostRootIDs(options.Spec) + if err != nil { + logrus.Errorf("error determining ownership for container stdio") + os.Exit(1) + } + rootUID := int(rootUID32) + rootGID := int(rootGID32) + relays := make(map[int]int) + closeOnceRunning := []*os.File{} + var ctty *os.File + var stdin io.Reader + var stdinCopy io.WriteCloser + var stdout io.Writer + var stderr io.Writer + fdDesc := make(map[int]string) + if options.Spec.Process.Terminal { + ptyMasterFd, ptyFd, err := pty.GetPtyDescriptors() + if err != nil { + logrus.Errorf("error opening PTY descriptors: %v", err) + os.Exit(1) + } + // Make notes about what's going where. + relays[ptyMasterFd] = unix.Stdout + relays[unix.Stdin] = ptyMasterFd + fdDesc[ptyMasterFd] = "container terminal" + fdDesc[unix.Stdin] = "stdin" + fdDesc[unix.Stdout] = "stdout" + winsize := &unix.Winsize{} + // Set the pseudoterminal's size to the configured size, or our own. + if options.Spec.Process.ConsoleSize != nil { + // Use configured sizes. + winsize.Row = uint16(options.Spec.Process.ConsoleSize.Height) + winsize.Col = uint16(options.Spec.Process.ConsoleSize.Width) + } else { + if term.IsTerminal(unix.Stdin) { + // Use the size of our terminal. + winsize, err = unix.IoctlGetWinsize(unix.Stdin, unix.TIOCGWINSZ) + if err != nil { + logrus.Debugf("error reading current terminal's size") + winsize.Row = 0 + winsize.Col = 0 + } + } + } + if winsize.Row != 0 && winsize.Col != 0 { + if err = unix.IoctlSetWinsize(ptyFd, unix.TIOCSWINSZ, winsize); err != nil { + logrus.Warnf("error setting terminal size for pty") + } + // FIXME - if we're connected to a terminal, we should + // be passing the updated terminal size down when we + // receive a SIGWINCH. + } + // Open an *os.File object that we can pass to our child. + ctty = os.NewFile(uintptr(ptyFd), "/dev/tty") + // Set ownership for the PTY. + if err = ctty.Chown(rootUID, rootGID); err != nil { + var cttyInfo unix.Stat_t + err2 := unix.Fstat(ptyFd, &cttyInfo) + from := "" + op := "setting" + if err2 == nil { + op = "changing" + from = fmt.Sprintf("from %d/%d ", cttyInfo.Uid, cttyInfo.Gid) + } + logrus.Warnf("error %s ownership of container PTY %sto %d/%d: %v", op, from, rootUID, rootGID, err) + } + // Set permissions on the PTY. + if err = ctty.Chmod(0o620); err != nil { + logrus.Errorf("error setting permissions of container PTY: %v", err) + os.Exit(1) + } + // Make a note that our child (the parent subprocess) should + // have the PTY connected to its stdio, and that we should + // close it once it's running. + stdin = ctty + stdout = ctty + stderr = ctty + closeOnceRunning = append(closeOnceRunning, ctty) + } else { + // Create pipes for stdio. + stdinRead, stdinWrite, err := os.Pipe() + if err != nil { + logrus.Errorf("error opening pipe for stdin: %v", err) + } + stdoutRead, stdoutWrite, err := os.Pipe() + if err != nil { + logrus.Errorf("error opening pipe for stdout: %v", err) + } + stderrRead, stderrWrite, err := os.Pipe() + if err != nil { + logrus.Errorf("error opening pipe for stderr: %v", err) + } + // Make notes about what's going where. + relays[unix.Stdin] = int(stdinWrite.Fd()) + relays[int(stdoutRead.Fd())] = unix.Stdout + relays[int(stderrRead.Fd())] = unix.Stderr + fdDesc[int(stdinWrite.Fd())] = "container stdin pipe" + fdDesc[int(stdoutRead.Fd())] = "container stdout pipe" + fdDesc[int(stderrRead.Fd())] = "container stderr pipe" + fdDesc[unix.Stdin] = "stdin" + fdDesc[unix.Stdout] = "stdout" + fdDesc[unix.Stderr] = "stderr" + // Set ownership for the pipes. + if err = stdinRead.Chown(rootUID, rootGID); err != nil { + logrus.Errorf("error setting ownership of container stdin pipe: %v", err) + os.Exit(1) + } + if err = stdoutWrite.Chown(rootUID, rootGID); err != nil { + logrus.Errorf("error setting ownership of container stdout pipe: %v", err) + os.Exit(1) + } + if err = stderrWrite.Chown(rootUID, rootGID); err != nil { + logrus.Errorf("error setting ownership of container stderr pipe: %v", err) + os.Exit(1) + } + // Make a note that our child (the parent subprocess) should + // have the pipes connected to its stdio, and that we should + // close its ends of them once it's running. + stdin = stdinRead + stdout = stdoutWrite + stderr = stderrWrite + closeOnceRunning = append(closeOnceRunning, stdinRead, stdoutWrite, stderrWrite) + stdinCopy = stdinWrite + defer stdoutRead.Close() + defer stderrRead.Close() + } + for readFd, writeFd := range relays { + if err := unix.SetNonblock(readFd, true); err != nil { + logrus.Errorf("error setting descriptor %d (%s) non-blocking: %v", readFd, fdDesc[readFd], err) + return + } + if err := unix.SetNonblock(writeFd, false); err != nil { + logrus.Errorf("error setting descriptor %d (%s) blocking: %v", relays[writeFd], fdDesc[writeFd], err) + return + } + } + if err := unix.SetNonblock(relays[unix.Stdin], true); err != nil { + logrus.Errorf("error setting %d to nonblocking: %v", relays[unix.Stdin], err) + } + go func() { + buffers := make(map[int]*bytes.Buffer) + for _, writeFd := range relays { + buffers[writeFd] = new(bytes.Buffer) + } + pollTimeout := -1 + stdinClose := false + for len(relays) > 0 { + fds := make([]unix.PollFd, 0, len(relays)) + for fd := range relays { + fds = append(fds, unix.PollFd{Fd: int32(fd), Events: unix.POLLIN | unix.POLLHUP}) + } + _, err := unix.Poll(fds, pollTimeout) + if !util.LogIfNotRetryable(err, fmt.Sprintf("poll: %v", err)) { + return + } + removeFds := make(map[int]struct{}) + for _, rfd := range fds { + if rfd.Revents&unix.POLLHUP == unix.POLLHUP { + removeFds[int(rfd.Fd)] = struct{}{} + } + if rfd.Revents&unix.POLLNVAL == unix.POLLNVAL { + logrus.Debugf("error polling descriptor %s: closed?", fdDesc[int(rfd.Fd)]) + removeFds[int(rfd.Fd)] = struct{}{} + } + if rfd.Revents&unix.POLLIN == 0 { + if stdinClose && stdinCopy == nil { + continue + } + continue + } + b := make([]byte, 8192) + nread, err := unix.Read(int(rfd.Fd), b) + util.LogIfNotRetryable(err, fmt.Sprintf("read %s: %v", fdDesc[int(rfd.Fd)], err)) + if nread > 0 { + if wfd, ok := relays[int(rfd.Fd)]; ok { + nwritten, err := buffers[wfd].Write(b[:nread]) + if err != nil { + logrus.Debugf("buffer: %v", err) + continue + } + if nwritten != nread { + logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", nread, nwritten) + continue + } + } + // If this is the last of the data we'll be able to read + // from this descriptor, read as much as there is to read. + for rfd.Revents&unix.POLLHUP == unix.POLLHUP { + nr, err := unix.Read(int(rfd.Fd), b) + util.LogIfUnexpectedWhileDraining(err, fmt.Sprintf("read %s: %v", fdDesc[int(rfd.Fd)], err)) + if nr <= 0 { + break + } + if wfd, ok := relays[int(rfd.Fd)]; ok { + nwritten, err := buffers[wfd].Write(b[:nr]) + if err != nil { + logrus.Debugf("buffer: %v", err) + break + } + if nwritten != nr { + logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", nr, nwritten) + break + } + } + } + } + if nread == 0 { + removeFds[int(rfd.Fd)] = struct{}{} + } + } + pollTimeout = -1 + for wfd, buffer := range buffers { + if buffer.Len() > 0 { + nwritten, err := unix.Write(wfd, buffer.Bytes()) + util.LogIfNotRetryable(err, fmt.Sprintf("write %s: %v", fdDesc[wfd], err)) + if nwritten >= 0 { + _ = buffer.Next(nwritten) + } + } + if buffer.Len() > 0 { + pollTimeout = 100 + } + if wfd == relays[unix.Stdin] && stdinClose && buffer.Len() == 0 { + stdinCopy.Close() + delete(relays, unix.Stdin) + } + } + for rfd := range removeFds { + if rfd == unix.Stdin { + buffer, found := buffers[relays[unix.Stdin]] + if found && buffer.Len() > 0 { + stdinClose = true + continue + } + } + if !options.Spec.Process.Terminal && rfd == unix.Stdin { + stdinCopy.Close() + } + delete(relays, rfd) + } + } + }() + + // Set up mounts and namespaces, and run the parent subprocess. + status, err := runUsingChroot(options.Spec, options.BundlePath, ctty, stdin, stdout, stderr, noPivot, closeOnceRunning) + if err != nil { + fmt.Fprintf(os.Stderr, "error running subprocess: %v\n", err) + os.Exit(1) + } + + // Pass the process's exit status back to the caller by exiting with the same status. + if status.Exited() { + if status.ExitStatus() != 0 { + fmt.Fprintf(os.Stderr, "subprocess exited with status %d\n", status.ExitStatus()) + } + os.Exit(status.ExitStatus()) + } else if status.Signaled() { + fmt.Fprintf(os.Stderr, "subprocess exited on %s\n", status.Signal()) + os.Exit(1) + } +} + +// runUsingChroot, still in the grandparent process, sets up various bind +// mounts and then runs the parent process in its own user namespace with the +// necessary ID mappings. +func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io.Reader, stdout, stderr io.Writer, noPivot bool, closeOnceRunning []*os.File) (wstatus unix.WaitStatus, err error) { + var confwg sync.WaitGroup + + // Create a new mount namespace for ourselves and bind mount everything to a new location. + undoIntermediates, err := bind.SetupIntermediateMountNamespace(spec, bundlePath) + if err != nil { + return 1, err + } + defer func() { + if undoErr := undoIntermediates(); undoErr != nil { + logrus.Debugf("error cleaning up intermediate mount NS: %v", err) + } + }() + + // Bind mount in our filesystems. + undoChroots, err := setupChrootBindMounts(spec, bundlePath) + if err != nil { + return 1, err + } + defer func() { + if undoErr := undoChroots(); undoErr != nil { + logrus.Debugf("error cleaning up intermediate chroot bind mounts: %v", err) + } + }() + + // Create a pipe for passing configuration down to the next process. + preader, pwriter, err := os.Pipe() + if err != nil { + return 1, fmt.Errorf("creating configuration pipe: %w", err) + } + config, conferr := json.Marshal(runUsingChrootExecSubprocOptions{ + Spec: spec, + BundlePath: bundlePath, + NoPivot: noPivot, + }) + if conferr != nil { + fmt.Fprintf(os.Stderr, "error re-encoding configuration for %q\n", runUsingChrootExecCommand) + os.Exit(1) + } + + // Apologize for the namespace configuration that we're about to ignore. + logNamespaceDiagnostics(spec) + + // We need to lock the thread so that PR_SET_PDEATHSIG won't trigger if the current thread exits. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + // Start the parent subprocess. + cmd := unshare.Command(append([]string{runUsingChrootExecCommand}, spec.Process.Args...)...) + setPdeathsig(cmd.Cmd) + cmd.Stdin, cmd.Stdout, cmd.Stderr = stdin, stdout, stderr + cmd.Dir = "/" + cmd.Env = []string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())} + if _, ok := os.LookupEnv(containersConfEnv); ok { + cmd.Env = append(cmd.Env, containersConfEnv+"="+os.Getenv(containersConfEnv)) + } + if ctty != nil { + cmd.Setsid = true + cmd.Ctty = ctty + } + cmd.ExtraFiles = append([]*os.File{preader}, cmd.ExtraFiles...) + if err := setPlatformUnshareOptions(spec, cmd); err != nil { + return 1, fmt.Errorf("setting platform unshare options: %w", err) + } + interrupted := make(chan os.Signal, 100) + cmd.Hook = func(int) error { + for _, f := range closeOnceRunning { + f.Close() + } + signal.Notify(interrupted, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + go func() { + for receivedSignal := range interrupted { + if err := cmd.Process.Signal(receivedSignal); err != nil { + logrus.Infof("%v while attempting to forward %v to child process", err, receivedSignal) + } + } + }() + return nil + } + + logrus.Debugf("Running %#v in %#v", cmd.Cmd, cmd) + confwg.Add(1) + go func() { + _, conferr = io.Copy(pwriter, bytes.NewReader(config)) + pwriter.Close() + confwg.Done() + }() + err = cmd.Run() + confwg.Wait() + signal.Stop(interrupted) + close(interrupted) + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + if waitStatus, ok := exitError.ProcessState.Sys().(syscall.WaitStatus); ok { + if waitStatus.Exited() { + if waitStatus.ExitStatus() != 0 { + fmt.Fprintf(os.Stderr, "subprocess exited with status %d\n", waitStatus.ExitStatus()) + } + os.Exit(waitStatus.ExitStatus()) + } else if waitStatus.Signaled() { + fmt.Fprintf(os.Stderr, "subprocess exited on %s\n", waitStatus.Signal()) + os.Exit(1) + } + } + } + fmt.Fprintf(os.Stderr, "process exited with error: %v\n", err) + os.Exit(1) + } + + return 0, nil +} + +// main() for parent subprocess. Its main job is to try to make our +// environment look like the one described by the runtime configuration blob, +// and then launch the intended command as a child. +func runUsingChrootExecMain() { + args := os.Args[1:] + var options runUsingChrootExecSubprocOptions + var err error + + runtime.LockOSThread() + + // Set logging. + if level := os.Getenv("LOGLEVEL"); level != "" { + if ll, err := strconv.Atoi(level); err == nil { + logrus.SetLevel(logrus.Level(ll)) + } + os.Unsetenv("LOGLEVEL") + } + + // Unpack our configuration. + confPipe := os.NewFile(3, "confpipe") + if confPipe == nil { + fmt.Fprintf(os.Stderr, "error reading options pipe\n") + os.Exit(1) + } + defer confPipe.Close() + if err := json.NewDecoder(confPipe).Decode(&options); err != nil { + fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err) + os.Exit(1) + } + + // Set the hostname. We're already in a distinct UTS namespace and are admins in the user + // namespace which created it, so we shouldn't get a permissions error, but seccomp policy + // might deny our attempt to call sethostname() anyway, so log a debug message for that. + if options.Spec == nil || options.Spec.Process == nil { + fmt.Fprintf(os.Stderr, "invalid options spec passed in\n") + os.Exit(1) + } + + if options.Spec.Hostname != "" { + setContainerHostname(options.Spec.Hostname) + } + + // Try to chroot into the root. Do this before we potentially + // block the syscall via the seccomp profile. Allow the + // platform to override this - on FreeBSD, we use a simple + // jail to set the hostname in the container, and on Linux + // we attempt to pivot_root. + if err := createPlatformContainer(options); err != nil { + logrus.Debugf("createPlatformContainer: %v", err) + var oldst, newst unix.Stat_t + if err := unix.Stat(options.Spec.Root.Path, &oldst); err != nil { + fmt.Fprintf(os.Stderr, "error stat()ing intended root directory %q: %v\n", options.Spec.Root.Path, err) + os.Exit(1) + } + if err := unix.Chdir(options.Spec.Root.Path); err != nil { + fmt.Fprintf(os.Stderr, "error chdir()ing to intended root directory %q: %v\n", options.Spec.Root.Path, err) + os.Exit(1) + } + if err := unix.Chroot(options.Spec.Root.Path); err != nil { + fmt.Fprintf(os.Stderr, "error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err) + os.Exit(1) + } + if err := unix.Stat("/", &newst); err != nil { + fmt.Fprintf(os.Stderr, "error stat()ing current root directory: %v\n", err) + os.Exit(1) + } + if oldst.Dev != newst.Dev || oldst.Ino != newst.Ino { + fmt.Fprintf(os.Stderr, "unknown error chroot()ing into directory %q: %v\n", options.Spec.Root.Path, err) + os.Exit(1) + } + logrus.Debugf("chrooted into %q", options.Spec.Root.Path) + } + + // not doing because it's still shared: creating devices + // not doing because it's not applicable: setting annotations + // not doing because it's still shared: setting sysctl settings + // not doing because cgroupfs is read only: configuring control groups + // -> this means we can use the freezer to make sure there aren't any lingering processes + // -> this means we ignore cgroups-based controls + // not doing because we don't set any in the config: running hooks + // not doing because we don't set it in the config: setting rootfs read-only + // not doing because we don't set it in the config: setting rootfs propagation + logrus.Debugf("setting apparmor profile") + if err = setApparmorProfile(options.Spec); err != nil { + fmt.Fprintf(os.Stderr, "error setting apparmor profile for process: %v\n", err) + os.Exit(1) + } + if err = setSelinuxLabel(options.Spec); err != nil { + fmt.Fprintf(os.Stderr, "error setting SELinux label for process: %v\n", err) + os.Exit(1) + } + + logrus.Debugf("setting resource limits") + if err = setRlimits(options.Spec, false, false); err != nil { + fmt.Fprintf(os.Stderr, "error setting process resource limits for process: %v\n", err) + os.Exit(1) + } + + // Try to change to the directory. + cwd := options.Spec.Process.Cwd + if !filepath.IsAbs(cwd) { + cwd = "/" + cwd + } + cwd = filepath.Clean(cwd) + if err := unix.Chdir("/"); err != nil { + fmt.Fprintf(os.Stderr, "error chdir()ing into new root directory %q: %v\n", options.Spec.Root.Path, err) + os.Exit(1) + } + if err := unix.Chdir(cwd); err != nil { + fmt.Fprintf(os.Stderr, "error chdir()ing into directory %q under root %q: %v\n", cwd, options.Spec.Root.Path, err) + os.Exit(1) + } + logrus.Debugf("changed working directory to %q", cwd) + + // Drop privileges. + user := options.Spec.Process.User + if len(user.AdditionalGids) > 0 { + gids := make([]int, len(user.AdditionalGids)) + for i := range user.AdditionalGids { + gids[i] = int(user.AdditionalGids[i]) + } + logrus.Debugf("setting supplemental groups") + if err = syscall.Setgroups(gids); err != nil { + fmt.Fprintf(os.Stderr, "error setting supplemental groups list: %v\n", err) + os.Exit(1) + } + } else { + setgroups, _ := os.ReadFile("/proc/self/setgroups") + if strings.Trim(string(setgroups), "\n") != "deny" { + logrus.Debugf("clearing supplemental groups") + if err = syscall.Setgroups([]int{}); err != nil { + fmt.Fprintf(os.Stderr, "error clearing supplemental groups list: %v\n", err) + os.Exit(1) + } + } + } + + logrus.Debugf("setting gid") + if err = unix.Setresgid(int(user.GID), int(user.GID), int(user.GID)); err != nil { + fmt.Fprintf(os.Stderr, "error setting GID: %v\n", err) + os.Exit(1) + } + + if err = setSeccomp(options.Spec); err != nil { + fmt.Fprintf(os.Stderr, "error setting seccomp filter for process: %v\n", err) + os.Exit(1) + } + + logrus.Debugf("setting capabilities") + var keepCaps []string + if user.UID != 0 { + keepCaps = []string{"CAP_SETUID"} + } + if err := setCapabilities(options.Spec, keepCaps...); err != nil { + fmt.Fprintf(os.Stderr, "error setting capabilities for process: %v\n", err) + os.Exit(1) + } + + logrus.Debugf("setting uid") + if err = unix.Setresuid(int(user.UID), int(user.UID), int(user.UID)); err != nil { + fmt.Fprintf(os.Stderr, "error setting UID: %v\n", err) + os.Exit(1) + } + + // Set $PATH to the value for the container, so that when args[0] is not an absolute path, + // exec.Command() can find it using exec.LookPath(). + for _, env := range slices.Backward(options.Spec.Process.Env) { + if val, ok := strings.CutPrefix(env, "PATH="); ok { + os.Setenv("PATH", val) + break + } + } + + // Actually run the specified command. + cmd := exec.Command(args[0], args[1:]...) + setPdeathsig(cmd) + cmd.Env = options.Spec.Process.Env + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + cmd.Dir = cwd + logrus.Debugf("Running %#v (PATH = %q)", cmd, os.Getenv("PATH")) + interrupted := make(chan os.Signal, 100) + if err = cmd.Start(); err != nil { + fmt.Fprintf(os.Stderr, "process failed to start with error: %v\n", err) + } + go func() { + for range interrupted { + if err := cmd.Process.Signal(syscall.SIGKILL); err != nil { + logrus.Infof("%v while attempting to send SIGKILL to child process", err) + } + } + }() + signal.Notify(interrupted, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + err = cmd.Wait() + signal.Stop(interrupted) + close(interrupted) + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + if waitStatus, ok := exitError.ProcessState.Sys().(syscall.WaitStatus); ok { + if waitStatus.Exited() { + if waitStatus.ExitStatus() != 0 { + fmt.Fprintf(os.Stderr, "subprocess exited with status %d\n", waitStatus.ExitStatus()) + } + os.Exit(waitStatus.ExitStatus()) + } else if waitStatus.Signaled() { + fmt.Fprintf(os.Stderr, "subprocess exited on %s\n", waitStatus.Signal()) + os.Exit(1) + } + } + } + fmt.Fprintf(os.Stderr, "process exited with error: %v\n", err) + os.Exit(1) + } +} + +// parses the resource limits for ourselves and any processes that +// we'll start into a format that's more in line with the kernel APIs +func parseRlimits(spec *specs.Spec) (map[int]unix.Rlimit, error) { + if spec.Process == nil { + return nil, nil + } + parsed := make(map[int]unix.Rlimit) + for _, limit := range spec.Process.Rlimits { + resource, recognized := rlimitsMap[strings.ToUpper(limit.Type)] + if !recognized { + return nil, fmt.Errorf("parsing limit type %q", limit.Type) + } + parsed[resource] = makeRlimit(limit) + } + return parsed, nil +} + +// setRlimits sets any resource limits that we want to apply to processes that +// we'll start. +func setRlimits(spec *specs.Spec, onlyLower, onlyRaise bool) error { + limits, err := parseRlimits(spec) + if err != nil { + return err + } + for resource, desired := range limits { + var current unix.Rlimit + if err := unix.Getrlimit(resource, ¤t); err != nil { + return fmt.Errorf("reading %q limit: %w", rlimitsReverseMap[resource], err) + } + if desired.Max > current.Max && onlyLower { + // this would raise a hard limit, and we're only here to lower them + continue + } + if desired.Max < current.Max && onlyRaise { + // this would lower a hard limit, and we're only here to raise them + continue + } + if err := unix.Setrlimit(resource, &desired); err != nil { + return fmt.Errorf("setting %q limit to soft=%d,hard=%d (was soft=%d,hard=%d): %w", rlimitsReverseMap[resource], desired.Cur, desired.Max, current.Cur, current.Max, err) + } + } + return nil +} + +func isDevNull(dev os.FileInfo) bool { + if dev.Mode()&os.ModeCharDevice != 0 { + stat, _ := dev.Sys().(*syscall.Stat_t) + nullStat := syscall.Stat_t{} + if err := syscall.Stat(os.DevNull, &nullStat); err != nil { + logrus.Warnf("unable to stat /dev/null: %v", err) + return false + } + if stat.Rdev == nullStat.Rdev { + return true + } + } + return false +} diff --git a/vendor/github.com/containers/buildah/chroot/run_freebsd.go b/vendor/github.com/containers/buildah/chroot/run_freebsd.go new file mode 100644 index 00000000..e8760544 --- /dev/null +++ b/vendor/github.com/containers/buildah/chroot/run_freebsd.go @@ -0,0 +1,272 @@ +//go:build freebsd + +package chroot + +import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + + "github.com/containers/buildah/pkg/jail" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/mount" + "go.podman.io/storage/pkg/unshare" + "golang.org/x/sys/unix" +) + +var ( + rlimitsMap = map[string]int{ + "RLIMIT_AS": unix.RLIMIT_AS, + "RLIMIT_CORE": unix.RLIMIT_CORE, + "RLIMIT_CPU": unix.RLIMIT_CPU, + "RLIMIT_DATA": unix.RLIMIT_DATA, + "RLIMIT_FSIZE": unix.RLIMIT_FSIZE, + "RLIMIT_MEMLOCK": unix.RLIMIT_MEMLOCK, + "RLIMIT_NOFILE": unix.RLIMIT_NOFILE, + "RLIMIT_NPROC": unix.RLIMIT_NPROC, + "RLIMIT_RSS": unix.RLIMIT_RSS, + "RLIMIT_STACK": unix.RLIMIT_STACK, + } + rlimitsReverseMap = map[int]string{} +) + +type runUsingChrootSubprocOptions struct { + Spec *specs.Spec + BundlePath string + NoPivot bool +} + +func setPlatformUnshareOptions(spec *specs.Spec, cmd *unshare.Cmd) error { + return nil +} + +func setContainerHostname(name string) { + // On FreeBSD, we have to set this later when we create the + // jail below in createPlatformContainer +} + +func setSelinuxLabel(spec *specs.Spec) error { + // Ignore this on FreeBSD + return nil +} + +func setApparmorProfile(spec *specs.Spec) error { + // FreeBSD doesn't have apparmor` + return nil +} + +func setCapabilities(spec *specs.Spec, keepCaps ...string) error { + // FreeBSD capabilities are nothing like Linux + return nil +} + +func makeRlimit(limit specs.POSIXRlimit) unix.Rlimit { + return unix.Rlimit{Cur: int64(limit.Soft), Max: int64(limit.Hard)} +} + +func createPlatformContainer(options runUsingChrootExecSubprocOptions) error { + path := options.Spec.Root.Path + jconf := jail.NewConfig() + jconf.Set("name", filepath.Base(path)+"-chroot") + jconf.Set("host.hostname", options.Spec.Hostname) + jconf.Set("persist", false) + jconf.Set("path", path) + jconf.Set("ip4", jail.INHERIT) + jconf.Set("ip6", jail.INHERIT) + jconf.Set("allow.raw_sockets", true) + jconf.Set("enforce_statfs", 1) + _, err := jail.CreateAndAttach(jconf) + if err != nil { + return fmt.Errorf("creating jail: %w", err) + } + return nil +} + +// logNamespaceDiagnostics knows which namespaces we want to create. +// Output debug messages when that differs from what we're being asked to do. +func logNamespaceDiagnostics(spec *specs.Spec) { + // Nothing here for FreeBSD +} + +func makeReadOnly(mntpoint string, flags uintptr) error { + var fs unix.Statfs_t + // Make sure it's read-only. + if err := unix.Statfs(mntpoint, &fs); err != nil { + return fmt.Errorf("checking if directory %q was bound read-only: %w", mntpoint, err) + } + return nil +} + +func saveDir(spec *specs.Spec, path string) string { + id := filepath.Base(spec.Root.Path) + return filepath.Join(filepath.Dir(path), ".save-"+id) +} + +func copyFile(source, dest string) error { + in, err := os.Open(source) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dest) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return out.Close() +} + +type rename struct { + from, to string +} + +// setupChrootBindMounts actually bind mounts things under the rootfs, and returns a +// callback that will clean up its work. +func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func() error, err error) { + renames := []rename{} + unmounts := []string{} + removes := []string{} + undoBinds = func() error { + for _, r := range renames { + if err2 := os.Rename(r.to, r.from); err2 != nil { + logrus.Warnf("pkg/chroot: error renaming %q to %q: %v", r.to, r.from, err2) + if err == nil { + err = err2 + } + } + } + for _, path := range unmounts { + if err2 := mount.Unmount(path); err2 != nil { + logrus.Warnf("pkg/chroot: error unmounting %q: %v", spec.Root.Path, err2) + if err == nil { + err = err2 + } + } + } + for _, path := range removes { + if err2 := os.Remove(path); err2 != nil { + logrus.Warnf("pkg/chroot: error removing %q: %v", path, err2) + if err == nil { + err = err2 + } + } + } + return err + } + + // Now mount all of those things to be under the rootfs's location in this + // mount namespace. + for _, m := range spec.Mounts { + // If the target is there, we can just mount it. + var srcinfo os.FileInfo + switch m.Type { + case "nullfs": + srcinfo, err = os.Stat(m.Source) + if err != nil { + return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", m.Source, err) + } + } + target := filepath.Join(spec.Root.Path, m.Destination) + if err := fileutils.Exists(target); err != nil { + // If the target can't be stat()ted, check the error. + if !errors.Is(err, fs.ErrNotExist) { + return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", target, err) + } + // The target isn't there yet, so create it, and make a + // note to remove it later. + // XXX: This was copied from the linux version which supports bind mounting files. + // Leaving it here since I plan to add this to FreeBSD's nullfs. + if m.Type != "nullfs" || srcinfo.IsDir() { + if err = os.MkdirAll(target, 0o111); err != nil { + return undoBinds, fmt.Errorf("creating mountpoint %q in mount namespace: %w", target, err) + } + removes = append(removes, target) + } else { + if err = os.MkdirAll(filepath.Dir(target), 0o111); err != nil { + return undoBinds, fmt.Errorf("ensuring parent of mountpoint %q (%q) is present in mount namespace: %w", target, filepath.Dir(target), err) + } + // Don't do this until we can support file mounts in nullfs + /*var file *os.File + if file, err = os.OpenFile(target, os.O_WRONLY|os.O_CREATE, 0); err != nil { + return undoBinds, errors.Wrapf(err, "error creating mountpoint %q in mount namespace", target) + } + file.Close() + removes = append(removes, target)*/ + } + } + logrus.Debugf("mount: %v", m) + switch m.Type { + case "nullfs": + // Do the bind mount. + if !srcinfo.IsDir() { + logrus.Debugf("emulating file mount %q on %q", m.Source, target) + err := fileutils.Exists(target) + if err == nil { + save := saveDir(spec, target) + if err := fileutils.Exists(save); err != nil { + if errors.Is(err, fs.ErrNotExist) { + err = os.MkdirAll(save, 0o111) + } + if err != nil { + return undoBinds, fmt.Errorf("creating file mount save directory %q: %w", save, err) + } + removes = append(removes, save) + } + savePath := filepath.Join(save, filepath.Base(target)) + if err := fileutils.Exists(target); err == nil { + logrus.Debugf("moving %q to %q", target, savePath) + if err := os.Rename(target, savePath); err != nil { + return undoBinds, fmt.Errorf("moving %q to %q: %w", target, savePath, err) + } + renames = append(renames, rename{ + from: target, + to: savePath, + }) + } + } else { + removes = append(removes, target) + } + if err := copyFile(m.Source, target); err != nil { + return undoBinds, fmt.Errorf("copying %q to %q: %w", m.Source, target, err) + } + } else { + logrus.Debugf("bind mounting %q on %q", m.Destination, filepath.Join(spec.Root.Path, m.Destination)) + if err := mount.Mount(m.Source, target, "nullfs", strings.Join(m.Options, ",")); err != nil { + return undoBinds, fmt.Errorf("bind mounting %q from host to %q in mount namespace (%q): %w", m.Source, m.Destination, target, err) + } + logrus.Debugf("bind mounted %q to %q", m.Source, target) + unmounts = append(unmounts, target) + } + case "devfs", "fdescfs", "tmpfs": + // Mount /dev, /dev/fd. + if err := mount.Mount(m.Source, target, m.Type, strings.Join(m.Options, ",")); err != nil { + return undoBinds, fmt.Errorf("mounting %q to %q in mount namespace (%q, %q): %w", m.Type, m.Destination, target, strings.Join(m.Options, ","), err) + } + logrus.Debugf("mounted a %q to %q", m.Type, target) + unmounts = append(unmounts, target) + } + } + return undoBinds, nil +} + +// setPdeathsig sets a parent-death signal for the process +func setPdeathsig(cmd *exec.Cmd) { + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL +} diff --git a/vendor/github.com/containers/buildah/chroot/run_linux.go b/vendor/github.com/containers/buildah/chroot/run_linux.go new file mode 100644 index 00000000..59c439d0 --- /dev/null +++ b/vendor/github.com/containers/buildah/chroot/run_linux.go @@ -0,0 +1,840 @@ +//go:build linux + +package chroot + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "slices" + "strings" + "syscall" + "time" + + "github.com/containers/buildah/copier" + "github.com/moby/sys/capability" + "github.com/opencontainers/runc/libcontainer/apparmor" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/mount" + "go.podman.io/storage/pkg/unshare" + "golang.org/x/sys/unix" +) + +var ( + rlimitsMap = map[string]int{ + "RLIMIT_AS": unix.RLIMIT_AS, + "RLIMIT_CORE": unix.RLIMIT_CORE, + "RLIMIT_CPU": unix.RLIMIT_CPU, + "RLIMIT_DATA": unix.RLIMIT_DATA, + "RLIMIT_FSIZE": unix.RLIMIT_FSIZE, + "RLIMIT_LOCKS": unix.RLIMIT_LOCKS, + "RLIMIT_MEMLOCK": unix.RLIMIT_MEMLOCK, + "RLIMIT_MSGQUEUE": unix.RLIMIT_MSGQUEUE, + "RLIMIT_NICE": unix.RLIMIT_NICE, + "RLIMIT_NOFILE": unix.RLIMIT_NOFILE, + "RLIMIT_NPROC": unix.RLIMIT_NPROC, + "RLIMIT_RSS": unix.RLIMIT_RSS, + "RLIMIT_RTPRIO": unix.RLIMIT_RTPRIO, + "RLIMIT_RTTIME": unix.RLIMIT_RTTIME, + "RLIMIT_SIGPENDING": unix.RLIMIT_SIGPENDING, + "RLIMIT_STACK": unix.RLIMIT_STACK, + } + rlimitsReverseMap = map[int]string{} + mountFlagMap = map[int]string{ + unix.MS_ACTIVE: "MS_ACTIVE", + unix.MS_BIND: "MS_BIND", + unix.MS_BORN: "MS_BORN", + unix.MS_DIRSYNC: "MS_DIRSYNC", + unix.MS_KERNMOUNT: "MS_KERNMOUNT", + unix.MS_LAZYTIME: "MS_LAZYTIME", + unix.MS_MANDLOCK: "MS_MANDLOCK", + unix.MS_MOVE: "MS_MOVE", + unix.MS_NOATIME: "MS_NOATIME", + unix.MS_NODEV: "MS_NODEV", + unix.MS_NODIRATIME: "MS_NODIRATIME", + unix.MS_NOEXEC: "MS_NOEXEC", + unix.MS_NOREMOTELOCK: "MS_NOREMOTELOCK", + unix.MS_NOSEC: "MS_NOSEC", + unix.MS_NOSUID: "MS_NOSUID", + unix.MS_NOSYMFOLLOW: "MS_NOSYMFOLLOW", + unix.MS_NOUSER: "MS_NOUSER", + unix.MS_POSIXACL: "MS_POSIXACL", + unix.MS_PRIVATE: "MS_PRIVATE", + unix.MS_RDONLY: "MS_RDONLY", + unix.MS_REC: "MS_REC", + unix.MS_RELATIME: "MS_RELATIME", + unix.MS_REMOUNT: "MS_REMOUNT", + unix.MS_SHARED: "MS_SHARED", + unix.MS_SILENT: "MS_SILENT", + unix.MS_SLAVE: "MS_SLAVE", + unix.MS_STRICTATIME: "MS_STRICTATIME", + unix.MS_SUBMOUNT: "MS_SUBMOUNT", + unix.MS_SYNCHRONOUS: "MS_SYNCHRONOUS", + unix.MS_UNBINDABLE: "MS_UNBINDABLE", + } + statFlagMap = map[int]string{ + unix.ST_MANDLOCK: "ST_MANDLOCK", + unix.ST_NOATIME: "ST_NOATIME", + unix.ST_NODEV: "ST_NODEV", + unix.ST_NODIRATIME: "ST_NODIRATIME", + unix.ST_NOEXEC: "ST_NOEXEC", + unix.ST_NOSUID: "ST_NOSUID", + unix.ST_RDONLY: "ST_RDONLY", + unix.ST_RELATIME: "ST_RELATIME", + unix.ST_SYNCHRONOUS: "ST_SYNCHRONOUS", + } +) + +func mountFlagNames(flags uintptr) []string { + var names []string + for flag, name := range mountFlagMap { + if int(flags)&flag == flag { + names = append(names, name) + flags = flags &^ (uintptr(flag)) + } + } + if flags != 0 { // got some unknown leftovers + names = append(names, fmt.Sprintf("%#x", flags)) + } + slices.Sort(names) + return names +} + +func statFlagNames(flags uintptr) []string { + var names []string + flags = flags & ^uintptr(0x20) // mask off ST_VALID + for flag, name := range statFlagMap { + if int(flags)&flag == flag { + names = append(names, name) + flags = flags &^ (uintptr(flag)) + } + } + if flags != 0 { // got some unknown leftovers + names = append(names, fmt.Sprintf("%#x", flags)) + } + slices.Sort(names) + return names +} + +type runUsingChrootSubprocOptions struct { + Spec *specs.Spec + BundlePath string + NoPivot bool + UIDMappings []syscall.SysProcIDMap + GIDMappings []syscall.SysProcIDMap +} + +func setPlatformUnshareOptions(spec *specs.Spec, cmd *unshare.Cmd) error { + // If we have configured ID mappings, set them here so that they can apply to the child. + hostUidmap, hostGidmap, err := unshare.GetHostIDMappings("") + if err != nil { + return err + } + uidmap, gidmap := spec.Linux.UIDMappings, spec.Linux.GIDMappings + if len(uidmap) == 0 { + // No UID mappings are configured for the container. Borrow our parent's mappings. + uidmap = slices.Clone(hostUidmap) + for i := range uidmap { + uidmap[i].HostID = uidmap[i].ContainerID + } + } + if len(gidmap) == 0 { + // No GID mappings are configured for the container. Borrow our parent's mappings. + gidmap = slices.Clone(hostGidmap) + for i := range gidmap { + gidmap[i].HostID = gidmap[i].ContainerID + } + } + + cmd.UnshareFlags = syscall.CLONE_NEWUTS | syscall.CLONE_NEWNS + requestedUserNS := false + for _, ns := range spec.Linux.Namespaces { + if ns.Type == specs.UserNamespace { + requestedUserNS = true + } + } + if len(spec.Linux.UIDMappings) > 0 || len(spec.Linux.GIDMappings) > 0 || requestedUserNS { + cmd.UnshareFlags = cmd.UnshareFlags | syscall.CLONE_NEWUSER + cmd.UidMappings = uidmap + cmd.GidMappings = gidmap + cmd.GidMappingsEnableSetgroups = true + } + cmd.OOMScoreAdj = spec.Process.OOMScoreAdj + return nil +} + +func setContainerHostname(name string) { + if err := unix.Sethostname([]byte(name)); err != nil { + logrus.Debugf("failed to set hostname %q for process: %v", name, err) + } +} + +// logNamespaceDiagnostics knows which namespaces we want to create. +// Output debug messages when that differs from what we're being asked to do. +func logNamespaceDiagnostics(spec *specs.Spec) { + sawMountNS := false + sawUTSNS := false + for _, ns := range spec.Linux.Namespaces { + switch ns.Type { + case specs.CgroupNamespace: + if ns.Path != "" { + logrus.Debugf("unable to join cgroup namespace, sorry about that") + } else { + logrus.Debugf("unable to create cgroup namespace, sorry about that") + } + case specs.IPCNamespace: + if ns.Path != "" { + logrus.Debugf("unable to join IPC namespace, sorry about that") + } else { + logrus.Debugf("unable to create IPC namespace, sorry about that") + } + case specs.MountNamespace: + if ns.Path != "" { + logrus.Debugf("unable to join mount namespace %q, creating a new one", ns.Path) + } + sawMountNS = true + case specs.NetworkNamespace: + if ns.Path != "" { + logrus.Debugf("unable to join network namespace, sorry about that") + } else { + logrus.Debugf("unable to create network namespace, sorry about that") + } + case specs.PIDNamespace: + if ns.Path != "" { + logrus.Debugf("unable to join PID namespace, sorry about that") + } else { + logrus.Debugf("unable to create PID namespace, sorry about that") + } + case specs.UserNamespace: + if ns.Path != "" { + logrus.Debugf("unable to join user namespace, sorry about that") + } + case specs.UTSNamespace: + if ns.Path != "" { + logrus.Debugf("unable to join UTS namespace %q, creating a new one", ns.Path) + } + sawUTSNS = true + } + } + if !sawMountNS { + logrus.Debugf("mount namespace not requested, but creating a new one anyway") + } + if !sawUTSNS { + logrus.Debugf("UTS namespace not requested, but creating a new one anyway") + } +} + +// setApparmorProfile sets the apparmor profile for ourselves, and hopefully any child processes that we'll start. +func setApparmorProfile(spec *specs.Spec) error { + if !apparmor.IsEnabled() || spec.Process.ApparmorProfile == "" { + return nil + } + if err := apparmor.ApplyProfile(spec.Process.ApparmorProfile); err != nil { + return fmt.Errorf("setting apparmor profile to %q: %w", spec.Process.ApparmorProfile, err) + } + return nil +} + +// setCapabilities sets capabilities for ourselves, to be more or less inherited by any processes that we'll start. +func setCapabilities(spec *specs.Spec, keepCaps ...string) error { + currentCaps, err := capability.NewPid2(0) + if err != nil { + return fmt.Errorf("reading capabilities of current process: %w", err) + } + if err := currentCaps.Load(); err != nil { + return fmt.Errorf("loading capabilities: %w", err) + } + caps, err := capability.NewPid2(0) + if err != nil { + return fmt.Errorf("reading capabilities of current process: %w", err) + } + capMap := map[capability.CapType][]string{ + capability.BOUNDING: spec.Process.Capabilities.Bounding, + capability.EFFECTIVE: spec.Process.Capabilities.Effective, + capability.INHERITABLE: {}, + capability.PERMITTED: spec.Process.Capabilities.Permitted, + capability.AMBIENT: {}, + } + knownCaps := capability.ListKnown() + noCap := capability.Cap(-1) + for capType, capList := range capMap { + for _, capSpec := range capList { + capToSet := noCap + for _, c := range knownCaps { + if strings.EqualFold("CAP_"+c.String(), capSpec) { + capToSet = c + break + } + } + if capToSet == noCap { + return fmt.Errorf("mapping capability %q to a number", capSpec) + } + caps.Set(capType, capToSet) + } + for _, capSpec := range keepCaps { + capToSet := noCap + for _, c := range knownCaps { + if strings.EqualFold("CAP_"+c.String(), capSpec) { + capToSet = c + break + } + } + if capToSet == noCap { + return fmt.Errorf("mapping capability %q to a number", capSpec) + } + if currentCaps.Get(capType, capToSet) { + caps.Set(capType, capToSet) + } + } + } + if err = caps.Apply(capability.CAPS | capability.BOUNDS | capability.AMBS); err != nil { + return fmt.Errorf("setting capabilities: %w", err) + } + return nil +} + +func makeRlimit(limit specs.POSIXRlimit) unix.Rlimit { + return unix.Rlimit{Cur: limit.Soft, Max: limit.Hard} +} + +func createPlatformContainer(options runUsingChrootExecSubprocOptions) error { + if options.NoPivot { + return errors.New("not using pivot_root()") + } + // borrowing a technique from runc, who credit the LXC maintainers for this + // open descriptors for the old and new root directories so that we can use fchdir() + oldRootFd, err := unix.Open("/", unix.O_DIRECTORY, 0) + if err != nil { + return fmt.Errorf("opening host root directory: %w", err) + } + defer func() { + if err := unix.Close(oldRootFd); err != nil { + logrus.Warnf("closing host root directory: %v", err) + } + }() + newRootFd, err := unix.Open(options.Spec.Root.Path, unix.O_DIRECTORY, 0) + if err != nil { + return fmt.Errorf("opening container root directory: %w", err) + } + defer func() { + if err := unix.Close(newRootFd); err != nil { + logrus.Warnf("closing container root directory: %v", err) + } + }() + // change to the new root directory + if err := unix.Fchdir(newRootFd); err != nil { + return fmt.Errorf("changing to container root directory: %w", err) + } + // this makes the current directory the root directory. not actually + // sure what happens to the other one + if err := unix.PivotRoot(".", "."); err != nil { + return fmt.Errorf("pivot_root: %w", err) + } + // go back and clean up the old one + if err := unix.Fchdir(oldRootFd); err != nil { + return fmt.Errorf("changing to host root directory: %w", err) + } + // make sure we only unmount things under this tree + if err := unix.Mount(".", ".", "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil { + return fmt.Errorf("tweaking mount flags on host root directory before unmounting from mount namespace: %w", err) + } + // detach this (unnamed?) old directory + if err := unix.Unmount(".", unix.MNT_DETACH); err != nil { + return fmt.Errorf("unmounting host root directory in mount namespace: %w", err) + } + // go back to a named root directory + if err := unix.Fchdir(newRootFd); err != nil { + return fmt.Errorf("changing to container root directory at last: %w", err) + } + logrus.Debugf("pivot_root()ed into %q", options.Spec.Root.Path) + return nil +} + +func mountFlagsForFSFlags(fsFlags uintptr) uintptr { + var mountFlags uintptr + for _, mapping := range []struct { + fsFlag uintptr + mountFlag uintptr + }{ + {unix.ST_MANDLOCK, unix.MS_MANDLOCK}, + {unix.ST_NOATIME, unix.MS_NOATIME}, + {unix.ST_NODEV, unix.MS_NODEV}, + {unix.ST_NODIRATIME, unix.MS_NODIRATIME}, + {unix.ST_NOEXEC, unix.MS_NOEXEC}, + {unix.ST_NOSUID, unix.MS_NOSUID}, + {unix.ST_RDONLY, unix.MS_RDONLY}, + {unix.ST_RELATIME, unix.MS_RELATIME}, + {unix.ST_SYNCHRONOUS, unix.MS_SYNCHRONOUS}, + } { + if fsFlags&mapping.fsFlag == mapping.fsFlag { + mountFlags |= mapping.mountFlag + } + } + return mountFlags +} + +func makeReadOnly(mntpoint string, flags uintptr) error { + var fs unix.Statfs_t + // Make sure it's read-only. + if err := unix.Statfs(mntpoint, &fs); err != nil { + return fmt.Errorf("checking if directory %q was bound read-only: %w", mntpoint, err) + } + if fs.Flags&unix.ST_RDONLY == 0 { + // All callers currently pass MS_RDONLY in "flags", but in case they stop doing + // that at some point in the future... + if err := unix.Mount(mntpoint, mntpoint, "bind", flags|unix.MS_RDONLY|unix.MS_REMOUNT|unix.MS_BIND, ""); err != nil { + return fmt.Errorf("remounting %s in mount namespace read-only: %w", mntpoint, err) + } + } + return nil +} + +// setupChrootBindMounts actually bind mounts things under the rootfs, and returns a +// callback that will clean up its work. +func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func() error, err error) { + var fs unix.Statfs_t + undoBinds = func() error { + if err2 := unix.Unmount(spec.Root.Path, unix.MNT_DETACH); err2 != nil { + retries := 0 + for (err2 == unix.EBUSY || err2 == unix.EAGAIN) && retries < 50 { + time.Sleep(50 * time.Millisecond) + err2 = unix.Unmount(spec.Root.Path, unix.MNT_DETACH) + retries++ + } + if err2 != nil { + logrus.Warnf("pkg/chroot: error unmounting %q (retried %d times): %v", spec.Root.Path, retries, err2) + if err == nil { + err = err2 + } + } + } + return err + } + + // Now bind mount all of those things to be under the rootfs's location in this + // mount namespace. + commonFlags := uintptr(unix.MS_BIND | unix.MS_REC | unix.MS_PRIVATE) + bindFlags := commonFlags + devFlags := commonFlags | unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_RDONLY + procFlags := devFlags | unix.MS_NODEV + sysFlags := devFlags | unix.MS_NODEV + + // Bind /dev read-only. + subDev := filepath.Join(spec.Root.Path, "/dev") + if err := unix.Mount("/dev", subDev, "bind", devFlags, ""); err != nil { + if errors.Is(err, os.ErrNotExist) { + err = os.Mkdir(subDev, 0o755) + if err == nil { + err = unix.Mount("/dev", subDev, "bind", devFlags, "") + } + } + if err != nil { + return undoBinds, fmt.Errorf("bind mounting /dev from host into mount namespace: %w", err) + } + } + // Make sure it's read-only. + if err = unix.Statfs(subDev, &fs); err != nil { + return undoBinds, fmt.Errorf("checking if directory %q was bound read-only: %w", subDev, err) + } + if fs.Flags&unix.ST_RDONLY == 0 { + if err := unix.Mount(subDev, subDev, "bind", devFlags|unix.MS_REMOUNT|unix.MS_BIND, ""); err != nil { + return undoBinds, fmt.Errorf("remounting /dev in mount namespace read-only: %w", err) + } + } + logrus.Debugf("bind mounted %q to %q", "/dev", filepath.Join(spec.Root.Path, "/dev")) + + // Bind /proc read-only. + subProc := filepath.Join(spec.Root.Path, "/proc") + if err := unix.Mount("/proc", subProc, "bind", procFlags, ""); err != nil { + if errors.Is(err, os.ErrNotExist) { + err = os.Mkdir(subProc, 0o755) + if err == nil { + err = unix.Mount("/proc", subProc, "bind", procFlags, "") + } + } + if err != nil { + return undoBinds, fmt.Errorf("bind mounting /proc from host into mount namespace: %w", err) + } + } + logrus.Debugf("bind mounted %q to %q", "/proc", filepath.Join(spec.Root.Path, "/proc")) + + // Bind /sys read-only. + subSys := filepath.Join(spec.Root.Path, "/sys") + if err := unix.Mount("/sys", subSys, "bind", sysFlags, ""); err != nil { + if errors.Is(err, os.ErrNotExist) { + err = os.Mkdir(subSys, 0o755) + if err == nil { + err = unix.Mount("/sys", subSys, "bind", sysFlags, "") + } + } + if err != nil { + return undoBinds, fmt.Errorf("bind mounting /sys from host into mount namespace: %w", err) + } + } + if err := makeReadOnly(subSys, sysFlags); err != nil { + return undoBinds, err + } + + mnts, _ := mount.GetMounts() + for _, m := range mnts { + if !strings.HasPrefix(m.Mountpoint, "/sys/") && + m.Mountpoint != "/sys" { + continue + } + subSys := filepath.Join(spec.Root.Path, m.Mountpoint) + if err := unix.Mount(m.Mountpoint, subSys, "bind", sysFlags, ""); err != nil { + msg := fmt.Sprintf("could not bind mount %q, skipping: %v", m.Mountpoint, err) + if strings.HasPrefix(m.Mountpoint, "/sys") { + logrus.Info(msg) + } else { + logrus.Warning(msg) + } + continue + } + if err := makeReadOnly(subSys, sysFlags); err != nil { + return undoBinds, err + } + } + logrus.Debugf("bind mounted %q to %q", "/sys", filepath.Join(spec.Root.Path, "/sys")) + + // Bind, overlay, or tmpfs mount everything we've been asked to mount. + for _, m := range spec.Mounts { + // Skip anything that we just mounted. + switch m.Destination { + case "/dev", "/proc", "/sys": + logrus.Debugf("already bind mounted %q on %q", m.Destination, filepath.Join(spec.Root.Path, m.Destination)) + continue + default: + if strings.HasPrefix(m.Destination, "/dev/") { + continue + } + if strings.HasPrefix(m.Destination, "/proc/") { + continue + } + if strings.HasPrefix(m.Destination, "/sys/") { + continue + } + } + // Skip anything that isn't a bind or overlay or tmpfs mount. + if m.Type != "bind" && m.Type != "tmpfs" && m.Type != "overlay" { + logrus.Debugf("skipping mount of type %q on %q", m.Type, m.Destination) + continue + } + // If the target is already there, we can just mount over it. + var srcinfo os.FileInfo + switch m.Type { + case "bind": + srcinfo, err = os.Stat(m.Source) + if err != nil { + return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", m.Source, err) + } + case "overlay", "tmpfs": + srcinfo, err = os.Stat("/") + if err != nil { + return undoBinds, fmt.Errorf("examining / to use as a template for a %s mount: %w", m.Type, err) + } + } + target := filepath.Join(spec.Root.Path, m.Destination) + // Check if target is a symlink. + stat, err := os.Lstat(target) + // If target is a symlink, follow the link and ensure the destination exists. + if err == nil && stat != nil && (stat.Mode()&os.ModeSymlink != 0) { + target, err = copier.Eval(spec.Root.Path, m.Destination, copier.EvalOptions{}) + if err != nil { + return nil, fmt.Errorf("evaluating symlink %q: %w", target, err) + } + // Stat the destination of the evaluated symlink. + _, err = os.Stat(target) + } + if err != nil { + // If the target can't be stat()ted, check the error. + if !errors.Is(err, os.ErrNotExist) { + return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", target, err) + } + // The target isn't there yet, so create it. If the source is a directory, + // we need a directory, otherwise we need a non-directory (i.e., a file). + if srcinfo.IsDir() { + if err = os.MkdirAll(target, 0o755); err != nil { + return undoBinds, fmt.Errorf("creating mountpoint %q in mount namespace: %w", target, err) + } + } else { + if err = os.MkdirAll(filepath.Dir(target), 0o755); err != nil { + return undoBinds, fmt.Errorf("ensuring parent of mountpoint %q (%q) is present in mount namespace: %w", target, filepath.Dir(target), err) + } + var file *os.File + if file, err = os.OpenFile(target, os.O_WRONLY|os.O_CREATE, 0o755); err != nil { + return undoBinds, fmt.Errorf("creating mountpoint %q in mount namespace: %w", target, err) + } + file.Close() + } + } + // Sort out which flags we're asking for, and what statfs() should be telling us + // if we successfully mounted with them. + requestFlags := uintptr(0) + expectedImportantFlags := uintptr(0) + importantFlags := uintptr(0) + possibleImportantFlags := uintptr(unix.ST_NODEV | unix.ST_NOEXEC | unix.ST_NOSUID | unix.ST_RDONLY) + for _, option := range m.Options { + switch option { + case "nodev": + requestFlags |= unix.MS_NODEV + importantFlags |= unix.ST_NODEV + expectedImportantFlags |= unix.ST_NODEV + case "dev": + requestFlags &= ^uintptr(unix.MS_NODEV) + importantFlags |= unix.ST_NODEV + expectedImportantFlags &= ^uintptr(unix.ST_NODEV) + case "noexec": + requestFlags |= unix.MS_NOEXEC + importantFlags |= unix.ST_NOEXEC + expectedImportantFlags |= unix.ST_NOEXEC + case "exec": + requestFlags &= ^uintptr(unix.MS_NOEXEC) + importantFlags |= unix.ST_NOEXEC + expectedImportantFlags &= ^uintptr(unix.ST_NOEXEC) + case "nosuid": + requestFlags |= unix.MS_NOSUID + importantFlags |= unix.ST_NOSUID + expectedImportantFlags |= unix.ST_NOSUID + case "suid": + requestFlags &= ^uintptr(unix.MS_NOSUID) + importantFlags |= unix.ST_NOSUID + expectedImportantFlags &= ^uintptr(unix.ST_NOSUID) + case "ro": + requestFlags |= unix.MS_RDONLY + importantFlags |= unix.ST_RDONLY + expectedImportantFlags |= unix.ST_RDONLY + case "rw": + requestFlags &= ^uintptr(unix.MS_RDONLY) + importantFlags |= unix.ST_RDONLY + expectedImportantFlags &= ^uintptr(unix.ST_RDONLY) + } + } + switch m.Type { + case "bind": + // Do the initial bind mount. We'll worry about the flags in a bit. + logrus.Debugf("bind mounting %q on %q %v", m.Destination, filepath.Join(spec.Root.Path, m.Destination), m.Options) + if err = unix.Mount(m.Source, target, "", bindFlags|requestFlags, ""); err != nil { + return undoBinds, fmt.Errorf("bind mounting %q from host to %q in mount namespace (%q): %w", m.Source, m.Destination, target, err) + } + logrus.Debugf("bind mounted %q to %q", m.Source, target) + case "tmpfs": + // Mount a tmpfs. We'll worry about the flags in a bit. + if err = mount.Mount(m.Source, target, m.Type, strings.Join(append(m.Options, "private"), ",")); err != nil { + return undoBinds, fmt.Errorf("mounting tmpfs to %q in mount namespace (%q, %q): %w", m.Destination, target, strings.Join(append(m.Options, "private"), ","), err) + } + logrus.Debugf("mounted a tmpfs to %q", target) + case "overlay": + // Mount an overlay. We'll worry about the flags in a bit. + if err = mount.Mount(m.Source, target, m.Type, strings.Join(append(m.Options, "private"), ",")); err != nil { + return undoBinds, fmt.Errorf("mounting overlay to %q in mount namespace (%q, %q): %w", m.Destination, target, strings.Join(append(m.Options, "private"), ","), err) + } + logrus.Debugf("mounted a overlay to %q", target) + } + // Time to worry about the flags. + if err = unix.Statfs(target, &fs); err != nil { + return undoBinds, fmt.Errorf("checking if volume %q was mounted with requested flags: %w", target, err) + } + effectiveImportantFlags := uintptr(fs.Flags) & importantFlags + if effectiveImportantFlags != expectedImportantFlags { + // Do a remount to try to get the desired flags to stick. + effectiveUnimportantFlags := uintptr(fs.Flags) & ^possibleImportantFlags + remountFlags := unix.MS_REMOUNT | bindFlags | requestFlags | mountFlagsForFSFlags(effectiveUnimportantFlags) + // If we are requesting a read-only mount, add any possibleImportantFlags present in fs.Flags to remountFlags. + if requestFlags&unix.ST_RDONLY == unix.ST_RDONLY { + remountFlags |= uintptr(fs.Flags) & possibleImportantFlags + } + if err = unix.Mount(target, target, m.Type, remountFlags, ""); err != nil { + return undoBinds, fmt.Errorf("remounting %q in mount namespace with flags %v instead of %v: %w", target, mountFlagNames(requestFlags), statFlagNames(effectiveImportantFlags), err) + } + // Check if the desired flags stuck. + if err = unix.Statfs(target, &fs); err != nil { + return undoBinds, fmt.Errorf("checking if directory %q was remounted with requested flags %v instead of %v: %w", target, mountFlagNames(requestFlags), statFlagNames(effectiveImportantFlags), err) + } + newEffectiveImportantFlags := uintptr(fs.Flags) & importantFlags + if newEffectiveImportantFlags != expectedImportantFlags { + return undoBinds, fmt.Errorf("unable to remount %q with requested flags %v instead of %v, just got %v back", target, mountFlagNames(requestFlags), statFlagNames(effectiveImportantFlags), statFlagNames(newEffectiveImportantFlags)) + } + } + } + + // Set up any read-only paths that we need to. If we're running inside + // of a container, some of these locations will already be read-only, in + // which case can declare victory and move on. + for _, roPath := range spec.Linux.ReadonlyPaths { + r := filepath.Join(spec.Root.Path, roPath) + target, err := filepath.EvalSymlinks(r) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // No target, no problem. + continue + } + return undoBinds, fmt.Errorf("checking %q for symlinks before marking it read-only: %w", r, err) + } + // Check if the location is already read-only. + var fs unix.Statfs_t + if err = unix.Statfs(target, &fs); err != nil { + if errors.Is(err, os.ErrNotExist) { + // No target, no problem. + continue + } + return undoBinds, fmt.Errorf("checking if directory %q is already read-only: %w", target, err) + } + if fs.Flags&unix.ST_RDONLY == unix.ST_RDONLY { + continue + } + // Mount the location over itself, so that we can remount it as read-only, making + // sure to preserve any combination of nodev/noexec/nosuid that's already in play. + roFlags := mountFlagsForFSFlags(uintptr(fs.Flags)) | unix.MS_RDONLY + if err := unix.Mount(target, target, "", bindFlags|roFlags, ""); err != nil { + if errors.Is(err, os.ErrNotExist) { + // No target, no problem. + continue + } + return undoBinds, fmt.Errorf("bind mounting %q onto itself in preparation for making it read-only: %w", target, err) + } + // Remount the location read-only. + if err = unix.Statfs(target, &fs); err != nil { + return undoBinds, fmt.Errorf("checking if directory %q was bound read-only: %w", target, err) + } + if fs.Flags&unix.ST_RDONLY == 0 { + if err := unix.Mount(target, target, "", unix.MS_REMOUNT|unix.MS_RDONLY|bindFlags|mountFlagsForFSFlags(uintptr(fs.Flags)), ""); err != nil { + return undoBinds, fmt.Errorf("remounting %q in mount namespace read-only: %w", target, err) + } + } + // Check again. + if err = unix.Statfs(target, &fs); err != nil { + return undoBinds, fmt.Errorf("checking if directory %q was remounted read-only: %w", target, err) + } + if fs.Flags&unix.ST_RDONLY == 0 { + // Still not read only. + return undoBinds, fmt.Errorf("verifying that %q in mount namespace was remounted read-only: %w", target, err) + } + } + + // Create an empty directory for to use for masking directories. + roEmptyDir := filepath.Join(bundlePath, "empty") + if len(spec.Linux.MaskedPaths) > 0 { + if err := os.Mkdir(roEmptyDir, 0o700); err != nil { + return undoBinds, fmt.Errorf("creating empty directory %q: %w", roEmptyDir, err) + } + } + + // Set up any masked paths that we need to. If we're running inside of + // a container, some of these locations will already be read-only tmpfs + // filesystems or bind mounted to os.DevNull. If we're not running + // inside of a container, and nobody else has done that, we'll do it. + for _, masked := range spec.Linux.MaskedPaths { + t := filepath.Join(spec.Root.Path, masked) + target, err := filepath.EvalSymlinks(t) + if err != nil { + target = t + } + // Get some info about the target. + targetinfo, err := os.Stat(target) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // No target, no problem. + continue + } + return undoBinds, fmt.Errorf("examining %q for masking in mount namespace: %w", target, err) + } + if targetinfo.IsDir() { + // The target's a directory. Check if it's a read-only filesystem. + var statfs unix.Statfs_t + if err = unix.Statfs(target, &statfs); err != nil { + return undoBinds, fmt.Errorf("checking if directory %q is a mountpoint: %w", target, err) + } + isReadOnly := statfs.Flags&unix.ST_RDONLY == unix.ST_RDONLY + // Check if any of the IDs we're mapping could read it. + var stat unix.Stat_t + if err = unix.Stat(target, &stat); err != nil { + return undoBinds, fmt.Errorf("checking permissions on directory %q: %w", target, err) + } + isAccessible := false + if stat.Mode&unix.S_IROTH|unix.S_IXOTH != 0 { + isAccessible = true + } + if !isAccessible && stat.Mode&unix.S_IROTH|unix.S_IXOTH != 0 { + if len(spec.Linux.GIDMappings) > 0 { + for _, mapping := range spec.Linux.GIDMappings { + if stat.Gid >= mapping.ContainerID && stat.Gid < mapping.ContainerID+mapping.Size { + isAccessible = true + break + } + } + } + } + if !isAccessible && stat.Mode&unix.S_IRUSR|unix.S_IXUSR != 0 { + if len(spec.Linux.UIDMappings) > 0 { + for _, mapping := range spec.Linux.UIDMappings { + if stat.Uid >= mapping.ContainerID && stat.Uid < mapping.ContainerID+mapping.Size { + isAccessible = true + break + } + } + } + } + // Check if it's empty. + hasContent := false + directory, err := os.Open(target) + if err != nil { + if !os.IsPermission(err) { + return undoBinds, fmt.Errorf("opening directory %q: %w", target, err) + } + } else { + names, err := directory.Readdirnames(0) + directory.Close() + if err != nil { + return undoBinds, fmt.Errorf("reading contents of directory %q: %w", target, err) + } + hasContent = false + for _, name := range names { + switch name { + case ".", "..": + continue + default: + hasContent = true + } + if hasContent { + break + } + } + } + // The target's a directory, so read-only bind mount an empty directory on it. + roFlags := uintptr(syscall.MS_BIND | syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC | syscall.MS_RDONLY) + if !isReadOnly || (hasContent && isAccessible) { + if err = unix.Mount(roEmptyDir, target, "bind", roFlags, ""); err != nil { + return undoBinds, fmt.Errorf("masking directory %q in mount namespace: %w", target, err) + } + if err = unix.Statfs(target, &fs); err != nil { + return undoBinds, fmt.Errorf("checking if masked directory %q was mounted read-only in mount namespace: %w", target, err) + } + if fs.Flags&unix.ST_RDONLY == 0 { + if err = unix.Mount(target, target, "", syscall.MS_REMOUNT|roFlags|mountFlagsForFSFlags(uintptr(fs.Flags)), ""); err != nil { + return undoBinds, fmt.Errorf("making sure masked directory %q in mount namespace is read only: %w", target, err) + } + } + } + } else { + // If the target's is not a directory or os.DevNull, bind mount os.DevNull over it. + if !isDevNull(targetinfo) { + if err = unix.Mount(os.DevNull, target, "", uintptr(syscall.MS_BIND|syscall.MS_RDONLY|syscall.MS_PRIVATE), ""); err != nil { + return undoBinds, fmt.Errorf("masking non-directory %q in mount namespace: %w", target, err) + } + } + } + } + return undoBinds, nil +} + +// setPdeathsig sets a parent-death signal for the process +func setPdeathsig(cmd *exec.Cmd) { + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL +} diff --git a/vendor/github.com/containers/buildah/chroot/seccomp.go b/vendor/github.com/containers/buildah/chroot/seccomp.go new file mode 100644 index 00000000..350e28cf --- /dev/null +++ b/vendor/github.com/containers/buildah/chroot/seccomp.go @@ -0,0 +1,176 @@ +//go:build linux && seccomp + +package chroot + +import ( + "fmt" + + specs "github.com/opencontainers/runtime-spec/specs-go" + libseccomp "github.com/seccomp/libseccomp-golang" + "github.com/sirupsen/logrus" +) + +// setSeccomp sets the seccomp filter for ourselves and any processes that we'll start. +func setSeccomp(spec *specs.Spec) error { + logrus.Debugf("setting seccomp configuration") + if spec.Linux.Seccomp == nil { + return nil + } + mapAction := func(specAction specs.LinuxSeccompAction, errnoRet *uint) libseccomp.ScmpAction { + switch specAction { + case specs.ActKill: + return libseccomp.ActKillThread + case specs.ActTrap: + return libseccomp.ActTrap + case specs.ActErrno: + action := libseccomp.ActErrno + if errnoRet != nil { + action = action.SetReturnCode(int16(*errnoRet)) + } + return action + case specs.ActTrace: + return libseccomp.ActTrace + case specs.ActAllow: + return libseccomp.ActAllow + case specs.ActLog: + return libseccomp.ActLog + case specs.ActKillProcess: + return libseccomp.ActKillProcess + default: + logrus.Errorf("unmappable action %v", specAction) + } + return libseccomp.ActInvalid + } + mapArch := func(specArch specs.Arch) libseccomp.ScmpArch { + switch specArch { + case specs.ArchX86: + return libseccomp.ArchX86 + case specs.ArchX86_64: + return libseccomp.ArchAMD64 + case specs.ArchX32: + return libseccomp.ArchX32 + case specs.ArchARM: + return libseccomp.ArchARM + case specs.ArchAARCH64: + return libseccomp.ArchARM64 + case specs.ArchMIPS: + return libseccomp.ArchMIPS + case specs.ArchMIPS64: + return libseccomp.ArchMIPS64 + case specs.ArchMIPS64N32: + return libseccomp.ArchMIPS64N32 + case specs.ArchMIPSEL: + return libseccomp.ArchMIPSEL + case specs.ArchMIPSEL64: + return libseccomp.ArchMIPSEL64 + case specs.ArchMIPSEL64N32: + return libseccomp.ArchMIPSEL64N32 + case specs.ArchPPC: + return libseccomp.ArchPPC + case specs.ArchPPC64: + return libseccomp.ArchPPC64 + case specs.ArchPPC64LE: + return libseccomp.ArchPPC64LE + case specs.ArchS390: + return libseccomp.ArchS390 + case specs.ArchS390X: + return libseccomp.ArchS390X + case specs.ArchPARISC: + return libseccomp.ArchPARISC + case specs.ArchPARISC64: + return libseccomp.ArchPARISC64 + case specs.ArchRISCV64: + return libseccomp.ArchRISCV64 + default: + logrus.Errorf("unmappable arch %v", specArch) + } + return libseccomp.ArchInvalid + } + mapOp := func(op specs.LinuxSeccompOperator) libseccomp.ScmpCompareOp { + switch op { + case specs.OpNotEqual: + return libseccomp.CompareNotEqual + case specs.OpLessThan: + return libseccomp.CompareLess + case specs.OpLessEqual: + return libseccomp.CompareLessOrEqual + case specs.OpEqualTo: + return libseccomp.CompareEqual + case specs.OpGreaterEqual: + return libseccomp.CompareGreaterEqual + case specs.OpGreaterThan: + return libseccomp.CompareGreater + case specs.OpMaskedEqual: + return libseccomp.CompareMaskedEqual + default: + logrus.Errorf("unmappable op %v", op) + } + return libseccomp.CompareInvalid + } + + filter, err := libseccomp.NewFilter(mapAction(spec.Linux.Seccomp.DefaultAction, spec.Linux.Seccomp.DefaultErrnoRet)) + if err != nil { + return fmt.Errorf("creating seccomp filter with default action %q: %w", spec.Linux.Seccomp.DefaultAction, err) + } + for _, arch := range spec.Linux.Seccomp.Architectures { + if err = filter.AddArch(mapArch(arch)); err != nil { + return fmt.Errorf("adding architecture %q(%q) to seccomp filter: %w", arch, mapArch(arch), err) + } + } + for _, rule := range spec.Linux.Seccomp.Syscalls { + scnames := make(map[libseccomp.ScmpSyscall]string) + for _, name := range rule.Names { + scnum, err := libseccomp.GetSyscallFromName(name) + if err != nil { + logrus.Debugf("error mapping syscall %q to a syscall, ignoring %q rule for %q", name, rule.Action, name) + continue + } + scnames[scnum] = name + } + for scnum := range scnames { + if len(rule.Args) == 0 { + if err = filter.AddRule(scnum, mapAction(rule.Action, rule.ErrnoRet)); err != nil { + return fmt.Errorf("adding a rule (%q:%q) to seccomp filter: %w", scnames[scnum], rule.Action, err) + } + continue + } + var conditions []libseccomp.ScmpCondition + opsAreAllEquality := true + for _, arg := range rule.Args { + condition, err := libseccomp.MakeCondition(arg.Index, mapOp(arg.Op), arg.Value, arg.ValueTwo) + if err != nil { + return fmt.Errorf("building a seccomp condition %d:%v:%d:%d: %w", arg.Index, arg.Op, arg.Value, arg.ValueTwo, err) + } + if arg.Op != specs.OpEqualTo { + opsAreAllEquality = false + } + conditions = append(conditions, condition) + } + if err = filter.AddRuleConditional(scnum, mapAction(rule.Action, rule.ErrnoRet), conditions); err != nil { + // Okay, if the rules specify multiple equality + // checks, assume someone thought that they + // were OR'd, when in fact they're ordinarily + // supposed to be AND'd. Break them up into + // different rules to get that OR effect. + if len(rule.Args) > 1 && opsAreAllEquality && err.Error() == "two checks on same syscall argument" { + for i := range conditions { + if err = filter.AddRuleConditional(scnum, mapAction(rule.Action, rule.ErrnoRet), conditions[i:i+1]); err != nil { + return fmt.Errorf("adding a conditional rule (%q:%q[%d]) to seccomp filter: %w", scnames[scnum], rule.Action, i, err) + } + } + } else { + return fmt.Errorf("adding a conditional rule (%q:%q) to seccomp filter: %w", scnames[scnum], rule.Action, err) + } + } + } + } + if err = filter.SetNoNewPrivsBit(spec.Process.NoNewPrivileges); err != nil { + return fmt.Errorf("setting no-new-privileges bit to %v: %w", spec.Process.NoNewPrivileges, err) + } + err = filter.Load() + filter.Release() + if err != nil { + return fmt.Errorf("activating seccomp filter: %w", err) + } + return nil +} diff --git a/vendor/github.com/containers/buildah/chroot/seccomp_freebsd.go b/vendor/github.com/containers/buildah/chroot/seccomp_freebsd.go new file mode 100644 index 00000000..134a5c4a --- /dev/null +++ b/vendor/github.com/containers/buildah/chroot/seccomp_freebsd.go @@ -0,0 +1,14 @@ +//go:build freebsd && seccomp + +package chroot + +import ( + "github.com/opencontainers/runtime-spec/specs-go" +) + +const seccompAvailable = false + +func setSeccomp(spec *specs.Spec) error { + // Ignore this on FreeBSD + return nil +} diff --git a/vendor/github.com/containers/buildah/chroot/seccomp_unsupported.go b/vendor/github.com/containers/buildah/chroot/seccomp_unsupported.go new file mode 100644 index 00000000..1d0387bc --- /dev/null +++ b/vendor/github.com/containers/buildah/chroot/seccomp_unsupported.go @@ -0,0 +1,16 @@ +//go:build (!linux && !freebsd) || !seccomp + +package chroot + +import ( + "errors" + + "github.com/opencontainers/runtime-spec/specs-go" +) + +func setSeccomp(spec *specs.Spec) error { + if spec.Linux.Seccomp != nil { + return errors.New("configured a seccomp filter without seccomp support?") + } + return nil +} diff --git a/vendor/github.com/containers/buildah/chroot/selinux.go b/vendor/github.com/containers/buildah/chroot/selinux.go new file mode 100644 index 00000000..dd253cd4 --- /dev/null +++ b/vendor/github.com/containers/buildah/chroot/selinux.go @@ -0,0 +1,22 @@ +//go:build linux + +package chroot + +import ( + "fmt" + + "github.com/opencontainers/runtime-spec/specs-go" + selinux "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" +) + +// setSelinuxLabel sets the process label for child processes that we'll start. +func setSelinuxLabel(spec *specs.Spec) error { + logrus.Debugf("setting selinux label") + if spec.Process.SelinuxLabel != "" && selinux.GetEnabled() { + if err := selinux.SetExecLabel(spec.Process.SelinuxLabel); err != nil { + return fmt.Errorf("setting process label to %q: %w", spec.Process.SelinuxLabel, err) + } + } + return nil +} diff --git a/vendor/github.com/containers/buildah/chroot/selinux_unsupported.go b/vendor/github.com/containers/buildah/chroot/selinux_unsupported.go new file mode 100644 index 00000000..ae9ac896 --- /dev/null +++ b/vendor/github.com/containers/buildah/chroot/selinux_unsupported.go @@ -0,0 +1,19 @@ +//go:build !linux && !freebsd + +package chroot + +import ( + "errors" + + "github.com/opencontainers/runtime-spec/specs-go" +) + +func setSelinuxLabel(spec *specs.Spec) error { + if spec.Linux.MountLabel != "" { + return errors.New("configured an SELinux mount label without SELinux support?") + } + if spec.Process.SelinuxLabel != "" { + return errors.New("configured an SELinux process label without SELinux support?") + } + return nil +} diff --git a/vendor/github.com/containers/buildah/chroot/unsupported.go b/vendor/github.com/containers/buildah/chroot/unsupported.go new file mode 100644 index 00000000..a2e7f45b --- /dev/null +++ b/vendor/github.com/containers/buildah/chroot/unsupported.go @@ -0,0 +1,15 @@ +//go:build !linux && !freebsd + +package chroot + +import ( + "fmt" + "io" + + "github.com/opencontainers/runtime-spec/specs-go" +) + +// RunUsingChroot is not supported. +func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reader, stdout, stderr io.Writer) (err error) { + return fmt.Errorf("--isolation chroot is not supported on this platform") +} diff --git a/vendor/github.com/containers/buildah/commit.go b/vendor/github.com/containers/buildah/commit.go new file mode 100644 index 00000000..1ae6c012 --- /dev/null +++ b/vendor/github.com/containers/buildah/commit.go @@ -0,0 +1,561 @@ +package buildah + +import ( + "context" + "encoding/json" + "fmt" + "io" + "maps" + "os" + "strings" + "time" + + "github.com/containers/buildah/pkg/blobcache" + "github.com/containers/buildah/util" + encconfig "github.com/containers/ocicrypt/config" + digest "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + "go.podman.io/common/libimage/manifests" + "go.podman.io/image/v5/docker" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/manifest" + "go.podman.io/image/v5/signature" + is "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/transports" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/stringid" +) + +const ( + // BuilderIdentityAnnotation is the name of the label which will be set + // to contain the name and version of the producer of the image at + // commit-time. (N.B. yes, the constant's name includes "Annotation", + // but it's added as a label.) + BuilderIdentityAnnotation = "io.buildah.version" +) + +// CommitOptions can be used to alter how an image is committed. +type CommitOptions struct { + // PreferredManifestType is the preferred type of image manifest. The + // image configuration format will be of a compatible type. + PreferredManifestType string + // Compression specifies the type of compression which is applied to + // layer blobs. The default is to not use compression, but + // archive.Gzip is recommended. + Compression archive.Compression + // SignaturePolicyPath specifies an override location for the signature + // policy which should be used for verifying the new image as it is + // being written. Except in specific circumstances, no value should be + // specified, indicating that the shared, system-wide default policy + // should be used. + SignaturePolicyPath string + // AdditionalTags is a list of additional names to add to the image, if + // the transport to which we're writing the image gives us a way to add + // them. + AdditionalTags []string + // ReportWriter is an io.Writer which will be used to log the writing + // of the new image. + ReportWriter io.Writer + // HistoryTimestamp specifies a timestamp to use for the image's + // created-on date, the corresponding field in new history entries, and + // the timestamps to set on contents in new layer diffs. If left + // unset, the current time is used for the configuration and manifest, + // and timestamps of layer contents are used as-is. + HistoryTimestamp *time.Time + // SourceDateEpoch specifies a timestamp to use for the image's + // created-on date and the corresponding field in new history entries. + // If left unset, the current time is used for the configuration and + // manifest. + SourceDateEpoch *time.Time + // RewriteTimestamp, if set, forces timestamps in generated layers to + // not be later than the SourceDateEpoch, if it is set. + RewriteTimestamp bool + // github.com/containers/image/types SystemContext to hold credentials + // and other authentication/authorization information. + SystemContext *types.SystemContext + // IIDFile tells the builder to write the image ID to the specified file + IIDFile string + // Squash tells the builder to produce an image with a single layer + // instead of with possibly more than one layer. + Squash bool + // OmitHistory tells the builder to ignore the history of build layers and + // base while preparing image-spec, setting this to true will ensure no history + // is added to the image-spec. (default false) + OmitHistory bool + // BlobDirectory is the name of a directory in which we'll look for + // prebuilt copies of layer blobs that we might otherwise need to + // regenerate from on-disk layers. If blobs are available, the + // manifest of the new image will reference the blobs rather than + // on-disk layers. + BlobDirectory string + // EmptyLayer tells the builder to omit the diff for the working + // container. + EmptyLayer bool + // OmitLayerHistoryEntry tells the builder to omit the diff for the + // working container and to not add an entry in the commit history. By + // default, the rest of the image's history is preserved, subject to + // the OmitHistory setting. N.B.: setting this flag, without any + // PrependedEmptyLayers, AppendedEmptyLayers, PrependedLinkedLayers, or + // AppendedLinkedLayers will more or less produce a copy of the base + // image. + OmitLayerHistoryEntry bool + // OmitTimestamp forces epoch 0 as created timestamp to allow for + // deterministic, content-addressable builds. + // Deprecated: use HistoryTimestamp or SourceDateEpoch (possibly with + // RewriteTimestamp) instead. + OmitTimestamp bool + // SignBy is the fingerprint of a GPG key to use for signing the image. + SignBy string + // Manifest list to add the image to. + Manifest string + // MaxRetries is the maximum number of attempts we'll make to commit + // the image to an external registry if the first attempt fails. + MaxRetries int + // RetryDelay is how long to wait before retrying a commit attempt to a + // registry. + RetryDelay time.Duration + // OciEncryptConfig when non-nil indicates that an image should be encrypted. + // The encryption options is derived from the construction of EncryptConfig object. + OciEncryptConfig *encconfig.EncryptConfig + // OciEncryptLayers represents the list of layers to encrypt. + // If nil, don't encrypt any layers. + // If non-nil and len==0, denotes encrypt all layers. + // integers in the slice represent 0-indexed layer indices, with support for negative + // indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer. + OciEncryptLayers *[]int + // ConfidentialWorkloadOptions is used to force the output image's rootfs to contain a + // LUKS-compatibly encrypted disk image (for use with krun) instead of the usual + // contents of a rootfs. + ConfidentialWorkloadOptions ConfidentialWorkloadOptions + // UnsetEnvs is a list of environments to not add to final image. + // Deprecated: use UnsetEnv() before committing, or set OverrideChanges + // instead. + UnsetEnvs []string + // OverrideConfig is an optional Schema2Config which can override parts + // of the working container's configuration for the image that is being + // committed. + OverrideConfig *manifest.Schema2Config + // OverrideChanges is a slice of Dockerfile-style instructions to make + // to the configuration of the image that is being committed, after + // OverrideConfig is applied. + OverrideChanges []string + // ExtraImageContent is a map which describes additional content to add + // to the new layer in the committed image. The map's keys are + // filesystem paths in the image and the corresponding values are the + // paths of files whose contents will be used in their place. The + // contents will be owned by 0:0 and have mode 0o644. Currently only + // accepts regular files. + ExtraImageContent map[string]string + // SBOMScanOptions encapsulates options which control whether or not we + // run scanners on the rootfs that we're about to commit, and how. + SBOMScanOptions []SBOMScanOptions + // CompatSetParent causes the "parent" field to be set when committing + // the image in Docker format. Newer BuildKit-based builds don't set + // this field. + CompatSetParent types.OptionalBool + // CompatLayerOmissions causes the "/dev", "/proc", and "/sys" + // directories to be omitted from the layer diff and related output, as + // the classic builder did. Newer BuildKit-based builds include them + // in the built image by default. + CompatLayerOmissions types.OptionalBool + // PrependedLinkedLayers and AppendedLinkedLayers are combinations of + // history entries and locations of either directory trees (if + // directories, per os.Stat()) or uncompressed layer blobs which should + // be added to the image at commit-time. The order of these relative + // to PrependedEmptyLayers and AppendedEmptyLayers, and relative to the + // corresponding members in the Builder object, in the committed image + // is not guaranteed. + PrependedLinkedLayers, AppendedLinkedLayers []LinkedLayer + // UnsetAnnotations is a list of annotations (names only) to withhold + // from the image. + UnsetAnnotations []string + // Annotations is a list of annotations (in the form "key=value") to + // add to the image. + Annotations []string + // CreatedAnnotation controls whether or not an "org.opencontainers.image.created" + // annotation is present in the output image. + CreatedAnnotation types.OptionalBool +} + +// LinkedLayer combines a history entry with the location of either a directory +// tree (if it's a directory, per os.Stat()) or an uncompressed layer blob +// which should be added to the image at commit-time. The BlobPath and +// History.EmptyLayer fields should be considered mutually-exclusive. +type LinkedLayer struct { + History v1.History // history entry to add + BlobPath string // corresponding uncompressed blob file (layer as a tar archive), or directory tree to archive +} + +// storageAllowedPolicyScopes overrides the policy for local storage +// to ensure that we can read images from it. +var storageAllowedPolicyScopes = signature.PolicyTransportScopes{ + "": []signature.PolicyRequirement{ + signature.NewPRInsecureAcceptAnything(), + }, +} + +// checkRegistrySourcesAllows checks the $BUILD_REGISTRY_SOURCES environment +// variable, if it's set. The contents are expected to be a JSON-encoded +// github.com/openshift/api/config/v1.Image, set by an OpenShift build +// controller that arranged for us to be run in a container. +func checkRegistrySourcesAllows(forWhat string, dest types.ImageReference) (insecure bool, err error) { + transport := dest.Transport() + if transport == nil { + return false, nil + } + if transport.Name() != docker.Transport.Name() { + return false, nil + } + dref := dest.DockerReference() + if dref == nil || reference.Domain(dref) == "" { + return false, nil + } + + if registrySources, ok := os.LookupEnv("BUILD_REGISTRY_SOURCES"); ok && len(registrySources) > 0 { + // Use local struct instead of github.com/openshift/api/config/v1 RegistrySources + var sources struct { + InsecureRegistries []string `json:"insecureRegistries,omitempty"` + BlockedRegistries []string `json:"blockedRegistries,omitempty"` + AllowedRegistries []string `json:"allowedRegistries,omitempty"` + } + if err := json.Unmarshal([]byte(registrySources), &sources); err != nil { + return false, fmt.Errorf("parsing $BUILD_REGISTRY_SOURCES (%q) as JSON: %w", registrySources, err) + } + blocked := false + if len(sources.BlockedRegistries) > 0 { + for _, blockedDomain := range sources.BlockedRegistries { + if blockedDomain == reference.Domain(dref) { + blocked = true + } + } + } + if blocked { + return false, fmt.Errorf("%s registry at %q denied by policy: it is in the blocked registries list", forWhat, reference.Domain(dref)) + } + allowed := true + if len(sources.AllowedRegistries) > 0 { + allowed = false + for _, allowedDomain := range sources.AllowedRegistries { + if allowedDomain == reference.Domain(dref) { + allowed = true + } + } + } + if !allowed { + return false, fmt.Errorf("%s registry at %q denied by policy: not in allowed registries list", forWhat, reference.Domain(dref)) + } + if len(sources.InsecureRegistries) > 0 { + return true, nil + } + } + return false, nil +} + +func (b *Builder) addManifest(ctx context.Context, manifestName string, imageSpec string) (string, error) { + var create bool + systemContext := &types.SystemContext{} + var list manifests.List + runtime, err := libimage.RuntimeFromStore(b.store, &libimage.RuntimeOptions{SystemContext: systemContext}) + if err != nil { + return "", err + } + manifestList, err := runtime.LookupManifestList(manifestName) + if err != nil { + create = true + list = manifests.Create() + } else { + locker, err := manifests.LockerForImage(b.store, manifestList.ID()) + if err != nil { + return "", err + } + locker.Lock() + defer locker.Unlock() + _, list, err = manifests.LoadFromImage(b.store, manifestList.ID()) + if err != nil { + return "", err + } + } + + names, err := util.ExpandNames([]string{manifestName}, systemContext, b.store) + if err != nil { + return "", fmt.Errorf("encountered while expanding manifest list name %q: %w", manifestName, err) + } + + ref, err := util.VerifyTagName(imageSpec) + if err != nil { + // check if the local image exists + if ref, _, err = util.FindImage(b.store, "", systemContext, imageSpec); err != nil { + return "", err + } + } + + if _, err = list.Add(ctx, systemContext, ref, true); err != nil { + return "", err + } + var imageID string + if create { + imageID, err = list.SaveToImage(b.store, "", names, manifest.DockerV2ListMediaType) + } else { + imageID, err = list.SaveToImage(b.store, manifestList.ID(), nil, "") + } + return imageID, err +} + +// Commit writes the contents of the container, along with its updated +// configuration, to a new image in the specified location, and if we know how, +// add any additional tags that were specified. Returns the ID of the new image +// if commit was successful and the image destination was local. +func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options CommitOptions) (string, reference.Canonical, digest.Digest, error) { + var ( + imgID string + src types.ImageReference + destinationTimestamp *time.Time + ) + + // If we weren't given a name, build a destination reference using a + // temporary name that we'll remove later. The correct thing to do + // would be to read the manifest and configuration blob, and ask the + // manifest for the ID that we'd give the image, but that computation + // requires that we know the digests of the layer blobs, which we don't + // want to compute here because we'll have to do it again when + // cp.Image() instantiates a source image, and we don't want to do the + // work twice. + if options.OmitTimestamp { + if options.HistoryTimestamp != nil { + return imgID, nil, "", fmt.Errorf("OmitTimestamp and HistoryTimestamp can not be used together") + } + timestamp := time.Unix(0, 0).UTC() + options.HistoryTimestamp = ×tamp + } + destinationTimestamp = options.HistoryTimestamp + if options.SourceDateEpoch != nil { + destinationTimestamp = options.SourceDateEpoch + } + nameToRemove := "" + if dest == nil { + nameToRemove = stringid.GenerateRandomID() + "-tmp" + dest2, err := is.Transport.ParseStoreReference(b.store, nameToRemove) + if err != nil { + return imgID, nil, "", fmt.Errorf("creating temporary destination reference for image: %w", err) + } + dest = dest2 + } + + systemContext := getSystemContext(b.store, options.SystemContext, options.SignaturePolicyPath) + + blocked, err := isReferenceBlocked(dest, systemContext) + if err != nil { + return "", nil, "", fmt.Errorf("checking if committing to registry for %q is blocked: %w", transports.ImageName(dest), err) + } + if blocked { + return "", nil, "", fmt.Errorf("commit access to registry for %q is blocked by configuration", transports.ImageName(dest)) + } + + // Load the system signing policy. + commitPolicy, err := signature.DefaultPolicy(systemContext) + if err != nil { + return "", nil, "", fmt.Errorf("obtaining default signature policy: %w", err) + } + // Override the settings for local storage to make sure that we can always read the source "image". + commitPolicy.Transports[is.Transport.Name()] = storageAllowedPolicyScopes + + policyContext, err := signature.NewPolicyContext(commitPolicy) + if err != nil { + return imgID, nil, "", fmt.Errorf("creating new signature policy context: %w", err) + } + defer func() { + if err2 := policyContext.Destroy(); err2 != nil { + logrus.Debugf("error destroying signature policy context: %v", err2) + } + }() + + // Check if the commit is blocked by $BUILDER_REGISTRY_SOURCES. + insecure, err := checkRegistrySourcesAllows("commit to", dest) + if err != nil { + return imgID, nil, "", err + } + if insecure { + if systemContext.DockerInsecureSkipTLSVerify == types.OptionalBoolFalse { + return imgID, nil, "", fmt.Errorf("can't require tls verification on an insecured registry") + } + systemContext.DockerInsecureSkipTLSVerify = types.OptionalBoolTrue + systemContext.OCIInsecureSkipTLSVerify = true + systemContext.DockerDaemonInsecureSkipTLSVerify = true + } + logrus.Debugf("committing image with reference %q is allowed by policy", transports.ImageName(dest)) + + // If we need to scan the rootfs, do it now. + options.ExtraImageContent = maps.Clone(options.ExtraImageContent) + var extraImageContent, extraLocalContent map[string]string + if len(options.SBOMScanOptions) != 0 { + var scansDirectory string + if extraImageContent, extraLocalContent, scansDirectory, err = b.sbomScan(ctx, options); err != nil { + return imgID, nil, "", fmt.Errorf("scanning rootfs to generate SBOM for container %q: %w", b.ContainerID, err) + } + if scansDirectory != "" { + defer func() { + if err := os.RemoveAll(scansDirectory); err != nil { + logrus.Warnf("removing temporary directory %q: %v", scansDirectory, err) + } + }() + } + if len(extraImageContent) > 0 { + if options.ExtraImageContent == nil { + options.ExtraImageContent = make(map[string]string, len(extraImageContent)) + } + // merge in the scanner-generated content + for k, v := range extraImageContent { + if _, set := options.ExtraImageContent[k]; !set { + options.ExtraImageContent[k] = v + } + } + } + } + + // Build an image reference from which we can copy the finished image. + src, err = b.makeContainerImageRef(options) + if err != nil { + return imgID, nil, "", fmt.Errorf("computing layer digests and building metadata for container %q: %w", b.ContainerID, err) + } + // In case we're using caching, decide how to handle compression for a cache. + // If we're using blob caching, set it up for the source. + maybeCachedSrc := src + maybeCachedDest := dest + if options.BlobDirectory != "" { + compress := types.PreserveOriginal + if options.Compression != archive.Uncompressed { + compress = types.Compress + } + cache, err := blobcache.NewBlobCache(src, options.BlobDirectory, compress) + if err != nil { + return imgID, nil, "", fmt.Errorf("wrapping image reference %q in blob cache at %q: %w", transports.ImageName(src), options.BlobDirectory, err) + } + maybeCachedSrc = cache + cache, err = blobcache.NewBlobCache(dest, options.BlobDirectory, compress) + if err != nil { + return imgID, nil, "", fmt.Errorf("wrapping image reference %q in blob cache at %q: %w", transports.ImageName(dest), options.BlobDirectory, err) + } + maybeCachedDest = cache + } + // "Copy" our image to where it needs to be. + switch options.Compression { + case archive.Uncompressed: + systemContext.OCIAcceptUncompressedLayers = true + case archive.Gzip: + systemContext.DirForceCompress = true + } + + if systemContext.ArchitectureChoice != b.Architecture() { + systemContext.ArchitectureChoice = b.Architecture() + } + if systemContext.OSChoice != b.OS() { + systemContext.OSChoice = b.OS() + } + + var manifestBytes []byte + if manifestBytes, err = retryCopyImage(ctx, policyContext, maybeCachedDest, maybeCachedSrc, dest, getCopyOptions(b.store, options.ReportWriter, nil, systemContext, "", false, options.SignBy, options.OciEncryptLayers, options.OciEncryptConfig, nil, destinationTimestamp), options.MaxRetries, options.RetryDelay); err != nil { + return imgID, nil, "", fmt.Errorf("copying layers and metadata for container %q: %w", b.ContainerID, err) + } + // If we've got more names to attach, and we know how to do that for + // the transport that we're writing the new image to, add them now. + if len(options.AdditionalTags) > 0 { + switch dest.Transport().Name() { + case is.Transport.Name(): + _, img, err := is.ResolveReference(dest) + if err != nil { + return imgID, nil, "", fmt.Errorf("locating just-written image %q: %w", transports.ImageName(dest), err) + } + if err = util.AddImageNames(b.store, "", systemContext, img, options.AdditionalTags); err != nil { + return imgID, nil, "", fmt.Errorf("setting image names to %v: %w", append(img.Names, options.AdditionalTags...), err) + } + logrus.Debugf("assigned names %v to image %q", img.Names, img.ID) + default: + logrus.Warnf("don't know how to add tags to images stored in %q transport", dest.Transport().Name()) + } + } + + if dest.Transport().Name() == is.Transport.Name() { + dest2, img, err := is.ResolveReference(dest) + if err != nil { + return imgID, nil, "", fmt.Errorf("locating image %q in local storage: %w", transports.ImageName(dest), err) + } + dest = dest2 + imgID = img.ID + toPruneNames := make([]string, 0, len(img.Names)) + for _, name := range img.Names { + if nameToRemove != "" && strings.Contains(name, nameToRemove) { + toPruneNames = append(toPruneNames, name) + } + } + if len(toPruneNames) > 0 { + if err = b.store.RemoveNames(imgID, toPruneNames); err != nil { + return imgID, nil, "", fmt.Errorf("failed to remove temporary name from image %q: %w", imgID, err) + } + logrus.Debugf("removing %v from assigned names to image %q", nameToRemove, img.ID) + } + if options.IIDFile != "" { + if err = os.WriteFile(options.IIDFile, []byte("sha256:"+img.ID), 0o644); err != nil { + return imgID, nil, "", err + } + } + } + // If we're supposed to store SBOM or PURL information in local files, write them now. + for filename, content := range extraLocalContent { + err := func() error { + output, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) + if err != nil { + return err + } + defer output.Close() + input, err := os.Open(content) + if err != nil { + return err + } + defer input.Close() + if _, err := io.Copy(output, input); err != nil { + return fmt.Errorf("copying from %q to %q: %w", content, filename, err) + } + return nil + }() + if err != nil { + return imgID, nil, "", err + } + } + + // Calculate the as-written digest of the image's manifest and build the digested + // reference for the image. + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return imgID, nil, "", fmt.Errorf("computing digest of manifest of new image %q: %w", transports.ImageName(dest), err) + } + if imgID == "" { + parsedManifest, err := manifest.FromBlob(manifestBytes, manifest.GuessMIMEType(manifestBytes)) + if err != nil { + return imgID, nil, "", fmt.Errorf("parsing written manifest to determine the image's ID: %w", err) + } + configInfo := parsedManifest.ConfigInfo() + if configInfo.Size > 2 && configInfo.Digest.Validate() == nil { // don't be returning a digest of "" or "{}" + imgID = configInfo.Digest.Encoded() + } + } + + var ref reference.Canonical + if name := dest.DockerReference(); name != nil { + ref, err = reference.WithDigest(name, manifestDigest) + if err != nil { + logrus.Warnf("error generating canonical reference with name %q and digest %s: %v", name, manifestDigest.String(), err) + } + } + + if options.Manifest != "" { + manifestID, err := b.addManifest(ctx, options.Manifest, imgID) + if err != nil { + return imgID, nil, "", err + } + logrus.Debugf("added imgID %s to manifestID %s", imgID, manifestID) + } + return imgID, ref, manifestDigest, nil +} diff --git a/vendor/github.com/containers/buildah/common.go b/vendor/github.com/containers/buildah/common.go new file mode 100644 index 00000000..fe734cfc --- /dev/null +++ b/vendor/github.com/containers/buildah/common.go @@ -0,0 +1,100 @@ +package buildah + +import ( + "context" + "errors" + "io" + "path/filepath" + "time" + + "github.com/containers/buildah/define" + encconfig "github.com/containers/ocicrypt/config" + "go.podman.io/common/pkg/retry" + cp "go.podman.io/image/v5/copy" + "go.podman.io/image/v5/docker" + "go.podman.io/image/v5/signature" + is "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/unshare" +) + +const ( + // OCI used to define the "oci" image format + OCI = define.OCI + // DOCKER used to define the "docker" image format + DOCKER = define.DOCKER +) + +func getCopyOptions(store storage.Store, reportWriter io.Writer, sourceSystemContext *types.SystemContext, destinationSystemContext *types.SystemContext, manifestType string, removeSignatures bool, addSigner string, ociEncryptLayers *[]int, ociEncryptConfig *encconfig.EncryptConfig, ociDecryptConfig *encconfig.DecryptConfig, destinationTimestamp *time.Time) *cp.Options { + sourceCtx := getSystemContext(store, nil, "") + if sourceSystemContext != nil { + *sourceCtx = *sourceSystemContext + } + + destinationCtx := getSystemContext(store, nil, "") + if destinationSystemContext != nil { + *destinationCtx = *destinationSystemContext + } + return &cp.Options{ + ReportWriter: reportWriter, + SourceCtx: sourceCtx, + DestinationCtx: destinationCtx, + ForceManifestMIMEType: manifestType, + RemoveSignatures: removeSignatures, + SignBy: addSigner, + OciEncryptConfig: ociEncryptConfig, + OciDecryptConfig: ociDecryptConfig, + OciEncryptLayers: ociEncryptLayers, + DestinationTimestamp: destinationTimestamp, + } +} + +func getSystemContext(store storage.Store, defaults *types.SystemContext, signaturePolicyPath string) *types.SystemContext { + sc := &types.SystemContext{} + if defaults != nil { + *sc = *defaults + } + if signaturePolicyPath != "" { + sc.SignaturePolicyPath = signaturePolicyPath + } + if store != nil { + if sc.SystemRegistriesConfPath == "" && unshare.IsRootless() { + userRegistriesFile := filepath.Join(store.GraphRoot(), "registries.conf") + if err := fileutils.Exists(userRegistriesFile); err == nil { + sc.SystemRegistriesConfPath = userRegistriesFile + } + } + } + return sc +} + +func retryCopyImage(ctx context.Context, policyContext *signature.PolicyContext, maybeWrappedDest, maybeWrappedSrc, directDest types.ImageReference, copyOptions *cp.Options, maxRetries int, retryDelay time.Duration) ([]byte, error) { + return retryCopyImageWithOptions(ctx, policyContext, maybeWrappedDest, maybeWrappedSrc, directDest, copyOptions, maxRetries, retryDelay, true) +} + +func retryCopyImageWithOptions(ctx context.Context, policyContext *signature.PolicyContext, maybeWrappedDest, maybeWrappedSrc, directDest types.ImageReference, copyOptions *cp.Options, maxRetries int, retryDelay time.Duration, retryOnLayerUnknown bool) ([]byte, error) { + var ( + manifestBytes []byte + err error + ) + err = retry.IfNecessary(ctx, func() error { + manifestBytes, err = cp.Image(ctx, policyContext, maybeWrappedDest, maybeWrappedSrc, copyOptions) + return err + }, &retry.RetryOptions{MaxRetry: maxRetries, Delay: retryDelay, IsErrorRetryable: func(err error) bool { + if retryOnLayerUnknown && directDest.Transport().Name() == is.Transport.Name() && errors.Is(err, storage.ErrLayerUnknown) { + // we were trying to reuse a layer that belonged to an + // image that was deleted at just the right (worst + // possible) time? yeah, try again + return true + } + if directDest.Transport().Name() != docker.Transport.Name() { + // if we're not talking to a registry, then nah + return false + } + // hand it off to the default should-this-be-retried logic + return retry.IsErrorRetryable(err) + }}) + return manifestBytes, err +} diff --git a/vendor/github.com/containers/buildah/config.go b/vendor/github.com/containers/buildah/config.go new file mode 100644 index 00000000..a5381972 --- /dev/null +++ b/vendor/github.com/containers/buildah/config.go @@ -0,0 +1,821 @@ +package buildah + +import ( + "context" + "encoding/json" + "fmt" + "maps" + "os" + "slices" + "strings" + "time" + + "github.com/containerd/platforms" + "github.com/containers/buildah/define" + "github.com/containers/buildah/docker" + internalUtil "github.com/containers/buildah/internal/util" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" + "go.podman.io/image/v5/manifest" + "go.podman.io/image/v5/pkg/compression" + "go.podman.io/image/v5/transports" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/stringid" +) + +// unmarshalConvertedConfig obtains the config blob of img valid for the wantedManifestMIMEType format +// (either as it exists, or converting the image if necessary), and unmarshals it into dest. +// NOTE: The MIME type is of the _manifest_, not of the _config_ that is returned. +func unmarshalConvertedConfig(ctx context.Context, dest any, img types.Image, wantedManifestMIMEType string) error { + _, actualManifestMIMEType, err := img.Manifest(ctx) + if err != nil { + return fmt.Errorf("getting manifest MIME type for %q: %w", transports.ImageName(img.Reference()), err) + } + if wantedManifestMIMEType != actualManifestMIMEType { + layerInfos := img.LayerInfos() + for i := range layerInfos { // force the "compression" to gzip, which is supported by all of the formats we care about + layerInfos[i].CompressionOperation = types.Compress + layerInfos[i].CompressionAlgorithm = &compression.Gzip + } + updatedImg, err := img.UpdatedImage(ctx, types.ManifestUpdateOptions{ + LayerInfos: layerInfos, + }) + if err != nil { + return fmt.Errorf("resetting recorded compression for %q: %w", transports.ImageName(img.Reference()), err) + } + secondUpdatedImg, err := updatedImg.UpdatedImage(ctx, types.ManifestUpdateOptions{ + ManifestMIMEType: wantedManifestMIMEType, + }) + if err != nil { + return fmt.Errorf("converting image %q from %q to %q: %w", transports.ImageName(img.Reference()), actualManifestMIMEType, wantedManifestMIMEType, err) + } + img = secondUpdatedImg + } + config, err := img.ConfigBlob(ctx) + if err != nil { + return fmt.Errorf("reading %s config from %q: %w", wantedManifestMIMEType, transports.ImageName(img.Reference()), err) + } + if err := json.Unmarshal(config, dest); err != nil { + return fmt.Errorf("parsing %s configuration %q from %q: %w", wantedManifestMIMEType, string(config), transports.ImageName(img.Reference()), err) + } + return nil +} + +func (b *Builder) initConfig(ctx context.Context, sys *types.SystemContext, img types.Image, options *BuilderOptions) error { + if img != nil { // A pre-existing image, as opposed to a "FROM scratch" new one. + rawManifest, manifestMIMEType, err := img.Manifest(ctx) + if err != nil { + return fmt.Errorf("reading image manifest for %q: %w", transports.ImageName(img.Reference()), err) + } + rawConfig, err := img.ConfigBlob(ctx) + if err != nil { + return fmt.Errorf("reading image configuration for %q: %w", transports.ImageName(img.Reference()), err) + } + b.Manifest = rawManifest + b.Config = rawConfig + + dimage := docker.V2Image{} + if err := unmarshalConvertedConfig(ctx, &dimage, img, manifest.DockerV2Schema2MediaType); err != nil { + return err + } + b.Docker = dimage + + oimage := ociv1.Image{} + if err := unmarshalConvertedConfig(ctx, &oimage, img, ociv1.MediaTypeImageManifest); err != nil { + return err + } + b.OCIv1 = oimage + + if manifestMIMEType == ociv1.MediaTypeImageManifest { + // Attempt to recover format-specific data from the manifest. + v1Manifest := ociv1.Manifest{} + if err := json.Unmarshal(b.Manifest, &v1Manifest); err != nil { + return fmt.Errorf("parsing OCI manifest %q: %w", string(b.Manifest), err) + } + if len(v1Manifest.Annotations) > 0 { + if b.ImageAnnotations == nil { + b.ImageAnnotations = make(map[string]string, len(v1Manifest.Annotations)) + } + maps.Copy(b.ImageAnnotations, v1Manifest.Annotations) + } + } + } else { + if options == nil || options.CompatScratchConfig != types.OptionalBoolTrue { + b.Docker = docker.V2Image{ + V1Image: docker.V1Image{ + Config: &docker.Config{ + WorkingDir: "/", + }, + }, + } + b.OCIv1 = ociv1.Image{ + Config: ociv1.ImageConfig{ + WorkingDir: "/", + }, + } + } + } + + b.setupLogger() + b.fixupConfig(sys) + return nil +} + +func (b *Builder) fixupConfig(sys *types.SystemContext) { + if b.Docker.Config != nil { + // Prefer image-level settings over those from the container it was built from. + b.Docker.ContainerConfig = *b.Docker.Config + } + b.Docker.Config = &b.Docker.ContainerConfig + b.Docker.DockerVersion = "" + now := time.Now().UTC() + if b.Docker.Created.IsZero() { + b.Docker.Created = now + } + if b.OCIv1.Created == nil || b.OCIv1.Created.IsZero() { + b.OCIv1.Created = &now + } + currentPlatformSpecification := platforms.DefaultSpec() + if b.OS() == "" { + if sys != nil && sys.OSChoice != "" { + b.SetOS(sys.OSChoice) + } else { + b.SetOS(currentPlatformSpecification.OS) + } + } + if b.Architecture() == "" { + if sys != nil && sys.ArchitectureChoice != "" { + b.SetArchitecture(sys.ArchitectureChoice) + b.SetVariant(sys.VariantChoice) + } else { + b.SetArchitecture(currentPlatformSpecification.Architecture) + b.SetVariant(currentPlatformSpecification.Variant) + } + // in case the arch string we started with was shorthand for a known arch+variant pair, normalize it + ps := internalUtil.NormalizePlatform(ociv1.Platform{OS: b.OS(), Architecture: b.Architecture(), Variant: b.Variant()}) + b.SetArchitecture(ps.Architecture) + b.SetVariant(ps.Variant) + } + if b.Format == define.Dockerv2ImageManifest && b.Hostname() == "" { + b.SetHostname(stringid.TruncateID(stringid.GenerateRandomID())) + } +} + +func (b *Builder) setupLogger() { + if b.Logger == nil { + b.Logger = logrus.New() + b.Logger.SetOutput(os.Stderr) + b.Logger.SetLevel(logrus.GetLevel()) + } +} + +// Annotations returns a set of key-value pairs from the image's manifest. +func (b *Builder) Annotations() map[string]string { + return maps.Clone(b.ImageAnnotations) +} + +// SetAnnotation adds or overwrites a key's value from the image's manifest. +// Note: this setting is not present in the Docker v2 image format, so it is +// discarded when writing images using Docker v2 formats. +func (b *Builder) SetAnnotation(key, value string) { + if b.ImageAnnotations == nil { + b.ImageAnnotations = map[string]string{} + } + b.ImageAnnotations[key] = value +} + +// UnsetAnnotation removes a key and its value from the image's manifest, if +// it's present. +func (b *Builder) UnsetAnnotation(key string) { + delete(b.ImageAnnotations, key) +} + +// ClearAnnotations removes all keys and their values from the image's +// manifest. +func (b *Builder) ClearAnnotations() { + b.ImageAnnotations = nil +} + +// CreatedBy returns a description of how this image was built. +func (b *Builder) CreatedBy() string { + return b.ImageCreatedBy +} + +// SetCreatedBy sets the description of how this image was built. +func (b *Builder) SetCreatedBy(how string) { + b.ImageCreatedBy = how +} + +// OS returns a name of the OS on which the container, or a container built +// using an image built from this container, is intended to be run. +func (b *Builder) OS() string { + return b.OCIv1.OS +} + +// SetOS sets the name of the OS on which the container, or a container built +// using an image built from this container, is intended to be run. +func (b *Builder) SetOS(os string) { + b.OCIv1.OS = os + b.Docker.OS = os +} + +// OSVersion returns a version of the OS on which the container, or a container +// built using an image built from this container, is intended to be run. +func (b *Builder) OSVersion() string { + return b.OCIv1.OSVersion +} + +// SetOSVersion sets the version of the OS on which the container, or a +// container built using an image built from this container, is intended to be +// run. +func (b *Builder) SetOSVersion(version string) { + b.OCIv1.OSVersion = version + b.Docker.OSVersion = version +} + +// OSFeatures returns a list of OS features which the container, or a container +// built using an image built from this container, depends on the OS supplying. +func (b *Builder) OSFeatures() []string { + return slices.Clone(b.OCIv1.OSFeatures) +} + +// SetOSFeature adds a feature of the OS which the container, or a container +// built using an image built from this container, depends on the OS supplying. +func (b *Builder) SetOSFeature(feature string) { + if !slices.Contains(b.OCIv1.OSFeatures, feature) { + b.OCIv1.OSFeatures = append(b.OCIv1.OSFeatures, feature) + } + if !slices.Contains(b.Docker.OSFeatures, feature) { + b.Docker.OSFeatures = append(b.Docker.OSFeatures, feature) + } +} + +// UnsetOSFeature removes a feature of the OS which the container, or a +// container built using an image built from this container, depends on the OS +// supplying. +func (b *Builder) UnsetOSFeature(feature string) { + if slices.Contains(b.OCIv1.OSFeatures, feature) { + features := make([]string, 0, len(b.OCIv1.OSFeatures)) + for _, f := range b.OCIv1.OSFeatures { + if f != feature { + features = append(features, f) + } + } + b.OCIv1.OSFeatures = features + } + if slices.Contains(b.Docker.OSFeatures, feature) { + features := make([]string, 0, len(b.Docker.OSFeatures)) + for _, f := range b.Docker.OSFeatures { + if f != feature { + features = append(features, f) + } + } + b.Docker.OSFeatures = features + } +} + +// ClearOSFeatures clears the list of features of the OS which the container, +// or a container built using an image built from this container, depends on +// the OS supplying. +func (b *Builder) ClearOSFeatures() { + b.OCIv1.OSFeatures = []string{} + b.Docker.OSFeatures = []string{} +} + +// Architecture returns a name of the architecture on which the container, or a +// container built using an image built from this container, is intended to be +// run. +func (b *Builder) Architecture() string { + return b.OCIv1.Architecture +} + +// SetArchitecture sets the name of the architecture on which the container, or +// a container built using an image built from this container, is intended to +// be run. +func (b *Builder) SetArchitecture(arch string) { + b.OCIv1.Architecture = arch + b.Docker.Architecture = arch +} + +// Variant returns a name of the architecture variant on which the container, +// or a container built using an image built from this container, is intended +// to be run. +func (b *Builder) Variant() string { + return b.OCIv1.Variant +} + +// SetVariant sets the name of the architecture variant on which the container, +// or a container built using an image built from this container, is intended +// to be run. +func (b *Builder) SetVariant(variant string) { + b.Docker.Variant = variant + b.OCIv1.Variant = variant +} + +// Maintainer returns contact information for the person who built the image. +func (b *Builder) Maintainer() string { + return b.OCIv1.Author +} + +// SetMaintainer sets contact information for the person who built the image. +func (b *Builder) SetMaintainer(who string) { + b.OCIv1.Author = who + b.Docker.Author = who +} + +// User returns information about the user as whom the container, or a +// container built using an image built from this container, should be run. +func (b *Builder) User() string { + return b.OCIv1.Config.User +} + +// SetUser sets information about the user as whom the container, or a +// container built using an image built from this container, should be run. +// Acceptable forms are a user name or ID, optionally followed by a colon and a +// group name or ID. +func (b *Builder) SetUser(spec string) { + b.OCIv1.Config.User = spec + b.Docker.Config.User = spec +} + +// OnBuild returns the OnBuild value from the container. +func (b *Builder) OnBuild() []string { + return slices.Clone(b.Docker.Config.OnBuild) +} + +// ClearOnBuild removes all values from the OnBuild structure +func (b *Builder) ClearOnBuild() { + b.Docker.Config.OnBuild = []string{} +} + +// SetOnBuild sets a trigger instruction to be executed when the image is used +// as the base of another image. +// Note: this setting is not present in the OCIv1 image format, so it is +// discarded when writing images using OCIv1 formats. +func (b *Builder) SetOnBuild(onBuild string) { + if onBuild != "" && b.Format != define.Dockerv2ImageManifest { + b.Logger.Warnf("ONBUILD is not supported for OCI image format, %s will be ignored. Must use `docker` format", onBuild) + } + b.Docker.Config.OnBuild = append(b.Docker.Config.OnBuild, onBuild) +} + +// WorkDir returns the default working directory for running commands in the +// container, or in a container built using an image built from this container. +func (b *Builder) WorkDir() string { + return b.OCIv1.Config.WorkingDir +} + +// SetWorkDir sets the location of the default working directory for running +// commands in the container, or in a container built using an image built from +// this container. +func (b *Builder) SetWorkDir(there string) { + b.OCIv1.Config.WorkingDir = there + b.Docker.Config.WorkingDir = there +} + +// Shell returns the default shell for running commands in the +// container, or in a container built using an image built from this container. +func (b *Builder) Shell() []string { + return slices.Clone(b.Docker.Config.Shell) +} + +// SetShell sets the default shell for running +// commands in the container, or in a container built using an image built from +// this container. +// Note: this setting is not present in the OCIv1 image format, so it is +// discarded when writing images using OCIv1 formats. +func (b *Builder) SetShell(shell []string) { + if len(shell) > 0 && b.Format != define.Dockerv2ImageManifest { + b.Logger.Warnf("SHELL is not supported for OCI image format, %s will be ignored. Must use `docker` format", shell) + } + + b.Docker.Config.Shell = slices.Clone(shell) +} + +// Env returns a list of key-value pairs to be set when running commands in the +// container, or in a container built using an image built from this container. +func (b *Builder) Env() []string { + return slices.Clone(b.OCIv1.Config.Env) +} + +// SetEnv adds or overwrites a value to the set of environment strings which +// should be set when running commands in the container, or in a container +// built using an image built from this container. +func (b *Builder) SetEnv(k string, v string) { + reset := func(s *[]string) { + n := []string{} + for i := range *s { + if !strings.HasPrefix((*s)[i], k+"=") { + n = append(n, (*s)[i]) + } + } + n = append(n, k+"="+v) + *s = n + } + reset(&b.OCIv1.Config.Env) + reset(&b.Docker.Config.Env) +} + +// UnsetEnv removes a value from the set of environment strings which should be +// set when running commands in this container, or in a container built using +// an image built from this container. +func (b *Builder) UnsetEnv(k string) { + unset := func(s *[]string) { + n := []string{} + for i := range *s { + if !strings.HasPrefix((*s)[i], k+"=") { + n = append(n, (*s)[i]) + } + } + *s = n + } + unset(&b.OCIv1.Config.Env) + unset(&b.Docker.Config.Env) +} + +// ClearEnv removes all values from the set of environment strings which should +// be set when running commands in this container, or in a container built +// using an image built from this container. +func (b *Builder) ClearEnv() { + b.OCIv1.Config.Env = []string{} + b.Docker.Config.Env = []string{} +} + +// Cmd returns the default command, or command parameters if an Entrypoint is +// set, to use when running a container built from an image built from this +// container. +func (b *Builder) Cmd() []string { + return slices.Clone(b.OCIv1.Config.Cmd) +} + +// SetCmd sets the default command, or command parameters if an Entrypoint is +// set, to use when running a container built from an image built from this +// container. +func (b *Builder) SetCmd(cmd []string) { + b.OCIv1.Config.Cmd = slices.Clone(cmd) + b.Docker.Config.Cmd = slices.Clone(cmd) +} + +// Entrypoint returns the command to be run for containers built from images +// built from this container. +func (b *Builder) Entrypoint() []string { + if len(b.OCIv1.Config.Entrypoint) > 0 { + return slices.Clone(b.OCIv1.Config.Entrypoint) + } + return nil +} + +// SetEntrypoint sets the command to be run for in containers built from images +// built from this container. +func (b *Builder) SetEntrypoint(ep []string) { + b.OCIv1.Config.Entrypoint = slices.Clone(ep) + b.Docker.Config.Entrypoint = slices.Clone(ep) +} + +// Labels returns a set of key-value pairs from the image's runtime +// configuration. +func (b *Builder) Labels() map[string]string { + return maps.Clone(b.OCIv1.Config.Labels) +} + +// SetLabel adds or overwrites a key's value from the image's runtime +// configuration. +func (b *Builder) SetLabel(k string, v string) { + if b.OCIv1.Config.Labels == nil { + b.OCIv1.Config.Labels = map[string]string{} + } + b.OCIv1.Config.Labels[k] = v + if b.Docker.Config.Labels == nil { + b.Docker.Config.Labels = map[string]string{} + } + b.Docker.Config.Labels[k] = v +} + +// UnsetLabel removes a key and its value from the image's runtime +// configuration, if it's present. +func (b *Builder) UnsetLabel(k string) { + delete(b.OCIv1.Config.Labels, k) + delete(b.Docker.Config.Labels, k) +} + +// ClearLabels removes all keys and their values from the image's runtime +// configuration. +func (b *Builder) ClearLabels() { + b.OCIv1.Config.Labels = map[string]string{} + b.Docker.Config.Labels = map[string]string{} +} + +// Ports returns the set of ports which should be exposed when a container +// based on an image built from this container is run. +func (b *Builder) Ports() []string { + p := []string{} + for k := range b.OCIv1.Config.ExposedPorts { + p = append(p, k) + } + return p +} + +// SetPort adds or overwrites an exported port in the set of ports which should +// be exposed when a container based on an image built from this container is +// run. +func (b *Builder) SetPort(p string) { + if b.OCIv1.Config.ExposedPorts == nil { + b.OCIv1.Config.ExposedPorts = map[string]struct{}{} + } + b.OCIv1.Config.ExposedPorts[p] = struct{}{} + if b.Docker.Config.ExposedPorts == nil { + b.Docker.Config.ExposedPorts = make(docker.PortSet) + } + b.Docker.Config.ExposedPorts[docker.Port(p)] = struct{}{} +} + +// UnsetPort removes an exposed port from the set of ports which should be +// exposed when a container based on an image built from this container is run. +func (b *Builder) UnsetPort(p string) { + delete(b.OCIv1.Config.ExposedPorts, p) + delete(b.Docker.Config.ExposedPorts, docker.Port(p)) +} + +// ClearPorts empties the set of ports which should be exposed when a container +// based on an image built from this container is run. +func (b *Builder) ClearPorts() { + b.OCIv1.Config.ExposedPorts = map[string]struct{}{} + b.Docker.Config.ExposedPorts = docker.PortSet{} +} + +// Volumes returns a list of filesystem locations which should be mounted from +// outside of the container when a container built from an image built from +// this container is run. +func (b *Builder) Volumes() []string { + v := []string{} + for k := range b.OCIv1.Config.Volumes { + v = append(v, k) + } + if len(v) > 0 { + return v + } + return nil +} + +// CheckVolume returns True if the location exists in the image's list of locations +// which should be mounted from outside of the container when a container +// based on an image built from this container is run + +func (b *Builder) CheckVolume(v string) bool { + _, OCIv1Volume := b.OCIv1.Config.Volumes[v] + _, DockerVolume := b.Docker.Config.Volumes[v] + return OCIv1Volume || DockerVolume +} + +// AddVolume adds a location to the image's list of locations which should be +// mounted from outside of the container when a container based on an image +// built from this container is run. +func (b *Builder) AddVolume(v string) { + if b.OCIv1.Config.Volumes == nil { + b.OCIv1.Config.Volumes = map[string]struct{}{} + } + b.OCIv1.Config.Volumes[v] = struct{}{} + if b.Docker.Config.Volumes == nil { + b.Docker.Config.Volumes = map[string]struct{}{} + } + b.Docker.Config.Volumes[v] = struct{}{} +} + +// RemoveVolume removes a location from the list of locations which should be +// mounted from outside of the container when a container based on an image +// built from this container is run. +func (b *Builder) RemoveVolume(v string) { + delete(b.OCIv1.Config.Volumes, v) + delete(b.Docker.Config.Volumes, v) +} + +// ClearVolumes removes all locations from the image's list of locations which +// should be mounted from outside of the container when a container based on an +// image built from this container is run. +func (b *Builder) ClearVolumes() { + b.OCIv1.Config.Volumes = map[string]struct{}{} + b.Docker.Config.Volumes = map[string]struct{}{} +} + +// Hostname returns the hostname which will be set in the container and in +// containers built using images built from the container. +func (b *Builder) Hostname() string { + return b.Docker.Config.Hostname +} + +// SetHostname sets the hostname which will be set in the container and in +// containers built using images built from the container. +// Note: this setting is not present in the OCIv1 image format, so it is +// discarded when writing images using OCIv1 formats. +func (b *Builder) SetHostname(name string) { + b.Docker.Config.Hostname = name +} + +// Domainname returns the domainname which will be set in the container and in +// containers built using images built from the container. +func (b *Builder) Domainname() string { + return b.Docker.Config.Domainname +} + +// SetDomainname sets the domainname which will be set in the container and in +// containers built using images built from the container. +// Note: this setting is not present in the OCIv1 image format, so it is +// discarded when writing images using OCIv1 formats. +func (b *Builder) SetDomainname(name string) { + if name != "" && b.Format != define.Dockerv2ImageManifest { + b.Logger.Warnf("DOMAINNAME is not supported for OCI image format, domainname %s will be ignored. Must use `docker` format", name) + } + b.Docker.Config.Domainname = name +} + +// SetDefaultMountsFilePath sets the mounts file path for testing purposes +func (b *Builder) SetDefaultMountsFilePath(path string) { + b.DefaultMountsFilePath = path +} + +// Comment returns the comment which will be set in the container and in +// containers built using images built from the container +func (b *Builder) Comment() string { + return b.Docker.Comment +} + +// SetComment sets the comment which will be set in the container and in +// containers built using images built from the container. +// Note: this setting is not present in the OCIv1 image format, so it is +// discarded when writing images using OCIv1 formats. +func (b *Builder) SetComment(comment string) { + if comment != "" && b.Format != define.Dockerv2ImageManifest { + logrus.Warnf("COMMENT is not supported for OCI image format, comment %s will be ignored. Must use `docker` format", comment) + } + b.Docker.Comment = comment +} + +// HistoryComment returns the comment which will be used in the history item +// which will describe the latest layer when we commit an image. +func (b *Builder) HistoryComment() string { + return b.ImageHistoryComment +} + +// SetHistoryComment sets the comment which will be used in the history item +// which will describe the latest layer when we commit an image. +func (b *Builder) SetHistoryComment(comment string) { + b.ImageHistoryComment = comment +} + +// StopSignal returns the signal which will be set in the container and in +// containers built using images built from the container +func (b *Builder) StopSignal() string { + return b.Docker.Config.StopSignal +} + +// SetStopSignal sets the signal which will be set in the container and in +// containers built using images built from the container. +func (b *Builder) SetStopSignal(stopSignal string) { + b.OCIv1.Config.StopSignal = stopSignal + b.Docker.Config.StopSignal = stopSignal +} + +// Healthcheck returns information that recommends how a container engine +// should check if a running container is "healthy". +func (b *Builder) Healthcheck() *docker.HealthConfig { + if b.Docker.Config.Healthcheck == nil { + return nil + } + return &docker.HealthConfig{ + Test: slices.Clone(b.Docker.Config.Healthcheck.Test), + Interval: b.Docker.Config.Healthcheck.Interval, + Timeout: b.Docker.Config.Healthcheck.Timeout, + StartPeriod: b.Docker.Config.Healthcheck.StartPeriod, + StartInterval: b.Docker.Config.Healthcheck.StartInterval, + Retries: b.Docker.Config.Healthcheck.Retries, + } +} + +// SetHealthcheck sets recommended commands to run in order to verify that a +// running container based on this image is "healthy", along with information +// specifying how often that test should be run, and how many times the test +// should fail before the container should be considered unhealthy. +// Note: this setting is not present in the OCIv1 image format, so it is +// discarded when writing images using OCIv1 formats. +func (b *Builder) SetHealthcheck(config *docker.HealthConfig) { + b.Docker.Config.Healthcheck = nil + if config != nil { + if b.Format != define.Dockerv2ImageManifest { + b.Logger.Warnf("HEALTHCHECK is not supported for OCI image format and will be ignored. Must use `docker` format") + } + b.Docker.Config.Healthcheck = &docker.HealthConfig{ + Test: slices.Clone(config.Test), + Interval: config.Interval, + Timeout: config.Timeout, + StartPeriod: config.StartPeriod, + StartInterval: config.StartInterval, + Retries: config.Retries, + } + } +} + +// AddPrependedEmptyLayer adds an item to the history that we'll create when +// committing the image, after any history we inherit from the base image, but +// before the history item that we'll use to describe the new layer that we're +// adding. +func (b *Builder) AddPrependedEmptyLayer(created *time.Time, createdBy, author, comment string) { + if created != nil { + copiedTimestamp := *created + created = &copiedTimestamp + } + b.PrependedEmptyLayers = append(b.PrependedEmptyLayers, ociv1.History{ + Created: created, + CreatedBy: createdBy, + Author: author, + Comment: comment, + EmptyLayer: true, + }) +} + +// ClearPrependedEmptyLayers clears the list of history entries that we'll add +// to the committed image before the entry for the layer that we're adding. +func (b *Builder) ClearPrependedEmptyLayers() { + b.PrependedEmptyLayers = nil +} + +// AddAppendedEmptyLayer adds an item to the history that we'll create when +// committing the image, after the history item that we'll use to describe the +// new layer that we're adding. +func (b *Builder) AddAppendedEmptyLayer(created *time.Time, createdBy, author, comment string) { + if created != nil { + copiedTimestamp := *created + created = &copiedTimestamp + } + b.AppendedEmptyLayers = append(b.AppendedEmptyLayers, ociv1.History{ + Created: created, + CreatedBy: createdBy, + Author: author, + Comment: comment, + EmptyLayer: true, + }) +} + +// ClearAppendedEmptyLayers clears the list of history entries that we'll add +// to the committed image after the entry for the layer that we're adding. +func (b *Builder) ClearAppendedEmptyLayers() { + b.AppendedEmptyLayers = nil +} + +// AddPrependedLinkedLayer adds an item to the history that we'll create when +// committing the image, optionally with a layer, after any history we inherit +// from the base image, but before the history item that we'll use to describe +// the new layer that we're adding. +// The blobPath can be either the location of an uncompressed archive, or a +// directory whose contents will be archived to use as a layer blob. Leaving +// blobPath empty is functionally similar to calling AddPrependedEmptyLayer(). +func (b *Builder) AddPrependedLinkedLayer(created *time.Time, createdBy, author, comment, blobPath string) { + if created != nil { + copiedTimestamp := *created + created = &copiedTimestamp + } + b.PrependedLinkedLayers = append(b.PrependedLinkedLayers, LinkedLayer{ + BlobPath: blobPath, + History: ociv1.History{ + Created: created, + CreatedBy: createdBy, + Author: author, + Comment: comment, + EmptyLayer: blobPath == "", + }, + }) +} + +// ClearPrependedLinkedLayers clears the list of history entries that we'll add +// the committed image before the layer that we're adding (if we're adding it). +func (b *Builder) ClearPrependedLinkedLayers() { + b.PrependedLinkedLayers = nil +} + +// AddAppendedLinkedLayer adds an item to the history that we'll create when +// committing the image, optionally with a layer, after the history item that +// we'll use to describe the new layer that we're adding. +// The blobPath can be either the location of an uncompressed archive, or a +// directory whose contents will be archived to use as a layer blob. Leaving +// blobPath empty is functionally similar to calling AddAppendedEmptyLayer(). +func (b *Builder) AddAppendedLinkedLayer(created *time.Time, createdBy, author, comment, blobPath string) { + if created != nil { + copiedTimestamp := *created + created = &copiedTimestamp + } + b.AppendedLinkedLayers = append(b.AppendedLinkedLayers, LinkedLayer{ + BlobPath: blobPath, + History: ociv1.History{ + Created: created, + CreatedBy: createdBy, + Author: author, + Comment: comment, + EmptyLayer: blobPath == "", + }, + }) +} + +// ClearAppendedLinkedLayers clears the list of linked layers that we'll add to +// the committed image after the layer that we're adding (if we're adding it). +func (b *Builder) ClearAppendedLinkedLayers() { + b.AppendedLinkedLayers = nil +} diff --git a/vendor/github.com/containers/buildah/convertcw.go b/vendor/github.com/containers/buildah/convertcw.go new file mode 100644 index 00000000..0793838b --- /dev/null +++ b/vendor/github.com/containers/buildah/convertcw.go @@ -0,0 +1,220 @@ +package buildah + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal/mkcw" + encconfig "github.com/containers/ocicrypt/config" + "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/archive" +) + +// CWConvertImageOptions provides both required and optional bits of +// configuration for CWConvertImage(). +type CWConvertImageOptions struct { + // Required parameters. + InputImage string + + // If supplied, we'll tag the resulting image with the specified name. + Tag string + OutputImage types.ImageReference + + // If supplied, we'll register the workload with this server. + // Practically necessary if DiskEncryptionPassphrase is not set, in + // which case we'll generate one and throw it away after. + AttestationURL string + + // Used to measure the environment. If left unset (0), defaults will be applied. + CPUs int + Memory int + + // Can be manually set. If left unset ("", false, nil), reasonable values will be used. + TeeType define.TeeType + IgnoreAttestationErrors bool + WorkloadID string + DiskEncryptionPassphrase string + Slop string + FirmwareLibrary string + BaseImage string + Logger *logrus.Logger + ExtraImageContent map[string]string + + // Passed through to BuilderOptions. Most settings won't make + // sense to be made available here because we don't launch a process. + ContainerSuffix string + PullPolicy PullPolicy + BlobDirectory string + SignaturePolicyPath string + ReportWriter io.Writer + IDMappingOptions *IDMappingOptions + Format string + MaxPullRetries int + PullRetryDelay time.Duration + OciDecryptConfig *encconfig.DecryptConfig + MountLabel string +} + +// CWConvertImage takes the rootfs and configuration from one image, generates a +// LUKS-encrypted disk image that more or less includes them both, and puts the +// result into a new container image. +// Returns the new image's ID and digest on success, along with a canonical +// reference for it if a repository name was specified. +func CWConvertImage(ctx context.Context, systemContext *types.SystemContext, store storage.Store, options CWConvertImageOptions) (string, reference.Canonical, digest.Digest, error) { + // Apply our defaults if some options aren't set. + logger := options.Logger + if logger == nil { + logger = logrus.StandardLogger() + } + + // Now create the target working container, pulling the base image if + // there is one and it isn't present. + builderOptions := BuilderOptions{ + FromImage: options.BaseImage, + SystemContext: systemContext, + Logger: logger, + + ContainerSuffix: options.ContainerSuffix, + PullPolicy: options.PullPolicy, + BlobDirectory: options.BlobDirectory, + SignaturePolicyPath: options.SignaturePolicyPath, + ReportWriter: options.ReportWriter, + IDMappingOptions: options.IDMappingOptions, + Format: options.Format, + MaxPullRetries: options.MaxPullRetries, + PullRetryDelay: options.PullRetryDelay, + OciDecryptConfig: options.OciDecryptConfig, + MountLabel: options.MountLabel, + } + target, err := NewBuilder(ctx, store, builderOptions) + if err != nil { + return "", nil, "", fmt.Errorf("creating container from target image: %w", err) + } + defer func() { + if err := target.Delete(); err != nil { + logrus.Warnf("deleting target container: %v", err) + } + }() + targetDir, err := target.Mount("") + if err != nil { + return "", nil, "", fmt.Errorf("mounting target container: %w", err) + } + defer func() { + if err := target.Unmount(); err != nil { + logrus.Warnf("unmounting target container: %v", err) + } + }() + + // Mount the source image, pulling it first if necessary. + builderOptions = BuilderOptions{ + FromImage: options.InputImage, + SystemContext: systemContext, + Logger: logger, + + ContainerSuffix: options.ContainerSuffix, + PullPolicy: options.PullPolicy, + BlobDirectory: options.BlobDirectory, + SignaturePolicyPath: options.SignaturePolicyPath, + ReportWriter: options.ReportWriter, + IDMappingOptions: options.IDMappingOptions, + Format: options.Format, + MaxPullRetries: options.MaxPullRetries, + PullRetryDelay: options.PullRetryDelay, + OciDecryptConfig: options.OciDecryptConfig, + MountLabel: options.MountLabel, + } + source, err := NewBuilder(ctx, store, builderOptions) + if err != nil { + return "", nil, "", fmt.Errorf("creating container from source image: %w", err) + } + defer func() { + if err := source.Delete(); err != nil { + logrus.Warnf("deleting source container: %v", err) + } + }() + sourceInfo := GetBuildInfo(source) + if err != nil { + return "", nil, "", fmt.Errorf("retrieving info about source image: %w", err) + } + sourceImageID := sourceInfo.FromImageID + sourceSize, err := store.ImageSize(sourceImageID) + if err != nil { + return "", nil, "", fmt.Errorf("computing size of source image: %w", err) + } + sourceDir, err := source.Mount("") + if err != nil { + return "", nil, "", fmt.Errorf("mounting source container: %w", err) + } + defer func() { + if err := source.Unmount(); err != nil { + logrus.Warnf("unmounting source container: %v", err) + } + }() + + // Generate the image contents. + archiveOptions := mkcw.ArchiveOptions{ + AttestationURL: options.AttestationURL, + CPUs: options.CPUs, + Memory: options.Memory, + TempDir: targetDir, + TeeType: options.TeeType, + IgnoreAttestationErrors: options.IgnoreAttestationErrors, + ImageSize: sourceSize, + WorkloadID: options.WorkloadID, + DiskEncryptionPassphrase: options.DiskEncryptionPassphrase, + Slop: options.Slop, + FirmwareLibrary: options.FirmwareLibrary, + Logger: logger, + GraphOptions: store.GraphOptions(), + ExtraImageContent: options.ExtraImageContent, + } + rc, workloadConfig, err := mkcw.Archive(sourceDir, &source.OCIv1, archiveOptions) + if err != nil { + return "", nil, "", fmt.Errorf("generating encrypted image content: %w", err) + } + if err = archive.Untar(rc, targetDir, &archive.TarOptions{}); err != nil { + if err = rc.Close(); err != nil { + logger.Warnf("cleaning up: %v", err) + } + return "", nil, "", fmt.Errorf("saving encrypted image content: %w", err) + } + if err = rc.Close(); err != nil { + return "", nil, "", fmt.Errorf("cleaning up: %w", err) + } + + // Commit the image. Clear out most of the configuration (if there is any — we default + // to scratch as a base) so that an engine that doesn't or can't set up a TEE will just + // run the static entrypoint. The rest of the configuration which the runtime consults + // is in the .krun_config.json file in the encrypted filesystem. + logger.Log(logrus.DebugLevel, "committing disk image") + target.ClearAnnotations() + target.ClearEnv() + target.ClearLabels() + target.ClearOnBuild() + target.ClearPorts() + target.ClearVolumes() + target.SetCmd(nil) + target.SetCreatedBy(fmt.Sprintf(": convert %q for use with %q", sourceImageID, workloadConfig.Type)) + target.SetDomainname("") + target.SetEntrypoint([]string{"/entrypoint"}) + target.SetHealthcheck(nil) + target.SetHostname("") + target.SetMaintainer("") + target.SetShell(nil) + target.SetUser("") + target.SetWorkDir("") + commitOptions := CommitOptions{ + SystemContext: systemContext, + } + if options.Tag != "" { + commitOptions.AdditionalTags = append(commitOptions.AdditionalTags, options.Tag) + } + return target.Commit(ctx, options.OutputImage, commitOptions) +} diff --git a/vendor/github.com/containers/buildah/copier/copier.go b/vendor/github.com/containers/buildah/copier/copier.go new file mode 100644 index 00000000..9aa662cb --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/copier.go @@ -0,0 +1,2560 @@ +package copier + +import ( + "archive/tar" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "net" + "os" + "os/user" + "path" + "path/filepath" + "slices" + "sort" + "strconv" + "strings" + "sync" + "syscall" + "time" + "unicode" + + "github.com/sirupsen/logrus" + "go.podman.io/image/v5/pkg/compression" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/reexec" +) + +const ( + copierCommand = "buildah-copier" + maxLoopsFollowed = 64 + // See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06, from archive/tar + cISUID = 0o4000 // Set uid, from archive/tar + cISGID = 0o2000 // Set gid, from archive/tar + cISVTX = 0o1000 // Save text (sticky bit), from archive/tar + // xattrs in the PAXRecords map are namespaced with this prefix + xattrPAXRecordNamespace = "SCHILY.xattr." +) + +func init() { + reexec.Register(copierCommand, copierMain) +} + +// extendedGlob calls filepath.Glob() on the passed-in patterns. If there is a +// "**" component in the pattern, filepath.Glob() will be called with the "**" +// replaced with all of the subdirectories under that point, and the results +// will be concatenated. +// The matched paths are returned in lexical order, which makes the output deterministic. +func extendedGlob(pattern string) (matches []string, err error) { + subdirs := func(dir string) []string { + var subdirectories []string + if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return nil + } + if d.IsDir() { + if rel, err := filepath.Rel(dir, path); err == nil { + subdirectories = append(subdirectories, rel) + } + } + return nil + }); err != nil { + subdirectories = []string{"."} + } + return subdirectories + } + expandPatterns := func(pattern string) []string { + components := []string{} + dir := pattern + file := "" + for dir != filepath.VolumeName(dir) && dir != string(os.PathSeparator) { + dir, file = filepath.Split(dir) + if file != "" { + components = append([]string{file}, components...) + } + dir = strings.TrimSuffix(dir, string(os.PathSeparator)) + } + patterns := []string{filepath.VolumeName(dir) + string(os.PathSeparator)} + for i := range components { + var nextPatterns []string + if components[i] == "**" { + for _, parent := range patterns { + nextSubdirs := subdirs(parent) + for _, nextSubdir := range nextSubdirs { + nextPatterns = append(nextPatterns, filepath.Join(parent, nextSubdir)) + } + } + } else { + for _, parent := range patterns { + nextPattern := filepath.Join(parent, components[i]) + nextPatterns = append(nextPatterns, nextPattern) + } + } + patterns = []string{} + seen := map[string]struct{}{} + for _, nextPattern := range nextPatterns { + if _, seen := seen[nextPattern]; seen { + continue + } + patterns = append(patterns, nextPattern) + seen[nextPattern] = struct{}{} + } + } + return patterns + } + patterns := expandPatterns(pattern) + for _, pattern := range patterns { + theseMatches, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + matches = append(matches, theseMatches...) + } + sort.Strings(matches) + return matches, nil +} + +// isArchivePath returns true if the specified path can be read like a (possibly +// compressed) tarball. +func isArchivePath(path string) bool { + f, err := os.Open(path) + if err != nil { + return false + } + defer f.Close() + rc, _, err := compression.AutoDecompress(f) + if err != nil { + return false + } + defer rc.Close() + tr := tar.NewReader(rc) + _, err = tr.Next() + return err == nil +} + +// requestType encodes exactly what kind of request this is. +type requestType string + +const ( + requestEval requestType = "EVAL" + requestStat requestType = "STAT" + requestGet requestType = "GET" + requestPut requestType = "PUT" + requestMkdir requestType = "MKDIR" + requestRemove requestType = "REMOVE" + requestQuit requestType = "QUIT" + requestEnsure requestType = "ENSURE" + requestConditionalRemove requestType = "CONDRM" +) + +// Request encodes a single request. +type request struct { + Request requestType + Root string // used by all requests + preservedRoot string + rootPrefix string // used to reconstruct paths being handed back to the caller + Directory string // used by all requests + preservedDirectory string + Globs []string `json:",omitempty"` // used by stat, get + preservedGlobs []string + StatOptions StatOptions + GetOptions GetOptions + PutOptions PutOptions + MkdirOptions MkdirOptions + RemoveOptions RemoveOptions + EnsureOptions EnsureOptions + ConditionalRemoveOptions ConditionalRemoveOptions +} + +func (req *request) Excludes() []string { + switch req.Request { + case requestEval: + return nil + case requestStat: + return req.StatOptions.Excludes + case requestGet: + return req.GetOptions.Excludes + case requestPut: + return nil + case requestMkdir: + return nil + case requestRemove: + return nil + case requestQuit: + return nil + case requestEnsure: + return nil + case requestConditionalRemove: + return nil + default: + panic(fmt.Sprintf("not an implemented request type: %q", req.Request)) + } +} + +func (req *request) UIDMap() []idtools.IDMap { + switch req.Request { + case requestEval: + return nil + case requestStat: + return req.StatOptions.UIDMap + case requestGet: + return req.GetOptions.UIDMap + case requestPut: + return req.PutOptions.UIDMap + case requestMkdir: + return req.MkdirOptions.UIDMap + case requestRemove: + return nil + case requestQuit: + return nil + case requestEnsure: + return req.EnsureOptions.UIDMap + case requestConditionalRemove: + return req.ConditionalRemoveOptions.UIDMap + default: + panic(fmt.Sprintf("not an implemented request type: %q", req.Request)) + } +} + +func (req *request) GIDMap() []idtools.IDMap { + switch req.Request { + case requestEval: + return nil + case requestStat: + return req.StatOptions.GIDMap + case requestGet: + return req.GetOptions.GIDMap + case requestPut: + return req.PutOptions.GIDMap + case requestMkdir: + return req.MkdirOptions.GIDMap + case requestRemove: + return nil + case requestQuit: + return nil + case requestEnsure: + return req.EnsureOptions.GIDMap + case requestConditionalRemove: + return req.ConditionalRemoveOptions.GIDMap + default: + panic(fmt.Sprintf("not an implemented request type: %q", req.Request)) + } +} + +// Response encodes a single response. +type response struct { + Error string `json:",omitempty"` + Stat statResponse + Eval evalResponse + Get getResponse + Put putResponse + Mkdir mkdirResponse + Remove removeResponse + Ensure ensureResponse + ConditionalRemove conditionalRemoveResponse +} + +// statResponse encodes a response for a single Stat request. +type statResponse struct { + Globs []*StatsForGlob +} + +// evalResponse encodes a response for a single Eval request. +type evalResponse struct { + Evaluated string +} + +// StatsForGlob encode results for a single glob pattern passed to Stat(). +type StatsForGlob struct { + Error string `json:",omitempty"` // error if the Glob pattern was malformed + Glob string // input pattern to which this result corresponds + Globbed []string // a slice of zero or more names that match the glob + Results map[string]*StatForItem // one for each Globbed value if there are any, or for Glob +} + +// StatForItem encode results for a single filesystem item, as returned by Stat(). +type StatForItem struct { + Error string `json:",omitempty"` + Name string + Size int64 // dereferenced value for symlinks + Mode os.FileMode // dereferenced value for symlinks + ModTime time.Time // dereferenced value for symlinks + UID, GID int64 // usually in the uint32 range, set to -1 if unknown + IsSymlink bool + IsDir bool // dereferenced value for symlinks + IsRegular bool // dereferenced value for symlinks + IsArchive bool // dereferenced value for symlinks + ImmediateTarget string `json:",omitempty"` // raw link content +} + +// getResponse encodes a response for a single Get request. +type getResponse struct{} + +// putResponse encodes a response for a single Put request. +type putResponse struct{} + +// mkdirResponse encodes a response for a single Mkdir request. +type mkdirResponse struct{} + +// removeResponse encodes a response for a single Remove request. +type removeResponse struct{} + +// ensureResponse encodes a response to an Ensure request. +type ensureResponse struct { + Created []string // paths that were created because they weren't already present + Noted []EnsureParentPath // preexisting paths that are parents of created items +} + +// conditionalRemoveResponse encodes a response to a conditionalRemove request. +type conditionalRemoveResponse struct { + Removed []string // paths that were removed +} + +// EvalOptions controls parts of Eval()'s behavior. +type EvalOptions struct{} + +// Eval evaluates the directory's path, including any intermediate symbolic +// links. +// If root is specified and the current OS supports it, and the calling process +// has the necessary privileges, evaluation is performed in a chrooted context. +// If the directory is specified as an absolute path, it should either be the +// root directory or a subdirectory of the root directory. Otherwise, the +// directory is treated as a path relative to the root directory. +func Eval(root string, directory string, _ EvalOptions) (string, error) { + req := request{ + Request: requestEval, + Root: root, + Directory: directory, + } + resp, err := copier(nil, nil, req) + if err != nil { + return "", err + } + if resp.Error != "" { + return "", errors.New(resp.Error) + } + return resp.Eval.Evaluated, nil +} + +// StatOptions controls parts of Stat()'s behavior. +type StatOptions struct { + UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs when returning results + CheckForArchives bool // check for and populate the IsArchive bit in returned values + Excludes []string // contents to pretend don't exist, using the OS-specific path separator +} + +// Stat globs the specified pattern in the specified directory and returns its +// results. +// If root and directory are both not specified, the current root directory is +// used, and relative names in the globs list are treated as being relative to +// the current working directory. +// If root is specified and the current OS supports it, and the calling process +// has the necessary privileges, the stat() is performed in a chrooted context. +// If the directory is specified as an absolute path, it should either be the +// root directory or a subdirectory of the root directory. Otherwise, the +// directory is treated as a path relative to the root directory. +// Relative names in the glob list are treated as being relative to the +// directory. +func Stat(root string, directory string, options StatOptions, globs []string) ([]*StatsForGlob, error) { + req := request{ + Request: requestStat, + Root: root, + Directory: directory, + Globs: slices.Clone(globs), + StatOptions: options, + } + resp, err := copier(nil, nil, req) + if err != nil { + return nil, err + } + if resp.Error != "" { + return nil, errors.New(resp.Error) + } + return resp.Stat.Globs, nil +} + +// GetOptions controls parts of Get()'s behavior. +type GetOptions struct { + UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs in the output archive + Excludes []string // contents to pretend don't exist, using the OS-specific path separator + ExpandArchives bool // extract the contents of named items that are archives + ChownDirs *idtools.IDPair // set ownership on directories. no effect on archives being extracted + ChmodDirs *os.FileMode // set permissions on directories. no effect on archives being extracted + ChownFiles *idtools.IDPair // set ownership of files. no effect on archives being extracted + ChmodFiles *os.FileMode // set permissions on files. no effect on archives being extracted + Parents bool // maintain the sources parent directory in the destination + StripSetuidBit bool // strip the setuid bit off of items being copied. no effect on archives being extracted + StripSetgidBit bool // strip the setgid bit off of items being copied. no effect on archives being extracted + StripStickyBit bool // strip the sticky bit off of items being copied. no effect on archives being extracted + StripXattrs bool // don't record extended attributes of items being copied. no effect on archives being extracted + KeepDirectoryNames bool // don't strip the top directory's basename from the paths of items in subdirectories + Rename map[string]string // rename items with the specified names, or under the specified names + NoDerefSymlinks bool // don't follow symlinks when globs match them + IgnoreUnreadable bool // ignore errors reading items, instead of returning an error + NoCrossDevice bool // if a subdirectory is a mountpoint with a different device number, include it but skip its contents + Timestamp *time.Time // timestamp to force on all contents +} + +// Get produces an archive containing items that match the specified glob +// patterns and writes it to bulkWriter. +// If root and directory are both not specified, the current root directory is +// used, and relative names in the globs list are treated as being relative to +// the current working directory. +// If root is specified and the current OS supports it, and the calling process +// has the necessary privileges, the contents are read in a chrooted context. +// If the directory is specified as an absolute path, it should either be the +// root directory or a subdirectory of the root directory. Otherwise, the +// directory is treated as a path relative to the root directory. +// Relative names in the glob list are treated as being relative to the +// directory. +func Get(root string, directory string, options GetOptions, globs []string, bulkWriter io.Writer) error { + req := request{ + Request: requestGet, + Root: root, + Directory: directory, + Globs: slices.Clone(globs), + StatOptions: StatOptions{ + CheckForArchives: options.ExpandArchives, + }, + GetOptions: options, + } + resp, err := copier(nil, bulkWriter, req) + if err != nil { + return err + } + if resp.Error != "" { + return errors.New(resp.Error) + } + return nil +} + +// PutOptions controls parts of Put()'s behavior. +type PutOptions struct { + UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs when writing contents to disk + DefaultDirOwner *idtools.IDPair // set ownership of implicitly-created directories, default is ChownDirs, or 0:0 if ChownDirs not set + DefaultDirMode *os.FileMode // set permissions on implicitly-created directories, default is ChmodDirs, or 0755 if ChmodDirs not set + ChownDirs *idtools.IDPair // set ownership of newly-created directories + ChmodDirs *os.FileMode // set permissions on newly-created directories + ChownFiles *idtools.IDPair // set ownership of newly-created files + ChmodFiles *os.FileMode // set permissions on newly-created files + StripSetuidBit bool // strip the setuid bit off of items being written + StripSetgidBit bool // strip the setgid bit off of items being written + StripStickyBit bool // strip the sticky bit off of items being written + StripXattrs bool // don't bother trying to set extended attributes of items being copied + IgnoreXattrErrors bool // ignore any errors encountered when attempting to set extended attributes + IgnoreDevices bool // ignore items which are character or block devices + NoOverwriteDirNonDir bool // instead of quietly overwriting directories with non-directories, return an error + NoOverwriteNonDirDir bool // instead of quietly overwriting non-directories with directories, return an error + Rename map[string]string // rename items with the specified names, or under the specified names +} + +// Put extracts an archive from the bulkReader at the specified directory. +// If root and directory are both not specified, the current root directory is +// used. +// If root is specified and the current OS supports it, and the calling process +// has the necessary privileges, the contents are written in a chrooted +// context. If the directory is specified as an absolute path, it should +// either be the root directory or a subdirectory of the root directory. +// Otherwise, the directory is treated as a path relative to the root +// directory. +func Put(root string, directory string, options PutOptions, bulkReader io.Reader) error { + req := request{ + Request: requestPut, + Root: root, + Directory: directory, + PutOptions: options, + } + resp, err := copier(bulkReader, nil, req) + if err != nil { + return err + } + if resp.Error != "" { + return errors.New(resp.Error) + } + return nil +} + +// MkdirOptions controls parts of Mkdir()'s behavior. +type MkdirOptions struct { + UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs when creating directories + ModTimeNew *time.Time // set mtime and atime of newly-created directories + ChownNew *idtools.IDPair // set ownership of newly-created directories + ChmodNew *os.FileMode // set permissions on newly-created directories +} + +// Mkdir ensures that the specified directory exists. Any directories which +// need to be created will be given the specified ownership and permissions. +// If root and directory are both not specified, the current root directory is +// used. +// If root is specified and the current OS supports it, and the calling process +// has the necessary privileges, the directory is created in a chrooted +// context. If the directory is specified as an absolute path, it should +// either be the root directory or a subdirectory of the root directory. +// Otherwise, the directory is treated as a path relative to the root +// directory. +func Mkdir(root string, directory string, options MkdirOptions) error { + req := request{ + Request: requestMkdir, + Root: root, + Directory: directory, + MkdirOptions: options, + } + resp, err := copier(nil, nil, req) + if err != nil { + return err + } + if resp.Error != "" { + return errors.New(resp.Error) + } + return nil +} + +// RemoveOptions controls parts of Remove()'s behavior. +type RemoveOptions struct { + All bool // if Directory is a directory, remove its contents as well +} + +// Remove removes the specified directory or item, traversing any intermediate +// symbolic links. +// If the root directory is not specified, the current root directory is used. +// If root is specified and the current OS supports it, and the calling process +// has the necessary privileges, the remove() is performed in a chrooted context. +// If the item to remove is specified as an absolute path, it should either be +// in the root directory or in a subdirectory of the root directory. Otherwise, +// the directory is treated as a path relative to the root directory. +func Remove(root string, item string, options RemoveOptions) error { + req := request{ + Request: requestRemove, + Root: root, + Directory: item, + RemoveOptions: options, + } + resp, err := copier(nil, nil, req) + if err != nil { + return err + } + if resp.Error != "" { + return errors.New(resp.Error) + } + return nil +} + +// cleanerReldirectory resolves relative path candidate lexically, attempting +// to ensure that when joined as a subdirectory of another directory, it does +// not reference anything outside of that other directory. +func cleanerReldirectory(candidate string) string { + cleaned := strings.TrimPrefix(filepath.Clean(string(os.PathSeparator)+candidate), string(os.PathSeparator)) + if cleaned == "" { + return "." + } + return cleaned +} + +// convertToRelSubdirectory returns the path of directory, bound and relative to +// root, as a relative path, or an error if that path can't be computed or if +// the two directories are on different volumes +func convertToRelSubdirectory(root, directory string) (relative string, err error) { + if root == "" || !filepath.IsAbs(root) { + return "", fmt.Errorf("expected root directory to be an absolute path, got %q", root) + } + if directory == "" || !filepath.IsAbs(directory) { + return "", fmt.Errorf("expected directory to be an absolute path, got %q", root) + } + if filepath.VolumeName(root) != filepath.VolumeName(directory) { + return "", fmt.Errorf("%q and %q are on different volumes", root, directory) + } + rel, err := filepath.Rel(root, directory) + if err != nil { + return "", fmt.Errorf("computing path of %q relative to %q: %w", directory, root, err) + } + return cleanerReldirectory(rel), nil +} + +func currentVolumeRoot() (string, error) { + cwd, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("getting current working directory: %w", err) + } + return filepath.VolumeName(cwd) + string(os.PathSeparator), nil +} + +func isVolumeRoot(candidate string) (bool, error) { + abs, err := filepath.Abs(candidate) + if err != nil { + return false, fmt.Errorf("converting %q to an absolute path: %w", candidate, err) + } + return abs == filepath.VolumeName(abs)+string(os.PathSeparator), nil +} + +func looksLikeAbs(candidate string) bool { + return candidate[0] == os.PathSeparator && (len(candidate) == 1 || candidate[1] != os.PathSeparator) +} + +func copier(bulkReader io.Reader, bulkWriter io.Writer, req request) (*response, error) { + if req.Directory == "" { + if req.Root == "" { + wd, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("getting current working directory: %w", err) + } + req.Directory = wd + } else { + req.Directory = req.Root + } + } + if req.Root == "" { + root, err := currentVolumeRoot() + if err != nil { + return nil, fmt.Errorf("determining root of current volume: %w", err) + } + req.Root = root + } + if filepath.IsAbs(req.Directory) { + _, err := convertToRelSubdirectory(req.Root, req.Directory) + if err != nil { + return nil, fmt.Errorf("rewriting %q to be relative to %q: %w", req.Directory, req.Root, err) + } + } + isAlreadyRoot, err := isVolumeRoot(req.Root) + if err != nil { + return nil, fmt.Errorf("checking if %q is a root directory: %w", req.Root, err) + } + if !isAlreadyRoot && canChroot { + return copierWithSubprocess(bulkReader, bulkWriter, req) + } + return copierWithoutSubprocess(bulkReader, bulkWriter, req) +} + +func copierWithoutSubprocess(bulkReader io.Reader, bulkWriter io.Writer, req request) (*response, error) { + req.preservedRoot = req.Root + req.rootPrefix = string(os.PathSeparator) + req.preservedDirectory = req.Directory + req.preservedGlobs = slices.Clone(req.Globs) + if !filepath.IsAbs(req.Directory) { + req.Directory = filepath.Join(req.Root, cleanerReldirectory(req.Directory)) + } + absoluteGlobs := make([]string, 0, len(req.Globs)) + for _, glob := range req.preservedGlobs { + if filepath.IsAbs(glob) { + relativeGlob, err := convertToRelSubdirectory(req.preservedRoot, glob) + if err != nil { + fmt.Fprintf(os.Stderr, "error rewriting %q to be relative to %q: %v", glob, req.preservedRoot, err) + os.Exit(1) + } + absoluteGlobs = append(absoluteGlobs, filepath.Join(req.Root, string(os.PathSeparator)+relativeGlob)) + } else { + absoluteGlobs = append(absoluteGlobs, filepath.Join(req.Directory, cleanerReldirectory(glob))) + } + } + req.Globs = absoluteGlobs + resp, cb, err := copierHandler(bulkReader, bulkWriter, req) + if err != nil { + return nil, err + } + if cb != nil { + if err = cb(); err != nil { + return nil, err + } + } + return resp, nil +} + +func closeIfNotNilYet(f **os.File, what string) { + if f != nil && *f != nil { + err := (*f).Close() + *f = nil + if err != nil { + logrus.Debugf("error closing %s: %v", what, err) + } + } +} + +func copierWithSubprocess(bulkReader io.Reader, bulkWriter io.Writer, req request) (resp *response, err error) { + if bulkReader == nil { + bulkReader = bytes.NewReader([]byte{}) + } + if bulkWriter == nil { + bulkWriter = io.Discard + } + cmd := reexec.Command(copierCommand) + stdinRead, stdinWrite, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("pipe: %w", err) + } + defer closeIfNotNilYet(&stdinRead, "stdin pipe reader") + defer closeIfNotNilYet(&stdinWrite, "stdin pipe writer") + encoder := json.NewEncoder(stdinWrite) + stdoutRead, stdoutWrite, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("pipe: %w", err) + } + defer closeIfNotNilYet(&stdoutRead, "stdout pipe reader") + defer closeIfNotNilYet(&stdoutWrite, "stdout pipe writer") + decoder := json.NewDecoder(stdoutRead) + bulkReaderRead, bulkReaderWrite, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("pipe: %w", err) + } + defer closeIfNotNilYet(&bulkReaderRead, "child bulk content reader pipe, read end") + defer closeIfNotNilYet(&bulkReaderWrite, "child bulk content reader pipe, write end") + bulkWriterRead, bulkWriterWrite, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("pipe: %w", err) + } + defer closeIfNotNilYet(&bulkWriterRead, "child bulk content writer pipe, read end") + defer closeIfNotNilYet(&bulkWriterWrite, "child bulk content writer pipe, write end") + cmd.Dir = "/" + cmd.Env = append([]string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}, os.Environ()...) + + errorBuffer := bytes.Buffer{} + cmd.Stdin = stdinRead + cmd.Stdout = stdoutWrite + cmd.Stderr = &errorBuffer + cmd.ExtraFiles = []*os.File{bulkReaderRead, bulkWriterWrite} + if err = cmd.Start(); err != nil { + return nil, fmt.Errorf("starting subprocess: %w", err) + } + cmdToWaitFor := cmd + defer func() { + if cmdToWaitFor != nil { + if err := cmdToWaitFor.Wait(); err != nil { + if errorBuffer.String() != "" { + logrus.Debug(errorBuffer.String()) + } + } + } + }() + stdinRead.Close() + stdinRead = nil + stdoutWrite.Close() + stdoutWrite = nil + bulkReaderRead.Close() + bulkReaderRead = nil + bulkWriterWrite.Close() + bulkWriterWrite = nil + killAndReturn := func(err error, step string) error { + if err2 := cmd.Process.Kill(); err2 != nil { + return fmt.Errorf("killing subprocess: %v; %s: %w", err2, step, err) + } + if errors.Is(err, io.ErrClosedPipe) || errors.Is(err, syscall.EPIPE) { + err2 := cmd.Wait() + if errorText := strings.TrimFunc(errorBuffer.String(), unicode.IsSpace); errorText != "" { + err = fmt.Errorf("%s: %w", errorText, err) + } + if err2 != nil { + return fmt.Errorf("waiting on subprocess: %v; %s: %w", err2, step, err) + } + } + return fmt.Errorf("%v: %w", step, err) + } + if err = encoder.Encode(req); err != nil { + return nil, killAndReturn(err, "error encoding work request for copier subprocess") + } + if err = decoder.Decode(&resp); err != nil { + if errors.Is(err, io.EOF) && errorBuffer.Len() > 0 { + return nil, killAndReturn(errors.New(errorBuffer.String()), "error in copier subprocess") + } + return nil, killAndReturn(err, "error decoding response from copier subprocess") + } + if err = encoder.Encode(&request{Request: requestQuit}); err != nil { + return nil, killAndReturn(err, "error encoding quit request for copier subprocess") + } + stdinWrite.Close() + stdinWrite = nil + stdoutRead.Close() + stdoutRead = nil + var wg sync.WaitGroup + var readError, writeError error + wg.Add(1) + go func() { + _, writeError = io.Copy(bulkWriter, bulkWriterRead) + bulkWriterRead.Close() + bulkWriterRead = nil + wg.Done() + }() + wg.Add(1) + go func() { + _, readError = io.Copy(bulkReaderWrite, bulkReader) + bulkReaderWrite.Close() + bulkReaderWrite = nil + wg.Done() + }() + wg.Wait() + cmdToWaitFor = nil + if err = cmd.Wait(); err != nil { + if errorBuffer.String() != "" { + err = fmt.Errorf("%s", errorBuffer.String()) + } + return nil, err + } + if cmd.ProcessState.Exited() && !cmd.ProcessState.Success() { + err = fmt.Errorf("subprocess exited with error") + if errorBuffer.String() != "" { + err = fmt.Errorf("%s", errorBuffer.String()) + } + return nil, err + } + loggedOutput := strings.TrimSuffix(errorBuffer.String(), "\n") + if len(loggedOutput) > 0 { + for output := range strings.SplitSeq(loggedOutput, "\n") { + logrus.Debug(output) + } + } + if readError != nil { + return nil, fmt.Errorf("passing bulk input to subprocess: %w", readError) + } + if writeError != nil { + return nil, fmt.Errorf("passing bulk output from subprocess: %w", writeError) + } + return resp, nil +} + +func copierMain() { + var chrooted bool + decoder := json.NewDecoder(os.Stdin) + encoder := json.NewEncoder(os.Stdout) + previousRequestRoot := "" + + // Attempt a user and host lookup to force libc (glibc, and possibly others that use dynamic + // modules to handle looking up user and host information) to load modules that match the libc + // our binary is currently using. Hopefully they're loaded on first use, so that they won't + // need to be loaded after we've chrooted into the rootfs, which could include modules that + // don't match our libc and which can't be loaded, or modules which we don't want to execute + // because we don't trust their code. + _, _ = user.Lookup("buildah") + _, _ = net.LookupHost("localhost") + + // Set logging. + if level := os.Getenv("LOGLEVEL"); level != "" { + if ll, err := strconv.Atoi(level); err == nil { + logrus.SetLevel(logrus.Level(ll)) + } + } + + // Set up descriptors for receiving and sending tarstreams. + bulkReader := os.NewFile(3, "bulk-reader") + bulkWriter := os.NewFile(4, "bulk-writer") + + for { + // Read a request. + req := new(request) + if err := decoder.Decode(req); err != nil { + fmt.Fprintf(os.Stderr, "error decoding request from copier parent process: %v", err) + os.Exit(1) + } + if req.Request == requestQuit { + // Making Quit a specific request means that we could + // run Stat() at a caller's behest before using the + // same process for Get() or Put(). Maybe later. + break + } + + // Multiple requests should list the same root, because we + // can't un-chroot to chroot to some other location. + if previousRequestRoot != "" { + // Check that we got the same input value for + // where-to-chroot-to. + if req.Root != previousRequestRoot { + fmt.Fprintf(os.Stderr, "error: can't change location of chroot from %q to %q", previousRequestRoot, req.Root) + os.Exit(1) + } + previousRequestRoot = req.Root + } else { + // Figure out where to chroot to, if we weren't told. + if req.Root == "" { + root, err := currentVolumeRoot() + if err != nil { + fmt.Fprintf(os.Stderr, "error determining root of current volume: %v", err) + os.Exit(1) + } + req.Root = root + } + // Change to the specified root directory. + var err error + chrooted, err = chroot(req.Root) + if err != nil { + fmt.Fprintf(os.Stderr, "%v", err) + os.Exit(1) + } + } + + req.preservedRoot = req.Root + req.rootPrefix = string(os.PathSeparator) + req.preservedDirectory = req.Directory + req.preservedGlobs = slices.Clone(req.Globs) + if chrooted { + // We'll need to adjust some things now that the root + // directory isn't what it was. Make the directory and + // globs absolute paths for simplicity's sake. + absoluteDirectory := req.Directory + if !filepath.IsAbs(req.Directory) { + absoluteDirectory = filepath.Join(req.Root, cleanerReldirectory(req.Directory)) + } + relativeDirectory, err := convertToRelSubdirectory(req.preservedRoot, absoluteDirectory) + if err != nil { + fmt.Fprintf(os.Stderr, "error rewriting %q to be relative to %q: %v", absoluteDirectory, req.preservedRoot, err) + os.Exit(1) + } + req.Directory = filepath.Clean(string(os.PathSeparator) + relativeDirectory) + absoluteGlobs := make([]string, 0, len(req.Globs)) + for i, glob := range req.preservedGlobs { + if filepath.IsAbs(glob) { + relativeGlob, err := convertToRelSubdirectory(req.preservedRoot, glob) + if err != nil { + fmt.Fprintf(os.Stderr, "error rewriting %q to be relative to %q: %v", glob, req.preservedRoot, err) + os.Exit(1) + } + absoluteGlobs = append(absoluteGlobs, filepath.Clean(string(os.PathSeparator)+relativeGlob)) + } else { + absoluteGlobs = append(absoluteGlobs, filepath.Join(req.Directory, cleanerReldirectory(req.Globs[i]))) + } + } + req.Globs = absoluteGlobs + req.rootPrefix = req.Root + req.Root = string(os.PathSeparator) + } else { + // Make the directory and globs absolute paths for + // simplicity's sake. + if !filepath.IsAbs(req.Directory) { + req.Directory = filepath.Join(req.Root, cleanerReldirectory(req.Directory)) + } + absoluteGlobs := make([]string, 0, len(req.Globs)) + for i, glob := range req.preservedGlobs { + if filepath.IsAbs(glob) { + absoluteGlobs = append(absoluteGlobs, req.Globs[i]) + } else { + absoluteGlobs = append(absoluteGlobs, filepath.Join(req.Directory, cleanerReldirectory(req.Globs[i]))) + } + } + req.Globs = absoluteGlobs + } + resp, cb, err := copierHandler(bulkReader, bulkWriter, *req) + if err != nil { + fmt.Fprintf(os.Stderr, "error handling request %#v from copier parent process: %v", *req, err) + os.Exit(1) + } + // Encode the response. + if err := encoder.Encode(resp); err != nil { + fmt.Fprintf(os.Stderr, "error encoding response %#v for copier parent process: %v", *req, err) + os.Exit(1) + } + // If there's bulk data to transfer, run the callback to either + // read or write it. + if cb != nil { + if err = cb(); err != nil { + fmt.Fprintf(os.Stderr, "error during bulk transfer for %#v: %v", *req, err) + os.Exit(1) + } + } + } +} + +func copierHandler(bulkReader io.Reader, bulkWriter io.Writer, req request) (*response, func() error, error) { + // NewPatternMatcher splits patterns into components using + // os.PathSeparator, implying that it expects OS-specific naming + // conventions. + excludes := req.Excludes() + pm, err := fileutils.NewPatternMatcher(excludes) + if err != nil { + return nil, nil, fmt.Errorf("processing excludes list %v: %w", excludes, err) + } + + var idMappings *idtools.IDMappings + uidMap, gidMap := req.UIDMap(), req.GIDMap() + if len(uidMap) > 0 && len(gidMap) > 0 { + idMappings = idtools.NewIDMappingsFromMaps(uidMap, gidMap) + } + + switch req.Request { + default: + return nil, nil, fmt.Errorf("not an implemented request type: %q", req.Request) + case requestEval: + resp := copierHandlerEval(req) + return resp, nil, nil + case requestStat: + resp := copierHandlerStat(req, pm, idMappings) + return resp, nil, nil + case requestGet: + return copierHandlerGet(bulkWriter, req, pm, idMappings) + case requestPut: + return copierHandlerPut(bulkReader, req, idMappings) + case requestMkdir: + return copierHandlerMkdir(req, idMappings) + case requestRemove: + resp := copierHandlerRemove(req) + return resp, nil, nil + case requestEnsure: + resp := copierHandlerEnsure(req, idMappings) + return resp, nil, nil + case requestConditionalRemove: + resp := copierHandlerConditionalRemove(req, idMappings) + return resp, nil, nil + case requestQuit: + return nil, nil, nil + } +} + +// pathIsExcluded computes path relative to root, then asks the pattern matcher +// if the result is excluded. Returns the relative path and the matcher's +// results. +func pathIsExcluded(root, path string, pm *fileutils.PatternMatcher) (string, bool, error) { + rel, err := convertToRelSubdirectory(root, path) + if err != nil { + return "", false, fmt.Errorf("copier: error computing path of %q relative to root %q: %w", path, root, err) + } + if pm == nil { + return rel, false, nil + } + if rel == "." { + // special case + return rel, false, nil + } + // Matches uses filepath.FromSlash() to convert candidates before + // checking if they match the patterns it's been given, implying that + // it expects Unix-style paths. + matches, err := pm.Matches(filepath.ToSlash(rel)) //nolint:staticcheck + if err != nil { + return rel, false, fmt.Errorf("copier: error checking if %q is excluded: %w", rel, err) + } + if matches { + return rel, true, nil + } + return rel, false, nil +} + +// resolvePath resolves symbolic links in paths, treating the specified +// directory as the root. +// Resolving the path this way, and using the result, is in no way secure +// against another process manipulating the content that we're looking at, and +// it is not expected to be. +// This helps us approximate chrooted behavior on systems and in test cases +// where chroot isn't available. +func resolvePath(root, path string, evaluateFinalComponent bool, pm *fileutils.PatternMatcher) (string, error) { + rel, err := convertToRelSubdirectory(root, path) + if err != nil { + return "", fmt.Errorf("making path %q relative to %q", path, root) + } + workingPath := root + followed := 0 + components := strings.Split(rel, string(os.PathSeparator)) + excluded := false + for len(components) > 0 { + // if anything we try to examine is excluded, then resolution has to "break" + _, thisExcluded, err := pathIsExcluded(root, filepath.Join(workingPath, components[0]), pm) + if err != nil { + return "", err + } + excluded = excluded || thisExcluded + if !excluded { + if target, err := os.Readlink(filepath.Join(workingPath, components[0])); err == nil && (len(components) != 1 || evaluateFinalComponent) { + followed++ + if followed > maxLoopsFollowed { + return "", &os.PathError{ + Op: "open", + Path: path, + Err: syscall.ELOOP, + } + } + if filepath.IsAbs(target) || looksLikeAbs(target) { + // symlink to an absolute path - prepend the + // root directory to that absolute path to + // replace the current location, and resolve + // the remaining components + workingPath = root + components = append(strings.Split(target, string(os.PathSeparator)), components[1:]...) + continue + } + // symlink to a relative path - add the link target to + // the current location to get the next location, and + // resolve the remaining components + rel, err := convertToRelSubdirectory(root, filepath.Join(workingPath, target)) + if err != nil { + return "", fmt.Errorf("making path %q relative to %q", filepath.Join(workingPath, target), root) + } + workingPath = root + components = append(strings.Split(filepath.Clean(string(os.PathSeparator)+rel), string(os.PathSeparator)), components[1:]...) + continue + } + } + // append the current component's name to get the next location + workingPath = filepath.Join(workingPath, components[0]) + if workingPath == filepath.Join(root, "..") { + // attempted to go above the root using a relative path .., scope it + workingPath = root + } + // ready to handle the next component + components = components[1:] + } + return workingPath, nil +} + +func copierHandlerEval(req request) *response { + errorResponse := func(fmtspec string, args ...any) *response { + return &response{Error: fmt.Sprintf(fmtspec, args...), Eval: evalResponse{}} + } + resolvedTarget, err := resolvePath(req.Root, req.Directory, true, nil) + if err != nil { + return errorResponse("copier: eval: error resolving %q: %v", req.Directory, err) + } + return &response{Eval: evalResponse{Evaluated: filepath.Join(req.rootPrefix, resolvedTarget)}} +} + +func copierHandlerStat(req request, pm *fileutils.PatternMatcher, idMappings *idtools.IDMappings) *response { + errorResponse := func(fmtspec string, args ...any) *response { + return &response{Error: fmt.Sprintf(fmtspec, args...), Stat: statResponse{}} + } + if len(req.Globs) == 0 { + return errorResponse("copier: stat: expected at least one glob pattern, got none") + } + var stats []*StatsForGlob + for i, glob := range req.Globs { + s := StatsForGlob{ + Glob: req.preservedGlobs[i], + } + // glob this pattern + globMatched, err := extendedGlob(glob) + if err != nil { + s.Error = fmt.Sprintf("copier: stat: %q while matching glob pattern %q", err.Error(), glob) + } + + if len(globMatched) == 0 && strings.ContainsAny(glob, "*?[") { + continue + } + // collect the matches + s.Globbed = make([]string, 0, len(globMatched)) + s.Results = make(map[string]*StatForItem) + for _, globbed := range globMatched { + rel, excluded, err := pathIsExcluded(req.Root, globbed, pm) + if err != nil { + return errorResponse("copier: stat: %v", err) + } + if excluded { + continue + } + // if the glob was an absolute path, reconstruct the + // path that we should hand back for the match + var resultName string + if filepath.IsAbs(req.preservedGlobs[i]) { + resultName = filepath.Join(req.rootPrefix, globbed) + } else { + relResult := rel + if req.Directory != req.Root { + relResult, err = convertToRelSubdirectory(req.Directory, globbed) + if err != nil { + return errorResponse("copier: stat: error making %q relative to %q: %v", globbed, req.Directory, err) + } + } + resultName = relResult + } + result := StatForItem{Name: resultName} + s.Globbed = append(s.Globbed, resultName) + s.Results[resultName] = &result + // lstat the matched value + linfo, err := os.Lstat(globbed) + if err != nil { + result.Error = err.Error() + continue + } + result.Size = linfo.Size() + result.Mode = linfo.Mode() + result.UID, result.GID = -1, -1 + if uid, gid, err := owner(linfo); err == nil { + if idMappings != nil && !idMappings.Empty() { + hostPair := idtools.IDPair{UID: uid, GID: gid} + uid, gid, err = idMappings.ToContainer(hostPair) + if err != nil { + return errorResponse("copier: stat: mapping host filesystem owners %#v to container filesystem owners: %w", hostPair, err) + } + } + result.UID, result.GID = int64(uid), int64(gid) + } + result.ModTime = linfo.ModTime() + result.IsDir = linfo.IsDir() + result.IsRegular = result.Mode.IsRegular() + result.IsSymlink = (linfo.Mode() & os.ModeType) == os.ModeSymlink + checkForArchive := req.StatOptions.CheckForArchives + if result.IsSymlink { + // if the match was a symbolic link, read it + immediateTarget, err := os.Readlink(globbed) + if err != nil { + result.Error = err.Error() + continue + } + // record where it points, both by itself (it + // could be a relative link) and in the context + // of the chroot + result.ImmediateTarget = immediateTarget + resolvedTarget, err := resolvePath(req.Root, globbed, true, pm) + if err != nil { + return errorResponse("copier: stat: error resolving %q: %v", globbed, err) + } + // lstat the thing that we point to + info, err := os.Lstat(resolvedTarget) + if err != nil { + result.Error = err.Error() + continue + } + // replace IsArchive/IsDir/IsRegular with info about the target + if info.Mode().IsRegular() && req.StatOptions.CheckForArchives { + result.IsArchive = isArchivePath(resolvedTarget) + checkForArchive = false + } + result.IsDir = info.IsDir() + result.IsRegular = info.Mode().IsRegular() + } + if result.IsRegular && checkForArchive { + // we were asked to check on this, and it + // wasn't a symlink, in which case we'd have + // already checked what the link points to + result.IsArchive = isArchivePath(globbed) + } + } + // no unskipped matches -> error + if len(s.Globbed) == 0 { + s.Globbed = nil + s.Results = nil + s.Error = fmt.Sprintf("copier: stat: %q: %v", glob, syscall.ENOENT) + } + stats = append(stats, &s) + } + // no matches -> error + if len(stats) == 0 { + s := StatsForGlob{ + Error: fmt.Sprintf("copier: stat: %q: %v", req.Globs, syscall.ENOENT), + } + stats = append(stats, &s) + } + return &response{Stat: statResponse{Globs: stats}} +} + +func errorIsPermission(err error) bool { + if err == nil { + return false + } + return errors.Is(err, os.ErrPermission) || strings.Contains(err.Error(), "permission denied") +} + +func getParents(path string, stopPath string) []string { + out := []string{} + for path != "/" && path != "." && path != stopPath { + path = filepath.Dir(path) + if path == stopPath { + continue + } + out = append(out, path) + } + slices.Reverse(out) + return out +} + +func checkLinks(item string, req request, info os.FileInfo) (string, os.FileInfo, error) { + // chase links. if we hit a dead end, we should just fail + oldItem := item + followedLinks := 0 + const maxFollowedLinks = 16 + for !req.GetOptions.NoDerefSymlinks && info.Mode()&os.ModeType == os.ModeSymlink && followedLinks < maxFollowedLinks { + path, err := os.Readlink(item) + if err != nil { + continue + } + if filepath.IsAbs(path) || looksLikeAbs(path) { + path = filepath.Join(req.Root, path) + } else { + path = filepath.Join(filepath.Dir(item), path) + } + item = path + if _, err = convertToRelSubdirectory(req.Root, item); err != nil { + return "", nil, fmt.Errorf("copier: get: computing path of %q(%q) relative to %q: %w", oldItem, item, req.Root, err) + } + if info, err = os.Lstat(item); err != nil { + return "", nil, fmt.Errorf("copier: get: lstat %q(%q): %w", oldItem, item, err) + } + followedLinks++ + } + if followedLinks >= maxFollowedLinks { + return "", nil, fmt.Errorf("copier: get: resolving symlink %q(%q): %w", oldItem, item, syscall.ELOOP) + } + return item, info, nil +} + +func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMatcher, idMappings *idtools.IDMappings) (*response, func() error, error) { + statRequest := req + statRequest.Request = requestStat + statResponse := copierHandlerStat(req, pm, idMappings) + errorResponse := func(fmtspec string, args ...any) (*response, func() error, error) { + return &response{Error: fmt.Sprintf(fmtspec, args...), Stat: statResponse.Stat, Get: getResponse{}}, nil, nil + } + if statResponse.Error != "" { + return errorResponse("%s", statResponse.Error) + } + if len(req.Globs) == 0 { + return errorResponse("copier: get: expected at least one glob pattern, got 0") + } + // build a queue of items by globbing + type queueItem struct { + glob string + parents []string + } + var queue []queueItem + globMatchedCount := 0 + for _, glob := range req.Globs { + globMatched, err := extendedGlob(glob) + if err != nil { + return errorResponse("copier: get: glob %q: %v", glob, err) + } + for _, path := range globMatched { + var parents []string + if req.GetOptions.Parents { + parents = getParents(path, req.Directory) + } + globMatchedCount++ + queue = append(queue, queueItem{glob: path, parents: parents}) + } + } + // no matches -> error + if len(queue) == 0 { + return errorResponse("copier: get: globs %v matched nothing (%d filtered out): %v", req.Globs, globMatchedCount, syscall.ENOENT) + } + topInfo, err := os.Stat(req.Directory) + if err != nil { + return errorResponse("copier: get: error reading info about directory %q: %v", req.Directory, err) + } + cb := func() error { + tw := tar.NewWriter(bulkWriter) + defer tw.Close() + hardlinkChecker := new(hardlinkChecker) + itemsCopied := 0 + addedParents := map[string]struct{}{} + for i, qItem := range queue { + item := qItem.glob + // if we're not discarding the names of individual directories, keep track of this one + relNamePrefix := "" + if req.GetOptions.KeepDirectoryNames { + relNamePrefix = filepath.Base(item) + } + // if the named thing-to-read is a symlink, dereference it + info, err := os.Lstat(item) + if err != nil { + return fmt.Errorf("copier: get: lstat %q: %w", item, err) + } + if req.GetOptions.Parents && info.Mode().IsDir() { + if !slices.Contains(qItem.parents, item) { + qItem.parents = append(qItem.parents, item) + } + } + // Copy parents in to tarball first if exists + for _, parent := range qItem.parents { + oldParent := parent + parentInfo, err := os.Lstat(parent) + if err != nil { + return fmt.Errorf("copier: get: lstat %q: %w", parent, err) + } + parent, parentInfo, err = checkLinks(parent, req, parentInfo) + if err != nil { + return err + } + parentName, err := convertToRelSubdirectory(req.Directory, oldParent) + if err != nil { + return fmt.Errorf("copier: get: error computing path of %q relative to %q: %w", parent, req.Directory, err) + } + if parentName == "" || parentName == "." { + // skip the "." entry + continue + } + + if _, ok := addedParents[parentName]; ok { + continue + } + addedParents[parentName] = struct{}{} + + if err := copierHandlerGetOne(parentInfo, "", parentName, parent, req.GetOptions, tw, hardlinkChecker, idMappings); err != nil { + if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) { + continue + } else if errors.Is(err, os.ErrNotExist) { + logrus.Warningf("copier: file disappeared while reading: %q", parent) + return nil + } + return fmt.Errorf("copier: get: %q: %w", queue[i].glob, err) + } + itemsCopied++ + } + + item, info, err = checkLinks(item, req, info) + if err != nil { + return err + } + + // evaluate excludes relative to the root directory + if info.Mode().IsDir() { + // we don't expand any of the contents that are archives + options := req.GetOptions + options.ExpandArchives = false + walkfn := func(path string, d fs.DirEntry, err error) error { + if err != nil { + if options.IgnoreUnreadable && errorIsPermission(err) { + if info != nil && d.IsDir() { + return filepath.SkipDir + } + return nil + } else if errors.Is(err, os.ErrNotExist) { + logrus.Warningf("copier: file disappeared while reading: %q", path) + return nil + } + return fmt.Errorf("copier: get: error reading %q: %w", path, err) + } + if d.Type() == os.ModeSocket { + logrus.Warningf("copier: skipping socket %q", d.Name()) + return nil + } + // compute the path of this item + // relative to the top-level directory, + // for the tar header + rel, relErr := convertToRelSubdirectory(item, path) + if relErr != nil { + return fmt.Errorf("copier: get: error computing path of %q relative to top directory %q: %w", path, item, relErr) + } + // prefix the original item's name if we're keeping it + if relNamePrefix != "" { + rel = filepath.Join(relNamePrefix, rel) + } + if rel == "" || rel == "." { + // skip the "." entry + return nil + } + skippedPath, skip, err := pathIsExcluded(req.Root, path, pm) + if err != nil { + return err + } + if skip { + if d.IsDir() { + // if there are no "include + // this anyway" patterns at + // all, we don't need to + // descend into this particular + // directory if it's a directory + if !pm.Exclusions() { + return filepath.SkipDir + } + // if there are exclusion + // patterns for which this + // path is a prefix, we + // need to keep descending + for _, pattern := range pm.Patterns() { + if !pattern.Exclusion() { + continue + } + spec := strings.Trim(pattern.String(), string(os.PathSeparator)) + trimmedPath := strings.Trim(skippedPath, string(os.PathSeparator)) + if strings.HasPrefix(spec+string(os.PathSeparator), trimmedPath) { + // we can't just skip over + // this directory + return nil + } + } + // there are exclusions, but + // none of them apply here + return filepath.SkipDir + } + // skip this item, but if we're + // a directory, a more specific + // but-include-this for + // something under it might + // also be in the excludes list + return nil + } + // if it's a symlink, read its target + symlinkTarget := "" + if d.Type() == os.ModeSymlink { + target, err := os.Readlink(path) + if err != nil { + return fmt.Errorf("copier: get: readlink(%q(%q)): %w", rel, path, err) + } + symlinkTarget = target + } + info, err := d.Info() + if err != nil { + return err + } + // if it's a directory and we're staying on one device, and it's on a + // different device than the one we started from, skip its contents + var ok error + if d.IsDir() && req.GetOptions.NoCrossDevice { + if !sameDevice(topInfo, info) { + ok = filepath.SkipDir + } + } + if req.GetOptions.Parents { + rel, err = convertToRelSubdirectory(req.Directory, path) + if err != nil { + return fmt.Errorf("copier: get: error computing path of %q relative to %q: %w", path, req.Root, err) + } + } + // add the item to the outgoing tar stream + if err := copierHandlerGetOne(info, symlinkTarget, rel, path, options, tw, hardlinkChecker, idMappings); err != nil { + if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) { + return ok + } else if errors.Is(err, os.ErrNotExist) { + logrus.Warningf("copier: file disappeared while reading: %q", path) + return nil + } + return err + } + return ok + } + // walk the directory tree, checking/adding items individually + if err := filepath.WalkDir(item, walkfn); err != nil { + return fmt.Errorf("copier: get: %q(%q): %w", queue[i].glob, item, err) + } + itemsCopied++ + } else { + _, skip, err := pathIsExcluded(req.Root, item, pm) + if err != nil { + return err + } + if skip { + continue + } + + name := filepath.Base(queue[i].glob) + if req.GetOptions.Parents { + name, err = convertToRelSubdirectory(req.Directory, queue[i].glob) + if err != nil { + return fmt.Errorf("copier: get: error computing path of %q relative to %q: %w", item, req.Root, err) + } + if name == "" || name == "." { + // skip the "." entry + continue + } + } + + if err := copierHandlerGetOne(info, "", name, item, req.GetOptions, tw, hardlinkChecker, idMappings); err != nil { + if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) { + continue + } + return fmt.Errorf("copier: get: %q: %w", queue[i].glob, err) + } + itemsCopied++ + } + } + if itemsCopied == 0 { + return fmt.Errorf("copier: get: copied no items: %w", syscall.ENOENT) + } + return nil + } + return &response{Stat: statResponse.Stat, Get: getResponse{}}, cb, nil +} + +func handleRename(rename map[string]string, name string) string { + if rename == nil { + return name + } + // header names always use '/', so use path instead of filepath to manipulate it + if directMapping, ok := rename[name]; ok { + return directMapping + } + prefix, remainder := path.Split(name) + for prefix != "" { + if mappedPrefix, ok := rename[prefix]; ok { + return path.Join(mappedPrefix, remainder) + } + if prefix[len(prefix)-1] == '/' { + prefix = prefix[:len(prefix)-1] + if mappedPrefix, ok := rename[prefix]; ok { + return path.Join(mappedPrefix, remainder) + } + } + newPrefix, middlePart := path.Split(prefix) + if newPrefix == prefix { + return name + } + prefix = newPrefix + remainder = path.Join(middlePart, remainder) + } + return name +} + +// mapWithPrefixedKeysWithoutKeyPrefix returns a map containing every element +// of m that had p as a prefix in its (string) key, with that prefix stripped +// from its key. items are shallow-copied using assignment. if m is nil, the +// returned map will be nil, otherwise it will at least have been allocated +func mapWithPrefixedKeysWithoutKeyPrefix[K any](m map[string]K, p string) map[string]K { + if m == nil { + return m + } + cloned := make(map[string]K, len(m)) + for k, v := range m { + if after, ok := strings.CutPrefix(k, p); ok { + cloned[after] = v + } + } + return cloned +} + +func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath string, options GetOptions, tw *tar.Writer, hardlinkChecker *hardlinkChecker, idMappings *idtools.IDMappings) error { + // build the header using the name provided + hdr, err := tar.FileInfoHeader(srcfi, symlinkTarget) + if err != nil { + return fmt.Errorf("generating tar header for %s (%s): %w", contentPath, symlinkTarget, err) + } + if name != "" { + hdr.Name = filepath.ToSlash(name) + } + hdr.Uname, hdr.Gname = "", "" + if options.Rename != nil { + hdr.Name = handleRename(options.Rename, hdr.Name) + } + if options.StripSetuidBit { + hdr.Mode &^= cISUID + } + if options.StripSetgidBit { + hdr.Mode &^= cISGID + } + if options.StripStickyBit { + hdr.Mode &^= cISVTX + } + // read extended attributes + var xattrs map[string]string + if !options.StripXattrs { + xattrs, err = Lgetxattrs(contentPath) + if err != nil { + return fmt.Errorf("getting extended attributes for %q: %w", contentPath, err) + } + if len(xattrs) > 0 && hdr.PAXRecords == nil { + hdr.PAXRecords = make(map[string]string, len(xattrs)) + } + } + for k, v := range xattrs { + hdr.PAXRecords[xattrPAXRecordNamespace+k] = v + } + if hdr.Typeflag == tar.TypeReg { + // if it's an archive and we're extracting archives, read the + // file and spool out its contents in-line. (if we just + // inlined the whole file, we'd also be inlining the EOF marker + // it contains) + if options.ExpandArchives && isArchivePath(contentPath) { + f, err := os.Open(contentPath) + if err != nil { + return fmt.Errorf("opening file for reading archive contents: %w", err) + } + defer f.Close() + rc, _, err := compression.AutoDecompress(f) + if err != nil { + return fmt.Errorf("decompressing %s: %w", contentPath, err) + } + defer rc.Close() + tr := tar.NewReader(rc) + hdr, err := tr.Next() + for err == nil { + if options.Rename != nil { + hdr.Name = handleRename(options.Rename, hdr.Name) + } + if options.Timestamp != nil { + timestamp := options.Timestamp.UTC() + hdr.ModTime = timestamp + if !hdr.AccessTime.IsZero() { + hdr.AccessTime = timestamp + } + if !hdr.ChangeTime.IsZero() { + hdr.ChangeTime = timestamp + } + } + if err = tw.WriteHeader(hdr); err != nil { + return fmt.Errorf("writing tar header from %q to pipe: %w", contentPath, err) + } + if hdr.Size != 0 { + n, err := io.Copy(tw, tr) + if err != nil { + return fmt.Errorf("extracting content from archive %s: %s: %w", contentPath, hdr.Name, err) + } + if n != hdr.Size { + return fmt.Errorf("extracting contents of archive %s: incorrect length for %q", contentPath, hdr.Name) + } + tw.Flush() + } + hdr, err = tr.Next() + } + if err != io.EOF { + return fmt.Errorf("extracting contents of archive %s: %w", contentPath, err) + } + return nil + } + // if this regular file is hard linked to something else we've + // already added, set up to output a TypeLink entry instead of + // a TypeReg entry + target := hardlinkChecker.Check(srcfi) + if target != "" { + hdr.Typeflag = tar.TypeLink + hdr.Linkname = filepath.ToSlash(target) + hdr.Size = 0 + } else { + // note the device/inode pair for this file + hardlinkChecker.Add(srcfi, name) + } + } + // map the ownership for the archive + if idMappings != nil && !idMappings.Empty() { + hostPair := idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid} + hdr.Uid, hdr.Gid, err = idMappings.ToContainer(hostPair) + if err != nil { + return fmt.Errorf("mapping host filesystem owners %#v to container filesystem owners: %w", hostPair, err) + } + } + // force ownership and/or permissions, if requested + if hdr.Typeflag == tar.TypeDir { + if options.ChownDirs != nil { + hdr.Uid, hdr.Gid = options.ChownDirs.UID, options.ChownDirs.GID + } + if options.ChmodDirs != nil { + hdr.Mode = int64(*options.ChmodDirs) + } + if !strings.HasSuffix(hdr.Name, "/") { + hdr.Name += "/" + } + } else { + if options.ChownFiles != nil { + hdr.Uid, hdr.Gid = options.ChownFiles.UID, options.ChownFiles.GID + } + if options.ChmodFiles != nil { + hdr.Mode = int64(*options.ChmodFiles) + } + } + // read fflags, if any + if err := archive.ReadFileFlagsToTarHeader(contentPath, hdr); err != nil { + return fmt.Errorf("getting fflags: %w", err) + } + var f *os.File + switch hdr.Typeflag { + case tar.TypeReg: + // open the file first so that we don't write a header for it if we can't actually read it + f, err = os.Open(contentPath) + if err != nil { + return fmt.Errorf("opening file for adding its contents to archive: %w", err) + } + defer f.Close() + case tar.TypeDir: + // open the directory file first to make sure we can access it. + f, err = os.Open(contentPath) + if err != nil { + return fmt.Errorf("opening directory for adding its contents to archive: %w", err) + } + defer f.Close() + } + if options.Timestamp != nil { + timestamp := options.Timestamp.UTC() + hdr.ModTime = timestamp + if !hdr.AccessTime.IsZero() { + hdr.AccessTime = timestamp + } + if !hdr.ChangeTime.IsZero() { + hdr.ChangeTime = timestamp + } + } + // output the header + if err = tw.WriteHeader(hdr); err != nil { + return fmt.Errorf("writing header for %s (%s): %w", contentPath, hdr.Name, err) + } + if hdr.Typeflag == tar.TypeReg { + // output the content + n, err := io.Copy(tw, f) + if err != nil { + return fmt.Errorf("copying %s: %w", contentPath, err) + } + if n != hdr.Size { + return fmt.Errorf("copying %s: incorrect size (expected %d bytes, read %d bytes)", contentPath, n, hdr.Size) + } + tw.Flush() + } + return nil +} + +func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDMappings) (*response, func() error, error) { + errorResponse := func(fmtspec string, args ...any) (*response, func() error, error) { + return &response{Error: fmt.Sprintf(fmtspec, args...), Put: putResponse{}}, nil, nil + } + dirUID, dirGID, defaultDirUID, defaultDirGID := 0, 0, 0, 0 + if req.PutOptions.ChownDirs != nil { + dirUID, dirGID = req.PutOptions.ChownDirs.UID, req.PutOptions.ChownDirs.GID + defaultDirUID, defaultDirGID = dirUID, dirGID + } + defaultDirMode := os.FileMode(0o755) + if req.PutOptions.ChmodDirs != nil { + defaultDirMode = *req.PutOptions.ChmodDirs + } + if req.PutOptions.DefaultDirOwner != nil { + defaultDirUID, defaultDirGID = req.PutOptions.DefaultDirOwner.UID, req.PutOptions.DefaultDirOwner.GID + } + if req.PutOptions.DefaultDirMode != nil { + defaultDirMode = *req.PutOptions.DefaultDirMode + } + var fileUID, fileGID *int + if req.PutOptions.ChownFiles != nil { + fileUID, fileGID = &req.PutOptions.ChownFiles.UID, &req.PutOptions.ChownFiles.GID + } + if idMappings != nil && !idMappings.Empty() { + containerDirPair := idtools.IDPair{UID: dirUID, GID: dirGID} + hostDirPair, err := idMappings.ToHost(containerDirPair) + if err != nil { + return errorResponse("copier: put: error mapping container filesystem owner %d:%d to host filesystem owners: %v", dirUID, dirGID, err) + } + dirUID, dirGID = hostDirPair.UID, hostDirPair.GID + defaultDirUID, defaultDirGID = hostDirPair.UID, hostDirPair.GID + if req.PutOptions.ChownFiles != nil { + containerFilePair := idtools.IDPair{UID: *fileUID, GID: *fileGID} + hostFilePair, err := idMappings.ToHost(containerFilePair) + if err != nil { + return errorResponse("copier: put: error mapping container filesystem owner %d:%d to host filesystem owners: %v", fileUID, fileGID, err) + } + fileUID, fileGID = &hostFilePair.UID, &hostFilePair.GID + } + } + directoryModes := make(map[string]os.FileMode) + ensureDirectoryUnderRoot := func(directory string) error { + rel, err := convertToRelSubdirectory(req.Root, directory) + if err != nil { + return fmt.Errorf("%q is not a subdirectory of %q: %w", directory, req.Root, err) + } + subdir := "" + for component := range strings.SplitSeq(rel, string(os.PathSeparator)) { + subdir = filepath.Join(subdir, component) + path := filepath.Join(req.Root, subdir) + if err := os.Mkdir(path, 0o700); err == nil { + if err = lchown(path, defaultDirUID, defaultDirGID); err != nil { + return fmt.Errorf("copier: put: error setting owner of %q to %d:%d: %w", path, defaultDirUID, defaultDirGID, err) + } + // make a conditional note to set this directory's permissions + // later, but not if we already had an explicitly-provided mode + if _, ok := directoryModes[path]; !ok { + directoryModes[path] = defaultDirMode + } + } else { + // FreeBSD can return EISDIR for "mkdir /": + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=59739. + if !errors.Is(err, os.ErrExist) && !errors.Is(err, syscall.EISDIR) { + return fmt.Errorf("copier: put: error checking directory %q: %w", path, err) + } + } + } + return nil + } + makeDirectoryWriteable := func(directory string) error { + if _, ok := directoryModes[directory]; !ok { + st, err := os.Lstat(directory) + if err != nil { + return fmt.Errorf("copier: put: error reading permissions of directory %q: %w", directory, err) + } + mode := st.Mode() + directoryModes[directory] = mode + } + if err := os.Chmod(directory, 0o700); err != nil { + return fmt.Errorf("copier: put: error making directory %q writable: %w", directory, err) + } + return nil + } + createFile := func(path string, tr *tar.Reader) (int64, error) { + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_EXCL, 0o600) + if err != nil && errors.Is(err, os.ErrExist) { + if req.PutOptions.NoOverwriteDirNonDir { + if st, err2 := os.Lstat(path); err2 == nil && st.IsDir() { + return 0, fmt.Errorf("copier: put: error creating file at %q: %w", path, err) + } + } + if err = os.RemoveAll(path); err != nil { + if os.IsPermission(err) { + if err := makeDirectoryWriteable(filepath.Dir(path)); err != nil { + return 0, err + } + err = os.RemoveAll(path) + } + if err != nil { + return 0, fmt.Errorf("copier: put: error removing item to be overwritten %q: %w", path, err) + } + } + f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_EXCL, 0o600) + } + if err != nil && os.IsPermission(err) { + if err = makeDirectoryWriteable(filepath.Dir(path)); err != nil { + return 0, err + } + f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_EXCL, 0o600) + } + if err != nil { + return 0, fmt.Errorf("copier: put: error opening file %q for writing: %w", path, err) + } + defer f.Close() + n, err := io.Copy(f, tr) + if err != nil { + return n, fmt.Errorf("copier: put: error writing file %q: %w", path, err) + } + return n, nil + } + targetDirectory, err := resolvePath(req.Root, req.Directory, true, nil) + if err != nil { + return errorResponse("copier: put: error resolving %q: %v", req.Directory, err) + } + info, err := os.Lstat(targetDirectory) + if err == nil { + if !info.IsDir() { + return errorResponse("copier: put: %s (%s): exists but is not a directory", req.Directory, targetDirectory) + } + } else { + if !errors.Is(err, os.ErrNotExist) { + return errorResponse("copier: put: %s: %v", req.Directory, err) + } + if err := ensureDirectoryUnderRoot(req.Directory); err != nil { + return errorResponse("copier: put: %v", err) + } + } + cb := func() error { + type directoryAndTimes struct { + directory string + atime, mtime time.Time + } + var directoriesAndTimes []directoryAndTimes + defer func() { + for i := range directoriesAndTimes { + directoryAndTimes := directoriesAndTimes[len(directoriesAndTimes)-i-1] + if err := lutimes(false, directoryAndTimes.directory, directoryAndTimes.atime, directoryAndTimes.mtime); err != nil { + logrus.Debugf("error setting access and modify timestamps on %q to %s and %s: %v", directoryAndTimes.directory, directoryAndTimes.atime, directoryAndTimes.mtime, err) + } + } + for directory, mode := range directoryModes { + if err := os.Chmod(directory, mode); err != nil { + logrus.Debugf("error setting permissions of %q to 0%o: %v", directory, uint32(mode), err) + } + } + }() + ignoredItems := make(map[string]struct{}) + tr := tar.NewReader(bulkReader) + hdr, err := tr.Next() + for err == nil { + nameBeforeRenaming := hdr.Name + if len(hdr.Name) == 0 { + // no name -> ignore the entry + ignoredItems[nameBeforeRenaming] = struct{}{} + hdr, err = tr.Next() + continue + } + if req.PutOptions.Rename != nil { + hdr.Name = handleRename(req.PutOptions.Rename, hdr.Name) + } + // figure out who should own this new item + if idMappings != nil && !idMappings.Empty() { + containerPair := idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid} + hostPair, err := idMappings.ToHost(containerPair) + if err != nil { + return fmt.Errorf("mapping container filesystem owner 0,0 to host filesystem owners: %w", err) + } + hdr.Uid, hdr.Gid = hostPair.UID, hostPair.GID + } + if hdr.Typeflag == tar.TypeDir { + if req.PutOptions.ChownDirs != nil { + hdr.Uid, hdr.Gid = dirUID, dirGID + } + } else { + if req.PutOptions.ChownFiles != nil { + hdr.Uid, hdr.Gid = *fileUID, *fileGID + } + } + // make sure the parent directory exists, including for tar.TypeXGlobalHeader entries + // that we otherwise ignore, because that's what docker build does with them + path := filepath.Join(targetDirectory, cleanerReldirectory(filepath.FromSlash(hdr.Name))) + if err := ensureDirectoryUnderRoot(filepath.Dir(path)); err != nil { + return err + } + // figure out what the permissions should be + if req.PutOptions.StripSetuidBit && hdr.Mode&cISUID == cISUID { + hdr.Mode &^= cISUID + } + if req.PutOptions.StripSetgidBit && hdr.Mode&cISGID == cISGID { + hdr.Mode &^= cISGID + } + if req.PutOptions.StripStickyBit && hdr.Mode&cISVTX == cISVTX { + hdr.Mode &^= cISVTX + } + if hdr.Typeflag == tar.TypeDir { + if req.PutOptions.ChmodDirs != nil { + hdr.Mode = int64(*req.PutOptions.ChmodDirs) + } + } else { + if req.PutOptions.ChmodFiles != nil { + hdr.Mode = int64(*req.PutOptions.ChmodFiles) + } + } + // create the new item + devMajor := uint32(hdr.Devmajor) + devMinor := uint32(hdr.Devminor) + mode := os.FileMode(hdr.Mode) & os.ModePerm + switch hdr.Typeflag { + // no type flag for sockets + default: + return fmt.Errorf("unrecognized Typeflag %c", hdr.Typeflag) + case tar.TypeReg: + var written int64 + written, err = createFile(path, tr) + // only check the length if there wasn't an error, which we'll + // check along with errors for other types of entries + if err == nil && written != hdr.Size { + return fmt.Errorf("copier: put: error creating regular file %q: incorrect length (%d != %d)", path, written, hdr.Size) + } + case tar.TypeLink: + var linkTarget string + if _, ignoredTarget := ignoredItems[hdr.Linkname]; ignoredTarget { + // hard link to an ignored item: skip this, too + ignoredItems[nameBeforeRenaming] = struct{}{} + goto nextHeader + } + if req.PutOptions.Rename != nil { + hdr.Linkname = handleRename(req.PutOptions.Rename, hdr.Linkname) + } + if linkTarget, err = resolvePath(targetDirectory, filepath.Join(req.Root, filepath.FromSlash(hdr.Linkname)), true, nil); err != nil { + return fmt.Errorf("resolving hardlink target path %q under root %q", hdr.Linkname, req.Root) + } + if err = os.Link(linkTarget, path); err != nil && errors.Is(err, os.ErrExist) { + if req.PutOptions.NoOverwriteDirNonDir { + if st, err := os.Lstat(path); err == nil && st.IsDir() { + break + } + } + if err = os.RemoveAll(path); err == nil { + err = os.Link(linkTarget, path) + } + } + case tar.TypeSymlink: + // if req.PutOptions.Rename != nil { + // todo: the general solution requires resolving to an absolute path, handling + // renaming, and then possibly converting back to a relative symlink + // } + if err = os.Symlink(filepath.FromSlash(hdr.Linkname), filepath.FromSlash(path)); err != nil && errors.Is(err, os.ErrExist) { + if req.PutOptions.NoOverwriteDirNonDir { + if st, err := os.Lstat(path); err == nil && st.IsDir() { + break + } + } + if err = os.RemoveAll(path); err == nil { + err = os.Symlink(filepath.FromSlash(hdr.Linkname), filepath.FromSlash(path)) + } + } + case tar.TypeChar: + if req.PutOptions.IgnoreDevices { + ignoredItems[nameBeforeRenaming] = struct{}{} + goto nextHeader + } + if err = mknod(path, chrMode(0o600), int(mkdev(devMajor, devMinor))); err != nil && errors.Is(err, os.ErrExist) { + if req.PutOptions.NoOverwriteDirNonDir { + if st, err := os.Lstat(path); err == nil && st.IsDir() { + break + } + } + if err = os.RemoveAll(path); err == nil { + err = mknod(path, chrMode(0o600), int(mkdev(devMajor, devMinor))) + } + } + case tar.TypeBlock: + if req.PutOptions.IgnoreDevices { + ignoredItems[nameBeforeRenaming] = struct{}{} + goto nextHeader + } + if err = mknod(path, blkMode(0o600), int(mkdev(devMajor, devMinor))); err != nil && errors.Is(err, os.ErrExist) { + if req.PutOptions.NoOverwriteDirNonDir { + if st, err := os.Lstat(path); err == nil && st.IsDir() { + break + } + } + if err = os.RemoveAll(path); err == nil { + err = mknod(path, blkMode(0o600), int(mkdev(devMajor, devMinor))) + } + } + case tar.TypeDir: + // FreeBSD can return EISDIR for "mkdir /": + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=59739. + if err = os.Mkdir(path, 0o700); err != nil && (errors.Is(err, os.ErrExist) || errors.Is(err, syscall.EISDIR)) { + if st, stErr := os.Lstat(path); stErr == nil && !st.IsDir() { + if req.PutOptions.NoOverwriteNonDirDir { + break + } + if err = os.Remove(path); err == nil { + err = os.Mkdir(path, 0o700) + } + } else { + err = stErr + } + // either we removed it and retried, or it was a directory, + // in which case we want to just add the new stuff under it + } + // make a note of the directory's times. we + // might create items under it, which will + // cause the mtime to change after we correct + // it, so we'll need to correct it again later + directoriesAndTimes = append(directoriesAndTimes, directoryAndTimes{ + directory: path, + atime: hdr.AccessTime, + mtime: hdr.ModTime, + }) + // set the mode here unconditionally, in case the directory is in + // the archive more than once for whatever reason + directoryModes[path] = mode + case tar.TypeFifo: + if err = mkfifo(path, 0o600); err != nil && errors.Is(err, os.ErrExist) { + if req.PutOptions.NoOverwriteDirNonDir { + if st, err := os.Lstat(path); err == nil && st.IsDir() { + break + } + } + if err = os.RemoveAll(path); err == nil { + err = mkfifo(path, 0o600) + } + } + case tar.TypeXGlobalHeader: + // Per archive/tar, PAX uses these to specify key=value information + // applies to all subsequent entries. The one in reported in #2717, + // https://www.openssl.org/source/openssl-1.1.1g.tar.gz, includes a + // comment=(40 byte hex string) at the start, possibly a digest. + // Don't try to create whatever path was used for the header. + goto nextHeader + } + // check for errors + if err != nil { + return fmt.Errorf("copier: put: error creating %q: %w", path, err) + } + // set ownership + if err = lchown(path, hdr.Uid, hdr.Gid); err != nil { + return fmt.Errorf("copier: put: error setting ownership of %q to %d:%d: %w", path, hdr.Uid, hdr.Gid, err) + } + // set permissions, except for symlinks, since we don't + // have an lchmod, and directories, which we'll fix up + // on our way out so that we don't get tripped up by + // directories which we're not supposed to be able to + // write to, but which we'll need to create content in + if hdr.Typeflag != tar.TypeSymlink && hdr.Typeflag != tar.TypeDir { + if err = os.Chmod(path, mode); err != nil { + return fmt.Errorf("copier: put: error setting permissions on %q to 0%o: %w", path, mode, err) + } + } + // set other bits that might have been reset by chown() + if hdr.Typeflag != tar.TypeSymlink { + if hdr.Mode&cISUID == cISUID { + mode |= os.ModeSetuid + } + if hdr.Mode&cISGID == cISGID { + mode |= os.ModeSetgid + } + if hdr.Mode&cISVTX == cISVTX { + mode |= os.ModeSticky + } + if hdr.Typeflag == tar.TypeDir { + // if/when we do the final setting of permissions on this + // directory, make sure to incorporate these bits, too + directoryModes[path] = mode + } + if err = os.Chmod(path, mode); err != nil { + return fmt.Errorf("copier: put: setting additional permissions on %q to 0%o: %w", path, mode, err) + } + } + // set xattrs, including some that might have been reset by chown() + if !req.PutOptions.StripXattrs { + xattrs := mapWithPrefixedKeysWithoutKeyPrefix(hdr.PAXRecords, xattrPAXRecordNamespace) + if err = Lsetxattrs(path, xattrs); err != nil { + if !req.PutOptions.IgnoreXattrErrors { + return fmt.Errorf("copier: put: error setting extended attributes on %q: %w", path, err) + } + } + } + // set time + if hdr.AccessTime.IsZero() || hdr.AccessTime.Before(hdr.ModTime) { + hdr.AccessTime = hdr.ModTime + } + if err = lutimes(hdr.Typeflag == tar.TypeSymlink, path, hdr.AccessTime, hdr.ModTime); err != nil { + return fmt.Errorf("setting access and modify timestamps on %q to %s and %s: %w", path, hdr.AccessTime, hdr.ModTime, err) + } + // set fflags if supported + if err := archive.WriteFileFlagsFromTarHeader(path, hdr); err != nil { + return fmt.Errorf("copier: put: error setting fflags on %q: %w", path, err) + } + nextHeader: + hdr, err = tr.Next() + } + if err != io.EOF { + return fmt.Errorf("reading tar stream: expected EOF: %w", err) + } + return nil + } + return &response{Error: "", Put: putResponse{}}, cb, nil +} + +func copierHandlerMkdir(req request, idMappings *idtools.IDMappings) (*response, func() error, error) { + errorResponse := func(fmtspec string, args ...any) (*response, func() error, error) { //nolint:unparam + return &response{Error: fmt.Sprintf(fmtspec, args...), Mkdir: mkdirResponse{}}, nil, nil + } + dirUID, dirGID := 0, 0 + if req.MkdirOptions.ChownNew != nil { + dirUID, dirGID = req.MkdirOptions.ChownNew.UID, req.MkdirOptions.ChownNew.GID + } + dirMode := os.FileMode(0o755) + if req.MkdirOptions.ChmodNew != nil { + dirMode = *req.MkdirOptions.ChmodNew + } + if idMappings != nil && !idMappings.Empty() { + containerDirPair := idtools.IDPair{UID: dirUID, GID: dirGID} + hostDirPair, err := idMappings.ToHost(containerDirPair) + if err != nil { + return errorResponse("copier: mkdir: error mapping container filesystem owner %d:%d to host filesystem owners: %v", dirUID, dirGID, err) + } + dirUID, dirGID = hostDirPair.UID, hostDirPair.GID + } + + directory, err := resolvePath(req.Root, req.Directory, true, nil) + if err != nil { + return errorResponse("copier: mkdir: error resolving %q: %v", req.Directory, err) + } + + rel, err := convertToRelSubdirectory(req.Root, directory) + if err != nil { + return errorResponse("copier: mkdir: error computing path of %q relative to %q: %v", directory, req.Root, err) + } + + subdir := "" + var created []string + for component := range strings.SplitSeq(rel, string(os.PathSeparator)) { + subdir = filepath.Join(subdir, component) + path := filepath.Join(req.Root, subdir) + if err := os.Mkdir(path, 0o700); err == nil { + if err = chown(path, dirUID, dirGID); err != nil { + return errorResponse("copier: mkdir: error setting owner of %q to %d:%d: %v", path, dirUID, dirGID, err) + } + if err = chmod(path, dirMode); err != nil { + return errorResponse("copier: mkdir: error setting permissions on %q to 0%o: %v", path, dirMode) + } + created = append(created, path) + } else { + // FreeBSD can return EISDIR for "mkdir /": + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=59739. + if !errors.Is(err, os.ErrExist) && !errors.Is(err, syscall.EISDIR) { + return errorResponse("copier: mkdir: error checking directory %q: %v", path, err) + } + } + } + // set timestamps last, in case we needed to create some nested directories, which would + // update the timestamps on directories that we'd just set timestamps on, if we had done + // that immediately + if req.MkdirOptions.ModTimeNew != nil { + when := *req.MkdirOptions.ModTimeNew + for _, newDirectory := range created { + if err = lutimes(false, newDirectory, when, when); err != nil { + return errorResponse("copier: mkdir: error setting datestamp on %q: %v", newDirectory, err) + } + } + } + + return &response{Error: "", Mkdir: mkdirResponse{}}, nil, nil +} + +func copierHandlerRemove(req request) *response { + errorResponse := func(fmtspec string, args ...any) *response { + return &response{Error: fmt.Sprintf(fmtspec, args...), Remove: removeResponse{}} + } + resolvedTarget, err := resolvePath(req.Root, req.Directory, false, nil) + if err != nil { + return errorResponse("copier: remove: %v", err) + } + if req.RemoveOptions.All { + err = os.RemoveAll(resolvedTarget) + } else { + err = os.Remove(resolvedTarget) + } + if err != nil { + return errorResponse("copier: remove %q: %v", req.Directory, err) + } + return &response{Error: "", Remove: removeResponse{}} +} + +// EnsurePath is a single item being passed to an Ensure() call. +type EnsurePath struct { + Path string // a pathname, relative to the Directory, possibly relative to the root + Typeflag byte // can be either TypeReg or TypeDir, everything else is currently ignored + ModTime *time.Time // mtime to set on newly-created items, default is to leave them be + Chmod *os.FileMode // mode, defaults to 000 for files and 700 for directories + Chown *idtools.IDPair // owner settings to set on newly-created items, defaults to 0:0 +} + +// EnsureOptions controls parts of Ensure()'s behavior. +type EnsureOptions struct { + UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs in the chroot + Paths []EnsurePath +} + +// EnsureParentPath is a parent (or grandparent, or...) directory of an item +// created by Ensure(), along with information about it, from before the item +// in question was created. If the information about this directory hasn't +// changed when commit-time rolls around, it's most likely that this directory +// is only being considered for inclusion in the layer because it was pulled +// up, and it was not actually changed. +type EnsureParentPath = ConditionalRemovePath + +// Ensure ensures that the specified mount point targets exist under the root. +// If the root directory is not specified, the current root directory is used. +// If root is specified and the current OS supports it, and the calling process +// has the necessary privileges, the operation is performed in a chrooted +// context. +// Returns a slice with the pathnames of items that needed to be created and a +// slice of affected parent directories and information about them. +func Ensure(root, directory string, options EnsureOptions) ([]string, []EnsureParentPath, error) { + req := request{ + Request: requestEnsure, + Root: root, + Directory: directory, + EnsureOptions: options, + } + resp, err := copier(nil, nil, req) + if err != nil { + return nil, nil, err + } + if resp.Error != "" { + return nil, nil, errors.New(resp.Error) + } + return resp.Ensure.Created, resp.Ensure.Noted, nil +} + +func copierHandlerEnsure(req request, idMappings *idtools.IDMappings) *response { + errorResponse := func(fmtspec string, args ...any) *response { + return &response{Error: fmt.Sprintf(fmtspec, args...), Ensure: ensureResponse{}} + } + slices.SortFunc(req.EnsureOptions.Paths, func(a, b EnsurePath) int { return strings.Compare(a.Path, b.Path) }) + var created []string + notedByName := map[string]EnsureParentPath{} + for _, item := range req.EnsureOptions.Paths { + uid, gid := 0, 0 + if item.Chown != nil { + uid, gid = item.Chown.UID, item.Chown.UID + } + var mode os.FileMode + switch item.Typeflag { + case tar.TypeReg: + mode = 0o000 + case tar.TypeDir: + mode = 0o700 + default: + continue + } + if item.Chmod != nil { + mode = *item.Chmod + } + if idMappings != nil && !idMappings.Empty() { + containerDirPair := idtools.IDPair{UID: uid, GID: gid} + hostDirPair, err := idMappings.ToHost(containerDirPair) + if err != nil { + return errorResponse("copier: ensure: error mapping container filesystem owner %d:%d to host filesystem owners: %v", uid, gid, err) + } + uid, gid = hostDirPair.UID, hostDirPair.GID + } + directory, err := resolvePath(req.Root, req.Directory, true, nil) + if err != nil { + return errorResponse("copier: ensure: error resolving %q: %v", req.Directory, err) + } + + rel, err := convertToRelSubdirectory(req.Root, directory) + if err != nil { + return errorResponse("copier: ensure: error computing path of %q relative to %q: %v", directory, req.Root, err) + } + + subdir := "" + components := strings.Split(filepath.Join(rel, item.Path), string(os.PathSeparator)) + components = slices.DeleteFunc(components, func(s string) bool { return s == "" || s == "." }) + for i, component := range components { + parentPath := subdir + if parentPath == "" { + parentPath = "." + } + leaf := filepath.Join(parentPath, component) + parentInfo, err := os.Stat(filepath.Join(req.Root, parentPath)) + if err != nil { + return errorResponse("copier: ensure: checking datestamps on %q (%d: %v): %v", parentPath, i, components, err) + } + if parentPath != "." { + parentModTime := parentInfo.ModTime().UTC() + parentMode := parentInfo.Mode() + uid, gid, err := owner(parentInfo) + if err != nil { + return errorResponse("copier: ensure: error reading owner of %q: %v", parentPath, err) + } + notedByName[parentPath] = EnsureParentPath{ + Path: parentPath, + ModTime: &parentModTime, + Mode: &parentMode, + Owner: &idtools.IDPair{UID: uid, GID: gid}, + } + } + if i < len(components)-1 || item.Typeflag == tar.TypeDir { + err = os.Mkdir(filepath.Join(req.Root, leaf), mode) + subdir = leaf + } else if item.Typeflag == tar.TypeReg { + var f *os.File + if f, err = os.OpenFile(filepath.Join(req.Root, leaf), os.O_CREATE|os.O_EXCL|os.O_RDWR, mode); err == nil { + f.Close() + } + } else { + continue + } + if err == nil { + createdLeaf := leaf + if len(createdLeaf) > 1 { + createdLeaf = strings.TrimPrefix(createdLeaf, string(os.PathSeparator)) + } + created = append(created, createdLeaf) + if err = chown(filepath.Join(req.Root, leaf), uid, uid); err != nil { + return errorResponse("copier: ensure: error setting owner of %q to %d:%d: %v", leaf, uid, gid, err) + } + if err = chmod(filepath.Join(req.Root, leaf), mode); err != nil { + return errorResponse("copier: ensure: error setting permissions on %q to 0%o: %v", leaf, mode) + } + if item.ModTime != nil { + if err := os.Chtimes(filepath.Join(req.Root, leaf), *item.ModTime, *item.ModTime); err != nil { + return errorResponse("copier: ensure: resetting datestamp on %q: %v", leaf, err) + } + } + } else { + // FreeBSD can return EISDIR for "mkdir /": + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=59739. + if !errors.Is(err, os.ErrExist) && !errors.Is(err, syscall.EISDIR) { + return errorResponse("copier: ensure: error checking item %q: %v", leaf, err) + } + } + if err := os.Chtimes(filepath.Join(req.Root, parentPath), parentInfo.ModTime(), parentInfo.ModTime()); err != nil { + return errorResponse("copier: ensure: resetting datestamp on %q: %v", parentPath, err) + } + } + } + slices.Sort(created) + noted := make([]EnsureParentPath, 0, len(notedByName)) + for _, n := range notedByName { + if slices.Contains(created, n.Path) { + continue + } + noted = append(noted, n) + } + slices.SortFunc(noted, func(a, b EnsureParentPath) int { return strings.Compare(a.Path, b.Path) }) + return &response{Error: "", Ensure: ensureResponse{Created: created, Noted: noted}} +} + +// ConditionalRemovePath is a single item being passed to an ConditionalRemove() call. +type ConditionalRemovePath struct { + Path string // a pathname, relative to the Directory, possibly relative to the root + ModTime *time.Time // mtime to expect this item to have, if it's a condition + Mode *os.FileMode // mode to expect this item to have, if it's a condition + Owner *idtools.IDPair // owner to expect this item to have, if it's a condition +} + +// ConditionalRemoveOptions controls parts of ConditionalRemove()'s behavior. +type ConditionalRemoveOptions struct { + UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs in the chroot + Paths []ConditionalRemovePath +} + +// ConditionalRemove removes the set of named items if they're present and +// currently match the additional conditions, returning the list of items it +// removed. Directories will also only be removed if they have no contents, +// and will be left in place otherwise. +func ConditionalRemove(root, directory string, options ConditionalRemoveOptions) ([]string, error) { + req := request{ + Request: requestConditionalRemove, + Root: root, + Directory: directory, + ConditionalRemoveOptions: options, + } + resp, err := copier(nil, nil, req) + if err != nil { + return nil, err + } + if resp.Error != "" { + return nil, errors.New(resp.Error) + } + return resp.ConditionalRemove.Removed, nil +} + +func copierHandlerConditionalRemove(req request, idMappings *idtools.IDMappings) *response { + errorResponse := func(fmtspec string, args ...any) *response { + return &response{Error: fmt.Sprintf(fmtspec, args...), ConditionalRemove: conditionalRemoveResponse{}} + } + slices.SortFunc(req.ConditionalRemoveOptions.Paths, func(a, b ConditionalRemovePath) int { return strings.Compare(b.Path, a.Path) }) + var removed []string + for _, item := range req.ConditionalRemoveOptions.Paths { + uid, gid := 0, 0 + if item.Owner != nil { + uid, gid = item.Owner.UID, item.Owner.GID + } + if idMappings != nil && !idMappings.Empty() { + containerDirPair := idtools.IDPair{UID: uid, GID: gid} + hostDirPair, err := idMappings.ToHost(containerDirPair) + if err != nil { + return errorResponse("copier: conditionalRemove: error mapping container filesystem owner %d:%d to host filesystem owners: %v", uid, gid, err) + } + uid, gid = hostDirPair.UID, hostDirPair.GID + } + directory, err := resolvePath(req.Root, req.Directory, true, nil) + if err != nil { + return errorResponse("copier: conditionalRemove: error resolving %q: %v", req.Directory, err) + } + + rel, err := convertToRelSubdirectory(req.Root, directory) + if err != nil { + return errorResponse("copier: conditionalRemove: error computing path of %q relative to %q: %v", directory, req.Root, err) + } + + components := strings.Split(filepath.Join(rel, item.Path), string(os.PathSeparator)) + components = slices.DeleteFunc(components, func(s string) bool { return s == "" || s == "." }) + if len(components) == 0 { + continue + } + itemPath := filepath.Join(append([]string{req.Root}, components...)...) + itemInfo, err := os.Lstat(itemPath) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return errorResponse("copier: conditionalRemove: checking on candidate %q: %v", itemPath, err) + } + // okay? + removed = append(removed, item.Path) + continue + } + parentPath := filepath.Dir(itemPath) + parentInfo, err := os.Stat(parentPath) + if err != nil { + return errorResponse("copier: conditionalRemove: checking on parent directory %q: %v", parentPath, err) + } + + if item.Mode != nil && itemInfo.Mode().Perm()&fs.ModePerm != *item.Mode&fs.ModePerm { + // mismatch, modified? ignore + continue + } + if item.ModTime != nil && !item.ModTime.Equal(itemInfo.ModTime()) { + // mismatch, modified? ignore + continue + } + if item.Owner != nil { + ownerUID, ownerGID, err := owner(itemInfo) + if err != nil { + return errorResponse("copier: conditionalRemove: checking ownership of %q: %v", itemPath, err) + } + if uid != ownerUID || gid != ownerGID { + // mismatch, modified? ignore + continue + } + } + if err := os.Remove(itemPath); err != nil && !errors.Is(err, os.ErrNotExist) { + if !errors.Is(err, syscall.EEXIST) && !errors.Is(err, syscall.ENOTEMPTY) { + return errorResponse("copier: conditionalRemove: removing %q: %v", itemPath, err) + } + // okay? not removed, but it wasn't empty, so okay? + continue + } + removed = append(removed, item.Path) + if err := os.Chtimes(parentPath, parentInfo.ModTime(), parentInfo.ModTime()); err != nil { + return errorResponse("copier: conditionalRemove: resetting datestamp on %q: %v", parentPath, err) + } + } + slices.Sort(removed) + return &response{Error: "", ConditionalRemove: conditionalRemoveResponse{Removed: removed}} +} diff --git a/vendor/github.com/containers/buildah/copier/hardlink_not_uint64.go b/vendor/github.com/containers/buildah/copier/hardlink_not_uint64.go new file mode 100644 index 00000000..cee34aca --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/hardlink_not_uint64.go @@ -0,0 +1,14 @@ +//go:build darwin || (linux && mips) || (linux && mipsle) || (linux && mips64) || (linux && mips64le) + +package copier + +import ( + "syscall" +) + +func makeHardlinkDeviceAndInode(st *syscall.Stat_t) hardlinkDeviceAndInode { + return hardlinkDeviceAndInode{ + device: uint64(st.Dev), + inode: uint64(st.Ino), + } +} diff --git a/vendor/github.com/containers/buildah/copier/hardlink_uint64.go b/vendor/github.com/containers/buildah/copier/hardlink_uint64.go new file mode 100644 index 00000000..e88e1899 --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/hardlink_uint64.go @@ -0,0 +1,14 @@ +//go:build (linux && !mips && !mipsle && !mips64 && !mips64le) || freebsd || netbsd + +package copier + +import ( + "syscall" +) + +func makeHardlinkDeviceAndInode(st *syscall.Stat_t) hardlinkDeviceAndInode { + return hardlinkDeviceAndInode{ + device: st.Dev, + inode: st.Ino, + } +} diff --git a/vendor/github.com/containers/buildah/copier/hardlink_unix.go b/vendor/github.com/containers/buildah/copier/hardlink_unix.go new file mode 100644 index 00000000..f4d45fe9 --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/hardlink_unix.go @@ -0,0 +1,32 @@ +//go:build !windows + +package copier + +import ( + "os" + "sync" + "syscall" +) + +type hardlinkDeviceAndInode struct { + device, inode uint64 +} + +type hardlinkChecker struct { + hardlinks sync.Map +} + +func (h *hardlinkChecker) Check(fi os.FileInfo) string { + if st, ok := fi.Sys().(*syscall.Stat_t); ok && fi.Mode().IsRegular() && st.Nlink > 1 { + if name, ok := h.hardlinks.Load(makeHardlinkDeviceAndInode(st)); ok && name.(string) != "" { + return name.(string) + } + } + return "" +} + +func (h *hardlinkChecker) Add(fi os.FileInfo, name string) { + if st, ok := fi.Sys().(*syscall.Stat_t); ok && fi.Mode().IsRegular() && st.Nlink > 1 { + h.hardlinks.Store(makeHardlinkDeviceAndInode(st), name) + } +} diff --git a/vendor/github.com/containers/buildah/copier/hardlink_windows.go b/vendor/github.com/containers/buildah/copier/hardlink_windows.go new file mode 100644 index 00000000..c2c44458 --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/hardlink_windows.go @@ -0,0 +1,16 @@ +//go:build !linux && !darwin + +package copier + +import ( + "os" +) + +type hardlinkChecker struct{} + +func (h *hardlinkChecker) Check(fi os.FileInfo) string { + return "" +} + +func (h *hardlinkChecker) Add(fi os.FileInfo, name string) { +} diff --git a/vendor/github.com/containers/buildah/copier/mknod_int.go b/vendor/github.com/containers/buildah/copier/mknod_int.go new file mode 100644 index 00000000..e23e7171 --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/mknod_int.go @@ -0,0 +1,11 @@ +//go:build !windows && !freebsd + +package copier + +import ( + "golang.org/x/sys/unix" +) + +func mknod(path string, mode uint32, dev int) error { + return unix.Mknod(path, mode, dev) +} diff --git a/vendor/github.com/containers/buildah/copier/mknod_uint64.go b/vendor/github.com/containers/buildah/copier/mknod_uint64.go new file mode 100644 index 00000000..10bc4c2e --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/mknod_uint64.go @@ -0,0 +1,11 @@ +//go:build freebsd + +package copier + +import ( + "golang.org/x/sys/unix" +) + +func mknod(path string, mode uint32, dev int) error { + return unix.Mknod(path, mode, uint64(dev)) +} diff --git a/vendor/github.com/containers/buildah/copier/syscall_unix.go b/vendor/github.com/containers/buildah/copier/syscall_unix.go new file mode 100644 index 00000000..ccde63ed --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/syscall_unix.go @@ -0,0 +1,93 @@ +//go:build !windows + +package copier + +import ( + "fmt" + "os" + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +var canChroot = os.Getuid() == 0 + +func chroot(root string) (bool, error) { + if canChroot { + if err := os.Chdir(root); err != nil { + return false, fmt.Errorf("changing to intended-new-root directory %q: %w", root, err) + } + if err := unix.Chroot(root); err != nil { + return false, fmt.Errorf("chrooting to directory %q: %w", root, err) + } + if err := os.Chdir(string(os.PathSeparator)); err != nil { + return false, fmt.Errorf("changing to just-became-root directory %q: %w", root, err) + } + return true, nil + } + return false, nil +} + +func chrMode(mode os.FileMode) uint32 { + return uint32(unix.S_IFCHR | mode) +} + +func blkMode(mode os.FileMode) uint32 { + return uint32(unix.S_IFBLK | mode) +} + +func mkdev(major, minor uint32) uint64 { + return unix.Mkdev(major, minor) +} + +func mkfifo(path string, mode uint32) error { + return unix.Mkfifo(path, mode) +} + +func chmod(path string, mode os.FileMode) error { + return os.Chmod(path, mode) +} + +func chown(path string, uid, gid int) error { + return os.Chown(path, uid, gid) +} + +func lchown(path string, uid, gid int) error { + return os.Lchown(path, uid, gid) +} + +func lutimes(_ bool, path string, atime, mtime time.Time) error { + if atime.IsZero() || mtime.IsZero() { + now := time.Now() + if atime.IsZero() { + atime = now + } + if mtime.IsZero() { + mtime = now + } + } + return unix.Lutimes(path, []unix.Timeval{unix.NsecToTimeval(atime.UnixNano()), unix.NsecToTimeval(mtime.UnixNano())}) +} + +func owner(info os.FileInfo) (int, int, error) { + if st, ok := info.Sys().(*syscall.Stat_t); ok { + return int(st.Uid), int(st.Gid), nil + } + return -1, -1, syscall.ENOSYS +} + +// sameDevice returns true unless we're sure that they're not on the same device +func sameDevice(a, b os.FileInfo) bool { + aSys := a.Sys() + bSys := b.Sys() + if aSys == nil || bSys == nil { + return true + } + uA, okA := aSys.(*syscall.Stat_t) + uB, okB := bSys.(*syscall.Stat_t) + if !okA || !okB { + return true + } + return uA.Dev == uB.Dev +} diff --git a/vendor/github.com/containers/buildah/copier/syscall_windows.go b/vendor/github.com/containers/buildah/copier/syscall_windows.go new file mode 100644 index 00000000..f5fd62b9 --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/syscall_windows.go @@ -0,0 +1,87 @@ +//go:build windows + +package copier + +import ( + "errors" + "os" + "syscall" + "time" + + "golang.org/x/sys/windows" +) + +var canChroot = false + +func chroot(path string) (bool, error) { + return false, nil +} + +func chrMode(mode os.FileMode) uint32 { + return windows.S_IFCHR | uint32(mode) +} + +func blkMode(mode os.FileMode) uint32 { + return windows.S_IFBLK | uint32(mode) +} + +func mkdev(major, minor uint32) uint64 { + return 0 +} + +func mkfifo(path string, mode uint32) error { + return syscall.ENOSYS +} + +func mknod(path string, mode uint32, dev int) error { + return syscall.ENOSYS +} + +func chmod(path string, mode os.FileMode) error { + err := os.Chmod(path, mode) + if err != nil && errors.Is(err, syscall.EWINDOWS) { + return nil + } + return err +} + +func chown(path string, uid, gid int) error { + err := os.Chown(path, uid, gid) + if err != nil && errors.Is(err, syscall.EWINDOWS) { + return nil + } + return err +} + +func lchown(path string, uid, gid int) error { + err := os.Lchown(path, uid, gid) + if err != nil && errors.Is(err, syscall.EWINDOWS) { + return nil + } + return err +} + +func lutimes(isSymlink bool, path string, atime, mtime time.Time) error { + if isSymlink { + return nil + } + if atime.IsZero() || mtime.IsZero() { + now := time.Now() + if atime.IsZero() { + atime = now + } + if mtime.IsZero() { + mtime = now + } + } + return windows.UtimesNano(path, []windows.Timespec{windows.NsecToTimespec(atime.UnixNano()), windows.NsecToTimespec(mtime.UnixNano())}) +} + +func owner(info os.FileInfo) (int, int, error) { + return -1, -1, syscall.ENOSYS +} + +// sameDevice returns true since we can't be sure that they're not on the same device +func sameDevice(a, b os.FileInfo) bool { + return true +} diff --git a/vendor/github.com/containers/buildah/copier/xattrs.go b/vendor/github.com/containers/buildah/copier/xattrs.go new file mode 100644 index 00000000..040a6603 --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/xattrs.go @@ -0,0 +1,115 @@ +//go:build linux || netbsd || freebsd || darwin + +package copier + +import ( + "errors" + "fmt" + "path/filepath" + "strings" + "syscall" + + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/unshare" + "golang.org/x/sys/unix" +) + +const ( + xattrsSupported = true + imaXattr = "security.ima" +) + +var ( + relevantAttributes = []string{"security.capability", imaXattr, "user.*"} // the attributes that we preserve - we discard others + irrelevantAttributes = []string{"user.overlay.*"} // the attributes that we discard, even from the relevantAttributes list + initialXattrListSize = 64 * 1024 + initialXattrValueSize = 64 * 1024 +) + +// isRelevantXattr checks if "attribute" matches one of the attribute patterns +// listed in the "relevantAttributes" list. +func isRelevantXattr(attribute string) bool { + for _, relevant := range relevantAttributes { + matched, err := filepath.Match(relevant, attribute) + if err != nil || !matched { + continue + } + for _, irrelevant := range irrelevantAttributes { + matched, err := filepath.Match(irrelevant, attribute) + if err != nil || !matched { + continue + } + return false + } + return true + } + return false +} + +// Lgetxattrs returns a map of the relevant extended attributes set on the given file. +func Lgetxattrs(path string) (map[string]string, error) { + maxSize := 64 * 1024 * 1024 + listSize := initialXattrListSize + var list []byte + for listSize < maxSize { + list = make([]byte, listSize) + size, err := unix.Llistxattr(path, list) + if err != nil { + if errors.Is(err, syscall.ERANGE) { + listSize *= 2 + continue + } + if errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.ENOSYS) { + // treat these errors listing xattrs as equivalent to "no xattrs" + list = list[:0] + break + } + return nil, fmt.Errorf("listing extended attributes of %q: %w", path, err) + } + list = list[:size] + break + } + if listSize >= maxSize { + return nil, fmt.Errorf("unable to read list of attributes for %q: size would have been too big", path) + } + m := make(map[string]string) + for attribute := range strings.SplitSeq(string(list), string('\000')) { + if isRelevantXattr(attribute) { + attributeSize := initialXattrValueSize + var attributeValue []byte + for attributeSize < maxSize { + attributeValue = make([]byte, attributeSize) + size, err := unix.Lgetxattr(path, attribute, attributeValue) + if err != nil { + if errors.Is(err, syscall.ERANGE) { + attributeSize *= 2 + continue + } + return nil, fmt.Errorf("getting value of extended attribute %q on %q: %w", attribute, path, err) + } + m[attribute] = string(attributeValue[:size]) + break + } + if attributeSize >= maxSize { + return nil, fmt.Errorf("unable to read attribute %q of %q: size would have been too big", attribute, path) + } + } + } + return m, nil +} + +// Lsetxattrs sets the relevant members of the specified extended attributes on the given file. +func Lsetxattrs(path string, xattrs map[string]string) error { + for attribute, value := range xattrs { + if isRelevantXattr(attribute) { + if err := unix.Lsetxattr(path, attribute, []byte(value), 0); err != nil { + if unshare.IsRootless() && attribute == imaXattr { + logrus.Warnf("Unable to set %q xattr on %q: %v", attribute, path, err) + } else { + return fmt.Errorf("setting value of extended attribute %q on %q: %w", attribute, path, err) + } + } + } + } + return nil +} diff --git a/vendor/github.com/containers/buildah/copier/xattrs_unsupported.go b/vendor/github.com/containers/buildah/copier/xattrs_unsupported.go new file mode 100644 index 00000000..59444ff6 --- /dev/null +++ b/vendor/github.com/containers/buildah/copier/xattrs_unsupported.go @@ -0,0 +1,15 @@ +//go:build !linux && !netbsd && !freebsd && !darwin + +package copier + +const ( + xattrsSupported = false +) + +func Lgetxattrs(path string) (map[string]string, error) { + return nil, nil +} + +func Lsetxattrs(path string, xattrs map[string]string) error { + return nil +} diff --git a/vendor/github.com/containers/buildah/define/build.go b/vendor/github.com/containers/buildah/define/build.go new file mode 100644 index 00000000..0b1d2080 --- /dev/null +++ b/vendor/github.com/containers/buildah/define/build.go @@ -0,0 +1,421 @@ +package define + +import ( + "io" + "time" + + encconfig "github.com/containers/ocicrypt/config" + "go.podman.io/common/libimage/manifests" + nettypes "go.podman.io/common/libnetwork/types" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/archive" + "golang.org/x/sync/semaphore" +) + +// AdditionalBuildContext contains verbose details about a parsed build context from --build-context +type AdditionalBuildContext struct { + // Value is the URL of an external tar archive. + IsURL bool + // Value is the name of an image which may or may not have already been pulled. + IsImage bool + // Value holds a URL (if IsURL), an image name (if IsImage), or an absolute filesystem path. + Value string + // Absolute filesystem path to a downloaded and exported build context + // from an external tar archive. This will be populated only if the + // build context was a URL and its contents have been downloaded. + DownloadedCache string +} + +// CommonBuildOptions are resources that can be defined by flags for both buildah from and build +type CommonBuildOptions struct { + // AddHost is the list of hostnames to add to the build container's /etc/hosts. + AddHost []string + // OmitHistory tells the builder to ignore the history of build layers and + // base while preparing image-spec, setting this to true will ensure no history + // is added to the image-spec. (default false) + OmitHistory bool + // CgroupParent is the path to cgroups under which the cgroup for the container will be created. + CgroupParent string + // CPUPeriod limits the CPU CFS (Completely Fair Scheduler) period + CPUPeriod uint64 + // CPUQuota limits the CPU CFS (Completely Fair Scheduler) quota + CPUQuota int64 + // CPUShares (relative weight + CPUShares uint64 + // CPUSetCPUs in which to allow execution (0-3, 0,1) + CPUSetCPUs string + // CPUSetMems memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. + CPUSetMems string + // HTTPProxy determines whether *_proxy env vars from the build host are passed into the container. + HTTPProxy bool + // IdentityLabel if set controls whether or not a `io.buildah.version` label is added to the built image. + // Setting this to false does not clear the label if it would be inherited from the base image. + IdentityLabel types.OptionalBool + // Memory is the upper limit (in bytes) on how much memory running containers can use. + Memory int64 + // DNSSearch is the list of DNS search domains to add to the build container's /etc/resolv.conf + DNSSearch []string + // DNSServers is the list of DNS servers to add to the build container's /etc/resolv.conf + DNSServers []string + // DNSOptions is the list of DNS + DNSOptions []string + // LabelOpts is a slice of the fields of an SELinux context, given in "field:pair" format, or "disable". + // Recognized field names are "role", "type", and "level". + LabelOpts []string + // Paths to mask + Masks []string + // MemorySwap limits the amount of memory and swap together. + MemorySwap int64 + // NoHostname tells the builder not to create /etc/hostname content when running + // containers. + NoHostname bool + // NoHosts tells the builder not to create /etc/hosts content when running + // containers. + NoHosts bool + // NoNewPrivileges removes the ability for the container to gain privileges + NoNewPrivileges bool + // OmitTimestamp forces epoch 0 as created timestamp to allow for + // deterministic, content-addressable builds. + OmitTimestamp bool + // SeccompProfilePath is the pathname of a seccomp profile. + SeccompProfilePath string + // ApparmorProfile is the name of an apparmor profile. + ApparmorProfile string + // ShmSize is the "size" value to use when mounting an shmfs on the container's /dev/shm directory. + ShmSize string + // Ulimit specifies resource limit options, in the form type:softlimit[:hardlimit]. + // These types are recognized: + // "core": maximum core dump size (ulimit -c) + // "cpu": maximum CPU time (ulimit -t) + // "data": maximum size of a process's data segment (ulimit -d) + // "fsize": maximum size of new files (ulimit -f) + // "locks": maximum number of file locks (ulimit -x) + // "memlock": maximum amount of locked memory (ulimit -l) + // "msgqueue": maximum amount of data in message queues (ulimit -q) + // "nice": niceness adjustment (nice -n, ulimit -e) + // "nofile": maximum number of open files (ulimit -n) + // "nproc": maximum number of processes (ulimit -u) + // "rss": maximum size of a process's (ulimit -m) + // "rtprio": maximum real-time scheduling priority (ulimit -r) + // "rttime": maximum amount of real-time execution between blocking syscalls + // "sigpending": maximum number of pending signals (ulimit -i) + // "stack": maximum stack size (ulimit -s) + Ulimit []string + // Volumes to bind mount into the container + Volumes []string + // Secrets are the available secrets to use in a build. Each item in the + // slice takes the form "id=foo,src=bar", where both "id" and "src" are + // required, in that order, and "bar" is the name of a file. + Secrets []string + // SSHSources is the available ssh agent connections to forward in the build + SSHSources []string + // OCIHooksDir is the location of OCI hooks for the build containers + OCIHooksDir []string + // Paths to unmask + Unmasks []string +} + +// BuildOptions can be used to alter how an image is built. +type BuildOptions struct { + // ContainerSuffix it the name to suffix containers with + ContainerSuffix string + // ContextDirectory is the default source location for COPY and ADD + // commands. + ContextDirectory string + // PullPolicy controls whether or not we pull images. It should be one + // of PullIfMissing, PullAlways, PullIfNewer, or PullNever. + PullPolicy PullPolicy + // Registry is a value which is prepended to the image's name, if it + // needs to be pulled and the image name alone can not be resolved to a + // reference to a source image. No separator is implicitly added. + Registry string + // IgnoreUnrecognizedInstructions tells us to just log instructions we + // don't recognize, and try to keep going. + IgnoreUnrecognizedInstructions bool + // Manifest Name to which the image will be added. + Manifest string + // Quiet tells us whether or not to announce steps as we go through them. + Quiet bool + // Isolation controls how Run() runs things. + Isolation Isolation + // Runtime is the name of the command to run for RUN instructions when + // Isolation is either IsolationDefault or IsolationOCI. It should + // accept the same arguments and flags that runc does. + Runtime string + // RuntimeArgs adds global arguments for the runtime. + RuntimeArgs []string + // TransientMounts is a list of unparsed mounts that will be provided to + // RUN instructions. + TransientMounts []string + // CacheFrom specifies any remote repository which can be treated as + // potential cache source. + CacheFrom []reference.Named + // CacheTo specifies any remote repository which can be treated as + // potential cache destination. + CacheTo []reference.Named + // CacheTTL specifies duration, if specified using `--cache-ttl` then + // cache intermediate images under this duration will be considered as + // valid cache sources and images outside this duration will be ignored. + CacheTTL time.Duration + // Compression specifies the type of compression which is applied to + // layer blobs. The default is to not use compression, but + // archive.Gzip is recommended. + Compression archive.Compression + // Arguments which can be interpolated into Dockerfiles + Args map[string]string + // Map of external additional build contexts + AdditionalBuildContexts map[string]*AdditionalBuildContext + // Name of the image to write to. + Output string + // BuildOutputs specifies if any custom build output is selected for + // following build. It allows the end user to export the image's + // rootfs to a directory or a tar archive. See the documentation of + // 'buildah build --output' for the details of the syntax. + BuildOutputs []string + // Deprecated: use BuildOutputs instead. + BuildOutput string + // ConfidentialWorkload controls whether or not, and if so, how, we produce an + // image that's meant to be run using krun as a VM instead of a conventional + // process-type container. + ConfidentialWorkload ConfidentialWorkloadOptions + // Additional tags to add to the image that we write, if we know of a + // way to add them. + AdditionalTags []string + // Logfile specifies if log output is redirected to an external file + // instead of stdout, stderr. + LogFile string + // LogByPlatform tells imagebuildah to split log to different log files + // for each platform if logging to external file was selected. + LogSplitByPlatform bool + // Log is a callback that will print a progress message. If no value + // is supplied, the message will be sent to Err (or os.Stderr, if Err + // is nil) by default. + Log func(format string, args ...any) + // In is connected to stdin for RUN instructions. + In io.Reader + // Out is a place where non-error log messages are sent. + Out io.Writer + // Err is a place where error log messages should be sent. + Err io.Writer + // SignaturePolicyPath specifies an override location for the signature + // policy which should be used for verifying the new image as it is + // being written. Except in specific circumstances, no value should be + // specified, indicating that the shared, system-wide default policy + // should be used. + SignaturePolicyPath string + // SkipUnusedStages allows users to skip stages in a multi-stage builds + // which do not contribute anything to the target stage. Expected default + // value is true. + SkipUnusedStages types.OptionalBool + // ReportWriter is an io.Writer which will be used to report the + // progress of the (possible) pulling of the source image and the + // writing of the new image. + ReportWriter io.Writer + // OutputFormat is the format of the output image's manifest and + // configuration data. + // Accepted values are buildah.OCIv1ImageManifest and buildah.Dockerv2ImageManifest. + OutputFormat string + // SystemContext holds parameters used for authentication. + SystemContext *types.SystemContext + // NamespaceOptions controls how we set up namespaces processes that we + // might need when handling RUN instructions. + NamespaceOptions []NamespaceOption + // ConfigureNetwork controls whether or not network interfaces and + // routing are configured for a new network namespace (i.e., when not + // joining another's namespace and not just using the host's + // namespace), effectively deciding whether or not the process has a + // usable network. + ConfigureNetwork NetworkConfigurationPolicy + // CNIPluginPath is the location of CNI plugin helpers, if they should be + // run from a location other than the default location. + CNIPluginPath string + // CNIConfigDir is the location of CNI configuration files, if the files in + // the default configuration directory shouldn't be used. + CNIConfigDir string + + // NetworkInterface is the libnetwork network interface used to setup CNI or netavark networks. + NetworkInterface nettypes.ContainerNetwork `json:"-"` + + // ID mapping options to use if we're setting up our own user namespace + // when handling RUN instructions. + IDMappingOptions *IDMappingOptions + // InheritLabels controls whether or not built images will retain the labels + // which were set in their base images + InheritLabels types.OptionalBool + // InheritAnnotations controls whether or not built images will retain the annotations + // which were set in their base images + InheritAnnotations types.OptionalBool + // AddCapabilities is a list of capabilities to add to the default set when + // handling RUN instructions. + AddCapabilities []string + // DropCapabilities is a list of capabilities to remove from the default set + // when handling RUN instructions. If a capability appears in both lists, it + // will be dropped. + DropCapabilities []string + // CommonBuildOpts is *required*. + CommonBuildOpts *CommonBuildOptions + // CPPFlags are additional arguments to pass to the C Preprocessor (cpp). + CPPFlags []string + // DefaultMountsFilePath is the file path holding the mounts to be mounted for RUN + // instructions in "host-path:container-path" format + DefaultMountsFilePath string + // IIDFile tells the builder to write the image ID to the specified file + IIDFile string + // Squash tells the builder to produce an image with a single layer instead of with + // possibly more than one layer, by only committing a new layer after processing the + // final instruction. + Squash bool + // Labels to set in a committed image. + Labels []string + // LayerLabels metadata for an intermediate image + LayerLabels []string + // Annotations to set in a committed image, in OCI format. + Annotations []string + // OnBuild commands to be run by builds that use the image we'll commit as a base image. + OnBuild []string + // Layers tells the builder to commit an image for each step in the Dockerfile. + Layers bool + // NoCache tells the builder to build the image from scratch without checking for a cache. + // It creates a new set of cached images for the build. + NoCache bool + // RemoveIntermediateCtrs tells the builder whether to remove intermediate containers used + // during the build process. Default is true. + RemoveIntermediateCtrs bool + // ForceRmIntermediateCtrs tells the builder to remove all intermediate containers even if + // the build was unsuccessful. + ForceRmIntermediateCtrs bool + // BlobDirectory is a directory which we'll use for caching layer blobs. + // + // This option will be overridden for cache pulls if + // CachePullDestinationLookupReferenceFunc is set, and overridden for cache pushes if + // CachePushSourceLookupReferenceFunc is set. + BlobDirectory string + // Target the targeted FROM in the Dockerfile to build. + Target string + // Devices are unparsed devices to provide to RUN instructions. + Devices []string + // SignBy is the fingerprint of a GPG key to use for signing images. + SignBy string + // Architecture specifies the target architecture of the image to be built. + Architecture string + // Timestamp specifies a timestamp to use for the image's created-on + // date, the corresponding field in new history entries, the timestamps + // to set on contents in new layer diffs, and the timestamps to set on + // contents written as specified in the BuildOutput field. If left + // unset, the current time is used for the configuration and manifest, + // and layer contents are recorded as-is. + Timestamp *time.Time + // SourceDateEpoch specifies a timestamp to use for the image's + // created-on date and the corresponding field in new history entries, + // and any content written as specified in the BuildOutput field. If + // left unset, the current time is used for the configuration and + // manifest, and layer and BuildOutput contents retain their original + // timestamps. + SourceDateEpoch *time.Time + // RewriteTimestamp, if set, forces timestamps in generated layers to + // not be later than the SourceDateEpoch, if it is also set. + RewriteTimestamp bool + // OS is the specifies the operating system of the image to be built. + OS string + // MaxPullPushRetries is the maximum number of attempts we'll make to pull or push any one + // image from or to an external registry if the first attempt fails. + MaxPullPushRetries int + // PullPushRetryDelay is how long to wait before retrying a pull or push attempt. + PullPushRetryDelay time.Duration + // OciDecryptConfig contains the config that can be used to decrypt an image if it is + // encrypted if non-nil. If nil, it does not attempt to decrypt an image. + OciDecryptConfig *encconfig.DecryptConfig + // Jobs is the number of stages to run in parallel. If not specified it defaults to 1. + // Ignored if a JobSemaphore is provided. + Jobs *int + // JobSemaphore, for when you want Jobs to be shared with more than just this build. + JobSemaphore *semaphore.Weighted + // LogRusage logs resource usage for each step. + LogRusage bool + // File to which the Rusage logs will be saved to instead of stdout. + RusageLogFile string + // Excludes is a list of excludes to be used instead of the .dockerignore file. + Excludes []string + // IgnoreFile is a name of the .containerignore file + IgnoreFile string + // From is the image name to use to replace the value specified in the first + // FROM instruction in the Containerfile. + From string + // GroupAdd is a list of groups to add to the primary process when handling RUN + // instructions. The magic 'keep-groups' value indicates that the process should + // be allowed to inherit the current set of supplementary groups. + GroupAdd []string + // Platforms is the list of parsed OS/Arch/Variant triples that we want + // to build the image for. If this slice has items in it, the OS and + // Architecture fields above are ignored. + Platforms []struct{ OS, Arch, Variant string } + // AllPlatforms tells the builder to set the list of target platforms + // to match the set of platforms for which all of the build's base + // images are available. If this field is set, Platforms is ignored. + AllPlatforms bool + // UnsetEnvs is a list of environments to not add to final image. + UnsetEnvs []string + // UnsetLabels is a list of labels to not add to final image from base image. + UnsetLabels []string + // UnsetAnnotations is a list of annotations to not add to final image from base image. + UnsetAnnotations []string + // Envs is a list of environment variables to set in the final image. + Envs []string + // OSFeatures specifies operating system features the image requires. + // It is typically only set when the OS is "windows". + OSFeatures []string + // OSVersion specifies the exact operating system version the image + // requires. It is typically only set when the OS is "windows". Any + // value set in a base image will be preserved, so this does not + // frequently need to be set. + OSVersion string + // SBOMScanOptions encapsulates options which control whether or not we + // run scanners on the rootfs that we're about to commit, and how. + SBOMScanOptions []SBOMScanOptions + // CDIConfigDir is the location of CDI configuration files, if the files in + // the default configuration locations shouldn't be used. + CDIConfigDir string + // CachePullSourceLookupReferenceFunc is an optional LookupReferenceFunc + // used to look up source references for cache pulls. + CachePullSourceLookupReferenceFunc manifests.LookupReferenceFunc + // CachePullDestinationLookupReferenceFunc is an optional generator + // function which provides a LookupReferenceFunc used to look up + // destination references for cache pulls. + // + // BlobDirectory will be ignored for cache pulls if this option is set. + CachePullDestinationLookupReferenceFunc func(srcRef types.ImageReference) manifests.LookupReferenceFunc + // CachePushSourceLookupReferenceFunc is an optional generator function + // which provides a LookupReferenceFunc used to look up source + // references for cache pushes. + // + // BlobDirectory will be ignored for cache pushes if this option is set. + CachePushSourceLookupReferenceFunc func(dest types.ImageReference) manifests.LookupReferenceFunc + // CachePushDestinationLookupReferenceFunc is an optional + // LookupReferenceFunc used to look up destination references for cache + // pushes + CachePushDestinationLookupReferenceFunc manifests.LookupReferenceFunc + // CompatSetParent causes the "parent" field to be set in the image's + // configuration when committing in Docker format. Newer + // BuildKit-based docker build doesn't set this field. + CompatSetParent types.OptionalBool + // CompatVolumes causes the contents of locations marked as volumes in + // base images or by a VOLUME instruction to be preserved during RUN + // instructions. Newer BuildKit-based docker build doesn't bother. + CompatVolumes types.OptionalBool + // CompatScratchConfig causes the image, if it does not have a base + // image, to begin with a truly empty default configuration instead of + // a minimal default configuration. Newer BuildKit-based docker build + // provides a minimal initial configuration with a working directory + // set in it. + CompatScratchConfig types.OptionalBool + // CompatLayerOmissions causes the "/dev", "/proc", and "/sys" + // directories to be omitted from the image and related output. Newer + // BuildKit-based builds include them in the built image by default. + CompatLayerOmissions types.OptionalBool + // NoPivotRoot inhibits the usage of pivot_root when setting up the rootfs + NoPivotRoot bool + // CreatedAnnotation controls whether or not an "org.opencontainers.image.created" + // annotation is present in the output image. + CreatedAnnotation types.OptionalBool +} diff --git a/vendor/github.com/containers/buildah/define/isolation.go b/vendor/github.com/containers/buildah/define/isolation.go new file mode 100644 index 00000000..53bea85f --- /dev/null +++ b/vendor/github.com/containers/buildah/define/isolation.go @@ -0,0 +1,32 @@ +package define + +import ( + "fmt" +) + +type Isolation int + +const ( + // IsolationDefault is whatever we think will work best. + IsolationDefault Isolation = iota + // IsolationOCI is a proper OCI runtime. + IsolationOCI + // IsolationChroot is a more chroot-like environment: less isolation, + // but with fewer requirements. + IsolationChroot + // IsolationOCIRootless is a proper OCI runtime in rootless mode. + IsolationOCIRootless +) + +// String converts a Isolation into a string. +func (i Isolation) String() string { + switch i { + case IsolationDefault, IsolationOCI: + return "oci" + case IsolationChroot: + return "chroot" + case IsolationOCIRootless: + return "rootless" + } + return fmt.Sprintf("unrecognized isolation type %d", i) +} diff --git a/vendor/github.com/containers/buildah/define/mount_freebsd.go b/vendor/github.com/containers/buildah/define/mount_freebsd.go new file mode 100644 index 00000000..c0ac6991 --- /dev/null +++ b/vendor/github.com/containers/buildah/define/mount_freebsd.go @@ -0,0 +1,14 @@ +//go:build freebsd + +package define + +const ( + // TypeBind is the type for mounting host dir + TypeBind = "nullfs" + + // TempDir is the default for storing temporary files + TempDir = "/var/tmp" +) + +// Mount potions for bind +var BindOptions = []string{} diff --git a/vendor/github.com/containers/buildah/define/mount_linux.go b/vendor/github.com/containers/buildah/define/mount_linux.go new file mode 100644 index 00000000..33530910 --- /dev/null +++ b/vendor/github.com/containers/buildah/define/mount_linux.go @@ -0,0 +1,14 @@ +//go:build linux + +package define + +const ( + // TypeBind is the type for mounting host dir + TypeBind = "bind" + + // TempDir is the default for storing temporary files + TempDir = "/dev/shm" +) + +// Mount potions for bind +var BindOptions = []string{"bind"} diff --git a/vendor/github.com/containers/buildah/define/mount_unsupported.go b/vendor/github.com/containers/buildah/define/mount_unsupported.go new file mode 100644 index 00000000..3feab28c --- /dev/null +++ b/vendor/github.com/containers/buildah/define/mount_unsupported.go @@ -0,0 +1,14 @@ +//go:build darwin || windows || netbsd + +package define + +const ( + // TypeBind is the type for mounting host dir + TypeBind = "bind" + + // TempDir is the default for storing temporary files + TempDir = "/var/tmp" +) + +// Mount potions for bind +var BindOptions = []string{""} diff --git a/vendor/github.com/containers/buildah/define/namespace.go b/vendor/github.com/containers/buildah/define/namespace.go new file mode 100644 index 00000000..d0247fe9 --- /dev/null +++ b/vendor/github.com/containers/buildah/define/namespace.go @@ -0,0 +1,87 @@ +package define + +import ( + "fmt" +) + +// NamespaceOption controls how we set up a namespace when launching processes. +type NamespaceOption struct { + // Name specifies the type of namespace, typically matching one of the + // ...Namespace constants defined in + // github.com/opencontainers/runtime-spec/specs-go. + Name string + // Host is used to force our processes to use the host's namespace of + // this type. + Host bool + // Path is the path of the namespace to attach our process to, if Host + // is not set. If Host is not set and Path is also empty, a new + // namespace will be created for the process that we're starting. + // If Name is specs.NetworkNamespace, if Path doesn't look like an + // absolute path, it is treated as a comma-separated list of CNI + // configuration names which will be selected from among all of the CNI + // network configurations which we find. + Path string +} + +// NamespaceOptions provides some helper methods for a slice of NamespaceOption +// structs. +type NamespaceOptions []NamespaceOption + +// Find the configuration for the namespace of the given type. If there are +// duplicates, find the _last_ one of the type, since we assume it was appended +// more recently. +func (n *NamespaceOptions) Find(namespace string) *NamespaceOption { + for i := range *n { + j := len(*n) - 1 - i + if (*n)[j].Name == namespace { + return &((*n)[j]) + } + } + return nil +} + +// AddOrReplace either adds or replaces the configuration for a given namespace. +func (n *NamespaceOptions) AddOrReplace(options ...NamespaceOption) { +nextOption: + for _, option := range options { + for i := range *n { + j := len(*n) - 1 - i + if (*n)[j].Name == option.Name { + (*n)[j] = option + continue nextOption + } + } + *n = append(*n, option) + } +} + +// NetworkConfigurationPolicy takes the value NetworkDefault, NetworkDisabled, +// or NetworkEnabled. +type NetworkConfigurationPolicy int + +const ( + // NetworkDefault is one of the values that BuilderOptions.ConfigureNetwork + // can take, signalling that the default behavior should be used. + NetworkDefault NetworkConfigurationPolicy = iota + // NetworkDisabled is one of the values that BuilderOptions.ConfigureNetwork + // can take, signalling that network interfaces should NOT be configured for + // newly-created network namespaces. + NetworkDisabled + // NetworkEnabled is one of the values that BuilderOptions.ConfigureNetwork + // can take, signalling that network interfaces should be configured for + // newly-created network namespaces. + NetworkEnabled +) + +// String formats a NetworkConfigurationPolicy as a string. +func (p NetworkConfigurationPolicy) String() string { + switch p { + case NetworkDefault: + return "NetworkDefault" + case NetworkDisabled: + return "NetworkDisabled" + case NetworkEnabled: + return "NetworkEnabled" + } + return fmt.Sprintf("unknown NetworkConfigurationPolicy %d", p) +} diff --git a/vendor/github.com/containers/buildah/define/pull.go b/vendor/github.com/containers/buildah/define/pull.go new file mode 100644 index 00000000..a98cc3d2 --- /dev/null +++ b/vendor/github.com/containers/buildah/define/pull.go @@ -0,0 +1,53 @@ +package define + +import ( + "fmt" +) + +// PullPolicy takes the value PullIfMissing, PullAlways, PullIfNewer, or PullNever. +// N.B.: the enumeration values for this type differ from those used by +// github.com/containers/common/pkg/config.PullPolicy (their zero values +// indicate different policies), so they are not interchangeable. +type PullPolicy int + +const ( + // PullIfMissing is one of the values that BuilderOptions.PullPolicy + // can take, signalling that the source image should be pulled from a + // registry if a local copy of it is not already present. + PullIfMissing PullPolicy = iota + // PullAlways is one of the values that BuilderOptions.PullPolicy can + // take, signalling that a fresh, possibly updated, copy of the image + // should be pulled from a registry before the build proceeds. + PullAlways + // PullIfNewer is one of the values that BuilderOptions.PullPolicy + // can take, signalling that the source image should only be pulled + // from a registry if a local copy is not already present or if a + // newer version the image is present on the repository. + PullIfNewer + // PullNever is one of the values that BuilderOptions.PullPolicy can + // take, signalling that the source image should not be pulled from a + // registry. + PullNever +) + +// String converts a PullPolicy into a string. +func (p PullPolicy) String() string { + switch p { + case PullIfMissing: + return "missing" + case PullAlways: + return "always" + case PullIfNewer: + return "ifnewer" + case PullNever: + return "never" + } + return fmt.Sprintf("unrecognized policy %d", p) +} + +var PolicyMap = map[string]PullPolicy{ + "missing": PullIfMissing, + "always": PullAlways, + "never": PullNever, + "ifnewer": PullIfNewer, +} diff --git a/vendor/github.com/containers/buildah/define/types.go b/vendor/github.com/containers/buildah/define/types.go new file mode 100644 index 00000000..47cea19c --- /dev/null +++ b/vendor/github.com/containers/buildah/define/types.go @@ -0,0 +1,354 @@ +package define + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "net/http" + urlpkg "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/image/v5/manifest" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/chrootarchive" + "go.podman.io/storage/pkg/ioutils" + "go.podman.io/storage/types" +) + +const ( + // Package is the name of this package, used in help output and to + // identify working containers. + Package = "buildah" + // Version for the Package. Also used by .packit.sh for Packit builds. + Version = "1.42.2" + + // DefaultRuntime if containers.conf fails. + DefaultRuntime = "runc" + + // OCIv1ImageManifest is the MIME type of an OCIv1 image manifest, + // suitable for specifying as a value of the PreferredManifestType + // member of a CommitOptions structure. It is also the default. + OCIv1ImageManifest = v1.MediaTypeImageManifest + // Dockerv2ImageManifest is the MIME type of a Docker v2s2 image + // manifest, suitable for specifying as a value of the + // PreferredManifestType member of a CommitOptions structure. + Dockerv2ImageManifest = manifest.DockerV2Schema2MediaType + + // OCI used to define the "oci" image format + OCI = "oci" + // DOCKER used to define the "docker" image format + DOCKER = "docker" + + // SEV is a known trusted execution environment type: AMD-SEV (secure encrypted virtualization using encrypted state, requires epyc 1000 "naples") + SEV TeeType = "sev" + // SNP is a known trusted execution environment type: AMD-SNP (SEV secure nested pages) (requires epyc 3000 "milan") + SNP TeeType = "snp" +) + +// DefaultRlimitValue is the value set by default for nofile and nproc +const RLimitDefaultValue = uint64(1048576) + +// TeeType is a supported trusted execution environment type. +type TeeType string + +var ( + // Deprecated: DefaultCapabilities values should be retrieved from + // github.com/containers/common/pkg/config + DefaultCapabilities = []string{ + "CAP_AUDIT_WRITE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_CHROOT", + } + // Deprecated: DefaultNetworkSysctl values should be retrieved from + // github.com/containers/common/pkg/config + DefaultNetworkSysctl = map[string]string{ + "net.ipv4.ping_group_range": "0 0", + } + + Gzip = archive.Gzip + Bzip2 = archive.Bzip2 + Xz = archive.Xz + Zstd = archive.Zstd + Uncompressed = archive.Uncompressed +) + +// IDMappingOptions controls how we set up UID/GID mapping when we set up a +// user namespace. +type IDMappingOptions struct { + HostUIDMapping bool + HostGIDMapping bool + UIDMap []specs.LinuxIDMapping + GIDMap []specs.LinuxIDMapping + AutoUserNs bool + AutoUserNsOpts types.AutoUserNsOptions +} + +// Secret is a secret source that can be used in a RUN +type Secret struct { + ID string + Source string + SourceType string +} + +// BuildOutputOptions contains the the outcome of parsing the value of a build --output flag +type BuildOutputOption struct { + Path string // Only valid if !IsStdout + IsDir bool + IsStdout bool +} + +// ConfidentialWorkloadOptions encapsulates options which control whether or not +// we output an image whose rootfs contains a LUKS-compatibly-encrypted disk image +// instead of the usual rootfs contents. +type ConfidentialWorkloadOptions struct { + Convert bool + AttestationURL string + CPUs int + Memory int + TempDir string // used for the temporary plaintext copy of the disk image + TeeType TeeType + IgnoreAttestationErrors bool + WorkloadID string + DiskEncryptionPassphrase string + Slop string + FirmwareLibrary string +} + +// SBOMMergeStrategy tells us how to merge multiple SBOM documents into one. +type SBOMMergeStrategy string + +const ( + // SBOMMergeStrategyCat literally concatenates the documents. + SBOMMergeStrategyCat SBOMMergeStrategy = "cat" + // SBOMMergeStrategyCycloneDXByComponentNameAndVersion adds components + // from the second document to the first, so long as they have a + // name+version combination which is not already present in the + // components array. + SBOMMergeStrategyCycloneDXByComponentNameAndVersion SBOMMergeStrategy = "merge-cyclonedx-by-component-name-and-version" + // SBOMMergeStrategySPDXByPackageNameAndVersionInfo adds packages from + // the second document to the first, so long as they have a + // name+versionInfo combination which is not already present in the + // first document's packages array, and adds hasExtractedLicensingInfos + // items from the second document to the first, so long as they include + // a licenseId value which is not already present in the first + // document's hasExtractedLicensingInfos array. + SBOMMergeStrategySPDXByPackageNameAndVersionInfo SBOMMergeStrategy = "merge-spdx-by-package-name-and-versioninfo" +) + +// SBOMScanOptions encapsulates options which control whether or not we run a +// scanner on the rootfs that we're about to commit, and how. +type SBOMScanOptions struct { + Type []string // a shorthand name for a defined group of these options + Image string // the scanner image to use + PullPolicy PullPolicy // how to get the scanner image + Commands []string // one or more commands to invoke for the image rootfs or ContextDir locations + ContextDir []string // one or more "source" directory locations + SBOMOutput string // where to save SBOM scanner output outside of the image (i.e., the local filesystem) + PURLOutput string // where to save PURL list outside of the image (i.e., the local filesystem) + ImageSBOMOutput string // where to save SBOM scanner output in the image + ImagePURLOutput string // where to save PURL list in the image + MergeStrategy SBOMMergeStrategy // how to merge the outputs of multiple scans +} + +// TempDirForURL checks if the passed-in string looks like a URL or "-". If it +// is, TempDirForURL creates a temporary directory, arranges for its contents +// to be the contents of that URL, and returns the temporary directory's path, +// along with the relative name of a subdirectory which should be used as the +// build context (which may be empty or "."). Removal of the temporary +// directory is the responsibility of the caller. If the string doesn't look +// like a URL or "-", TempDirForURL returns empty strings and a nil error code. +func TempDirForURL(dir, prefix, url string) (name string, subdir string, err error) { + if !strings.HasPrefix(url, "http://") && + !strings.HasPrefix(url, "https://") && + !strings.HasPrefix(url, "git://") && + !strings.HasPrefix(url, "github.com/") && + url != "-" { + return "", "", nil + } + name, err = os.MkdirTemp(dir, prefix) + if err != nil { + return "", "", fmt.Errorf("creating temporary directory for %q: %w", url, err) + } + downloadDir := filepath.Join(name, "download") + if err = os.MkdirAll(downloadDir, 0o700); err != nil { + return "", "", fmt.Errorf("creating directory %q for %q: %w", downloadDir, url, err) + } + urlParsed, err := urlpkg.Parse(url) + if err != nil { + return "", "", fmt.Errorf("parsing url %q: %w", url, err) + } + if strings.HasPrefix(url, "git://") || strings.HasSuffix(urlParsed.Path, ".git") { + combinedOutput, gitSubDir, err := cloneToDirectory(url, downloadDir) + if err != nil { + if err2 := os.RemoveAll(name); err2 != nil { + logrus.Debugf("error removing temporary directory %q: %v", name, err2) + } + return "", "", fmt.Errorf("cloning %q to %q:\n%s: %w", url, name, string(combinedOutput), err) + } + logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, gitSubDir)) + return name, filepath.Join(filepath.Base(downloadDir), gitSubDir), nil + } + if strings.HasPrefix(url, "github.com/") { + ghurl := url + url = fmt.Sprintf("https://%s/archive/master.tar.gz", ghurl) + logrus.Debugf("resolving url %q to %q", ghurl, url) + subdir = path.Base(ghurl) + "-master" + } + if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { + err = downloadToDirectory(url, downloadDir) + if err != nil { + if err2 := os.RemoveAll(name); err2 != nil { + logrus.Debugf("error removing temporary directory %q: %v", name, err2) + } + return "", "", err + } + logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, subdir)) + return name, filepath.Join(filepath.Base(downloadDir), subdir), nil + } + if url == "-" { + err = stdinToDirectory(downloadDir) + if err != nil { + if err2 := os.RemoveAll(name); err2 != nil { + logrus.Debugf("error removing temporary directory %q: %v", name, err2) + } + return "", "", err + } + logrus.Debugf("Build context is at %q", filepath.Join(downloadDir, subdir)) + return name, filepath.Join(filepath.Base(downloadDir), subdir), nil + } + logrus.Debugf("don't know how to retrieve %q", url) + if err2 := os.RemoveAll(name); err2 != nil { + logrus.Debugf("error removing temporary directory %q: %v", name, err2) + } + return "", "", errors.New("unreachable code reached") +} + +// parseGitBuildContext parses git build context to `repo`, `sub-dir` +// `branch/commit`, accepts GitBuildContext in the format of +// `repourl.git[#[branch-or-commit]:subdir]`. +func parseGitBuildContext(url string) (string, string, string) { + gitSubdir := "" + gitBranch := "" + gitBranchPart := strings.Split(url, "#") + if len(gitBranchPart) > 1 { + // check if string contains path to a subdir + gitSubDirPart := strings.Split(gitBranchPart[1], ":") + if len(gitSubDirPart) > 1 { + gitSubdir = gitSubDirPart[1] + } + gitBranch = gitSubDirPart[0] + } + return gitBranchPart[0], gitSubdir, gitBranch +} + +func cloneToDirectory(url, dir string) ([]byte, string, error) { + var cmd *exec.Cmd + gitRepo, gitSubdir, gitRef := parseGitBuildContext(url) + // init repo + cmd = exec.Command("git", "init", dir) + combinedOutput, err := cmd.CombinedOutput() + if err != nil { + // Return err.Error() instead of err as we want buildah to override error code with more predictable + // value. + return combinedOutput, gitSubdir, fmt.Errorf("failed while performing `git init`: %s", err.Error()) + } + // add origin + cmd = exec.Command("git", "remote", "add", "origin", gitRepo) + cmd.Dir = dir + combinedOutput, err = cmd.CombinedOutput() + if err != nil { + // Return err.Error() instead of err as we want buildah to override error code with more predictable + // value. + return combinedOutput, gitSubdir, fmt.Errorf("failed while performing `git remote add`: %s", err.Error()) + } + + logrus.Debugf("fetching repo %q and branch (or commit ID) %q to %q", gitRepo, gitRef, dir) + args := []string{"fetch", "-u", "--depth=1", "origin", "--", gitRef} + cmd = exec.Command("git", args...) + cmd.Dir = dir + combinedOutput, err = cmd.CombinedOutput() + if err != nil { + // Return err.Error() instead of err as we want buildah to override error code with more predictable + // value. + return combinedOutput, gitSubdir, fmt.Errorf("failed while performing `git fetch`: %s", err.Error()) + } + + cmd = exec.Command("git", "checkout", "FETCH_HEAD") + cmd.Dir = dir + combinedOutput, err = cmd.CombinedOutput() + if err != nil { + // Return err.Error() instead of err as we want buildah to override error code with more predictable + // value. + return combinedOutput, gitSubdir, fmt.Errorf("failed while performing `git checkout`: %s", err.Error()) + } + return combinedOutput, gitSubdir, nil +} + +func downloadToDirectory(url, dir string) error { + logrus.Debugf("extracting %q to %q", url, dir) + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest { + return fmt.Errorf("invalid response status %d", resp.StatusCode) + } + if resp.ContentLength == 0 { + return fmt.Errorf("no contents in %q", url) + } + if err := chrootarchive.Untar(resp.Body, dir, nil); err != nil { + resp1, err := http.Get(url) + if err != nil { + return err + } + defer resp1.Body.Close() + body, err := io.ReadAll(resp1.Body) + if err != nil { + return err + } + dockerfile := filepath.Join(dir, "Dockerfile") + // Assume this is a Dockerfile + if err := ioutils.AtomicWriteFile(dockerfile, body, 0o600); err != nil { + return fmt.Errorf("failed to write %q to %q: %w", url, dockerfile, err) + } + } + return nil +} + +func stdinToDirectory(dir string) error { + logrus.Debugf("extracting stdin to %q", dir) + r := bufio.NewReader(os.Stdin) + b, err := io.ReadAll(r) + if err != nil { + return fmt.Errorf("failed to read from stdin: %w", err) + } + reader := bytes.NewReader(b) + if err := chrootarchive.Untar(reader, dir, nil); err != nil { + dockerfile := filepath.Join(dir, "Dockerfile") + // Assume this is a Dockerfile + if err := ioutils.AtomicWriteFile(dockerfile, b, 0o600); err != nil { + return fmt.Errorf("failed to write bytes to %q: %w", dockerfile, err) + } + } + return nil +} diff --git a/vendor/github.com/containers/buildah/define/types_unix.go b/vendor/github.com/containers/buildah/define/types_unix.go new file mode 100644 index 00000000..20b1a7b2 --- /dev/null +++ b/vendor/github.com/containers/buildah/define/types_unix.go @@ -0,0 +1,18 @@ +//go:build darwin || linux + +package define + +import ( + "github.com/opencontainers/runc/libcontainer/devices" +) + +// BuildahDevice is a wrapper around devices.Device +// with additional support for renaming a device +// using bind-mount in rootless environments. +type BuildahDevice struct { + devices.Device + Source string + Destination string +} + +type ContainerDevices = []BuildahDevice diff --git a/vendor/github.com/containers/buildah/define/types_unsupported.go b/vendor/github.com/containers/buildah/define/types_unsupported.go new file mode 100644 index 00000000..7db27204 --- /dev/null +++ b/vendor/github.com/containers/buildah/define/types_unsupported.go @@ -0,0 +1,6 @@ +//go:build !linux && !darwin + +package define + +// ContainerDevices is currently not implemented. +type ContainerDevices = []struct{} diff --git a/vendor/github.com/containers/buildah/delete.go b/vendor/github.com/containers/buildah/delete.go new file mode 100644 index 00000000..7adb7c5e --- /dev/null +++ b/vendor/github.com/containers/buildah/delete.go @@ -0,0 +1,15 @@ +package buildah + +import "fmt" + +// Delete removes the working container. The buildah.Builder object should not +// be used after this method is called. +func (b *Builder) Delete() error { + if err := b.store.DeleteContainer(b.ContainerID); err != nil { + return fmt.Errorf("deleting build container %q: %w", b.ContainerID, err) + } + b.MountPoint = "" + b.Container = "" + b.ContainerID = "" + return nil +} diff --git a/vendor/github.com/containers/buildah/developmentplan.md b/vendor/github.com/containers/buildah/developmentplan.md new file mode 100644 index 00000000..7d82c2e6 --- /dev/null +++ b/vendor/github.com/containers/buildah/developmentplan.md @@ -0,0 +1,13 @@ +![buildah logo](https://cdn.rawgit.com/containers/buildah/main/logos/buildah-logo_large.png) + +# Development Plan + +## Development goals for Buildah + + * Integration into Kubernetes and potentially other tools. The biggest requirement for this is to be able run Buildah within a standard linux container without SYS_ADMIN privileges. This would allow Buildah to run non-privileged containers inside of Kubernetes, so you could distribute your container workloads. + + * Integration with User Namespace, Podman has this already and the goal is to get `buildah build` and `buildah run` to be able to run its containers in a usernamespace to give the builder better security isolation from the host. + + * Buildah `buildah build` command's goal is to have feature parity with other OCI image and container build systems. + + * Addressing issues from the community as reported in the [Issues](https://github.com/containers/buildah/issues) page. diff --git a/vendor/github.com/containers/buildah/digester.go b/vendor/github.com/containers/buildah/digester.go new file mode 100644 index 00000000..65e11569 --- /dev/null +++ b/vendor/github.com/containers/buildah/digester.go @@ -0,0 +1,286 @@ +package buildah + +import ( + "archive/tar" + "errors" + "fmt" + "hash" + "io" + "sync" + "time" + + digest "github.com/opencontainers/go-digest" +) + +type digester interface { + io.WriteCloser + ContentType() string + Digest() digest.Digest +} + +// A simple digester just digests its content as-is. +type simpleDigester struct { + digester digest.Digester + hasher hash.Hash + contentType string +} + +func newSimpleDigester(contentType string) digester { + finalDigester := digest.Canonical.Digester() + return &simpleDigester{ + digester: finalDigester, + hasher: finalDigester.Hash(), + contentType: contentType, + } +} + +func (s *simpleDigester) ContentType() string { + return s.contentType +} + +func (s *simpleDigester) Write(p []byte) (int, error) { + return s.hasher.Write(p) +} + +func (s *simpleDigester) Close() error { + return nil +} + +func (s *simpleDigester) Digest() digest.Digest { + return s.digester.Digest() +} + +// A tarFilterer passes a tarball through to an io.WriteCloser, potentially +// modifying headers as it goes. +type tarFilterer struct { + wg sync.WaitGroup + pipeWriter *io.PipeWriter + closedLock sync.Mutex + closed bool + err error +} + +func (t *tarFilterer) Write(p []byte) (int, error) { + n, err := t.pipeWriter.Write(p) + if err != nil { + t.closedLock.Lock() + closed := t.closed + t.closedLock.Unlock() + err = fmt.Errorf("writing to tar filter pipe (closed=%v,err=%v): %w", closed, t.err, err) + } + return n, err +} + +func (t *tarFilterer) Close() error { + t.closedLock.Lock() + if t.closed { + t.closedLock.Unlock() + return errors.New("tar filter is already closed") + } + t.closed = true + t.closedLock.Unlock() + err := t.pipeWriter.Close() + t.wg.Wait() + if err != nil { + return fmt.Errorf("closing filter pipe: %w", err) + } + return t.err +} + +// newTarFilterer passes one or more tar archives through to an io.WriteCloser +// as a single archive, potentially calling filter to modify headers and +// contents as it goes. +// +// Note: if "filter" indicates that a given item should be skipped, there is no +// guarantee that there will not be a subsequent item of type TypeLink, which +// is a hard link, which points to the skipped item as the link target. +func newTarFilterer(writeCloser io.WriteCloser, filter func(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader)) io.WriteCloser { + pipeReader, pipeWriter := io.Pipe() + tarWriter := tar.NewWriter(writeCloser) + filterer := &tarFilterer{ + pipeWriter: pipeWriter, + } + filterer.wg.Add(1) + go func() { + filterer.closedLock.Lock() + closed := filterer.closed + filterer.closedLock.Unlock() + for !closed { + tarReader := tar.NewReader(pipeReader) + hdr, err := tarReader.Next() + for err == nil { + var skip, replaceContents bool + var replacementContents io.Reader + if filter != nil { + skip, replaceContents, replacementContents = filter(hdr) + } + if !skip { + if err = tarWriter.WriteHeader(hdr); err != nil { + err = fmt.Errorf("writing tar header for %q: %w", hdr.Name, err) + break + } + if hdr.Size != 0 { + var n int64 + var copyErr error + if replaceContents { + n, copyErr = io.CopyN(tarWriter, replacementContents, hdr.Size) + } else { + n, copyErr = io.Copy(tarWriter, tarReader) + } + if copyErr != nil { + err = fmt.Errorf("copying content for %q: %w", hdr.Name, copyErr) + break + } + if n != hdr.Size { + err = fmt.Errorf("filtering content for %q: expected %d bytes, got %d bytes", hdr.Name, hdr.Size, n) + break + } + } + if err = tarWriter.Flush(); err != nil { + err = fmt.Errorf("flushing tar item padding for %q: %w", hdr.Name, err) + break + } + } + hdr, err = tarReader.Next() + } + if !errors.Is(err, io.EOF) { + filterer.err = fmt.Errorf("reading tar archive: %w", err) + break + } + filterer.closedLock.Lock() + closed = filterer.closed + filterer.closedLock.Unlock() + } + err1 := tarWriter.Close() + err := writeCloser.Close() + if err == nil { + err = err1 + } + if err != nil { + pipeReader.CloseWithError(err) + } else { + pipeReader.Close() + } + filterer.wg.Done() + }() + return filterer +} + +// A tar digester digests an archive, modifying the headers it digests by +// calling a specified function to potentially modify the header that it's +// about to write. +type tarDigester struct { + isOpen bool + nested digester + tarFilterer io.WriteCloser +} + +func modifyTarHeaderForDigesting(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader) { + zeroTime := time.Time{} + hdr.ModTime = zeroTime + hdr.AccessTime = zeroTime + hdr.ChangeTime = zeroTime + return false, false, nil +} + +func newTarDigester(contentType string) digester { + nested := newSimpleDigester(contentType) + digester := &tarDigester{ + isOpen: true, + nested: nested, + tarFilterer: newTarFilterer(nested, modifyTarHeaderForDigesting), + } + return digester +} + +func (t *tarDigester) ContentType() string { + return t.nested.ContentType() +} + +func (t *tarDigester) Digest() digest.Digest { + return t.nested.Digest() +} + +func (t *tarDigester) Write(p []byte) (int, error) { + return t.tarFilterer.Write(p) +} + +func (t *tarDigester) Close() error { + if t.isOpen { + t.isOpen = false + return t.tarFilterer.Close() + } + return nil +} + +// CompositeDigester can compute a digest over multiple items. +type CompositeDigester struct { + digesters []digester + closer io.Closer +} + +// closeOpenDigester closes an open sub-digester, if we have one. +func (c *CompositeDigester) closeOpenDigester() { + if c.closer != nil { + c.closer.Close() + c.closer = nil + } +} + +// Restart clears all state, so that the composite digester can start over. +func (c *CompositeDigester) Restart() { + c.closeOpenDigester() + c.digesters = nil +} + +// Start starts recording the digest for a new item ("", "file", or "dir"). +// The caller should call Hash() immediately after to retrieve the new +// io.WriteCloser. +func (c *CompositeDigester) Start(contentType string) { + c.closeOpenDigester() + switch contentType { + case "": + c.digesters = append(c.digesters, newSimpleDigester("")) + case "file", "dir": + digester := newTarDigester(contentType) + c.closer = digester + c.digesters = append(c.digesters, digester) + default: + panic(fmt.Sprintf(`unrecognized content type: expected "", "file", or "dir", got %q`, contentType)) + } +} + +// Hash returns the hasher for the current item. +func (c *CompositeDigester) Hash() io.WriteCloser { + num := len(c.digesters) + if num == 0 { + return nil + } + return c.digesters[num-1] +} + +// Digest returns the content type and a composite digest over everything +// that's been digested. +func (c *CompositeDigester) Digest() (string, digest.Digest) { + c.closeOpenDigester() + num := len(c.digesters) + switch num { + case 0: + return "", "" + case 1: + return c.digesters[0].ContentType(), c.digesters[0].Digest() + default: + content := "" + for i, digester := range c.digesters { + if i > 0 { + content += "," + } + contentType := digester.ContentType() + if contentType != "" { + contentType += ":" + } + content += contentType + digester.Digest().Encoded() + } + return "multi", digest.Canonical.FromString(content) + } +} diff --git a/vendor/github.com/containers/buildah/docker/AUTHORS b/vendor/github.com/containers/buildah/docker/AUTHORS new file mode 100644 index 00000000..b2cd9ecb --- /dev/null +++ b/vendor/github.com/containers/buildah/docker/AUTHORS @@ -0,0 +1,1788 @@ +# This file lists all individuals having contributed content to the repository. +# For how it is generated, see `hack/generate-authors.sh`. + +Aanand Prasad +Aaron Davidson +Aaron Feng +Aaron Huslage +Aaron Lehmann +Aaron Welch +Aaron.L.Xu +Abel Muiño +Abhijeet Kasurde +Abhinav Ajgaonkar +Abhishek Chanda +Abin Shahab +Adam Avilla +Adam Eijdenberg +Adam Kunk +Adam Miller +Adam Mills +Adam Singer +Adam Walz +Addam Hardy +Aditi Rajagopal +Aditya +Adolfo Ochagavía +Adria Casas +Adrian Moisey +Adrian Mouat +Adrian Oprea +Adrien Folie +Adrien Gallouët +Ahmed Kamal +Ahmet Alp Balkan +Aidan Feldman +Aidan Hobson Sayers +AJ Bowen +Ajey Charantimath +ajneu +Akihiro Suda +Akira Koyasu +Akshay Karle +Al Tobey +alambike +Alan Scherger +Alan Thompson +Albert Callarisa +Albert Zhang +Aleksa Sarai +Aleksandrs Fadins +Alena Prokharchyk +Alessandro Boch +Alessio Biancalana +Alex Chan +Alex Chen +Alex Coventry +Alex Crawford +Alex Ellis +Alex Gaynor +Alex Olshansky +Alex Samorukov +Alex Warhawk +Alexander Artemenko +Alexander Boyd +Alexander Larsson +Alexander Morozov +Alexander Shopov +Alexandre Beslic +Alexandre González +Alexandru Sfirlogea +Alexey Guskov +Alexey Kotlyarov +Alexey Shamrin +Alexis THOMAS +Alfred Landrum +Ali Dehghani +Alicia Lauerman +Alihan Demir +Allen Madsen +Allen Sun +almoehi +Alvaro Saurin +Alvin Richards +amangoel +Amen Belayneh +Amir Goldstein +Amit Bakshi +Amit Krishnan +Amit Shukla +Amy Lindburg +Anand Patil +AnandkumarPatel +Anatoly Borodin +Anchal Agrawal +Anders Janmyr +Andre Dublin <81dublin@gmail.com> +Andre Granovsky +Andrea Luzzardi +Andrea Turli +Andreas Köhler +Andreas Savvides +Andreas Tiefenthaler +Andrei Gherzan +Andrew C. Bodine +Andrew Clay Shafer +Andrew Duckworth +Andrew France +Andrew Gerrand +Andrew Guenther +Andrew Hsu +Andrew Kuklewicz +Andrew Macgregor +Andrew Macpherson +Andrew Martin +Andrew McDonnell +Andrew Munsell +Andrew Po +Andrew Weiss +Andrew Williams +Andrews Medina +Andrey Petrov +Andrey Stolbovsky +André Martins +andy +Andy Chambers +andy diller +Andy Goldstein +Andy Kipp +Andy Rothfusz +Andy Smith +Andy Wilson +Anes Hasicic +Anil Belur +Anil Madhavapeddy +Ankush Agarwal +Anonmily +Anran Qiao +Anthon van der Neut +Anthony Baire +Anthony Bishopric +Anthony Dahanne +Anthony Sottile +Anton Löfgren +Anton Nikitin +Anton Polonskiy +Anton Tiurin +Antonio Murdaca +Antonis Kalipetis +Antony Messerli +Anuj Bahuguna +Anusha Ragunathan +apocas +Arash Deshmeh +ArikaChen +Arnaud Lefebvre +Arnaud Porterie +Arthur Barr +Arthur Gautier +Artur Meyster +Arun Gupta +Asbjørn Enge +averagehuman +Avi Das +Avi Miller +Avi Vaid +ayoshitake +Azat Khuyiyakhmetov +Bardia Keyoumarsi +Barnaby Gray +Barry Allard +Bartłomiej Piotrowski +Bastiaan Bakker +bdevloed +Ben Bonnefoy +Ben Firshman +Ben Golub +Ben Hall +Ben Sargent +Ben Severson +Ben Toews +Ben Wiklund +Benjamin Atkin +Benoit Chesneau +Bernerd Schaefer +Bert Goethals +Bharath Thiruveedula +Bhiraj Butala +Bhumika Bayani +Bilal Amarni +Bill W +bin liu +Bingshen Wang +Blake Geno +Boaz Shuster +bobby abbott +Boshi Lian +boucher +Bouke Haarsma +Boyd Hemphill +boynux +Bradley Cicenas +Bradley Wright +Brandon Liu +Brandon Philips +Brandon Rhodes +Brendan Dixon +Brent Salisbury +Brett Higgins +Brett Kochendorfer +Brian (bex) Exelbierd +Brian Bland +Brian DeHamer +Brian Dorsey +Brian Flad +Brian Goff +Brian McCallister +Brian Olsen +Brian Shumate +Brian Torres-Gil +Brian Trump +Brice Jaglin +Briehan Lombaard +Bruno Bigras +Bruno Binet +Bruno Gazzera +Bruno Renié +Bruno Tavares +Bryan Bess +Bryan Boreham +Bryan Matsuo +Bryan Murphy +buddhamagnet +Burke Libbey +Byung Kang +Caleb Spare +Calen Pennington +Cameron Boehmer +Cameron Spear +Campbell Allen +Candid Dauth +Cao Weiwei +Carl Henrik Lunde +Carl Loa Odin +Carl X. Su +Carlos Alexandro Becker +Carlos Sanchez +Carol Fager-Higgins +Cary +Casey Bisson +Ce Gao +Cedric Davies +Cezar Sa Espinola +Chad Swenson +Chance Zibolski +Chander G +Charles Chan +Charles Hooper +Charles Law +Charles Lindsay +Charles Merriam +Charles Sarrazin +Charles Smith +Charlie Drage +Charlie Lewis +Chase Bolt +ChaYoung You +Chen Chao +Chen Chuanliang +Chen Hanxiao +Chen Mingjie +cheney90 +Chewey +Chia-liang Kao +chli +Cholerae Hu +Chris Alfonso +Chris Armstrong +Chris Dituri +Chris Fordham +Chris Gavin +Chris Khoo +Chris McKinnel +Chris Seto +Chris Snow +Chris St. Pierre +Chris Stivers +Chris Swan +Chris Wahl +Chris Weyl +chrismckinnel +Christian Berendt +Christian Böhme +Christian Persson +Christian Rotzoll +Christian Simon +Christian Stefanescu +ChristoperBiscardi +Christophe Mehay +Christophe Troestler +Christopher Currie +Christopher Jones +Christopher Latham +Christopher Rigor +Christy Perez +Chun Chen +Ciro S. Costa +Clayton Coleman +Clinton Kitson +Coenraad Loubser +Colin Dunklau +Colin Rice +Colin Walters +Collin Guarino +Colm Hally +companycy +Cory Forsyth +cressie176 +CrimsonGlory +Cristian Staretu +cristiano balducci +Cruceru Calin-Cristian +CUI Wei +Cyprian Gracz +Cyril F +Daan van Berkel +Daehyeok Mun +Dafydd Crosby +dalanlan +Damian Smyth +Damien Nadé +Damien Nozay +Damjan Georgievski +Dan Anolik +Dan Buch +Dan Cotora +Dan Feldman +Dan Griffin +Dan Hirsch +Dan Keder +Dan Levy +Dan McPherson +Dan Stine +Dan Walsh +Dan Williams +Daniel Antlinger +Daniel Exner +Daniel Farrell +Daniel Garcia +Daniel Gasienica +Daniel Hiltgen +Daniel Menet +Daniel Mizyrycki +Daniel Nephin +Daniel Norberg +Daniel Nordberg +Daniel Robinson +Daniel S +Daniel Von Fange +Daniel X Moore +Daniel YC Lin +Daniel Zhang +Daniel, Dao Quang Minh +Danny Berger +Danny Yates +Darren Coxall +Darren Shepherd +Darren Stahl +Dattatraya Kumbhar +Davanum Srinivas +Dave Barboza +Dave Henderson +Dave MacDonald +Dave Tucker +David Anderson +David Calavera +David Corking +David Cramer +David Currie +David Davis +David Dooling +David Gageot +David Gebler +David Lawrence +David Lechner +David M. Karr +David Mackey +David Mat +David Mcanulty +David Pelaez +David R. Jenni +David Röthlisberger +David Sheets +David Sissitka +David Trott +David Williamson +David Xia +David Young +Davide Ceretti +Dawn Chen +dbdd +dcylabs +decadent +deed02392 +Deng Guangxing +Deni Bertovic +Denis Gladkikh +Denis Ollier +Dennis Chen +Dennis Docter +Derek +Derek +Derek Ch +Derek McGowan +Deric Crago +Deshi Xiao +devmeyster +Devvyn Murphy +Dharmit Shah +Diego Romero +Diego Siqueira +Dieter Reuter +Dillon Dixon +Dima Stopel +Dimitri John Ledkov +Dimitris Rozakis +Dimitry Andric +Dinesh Subhraveti +Ding Fei +Diogo Monica +DiuDiugirl +Djibril Koné +dkumor +Dmitri Logvinenko +Dmitri Shuralyov +Dmitry Demeshchuk +Dmitry Gusev +Dmitry Kononenko +Dmitry Shyshkin +Dmitry Smirnov +Dmitry V. Krivenok +Dmitry Vorobev +Dolph Mathews +Dominik Dingel +Dominik Finkbeiner +Dominik Honnef +Don Kirkby +Don Kjer +Don Spaulding +Donald Huang +Dong Chen +Donovan Jones +Doron Podoleanu +Doug Davis +Doug MacEachern +Doug Tangren +Dr Nic Williams +dragon788 +Dražen Lučanin +Drew Erny +Dustin Sallings +Ed Costello +Edmund Wagner +Eiichi Tsukata +Eike Herzbach +Eivin Giske Skaaren +Eivind Uggedal +Elan Ruusamäe +Elena Morozova +Elias Probst +Elijah Zupancic +eluck +Elvir Kuric +Emil Hernvall +Emily Maier +Emily Rose +Emir Ozer +Enguerran +Eohyung Lee +epeterso +Eric Barch +Eric Curtin +Eric Hanchrow +Eric Lee +Eric Myhre +Eric Paris +Eric Rafaloff +Eric Rosenberg +Eric Sage +Erica Windisch +Eric Yang +Eric-Olivier Lamey +Erik Bray +Erik Dubbelboer +Erik Hollensbe +Erik Inge Bolsø +Erik Kristensen +Erik St. Martin +Erik Weathers +Erno Hopearuoho +Erwin van der Koogh +Euan +Eugene Yakubovich +eugenkrizo +evalle +Evan Allrich +Evan Carmi +Evan Hazlett +Evan Hazlett +Evan Krall +Evan Phoenix +Evan Wies +Evelyn Xu +Everett Toews +Evgeny Vereshchagin +Ewa Czechowska +Eystein Måløy Stenberg +ezbercih +Ezra Silvera +Fabiano Rosas +Fabio Falci +Fabio Rapposelli +Fabio Rehm +Fabrizio Regini +Fabrizio Soppelsa +Faiz Khan +falmp +Fangyuan Gao <21551127@zju.edu.cn> +Fareed Dudhia +Fathi Boudra +Federico Gimenez +Felipe Oliveira +Felix Abecassis +Felix Geisendörfer +Felix Hupfeld +Felix Rabe +Felix Ruess +Felix Schindler +Ferenc Szabo +Fernando +Fero Volar +Ferran Rodenas +Filipe Brandenburger +Filipe Oliveira +fl0yd +Flavio Castelli +FLGMwt +Florian +Florian Klein +Florian Maier +Florian Weingarten +Florin Asavoaie +fonglh +fortinux +Francesc Campoy +Francis Chuang +Francisco Carriedo +Francisco Souza +Frank Groeneveld +Frank Herrmann +Frank Macreery +Frank Rosquin +Fred Lifton +Frederick F. Kautz IV +Frederik Loeffert +Frederik Nordahl Jul Sabroe +Freek Kalter +frosforever +fy2462 +Félix Baylac-Jacqué +Félix Cantournet +Gabe Rosenhouse +Gabor Nagy +Gabriel Linder +Gabriel Monroy +Gabriel Nicolas Avellaneda +Gaetan de Villele +Galen Sampson +Gang Qiao +Gareth Rushgrove +Garrett Barboza +Gaurav +gautam, prasanna +Gaël PORTAY +GennadySpb +Geoffrey Bachelet +George MacRorie +George Xie +Georgi Hristozov +Gereon Frey +German DZ +Gert van Valkenhoef +Gerwim +Gianluca Borello +Gildas Cuisinier +gissehel +Giuseppe Mazzotta +Gleb Fotengauer-Malinovskiy +Gleb M Borisov +Glyn Normington +GoBella +Goffert van Gool +Gosuke Miyashita +Gou Rao +Govinda Fichtner +Grant Reaber +Graydon Hoare +Greg Fausak +Greg Thornton +grossws +grunny +gs11 +Guilhem Lettron +Guilherme Salgado +Guillaume Dufour +Guillaume J. Charmes +guoxiuyan +Gurjeet Singh +Guruprasad +gwx296173 +Günter Zöchbauer +Hans Kristian Flaatten +Hans Rødtang +Hao Shu Wei +Hao Zhang <21521210@zju.edu.cn> +Harald Albers +Harley Laue +Harold Cooper +Harry Zhang +Harshal Patil +He Simei +He Xin +heartlock <21521209@zju.edu.cn> +Hector Castro +Helen Xie +Henning Sprang +Hobofan +Hollie Teal +Hong Xu +Hongbin Lu +hsinko <21551195@zju.edu.cn> +Hu Keping +Hu Tao +Huanzhong Zhang +Huayi Zhang +Hugo Duncan +Hugo Marisco <0x6875676f@gmail.com> +Hunter Blanks +huqun +Huu Nguyen +hyeongkyu.lee +hyp3rdino +Hyzhou <1187766782@qq.com> +Ian Babrou +Ian Bishop +Ian Bull +Ian Calvert +Ian Campbell +Ian Lee +Ian Main +Ian Truslove +Iavael +Icaro Seara +Igor Dolzhikov +Iliana Weller +Ilkka Laukkanen +Ilya Dmitrichenko +Ilya Gusev +ILYA Khlopotov +imre Fitos +inglesp +Ingo Gottwald +Isaac Dupree +Isabel Jimenez +Isao Jonas +Ivan Babrou +Ivan Fraixedes +Ivan Grcic +J Bruni +J. Nunn +Jack Danger Canty +Jacob Atzen +Jacob Edelman +Jacob Tomlinson +Jake Champlin +Jake Moshenko +Jake Sanders +jakedt +James Allen +James Carey +James Carr +James DeFelice +James Harrison Fisher +James Kyburz +James Kyle +James Lal +James Mills +James Nugent +James Turnbull +Jamie Hannaford +Jamshid Afshar +Jan Keromnes +Jan Koprowski +Jan Pazdziora +Jan Toebes +Jan-Gerd Tenberge +Jan-Jaap Driessen +Jana Radhakrishnan +Jannick Fahlbusch +Janonymous +Januar Wayong +Jared Biel +Jared Hocutt +Jaroslaw Zabiello +jaseg +Jasmine Hegman +Jason Divock +Jason Giedymin +Jason Green +Jason Hall +Jason Heiss +Jason Livesay +Jason McVetta +Jason Plum +Jason Shepherd +Jason Smith +Jason Sommer +Jason Stangroome +jaxgeller +Jay +Jay +Jay Kamat +Jean-Baptiste Barth +Jean-Baptiste Dalido +Jean-Christophe Berthon +Jean-Paul Calderone +Jean-Pierre Huynh +Jean-Tiare Le Bigot +Jeff Anderson +Jeff Johnston +Jeff Lindsay +Jeff Mickey +Jeff Minard +Jeff Nickoloff +Jeff Silberman +Jeff Welch +Jeffrey Bolle +Jeffrey Morgan +Jeffrey van Gogh +Jenny Gebske +Jeremy Grosser +Jeremy Price +Jeremy Qian +Jeremy Unruh +Jeroen Jacobs +Jesse Dearing +Jesse Dubay +Jessica Frazelle +Jezeniel Zapanta +jgeiger +Jhon Honce +Ji.Zhilong +Jian Zhang +jianbosun +Jie Luo +Jilles Oldenbeuving +Jim Alateras +Jim Minter +Jim Perrin +Jimmy Cuadra +Jimmy Puckett +jimmyxian +Jinsoo Park +Jiri Popelka +Jiuyue Ma +Jiří Župka +jjy +jmzwcn +Joao Fernandes +Joe Beda +Joe Doliner +Joe Ferguson +Joe Gordon +Joe Shaw +Joe Van Dyk +Joel Friedly +Joel Handwell +Joel Hansson +Joel Wurtz +Joey Geiger +Joey Gibson +Joffrey F +Johan Euphrosine +Johan Rydberg +Johanan Lieberman +Johannes 'fish' Ziemke +John Costa +John Feminella +John Gardiner Myers +John Gossman +John Howard (VM) +John Mulhausen +John OBrien III +John Starks +John Stephens +John Tims +John Warwick +John Willis +johnharris85 +Jon Wedaman +Jonas Pfenniger +Jonathan A. Sternberg +Jonathan Boulle +Jonathan Camp +Jonathan Dowland +Jonathan Lebon +Jonathan Lomas +Jonathan McCrohan +Jonathan Mueller +Jonathan Pares +Jonathan Rudenberg +Jonathan Stoppani +Jonh Wendell +Joost Cassee +Jordan +Jordan Arentsen +Jordan Sissel +Jorge Marin +Jose Diaz-Gonzalez +Joseph Anthony Pasquale Holsten +Joseph Hager +Joseph Kern +Josh +Josh Bodah +Josh Chorlton +Josh Eveleth +Josh Hawn +Josh Horwitz +Josh Poimboeuf +Josh Wilson +Josiah Kiehl +José Tomás Albornoz +JP +jrabbit +jroenf +Julian Taylor +Julien Barbier +Julien Bisconti +Julien Bordellier +Julien Dubois +Julien Pervillé +Julio Montes +Jun-Ru Chang +Jussi Nummelin +Justas Brazauskas +Justin Cormack +Justin Force +Justin Plock +Justin Simonelis +Justin Terry +Justyn Temme +Jyrki Puttonen +Jérôme Petazzoni +Jörg Thalheim +Kai Blin +Kai Qiang Wu(Kennan) +Kamil Domański +kamjar gerami +Kanstantsin Shautsou +Kara Alexandra +Karan Lyons +Kareem Khazem +kargakis +Karl Grzeszczak +Karol Duleba +Katie McLaughlin +Kato Kazuyoshi +Katrina Owen +Kawsar Saiyeed +Kay Yan +kayrus +Ke Li +Ke Xu +Kei Ohmura +Keith Hudgins +Keli Hu +Ken Cochrane +Ken Herner +Ken ICHIKAWA +Kenfe-Mickaël Laventure +Kenjiro Nakayama +Kent Johnson +Kevin "qwazerty" Houdebert +Kevin Burke +Kevin Clark +Kevin J. Lynagh +Kevin Jing Qiu +Kevin Kern +Kevin Menard +Kevin P. Kucharczyk +Kevin Richardson +Kevin Shi +Kevin Wallace +Kevin Yap +kevinmeredith +Keyvan Fatehi +kies +Kim BKC Carlbacker +Kim Eik +Kimbro Staken +Kir Kolyshkin +Kiran Gangadharan +Kirill Kolyshkin +Kirill SIbirev +knappe +Kohei Tsuruta +Koichi Shiraishi +Konrad Kleine +Konstantin L +Konstantin Pelykh +Krasi Georgiev +Krasimir Georgiev +Kris-Mikael Krister +Kristian Haugene +Kristina Zabunova +krrg +Kun Zhang +Kunal Kushwaha +Kyle Conroy +Kyle Linden +kyu +Lachlan Coote +Lai Jiangshan +Lajos Papp +Lakshan Perera +Lalatendu Mohanty +Lance Chen +Lance Kinley +Lars Butler +Lars Kellogg-Stedman +Lars R. Damerow +Lars-Magnus Skog +Laszlo Meszaros +Laura Frank +Laurent Erignoux +Laurie Voss +Leandro Siqueira +Lee Chao <932819864@qq.com> +Lee, Meng-Han +leeplay +Lei Jitang +Len Weincier +Lennie +Leo Gallucci +Leszek Kowalski +Levi Blackstone +Levi Gross +Lewis Daly +Lewis Marshall +Lewis Peckover +Liam Macgillavry +Liana Lo +Liang Mingqiang +Liang-Chi Hsieh +liaoqingwei +Lily Guo +limsy +Lin Lu +LingFaKe +Linus Heckemann +Liran Tal +Liron Levin +Liu Bo +Liu Hua +liwenqi +lixiaobing10051267 +Liz Zhang +LIZAO LI +Lizzie Dixon <_@lizzie.io> +Lloyd Dewolf +Lokesh Mandvekar +longliqiang88 <394564827@qq.com> +Lorenz Leutgeb +Lorenzo Fontana +Louis Opter +Luca Favatella +Luca Marturana +Luca Orlandi +Luca-Bogdan Grigorescu +Lucas Chan +Lucas Chi +Luciano Mores +Luis Martínez de Bartolomé Izquierdo +Luiz Svoboda +Lukas Waslowski +lukaspustina +Lukasz Zajaczkowski +lukemarsden +Lyn +Lynda O'Leary +Lénaïc Huard +Ma Müller +Ma Shimiao +Mabin +Madhav Puri +Madhu Venugopal +Mageee <21521230.zju.edu.cn> +Mahesh Tiyyagura +malnick +Malte Janduda +manchoz +Manfred Touron +Manfred Zabarauskas +Mansi Nahar +mansinahar +Manuel Meurer +Manuel Woelker +mapk0y +Marc Abramowitz +Marc Kuo +Marc Tamsky +Marcelo Salazar +Marco Hennings +Marcus Cobden +Marcus Farkas +Marcus Linke +Marcus Ramberg +Marek Goldmann +Marian Marinov +Marianna Tessel +Mario Loriedo +Marius Gundersen +Marius Sturm +Marius Voila +Mark Allen +Mark McGranaghan +Mark McKinstry +Mark Milstein +Mark Parker +Mark West +Marko Mikulicic +Marko Tibold +Markus Fix +Martijn Dwars +Martijn van Oosterhout +Martin Honermeyer +Martin Kelly +Martin Mosegaard Amdisen +Martin Redmond +Mary Anthony +Masahito Zembutsu +Masayuki Morita +Mason Malone +Mateusz Sulima +Mathias Monnerville +Mathieu Le Marec - Pasquet +Mathieu Parent +Matt Apperson +Matt Bachmann +Matt Bentley +Matt Haggard +Matt Hoyle +Matt McCormick +Matt Moore +Matt Richardson +Matt Robenolt +Matthew Heon +Matthew Lapworth +Matthew Mayer +Matthew Mueller +Matthew Riley +Matthias Klumpp +Matthias Kühnle +Matthias Rampke +Matthieu Hauglustaine +mattymo +mattyw +Mauricio Garavaglia +mauriyouth +Max Shytikov +Maxim Fedchyshyn +Maxim Ivanov +Maxim Kulkin +Maxim Treskin +Maxime Petazzoni +Meaglith Ma +meejah +Megan Kostick +Mehul Kar +Mei ChunTao +Mengdi Gao +Mert Yazıcıoğlu +mgniu +Micah Zoltu +Michael A. Smith +Michael Bridgen +Michael Brown +Michael Chiang +Michael Crosby +Michael Currie +Michael Friis +Michael Gorsuch +Michael Grauer +Michael Holzheu +Michael Hudson-Doyle +Michael Huettermann +Michael Irwin +Michael Käufl +Michael Neale +Michael Prokop +Michael Scharf +Michael Stapelberg +Michael Steinert +Michael Thies +Michael West +Michal Fojtik +Michal Gebauer +Michal Jemala +Michal Minář +Michal Wieczorek +Michaël Pailloncy +Michał Czeraszkiewicz +Michiel@unhosted +Mickaël FORTUNATO +Miguel Angel Fernández +Miguel Morales +Mihai Borobocea +Mihuleacc Sergiu +Mike Brown +Mike Chelen +Mike Danese +Mike Dillon +Mike Dougherty +Mike Gaffney +Mike Goelzer +Mike Leone +Mike MacCana +Mike Naberezny +Mike Snitzer +mikelinjie <294893458@qq.com> +Mikhail Sobolev +Milind Chawre +Miloslav Trmač +mingqing +Mingzhen Feng +Misty Stanley-Jones +Mitch Capper +mlarcher +Mohammad Banikazemi +Mohammed Aaqib Ansari +Mohit Soni +Morgan Bauer +Morgante Pell +Morgy93 +Morten Siebuhr +Morton Fox +Moysés Borges +mqliang +Mrunal Patel +msabansal +mschurenko +Muayyad Alsadi +muge +Mustafa Akın +Muthukumar R +Máximo Cuadros +Médi-Rémi Hashim +Nahum Shalman +Nakul Pathak +Nalin Dahyabhai +Nan Monnand Deng +Naoki Orii +Natalie Parker +Natanael Copa +Nate Brennand +Nate Eagleson +Nate Jones +Nathan Hsieh +Nathan Kleyn +Nathan LeClaire +Nathan McCauley +Nathan Williams +Neal McBurnett +Neil Peterson +Nelson Chen +Neyazul Haque +Nghia Tran +Niall O'Higgins +Nicholas E. Rabenau +nick +Nick DeCoursin +Nick Irvine +Nick Parker +Nick Payne +Nick Stenning +Nick Stinemates +NickrenREN +Nicola Kabar +Nicolas Borboën +Nicolas De loof +Nicolas Dudebout +Nicolas Goy +Nicolas Kaiser +Nicolás Hock Isaza +Nigel Poulton +NikolaMandic +nikolas +Nikolay Milovanov +Nirmal Mehta +Nishant Totla +NIWA Hideyuki +Noah Treuhaft +noducks +Nolan Darilek +nponeccop +Nuutti Kotivuori +nzwsch +O.S. Tezer +objectified +OddBloke +odk- +Oguz Bilgic +Oh Jinkyun +Ohad Schneider +ohmystack +Ole Reifschneider +Oliver Neal +Olivier Gambier +Olle Jonsson +Oriol Francès +orkaa +Oskar Niburski +Otto Kekäläinen +Ovidio Mallo +oyld +ozlerhakan +paetling +pandrew +panticz +Paolo G. Giarrusso +Pascal Borreli +Pascal Hartig +Patrick Böänziger +Patrick Devine +Patrick Hemmer +Patrick Stapleton +pattichen +Paul +paul +Paul Annesley +Paul Bellamy +Paul Bowsher +Paul Furtado +Paul Hammond +Paul Jimenez +Paul Kehrer +Paul Lietar +Paul Liljenberg +Paul Morie +Paul Nasrat +Paul Weaver +Paulo Ribeiro +Pavel Lobashov +Pavel Pospisil +Pavel Sutyrin +Pavel Tikhomirov +Pavlos Ratis +Pavol Vargovcik +Peeyush Gupta +Peggy Li +Pei Su +Penghan Wang +perhapszzy@sina.com +pestophagous +Peter Bourgon +Peter Braden +Peter Choi +Peter Dave Hello +Peter Edge +Peter Ericson +Peter Esbensen +Peter Malmgren +Peter Salvatore +Peter Volpe +Peter Waller +Petr Švihlík +Phil +Phil Estes +Phil Spitler +Philip Monroe +Philipp Wahala +Philipp Weissensteiner +Phillip Alexander +pidster +Piergiuliano Bossi +Pierre +Pierre Carrier +Pierre Dal-Pra +Pierre Wacrenier +Pierre-Alain RIVIERE +Piotr Bogdan +pixelistik +Porjo +Poul Kjeldager Sørensen +Pradeep Chhetri +Prasanna Gautam +Prayag Verma +Przemek Hejman +pysqz +qhuang +Qiang Huang +Qinglan Peng +qudongfang +Quentin Brossard +Quentin Perez +Quentin Tayssier +r0n22 +Rafal Jeczalik +Rafe Colton +Raghavendra K T +Raghuram Devarakonda +Rajat Pandit +Rajdeep Dua +Ralf Sippl +Ralle +Ralph Bean +Ramkumar Ramachandra +Ramon Brooker +Ramon van Alteren +Ray Tsang +ReadmeCritic +Recursive Madman +Reficul +Regan McCooey +Remi Rampin +Renato Riccieri Santos Zannon +resouer +rgstephens +Rhys Hiltner +Rich Moyse +Rich Seymour +Richard +Richard Burnison +Richard Harvey +Richard Mathie +Richard Metzler +Richard Scothern +Richo Healey +Rick Bradley +Rick van de Loo +Rick Wieman +Rik Nijessen +Riku Voipio +Riley Guerin +Ritesh H Shukla +Riyaz Faizullabhoy +Rob Vesse +Robert Bachmann +Robert Bittle +Robert Obryk +Robert Stern +Robert Terhaar +Robert Wallis +Roberto G. Hashioka +Roberto Muñoz Fernández +Robin Naundorf +Robin Schneider +Robin Speekenbrink +robpc +Rodolfo Carvalho +Rodrigo Vaz +Roel Van Nyen +Roger Peppe +Rohit Jnagal +Rohit Kadam +Rojin George +Roland Huß +Roland Kammerer +Roland Moriz +Roma Sokolov +Roman Strashkin +Ron Smits +Ron Williams +root +root +root +root +root +Rory Hunter +Rory McCune +Ross Boucher +Rovanion Luckey +Rozhnov Alexandr +rsmoorthy +Rudolph Gottesheim +Rui Lopes +Runshen Zhu +Ryan Abrams +Ryan Anderson +Ryan Aslett +Ryan Belgrave +Ryan Detzel +Ryan Fowler +Ryan McLaughlin +Ryan O'Donnell +Ryan Seto +Ryan Thomas +Ryan Trauntvein +Ryan Wallner +Ryan Zhang +RyanDeng +Rémy Greinhofer +s. rannou +s00318865 +Sabin Basyal +Sachin Joshi +Sagar Hani +Sainath Grandhi +sakeven +Sally O'Malley +Sam Abed +Sam Alba +Sam Bailey +Sam J Sharpe +Sam Neirinck +Sam Reis +Sam Rijs +Sambuddha Basu +Sami Wagiaalla +Samuel Andaya +Samuel Dion-Girardeau +Samuel Karp +Samuel PHAN +Sandeep Bansal +Sankar சங்கர் +Sanket Saurav +Santhosh Manohar +sapphiredev +Satnam Singh +satoru +Satoshi Amemiya +Satoshi Tagomori +scaleoutsean +Scott Bessler +Scott Collier +Scott Johnston +Scott Stamp +Scott Walls +sdreyesg +Sean Christopherson +Sean Cronin +Sean McIntyre +Sean OMeara +Sean P. Kane +Sean Rodman +Sebastiaan van Steenis +Sebastiaan van Stijn +Senthil Kumar Selvaraj +Senthil Kumaran +SeongJae Park +Seongyeol Lim +Serge Hallyn +Sergey Alekseev +Sergey Evstifeev +Sergii Kabashniuk +Serhat Gülçiçek +Sevki Hasirci +Shane Canon +Shane da Silva +shaunol +Shawn Landden +Shawn Siefkas +shawnhe +Shayne Wang +Shekhar Gulati +Sheng Yang +Shengbo Song +Shev Yan +Shih-Yuan Lee +Shijiang Wei +Shishir Mahajan +Shoubhik Bose +Shourya Sarcar +shuai-z +Shukui Yang +Shuwei Hao +Sian Lerk Lau +sidharthamani +Silas Sewell +Silvan Jegen +Simei He +Simon Eskildsen +Simon Leinen +Simon Taranto +Sindhu S +Sjoerd Langkemper +skaasten +Solganik Alexander +Solomon Hykes +Song Gao +Soshi Katsuta +Soulou +Spencer Brown +Spencer Smith +Sridatta Thatipamala +Sridhar Ratnakumar +Srini Brahmaroutu +srinsriv +Steeve Morin +Stefan Berger +Stefan J. Wernli +Stefan Praszalowicz +Stefan S. +Stefan Scherer +Stefan Staudenmeyer +Stefan Weil +Stephen Crosby +Stephen Day +Stephen Drake +Stephen Rust +Steve Dougherty +Steve Durrheimer +Steve Francia +Steve Koch +Steven Burgess +Steven Erenst +Steven Hartland +Steven Iveson +Steven Merrill +Steven Richards +Steven Taylor +Subhajit Ghosh +Sujith Haridasan +Sun Gengze <690388648@qq.com> +Suryakumar Sudar +Sven Dowideit +Swapnil Daingade +Sylvain Baubeau +Sylvain Bellemare +Sébastien +Sébastien Luttringer +Sébastien Stormacq +Tadej Janež +TAGOMORI Satoshi +tang0th +Tangi COLIN +Tatsuki Sugiura +Tatsushi Inagaki +Taylor Jones +tbonza +Ted M. Young +Tehmasp Chaudhri +Tejesh Mehta +terryding77 <550147740@qq.com> +tgic +Thatcher Peskens +theadactyl +Thell 'Bo' Fowler +Thermionix +Thijs Terlouw +Thomas Bikeev +Thomas Frössman +Thomas Gazagnaire +Thomas Grainger +Thomas Hansen +Thomas Leonard +Thomas LEVEIL +Thomas Orozco +Thomas Riccardi +Thomas Schroeter +Thomas Sjögren +Thomas Swift +Thomas Tanaka +Thomas Texier +Tianon Gravi +Tianyi Wang +Tibor Vass +Tiffany Jernigan +Tiffany Low +Tim Bosse +Tim Dettrick +Tim Düsterhus +Tim Hockin +Tim Ruffles +Tim Smith +Tim Terhorst +Tim Wang +Tim Waugh +Tim Wraight +Tim Zju <21651152@zju.edu.cn> +timfeirg +Timothy Hobbs +tjwebb123 +tobe +Tobias Bieniek +Tobias Bradtke +Tobias Gesellchen +Tobias Klauser +Tobias Munk +Tobias Schmidt +Tobias Schwab +Todd Crane +Todd Lunter +Todd Whiteman +Toli Kuznets +Tom Barlow +Tom Booth +Tom Denham +Tom Fotherby +Tom Howe +Tom Hulihan +Tom Maaswinkel +Tom Wilkie +Tom X. Tobin +Tomas Tomecek +Tomasz Kopczynski +Tomasz Lipinski +Tomasz Nurkiewicz +Tommaso Visconti +Tomáš Hrčka +Tonny Xu +Tony Abboud +Tony Daws +Tony Miller +toogley +Torstein Husebø +Tõnis Tiigi +tpng +tracylihui <793912329@qq.com> +Trapier Marshall +Travis Cline +Travis Thieman +Trent Ogren +Trevor +Trevor Pounds +Trevor Sullivan +trishnaguha +Tristan Carel +Troy Denton +Tyler Brock +Tzu-Jung Lee +Ulysse Carion +unknown +vagrant +Vaidas Jablonskis +Veres Lajos +vgeta +Victor Algaze +Victor Coisne +Victor Costan +Victor I. Wood +Victor Lyuboslavsky +Victor Marmol +Victor Palma +Victor Vieux +Victoria Bialas +Vijaya Kumar K +Viktor Stanchev +Viktor Vojnovski +VinayRaghavanKS +Vincent Batts +Vincent Bernat +Vincent Bernat +Vincent Demeester +Vincent Giersch +Vincent Mayers +Vincent Woo +Vinod Kulkarni +Vishal Doshi +Vishnu Kannan +Vitor Monteiro +Vivek Agarwal +Vivek Dasgupta +Vivek Goyal +Vladimir Bulyga +Vladimir Kirillov +Vladimir Pouzanov +Vladimir Rutsky +Vladimir Varankin +VladimirAus +Vojtech Vitek (V-Teq) +waitingkuo +Walter Leibbrandt +Walter Stanish +WANG Chao +Wang Long +Wang Ping +Wang Xing +Wang Yuexiao +Ward Vandewege +WarheadsSE +Wayne Chang +Wei Wu +Wei-Ting Kuo +weiyan +Weiyang Zhu +Wen Cheng Ma +Wendel Fleming +Wenkai Yin +Wentao Zhang +Wenxuan Zhao +Wenyu You <21551128@zju.edu.cn> +Wenzhi Liang +Wes Morgan +Wewang Xiaorenfine +Will Dietz +Will Rouesnel +Will Weaver +willhf +William Delanoue +William Henry +William Hubbs +William Martin +William Riancho +William Thurston +WiseTrem +wlan0 +Wolfgang Powisch +wonderflow +Wonjun Kim +xamyzhao +Xianglin Gao +Xianlu Bird +XiaoBing Jiang +Xiaoxu Chen +xiekeyang +Xinbo Weng +Xinzi Zhou +Xiuming Chen +xlgao-zju +xuzhaokui +Yahya +YAMADA Tsuyoshi +Yan Feng +Yang Bai +yangshukui +Yanqiang Miao +Yasunori Mahata +Yestin Sun +Yi EungJun +Yibai Zhang +Yihang Ho +Ying Li +Yohei Ueda +Yong Tang +Yongzhi Pan +yorkie +You-Sheng Yang (楊有勝) +Youcef YEKHLEF +Yu Peng +Yuan Sun +yuchangchun +yuchengxia +Yunxiang Huang +Yurii Rashkovskii +yuzou +Zac Dover +Zach Borboa +Zachary Jaffee +Zain Memon +Zaiste! +Zane DeGraffenried +Zefan Li +Zen Lin(Zhinan Lin) +Zhang Kun +Zhang Wei +Zhang Wentao +zhangxianwei +Zhenan Ye <21551168@zju.edu.cn> +zhenghenghuo +Zhenkun Bi +zhouhao +Zhu Guihua +Zhu Kunjia +Zhuoyun Wei +Zilin Du +zimbatm +Ziming Dong +ZJUshuaizhou <21551191@zju.edu.cn> +zmarouf +Zoltan Tombol +zqh +Zuhayr Elahi +Zunayed Ali +Álex González +Álvaro Lázaro +Átila Camurça Alves +尹吉峰 +搏通 diff --git a/vendor/github.com/containers/buildah/docker/types.go b/vendor/github.com/containers/buildah/docker/types.go new file mode 100644 index 00000000..352ae479 --- /dev/null +++ b/vendor/github.com/containers/buildah/docker/types.go @@ -0,0 +1,259 @@ +package docker + +// +// Types extracted from Docker +// + +import ( + "time" + + digest "github.com/opencontainers/go-digest" + "go.podman.io/image/v5/pkg/strslice" +) + +// github.com/moby/moby/image/rootfs.go +const TypeLayers = "layers" + +// github.com/docker/distribution/manifest/schema2/manifest.go +const V2S2MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar" + +// github.com/moby/moby/image/rootfs.go +// V2S2RootFS describes images root filesystem +// This is currently a placeholder that only supports layers. In the future +// this can be made into an interface that supports different implementations. +type V2S2RootFS struct { + Type string `json:"type"` + DiffIDs []digest.Digest `json:"diff_ids,omitempty"` +} + +// github.com/moby/moby/image/image.go +// V2S2History stores build commands that were used to create an image +type V2S2History struct { + // Created is the timestamp at which the image was created + Created time.Time `json:"created"` + // Author is the name of the author that was specified when committing the image + Author string `json:"author,omitempty"` + // CreatedBy keeps the Dockerfile command used while building the image + CreatedBy string `json:"created_by,omitempty"` + // Comment is the commit message that was set when committing the image + Comment string `json:"comment,omitempty"` + // EmptyLayer is set to true if this history item did not generate a + // layer. Otherwise, the history item is associated with the next + // layer in the RootFS section. + EmptyLayer bool `json:"empty_layer,omitempty"` +} + +// github.com/moby/moby/image/image.go +// ID is the content-addressable ID of an image. +type ID digest.Digest + +// github.com/moby/moby/api/types/container/config.go +// HealthConfig holds configuration settings for the HEALTHCHECK feature. +type HealthConfig struct { + // Test is the test to perform to check that the container is healthy. + // An empty slice means to inherit the default. + // The options are: + // {} : inherit healthcheck + // {"NONE"} : disable healthcheck + // {"CMD", args...} : exec arguments directly + // {"CMD-SHELL", command} : run command with system's default shell + Test []string `json:",omitempty"` + + // Zero means to inherit. Durations are expressed as integer nanoseconds. + Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks. + Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung. + StartPeriod time.Duration `json:",omitempty"` // Time to wait after the container starts before running the first check. + StartInterval time.Duration `json:",omitempty"` // Time to wait between checks during the StartPeriod. + + // Retries is the number of consecutive failures needed to consider a container as unhealthy. + // Zero means inherit. + Retries int `json:",omitempty"` +} + +// github.com/docker/go-connections/nat/nat.go +// PortSet is a collection of structs indexed by Port +type PortSet map[Port]struct{} + +// github.com/docker/go-connections/nat/nat.go +// Port is a string containing port number and protocol in the format "80/tcp" +type Port string + +// github.com/moby/moby/api/types/container/config.go +// Config contains the configuration data about a container. +// It should hold only portable information about the container. +// Here, "portable" means "independent from the host we are running on". +// Non-portable information *should* appear in HostConfig. +// All fields added to this struct must be marked `omitempty` to keep getting +// predictable hashes from the old `v1Compatibility` configuration. +type Config struct { + Hostname string // Hostname + Domainname string // Domainname + User string // User that will run the command(s) inside the container, also support user:group + AttachStdin bool // Attach the standard input, makes possible user interaction + AttachStdout bool // Attach the standard output + AttachStderr bool // Attach the standard error + ExposedPorts PortSet `json:",omitempty"` // List of exposed ports + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin + StdinOnce bool // If true, close stdin after the 1 attached client disconnects. + Env []string // List of environment variable to set in the container + Cmd strslice.StrSlice // Command to run when starting the container + Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy + ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific) + Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) + Volumes map[string]struct{} // List of volumes (mounts) used for the container + WorkingDir string // Current directory (PWD) in the command will be launched + Entrypoint strslice.StrSlice // Entrypoint to run when starting the container + NetworkDisabled bool `json:",omitempty"` // Is network disabled + MacAddress string `json:",omitempty"` // Mac Address of the container + OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile + Labels map[string]string // List of labels set to this container + StopSignal string `json:",omitempty"` // Signal to stop a container + StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container + Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT +} + +// github.com/docker/distribution/manifest/schema1/config_builder.go +// For non-top-level layers, create fake V1Compatibility strings that +// fit the format and don't collide with anything else, but don't +// result in runnable images on their own. +type V1Compatibility struct { + ID string `json:"id"` + Parent string `json:"parent,omitempty"` + Comment string `json:"comment,omitempty"` + Created time.Time `json:"created"` + ContainerConfig struct { + Cmd []string + } `json:"container_config"` + Author string `json:"author,omitempty"` + ThrowAway bool `json:"throwaway,omitempty"` +} + +// github.com/moby/moby/image/image.go +// V1Image stores the V1 image configuration. +type V1Image struct { + // ID is a unique 64 character identifier of the image + ID string `json:"id,omitempty"` + // Parent is the ID of the parent image + Parent string `json:"parent,omitempty"` + // Comment is the commit message that was set when committing the image + Comment string `json:"comment,omitempty"` + // Created is the timestamp at which the image was created + Created time.Time `json:"created"` + // Container is the id of the container used to commit + Container string `json:"container,omitempty"` + // ContainerConfig is the configuration of the container that is committed into the image + ContainerConfig Config `json:"container_config"` + // DockerVersion specifies the version of Docker that was used to build the image + DockerVersion string `json:"docker_version,omitempty"` + // Author is the name of the author that was specified when committing the image + Author string `json:"author,omitempty"` + // Config is the configuration of the container received from the client + Config *Config `json:"config,omitempty"` + // Architecture is the hardware that the image is build and runs on + Architecture string `json:"architecture,omitempty"` + // Variant is a variant of the CPU that the image is built and runs on + Variant string `json:"variant,omitempty"` + // OS is the operating system used to build and run the image + OS string `json:"os,omitempty"` + // Size is the total size of the image including all layers it is composed of + Size int64 `json:",omitempty"` +} + +// github.com/moby/moby/image/image.go +// V2Image stores the image configuration +type V2Image struct { + V1Image + Parent ID `json:"parent,omitempty"` + RootFS *V2S2RootFS `json:"rootfs,omitempty"` + History []V2S2History `json:"history,omitempty"` + OSVersion string `json:"os.version,omitempty"` + OSFeatures []string `json:"os.features,omitempty"` +} + +// github.com/docker/distribution/manifest/versioned.go +// V2Versioned provides a struct with the manifest schemaVersion and mediaType. +// Incoming content with unknown schema version can be decoded against this +// struct to check the version. +type V2Versioned struct { + // SchemaVersion is the image manifest schema that this image follows + SchemaVersion int `json:"schemaVersion"` + + // MediaType is the media type of this schema. + MediaType string `json:"mediaType,omitempty"` +} + +// github.com/docker/distribution/manifest/schema1/manifest.go +// V2S1FSLayer is a container struct for BlobSums defined in an image manifest +type V2S1FSLayer struct { + // BlobSum is the tarsum of the referenced filesystem image layer + BlobSum digest.Digest `json:"blobSum"` +} + +// github.com/docker/distribution/manifest/schema1/manifest.go +// V2S1History stores unstructured v1 compatibility information +type V2S1History struct { + // V1Compatibility is the raw v1 compatibility information + V1Compatibility string `json:"v1Compatibility"` +} + +// github.com/docker/distribution/manifest/schema1/manifest.go +// V2S1Manifest provides the base accessible fields for working with V2 image +// format in the registry. +type V2S1Manifest struct { + V2Versioned + + // Name is the name of the image's repository + Name string `json:"name"` + + // Tag is the tag of the image specified by this manifest + Tag string `json:"tag"` + + // Architecture is the host architecture on which this image is intended to + // run + Architecture string `json:"architecture"` + + // FSLayers is a list of filesystem layer blobSums contained in this image + FSLayers []V2S1FSLayer `json:"fsLayers"` + + // History is a list of unstructured historical data for v1 compatibility + History []V2S1History `json:"history"` +} + +// github.com/docker/distribution/blobs.go +// V2S2Descriptor describes targeted content. Used in conjunction with a blob +// store, a descriptor can be used to fetch, store and target any kind of +// blob. The struct also describes the wire protocol format. Fields should +// only be added but never changed. +type V2S2Descriptor struct { + // MediaType describe the type of the content. All text based formats are + // encoded as utf-8. + MediaType string `json:"mediaType,omitempty"` + + // Size in bytes of content. + Size int64 `json:"size,omitempty"` + + // Digest uniquely identifies the content. A byte stream can be verified + // against against this digest. + Digest digest.Digest `json:"digest,omitempty"` + + // URLs contains the source URLs of this content. + URLs []string `json:"urls,omitempty"` + + // NOTE: Before adding a field here, please ensure that all + // other options have been exhausted. Much of the type relationships + // depend on the simplicity of this type. +} + +// github.com/docker/distribution/manifest/schema2/manifest.go +// V2S2Manifest defines a schema2 manifest. +type V2S2Manifest struct { + V2Versioned + + // Config references the image configuration as a blob. + Config V2S2Descriptor `json:"config"` + + // Layers lists descriptors for the layers referenced by the + // configuration. + Layers []V2S2Descriptor `json:"layers"` +} diff --git a/vendor/github.com/containers/buildah/image.go b/vendor/github.com/containers/buildah/image.go new file mode 100644 index 00000000..469db661 --- /dev/null +++ b/vendor/github.com/containers/buildah/image.go @@ -0,0 +1,1705 @@ +package buildah + +import ( + "archive/tar" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "maps" + "os" + "path" + "path/filepath" + "slices" + "strings" + "time" + + "github.com/containers/buildah/copier" + "github.com/containers/buildah/define" + "github.com/containers/buildah/docker" + "github.com/containers/buildah/internal/config" + "github.com/containers/buildah/internal/mkcw" + "github.com/containers/buildah/internal/tmpdir" + digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/image" + "go.podman.io/image/v5/manifest" + is "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/chrootarchive" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/ioutils" +) + +const ( + // OCIv1ImageManifest is the MIME type of an OCIv1 image manifest, + // suitable for specifying as a value of the PreferredManifestType + // member of a CommitOptions structure. It is also the default. + OCIv1ImageManifest = define.OCIv1ImageManifest + // Dockerv2ImageManifest is the MIME type of a Docker v2s2 image + // manifest, suitable for specifying as a value of the + // PreferredManifestType member of a CommitOptions structure. + Dockerv2ImageManifest = define.Dockerv2ImageManifest + // containerExcludesDir is the subdirectory of the container data + // directory where we drop exclusions + containerExcludesDir = "commit-excludes" + // containerPulledUpDir is the subdirectory of the container + // data directory where we drop exclusions when we're not squashing + containerPulledUpDir = "commit-pulled-up" + // containerExcludesSubstring is the suffix of files under + // $cdir/containerExcludesDir and $cdir/containerPulledUpDir which + // should be ignored, as they only exist because we use CreateTemp() to + // create uniquely-named files, but we don't want to try to use their + // contents until after they've been written to + containerExcludesSubstring = ".tmp" +) + +// ExtractRootfsOptions is consumed by ExtractRootfs() which allows users to +// control whether various information like the like setuid and setgid bits and +// xattrs are preserved when extracting file system objects. +type ExtractRootfsOptions struct { + StripSetuidBit bool // strip the setuid bit off of items being extracted. + StripSetgidBit bool // strip the setgid bit off of items being extracted. + StripXattrs bool // don't record extended attributes of items being extracted. + ForceTimestamp *time.Time // force timestamps in output content +} + +type containerImageRef struct { + fromImageName string + fromImageID string + store storage.Store + compression archive.Compression + name reference.Named + names []string + containerID string + mountLabel string + layerID string + oconfig []byte + dconfig []byte + created *time.Time + createdBy string + layerModTime *time.Time + layerLatestModTime *time.Time + historyComment string + annotations map[string]string + preferredManifestType string + squash bool + confidentialWorkload ConfidentialWorkloadOptions + omitHistory bool + emptyLayer bool + omitLayerHistoryEntry bool + idMappingOptions *define.IDMappingOptions + parent string + blobDirectory string + preEmptyLayers []v1.History + preLayers []commitLinkedLayerInfo + postEmptyLayers []v1.History + postLayers []commitLinkedLayerInfo + overrideChanges []string + overrideConfig *manifest.Schema2Config + extraImageContent map[string]string + compatSetParent types.OptionalBool + layerExclusions []copier.ConditionalRemovePath + layerMountTargets []copier.ConditionalRemovePath + layerPullUps []copier.EnsureParentPath + unsetAnnotations []string + setAnnotations []string + createdAnnotation types.OptionalBool +} + +type blobLayerInfo struct { + ID string + Size int64 +} + +type commitLinkedLayerInfo struct { + layerID string // more like layer "ID" + linkedLayer LinkedLayer + uncompressedDigest digest.Digest + size int64 +} + +type containerImageSource struct { + path string + ref *containerImageRef + store storage.Store + containerID string + mountLabel string + layerID string + names []string + compression archive.Compression + config []byte + configDigest digest.Digest + manifest []byte + manifestType string + blobDirectory string + blobLayers map[digest.Digest]blobLayerInfo +} + +func (i *containerImageRef) NewImage(ctx context.Context, sc *types.SystemContext) (types.ImageCloser, error) { + src, err := i.NewImageSource(ctx, sc) + if err != nil { + return nil, err + } + return image.FromSource(ctx, sc, src) +} + +func expectedOCIDiffIDs(image v1.Image) int { + expected := 0 + for _, history := range image.History { + if !history.EmptyLayer { + expected = expected + 1 + } + } + return expected +} + +func expectedDockerDiffIDs(image docker.V2Image) int { + expected := 0 + for _, history := range image.History { + if !history.EmptyLayer { + expected = expected + 1 + } + } + return expected +} + +// Extract the container's whole filesystem as a filesystem image, wrapped +// in LUKS-compatible encryption. +func (i *containerImageRef) extractConfidentialWorkloadFS(options ConfidentialWorkloadOptions) (io.ReadCloser, error) { + var image v1.Image + if err := json.Unmarshal(i.oconfig, &image); err != nil { + return nil, fmt.Errorf("recreating OCI configuration for %q: %w", i.containerID, err) + } + if options.TempDir == "" { + cdir, err := i.store.ContainerDirectory(i.containerID) + if err != nil { + return nil, fmt.Errorf("getting the per-container data directory for %q: %w", i.containerID, err) + } + tempdir, err := os.MkdirTemp(cdir, "buildah-rootfs") + if err != nil { + return nil, fmt.Errorf("creating a temporary data directory to hold a rootfs image for %q: %w", i.containerID, err) + } + defer func() { + if err := os.RemoveAll(tempdir); err != nil { + logrus.Warnf("removing temporary directory %q: %v", tempdir, err) + } + }() + options.TempDir = tempdir + } + mountPoint, err := i.store.Mount(i.containerID, i.mountLabel) + if err != nil { + return nil, fmt.Errorf("mounting container %q: %w", i.containerID, err) + } + archiveOptions := mkcw.ArchiveOptions{ + AttestationURL: options.AttestationURL, + CPUs: options.CPUs, + Memory: options.Memory, + TempDir: options.TempDir, + TeeType: options.TeeType, + IgnoreAttestationErrors: options.IgnoreAttestationErrors, + WorkloadID: options.WorkloadID, + DiskEncryptionPassphrase: options.DiskEncryptionPassphrase, + Slop: options.Slop, + FirmwareLibrary: options.FirmwareLibrary, + GraphOptions: i.store.GraphOptions(), + ExtraImageContent: i.extraImageContent, + } + rc, _, err := mkcw.Archive(mountPoint, &image, archiveOptions) + if err != nil { + if _, err2 := i.store.Unmount(i.containerID, false); err2 != nil { + logrus.Debugf("unmounting container %q: %v", i.containerID, err2) + } + return nil, fmt.Errorf("converting rootfs %q: %w", i.containerID, err) + } + return ioutils.NewReadCloserWrapper(rc, func() error { + if err = rc.Close(); err != nil { + err = fmt.Errorf("closing tar archive of container %q: %w", i.containerID, err) + } + if _, err2 := i.store.Unmount(i.containerID, false); err == nil { + if err2 != nil { + err2 = fmt.Errorf("unmounting container %q: %w", i.containerID, err2) + } + err = err2 + } else { + logrus.Debugf("unmounting container %q: %v", i.containerID, err2) + } + return err + }), nil +} + +// Extract the container's whole filesystem as if it were a single layer. +// The ExtractRootfsOptions control whether or not to preserve setuid and +// setgid bits and extended attributes on contents. +func (i *containerImageRef) extractRootfs(opts ExtractRootfsOptions) (io.ReadCloser, chan error, error) { + var uidMap, gidMap []idtools.IDMap + mountPoint, err := i.store.Mount(i.containerID, i.mountLabel) + if err != nil { + return nil, nil, fmt.Errorf("mounting container %q: %w", i.containerID, err) + } + pipeReader, pipeWriter := io.Pipe() + errChan := make(chan error, 1) + go func() { + defer pipeWriter.Close() + defer close(errChan) + if len(i.extraImageContent) > 0 { + // Abuse the tar format and _prepend_ the synthesized + // data items to the archive we'll get from + // copier.Get(), in a way that looks right to a reader + // as long as we DON'T Close() the tar Writer. + filename, _, _, err := i.makeExtraImageContentDiff(false, opts.ForceTimestamp) + if err != nil { + errChan <- fmt.Errorf("creating part of archive with extra content: %w", err) + return + } + file, err := os.Open(filename) + if err != nil { + errChan <- err + return + } + defer file.Close() + if _, err = io.Copy(pipeWriter, file); err != nil { + errChan <- fmt.Errorf("writing contents of %q: %w", filename, err) + return + } + } + if i.idMappingOptions != nil { + uidMap, gidMap = convertRuntimeIDMaps(i.idMappingOptions.UIDMap, i.idMappingOptions.GIDMap) + } + copierOptions := copier.GetOptions{ + UIDMap: uidMap, + GIDMap: gidMap, + StripSetuidBit: opts.StripSetuidBit, + StripSetgidBit: opts.StripSetgidBit, + StripXattrs: opts.StripXattrs, + Timestamp: opts.ForceTimestamp, + } + err := copier.Get(mountPoint, mountPoint, copierOptions, []string{"."}, pipeWriter) + errChan <- err + }() + return ioutils.NewReadCloserWrapper(pipeReader, func() error { + if err = pipeReader.Close(); err != nil { + err = fmt.Errorf("closing tar archive of container %q: %w", i.containerID, err) + } + if _, err2 := i.store.Unmount(i.containerID, false); err == nil { + if err2 != nil { + err2 = fmt.Errorf("unmounting container %q: %w", i.containerID, err2) + } + err = err2 + } + return err + }), errChan, nil +} + +type manifestBuilder interface { + // addLayer adds notes to the manifest and config about the layer. The layer blobs are + // identified by their possibly-compressed blob digests and sizes in the manifest, and by + // their uncompressed digests (diffIDs) in the config. + addLayer(layerBlobSum digest.Digest, layerBlobSize int64, diffID digest.Digest) + computeLayerMIMEType(what string, layerCompression archive.Compression) error + buildHistory(extraImageContentDiff string, extraImageContentDiffDigest digest.Digest) error + manifestAndConfig() ([]byte, []byte, error) +} + +type dockerSchema2ManifestBuilder struct { + i *containerImageRef + layerMediaType string + dimage docker.V2Image + dmanifest docker.V2S2Manifest +} + +// Build fresh copies of the container configuration structures so that we can edit them +// without making unintended changes to the original Builder (Docker schema 2). +func (i *containerImageRef) newDockerSchema2ManifestBuilder() (manifestBuilder, error) { + created := time.Now().UTC() + if i.created != nil { + created = *i.created + } + + // Build an empty image, and then decode over it. + dimage := docker.V2Image{} + if err := json.Unmarshal(i.dconfig, &dimage); err != nil { + return nil, err + } + // Suppress the hostname and domainname if we're running with the + // equivalent of either --timestamp or --source-date-epoch. + if i.created != nil { + dimage.Config.Hostname = "sandbox" + dimage.Config.Domainname = "" + } + // Set the parent, but only if we want to be compatible with "classic" docker build. + if i.compatSetParent == types.OptionalBoolTrue { + dimage.Parent = docker.ID(i.parent) + } + // Set the container ID and containerConfig in the docker format. + dimage.Container = i.containerID + if i.created != nil { + dimage.Container = "" + } + if dimage.Config != nil { + dimage.ContainerConfig = *dimage.Config + } + // Always replace this value, since we're newer than our base image. + dimage.Created = created + // Clear the list of diffIDs, since we always repopulate it. + dimage.RootFS = &docker.V2S2RootFS{} + dimage.RootFS.Type = docker.TypeLayers + dimage.RootFS.DiffIDs = []digest.Digest{} + // Only clear the history if we're squashing, otherwise leave it be so + // that we can append entries to it. Clear the parent, too, to reflect + // that we no longer include its layers and history. + if i.confidentialWorkload.Convert || i.squash || i.omitHistory { + dimage.Parent = "" + dimage.History = []docker.V2S2History{} + } + + // If we were supplied with a configuration, copy fields from it to + // matching fields in both formats. + if err := config.OverrideDocker(dimage.Config, i.overrideChanges, i.overrideConfig); err != nil { + return nil, fmt.Errorf("applying changes: %w", err) + } + + // If we're producing a confidential workload, override the command and + // assorted other settings that aren't expected to work correctly. + if i.confidentialWorkload.Convert { + dimage.Config.Entrypoint = []string{"/entrypoint"} + dimage.Config.Cmd = nil + dimage.Config.User = "" + dimage.Config.WorkingDir = "" + dimage.Config.Healthcheck = nil + dimage.Config.Shell = nil + dimage.Config.Volumes = nil + dimage.Config.ExposedPorts = nil + } + + // Return partial manifest. The Layers lists will be populated later. + return &dockerSchema2ManifestBuilder{ + i: i, + layerMediaType: docker.V2S2MediaTypeUncompressedLayer, + dimage: dimage, + dmanifest: docker.V2S2Manifest{ + V2Versioned: docker.V2Versioned{ + SchemaVersion: 2, + MediaType: manifest.DockerV2Schema2MediaType, + }, + Config: docker.V2S2Descriptor{ + MediaType: manifest.DockerV2Schema2ConfigMediaType, + }, + Layers: []docker.V2S2Descriptor{}, + }, + }, nil +} + +func (mb *dockerSchema2ManifestBuilder) addLayer(layerBlobSum digest.Digest, layerBlobSize int64, diffID digest.Digest) { + dlayerDescriptor := docker.V2S2Descriptor{ + MediaType: mb.layerMediaType, + Digest: layerBlobSum, + Size: layerBlobSize, + } + mb.dmanifest.Layers = append(mb.dmanifest.Layers, dlayerDescriptor) + // Note this layer in the list of diffIDs, again using the uncompressed digest. + mb.dimage.RootFS.DiffIDs = append(mb.dimage.RootFS.DiffIDs, diffID) +} + +// Compute the media types which we need to attach to a layer, given the type of +// compression that we'll be applying. +func (mb *dockerSchema2ManifestBuilder) computeLayerMIMEType(what string, layerCompression archive.Compression) error { + dmediaType := docker.V2S2MediaTypeUncompressedLayer + if layerCompression != archive.Uncompressed { + switch layerCompression { + case archive.Gzip: + dmediaType = manifest.DockerV2Schema2LayerMediaType + logrus.Debugf("compressing %s with gzip", what) + case archive.Bzip2: + // Until the image specs define a media type for bzip2-compressed layers, even if we know + // how to decompress them, we can't try to compress layers with bzip2. + return errors.New("media type for bzip2-compressed layers is not defined") + case archive.Xz: + // Until the image specs define a media type for xz-compressed layers, even if we know + // how to decompress them, we can't try to compress layers with xz. + return errors.New("media type for xz-compressed layers is not defined") + case archive.Zstd: + // Until the image specs define a media type for zstd-compressed layers, even if we know + // how to decompress them, we can't try to compress layers with zstd. + return errors.New("media type for zstd-compressed layers is not defined") + default: + logrus.Debugf("compressing %s with unknown compressor(?)", what) + } + } + mb.layerMediaType = dmediaType + return nil +} + +func (mb *dockerSchema2ManifestBuilder) buildHistory(extraImageContentDiff string, extraImageContentDiffDigest digest.Digest) error { + // Build history notes in the image configuration. + appendHistory := func(history []v1.History, empty bool) { + for i := range history { + var created time.Time + if history[i].Created != nil { + created = *history[i].Created + } + dnews := docker.V2S2History{ + Created: created, + CreatedBy: history[i].CreatedBy, + Author: history[i].Author, + Comment: history[i].Comment, + EmptyLayer: empty, + } + mb.dimage.History = append(mb.dimage.History, dnews) + } + } + + // Keep track of how many entries the base image's history had + // before we started adding to it. + baseImageHistoryLen := len(mb.dimage.History) + + // Add history entries for prepended empty layers. + appendHistory(mb.i.preEmptyLayers, true) + // Add history entries for prepended API-supplied layers. + for _, h := range mb.i.preLayers { + appendHistory([]v1.History{h.linkedLayer.History}, h.linkedLayer.History.EmptyLayer) + } + // Add a history entry for this layer, empty or not. + created := time.Now().UTC() + if mb.i.created != nil { + created = (*mb.i.created).UTC() + } + if !mb.i.omitLayerHistoryEntry { + dnews := docker.V2S2History{ + Created: created, + CreatedBy: mb.i.createdBy, + Author: mb.dimage.Author, + EmptyLayer: mb.i.emptyLayer, + Comment: mb.i.historyComment, + } + mb.dimage.History = append(mb.dimage.History, dnews) + } + // Add a history entry for the extra image content if we added a layer for it. + // This diff was added to the list of layers before API-supplied layers that + // needed to be appended, and we need to keep the order of history entries for + // not-empty layers consistent with that. + if extraImageContentDiff != "" { + createdBy := fmt.Sprintf(`/bin/sh -c #(nop) ADD dir:%s in /",`, extraImageContentDiffDigest.Encoded()) + dnews := docker.V2S2History{ + Created: created, + CreatedBy: createdBy, + } + mb.dimage.History = append(mb.dimage.History, dnews) + } + // Add history entries for appended empty layers. + appendHistory(mb.i.postEmptyLayers, true) + // Add history entries for appended API-supplied layers. + for _, h := range mb.i.postLayers { + appendHistory([]v1.History{h.linkedLayer.History}, h.linkedLayer.History.EmptyLayer) + } + + // Assemble a comment indicating which base image was used, if it + // wasn't just an image ID, and add it to the first history entry we + // added, if we indeed added one. + if len(mb.dimage.History) > baseImageHistoryLen { + var fromComment string + if strings.Contains(mb.i.parent, mb.i.fromImageID) && mb.i.fromImageName != "" && !strings.HasPrefix(mb.i.fromImageID, mb.i.fromImageName) { + if mb.dimage.History[baseImageHistoryLen].Comment != "" { + fromComment = " " + } + fromComment += "FROM " + mb.i.fromImageName + } + mb.dimage.History[baseImageHistoryLen].Comment += fromComment + } + + // Confidence check that we didn't just create a mismatch between non-empty layers in the + // history and the number of diffIDs. Only applicable if the base image (if there was + // one) provided us at least one entry to use as a starting point. + if baseImageHistoryLen != 0 { + expectedDiffIDs := expectedDockerDiffIDs(mb.dimage) + if len(mb.dimage.RootFS.DiffIDs) != expectedDiffIDs { + return fmt.Errorf("internal error: history lists %d non-empty layers, but we have %d layers on disk", expectedDiffIDs, len(mb.dimage.RootFS.DiffIDs)) + } + } + return nil +} + +func (mb *dockerSchema2ManifestBuilder) manifestAndConfig() ([]byte, []byte, error) { + // Encode the image configuration blob. + dconfig, err := json.Marshal(&mb.dimage) + if err != nil { + return nil, nil, fmt.Errorf("encoding %#v as json: %w", mb.dimage, err) + } + logrus.Debugf("Docker v2s2 config = %s", dconfig) + + // Add the configuration blob to the manifest. + mb.dmanifest.Config.Digest = digest.Canonical.FromBytes(dconfig) + mb.dmanifest.Config.Size = int64(len(dconfig)) + mb.dmanifest.Config.MediaType = manifest.DockerV2Schema2ConfigMediaType + + // Encode the manifest. + dmanifestbytes, err := json.Marshal(&mb.dmanifest) + if err != nil { + return nil, nil, fmt.Errorf("encoding %#v as json: %w", mb.dmanifest, err) + } + logrus.Debugf("Docker v2s2 manifest = %s", dmanifestbytes) + + return dmanifestbytes, dconfig, nil +} + +type ociManifestBuilder struct { + i *containerImageRef + layerMediaType string + oimage v1.Image + omanifest v1.Manifest +} + +// Build fresh copies of the container configuration structures so that we can edit them +// without making unintended changes to the original Builder (OCI manifest). +func (i *containerImageRef) newOCIManifestBuilder() (manifestBuilder, error) { + created := time.Now().UTC() + if i.created != nil { + created = *i.created + } + + // Build an empty image, and then decode over it. + oimage := v1.Image{} + if err := json.Unmarshal(i.oconfig, &oimage); err != nil { + return nil, err + } + // Always replace this value, since we're newer than our base image. + oimage.Created = &created + // Clear the list of diffIDs, since we always repopulate it. + oimage.RootFS.Type = docker.TypeLayers + oimage.RootFS.DiffIDs = []digest.Digest{} + // Only clear the history if we're squashing, otherwise leave it be so that we can append + // entries to it. + if i.confidentialWorkload.Convert || i.squash || i.omitHistory { + oimage.History = []v1.History{} + } + + // If we were supplied with a configuration, copy fields from it to + // matching fields in both formats. + if err := config.OverrideOCI(&oimage.Config, i.overrideChanges, i.overrideConfig); err != nil { + return nil, fmt.Errorf("applying changes: %w", err) + } + + // If we're producing a confidential workload, override the command and + // assorted other settings that aren't expected to work correctly. + if i.confidentialWorkload.Convert { + oimage.Config.Entrypoint = []string{"/entrypoint"} + oimage.Config.Cmd = nil + oimage.Config.User = "" + oimage.Config.WorkingDir = "" + oimage.Config.Volumes = nil + oimage.Config.ExposedPorts = nil + } + + // Return partial manifest. The Layers lists will be populated later. + annotations := make(map[string]string) + maps.Copy(annotations, i.annotations) + switch i.createdAnnotation { + case types.OptionalBoolFalse: + delete(annotations, v1.AnnotationCreated) + default: + fallthrough + case types.OptionalBoolTrue, types.OptionalBoolUndefined: + annotations[v1.AnnotationCreated] = created.UTC().Format(time.RFC3339Nano) + } + for _, k := range i.unsetAnnotations { + delete(annotations, k) + } + for _, kv := range i.setAnnotations { + k, v, _ := strings.Cut(kv, "=") + annotations[k] = v + } + return &ociManifestBuilder{ + i: i, + // The default layer media type assumes no compression. + layerMediaType: v1.MediaTypeImageLayer, + oimage: oimage, + omanifest: v1.Manifest{ + Versioned: specs.Versioned{ + SchemaVersion: 2, + }, + MediaType: v1.MediaTypeImageManifest, + Config: v1.Descriptor{ + MediaType: v1.MediaTypeImageConfig, + }, + Layers: []v1.Descriptor{}, + Annotations: annotations, + }, + }, nil +} + +func (mb *ociManifestBuilder) addLayer(layerBlobSum digest.Digest, layerBlobSize int64, diffID digest.Digest) { + olayerDescriptor := v1.Descriptor{ + MediaType: mb.layerMediaType, + Digest: layerBlobSum, + Size: layerBlobSize, + } + mb.omanifest.Layers = append(mb.omanifest.Layers, olayerDescriptor) + // Note this layer in the list of diffIDs, again using the uncompressed digest. + mb.oimage.RootFS.DiffIDs = append(mb.oimage.RootFS.DiffIDs, diffID) +} + +// Compute the media types which we need to attach to a layer, given the type of +// compression that we'll be applying. +func (mb *ociManifestBuilder) computeLayerMIMEType(what string, layerCompression archive.Compression) error { + omediaType := v1.MediaTypeImageLayer + if layerCompression != archive.Uncompressed { + switch layerCompression { + case archive.Gzip: + omediaType = v1.MediaTypeImageLayerGzip + logrus.Debugf("compressing %s with gzip", what) + case archive.Bzip2: + // Until the image specs define a media type for bzip2-compressed layers, even if we know + // how to decompress them, we can't try to compress layers with bzip2. + return errors.New("media type for bzip2-compressed layers is not defined") + case archive.Xz: + // Until the image specs define a media type for xz-compressed layers, even if we know + // how to decompress them, we can't try to compress layers with xz. + return errors.New("media type for xz-compressed layers is not defined") + case archive.Zstd: + omediaType = v1.MediaTypeImageLayerZstd + logrus.Debugf("compressing %s with zstd", what) + default: + logrus.Debugf("compressing %s with unknown compressor(?)", what) + } + } + mb.layerMediaType = omediaType + return nil +} + +func (mb *ociManifestBuilder) buildHistory(extraImageContentDiff string, extraImageContentDiffDigest digest.Digest) error { + // Build history notes in the image configuration. + appendHistory := func(history []v1.History, empty bool) { + for i := range history { + var created *time.Time + if history[i].Created != nil { + copiedTimestamp := *history[i].Created + created = &copiedTimestamp + } + onews := v1.History{ + Created: created, + CreatedBy: history[i].CreatedBy, + Author: history[i].Author, + Comment: history[i].Comment, + EmptyLayer: empty, + } + mb.oimage.History = append(mb.oimage.History, onews) + } + } + + // Keep track of how many entries the base image's history had + // before we started adding to it. + baseImageHistoryLen := len(mb.oimage.History) + + // Add history entries for prepended empty layers. + appendHistory(mb.i.preEmptyLayers, true) + // Add history entries for prepended API-supplied layers. + for _, h := range mb.i.preLayers { + appendHistory([]v1.History{h.linkedLayer.History}, h.linkedLayer.History.EmptyLayer) + } + // Add a history entry for this layer, empty or not. + created := time.Now().UTC() + if mb.i.created != nil { + created = (*mb.i.created).UTC() + } + if !mb.i.omitLayerHistoryEntry { + onews := v1.History{ + Created: &created, + CreatedBy: mb.i.createdBy, + Author: mb.oimage.Author, + EmptyLayer: mb.i.emptyLayer, + Comment: mb.i.historyComment, + } + mb.oimage.History = append(mb.oimage.History, onews) + } + // Add a history entry for the extra image content if we added a layer for it. + // This diff was added to the list of layers before API-supplied layers that + // needed to be appended, and we need to keep the order of history entries for + // not-empty layers consistent with that. + if extraImageContentDiff != "" { + createdBy := fmt.Sprintf(`/bin/sh -c #(nop) ADD dir:%s in /",`, extraImageContentDiffDigest.Encoded()) + onews := v1.History{ + Created: &created, + CreatedBy: createdBy, + } + mb.oimage.History = append(mb.oimage.History, onews) + } + // Add history entries for appended empty layers. + appendHistory(mb.i.postEmptyLayers, true) + // Add history entries for appended API-supplied layers. + for _, h := range mb.i.postLayers { + appendHistory([]v1.History{h.linkedLayer.History}, h.linkedLayer.History.EmptyLayer) + } + + // Assemble a comment indicating which base image was used, if it + // wasn't just an image ID, and add it to the first history entry we + // added, if we indeed added one. + if len(mb.oimage.History) > baseImageHistoryLen { + var fromComment string + if strings.Contains(mb.i.parent, mb.i.fromImageID) && mb.i.fromImageName != "" && !strings.HasPrefix(mb.i.fromImageID, mb.i.fromImageName) { + if mb.oimage.History[baseImageHistoryLen].Comment != "" { + fromComment = " " + } + fromComment += "FROM " + mb.i.fromImageName + } + mb.oimage.History[baseImageHistoryLen].Comment += fromComment + } + + // Confidence check that we didn't just create a mismatch between non-empty layers in the + // history and the number of diffIDs. Only applicable if the base image (if there was + // one) provided us at least one entry to use as a starting point. + if baseImageHistoryLen != 0 { + expectedDiffIDs := expectedOCIDiffIDs(mb.oimage) + if len(mb.oimage.RootFS.DiffIDs) != expectedDiffIDs { + return fmt.Errorf("internal error: history lists %d non-empty layers, but we have %d layers on disk", expectedDiffIDs, len(mb.oimage.RootFS.DiffIDs)) + } + } + return nil +} + +func (mb *ociManifestBuilder) manifestAndConfig() ([]byte, []byte, error) { + // Encode the image configuration blob. + oconfig, err := json.Marshal(&mb.oimage) + if err != nil { + return nil, nil, fmt.Errorf("encoding %#v as json: %w", mb.oimage, err) + } + logrus.Debugf("OCIv1 config = %s", oconfig) + + // Add the configuration blob to the manifest. + mb.omanifest.Config.Digest = digest.Canonical.FromBytes(oconfig) + mb.omanifest.Config.Size = int64(len(oconfig)) + mb.omanifest.Config.MediaType = v1.MediaTypeImageConfig + + // Encode the manifest. + omanifestbytes, err := json.Marshal(&mb.omanifest) + if err != nil { + return nil, nil, fmt.Errorf("encoding %#v as json: %w", mb.omanifest, err) + } + logrus.Debugf("OCIv1 manifest = %s", omanifestbytes) + + return omanifestbytes, oconfig, nil +} + +// filterExclusionsByImage returns a slice of the members of "exclusions" which are present in the image with the specified ID +func (i containerImageRef) filterExclusionsByImage(ctx context.Context, exclusions []copier.EnsureParentPath, imageID string) ([]copier.EnsureParentPath, error) { + if len(exclusions) == 0 || imageID == "" { + return nil, nil + } + var paths []copier.EnsureParentPath + mountPoint, err := i.store.MountImage(imageID, nil, i.mountLabel) + cleanup := func() { + if _, err := i.store.UnmountImage(imageID, false); err != nil { + logrus.Debugf("unmounting image %q: %v", imageID, err) + } + } + if err != nil && errors.Is(err, storage.ErrLayerUnknown) { + // if an imagestore is being used, this could be expected + if b, err2 := NewBuilder(ctx, i.store, BuilderOptions{ + FromImage: imageID, + PullPolicy: define.PullNever, + ContainerSuffix: "tmp", + }); err2 == nil { + mountPoint, err = b.Mount(i.mountLabel) + cleanup = func() { + cid := b.ContainerID + if err := b.Delete(); err != nil { + logrus.Debugf("unmounting image %q as container %q: %v", imageID, cid, err) + } + } + } + } + if err != nil { + return nil, fmt.Errorf("mounting image %q to examine its contents: %w", imageID, err) + } + defer cleanup() + globs := make([]string, 0, len(exclusions)) + for _, exclusion := range exclusions { + globs = append(globs, exclusion.Path) + } + options := copier.StatOptions{} + stats, err := copier.Stat(mountPoint, mountPoint, options, globs) + if err != nil { + return nil, fmt.Errorf("checking for potential exclusion items in image %q: %w", imageID, err) + } + for _, stat := range stats { + for _, exclusion := range exclusions { + if stat.Glob != exclusion.Path { + continue + } + for result, stat := range stat.Results { + if result != exclusion.Path { + continue + } + if exclusion.ModTime != nil && !exclusion.ModTime.Equal(stat.ModTime) { + continue + } + if exclusion.Mode != nil && *exclusion.Mode != stat.Mode { + continue + } + if exclusion.Owner != nil && (int64(exclusion.Owner.UID) != stat.UID && int64(exclusion.Owner.GID) != stat.GID) { + continue + } + paths = append(paths, exclusion) + } + } + } + return paths, nil +} + +func (i *containerImageRef) NewImageSource(ctx context.Context, _ *types.SystemContext) (src types.ImageSource, err error) { + // These maps will let us check if a layer ID is part of one group or another. + parentLayerIDs := make(map[string]bool) + apiLayerIDs := make(map[string]bool) + // Start building the list of layers with any prepended layers. + layers := []string{} + for _, preLayer := range i.preLayers { + layers = append(layers, preLayer.layerID) + apiLayerIDs[preLayer.layerID] = true + } + // Now look at the read-write layer, and prepare to work our way back + // through all of its parent layers. + layerID := i.layerID + layer, err := i.store.Layer(layerID) + if err != nil { + return nil, fmt.Errorf("unable to read layer %q: %w", layerID, err) + } + // Walk the list of parent layers, prepending each as we go. If we're squashing + // or making a confidential workload, we're only producing one layer, so stop at + // the layer ID of the top layer, which we won't really be using anyway. + for layer != nil { + if layerID == i.layerID { + // append the layer for this container to the list, + // whether it's first or after some prepended layers + layers = append(layers, layerID) + } else { + // prepend this parent layer to the list + layers = append(append([]string{}, layerID), layers...) + parentLayerIDs[layerID] = true + } + layerID = layer.Parent + if layerID == "" || i.confidentialWorkload.Convert || i.squash { + err = nil + break + } + layer, err = i.store.Layer(layerID) + if err != nil { + return nil, fmt.Errorf("unable to read layer %q: %w", layerID, err) + } + } + layer = nil + + // If we're slipping in a synthesized layer to hold some files, we need + // to add a placeholder for it to the list just after the read-write + // layer. Confidential workloads and squashed images will just inline + // the files, so we don't need to create a layer in those cases. + const synthesizedLayerID = "(synthesized layer)" + if len(i.extraImageContent) > 0 && !i.confidentialWorkload.Convert && !i.squash { + layers = append(layers, synthesizedLayerID) + } + // Now add any API-supplied layers we have to append. + for _, postLayer := range i.postLayers { + layers = append(layers, postLayer.layerID) + apiLayerIDs[postLayer.layerID] = true + } + logrus.Debugf("layer list: %q", layers) + + // It's simpler from here on to keep track of these as a group. + apiLayers := append(slices.Clone(i.preLayers), slices.Clone(i.postLayers)...) + + // Make a temporary directory to hold blobs. + path, err := os.MkdirTemp(tmpdir.GetTempDir(), define.Package) + if err != nil { + return nil, fmt.Errorf("creating temporary directory to hold layer blobs: %w", err) + } + logrus.Debugf("using %q to hold temporary data", path) + defer func() { + if src == nil { + err2 := os.RemoveAll(path) + if err2 != nil { + logrus.Errorf("error removing layer blob directory: %v", err) + } + } + }() + + // Build fresh copies of the configurations and manifest so that we don't mess with any + // values in the Builder object itself. + var mb manifestBuilder + switch i.preferredManifestType { + case v1.MediaTypeImageManifest: + mb, err = i.newOCIManifestBuilder() + if err != nil { + return nil, err + } + case manifest.DockerV2Schema2MediaType: + mb, err = i.newDockerSchema2ManifestBuilder() + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("no supported manifest types (attempted to use %q, only know %q and %q)", + i.preferredManifestType, v1.MediaTypeImageManifest, manifest.DockerV2Schema2MediaType) + } + + // Extract each layer and compute its digests, both compressed (if requested) and uncompressed. + var extraImageContentDiff string + var extraImageContentDiffDigest digest.Digest + blobLayers := make(map[digest.Digest]blobLayerInfo) + for _, layerID := range layers { + what := fmt.Sprintf("layer %q", layerID) + if i.confidentialWorkload.Convert || i.squash { + what = fmt.Sprintf("container %q", i.containerID) + } + if layerID == synthesizedLayerID { + what = synthesizedLayerID + } + if apiLayerIDs[layerID] { + what = layerID + } + // Look up this layer. + var layerUncompressedDigest digest.Digest + var layerUncompressedSize int64 + linkedLayerHasLayerID := func(l commitLinkedLayerInfo) bool { return l.layerID == layerID } + if apiLayerIDs[layerID] { + // API-provided prepended or appended layer + apiLayerIndex := slices.IndexFunc(apiLayers, linkedLayerHasLayerID) + layerUncompressedDigest = apiLayers[apiLayerIndex].uncompressedDigest + layerUncompressedSize = apiLayers[apiLayerIndex].size + } else if layerID == synthesizedLayerID { + // layer diff consisting of extra files to synthesize into a layer + diffFilename, digest, size, err := i.makeExtraImageContentDiff(true, nil) + if err != nil { + return nil, fmt.Errorf("unable to generate layer for additional content: %w", err) + } + extraImageContentDiff = diffFilename + extraImageContentDiffDigest = digest + layerUncompressedDigest = digest + layerUncompressedSize = size + } else { + // "normal" layer + layer, err := i.store.Layer(layerID) + if err != nil { + return nil, fmt.Errorf("unable to locate layer %q: %w", layerID, err) + } + layerID = layer.ID + layerUncompressedDigest = layer.UncompressedDigest + layerUncompressedSize = layer.UncompressedSize + } + // We already know the digest of the contents of parent layers, + // so if this is a parent layer, and we know its digest, reuse + // its blobsum, diff ID, and size. + if !i.confidentialWorkload.Convert && !i.squash && parentLayerIDs[layerID] && layerUncompressedDigest != "" { + layerBlobSum := layerUncompressedDigest + layerBlobSize := layerUncompressedSize + diffID := layerUncompressedDigest + // Note this layer in the manifest, using the appropriate blobsum. + mb.addLayer(layerBlobSum, layerBlobSize, diffID) + blobLayers[diffID] = blobLayerInfo{ + ID: layerID, + Size: layerBlobSize, + } + continue + } + // Figure out if we need to change the media type, in case we've changed the compression. + if err := mb.computeLayerMIMEType(what, i.compression); err != nil { + return nil, err + } + // Start reading either the layer or the whole container rootfs. + noCompression := archive.Uncompressed + diffOptions := &storage.DiffOptions{ + Compression: &noCompression, + } + var rc io.ReadCloser + var errChan chan error + var layerExclusions []copier.ConditionalRemovePath + if i.confidentialWorkload.Convert { + // Convert the root filesystem into an encrypted disk image. + rc, err = i.extractConfidentialWorkloadFS(i.confidentialWorkload) + if err != nil { + return nil, err + } + } else if i.squash { + // Extract the root filesystem as a single layer. + rc, errChan, err = i.extractRootfs(ExtractRootfsOptions{}) + if err != nil { + return nil, err + } + } else { + if apiLayerIDs[layerID] { + // We're reading an API-supplied blob. + apiLayerIndex := slices.IndexFunc(apiLayers, linkedLayerHasLayerID) + f, err := os.Open(apiLayers[apiLayerIndex].linkedLayer.BlobPath) + if err != nil { + return nil, fmt.Errorf("opening layer blob for %s: %w", layerID, err) + } + rc = f + } else if layerID == synthesizedLayerID { + // Slip in additional content as an additional layer. + if rc, err = os.Open(extraImageContentDiff); err != nil { + return nil, err + } + } else { + // If we're up to the final layer, but we don't want to + // include a diff for it, we're done. + if i.emptyLayer && layerID == i.layerID { + continue + } + if layerID == i.layerID { + // We need to filter out any mount targets that we created. + layerExclusions = append(slices.Clone(i.layerExclusions), i.layerMountTargets...) + // And we _might_ need to filter out directories that modified + // by creating and removing mount targets, _if_ they were the + // same in the base image for this stage. + layerPullUps, err := i.filterExclusionsByImage(ctx, i.layerPullUps, i.fromImageID) + if err != nil { + return nil, fmt.Errorf("checking which exclusions are in base image %q: %w", i.fromImageID, err) + } + layerExclusions = append(layerExclusions, layerPullUps...) + } + // Extract this layer, one of possibly many. + rc, err = i.store.Diff("", layerID, diffOptions) + if err != nil { + return nil, fmt.Errorf("extracting %s: %w", what, err) + } + } + } + srcHasher := digest.Canonical.Digester() + // Set up to write the possibly-recompressed blob. + layerFile, err := os.OpenFile(filepath.Join(path, "layer"), os.O_CREATE|os.O_WRONLY, 0o600) + if err != nil { + rc.Close() + return nil, fmt.Errorf("opening file for %s: %w", what, err) + } + + counter := ioutils.NewWriteCounter(layerFile) + var destHasher digest.Digester + var multiWriter io.Writer + // Avoid rehashing when we compress or mess with the layer contents somehow. + // At this point, there are multiple ways that can happen. + diffBeingAltered := i.compression != archive.Uncompressed + diffBeingAltered = diffBeingAltered || i.layerModTime != nil || i.layerLatestModTime != nil + diffBeingAltered = diffBeingAltered || len(layerExclusions) != 0 + if diffBeingAltered { + destHasher = digest.Canonical.Digester() + multiWriter = io.MultiWriter(counter, destHasher.Hash()) + } else { + destHasher = srcHasher + multiWriter = counter + } + // Compress the layer, if we're recompressing it. + writeCloser, err := archive.CompressStream(multiWriter, i.compression) + if err != nil { + layerFile.Close() + rc.Close() + return nil, fmt.Errorf("compressing %s: %w", what, err) + } + writer := io.MultiWriter(writeCloser, srcHasher.Hash()) + + // Use specified timestamps in the layer, if we're doing that for history + // entries. + nestedWriteCloser := ioutils.NewWriteCloserWrapper(writer, writeCloser.Close) + writeCloser = makeFilteredLayerWriteCloser(nestedWriteCloser, i.layerModTime, i.layerLatestModTime, layerExclusions) + writer = writeCloser + // Okay, copy from the raw diff through the filter, compressor, and counter and + // digesters. + size, err := io.Copy(writer, rc) + if err != nil { + writeCloser.Close() + layerFile.Close() + rc.Close() + return nil, fmt.Errorf("storing %s to file: on copy: %w", what, err) + } + if err := writeCloser.Close(); err != nil { + layerFile.Close() + rc.Close() + return nil, fmt.Errorf("storing %s to file: on pipe close: %w", what, err) + } + if err := layerFile.Close(); err != nil { + rc.Close() + return nil, fmt.Errorf("storing %s to file: on file close: %w", what, err) + } + rc.Close() + + if errChan != nil { + err = <-errChan + if err != nil { + return nil, fmt.Errorf("extracting container rootfs: %w", err) + } + } + + if err != nil { + return nil, fmt.Errorf("storing %s to file: %w", what, err) + } + if diffBeingAltered { + size = counter.Count + } else { + if size != counter.Count { + return nil, fmt.Errorf("storing %s to file: inconsistent layer size (copied %d, wrote %d)", what, size, counter.Count) + } + } + logrus.Debugf("%s size is %d bytes, uncompressed digest %s, possibly-compressed digest %s", what, size, srcHasher.Digest().String(), destHasher.Digest().String()) + // Rename the layer so that we can more easily find it by digest later. + finalBlobName := filepath.Join(path, destHasher.Digest().String()) + if err = os.Rename(filepath.Join(path, "layer"), finalBlobName); err != nil { + return nil, fmt.Errorf("storing %s to file while renaming %q to %q: %w", what, filepath.Join(path, "layer"), finalBlobName, err) + } + mb.addLayer(destHasher.Digest(), size, srcHasher.Digest()) + } + + // Only attempt to append history if history was not disabled explicitly. + if !i.omitHistory { + if err := mb.buildHistory(extraImageContentDiff, extraImageContentDiffDigest); err != nil { + return nil, err + } + } + + imageManifest, config, err := mb.manifestAndConfig() + if err != nil { + return nil, err + } + src = &containerImageSource{ + path: path, + ref: i, + store: i.store, + containerID: i.containerID, + mountLabel: i.mountLabel, + layerID: i.layerID, + names: i.names, + compression: i.compression, + config: config, + configDigest: digest.Canonical.FromBytes(config), + manifest: imageManifest, + manifestType: i.preferredManifestType, + blobDirectory: i.blobDirectory, + blobLayers: blobLayers, + } + return src, nil +} + +func (i *containerImageRef) NewImageDestination(_ context.Context, _ *types.SystemContext) (types.ImageDestination, error) { + return nil, errors.New("can't write to a container") +} + +func (i *containerImageRef) DockerReference() reference.Named { + return i.name +} + +func (i *containerImageRef) StringWithinTransport() string { + if len(i.names) > 0 { + return i.names[0] + } + return "" +} + +func (i *containerImageRef) DeleteImage(context.Context, *types.SystemContext) error { + // we were never here + return nil +} + +func (i *containerImageRef) PolicyConfigurationIdentity() string { + return "" +} + +func (i *containerImageRef) PolicyConfigurationNamespaces() []string { + return nil +} + +func (i *containerImageRef) Transport() types.ImageTransport { + return is.Transport +} + +func (i *containerImageSource) Close() error { + err := os.RemoveAll(i.path) + if err != nil { + return fmt.Errorf("removing layer blob directory: %w", err) + } + return nil +} + +func (i *containerImageSource) Reference() types.ImageReference { + return i.ref +} + +func (i *containerImageSource) GetSignatures(_ context.Context, _ *digest.Digest) ([][]byte, error) { + return nil, nil +} + +func (i *containerImageSource) GetManifest(_ context.Context, _ *digest.Digest) ([]byte, string, error) { + return i.manifest, i.manifestType, nil +} + +func (i *containerImageSource) LayerInfosForCopy(_ context.Context, _ *digest.Digest) ([]types.BlobInfo, error) { + return nil, nil +} + +func (i *containerImageSource) HasThreadSafeGetBlob() bool { + return false +} + +func (i *containerImageSource) GetBlob(_ context.Context, blob types.BlobInfo, _ types.BlobInfoCache) (reader io.ReadCloser, size int64, err error) { + if blob.Digest == i.configDigest { + logrus.Debugf("start reading config") + reader := bytes.NewReader(i.config) + closer := func() error { + logrus.Debugf("finished reading config") + return nil + } + return ioutils.NewReadCloserWrapper(reader, closer), reader.Size(), nil + } + var layerReadCloser io.ReadCloser + size = -1 + if blobLayerInfo, ok := i.blobLayers[blob.Digest]; ok { + noCompression := archive.Uncompressed + diffOptions := &storage.DiffOptions{ + Compression: &noCompression, + } + layerReadCloser, err = i.store.Diff("", blobLayerInfo.ID, diffOptions) + size = blobLayerInfo.Size + } else { + for _, blobDir := range []string{i.blobDirectory, i.path} { + var layerFile *os.File + layerFile, err = os.OpenFile(filepath.Join(blobDir, blob.Digest.String()), os.O_RDONLY, 0o600) + if err == nil { + st, err := layerFile.Stat() + if err != nil { + logrus.Warnf("error reading size of layer file %q: %v", blob.Digest.String(), err) + } else { + size = st.Size() + layerReadCloser = layerFile + break + } + layerFile.Close() + } + if !errors.Is(err, os.ErrNotExist) { + logrus.Debugf("error checking for layer %q in %q: %v", blob.Digest.String(), blobDir, err) + } + } + } + if err != nil || layerReadCloser == nil || size == -1 { + logrus.Debugf("error reading layer %q: %v", blob.Digest.String(), err) + return nil, -1, fmt.Errorf("opening layer blob: %w", err) + } + logrus.Debugf("reading layer %q", blob.Digest.String()) + closer := func() error { + logrus.Debugf("finished reading layer %q", blob.Digest.String()) + if err := layerReadCloser.Close(); err != nil { + return fmt.Errorf("closing layer %q after reading: %w", blob.Digest.String(), err) + } + return nil + } + return ioutils.NewReadCloserWrapper(layerReadCloser, closer), size, nil +} + +// makeExtraImageContentDiff creates an archive file containing the contents of +// files named in i.extraImageContent. The footer that marks the end of the +// archive may be omitted. +func (i *containerImageRef) makeExtraImageContentDiff(includeFooter bool, timestamp *time.Time) (_ string, _ digest.Digest, _ int64, retErr error) { + cdir, err := i.store.ContainerDirectory(i.containerID) + if err != nil { + return "", "", -1, err + } + diff, err := os.CreateTemp(cdir, "extradiff") + if err != nil { + return "", "", -1, err + } + defer diff.Close() + defer func() { + if retErr != nil { + os.Remove(diff.Name()) + } + }() + digester := digest.Canonical.Digester() + counter := ioutils.NewWriteCounter(digester.Hash()) + tw := tar.NewWriter(io.MultiWriter(diff, counter)) + if timestamp == nil { + now := time.Now() + timestamp = &now + if i.created != nil { + timestamp = i.created + } + } + for path, contents := range i.extraImageContent { + if err := func() error { + content, err := os.Open(contents) + if err != nil { + return err + } + defer content.Close() + st, err := content.Stat() + if err != nil { + return err + } + if err := tw.WriteHeader(&tar.Header{ + Name: path, + Typeflag: tar.TypeReg, + Mode: 0o644, + ModTime: *timestamp, + Size: st.Size(), + }); err != nil { + return fmt.Errorf("writing header for %q: %w", path, err) + } + if _, err := io.Copy(tw, content); err != nil { + return fmt.Errorf("writing content for %q: %w", path, err) + } + if err := tw.Flush(); err != nil { + return fmt.Errorf("flushing content for %q: %w", path, err) + } + return nil + }(); err != nil { + return "", "", -1, fmt.Errorf("writing %q to prepend-to-archive blob: %w", contents, err) + } + } + if includeFooter { + if err = tw.Close(); err != nil { + return "", "", -1, fmt.Errorf("closingprepend-to-archive blob after final write: %w", err) + } + } else { + if err = tw.Flush(); err != nil { + return "", "", -1, fmt.Errorf("flushing prepend-to-archive blob after final write: %w", err) + } + } + return diff.Name(), digester.Digest(), counter.Count, nil +} + +// makeFilteredLayerWriteCloser returns either the passed-in WriteCloser, or if +// layerModeTime or layerLatestModTime are set, a WriteCloser which modifies +// the tarball that's written to it so that timestamps in headers are set to +// layerModTime exactly (if a value is provided for it), and then clamped to be +// no later than layerLatestModTime (if a value is provided for it). +// This implies that if both values are provided, the archive's timestamps will +// be set to the earlier of the two values. +func makeFilteredLayerWriteCloser(wc io.WriteCloser, layerModTime, layerLatestModTime *time.Time, exclusions []copier.ConditionalRemovePath) io.WriteCloser { + if layerModTime == nil && layerLatestModTime == nil && len(exclusions) == 0 { + return wc + } + exclusionsMap := make(map[string]copier.ConditionalRemovePath) + for _, exclusionSpec := range exclusions { + pathSpec := strings.Trim(path.Clean(exclusionSpec.Path), "/") + if pathSpec == "" { + continue + } + exclusionsMap[pathSpec] = exclusionSpec + } + wc = newTarFilterer(wc, func(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader) { + // Changing a zeroed field to a non-zero field can affect the + // format that the library uses for writing the header, so only + // change fields that are already set to avoid changing the + // format (and as a result, changing the length) of the header + // that we write. + modTime := hdr.ModTime + nameSpec := strings.Trim(path.Clean(hdr.Name), "/") + if conditions, ok := exclusionsMap[nameSpec]; ok { + if (conditions.ModTime == nil || conditions.ModTime.Equal(modTime)) && + (conditions.Owner == nil || (conditions.Owner.UID == hdr.Uid && conditions.Owner.GID == hdr.Gid)) && + (conditions.Mode == nil || (*conditions.Mode&os.ModePerm == os.FileMode(hdr.Mode)&os.ModePerm)) { + return true, false, nil + } + } + if layerModTime != nil { + modTime = *layerModTime + } + if layerLatestModTime != nil && layerLatestModTime.Before(modTime) { + modTime = *layerLatestModTime + } + if !hdr.ModTime.IsZero() { + hdr.ModTime = modTime + } + if !hdr.AccessTime.IsZero() { + hdr.AccessTime = modTime + } + if !hdr.ChangeTime.IsZero() { + hdr.ChangeTime = modTime + } + return false, false, nil + }) + return wc +} + +// makeLinkedLayerInfos calculates the size and digest information for a layer +// we intend to add to the image that we're committing. +func (b *Builder) makeLinkedLayerInfos(layers []LinkedLayer, layerType string, layerModTime, layerLatestModTime *time.Time) ([]commitLinkedLayerInfo, error) { + if layers == nil { + return nil, nil + } + infos := make([]commitLinkedLayerInfo, 0, len(layers)) + for i, layer := range layers { + // complain if EmptyLayer and "is the BlobPath empty" don't agree + if layer.History.EmptyLayer != (layer.BlobPath == "") { + return nil, fmt.Errorf("internal error: layer-is-empty = %v, but content path is %q", layer.History.EmptyLayer, layer.BlobPath) + } + // if there's no layer contents, we're done with this one + if layer.History.EmptyLayer { + continue + } + // check if it's a directory or a non-directory + st, err := os.Stat(layer.BlobPath) + if err != nil { + return nil, fmt.Errorf("checking if layer content %s is a directory: %w", layer.BlobPath, err) + } + info := commitLinkedLayerInfo{ + layerID: fmt.Sprintf("(%s %d)", layerType, i+1), + linkedLayer: layer, + } + if err = func() error { + cdir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return fmt.Errorf("determining directory for working container: %w", err) + } + f, err := os.CreateTemp(cdir, "") + if err != nil { + return fmt.Errorf("creating temporary file to hold blob for %q: %w", info.linkedLayer.BlobPath, err) + } + defer f.Close() + var rc io.ReadCloser + var what string + if st.IsDir() { + // if it's a directory, archive it and digest the archive while we're storing a copy somewhere + what = "directory" + rc, err = chrootarchive.Tar(info.linkedLayer.BlobPath, nil, info.linkedLayer.BlobPath) + if err != nil { + return fmt.Errorf("generating a layer blob from %q: %w", info.linkedLayer.BlobPath, err) + } + } else { + what = "file" + // if it's not a directory, just digest it while we're storing a copy somewhere + rc, err = os.Open(info.linkedLayer.BlobPath) + if err != nil { + return err + } + } + + digester := digest.Canonical.Digester() + sizeCountedFile := ioutils.NewWriteCounter(io.MultiWriter(digester.Hash(), f)) + wc := makeFilteredLayerWriteCloser(ioutils.NopWriteCloser(sizeCountedFile), layerModTime, layerLatestModTime, nil) + _, copyErr := io.Copy(wc, rc) + wcErr := wc.Close() + if err := rc.Close(); err != nil { + return fmt.Errorf("storing a copy of %s %q: closing reader: %w", what, info.linkedLayer.BlobPath, err) + } + if copyErr != nil { + return fmt.Errorf("storing a copy of %s %q: copying data: %w", what, info.linkedLayer.BlobPath, copyErr) + } + if wcErr != nil { + return fmt.Errorf("storing a copy of %s %q: closing writer: %w", what, info.linkedLayer.BlobPath, wcErr) + } + info.uncompressedDigest = digester.Digest() + info.size = sizeCountedFile.Count + info.linkedLayer.BlobPath = f.Name() + return nil + }(); err != nil { + return nil, err + } + infos = append(infos, info) + } + return infos, nil +} + +// makeContainerImageRef creates a containers/image/v5/types.ImageReference +// which is mainly used for representing the working container as a source +// image that can be copied, which is how we commit the container to create the +// image. +func (b *Builder) makeContainerImageRef(options CommitOptions) (*containerImageRef, error) { + if (len(options.PrependedLinkedLayers) > 0 || len(options.AppendedLinkedLayers) > 0) && + (options.ConfidentialWorkloadOptions.Convert || options.Squash) { + return nil, errors.New("can't add prebuilt layers and produce an image with only one layer, at the same time") + } + var name reference.Named + container, err := b.store.Container(b.ContainerID) + if err != nil { + return nil, fmt.Errorf("locating container %q: %w", b.ContainerID, err) + } + if len(container.Names) > 0 { + if parsed, err2 := reference.ParseNamed(container.Names[0]); err2 == nil { + name = parsed + } + } + + cdir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return nil, fmt.Errorf("getting the per-container data directory for %q: %w", b.ContainerID, err) + } + + gatherExclusions := func(excludesFiles []string) ([]copier.ConditionalRemovePath, error) { + var excludes []copier.ConditionalRemovePath + for _, excludesFile := range excludesFiles { + if strings.Contains(excludesFile, containerExcludesSubstring) { + continue + } + excludesData, err := os.ReadFile(excludesFile) + if err != nil { + return nil, fmt.Errorf("reading commit exclusions for %q: %w", b.ContainerID, err) + } + var theseExcludes []copier.ConditionalRemovePath + if err := json.Unmarshal(excludesData, &theseExcludes); err != nil { + return nil, fmt.Errorf("parsing commit exclusions for %q: %w", b.ContainerID, err) + } + excludes = append(excludes, theseExcludes...) + } + return excludes, nil + } + mountTargetFiles, err := filepath.Glob(filepath.Join(cdir, containerExcludesDir, "*")) + if err != nil { + return nil, fmt.Errorf("checking for commit exclusions for %q: %w", b.ContainerID, err) + } + pulledUpFiles, err := filepath.Glob(filepath.Join(cdir, containerPulledUpDir, "*")) + if err != nil { + return nil, fmt.Errorf("checking for commit pulled-up items for %q: %w", b.ContainerID, err) + } + layerMountTargets, err := gatherExclusions(mountTargetFiles) + if err != nil { + return nil, err + } + if len(layerMountTargets) > 0 { + logrus.Debugf("these items were created for use as mount targets: %#v", layerMountTargets) + } + layerPullUps, err := gatherExclusions(pulledUpFiles) + if err != nil { + return nil, err + } + if len(layerPullUps) > 0 { + logrus.Debugf("these items appear to have been pulled up: %#v", layerPullUps) + } + var layerExclusions []copier.ConditionalRemovePath + if options.CompatLayerOmissions == types.OptionalBoolTrue { + layerExclusions = slices.Clone(compatLayerExclusions) + } + if len(layerExclusions) > 0 { + logrus.Debugf("excluding these items from committed layer: %#v", layerExclusions) + } + + manifestType := options.PreferredManifestType + if manifestType == "" { + manifestType = define.OCIv1ImageManifest + } + + for _, u := range options.UnsetEnvs { + b.UnsetEnv(u) + } + oconfig, err := json.Marshal(&b.OCIv1) + if err != nil { + return nil, fmt.Errorf("encoding OCI-format image configuration %#v: %w", b.OCIv1, err) + } + dconfig, err := json.Marshal(&b.Docker) + if err != nil { + return nil, fmt.Errorf("encoding docker-format image configuration %#v: %w", b.Docker, err) + } + var created, layerModTime, layerLatestModTime *time.Time + if options.HistoryTimestamp != nil { + historyTimestampUTC := options.HistoryTimestamp.UTC() + created = &historyTimestampUTC + layerModTime = &historyTimestampUTC + } + if options.SourceDateEpoch != nil { + sourceDateEpochUTC := options.SourceDateEpoch.UTC() + created = &sourceDateEpochUTC + if options.RewriteTimestamp { + layerLatestModTime = &sourceDateEpochUTC + } + } + createdBy := b.CreatedBy() + if createdBy == "" { + createdBy = strings.Join(b.Shell(), " ") + if createdBy == "" { + createdBy = "/bin/sh" + } + } + + parent := "" + forceOmitHistory := false + if b.FromImageID != "" { + parentDigest := digest.NewDigestFromEncoded(digest.Canonical, b.FromImageID) + if parentDigest.Validate() == nil { + parent = parentDigest.String() + } + if !options.OmitHistory && len(b.OCIv1.History) == 0 && len(b.OCIv1.RootFS.DiffIDs) != 0 { + // Parent had layers, but no history. We shouldn't confuse + // our own confidence checks by adding history for layers + // that we're adding, creating an image with multiple layers, + // only some of which have history entries, which would be + // broken in confusing ways. + b.Logger.Debugf("parent image %q had no history but had %d layers, assuming OmitHistory", b.FromImageID, len(b.OCIv1.RootFS.DiffIDs)) + forceOmitHistory = true + } + } + + preLayerInfos, err := b.makeLinkedLayerInfos(append(slices.Clone(b.PrependedLinkedLayers), slices.Clone(options.PrependedLinkedLayers)...), "prepended layer", layerModTime, layerLatestModTime) + if err != nil { + return nil, err + } + postLayerInfos, err := b.makeLinkedLayerInfos(append(slices.Clone(options.AppendedLinkedLayers), slices.Clone(b.AppendedLinkedLayers)...), "appended layer", layerModTime, layerLatestModTime) + if err != nil { + return nil, err + } + + ref := &containerImageRef{ + fromImageName: b.FromImage, + fromImageID: b.FromImageID, + store: b.store, + compression: options.Compression, + name: name, + names: container.Names, + containerID: container.ID, + mountLabel: b.MountLabel, + layerID: container.LayerID, + oconfig: oconfig, + dconfig: dconfig, + created: created, + createdBy: createdBy, + layerModTime: layerModTime, + layerLatestModTime: layerLatestModTime, + historyComment: b.HistoryComment(), + annotations: b.Annotations(), + setAnnotations: slices.Clone(options.Annotations), + unsetAnnotations: slices.Clone(options.UnsetAnnotations), + preferredManifestType: manifestType, + squash: options.Squash, + confidentialWorkload: options.ConfidentialWorkloadOptions, + omitHistory: options.OmitHistory || forceOmitHistory, + emptyLayer: (options.EmptyLayer || options.OmitLayerHistoryEntry) && !options.Squash && !options.ConfidentialWorkloadOptions.Convert, + omitLayerHistoryEntry: options.OmitLayerHistoryEntry && !options.Squash && !options.ConfidentialWorkloadOptions.Convert, + idMappingOptions: &b.IDMappingOptions, + parent: parent, + blobDirectory: options.BlobDirectory, + preEmptyLayers: slices.Clone(b.PrependedEmptyLayers), + preLayers: preLayerInfos, + postEmptyLayers: slices.Clone(b.AppendedEmptyLayers), + postLayers: postLayerInfos, + overrideChanges: options.OverrideChanges, + overrideConfig: options.OverrideConfig, + extraImageContent: maps.Clone(options.ExtraImageContent), + compatSetParent: options.CompatSetParent, + layerExclusions: layerExclusions, + layerMountTargets: layerMountTargets, + layerPullUps: layerPullUps, + createdAnnotation: options.CreatedAnnotation, + } + if ref.created != nil { + for i := range ref.preEmptyLayers { + ref.preEmptyLayers[i].Created = ref.created + } + for i := range ref.preLayers { + ref.preLayers[i].linkedLayer.History.Created = ref.created + } + for i := range ref.postEmptyLayers { + ref.postEmptyLayers[i].Created = ref.created + } + for i := range ref.postLayers { + ref.postLayers[i].linkedLayer.History.Created = ref.created + } + } + return ref, nil +} + +// Extract the container's whole filesystem as if it were a single layer from current builder instance +func (b *Builder) ExtractRootfs(options CommitOptions, opts ExtractRootfsOptions) (io.ReadCloser, chan error, error) { + src, err := b.makeContainerImageRef(options) + if err != nil { + return nil, nil, fmt.Errorf("creating image reference for container %q to extract its contents: %w", b.ContainerID, err) + } + return src.extractRootfs(opts) +} diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go new file mode 100644 index 00000000..9bfc41d1 --- /dev/null +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -0,0 +1,774 @@ +package imagebuildah + +import ( + "bytes" + "context" + "errors" + "fmt" + gotypes "go/types" + "io" + "maps" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "slices" + "strconv" + "strings" + "sync" + "time" + + "github.com/containerd/platforms" + "github.com/containers/buildah" + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal" + internalUtil "github.com/containers/buildah/internal/util" + "github.com/containers/buildah/pkg/parse" + "github.com/containers/buildah/util" + "github.com/hashicorp/go-multierror" + "github.com/mattn/go-shellwords" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/openshift/imagebuilder" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + "go.podman.io/common/pkg/config" + "go.podman.io/image/v5/docker" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/image" + "go.podman.io/image/v5/manifest" + "go.podman.io/image/v5/pkg/shortnames" + istorage "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/archive" + "golang.org/x/sync/semaphore" +) + +const ( + PullIfMissing = define.PullIfMissing + PullAlways = define.PullAlways + PullIfNewer = define.PullIfNewer + PullNever = define.PullNever + + Gzip = archive.Gzip + Bzip2 = archive.Bzip2 + Xz = archive.Xz + Zstd = archive.Zstd + Uncompressed = archive.Uncompressed +) + +// Mount is a mountpoint for the build container. +type Mount = specs.Mount + +type BuildOptions = define.BuildOptions + +// BuildDockerfiles parses a set of one or more Dockerfiles (which may be +// URLs), creates one or more new Executors, and then runs +// Prepare/Execute/Commit/Delete over the entire set of instructions. +// If the Manifest option is set, returns the ID of the manifest list, else it +// returns the ID of the built image, and if a name was assigned to it, a +// canonical reference for that image. +func BuildDockerfiles(ctx context.Context, store storage.Store, options define.BuildOptions, paths ...string) (id string, ref reference.Canonical, err error) { + if options.CommonBuildOpts == nil { + options.CommonBuildOpts = &define.CommonBuildOptions{} + } + if options.Args == nil { + options.Args = make(map[string]string) + } + if err := parse.Volumes(options.CommonBuildOpts.Volumes); err != nil { + return "", nil, fmt.Errorf("validating volumes: %w", err) + } + if len(paths) == 0 { + return "", nil, errors.New("building: no dockerfiles specified") + } + if len(options.Platforms) > 1 && options.IIDFile != "" { + return "", nil, fmt.Errorf("building multiple images, but iidfile %q can only be used to store one image ID", options.IIDFile) + } + + logger := logrus.New() + if options.Err != nil { + logger.SetOutput(options.Err) + } else { + logger.SetOutput(os.Stderr) + } + logger.SetLevel(logrus.GetLevel()) + + var dockerfiles []io.Reader + + for _, tag := range append([]string{options.Output}, options.AdditionalTags...) { + if tag == "" { + continue + } + if _, err := util.VerifyTagName(tag); err != nil { + return "", nil, fmt.Errorf("tag %s: %w", tag, err) + } + } + + for _, dfile := range paths { + var data io.Reader + + if strings.HasPrefix(dfile, "http://") || strings.HasPrefix(dfile, "https://") { + logger.Debugf("reading remote Dockerfile %q", dfile) + resp, err := http.Get(dfile) + if err != nil { + return "", nil, err + } + defer resp.Body.Close() + if resp.ContentLength == 0 { + return "", nil, fmt.Errorf("no contents in %q", dfile) + } + data = resp.Body + } else { + dinfo, err := os.Stat(dfile) + if err != nil { + // If the Dockerfile isn't available, try again with + // context directory prepended (if not prepended yet). + if !strings.HasPrefix(dfile, options.ContextDirectory) { + dfile = filepath.Join(options.ContextDirectory, dfile) + dinfo, err = os.Stat(dfile) + } + } + if err != nil { + return "", nil, err + } + + var contents *os.File + // If given a directory error out since `-f` does not supports path to directory + if dinfo.Mode().IsDir() { + return "", nil, fmt.Errorf("containerfile: %q cannot be path to a directory", dfile) + } + contents, err = os.Open(dfile) + if err != nil { + return "", nil, fmt.Errorf("reading build instructions: %w", err) + } + defer contents.Close() + dinfo, err = contents.Stat() + if err != nil { + return "", nil, fmt.Errorf("reading info about %q: %w", dfile, err) + } + if dinfo.Mode().IsRegular() && dinfo.Size() == 0 { + return "", nil, fmt.Errorf("no contents in %q", dfile) + } + data = contents + } + + // pre-process Dockerfiles with ".in" suffix + if strings.HasSuffix(dfile, ".in") { + pData, err := preprocessContainerfileContents(logger, dfile, data, options.ContextDirectory, options.CPPFlags) + if err != nil { + return "", nil, err + } + data = pData + } + + dockerfiles = append(dockerfiles, data) + } + + var files [][]byte + for _, dockerfile := range dockerfiles { + var b bytes.Buffer + if _, err := b.ReadFrom(dockerfile); err != nil { + return "", nil, err + } + files = append(files, b.Bytes()) + } + + if options.JobSemaphore == nil { + if options.Jobs != nil { + if *options.Jobs < 0 { + return "", nil, errors.New("building: invalid value for jobs. It must be a positive integer") + } + if *options.Jobs > 0 { + options.JobSemaphore = semaphore.NewWeighted(int64(*options.Jobs)) + } + } else { + options.JobSemaphore = semaphore.NewWeighted(1) + } + } + + manifestList := options.Manifest + options.Manifest = "" + type instance struct { + v1.Platform + ID string + Ref reference.Canonical + } + var instances []instance + var instancesLock sync.Mutex + + var builds multierror.Group + if options.SystemContext == nil { + options.SystemContext = &types.SystemContext{} + } + if options.AdditionalBuildContexts == nil { + options.AdditionalBuildContexts = make(map[string]*define.AdditionalBuildContext) + } + + if len(options.Platforms) == 0 { + options.Platforms = append(options.Platforms, struct{ OS, Arch, Variant string }{ + OS: options.SystemContext.OSChoice, + Arch: options.SystemContext.ArchitectureChoice, + }) + } + + if options.AllPlatforms { + options.Platforms, err = platformsForBaseImages(ctx, logger, paths, files, options.From, options.Args, options.AdditionalBuildContexts, options.SystemContext) + if err != nil { + return "", nil, err + } + } + + if sourceDateEpoch, ok := options.Args[internal.SourceDateEpochName]; ok && options.SourceDateEpoch == nil { + sde, err := strconv.ParseInt(sourceDateEpoch, 10, 64) + if err != nil { + return "", nil, fmt.Errorf("parsing SOURCE_DATE_EPOCH build-arg %q: %w", sourceDateEpoch, err) + } + sdeTime := time.Unix(sde, 0) + options.SourceDateEpoch = &sdeTime + } + + systemContext := options.SystemContext + for _, platform := range options.Platforms { + platformContext := *systemContext + if platform.OS == "" && platform.Arch != "" { + platform.OS = runtime.GOOS + } + if platform.OS == "" && platform.Arch == "" { + if targetPlatform, ok := options.Args["TARGETPLATFORM"]; ok { + targetPlatform, err := platforms.Parse(targetPlatform) + if err != nil { + return "", nil, fmt.Errorf("parsing TARGETPLATFORM value %q: %w", targetPlatform, err) + } + platform.OS = targetPlatform.OS + platform.Arch = targetPlatform.Architecture + platform.Variant = targetPlatform.Variant + } + } + platformSpec := internalUtil.NormalizePlatform(v1.Platform{ + OS: platform.OS, + Architecture: platform.Arch, + Variant: platform.Variant, + }) + // internalUtil.NormalizePlatform converts an empty os value to GOOS + // so we have to check the original value here to not overwrite the default for no reason + if platform.OS != "" { + platformContext.OSChoice = platformSpec.OS + } + if platform.Arch != "" { + platformContext.ArchitectureChoice = platformSpec.Architecture + platformContext.VariantChoice = platformSpec.Variant + } + platformOptions := options + platformOptions.SystemContext = &platformContext + platformOptions.OS = platformContext.OSChoice + platformOptions.Architecture = platformContext.ArchitectureChoice + logPrefix := "" + if len(options.Platforms) > 1 { + logPrefix = "[" + platforms.Format(platformSpec) + "] " + } + // Deep copy args to prevent concurrent read/writes over Args. + platformOptions.Args = maps.Clone(options.Args) + + if options.SourceDateEpoch != nil { + if options.Timestamp != nil { + return "", nil, errors.New("timestamp and source-date-epoch would be ambiguous if allowed together") + } + if _, alreadySet := platformOptions.Args[internal.SourceDateEpochName]; !alreadySet { + platformOptions.Args[internal.SourceDateEpochName] = fmt.Sprintf("%d", options.SourceDateEpoch.Unix()) + } + } + + builds.Go(func() error { + loggerPerPlatform := logger + if platformOptions.LogFile != "" && platformOptions.LogSplitByPlatform { + logFile := platformOptions.LogFile + "_" + platformOptions.OS + "_" + platformOptions.Architecture + f, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) + if err != nil { + return fmt.Errorf("opening logfile: %q: %w", logFile, err) + } + defer f.Close() + loggerPerPlatform = logrus.New() + loggerPerPlatform.SetOutput(f) + loggerPerPlatform.SetLevel(logrus.GetLevel()) + stdout := f + stderr := f + reporter := f + platformOptions.Out = stdout + platformOptions.ReportWriter = reporter + platformOptions.Err = stderr + } + thisID, thisRef, err := buildDockerfilesOnce(ctx, store, loggerPerPlatform, logPrefix, platformOptions, paths, files) + if err != nil { + if errorContext := strings.TrimSpace(logPrefix); errorContext != "" { + return fmt.Errorf("%s: %w", errorContext, err) + } + return err + } + instancesLock.Lock() + instances = append(instances, instance{ + ID: thisID, + Ref: thisRef, + Platform: platformSpec, + }) + instancesLock.Unlock() + return nil + }) + } + + if merr := builds.Wait(); merr != nil { + if merr.Len() == 1 { + return "", nil, merr.Errors[0] + } + return "", nil, merr.ErrorOrNil() + } + + // Reasons for this id, ref assignment w.r.t to use-case: + // + // * Single-platform build: On single platform build we only + // have one built instance i.e on indice 0 of built instances, + // so assign that. + // + // * Multi-platform build with manifestList: If this is a build for + // multiple platforms ( more than one platform ) and --manifest + // option then this assignment is insignificant since it will be + // overridden anyways with the id and ref of manifest list later in + // in this code. + // + // * Multi-platform build without manifest list: If this is a build for + // multiple platforms without --manifest then we are free to return + // id and ref of any one of the image in the instance list so always + // return indice 0 for predictable output instead returning the id and + // ref of the go routine which completed at last. + id, ref = instances[0].ID, instances[0].Ref + + if manifestList != "" { + rt, err := libimage.RuntimeFromStore(store, nil) + if err != nil { + return "", nil, err + } + // Create the manifest list ourselves, so that it's not in a + // partially-populated state at any point if we're creating it + // fresh. + list, err := rt.LookupManifestList(manifestList) + if err != nil && errors.Is(err, storage.ErrImageUnknown) { + list, err = rt.CreateManifestList(manifestList) + } + if err != nil { + return "", nil, err + } + // Add each instance to the list in turn. + storeTransportName := istorage.Transport.Name() + for _, instance := range instances { + instanceDigest, err := list.Add(ctx, storeTransportName+":"+instance.ID, nil) + if err != nil { + return "", nil, err + } + err = list.AnnotateInstance(instanceDigest, &libimage.ManifestListAnnotateOptions{ + Architecture: instance.Architecture, + OS: instance.OS, + Variant: instance.Variant, + }) + if err != nil { + return "", nil, err + } + } + id, ref = list.ID(), nil + // Put together a canonical reference + storeRef, err := istorage.Transport.NewStoreReference(store, nil, list.ID()) + if err != nil { + return "", nil, err + } + imgSource, err := storeRef.NewImageSource(ctx, nil) + if err != nil { + return "", nil, err + } + defer imgSource.Close() + manifestBytes, _, err := image.UnparsedInstance(imgSource, nil).Manifest(ctx) + if err != nil { + return "", nil, err + } + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return "", nil, err + } + img, err := store.Image(id) + if err != nil { + return "", nil, err + } + for _, name := range img.Names { + if named, err := reference.ParseNamed(name); err == nil { + if r, err := reference.WithDigest(reference.TrimNamed(named), manifestDigest); err == nil { + ref = r + break + } + } + } + } + + return id, ref, nil +} + +func buildDockerfilesOnce(ctx context.Context, store storage.Store, logger *logrus.Logger, logPrefix string, options define.BuildOptions, containerFiles []string, dockerfilecontents [][]byte) (string, reference.Canonical, error) { + mainNode, err := imagebuilder.ParseDockerfile(bytes.NewReader(dockerfilecontents[0])) + if err != nil { + return "", nil, fmt.Errorf("parsing main Dockerfile: %s: %w", containerFiles[0], err) + } + + // --platform was explicitly selected for this build + // so set correct TARGETPLATFORM in args if it is not + // already selected by the user. + builtinArgDefaults := make(map[string]string) + if options.SystemContext.OSChoice != "" && options.SystemContext.ArchitectureChoice != "" { + // os component from --platform string populates TARGETOS + // buildkit parity: give priority to user's `--build-arg` + builtinArgDefaults["TARGETOS"] = options.SystemContext.OSChoice + // arch component from --platform string populates TARGETARCH + // buildkit parity: give priority to user's `--build-arg` + builtinArgDefaults["TARGETARCH"] = options.SystemContext.ArchitectureChoice + // variant component from --platform string populates TARGETVARIANT + // buildkit parity: give priority to user's `--build-arg` + builtinArgDefaults["TARGETVARIANT"] = options.SystemContext.VariantChoice + // buildkit parity: give priority to user's `--build-arg` + // buildkit parity: TARGETPLATFORM should be always created + // from SystemContext and not `TARGETOS` and `TARGETARCH` because + // users can always override values of `TARGETOS` and `TARGETARCH` + // but `TARGETPLATFORM` should be set independent of those values. + builtinArgDefaults["TARGETPLATFORM"] = builtinArgDefaults["TARGETOS"] + "/" + builtinArgDefaults["TARGETARCH"] + if options.SystemContext.VariantChoice != "" { + builtinArgDefaults["TARGETPLATFORM"] += "/" + options.SystemContext.VariantChoice + } + } + delete(options.Args, "TARGETPLATFORM") + + for i, d := range dockerfilecontents[1:] { + additionalNode, err := imagebuilder.ParseDockerfile(bytes.NewReader(d)) + if err != nil { + containerFiles := containerFiles[1:] + return "", nil, fmt.Errorf("parsing additional Dockerfile %s: %w", containerFiles[i], err) + } + mainNode.Children = append(mainNode.Children, additionalNode.Children...) + } + + exec, err := newExecutor(logger, logPrefix, store, options, mainNode, containerFiles) + if err != nil { + return "", nil, fmt.Errorf("creating build executor: %w", err) + } + b := imagebuilder.NewBuilder(options.Args) + maps.Copy(b.BuiltinArgDefaults, builtinArgDefaults) + + defaultContainerConfig, err := config.Default() + if err != nil { + return "", nil, fmt.Errorf("failed to get container config: %w", err) + } + b.Env = append(defaultContainerConfig.GetDefaultEnv(), b.Env...) + stages, err := imagebuilder.NewStages(mainNode, b) + if err != nil { + return "", nil, fmt.Errorf("reading multiple stages: %w", err) + } + if options.Target != "" { + stagesTargeted, ok := stages.ThroughTarget(options.Target) + if !ok { + return "", nil, fmt.Errorf("the target %q was not found in the provided Dockerfile", options.Target) + } + stages = stagesTargeted + } + return exec.Build(ctx, stages) +} + +// preprocessContainerfileContents runs CPP(1) in preprocess-only mode on the input +// dockerfile content and will use ctxDir as the base include path. +func preprocessContainerfileContents(logger *logrus.Logger, containerfile string, r io.Reader, ctxDir string, cppFlags []string) (stdout io.Reader, err error) { + cppCommand := "cpp" + cppPath, err := exec.LookPath(cppCommand) + if err != nil { + if errors.Is(err, exec.ErrNotFound) { + err = fmt.Errorf("%v: .in support requires %s to be installed", err, cppCommand) + } + return nil, err + } + + stdoutBuffer := bytes.Buffer{} + stderrBuffer := bytes.Buffer{} + + cppArgs := []string{"-E", "-iquote", ctxDir, "-traditional", "-undef", "-"} + if flags, ok := os.LookupEnv("BUILDAH_CPPFLAGS"); ok { + args, err := shellwords.Parse(flags) + if err != nil { + return nil, fmt.Errorf("parsing BUILDAH_CPPFLAGS %q: %v", flags, err) + } + cppArgs = append(cppArgs, args...) + } + cppArgs = append(cppArgs, cppFlags...) + cmd := exec.Command(cppPath, cppArgs...) + cmd.Stdin = r + cmd.Stdout = &stdoutBuffer + cmd.Stderr = &stderrBuffer + + if err = cmd.Start(); err != nil { + return nil, fmt.Errorf("preprocessing %s: %w", containerfile, err) + } + if err = cmd.Wait(); err != nil { + if stderrBuffer.Len() != 0 { + logger.Warnf("Ignoring %s\n", stderrBuffer.String()) + } + if stdoutBuffer.Len() == 0 { + return nil, fmt.Errorf("preprocessing %s: preprocessor produced no output: %w", containerfile, err) + } + } + return &stdoutBuffer, nil +} + +// platformIsUnknown checks if the platform value indicates that the +// corresponding index entry is suitable for use as a base image +func platformIsAcceptable(platform *v1.Platform, logger *logrus.Logger) bool { + if platform == nil { + logger.Trace("rejecting potential base image with no platform information") + return false + } + if gotypes.SizesFor("gc", platform.Architecture) == nil { + // the compiler's never heard of this + logger.Tracef("rejecting potential base image architecture %q for which Go has no knowledge of how to do unsafe code", platform.Architecture) + return false + } + if slices.Contains([]string{"", "unknown"}, platform.OS) { + // we're hard-wired to reject images with these values + logger.Tracef("rejecting potential base image for which the OS value is always-rejected value %q", platform.OS) + return false + } + return true +} + +// platformsForBaseImages resolves the names of base images from the +// dockerfiles, and if they are all valid references to manifest lists, returns +// the list of platforms that are supported by all of the base images. +func platformsForBaseImages(ctx context.Context, logger *logrus.Logger, dockerfilepaths []string, dockerfiles [][]byte, from string, args map[string]string, additionalBuildContext map[string]*define.AdditionalBuildContext, systemContext *types.SystemContext) ([]struct{ OS, Arch, Variant string }, error) { + baseImages, err := baseImages(dockerfilepaths, dockerfiles, from, args, additionalBuildContext) + if err != nil { + return nil, fmt.Errorf("determining list of base images: %w", err) + } + logrus.Debugf("unresolved base images: %v", baseImages) + if len(baseImages) == 0 { + return nil, fmt.Errorf("build uses no non-scratch base images: %w", err) + } + targetPlatforms := make(map[string]struct{}) + var platformList []struct{ OS, Arch, Variant string } + for baseImageIndex, baseImage := range baseImages { + resolved, err := shortnames.Resolve(systemContext, baseImage) + if err != nil { + return nil, fmt.Errorf("resolving image name %q: %w", baseImage, err) + } + var manifestBytes []byte + var manifestType string + for _, candidate := range resolved.PullCandidates { + ref, err := docker.NewReference(candidate.Value) + if err != nil { + // github.com/containers/common/libimage.Runtime.Pull() will catch + // references that include both a tag and a digest, and drop the + // tag as part of pulling the image. Fall back to doing roughly + // the same here. + var nonDigestedRef reference.Named + if named, err2 := reference.ParseNamed(candidate.Value.String()); err2 == nil { + _, isTagged := named.(reference.NamedTagged) + digested, isDigested := named.(reference.Digested) + if isTagged && isDigested { + if nonDigestedRef, err2 = reference.WithDigest(reference.TrimNamed(named), digested.Digest()); err2 != nil { + nonDigestedRef = nil + } + } + } + if nonDigestedRef == nil { + // not a tagged-and-digested reference, either, so log the original error + logrus.Debugf("parsing image reference %q: %v", candidate.Value.String(), err) + continue + } + ref, err = docker.NewReference(nonDigestedRef) + if err != nil { + logrus.Debugf("re-parsing image reference %q: %v", nonDigestedRef.String(), err) + continue + } + } + src, err := ref.NewImageSource(ctx, systemContext) + if err != nil { + logrus.Debugf("preparing to read image manifest for %q: %v", baseImage, err) + continue + } + candidateBytes, candidateType, err := image.UnparsedInstance(src, nil).Manifest(ctx) + _ = src.Close() + if err != nil { + logrus.Debugf("reading image manifest for %q: %v", baseImage, err) + continue + } + if !manifest.MIMETypeIsMultiImage(candidateType) { + logrus.Debugf("base image %q is not a reference to a manifest list: %v", baseImage, err) + continue + } + if err := candidate.Record(); err != nil { + logrus.Debugf("error recording name %q for base image %q: %v", candidate.Value.String(), baseImage, err) + continue + } + baseImage = candidate.Value.String() + manifestBytes, manifestType = candidateBytes, candidateType + break + } + if len(manifestBytes) == 0 { + if len(resolved.PullCandidates) > 0 { + return nil, fmt.Errorf("base image name %q didn't resolve to a manifest list", baseImage) + } + return nil, fmt.Errorf("base image name %q didn't resolve to anything", baseImage) + } + if manifestType != v1.MediaTypeImageIndex { + list, err := manifest.ListFromBlob(manifestBytes, manifestType) + if err != nil { + return nil, fmt.Errorf("parsing manifest list from base image %q: %w", baseImage, err) + } + list, err = list.ConvertToMIMEType(v1.MediaTypeImageIndex) + if err != nil { + return nil, fmt.Errorf("converting manifest list from base image %q to v2s2 list: %w", baseImage, err) + } + manifestBytes, err = list.Serialize() + if err != nil { + return nil, fmt.Errorf("encoding converted v2s2 manifest list for base image %q: %w", baseImage, err) + } + } + index, err := manifest.OCI1IndexFromManifest(manifestBytes) + if err != nil { + return nil, fmt.Errorf("decoding manifest list for base image %q: %w", baseImage, err) + } + if baseImageIndex == 0 { + // populate the list with the first image's normalized platforms + for _, instance := range index.Manifests { + if !platformIsAcceptable(instance.Platform, logger) { + continue + } + if instance.ArtifactType != "" { + continue + } + platform := internalUtil.NormalizePlatform(*instance.Platform) + targetPlatforms[platforms.Format(platform)] = struct{}{} + logger.Debugf("image %q supports %q", baseImage, platforms.Format(platform)) + } + } else { + // prune the list of any normalized platforms this base image doesn't support + imagePlatforms := make(map[string]struct{}) + for _, instance := range index.Manifests { + if !platformIsAcceptable(instance.Platform, logger) { + continue + } + if instance.ArtifactType != "" { + continue + } + platform := internalUtil.NormalizePlatform(*instance.Platform) + imagePlatforms[platforms.Format(platform)] = struct{}{} + logger.Debugf("image %q supports %q", baseImage, platforms.Format(platform)) + } + var removed []string + for platform := range targetPlatforms { + if _, present := imagePlatforms[platform]; !present { + removed = append(removed, platform) + logger.Debugf("image %q does not support %q", baseImage, platform) + } + } + for _, remove := range removed { + delete(targetPlatforms, remove) + } + } + if baseImageIndex == len(baseImages)-1 && len(targetPlatforms) > 0 { + // extract the list + for platform := range targetPlatforms { + platform, err := platforms.Parse(platform) + if err != nil { + return nil, fmt.Errorf("parsing platform double/triple %q: %w", platform, err) + } + platformList = append(platformList, struct{ OS, Arch, Variant string }{ + OS: platform.OS, + Arch: platform.Architecture, + Variant: platform.Variant, + }) + logger.Debugf("base images all support %q", platform) + } + } + } + if len(platformList) == 0 { + return nil, errors.New("base images have no platforms in common") + } + return platformList, nil +} + +// baseImages parses the dockerfilecontents, possibly replacing the first +// stage's base image with FROM, and returns the list of base images as +// provided. Each entry in the dockerfilenames slice corresponds to a slice in +// dockerfilecontents. +func baseImages(dockerfilenames []string, dockerfilecontents [][]byte, from string, args map[string]string, additionalBuildContext map[string]*define.AdditionalBuildContext) ([]string, error) { + mainNode, err := imagebuilder.ParseDockerfile(bytes.NewReader(dockerfilecontents[0])) + if err != nil { + return nil, fmt.Errorf("parsing main Dockerfile: %s: %w", dockerfilenames[0], err) + } + + for i, d := range dockerfilecontents[1:] { + additionalNode, err := imagebuilder.ParseDockerfile(bytes.NewReader(d)) + if err != nil { + dockerfilenames := dockerfilenames[1:] + return nil, fmt.Errorf("parsing additional Dockerfile %s: %w", dockerfilenames[i], err) + } + mainNode.Children = append(mainNode.Children, additionalNode.Children...) + } + + b := imagebuilder.NewBuilder(args) + defaultContainerConfig, err := config.Default() + if err != nil { + return nil, fmt.Errorf("failed to get container config: %w", err) + } + b.Env = defaultContainerConfig.GetDefaultEnv() + stages, err := imagebuilder.NewStages(mainNode, b) + if err != nil { + return nil, fmt.Errorf("reading multiple stages: %w", err) + } + var baseImages []string + nicknames := make(map[string]struct{}) + for stageIndex, stage := range stages { + node := stage.Node // first line + for node != nil { // each line + for _, child := range node.Children { // tokens on this line, though we only care about the first + switch strings.ToUpper(child.Value) { // first token - instruction + case "FROM": + if child.Next != nil { // second token on this line + // If we have a fromOverride, replace the value of + // image name for the first FROM in the Containerfile. + if from != "" { + child.Next.Value = from + from = "" + } + if replaceBuildContext, ok := additionalBuildContext[child.Next.Value]; ok { + if replaceBuildContext.IsImage { + child.Next.Value = replaceBuildContext.Value + } else { + return nil, fmt.Errorf("build context %q is not an image, can not be used for FROM %q", child.Next.Value, child.Next.Value) + } + } + base := child.Next.Value + if base != "" && base != buildah.BaseImageFakeName && !internalUtil.SetHas(nicknames, base) { + headingArgs := argsMapToSlice(stage.Builder.HeadingArgs) + userArgs := argsMapToSlice(stage.Builder.Args) + // append heading args so if --build-arg key=value is not + // specified but default value is set in Containerfile + // via `ARG key=value` so default value can be used. + userArgs = append(headingArgs, userArgs...) + baseWithArg, err := imagebuilder.ProcessWord(base, userArgs) + if err != nil { + return nil, fmt.Errorf("while replacing arg variables with values for format %q: %w", base, err) + } + baseImages = append(baseImages, baseWithArg) + } + } + } + } + node = node.Next // next line + } + if stage.Name != strconv.Itoa(stageIndex) { + nicknames[stage.Name] = struct{}{} + } + } + return baseImages, nil +} diff --git a/vendor/github.com/containers/buildah/imagebuildah/executor.go b/vendor/github.com/containers/buildah/imagebuildah/executor.go new file mode 100644 index 00000000..9f4c707d --- /dev/null +++ b/vendor/github.com/containers/buildah/imagebuildah/executor.go @@ -0,0 +1,1123 @@ +package imagebuildah + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "slices" + "strconv" + "strings" + "sync" + "time" + + "github.com/containers/buildah" + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal" + internalUtil "github.com/containers/buildah/internal/util" + "github.com/containers/buildah/pkg/parse" + "github.com/containers/buildah/pkg/sshagent" + "github.com/containers/buildah/util" + encconfig "github.com/containers/ocicrypt/config" + digest "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/openshift/imagebuilder" + "github.com/openshift/imagebuilder/dockerfile/parser" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + nettypes "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/manifest" + storageTransport "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/transports" + "go.podman.io/image/v5/transports/alltransports" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/archive" + "golang.org/x/sync/semaphore" +) + +// builtinAllowedBuildArgs is list of built-in allowed build args. Normally we +// complain if we're given values for arguments which have no corresponding ARG +// instruction in the Dockerfile, since that's usually an indication of a user +// error, but for these values we make exceptions and ignore them. +var builtinAllowedBuildArgs = map[string]struct{}{ + "HTTP_PROXY": {}, + "http_proxy": {}, + "HTTPS_PROXY": {}, + "https_proxy": {}, + "FTP_PROXY": {}, + "ftp_proxy": {}, + "NO_PROXY": {}, + "no_proxy": {}, + "TARGETARCH": {}, + "TARGETOS": {}, + "TARGETPLATFORM": {}, + "TARGETVARIANT": {}, + internal.SourceDateEpochName: {}, +} + +// Executor is a buildah-based implementation of the imagebuilder.Executor +// interface. It coordinates the entire build by using one or more +// StageExecutors to handle each stage of the build. +type Executor struct { + cacheFrom []reference.Named + cacheTo []reference.Named + cacheTTL time.Duration + containerSuffix string + logger *logrus.Logger + stages map[string]*StageExecutor + store storage.Store + contextDir string + pullPolicy define.PullPolicy + registry string + ignoreUnrecognizedInstructions bool + quiet bool + runtime string + runtimeArgs []string + transientMounts []Mount + compression archive.Compression + output string + outputFormat string + additionalTags []string + log func(format string, args ...any) // can be nil + in io.Reader + inheritLabels types.OptionalBool + inheritAnnotations types.OptionalBool + out io.Writer + err io.Writer + signaturePolicyPath string + skipUnusedStages types.OptionalBool + systemContext *types.SystemContext + reportWriter io.Writer + isolation define.Isolation + namespaceOptions []define.NamespaceOption + configureNetwork define.NetworkConfigurationPolicy + cniPluginPath string + cniConfigDir string + // NetworkInterface is the libnetwork network interface used to setup CNI or netavark networks. + networkInterface nettypes.ContainerNetwork + idmappingOptions *define.IDMappingOptions + commonBuildOptions *define.CommonBuildOptions + defaultMountsFilePath string + iidfile string + squash bool + labels []string + layerLabels []string + annotations []string + layers bool + noHostname bool + noHosts bool + useCache bool + removeIntermediateCtrs bool + forceRmIntermediateCtrs bool + imageMap map[string]string // Used to map images that we create to handle the AS construct. + containerMap map[string]*buildah.Builder // Used to map from image names to only-created-for-the-rootfs containers. + baseMap map[string]struct{} // Holds the names of every base image, as given. + rootfsMap map[string]struct{} // Holds the names of every stage whose rootfs is referenced in a COPY or ADD instruction. + blobDirectory string + excludes []string + groupAdd []string + ignoreFile string + args map[string]string + globalArgs map[string]string + unusedArgs map[string]struct{} + capabilities []string + devices define.ContainerDevices + deviceSpecs []string + signBy string + architecture string + timestamp *time.Time + os string + maxPullPushRetries int + retryPullPushDelay time.Duration + cachePullSourceLookupReferenceFunc libimage.LookupReferenceFunc + cachePullDestinationLookupReferenceFunc func(srcRef types.ImageReference) libimage.LookupReferenceFunc + cachePushSourceLookupReferenceFunc func(dest types.ImageReference) libimage.LookupReferenceFunc + cachePushDestinationLookupReferenceFunc libimage.LookupReferenceFunc + ociDecryptConfig *encconfig.DecryptConfig + lastError error + terminatedStage map[string]error + stagesLock sync.Mutex + stagesSemaphore *semaphore.Weighted + logRusage bool + rusageLogFile io.Writer + imageInfoLock sync.Mutex + imageInfoCache map[string]imageTypeAndHistoryAndDiffIDs + fromOverride string + additionalBuildContexts map[string]*define.AdditionalBuildContext + manifest string + secrets map[string]define.Secret + sshsources map[string]*sshagent.Source + logPrefix string + unsetEnvs []string + unsetLabels []string + unsetAnnotations []string + processLabel string // Shares processLabel of first stage container with containers of other stages in same build + mountLabel string // Shares mountLabel of first stage container with containers of other stages in same build + buildOutputs []string // Specifies instructions for any custom build output + osVersion string + osFeatures []string + envs []string + confidentialWorkload define.ConfidentialWorkloadOptions + sbomScanOptions []define.SBOMScanOptions + cdiConfigDir string + compatSetParent types.OptionalBool + compatVolumes types.OptionalBool + compatScratchConfig types.OptionalBool + compatLayerOmissions types.OptionalBool + noPivotRoot bool + sourceDateEpoch *time.Time + rewriteTimestamp bool + createdAnnotation types.OptionalBool +} + +type imageTypeAndHistoryAndDiffIDs struct { + manifestType string + history []v1.History + diffIDs []digest.Digest + err error + architecture string + os string +} + +// newExecutor creates a new instance of the imagebuilder.Executor interface. +func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, options define.BuildOptions, mainNode *parser.Node, containerFiles []string) (*Executor, error) { + defaultContainerConfig, err := config.Default() + if err != nil { + return nil, fmt.Errorf("failed to get container config: %w", err) + } + + excludes := options.Excludes + if len(excludes) == 0 { + excludes, options.IgnoreFile, err = parse.ContainerIgnoreFile(options.ContextDirectory, options.IgnoreFile, containerFiles) + if err != nil { + return nil, err + } + } + capabilities, err := defaultContainerConfig.Capabilities("", options.AddCapabilities, options.DropCapabilities) + if err != nil { + return nil, err + } + + var transientMounts []Mount + + for _, volume := range append(defaultContainerConfig.Volumes(), options.TransientMounts...) { + mount, err := parse.Volume(volume) + if err != nil { + return nil, err + } + transientMounts = append([]Mount{mount}, transientMounts...) + } + + secrets, err := parse.Secrets(options.CommonBuildOpts.Secrets) + if err != nil { + return nil, err + } + sshsources, err := parse.SSH(options.CommonBuildOpts.SSHSources) + if err != nil { + return nil, err + } + + writer := options.ReportWriter + if options.Quiet { + writer = io.Discard + } + + var rusageLogFile io.Writer + + if options.LogRusage && !options.Quiet { + if options.RusageLogFile == "" { + rusageLogFile = options.Out + } else { + rusageLogFile, err = os.OpenFile(options.RusageLogFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return nil, fmt.Errorf("creating file to store rusage logs: %w", err) + } + } + } + + buildOutputs := slices.Clone(options.BuildOutputs) + if options.BuildOutput != "" { //nolint:staticcheck + buildOutputs = append(buildOutputs, options.BuildOutput) //nolint:staticcheck + } + + exec := Executor{ + args: options.Args, + cacheFrom: options.CacheFrom, + cacheTo: options.CacheTo, + cacheTTL: options.CacheTTL, + containerSuffix: options.ContainerSuffix, + logger: logger, + stages: make(map[string]*StageExecutor), + store: store, + contextDir: options.ContextDirectory, + excludes: excludes, + groupAdd: options.GroupAdd, + ignoreFile: options.IgnoreFile, + pullPolicy: options.PullPolicy, + registry: options.Registry, + ignoreUnrecognizedInstructions: options.IgnoreUnrecognizedInstructions, + quiet: options.Quiet, + runtime: options.Runtime, + runtimeArgs: options.RuntimeArgs, + transientMounts: transientMounts, + compression: options.Compression, + output: options.Output, + outputFormat: options.OutputFormat, + additionalTags: options.AdditionalTags, + signaturePolicyPath: options.SignaturePolicyPath, + skipUnusedStages: options.SkipUnusedStages, + systemContext: options.SystemContext, + log: options.Log, + in: options.In, + out: options.Out, + err: options.Err, + reportWriter: writer, + isolation: options.Isolation, + inheritLabels: options.InheritLabels, + inheritAnnotations: options.InheritAnnotations, + namespaceOptions: options.NamespaceOptions, + configureNetwork: options.ConfigureNetwork, + cniPluginPath: options.CNIPluginPath, + cniConfigDir: options.CNIConfigDir, + networkInterface: options.NetworkInterface, + idmappingOptions: options.IDMappingOptions, + commonBuildOptions: options.CommonBuildOpts, + defaultMountsFilePath: options.DefaultMountsFilePath, + iidfile: options.IIDFile, + squash: options.Squash, + labels: slices.Clone(options.Labels), + layerLabels: slices.Clone(options.LayerLabels), + annotations: slices.Clone(options.Annotations), + layers: options.Layers, + noHostname: options.CommonBuildOpts.NoHostname, + noHosts: options.CommonBuildOpts.NoHosts, + useCache: !options.NoCache, + removeIntermediateCtrs: options.RemoveIntermediateCtrs, + forceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, + imageMap: make(map[string]string), + containerMap: make(map[string]*buildah.Builder), + baseMap: make(map[string]struct{}), + rootfsMap: make(map[string]struct{}), + blobDirectory: options.BlobDirectory, + unusedArgs: make(map[string]struct{}), + capabilities: capabilities, + deviceSpecs: options.Devices, + signBy: options.SignBy, + architecture: options.Architecture, + timestamp: options.Timestamp, + os: options.OS, + maxPullPushRetries: options.MaxPullPushRetries, + retryPullPushDelay: options.PullPushRetryDelay, + cachePullSourceLookupReferenceFunc: options.CachePullSourceLookupReferenceFunc, + cachePullDestinationLookupReferenceFunc: options.CachePullDestinationLookupReferenceFunc, + cachePushSourceLookupReferenceFunc: options.CachePushSourceLookupReferenceFunc, + cachePushDestinationLookupReferenceFunc: options.CachePushDestinationLookupReferenceFunc, + ociDecryptConfig: options.OciDecryptConfig, + terminatedStage: make(map[string]error), + stagesSemaphore: options.JobSemaphore, + logRusage: options.LogRusage, + rusageLogFile: rusageLogFile, + imageInfoCache: make(map[string]imageTypeAndHistoryAndDiffIDs), + fromOverride: options.From, + additionalBuildContexts: options.AdditionalBuildContexts, + manifest: options.Manifest, + secrets: secrets, + sshsources: sshsources, + logPrefix: logPrefix, + unsetEnvs: slices.Clone(options.UnsetEnvs), + unsetLabels: slices.Clone(options.UnsetLabels), + unsetAnnotations: slices.Clone(options.UnsetAnnotations), + buildOutputs: buildOutputs, + osVersion: options.OSVersion, + osFeatures: slices.Clone(options.OSFeatures), + envs: slices.Clone(options.Envs), + confidentialWorkload: options.ConfidentialWorkload, + sbomScanOptions: options.SBOMScanOptions, + cdiConfigDir: options.CDIConfigDir, + compatSetParent: options.CompatSetParent, + compatVolumes: options.CompatVolumes, + compatScratchConfig: options.CompatScratchConfig, + compatLayerOmissions: options.CompatLayerOmissions, + noPivotRoot: options.NoPivotRoot, + sourceDateEpoch: options.SourceDateEpoch, + rewriteTimestamp: options.RewriteTimestamp, + createdAnnotation: options.CreatedAnnotation, + } + // sort unsetAnnotations because we will later write these + // values to the history of the image therefore we want to + // make sure that order is always consistent. + slices.Sort(exec.unsetAnnotations) + + if exec.err == nil { + exec.err = os.Stderr + } + if exec.out == nil { + exec.out = os.Stdout + } + + for arg := range options.Args { + if _, isBuiltIn := builtinAllowedBuildArgs[arg]; !isBuiltIn { + exec.unusedArgs[arg] = struct{}{} + } + } + // Use this flag to collect all args declared before + // first stage and treat them as global args which is + // accessible to all stages. + foundFirstStage := false + globalArgs := make(map[string]string) + for _, line := range mainNode.Children { + node := line + for node != nil { // tokens on this line, though we only care about the first + switch strings.ToUpper(node.Value) { // first token - instruction + case "ARG": + arg := node.Next + if arg != nil { + // We have to be careful here - it's either an argument + // and value, or just an argument, since they can be + // separated by either "=" or whitespace. + argName, argValue, hasValue := strings.Cut(arg.Value, "=") + if !foundFirstStage { + if hasValue { + globalArgs[argName] = argValue + } + } + delete(exec.unusedArgs, argName) + } + case "FROM": + foundFirstStage = true + } + break + } + } + exec.globalArgs = globalArgs + return &exec, nil +} + +// startStage creates a new stage executor that will be referenced whenever a +// COPY or ADD statement uses a --from=NAME flag. +func (b *Executor) startStage(ctx context.Context, stage *imagebuilder.Stage, stages imagebuilder.Stages, output string) *StageExecutor { + // create a copy of systemContext for each stage executor. + systemContext := *b.systemContext + stageExec := &StageExecutor{ + ctx: ctx, + executor: b, + systemContext: &systemContext, + log: b.log, + index: stage.Position, + stages: stages, + name: stage.Name, + volumeCache: make(map[string]string), + volumeCacheInfo: make(map[string]os.FileInfo), + output: output, + stage: stage, + } + b.stages[stage.Name] = stageExec + if idx := strconv.Itoa(stage.Position); idx != stage.Name { + b.stages[idx] = stageExec + } + return stageExec +} + +// resolveNameToImageRef creates a types.ImageReference for the output name in local storage +func (b *Executor) resolveNameToImageRef(output string) (types.ImageReference, error) { + if imageRef, err := alltransports.ParseImageName(output); err == nil { + return imageRef, nil + } + resolved, err := libimage.NormalizeName(output) + if err != nil { + return nil, err + } + imageRef, err := storageTransport.Transport.ParseStoreReference(b.store, resolved.String()) + if err == nil { + return imageRef, nil + } + + return imageRef, err +} + +// waitForStage waits for an entry to be added to terminatedStage indicating +// that the specified stage has finished. If there is no stage defined by that +// name, then it will return (false, nil). If there is a stage defined by that +// name, it will return true along with any error it encounters. +func (b *Executor) waitForStage(ctx context.Context, name string, stages imagebuilder.Stages) (bool, error) { + found := false + for _, otherStage := range stages { + if otherStage.Name == name || strconv.Itoa(otherStage.Position) == name { + found = true + break + } + } + if !found { + return false, nil + } + for { + if b.lastError != nil { + return true, b.lastError + } + + b.stagesLock.Lock() + terminationError, terminated := b.terminatedStage[name] + b.stagesLock.Unlock() + + if terminationError != nil { + return false, terminationError + } + if terminated { + return true, nil + } + + b.stagesSemaphore.Release(1) + time.Sleep(time.Millisecond * 10) + if err := b.stagesSemaphore.Acquire(ctx, 1); err != nil { + return true, fmt.Errorf("reacquiring job semaphore: %w", err) + } + } +} + +// getImageTypeAndHistoryAndDiffIDs returns the os, architecture, manifest type, history, and diff IDs list of imageID. +func (b *Executor) getImageTypeAndHistoryAndDiffIDs(ctx context.Context, imageID string) (string, string, string, []v1.History, []digest.Digest, error) { + b.imageInfoLock.Lock() + imageInfo, ok := b.imageInfoCache[imageID] + b.imageInfoLock.Unlock() + if ok { + return imageInfo.os, imageInfo.architecture, imageInfo.manifestType, imageInfo.history, imageInfo.diffIDs, imageInfo.err + } + imageRef, err := storageTransport.Transport.ParseStoreReference(b.store, "@"+imageID) + if err != nil { + return "", "", "", nil, nil, fmt.Errorf("getting image reference %q: %w", imageID, err) + } + ref, err := imageRef.NewImage(ctx, nil) + if err != nil { + return "", "", "", nil, nil, fmt.Errorf("creating new image from reference to image %q: %w", imageID, err) + } + defer ref.Close() + oci, err := ref.OCIConfig(ctx) + if err != nil { + return "", "", "", nil, nil, fmt.Errorf("getting possibly-converted OCI config of image %q: %w", imageID, err) + } + manifestBytes, manifestFormat, err := ref.Manifest(ctx) + if err != nil { + return "", "", "", nil, nil, fmt.Errorf("getting manifest of image %q: %w", imageID, err) + } + if manifestFormat == "" && len(manifestBytes) > 0 { + manifestFormat = manifest.GuessMIMEType(manifestBytes) + } + b.imageInfoLock.Lock() + b.imageInfoCache[imageID] = imageTypeAndHistoryAndDiffIDs{ + manifestType: manifestFormat, + history: oci.History, + diffIDs: oci.RootFS.DiffIDs, + err: nil, + architecture: oci.Architecture, + os: oci.OS, + } + b.imageInfoLock.Unlock() + return oci.OS, oci.Architecture, manifestFormat, oci.History, oci.RootFS.DiffIDs, nil +} + +func (b *Executor) buildStage(ctx context.Context, cleanupStages map[int]*StageExecutor, stages imagebuilder.Stages, stageIndex int) (imageID string, ref reference.Canonical, onlyBaseImage bool, err error) { + stage := stages[stageIndex] + ib := stage.Builder + node := stage.Node + base, err := ib.From(node) + if err != nil { + logrus.Debugf("buildStage(node.Children=%#v)", node.Children) + return "", nil, false, err + } + + // If this is the last stage, then the image that we produce at + // its end should be given the desired output name. + output := "" + if stageIndex == len(stages)-1 { + output = b.output + // Check if any labels were passed in via the API, and add a final line + // to the Dockerfile that would provide the same result. + // Reason: Docker adds label modification as a last step which can be + // processed like regular steps, and if no modification is done to + // layers, its easier to reuse cached layers. + if len(b.labels) > 0 { + var labelLine string + labels := slices.Clone(b.labels) + for _, labelSpec := range labels { + key, value, _ := strings.Cut(labelSpec, "=") + // check only for an empty key since docker allows empty values + if key != "" { + labelLine += fmt.Sprintf(" %q=%q", key, value) + } + } + if len(labelLine) > 0 { + additionalNode, err := imagebuilder.ParseDockerfile(strings.NewReader("LABEL" + labelLine + "\n")) + if err != nil { + return "", nil, false, fmt.Errorf("while adding additional LABEL step: %w", err) + } + stage.Node.Children = append(stage.Node.Children, additionalNode.Children...) + } + } + } + + // If this stage is starting out with environment variables that were + // passed in via our API, we should include them in the history, since + // they affect RUN instructions in this stage. + if len(b.envs) > 0 { + var envLine string + for _, envSpec := range b.envs { + key, value, hasValue := strings.Cut(envSpec, "=") + if hasValue { + envLine += fmt.Sprintf(" %q=%q", key, value) + } else { + return "", nil, false, fmt.Errorf("BUG: unresolved environment variable: %q", key) + } + } + if len(envLine) > 0 { + additionalNode, err := imagebuilder.ParseDockerfile(strings.NewReader("ENV" + envLine + "\n")) + if err != nil { + return "", nil, false, fmt.Errorf("while adding additional ENV step: %w", err) + } + // make this the first instruction in the stage after its FROM instruction + stage.Node.Children = append(additionalNode.Children, stage.Node.Children...) + } + } + + b.stagesLock.Lock() + stageExecutor := b.startStage(ctx, &stage, stages, output) + if stageExecutor.log == nil { + stepCounter := 0 + stageExecutor.log = func(format string, args ...any) { + prefix := b.logPrefix + if len(stages) > 1 { + prefix += fmt.Sprintf("[%d/%d] ", stageIndex+1, len(stages)) + } + if !strings.HasPrefix(format, "COMMIT") { + stepCounter++ + prefix += fmt.Sprintf("STEP %d", stepCounter) + if stepCounter <= len(stage.Node.Children)+1 { + prefix += fmt.Sprintf("/%d", len(stage.Node.Children)+1) + } + prefix += ": " + } + suffix := "\n" + fmt.Fprintf(stageExecutor.executor.out, prefix+format+suffix, args...) + } + } + b.stagesLock.Unlock() + + // If this a single-layer build, or if it's a multi-layered + // build and b.forceRmIntermediateCtrs is set, make sure we + // remove the intermediate/build containers, regardless of + // whether or not the stage's build fails. + if b.forceRmIntermediateCtrs || !b.layers { + b.stagesLock.Lock() + cleanupStages[stage.Position] = stageExecutor + b.stagesLock.Unlock() + } + + // Build this stage. + if imageID, ref, onlyBaseImage, err = stageExecutor.Execute(ctx, base); err != nil { + return "", nil, onlyBaseImage, err + } + + // The stage succeeded, so remove its build container if we're + // told to delete successful intermediate/build containers for + // multi-layered builds. + // Skip cleanup if the stage has no instructions. + if b.removeIntermediateCtrs && len(stage.Node.Children) > 0 { + b.stagesLock.Lock() + cleanupStages[stage.Position] = stageExecutor + b.stagesLock.Unlock() + } + + return imageID, ref, onlyBaseImage, nil +} + +type stageDependencyInfo struct { + Name string + Position int + Needs []string + NeededByTarget bool +} + +// Marks `NeededByTarget` as true for the given stage and all its dependency stages as true recursively. +func markDependencyStagesForTarget(dependencyMap map[string]*stageDependencyInfo, stage string) { + if stageDependencyInfo, ok := dependencyMap[stage]; ok { + if !stageDependencyInfo.NeededByTarget { + stageDependencyInfo.NeededByTarget = true + for _, need := range stageDependencyInfo.Needs { + markDependencyStagesForTarget(dependencyMap, need) + } + } + } +} + +func (b *Executor) warnOnUnsetBuildArgs(stages imagebuilder.Stages, dependencyMap map[string]*stageDependencyInfo, args map[string]string) { + argFound := make(map[string]struct{}) + for _, stage := range stages { + node := stage.Node // first line + for node != nil { // each line + for _, child := range node.Children { + switch strings.ToUpper(child.Value) { + case "ARG": + argName := child.Next.Value + if strings.Contains(argName, "=") { + res := strings.Split(argName, "=") + if res[1] != "" { + argFound[res[0]] = struct{}{} + } + } + argHasValue := true + if !strings.Contains(argName, "=") { + argHasValue = internalUtil.SetHas(argFound, argName) + } + if _, ok := args[argName]; !argHasValue && !ok { + shouldWarn := true + if stageDependencyInfo, ok := dependencyMap[stage.Name]; ok { + if !stageDependencyInfo.NeededByTarget && b.skipUnusedStages != types.OptionalBoolFalse { + shouldWarn = false + } + } + if _, isBuiltIn := builtinAllowedBuildArgs[argName]; isBuiltIn { + shouldWarn = false + } + if _, isGlobalArg := b.globalArgs[argName]; isGlobalArg { + shouldWarn = false + } + if shouldWarn { + b.logger.Warnf("missing %q build argument. Try adding %q to the command line", argName, fmt.Sprintf("--build-arg %s=", argName)) + } + } + default: + continue + } + } + node = node.Next + } + } +} + +// Build takes care of the details of running Prepare/Execute/Commit/Delete +// over each of the one or more parsed Dockerfiles and stages. +func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (imageID string, ref reference.Canonical, err error) { + if len(stages) == 0 { + return "", nil, errors.New("building: no stages to build") + } + var cleanupImages []string + cleanupStages := make(map[int]*StageExecutor) + + stdout := b.out + if b.quiet { + b.out = io.Discard + } + + cleanup := func() error { + var lastErr error + // Clean up any containers associated with the final container + // built by a stage, for stages that succeeded, since we no + // longer need their filesystem contents. + + b.stagesLock.Lock() + for _, stage := range cleanupStages { + if err := stage.Delete(); err != nil { + logrus.Debugf("Failed to cleanup stage containers: %v", err) + lastErr = err + } + } + cleanupStages = nil + b.stagesLock.Unlock() + + // Clean up any builders that we used to get data from images. + for _, builder := range b.containerMap { + if err := builder.Delete(); err != nil { + logrus.Debugf("Failed to cleanup image containers: %v", err) + lastErr = err + } + } + b.containerMap = nil + // Clean up any intermediate containers associated with stages, + // since we're not keeping them for debugging. + if b.removeIntermediateCtrs { + if err := b.deleteSuccessfulIntermediateCtrs(); err != nil { + logrus.Debugf("Failed to cleanup intermediate containers: %v", err) + lastErr = err + } + } + // Remove images from stages except the last one, since we're + // not going to use them as a starting point for any new + // stages. + for i := range cleanupImages { + removeID := cleanupImages[len(cleanupImages)-i-1] + if removeID == imageID { + continue + } + if _, err := b.store.DeleteImage(removeID, true); err != nil { + logrus.Debugf("failed to remove intermediate image %q: %v", removeID, err) + if b.forceRmIntermediateCtrs || !errors.Is(err, storage.ErrImageUsedByContainer) { + lastErr = err + } + } + } + cleanupImages = nil + + if b.rusageLogFile != nil && b.rusageLogFile != b.out { + // we deliberately ignore the error here, as this + // function can be called multiple times + if closer, ok := b.rusageLogFile.(interface{ Close() error }); ok { + closer.Close() + } + } + return lastErr + } + + defer func() { + if cleanupErr := cleanup(); cleanupErr != nil { + if err == nil { + err = cleanupErr + } else { + err = fmt.Errorf("%v: %w", cleanupErr.Error(), err) + } + } + }() + + // dependencyMap contains dependencyInfo for each stage, + // dependencyInfo is used later to mark if a particular + // stage is needed by target or not. + dependencyMap := make(map[string]*stageDependencyInfo) + // Build maps of every named base image and every referenced stage root + // filesystem. Individual stages can use them to determine whether or + // not they can skip certain steps near the end of their stages. + for stageIndex, stage := range stages { + dependencyMap[stage.Name] = &stageDependencyInfo{Name: stage.Name, Position: stage.Position} + node := stage.Node // first line + for node != nil { // each line + for _, child := range node.Children { // tokens on this line, though we only care about the first + switch strings.ToUpper(child.Value) { // first token - instruction + case "FROM": + if child.Next != nil { // second token on this line + // If we have a fromOverride, replace the value of + // image name for the first FROM in the Containerfile. + if b.fromOverride != "" { + child.Next.Value = b.fromOverride + b.fromOverride = "" + } + base := child.Next.Value + if base != "" && base != buildah.BaseImageFakeName { + if replaceBuildContext, ok := b.additionalBuildContexts[child.Next.Value]; ok { + if replaceBuildContext.IsImage { + child.Next.Value = replaceBuildContext.Value + base = child.Next.Value + } + } + builtinArgs := argsMapToSlice(stage.Builder.BuiltinArgDefaults) + headingArgs := argsMapToSlice(stage.Builder.HeadingArgs) + userArgs := argsMapToSlice(stage.Builder.Args) + // append heading args so if --build-arg key=value is not + // specified but default value is set in Containerfile + // via `ARG key=value` so default value can be used. + userArgs = append(builtinArgs, append(userArgs, headingArgs...)...) + baseWithArg, err := imagebuilder.ProcessWord(base, userArgs) + if err != nil { + return "", nil, fmt.Errorf("while replacing arg variables with values for format %q: %w", base, err) + } + b.baseMap[baseWithArg] = struct{}{} + logrus.Debugf("base for stage %d: %q resolves to %q", stageIndex, base, baseWithArg) + // Check if selected base is not an additional + // build context and if base is a valid stage + // add it to current stage's dependency tree. + if _, ok := b.additionalBuildContexts[baseWithArg]; !ok { + if _, ok := dependencyMap[baseWithArg]; ok { + // update current stage's dependency info + currentStageInfo := dependencyMap[stage.Name] + currentStageInfo.Needs = append(currentStageInfo.Needs, baseWithArg) + } + } + } + } + case "ADD", "COPY": + for _, flag := range child.Flags { // flags for this instruction + if after, ok := strings.CutPrefix(flag, "--from="); ok { + // TODO: this didn't undergo variable and + // arg expansion, so if the previous stage + // was named using argument values, we might + // not record the right value here. + rootfs := after + b.rootfsMap[rootfs] = struct{}{} + logrus.Debugf("rootfs needed for COPY in stage %d: %q", stageIndex, rootfs) + // Populate dependency tree and check + // if following ADD or COPY needs any other + // stage. + stageName := rootfs + builtinArgs := argsMapToSlice(stage.Builder.BuiltinArgDefaults) + headingArgs := argsMapToSlice(stage.Builder.HeadingArgs) + userArgs := argsMapToSlice(stage.Builder.Args) + // append heading args so if --build-arg key=value is not + // specified but default value is set in Containerfile + // via `ARG key=value` so default value can be used. + userArgs = append(builtinArgs, append(userArgs, headingArgs...)...) + baseWithArg, err := imagebuilder.ProcessWord(stageName, userArgs) + if err != nil { + return "", nil, fmt.Errorf("while replacing arg variables with values for format %q: %w", stageName, err) + } + logrus.Debugf("stage %d name: %q resolves to %q", stageIndex, stageName, baseWithArg) + stageName = baseWithArg + // If --from= convert index to name + if index, err := strconv.Atoi(stageName); err == nil && index >= 0 && index < stageIndex { + stageName = stages[index].Name + } + // Check if selected base is not an additional + // build context and if base is a valid stage + // add it to current stage's dependency tree. + if _, ok := b.additionalBuildContexts[stageName]; !ok { + if _, ok := dependencyMap[stageName]; ok { + // update current stage's dependency info + currentStageInfo := dependencyMap[stage.Name] + currentStageInfo.Needs = append(currentStageInfo.Needs, stageName) + } + } + } + } + case "RUN": + for _, flag := range child.Flags { // flags for this instruction + // We need to populate dependency tree of stages + // if it is using `--mount` and `from=` field is set + // and `from=` points to a stage consider it in + // dependency calculation. + if strings.HasPrefix(flag, "--mount=") && strings.Contains(flag, "from") { + mountFlags := strings.TrimPrefix(flag, "--mount=") + fields := strings.SplitSeq(mountFlags, ",") + for field := range fields { + if mountFrom, hasFrom := strings.CutPrefix(field, "from="); hasFrom { + // Check if this base is a stage if yes + // add base to current stage's dependency tree + // but also confirm if this is not in additional context. + if _, ok := b.additionalBuildContexts[mountFrom]; !ok { + // Treat from as a rootfs we need to preserve + b.rootfsMap[mountFrom] = struct{}{} + if _, ok := dependencyMap[mountFrom]; ok { + // update current stage's dependency info + currentStageInfo := dependencyMap[stage.Name] + currentStageInfo.Needs = append(currentStageInfo.Needs, mountFrom) + } + } + } + } + } + } + } + } + node = node.Next // next line + } + // Last stage is always target stage. + // Since last/target stage is processed + // let's calculate dependency map of stages + // so we can mark stages which can be skipped. + if stage.Position == (len(stages) - 1) { + markDependencyStagesForTarget(dependencyMap, stage.Name) + } + } + b.warnOnUnsetBuildArgs(stages, dependencyMap, b.args) + + type Result struct { + Index int + ImageID string + OnlyBaseImage bool + Ref reference.Canonical + Error error + } + + ch := make(chan Result, len(stages)) + + if b.stagesSemaphore == nil { + b.stagesSemaphore = semaphore.NewWeighted(int64(len(stages))) + } + + var wg sync.WaitGroup + wg.Add(len(stages)) + + go func() { + cancel := false + for stageIndex := range stages { + index := stageIndex + // Acquire the semaphore before creating the goroutine so we are sure they + // run in the specified order. + if err := b.stagesSemaphore.Acquire(ctx, 1); err != nil { + cancel = true + b.lastError = err + ch <- Result{ + Index: index, + Error: err, + } + wg.Done() + continue + } + b.stagesLock.Lock() + cleanupStages := cleanupStages + b.stagesLock.Unlock() + go func() { + defer b.stagesSemaphore.Release(1) + defer wg.Done() + if cancel || cleanupStages == nil { + var err error + if stages[index].Name != strconv.Itoa(index) { + err = fmt.Errorf("not building stage %d: build canceled", index) + } else { + err = fmt.Errorf("not building stage %d (%s): build canceled", index, stages[index].Name) + } + ch <- Result{ + Index: index, + Error: err, + } + return + } + // Skip stage if it is not needed by TargetStage + // or any of its dependency stages and `SkipUnusedStages` + // is not set to `false`. + if stageDependencyInfo, ok := dependencyMap[stages[index].Name]; ok { + if !stageDependencyInfo.NeededByTarget && b.skipUnusedStages != types.OptionalBoolFalse { + logrus.Debugf("Skipping stage with Name %q and index %d since its not needed by the target stage", stages[index].Name, index) + ch <- Result{ + Index: index, + Error: nil, + } + return + } + } + stageID, stageRef, stageOnlyBaseImage, stageErr := b.buildStage(ctx, cleanupStages, stages, index) + if stageErr != nil { + cancel = true + ch <- Result{ + Index: index, + Error: stageErr, + OnlyBaseImage: stageOnlyBaseImage, + } + return + } + + ch <- Result{ + Index: index, + ImageID: stageID, + Ref: stageRef, + OnlyBaseImage: stageOnlyBaseImage, + Error: nil, + } + }() + } + }() + go func() { + wg.Wait() + close(ch) + }() + + for r := range ch { + stage := stages[r.Index] + + b.stagesLock.Lock() + b.terminatedStage[stage.Name] = r.Error + b.terminatedStage[strconv.Itoa(stage.Position)] = r.Error + + if r.Error != nil { + b.stagesLock.Unlock() + b.lastError = r.Error + return "", nil, r.Error + } + + // If this is an intermediate stage, make a note of the ID, so + // that we can look it up later. + if r.Index < len(stages)-1 && r.ImageID != "" { + b.imageMap[stage.Name] = r.ImageID + // We're not populating the cache with intermediate + // images, so add this one to the list of images that + // we'll remove later. + // Only remove intermediate image is `--layers` is not provided + // or following stage was not only a base image ( i.e a different image ). + if !b.layers && !r.OnlyBaseImage { + cleanupImages = append(cleanupImages, r.ImageID) + } + } + if r.Index == len(stages)-1 { + imageID = r.ImageID + ref = r.Ref + } + b.stagesLock.Unlock() + } + + if len(b.unusedArgs) > 0 { + unusedList := make([]string, 0, len(b.unusedArgs)) + for k := range b.unusedArgs { + unusedList = append(unusedList, k) + } + slices.Sort(unusedList) + fmt.Fprintf(b.out, "[Warning] one or more build args were not consumed: %v\n", unusedList) + } + + // Add additional tags and print image names recorded in storage + if dest, err := b.resolveNameToImageRef(b.output); err == nil { + switch dest.Transport().Name() { + case storageTransport.Transport.Name(): + _, img, err := storageTransport.ResolveReference(dest) + if err != nil { + return imageID, ref, fmt.Errorf("locating just-written image %q: %w", transports.ImageName(dest), err) + } + if len(b.additionalTags) > 0 { + if err = util.AddImageNames(b.store, "", b.systemContext, img, b.additionalTags); err != nil { + return imageID, ref, fmt.Errorf("setting image names to %v: %w", append(img.Names, b.additionalTags...), err) + } + logrus.Debugf("assigned names %v to image %q", img.Names, img.ID) + } + // Report back the caller the tags applied, if any. + _, img, err = storageTransport.ResolveReference(dest) + if err != nil { + return imageID, ref, fmt.Errorf("locating just-written image %q: %w", transports.ImageName(dest), err) + } + for _, name := range img.Names { + fmt.Fprintf(b.out, "Successfully tagged %s\n", name) + } + + default: + if len(b.additionalTags) > 0 { + b.logger.Warnf("don't know how to add tags to images stored in %q transport", dest.Transport().Name()) + } + } + } + + if err := cleanup(); err != nil { + return "", nil, err + } + logrus.Debugf("printing final image id %q", imageID) + if b.iidfile != "" { + iid := imageID + if iid != "" { + iid = "sha256:" + iid // only prepend a digest algorithm name if we actually got a value back + } + if err = os.WriteFile(b.iidfile, []byte(iid), 0o644); err != nil { + return imageID, ref, fmt.Errorf("failed to write image ID to file %q: %w", b.iidfile, err) + } + } else { + if _, err := stdout.Write([]byte(imageID + "\n")); err != nil { + return imageID, ref, fmt.Errorf("failed to write image ID to stdout: %w", err) + } + } + return imageID, ref, nil +} + +// deleteSuccessfulIntermediateCtrs goes through the container IDs in each +// stage's containerIDs list and deletes the containers associated with those +// IDs. +func (b *Executor) deleteSuccessfulIntermediateCtrs() error { + var lastErr error + for _, s := range b.stages { + for _, ctr := range s.containerIDs { + if err := b.store.DeleteContainer(ctr); err != nil { + b.logger.Errorf("error deleting build container %q: %v\n", ctr, err) + lastErr = err + } + } + // The stages map includes some stages under multiple keys, so + // clearing their lists after we process a given stage is + // necessary to avoid triggering errors that would occur if we + // tried to delete a given stage's containers multiple times. + s.containerIDs = nil + } + return lastErr +} diff --git a/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go new file mode 100644 index 00000000..7a57d80e --- /dev/null +++ b/vendor/github.com/containers/buildah/imagebuildah/stage_executor.go @@ -0,0 +1,2671 @@ +package imagebuildah + +import ( + "context" + "crypto/sha256" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "slices" + "strconv" + "strings" + "time" + + "github.com/containers/buildah" + "github.com/containers/buildah/copier" + "github.com/containers/buildah/define" + buildahdocker "github.com/containers/buildah/docker" + "github.com/containers/buildah/internal" + "github.com/containers/buildah/internal/tmpdir" + internalUtil "github.com/containers/buildah/internal/util" + "github.com/containers/buildah/pkg/parse" + "github.com/containers/buildah/pkg/rusage" + "github.com/containers/buildah/util" + docker "github.com/fsouza/go-dockerclient" + buildkitparser "github.com/moby/buildkit/frontend/dockerfile/parser" + digest "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/openshift/imagebuilder" + "github.com/openshift/imagebuilder/dockerfile/command" + "github.com/openshift/imagebuilder/dockerfile/parser" + "github.com/sirupsen/logrus" + config "go.podman.io/common/pkg/config" + cp "go.podman.io/image/v5/copy" + imagedocker "go.podman.io/image/v5/docker" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/manifest" + is "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/transports" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/chrootarchive" + "go.podman.io/storage/pkg/unshare" +) + +// StageExecutor bundles up what we need to know when executing one stage of a +// (possibly multi-stage) build. +// Each stage may need to produce an image to be used as the base in a later +// stage (with the last stage's image being the end product of the build), and +// it may need to leave its working container in place so that the container's +// root filesystem's contents can be used as the source for a COPY instruction +// in a later stage. +// Each stage has its own base image, so it starts with its own configuration +// and set of volumes. +// If we're naming the result of the build, only the last stage will apply that +// name to the image that it produces. +type StageExecutor struct { + ctx context.Context + systemContext *types.SystemContext + executor *Executor + log func(format string, args ...any) + index int + stages imagebuilder.Stages + name string + builder *buildah.Builder + preserved int + volumes imagebuilder.VolumeSet // list of directories which are volumes + volumeCache map[string]string // mapping from volume directories to cache archives (used by vfs method) + volumeCacheInfo map[string]os.FileInfo // mapping from volume directories to perms/datestamps to reset after restoring + mountPoint string + output string + containerIDs []string + stage *imagebuilder.Stage + didExecute bool + argsFromContainerfile []string + hasLink bool + isLastStep bool +} + +// Preserve informs the stage executor that from this point on, it needs to +// ensure that only COPY and ADD instructions can modify the contents of this +// directory or anything below it. +// When CompatVolumes is enabled, the StageExecutor handles this by caching the +// contents of directories which have been marked this way before executing a +// RUN instruction, invalidating that cache when an ADD or COPY instruction +// sets any location under the directory as the destination, and using the +// cache to reset the contents of the directory tree after processing each RUN +// instruction. +// It would be simpler if we could just mark the directory as a read-only bind +// mount of itself during Run(), but the directory is expected to be remain +// writeable while the RUN instruction is being handled, even if any changes +// made within the directory are ultimately discarded. +func (s *StageExecutor) Preserve(path string) error { + logrus.Debugf("PRESERVE %q in %q (already preserving %v)", path, s.builder.ContainerID, s.volumes) + + // Try and resolve the symlink (if one exists) + // Set archivedPath and path based on whether a symlink is found or not + var archivedPath string + if evaluated, err := copier.Eval(s.mountPoint, filepath.Join(s.mountPoint, path), copier.EvalOptions{}); err == nil { + symLink, err := filepath.Rel(s.mountPoint, evaluated) + if err != nil { + return fmt.Errorf("making evaluated path %q relative to %q: %w", evaluated, s.mountPoint, err) + } + if strings.HasPrefix(symLink, ".."+string(os.PathSeparator)) { + return fmt.Errorf("evaluated path %q was not below %q", evaluated, s.mountPoint) + } + archivedPath = evaluated + path = string(os.PathSeparator) + symLink + } else { + return fmt.Errorf("evaluating path %q: %w", path, err) + } + + // Whether or not we're caching and restoring the contents of this + // directory, we need to ensure it exists now. + const createdDirPerms = os.FileMode(0o755) + st, err := os.Stat(archivedPath) + if errors.Is(err, os.ErrNotExist) { + // Yup, we do have to create it. That means it's not in any + // cached copy of the path that covers it, so we have to + // invalidate such cached copy. + logrus.Debugf("have to create volume %q", path) + createdDirPerms := createdDirPerms + if err := copier.Mkdir(s.mountPoint, archivedPath, copier.MkdirOptions{ChmodNew: &createdDirPerms}); err != nil { + return fmt.Errorf("ensuring volume path exists: %w", err) + } + if err := s.volumeCacheInvalidate(path); err != nil { + return fmt.Errorf("ensuring volume path %q is preserved: %w", filepath.Join(s.mountPoint, path), err) + } + if st, err = os.Stat(archivedPath); err != nil { + return fmt.Errorf("checking on just-created volume path: %w", err) + } + } + if err != nil { + return fmt.Errorf("reading info cache for volume at %q: %w", path, err) + } + + if s.volumes.Covers(path) { + // This path is a subdirectory of a volume path that we're + // already preserving, so there's nothing new to be done now + // that we've ensured that it exists. + s.volumeCacheInfo[path] = st + return nil + } + + // Add the new volume path to the ones that we're tracking. + if !s.volumes.Add(path) { + // This path is not a subdirectory of a volume path that we're + // already preserving, so adding it to the list should have + // worked. + return fmt.Errorf("adding %q to the volume cache", path) + } + s.volumeCacheInfo[path] = st + + // If we're not doing save/restore, we're done, since volumeCache + // should be empty. + if s.executor.compatVolumes != types.OptionalBoolTrue { + logrus.Debugf("not doing volume save-and-restore of %q in %q", path, s.builder.ContainerID) + return nil + } + + // Decide where the cache for this volume will be stored. + s.preserved++ + cacheDir, err := s.executor.store.ContainerDirectory(s.builder.ContainerID) + if err != nil { + return fmt.Errorf("unable to locate temporary directory for container") + } + cacheFile := filepath.Join(cacheDir, fmt.Sprintf("volume%d.tar", s.preserved)) + s.volumeCache[path] = cacheFile + + // Now prune cache files for volumes that are newly supplanted by this one. + removed := []string{} + for cachedPath := range s.volumeCache { + // Walk our list of cached volumes, and check that they're + // still in the list of locations that we need to cache. + found := slices.Contains(s.volumes, cachedPath) + if !found { + // We don't need to keep this volume's cache. Make a + // note to remove it. + removed = append(removed, cachedPath) + } + } + + // Actually remove the caches that we decided to remove. + for _, cachedPath := range removed { + archivedPath := filepath.Join(s.mountPoint, cachedPath) + logrus.Debugf("no longer need cache of %q in %q", archivedPath, s.volumeCache[cachedPath]) + if err := os.Remove(s.volumeCache[cachedPath]); err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + return fmt.Errorf("removing cache of %q: %w", archivedPath, err) + } + delete(s.volumeCache, cachedPath) + } + return nil +} + +// Remove any volume cache item which will need to be re-saved because we're +// writing to part of it. +func (s *StageExecutor) volumeCacheInvalidate(path string) error { + invalidated := []string{} + for cachedPath := range s.volumeCache { + if strings.HasPrefix(path, cachedPath+string(os.PathSeparator)) { + invalidated = append(invalidated, cachedPath) + } + } + for _, cachedPath := range invalidated { + if err := os.Remove(s.volumeCache[cachedPath]); err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + return err + } + archivedPath := filepath.Join(s.mountPoint, cachedPath) + logrus.Debugf("invalidated volume cache %q for %q from %q", archivedPath, path, s.volumeCache[cachedPath]) + } + return nil +} + +// Save the contents of each of the executor's list of volumes for which we +// don't already have a cache file. +func (s *StageExecutor) volumeCacheSaveVFS() (mounts []specs.Mount, err error) { + for cachedPath, cacheFile := range s.volumeCache { + archivedPath, err := copier.Eval(s.mountPoint, filepath.Join(s.mountPoint, cachedPath), copier.EvalOptions{}) + if err != nil { + return nil, fmt.Errorf("evaluating volume path: %w", err) + } + relativePath, err := filepath.Rel(s.mountPoint, archivedPath) + if err != nil { + return nil, fmt.Errorf("converting %q into a path relative to %q: %w", archivedPath, s.mountPoint, err) + } + if strings.HasPrefix(relativePath, ".."+string(os.PathSeparator)) { + return nil, fmt.Errorf("converting %q into a path relative to %q", archivedPath, s.mountPoint) + } + _, err = os.Stat(cacheFile) + if err == nil { + logrus.Debugf("contents of volume %q are already cached in %q", archivedPath, cacheFile) + continue + } + if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("checking for presence of a cached copy of %q at %q: %w", cachedPath, cacheFile, err) + } + logrus.Debugf("caching contents of volume %q in %q", archivedPath, cacheFile) + cache, err := os.Create(cacheFile) + if err != nil { + return nil, fmt.Errorf("creating cache for volume %q: %w", archivedPath, err) + } + defer cache.Close() + rc, err := chrootarchive.Tar(archivedPath, nil, s.mountPoint) + if err != nil { + return nil, fmt.Errorf("archiving %q: %w", archivedPath, err) + } + defer rc.Close() + _, err = io.Copy(cache, rc) + if err != nil { + return nil, fmt.Errorf("archiving %q to %q: %w", archivedPath, cacheFile, err) + } + mount := specs.Mount{ + Source: archivedPath, + Destination: string(os.PathSeparator) + relativePath, + Type: "bind", + Options: []string{"private"}, + } + mounts = append(mounts, mount) + } + return nil, nil +} + +// Restore the contents of each of the executor's list of volumes. +func (s *StageExecutor) volumeCacheRestoreVFS() (err error) { + for cachedPath, cacheFile := range s.volumeCache { + archivedPath, err := copier.Eval(s.mountPoint, filepath.Join(s.mountPoint, cachedPath), copier.EvalOptions{}) + if err != nil { + return fmt.Errorf("evaluating volume path: %w", err) + } + logrus.Debugf("restoring contents of volume %q from %q", archivedPath, cacheFile) + cache, err := os.Open(cacheFile) + if err != nil { + return fmt.Errorf("restoring contents of volume %q: %w", archivedPath, err) + } + defer cache.Close() + if err := copier.Remove(s.mountPoint, archivedPath, copier.RemoveOptions{All: true}); err != nil { + return err + } + err = chrootarchive.Untar(cache, archivedPath, nil) + if err != nil { + return fmt.Errorf("extracting archive at %q: %w", archivedPath, err) + } + if st, ok := s.volumeCacheInfo[cachedPath]; ok { + if err := os.Chmod(archivedPath, st.Mode()); err != nil { + return err + } + uid := 0 + gid := 0 + if st.Sys() != nil { + uid = util.UID(st) + gid = util.GID(st) + } + if err := os.Chown(archivedPath, uid, gid); err != nil { + return err + } + if err := os.Chtimes(archivedPath, st.ModTime(), st.ModTime()); err != nil { + return err + } + } + } + return nil +} + +// Save the contents of each of the executor's list of volumes for which we +// don't already have a cache file. For overlay, we "save" and "restore" by +// using it as a lower for an overlay mount in the same location, and then +// discarding the upper. +func (s *StageExecutor) volumeCacheSaveOverlay() (mounts []specs.Mount, err error) { + for cachedPath := range s.volumeCache { + volumePath := filepath.Join(s.mountPoint, cachedPath) + mount := specs.Mount{ + Source: volumePath, + Destination: cachedPath, + Options: []string{"O", "private"}, + } + mounts = append(mounts, mount) + } + return mounts, nil +} + +// Reset the contents of each of the executor's list of volumes. +func (s *StageExecutor) volumeCacheRestoreOverlay() error { + return nil +} + +// Save the contents of each of the executor's list of volumes for which we +// don't already have a cache file. +func (s *StageExecutor) volumeCacheSave() (mounts []specs.Mount, err error) { + switch s.executor.store.GraphDriverName() { + case "overlay": + return s.volumeCacheSaveOverlay() + } + return s.volumeCacheSaveVFS() +} + +// Reset the contents of each of the executor's list of volumes. +func (s *StageExecutor) volumeCacheRestore() error { + switch s.executor.store.GraphDriverName() { + case "overlay": + return s.volumeCacheRestoreOverlay() + } + return s.volumeCacheRestoreVFS() +} + +// Copy copies data into the working tree. The "Download" field is how +// imagebuilder tells us the instruction was "ADD" and not "COPY". +func (s *StageExecutor) Copy(excludes []string, copies ...imagebuilder.Copy) error { + for _, cp := range copies { + if cp.KeepGitDir { + if cp.Download { + return errors.New("ADD --keep-git-dir is not supported") + } + return errors.New("COPY --keep-git-dir is not supported") + } + if cp.Link && s.executor.layers { + s.hasLink = true + } else if cp.Link { + s.executor.logger.Warn("--link is not supported when building without --layers, ignoring --link") + s.hasLink = false + } + if len(cp.Excludes) > 0 { + excludes = append(slices.Clone(excludes), cp.Excludes...) + } + } + s.builder.ContentDigester.Restart() + return s.performCopy(excludes, copies...) +} + +func (s *StageExecutor) performCopy(excludes []string, copies ...imagebuilder.Copy) error { + copiesExtend := []imagebuilder.Copy{} + for _, copy := range copies { + if err := s.volumeCacheInvalidate(copy.Dest); err != nil { + return err + } + var sources []string + // The From field says to read the content from another + // container. Update the ID mappings and + // all-content-comes-from-below-this-directory value. + var idMappingOptions *define.IDMappingOptions + var copyExcludes []string + stripSetuid := false + stripSetgid := false + preserveOwnership := false + contextDir := s.executor.contextDir + // If we are copying files via heredoc syntax, then + // its time to create these temporary files on host + // and copy these to container + if len(copy.Files) > 0 { + // If we are copying files from heredoc syntax, there + // maybe regular files from context as well so split and + // process them differently + if len(copy.Src) > len(copy.Files) { + regularSources := []string{} + for _, src := range copy.Src { + // If this source is not a heredoc, then it is a regular file from + // build context or from another stage (`--from=`) so treat this differently. + if !strings.HasPrefix(src, "<<") { + regularSources = append(regularSources, src) + } + } + copyEntry := copy + // Remove heredoc if any, since we are already processing them + // so create new entry with sources containing regular files + // only, since regular files can have different context then + // heredoc files. + copyEntry.Files = nil + copyEntry.Src = regularSources + copiesExtend = append(copiesExtend, copyEntry) + } + copySources := []string{} + for _, file := range copy.Files { + data := file.Data + // remove first break line added while parsing heredoc + data = strings.TrimPrefix(data, "\n") + // add breakline when heredoc ends for docker compat + data = data + "\n" + // Create separate subdir for this file. + tmpDir, err := os.MkdirTemp(parse.GetTempDir(), "buildah-heredoc") + if err != nil { + return fmt.Errorf("unable to create tmp dir for heredoc run %q: %w", parse.GetTempDir(), err) + } + defer os.RemoveAll(tmpDir) + tmpFile, err := os.Create(filepath.Join(tmpDir, path.Base(filepath.ToSlash(file.Name)))) + if err != nil { + return fmt.Errorf("unable to create tmp file for COPY instruction at %q: %w", parse.GetTempDir(), err) + } + err = tmpFile.Chmod(0o644) // 644 is consistent with buildkit + if err != nil { + tmpFile.Close() + return fmt.Errorf("unable to chmod tmp file created for COPY instruction at %q: %w", tmpFile.Name(), err) + } + defer os.Remove(tmpFile.Name()) + _, err = tmpFile.WriteString(data) + if err != nil { + tmpFile.Close() + return fmt.Errorf("unable to write contents of heredoc file at %q: %w", tmpFile.Name(), err) + } + copySources = append(copySources, filepath.Join(filepath.Base(tmpDir), filepath.Base(tmpFile.Name()))) + tmpFile.Close() + } + contextDir = parse.GetTempDir() + copy.Src = copySources + } + + if len(copy.From) > 0 && len(copy.Files) == 0 { + // If from has an argument within it, resolve it to its + // value. Otherwise just return the value found. + from, fromErr := imagebuilder.ProcessWord(copy.From, s.stage.Builder.Arguments()) + if fromErr != nil { + return fmt.Errorf("unable to resolve argument %q: %w", copy.From, fromErr) + } + var additionalBuildContext *define.AdditionalBuildContext + if foundContext, ok := s.executor.additionalBuildContexts[from]; ok { + additionalBuildContext = foundContext + } else { + // Maybe index is given in COPY --from=index + // if that's the case check if provided index + // exists and if stage short_name matches any + // additionalContext replace stage with additional + // build context. + if index, err := strconv.Atoi(from); err == nil && index >= 0 && index < s.index { + from = s.stages[index].Name + } + if foundContext, ok := s.executor.additionalBuildContexts[from]; ok { + additionalBuildContext = foundContext + } + } + if additionalBuildContext != nil { + if !additionalBuildContext.IsImage { + contextDir = additionalBuildContext.Value + if additionalBuildContext.IsURL { + // Check if following buildContext was already + // downloaded before in any other RUN step. If not + // download it and populate DownloadCache field for + // future RUN steps. + if additionalBuildContext.DownloadedCache == "" { + // additional context contains a tar file + // so download and explode tar to buildah + // temp and point context to that. + path, subdir, err := define.TempDirForURL(tmpdir.GetTempDir(), internal.BuildahExternalArtifactsDir, additionalBuildContext.Value) + if err != nil { + return fmt.Errorf("unable to download context from external source %q: %w", additionalBuildContext.Value, err) + } + // point context dir to the extracted path + contextDir = filepath.Join(path, subdir) + // populate cache for next RUN step + additionalBuildContext.DownloadedCache = contextDir + } else { + contextDir = additionalBuildContext.DownloadedCache + } + } else { + // This points to a path on the filesystem + // Check to see if there's a .containerignore + // file, update excludes for this stage before + // proceeding + buildContextExcludes, _, err := parse.ContainerIgnoreFile(additionalBuildContext.Value, "", nil) + if err != nil { + return err + } + excludes = append(excludes, buildContextExcludes...) + } + } else { + copy.From = additionalBuildContext.Value + } + } + if additionalBuildContext == nil { + if isStage, err := s.executor.waitForStage(s.ctx, from, s.stages[:s.index]); isStage && err != nil { + return err + } + if other, ok := s.executor.stages[from]; ok && other.index < s.index { + contextDir = other.mountPoint + idMappingOptions = &other.builder.IDMappingOptions + } else if builder, ok := s.executor.containerMap[copy.From]; ok { + contextDir = builder.MountPoint + idMappingOptions = &builder.IDMappingOptions + } else { + return fmt.Errorf("the stage %q has not been built", copy.From) + } + } else if additionalBuildContext.IsImage { + // Image was selected as additionalContext so only process image. + mountPoint, err := s.getImageRootfs(s.ctx, copy.From) + if err != nil { + return err + } + contextDir = mountPoint + } + // Original behaviour of buildah still stays true for COPY irrespective of additional context. + preserveOwnership = true + copyExcludes = excludes + } else { + copyExcludes = append(s.executor.excludes, excludes...) + stripSetuid = true // did this change between 18.06 and 19.03? + stripSetgid = true // did this change between 18.06 and 19.03? + } + if copy.Download { + logrus.Debugf("ADD %#v, %#v", excludes, copy) + } else { + logrus.Debugf("COPY %#v, %#v", excludes, copy) + } + for _, src := range copy.Src { + if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") { + // Source is a URL, allowed for ADD but not COPY. + if copy.Download { + sources = append(sources, src) + } else { + // returns an error to be compatible with docker + return fmt.Errorf("source can't be a URL for COPY") + } + } else { + // filepath.Join clean path so /./ is removed + if _, suffix, found := strings.Cut(src, "/./"); found && copy.Parents { + fullPath := filepath.Join(contextDir, src) + suffix = filepath.Clean(suffix) + prefix := strings.TrimSuffix(fullPath, suffix) + prefix = filepath.Clean(prefix) + src = prefix + "/./" + suffix + } else { + src = filepath.Join(contextDir, src) + } + sources = append(sources, src) + } + } + labelsAndAnnotations := s.buildMetadata(s.isLastStep, true) + options := buildah.AddAndCopyOptions{ + Chmod: copy.Chmod, + Chown: copy.Chown, + Checksum: copy.Checksum, + PreserveOwnership: preserveOwnership, + ContextDir: contextDir, + Excludes: copyExcludes, + IgnoreFile: s.executor.ignoreFile, + IDMappingOptions: idMappingOptions, + StripSetuidBit: stripSetuid, + StripSetgidBit: stripSetgid, + // The values for these next two fields are ultimately + // based on command line flags with names that sound + // much more generic. + CertPath: s.systemContext.DockerCertPath, + InsecureSkipTLSVerify: s.systemContext.DockerInsecureSkipTLSVerify, + MaxRetries: s.executor.maxPullPushRetries, + RetryDelay: s.executor.retryPullPushDelay, + Parents: copy.Parents, + Link: s.hasLink, + BuildMetadata: labelsAndAnnotations, + } + if len(copy.Files) > 0 { + // If we are copying heredoc files, we need to temporary place + // them in the context dir and then move to container via copier + // there are cases where .containerignore can have a patterns like + // '*' which can match our heredoc files so let's not set any excludes + // or IgnoreFile for this copy. + options.Excludes = nil + options.IgnoreFile = "" + } + if err := s.builder.Add(copy.Dest, copy.Download, options, sources...); err != nil { + return err + } + } + if len(copiesExtend) > 0 { + // If we found heredocs and regularfiles together + // in same statement then we produced new copies to + // process regular files separately since they need + // different context. + return s.performCopy(excludes, copiesExtend...) + } + return nil +} + +// Returns a map of StageName/ImageName:internal.StageMountDetails for the +// items in the passed-in mounts list which include a "from=" value. +func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]internal.StageMountDetails, error) { + stageMountPoints := make(map[string]internal.StageMountDetails) + for _, flag := range mountList { + if strings.Contains(flag, "from") { + tokens := strings.Split(flag, ",") + if len(tokens) < 2 { + return nil, fmt.Errorf("invalid --mount command: %s", flag) + } + for _, token := range tokens { + key, val, hasVal := strings.Cut(token, "=") + switch key { + case "from": + if !hasVal { + return nil, fmt.Errorf("unable to resolve argument for `from=`: bad argument") + } + if val == "" { + return nil, fmt.Errorf("unable to resolve argument for `from=`: from points to an empty value") + } + from, fromErr := imagebuilder.ProcessWord(val, s.stage.Builder.Arguments()) + if fromErr != nil { + return nil, fmt.Errorf("unable to resolve argument %q: %w", val, fromErr) + } + // If the value corresponds to an additional build context, + // the mount source is either either the rootfs of the image, + // the filesystem path, or a temporary directory populated + // with the contents of the URL, all in preference to any + // stage which might have the value as its name. + if additionalBuildContext, ok := s.executor.additionalBuildContexts[from]; ok { + if additionalBuildContext.IsImage { + mountPoint, err := s.getImageRootfs(s.ctx, additionalBuildContext.Value) + if err != nil { + return nil, fmt.Errorf("%s from=%s: image found with that name", flag, from) + } + // The `from` in stageMountPoints should point + // to `mountPoint` replaced from additional + // build-context. Reason: Parser will use this + // `from` to refer from stageMountPoints map later. + stageMountPoints[from] = internal.StageMountDetails{ + IsAdditionalBuildContext: true, + IsImage: true, + DidExecute: true, + MountPoint: mountPoint, + } + break + } + // Most likely this points to path on filesystem + // or external tar archive, Treat it as a stage + // nothing is different for this. So process and + // point mountPoint to path on host and it will + // be automatically handled correctly by since + // GetBindMount will honor IsStage:false while + // processing stageMountPoints. + mountPoint := additionalBuildContext.Value + if additionalBuildContext.IsURL { + // Check if following buildContext was already + // downloaded before in any other RUN step. If not + // download it and populate DownloadCache field for + // future RUN steps. + if additionalBuildContext.DownloadedCache == "" { + // additional context contains a tar file + // so download and explode tar to buildah + // temp and point context to that. + path, subdir, err := define.TempDirForURL(tmpdir.GetTempDir(), internal.BuildahExternalArtifactsDir, additionalBuildContext.Value) + if err != nil { + return nil, fmt.Errorf("unable to download context from external source %q: %w", additionalBuildContext.Value, err) + } + // point context dir to the extracted path + mountPoint = filepath.Join(path, subdir) + // populate cache for next RUN step + additionalBuildContext.DownloadedCache = mountPoint + } else { + mountPoint = additionalBuildContext.DownloadedCache + } + } + stageMountPoints[from] = internal.StageMountDetails{ + IsAdditionalBuildContext: true, + DidExecute: true, + MountPoint: mountPoint, + } + break + } + // If the source's name corresponds to the + // result of an earlier stage, wait for that + // stage to finish being built. + if isStage, err := s.executor.waitForStage(s.ctx, from, s.stages[:s.index]); isStage && err != nil { + return nil, err + } + // If the source's name is a stage, return a + // pointer to its rootfs. + if otherStage, ok := s.executor.stages[from]; ok && otherStage.index < s.index { + stageMountPoints[from] = internal.StageMountDetails{ + IsStage: true, + DidExecute: otherStage.didExecute, + MountPoint: otherStage.mountPoint, + } + break + } + // Otherwise, treat the source's name as the name of an image. + mountPoint, err := s.getImageRootfs(s.ctx, from) + if err != nil { + return nil, fmt.Errorf("%s from=%s: no stage or image found with that name", flag, from) + } + stageMountPoints[from] = internal.StageMountDetails{ + IsImage: true, + DidExecute: true, + MountPoint: mountPoint, + } + default: + continue + } + } + } + } + return stageMountPoints, nil +} + +func (s *StageExecutor) createNeededHeredocMountsForRun(files []imagebuilder.File) ([]Mount, error) { + mountResult := []Mount{} + for _, file := range files { + f, err := os.CreateTemp(parse.GetTempDir(), "buildahheredoc") + if err != nil { + return nil, err + } + if _, err := f.WriteString(file.Data); err != nil { + f.Close() + return nil, err + } + err = f.Chmod(0o755) + if err != nil { + f.Close() + return nil, err + } + // dest path is same as buildkit for compat + dest := filepath.Join("/dev/pipes/", filepath.Base(f.Name())) + mount := Mount{Destination: dest, Type: define.TypeBind, Source: f.Name(), Options: append(define.BindOptions, "rprivate", "z", "Z")} + mountResult = append(mountResult, mount) + f.Close() + } + return mountResult, nil +} + +func parseSheBang(data string) string { + lines := strings.Split(data, "\n") + if len(lines) > 2 && strings.HasPrefix(lines[1], "#!") { + shebang := strings.TrimLeft(lines[1], "#!") + return shebang + } + return "" +} + +// Run executes a RUN instruction using the stage's current working container +// as a root directory. +func (s *StageExecutor) Run(run imagebuilder.Run, config docker.Config) error { + logrus.Debugf("RUN %#v, %#v", run, config) + args := run.Args + heredocMounts := []Mount{} + if len(run.Files) > 0 { + if heredoc := buildkitparser.MustParseHeredoc(args[0]); heredoc != nil { + if strings.HasPrefix(run.Files[0].Data, "#!") || strings.HasPrefix(run.Files[0].Data, "\n#!") { + // This is a single heredoc with a shebang, so create a file + // and run it with program specified in shebang. + heredocMount, err := s.createNeededHeredocMountsForRun(run.Files) + if err != nil { + return err + } + shebangArgs := parseSheBang(run.Files[0].Data) + if shebangArgs != "" { + args = []string{shebangArgs + " " + heredocMount[0].Destination} + } else { + args = []string{heredocMount[0].Destination} + } + heredocMounts = append(heredocMounts, heredocMount...) + } else { + args = []string{run.Files[0].Data} + } + } else { + full := args[0] + for _, file := range run.Files { + full += file.Data + "\n" + file.Name + } + args = []string{full} + } + } + stageMountPoints, err := s.runStageMountPoints(run.Mounts) + if err != nil { + return err + } + if s.builder == nil { + return fmt.Errorf("no build container available") + } + stdin := s.executor.in + if stdin == nil { + devNull, err := os.Open(os.DevNull) + if err != nil { + return fmt.Errorf("opening %q for reading: %v", os.DevNull, err) + } + defer devNull.Close() + stdin = devNull + } + namespaceOptions := slices.Clone(s.executor.namespaceOptions) + options := buildah.RunOptions{ + Args: s.executor.runtimeArgs, + Cmd: config.Cmd, + ContextDir: s.executor.contextDir, + ConfigureNetwork: s.executor.configureNetwork, + Entrypoint: config.Entrypoint, + Env: config.Env, + Hostname: config.Hostname, + Logger: s.executor.logger, + Mounts: slices.Clone(s.executor.transientMounts), + NamespaceOptions: namespaceOptions, + NoHostname: s.executor.noHostname, + NoHosts: s.executor.noHosts, + NoPivot: os.Getenv("BUILDAH_NOPIVOT") != "" || s.executor.noPivotRoot, + Quiet: s.executor.quiet, + CompatBuiltinVolumes: types.OptionalBoolFalse, + RunMounts: run.Mounts, + Runtime: s.executor.runtime, + Secrets: s.executor.secrets, + SSHSources: s.executor.sshsources, + StageMountPoints: stageMountPoints, + Stderr: s.executor.err, + Stdin: stdin, + Stdout: s.executor.out, + SystemContext: s.systemContext, + Terminal: buildah.WithoutTerminal, + User: config.User, + WorkingDir: config.WorkingDir, + } + + // Honor `RUN --network=<>`. + switch run.Network { + case "host": + options.NamespaceOptions.AddOrReplace(define.NamespaceOption{Name: "network", Host: true}) + options.ConfigureNetwork = define.NetworkEnabled + case "none": + options.ConfigureNetwork = define.NetworkDisabled + case "", "default": + // do nothing + default: + return fmt.Errorf(`unsupported value %q for "RUN --network", must be either "host" or "none"`, run.Network) + } + + if config.NetworkDisabled { + options.ConfigureNetwork = buildah.NetworkDisabled + } + + if run.Shell { + if len(config.Shell) > 0 && s.builder.Format == define.Dockerv2ImageManifest { + args = append(config.Shell, args...) + } else { + args = append([]string{"/bin/sh", "-c"}, args...) + } + } + + if s.executor.compatVolumes == types.OptionalBoolTrue { + // Only bother with saving/restoring the contents of volumes if + // we've been specifically asked to. + mounts, err := s.volumeCacheSave() + if err != nil { + return err + } + options.Mounts = append(options.Mounts, mounts...) + } + + // The list of built-in volumes isn't passed in via RunOptions, so make + // sure the builder's list of built-in volumes includes anything that + // the configuration thinks is a built-in volume. + s.builder.ClearVolumes() + for v := range config.Volumes { + s.builder.AddVolume(v) + } + + if len(heredocMounts) > 0 { + options.Mounts = append(options.Mounts, heredocMounts...) + } + err = s.builder.Run(args, options) + + if s.executor.compatVolumes == types.OptionalBoolTrue { + // Only bother with saving/restoring the contents of volumes if + // we've been specifically asked to. + if err2 := s.volumeCacheRestore(); err2 != nil { + if err == nil { + return err2 + } + } + } + + return err +} + +// UnrecognizedInstruction is called when we encounter an instruction that the +// imagebuilder parser didn't understand. +func (s *StageExecutor) UnrecognizedInstruction(step *imagebuilder.Step) error { + errStr := fmt.Sprintf("Build error: Unknown instruction: %q ", strings.ToUpper(step.Command)) + err := fmt.Sprintf(errStr+"%#v", step) + if s.executor.ignoreUnrecognizedInstructions { + logrus.Debug(err) + return nil + } + + switch logrus.GetLevel() { + case logrus.ErrorLevel: + s.executor.logger.Error(errStr) + case logrus.DebugLevel: + logrus.Debug(err) + default: + s.executor.logger.Errorf("+(UNHANDLED LOGLEVEL) %#v", step) + } + + return errors.New(err) +} + +// prepare creates a working container based on the specified image, or if one +// isn't specified, the first argument passed to the first FROM instruction we +// can find in the stage's parsed tree. +func (s *StageExecutor) prepare(ctx context.Context, from string, initializeIBConfig, rebase, preserveBaseImageAnnotations bool, pullPolicy define.PullPolicy) (builder *buildah.Builder, err error) { + stage := s.stage + ib := stage.Builder + node := stage.Node + + if from == "" { + base, err := ib.From(node) + if err != nil { + logrus.Debugf("prepare(node.Children=%#v)", node.Children) + return nil, fmt.Errorf("determining starting point for build: %w", err) + } + from = base + } + displayFrom := from + if ib.Platform != "" { + displayFrom = "--platform=" + ib.Platform + " " + displayFrom + } + + // stage.Name will be a numeric string for all stages without an "AS" clause + asImageName := stage.Name + if asImageName != "" { + if _, err := strconv.Atoi(asImageName); err != nil { + displayFrom += " AS " + asImageName + } + } + + if initializeIBConfig && rebase { + logrus.Debugf("FROM %#v", displayFrom) + if !s.executor.quiet { + s.log("FROM %s", displayFrom) + } + } + + // In a multi-stage build where `FROM --platform=<>` was used then we must + // reset context for new stages so that new stages don't inherit unexpected + // `--platform` from prior stages. + if stage.Builder.Platform != "" || (len(s.stages) > 1 && (s.systemContext.ArchitectureChoice == "" && s.systemContext.VariantChoice == "" && s.systemContext.OSChoice == "")) { + imageOS, imageArch, imageVariant, err := parse.Platform(stage.Builder.Platform) + if err != nil { + return nil, fmt.Errorf("unable to parse platform %q: %w", stage.Builder.Platform, err) + } + if imageArch != "" || imageVariant != "" { + s.systemContext.ArchitectureChoice = imageArch + s.systemContext.VariantChoice = imageVariant + } + if imageOS != "" { + s.systemContext.OSChoice = imageOS + } + } + + builderOptions := buildah.BuilderOptions{ + Args: ib.Args, + FromImage: from, + GroupAdd: s.executor.groupAdd, + PullPolicy: pullPolicy, + ContainerSuffix: s.executor.containerSuffix, + Registry: s.executor.registry, + BlobDirectory: s.executor.blobDirectory, + SignaturePolicyPath: s.executor.signaturePolicyPath, + ReportWriter: s.executor.reportWriter, + SystemContext: s.systemContext, + Isolation: s.executor.isolation, + NamespaceOptions: s.executor.namespaceOptions, + ConfigureNetwork: s.executor.configureNetwork, + CNIPluginPath: s.executor.cniPluginPath, + CNIConfigDir: s.executor.cniConfigDir, + NetworkInterface: s.executor.networkInterface, + IDMappingOptions: s.executor.idmappingOptions, + CommonBuildOpts: s.executor.commonBuildOptions, + DefaultMountsFilePath: s.executor.defaultMountsFilePath, + Format: s.executor.outputFormat, + Capabilities: s.executor.capabilities, + Devices: s.executor.devices, + DeviceSpecs: s.executor.deviceSpecs, + MaxPullRetries: s.executor.maxPullPushRetries, + PullRetryDelay: s.executor.retryPullPushDelay, + OciDecryptConfig: s.executor.ociDecryptConfig, + Logger: s.executor.logger, + ProcessLabel: s.executor.processLabel, + MountLabel: s.executor.mountLabel, + PreserveBaseImageAnns: preserveBaseImageAnnotations, + CDIConfigDir: s.executor.cdiConfigDir, + CompatScratchConfig: s.executor.compatScratchConfig, + } + + builder, err = buildah.NewBuilder(ctx, s.executor.store, builderOptions) + if err != nil { + return nil, fmt.Errorf("creating build container: %w", err) + } + + // If executor's ProcessLabel and MountLabel is empty means this is the first stage + // Make sure we share first stage's ProcessLabel and MountLabel with all other subsequent stages + // Doing this will ensure and one stage in same build can mount another stage even if `selinux` + // is enabled. + + if s.executor.mountLabel == "" && s.executor.processLabel == "" { + s.executor.mountLabel = builder.MountLabel + s.executor.processLabel = builder.ProcessLabel + } + + if initializeIBConfig { + volumes := map[string]struct{}{} + for _, v := range builder.Volumes() { + volumes[v] = struct{}{} + } + ports := map[docker.Port]struct{}{} + for _, p := range builder.Ports() { + ports[docker.Port(p)] = struct{}{} + } + hostname, domainname := builder.Hostname(), builder.Domainname() + containerName := builder.Container + if s.executor.timestamp != nil || s.executor.sourceDateEpoch != nil { + hostname, domainname, containerName = "sandbox", "", "" + } + dConfig := docker.Config{ + Hostname: hostname, + Domainname: domainname, + User: builder.User(), + Env: builder.Env(), + Cmd: builder.Cmd(), + Image: from, + Volumes: volumes, + WorkingDir: builder.WorkDir(), + Entrypoint: builder.Entrypoint(), + Healthcheck: (*docker.HealthConfig)(builder.Healthcheck()), + Labels: builder.Labels(), + Shell: builder.Shell(), + StopSignal: builder.StopSignal(), + OnBuild: builder.OnBuild(), + ExposedPorts: ports, + } + var rootfs *docker.RootFS + if builder.Docker.RootFS != nil { + rootfs = &docker.RootFS{ + Type: builder.Docker.RootFS.Type, + } + for _, id := range builder.Docker.RootFS.DiffIDs { + rootfs.Layers = append(rootfs.Layers, id.String()) + } + } + dImage := docker.Image{ + Parent: builder.FromImageID, + ContainerConfig: dConfig, + Container: containerName, + Author: builder.Maintainer(), + Architecture: builder.Architecture(), + RootFS: rootfs, + } + dImage.Config = &dImage.ContainerConfig + if s.executor.inheritLabels == types.OptionalBoolFalse { + // If user has selected `--inherit-labels=false` let's not + // inherit labels from base image. + dImage.Config.Labels = nil + } + err = ib.FromImage(&dImage, node) + if err != nil { + if err2 := builder.Delete(); err2 != nil { + logrus.Debugf("error deleting container which we failed to update: %v", err2) + } + return nil, fmt.Errorf("updating build context: %w", err) + } + } + mountPoint, err := builder.Mount(builder.MountLabel) + if err != nil { + if err2 := builder.Delete(); err2 != nil { + logrus.Debugf("error deleting container which we failed to mount: %v", err2) + } + return nil, fmt.Errorf("mounting new container: %w", err) + } + if rebase { + // Make this our "current" working container. + s.mountPoint = mountPoint + s.builder = builder + // Now that the rootfs is mounted, set up handling of volumes from the base image. + s.volumes = make([]string, 0, len(s.volumes)) + s.volumeCache = make(map[string]string) + s.volumeCacheInfo = make(map[string]os.FileInfo) + for _, v := range builder.Volumes() { + if err := s.Preserve(v); err != nil { + return nil, fmt.Errorf("marking base image volume %q for preservation: %w", v, err) + } + } + } + logrus.Debugln("Container ID:", builder.ContainerID) + return builder, nil +} + +// Delete deletes the stage's working container, if we have one. +func (s *StageExecutor) Delete() (err error) { + if s.builder != nil { + err = s.builder.Delete() + s.builder = nil + } + return err +} + +// stepRequiresLayer indicates whether or not the step should be followed by +// committing a layer container when creating an intermediate image. +func (*StageExecutor) stepRequiresLayer(step *imagebuilder.Step) bool { + switch strings.ToUpper(step.Command) { + case "ADD", "COPY", "RUN": + return true + } + return false +} + +// getImageRootfs checks for an image matching the passed-in name in local +// storage. If it isn't found, it pulls down a copy. Then, if we don't have a +// working container root filesystem based on the image, it creates one. Then +// it returns that root filesystem's location. +func (s *StageExecutor) getImageRootfs(ctx context.Context, image string) (mountPoint string, err error) { + if builder, ok := s.executor.containerMap[image]; ok { + return builder.MountPoint, nil + } + builder, err := s.prepare(ctx, image, false, false, false, s.executor.pullPolicy) + if err != nil { + return "", err + } + s.executor.containerMap[image] = builder + return builder.MountPoint, nil +} + +// getContentSummary generates a description of what was most recently added to the container, +// typically in the form "file", "dir", or "multi" followed by a colon and the hex part of the +// digest of the content, for inclusion in the corresponding history entry's "createdBy" field +func (s *StageExecutor) getContentSummaryAfterAddingContent() string { + contentType, digest := s.builder.ContentDigester.Digest() + summary := contentType + if digest != "" { + if summary != "" { + summary = summary + ":" + } + summary = summary + digest.Encoded() + logrus.Debugf("added content %s", summary) + } + return summary +} + +// Execute runs each of the steps in the stage's parsed tree, in turn. +func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string, ref reference.Canonical, onlyBaseImg bool, err error) { + var resourceUsage rusage.Rusage + stage := s.stage + ib := stage.Builder + checkForLayers := s.executor.layers && s.executor.useCache + moreStages := s.index < len(s.stages)-1 + lastStage := !moreStages + onlyBaseImage := false + imageIsUsedLater := moreStages && (internalUtil.SetHas(s.executor.baseMap, stage.Name) || internalUtil.SetHas(s.executor.baseMap, strconv.Itoa(stage.Position))) + rootfsIsUsedLater := moreStages && (internalUtil.SetHas(s.executor.rootfsMap, stage.Name) || internalUtil.SetHas(s.executor.rootfsMap, strconv.Itoa(stage.Position))) + + // If the base image's name corresponds to the result of an earlier + // stage, make sure that stage has finished building an image, and + // substitute that image's ID for the base image's name here and force + // the pull policy to "never" to avoid triggering an error when it's + // set to "always", which doesn't make sense for image IDs. + // If not, then go on assuming that it's just a regular image that's + // either in local storage, or one that we have to pull from a + // registry, subject to the passed-in pull policy. + if isStage, err := s.executor.waitForStage(ctx, base, s.stages[:s.index]); isStage && err != nil { + return "", nil, false, err + } + pullPolicy := s.executor.pullPolicy + s.executor.stagesLock.Lock() + var preserveBaseImageAnnotationsAtStageStart bool + if stageImage, isPreviousStage := s.executor.imageMap[base]; isPreviousStage { + base = stageImage + pullPolicy = define.PullNever + preserveBaseImageAnnotationsAtStageStart = true + } + s.executor.stagesLock.Unlock() + + // Set things up so that we can log resource usage as we go. + logRusage := func() { + if rusage.Supported() { + usage, err := rusage.Get() + if err != nil { + fmt.Fprintf(s.executor.out, "error gathering resource usage information: %v\n", err) + return + } + if s.executor.rusageLogFile != nil { + fmt.Fprintf(s.executor.rusageLogFile, "%s\n", rusage.FormatDiff(usage.Subtract(resourceUsage))) + } + resourceUsage = usage + } + } + + // Start counting resource usage before we potentially pull a base image. + if rusage.Supported() { + if resourceUsage, err = rusage.Get(); err != nil { + return "", nil, false, err + } + // Log the final incremental resource usage counter before we return. + defer logRusage() + } + + // Create the (first) working container for this stage. Reinitializing + // the imagebuilder configuration may alter the list of steps we have, + // so take a snapshot of them *after* that. + if _, err := s.prepare(ctx, base, true, true, preserveBaseImageAnnotationsAtStageStart, pullPolicy); err != nil { + return "", nil, false, err + } + children := stage.Node.Children + + // A helper function to only log "COMMIT" as an explicit step if it's + // the very last step of a (possibly multi-stage) build. + logCommit := func(output string, instruction int) { + moreInstructions := instruction < len(children)-1 + if moreInstructions || moreStages { + return + } + commitMessage := "COMMIT" + if output != "" { + commitMessage = fmt.Sprintf("%s %s", commitMessage, output) + } + logrus.Debug(commitMessage) + if !s.executor.quiet { + s.log(commitMessage) + } + } + // logCachePulled produces build log for cases when `--cache-from` + // is used and a valid intermediate image is pulled from remote source. + logCachePulled := func(cacheKey string, remote reference.Named) { + if !s.executor.quiet { + cachePullMessage := "--> Cache pulled from remote" + fmt.Fprintf(s.executor.out, "%s %s\n", cachePullMessage, fmt.Sprintf("%s:%s", remote.String(), cacheKey)) + } + } + // logCachePush produces build log for cases when `--cache-to` + // is used and a valid intermediate image is pushed tp remote source. + logCachePush := func(cacheKey string) { + if !s.executor.quiet { + cachePushMessage := "--> Pushing cache" + fmt.Fprintf(s.executor.out, "%s %s\n", cachePushMessage, fmt.Sprintf("%s:%s", s.executor.cacheTo, cacheKey)) + } + } + logCacheHit := func(cacheID string) { + if !s.executor.quiet { + cacheHitMessage := "--> Using cache" + fmt.Fprintf(s.executor.out, "%s %s\n", cacheHitMessage, cacheID) + } + } + logImageID := func(imgID string) { + if len(imgID) > 12 { + imgID = imgID[:12] + } + if s.executor.iidfile == "" { + fmt.Fprintf(s.executor.out, "--> %s\n", imgID) + } + } + + // Parse and populate buildOutputOption if needed + var buildOutputOptions []define.BuildOutputOption + if lastStage && len(s.executor.buildOutputs) > 0 { + for _, buildOutput := range s.executor.buildOutputs { + logrus.Debugf("generating custom build output with options %q", buildOutput) + buildOutputOption, err := parse.GetBuildOutput(buildOutput) + if err != nil { + return "", nil, false, fmt.Errorf("failed to parse build output %q: %w", buildOutput, err) + } + buildOutputOptions = append(buildOutputOptions, buildOutputOption) + } + } + + if len(children) == 0 { + // There are no steps. + if s.builder.FromImageID == "" || s.executor.squash || s.executor.confidentialWorkload.Convert || len(s.executor.annotations) > 0 || len(s.executor.unsetEnvs) > 0 || len(s.executor.unsetLabels) > 0 || len(s.executor.sbomScanOptions) > 0 || len(s.executor.unsetAnnotations) > 0 || s.executor.inheritLabels == types.OptionalBoolFalse || s.executor.inheritAnnotations == types.OptionalBoolFalse { + // We either don't have a base image, or we need to + // transform the contents of the base image, or we need + // to make some changes to just the config blob. Whichever + // is the case, we need to commit() to create a new image. + logCommit(s.output, -1) + // No base image means there's nothing to put in a + // layer, so don't create one. + emptyLayer := (s.builder.FromImageID == "") + createdBy, err := s.getCreatedBy(nil, "", lastStage) + if err != nil { + return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err) + } + if imgID, ref, err = s.commit(ctx, createdBy, emptyLayer, s.output, s.executor.squash || s.executor.confidentialWorkload.Convert, lastStage); err != nil { + return "", nil, false, fmt.Errorf("committing base container: %w", err) + } + } else { + // We don't need to squash or otherwise transform the + // base image, and the image wouldn't be modified by + // the command line options, so just reuse the base + // image. + logCommit(s.output, -1) + if imgID, ref, err = s.tagExistingImage(ctx, s.builder.FromImageID, s.output); err != nil { + return "", nil, onlyBaseImage, err + } + onlyBaseImage = true + } + // Generate build output from the new image, or the preexisting + // one if we didn't actually do anything, if needed. + for _, buildOutputOption := range buildOutputOptions { + if err := s.generateBuildOutput(buildOutputOption); err != nil { + return "", nil, onlyBaseImage, err + } + } + logImageID(imgID) + } + + for i, node := range children { + logRusage() + moreInstructions := i < len(children)-1 + lastInstruction := !moreInstructions + + s.isLastStep = lastStage && lastInstruction + // Resolve any arguments in this instruction. + step := ib.Step() + if err := step.Resolve(node); err != nil { + return "", nil, false, fmt.Errorf("resolving step %+v: %w", *node, err) + } + logrus.Debugf("Parsed Step: %+v", *step) + if !s.executor.quiet { + logMsg := step.Original + if len(step.Heredocs) > 0 { + summarizeHeredoc := func(doc string) string { + doc = strings.TrimSpace(doc) + lines := strings.Split(strings.ReplaceAll(doc, "\r\n", "\n"), "\n") + summary := lines[0] + if len(lines) > 1 { + summary += "..." + } + return summary + } + + for _, doc := range node.Heredocs { + heredocContent := summarizeHeredoc(doc.Content) + logMsg = logMsg + " (" + heredocContent + ")" + } + } + s.log("%s", logMsg) + } + + // Check if there's a --from if the step command is COPY. + // Also check the chmod and the chown flags for validity. + for _, flag := range step.Flags { + command := strings.ToUpper(step.Command) + // chmod, chown and from flags should have an '=' sign, '--chmod=', '--chown=' or '--from=' or '--exclude=' + if command == "COPY" && (flag == "--chmod" || flag == "--chown" || flag == "--from" || flag == "--exclude") { + return "", nil, false, fmt.Errorf("COPY only supports the --chmod= --chown= --from= and the --exclude= flags") + } + if command == "ADD" && (flag == "--chmod" || flag == "--chown" || flag == "--checksum" || flag == "--exclude") { + return "", nil, false, fmt.Errorf("ADD only supports the --chmod=, --chown=, and --checksum= --exclude= flags") + } + if strings.Contains(flag, "--from") && command == "COPY" { + arr := strings.Split(flag, "=") + if len(arr) != 2 { + return "", nil, false, fmt.Errorf("%s: invalid --from flag %q, should be --from=", command, flag) + } + // If arr[1] has an argument within it, resolve it to its + // value. Otherwise just return the value found. + from, fromErr := imagebuilder.ProcessWord(arr[1], s.stage.Builder.Arguments()) + if fromErr != nil { + return "", nil, false, fmt.Errorf("unable to resolve argument %q: %w", arr[1], fromErr) + } + + // Before looking into additional context + // also account if the index is given instead + // of name so convert index in --from= + // to name. + if index, err := strconv.Atoi(from); err == nil && index >= 0 && index < s.index { + from = s.stages[index].Name + } + // If additional buildContext contains this + // give priority to that and break if additional + // is not an external image. + if additionalBuildContext, ok := s.executor.additionalBuildContexts[from]; ok { + if !additionalBuildContext.IsImage { + // We don't need to pull this + // since this additional context + // is not an image. + break + } + // replace with image set in build context + from = additionalBuildContext.Value + if _, err := s.getImageRootfs(ctx, from); err != nil { + return "", nil, false, fmt.Errorf("%s --from=%s: no stage or image found with that name", command, from) + } + break + } + + // If the source's name corresponds to the + // result of an earlier stage, wait for that + // stage to finish being built. + if isStage, err := s.executor.waitForStage(ctx, from, s.stages[:s.index]); isStage && err != nil { + return "", nil, false, err + } + if otherStage, ok := s.executor.stages[from]; ok && otherStage.index < s.index { + break + } else if _, err = s.getImageRootfs(ctx, from); err != nil { + return "", nil, false, fmt.Errorf("%s --from=%s: no stage or image found with that name", command, from) + } + break + } + } + + // Determine if there are any RUN instructions to be run after + // this step. If not, we won't have to bother preserving the + // contents of any volumes declared between now and when we + // finish. + noRunsRemaining := false + if moreInstructions { + noRunsRemaining = !ib.RequiresStart(&parser.Node{Children: children[i+1:]}) + } + + // If we're doing a single-layer build, just process the + // instruction. + if !s.executor.layers { + s.didExecute = true + err := ib.Run(step, s, noRunsRemaining) + if err != nil { + logrus.Debugf("Error building at step %+v: %v", *step, err) + return "", nil, false, fmt.Errorf("building at STEP \"%s\": %w", step.Message, err) + } + // In case we added content, retrieve its digest. + addedContentSummary := s.getContentSummaryAfterAddingContent() + if moreInstructions { + // There are still more instructions to process + // for this stage. Make a note of the + // instruction in the history that we'll write + // for the image when we eventually commit it. + timestamp := time.Now().UTC() + if s.executor.timestamp != nil { + timestamp = *s.executor.timestamp + } + createdBy, err := s.getCreatedBy(node, addedContentSummary, false) + if err != nil { + return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err) + } + s.builder.AddPrependedEmptyLayer(×tamp, createdBy, "", "") + continue + } + // This is the last instruction for this stage, + // so we should commit this container to create + // an image, but only if it's the last stage, + // or if it's used as the basis for a later + // stage. + if lastStage || imageIsUsedLater { + logCommit(s.output, i) + createdBy, err := s.getCreatedBy(node, addedContentSummary, lastStage && lastInstruction) + if err != nil { + return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err) + } + imgID, ref, err = s.commit(ctx, createdBy, false, s.output, s.executor.squash, lastStage && lastInstruction) + if err != nil { + return "", nil, false, fmt.Errorf("committing container for step %+v: %w", *step, err) + } + logImageID(imgID) + // Generate build output if needed. + for _, buildOutputOption := range buildOutputOptions { + if err := s.generateBuildOutput(buildOutputOption); err != nil { + return "", nil, false, err + } + } + } else { + imgID = "" + } + break + } + + // We're in a multi-layered build. + s.didExecute = false + var ( + commitName string + cacheID string + cacheKey string + pulledAndUsedCacheImage bool + err error + rebase bool + addedContentSummary string + canMatchCacheOnlyAfterRun bool + ) + + // Only attempt to find cache if its needed, this part is needed + // so that if a step is using RUN --mount and mounts content from + // previous stages then it uses the freshly built stage instead + // of re-using the older stage from the store. + avoidLookingCache := false + var mounts []string + for _, a := range node.Flags { + arg, err := imagebuilder.ProcessWord(a, s.stage.Builder.Arguments()) + if err != nil { + return "", nil, false, err + } + switch { + case strings.HasPrefix(arg, "--mount="): + mount := strings.TrimPrefix(arg, "--mount=") + mounts = append(mounts, mount) + default: + continue + } + } + stageMountPoints, err := s.runStageMountPoints(mounts) + if err != nil { + return "", nil, false, err + } + for _, mountPoint := range stageMountPoints { + if mountPoint.DidExecute && mountPoint.IsStage { + avoidLookingCache = true + } + } + + needsCacheKey := (len(s.executor.cacheFrom) != 0 && !avoidLookingCache) || len(s.executor.cacheTo) != 0 + + // If we have to commit for this instruction, only assign the + // stage's configured output name to the last layer. + if lastInstruction { + commitName = s.output + } + + // If --cache-from or --cache-to is specified make sure to populate + // cacheKey since it will be used either while pulling or pushing the + // cache images. + if needsCacheKey { + cacheKey, err = s.generateCacheKey(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage) + if err != nil { + return "", nil, false, fmt.Errorf("failed while generating cache key: %w", err) + } + } + // Check if there's already an image based on our parent that + // has the same change that we're about to make, so far as we + // can tell. + // Only do this if the step we are on is not an ARG step, + // we need to call ib.Run() to correctly put the args together before + // determining if a cached layer with the same build args already exists + // and that is done in the if block below. + if checkForLayers && step.Command != "arg" && (!s.executor.squash || !lastInstruction || !lastStage) && !avoidLookingCache { + // For `COPY` and `ADD`, history entries include digests computed from + // the content that's copied in. We need to compute that information so that + // it can be used to evaluate the cache, which means we need to go ahead + // and copy the content. + canMatchCacheOnlyAfterRun = (step.Command == command.Add || step.Command == command.Copy) + if canMatchCacheOnlyAfterRun { + if err = ib.Run(step, s, noRunsRemaining); err != nil { + logrus.Debugf("Error building at step %+v: %v", *step, err) + return "", nil, false, fmt.Errorf("building at STEP \"%s\": %w", step.Message, err) + } + // Retrieve the digest info for the content that we just copied + // into the rootfs. + addedContentSummary = s.getContentSummaryAfterAddingContent() + // regenerate cache key with updated content summary + if needsCacheKey { + cacheKey, err = s.generateCacheKey(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage) + if err != nil { + return "", nil, false, fmt.Errorf("failed while generating cache key: %w", err) + } + } + } + cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage) + if err != nil { + return "", nil, false, fmt.Errorf("checking if cached image exists from a previous build: %w", err) + } + // All the best effort to find cache on localstorage have failed try pulling + // cache from remote repo if `--cache-from` was configured. + if cacheID == "" && len(s.executor.cacheFrom) != 0 { + // only attempt to use cache again if pulling was successful + // otherwise do nothing and attempt to run the step, err != nil + // is ignored and will be automatically logged for --log-level debug + if ref, id, err := s.pullCache(ctx, cacheKey); ref != nil && id != "" && err == nil { + logCachePulled(cacheKey, ref) + cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage) + if err != nil { + return "", nil, false, fmt.Errorf("checking if cached image exists from a previous build: %w", err) + } + if cacheID != "" { + pulledAndUsedCacheImage = true + } + } + } + if canMatchCacheOnlyAfterRun && cacheID == "" { + s.didExecute = true + } + } + + // If we didn't find a cache entry, or we need to add content + // to find the digest of the content to check for a cached + // image, run the step so that we can check if the result + // matches a cache. + // We already called ib.Run() for the `canMatchCacheOnlyAfterRun` + // cases above, so we shouldn't do it again. + if cacheID == "" && !canMatchCacheOnlyAfterRun { + // Process the instruction directly. + s.didExecute = true + if err = ib.Run(step, s, noRunsRemaining); err != nil { + logrus.Debugf("Error building at step %+v: %v", *step, err) + return "", nil, false, fmt.Errorf("building at STEP \"%s\": %w", step.Message, err) + } + + // In case we added content, retrieve its digest. + addedContentSummary = s.getContentSummaryAfterAddingContent() + // regenerate cache key with updated content summary + if needsCacheKey { + cacheKey, err = s.generateCacheKey(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage) + if err != nil { + return "", nil, false, fmt.Errorf("failed while generating cache key: %w", err) + } + } + + // Check if there's already an image based on our parent that + // has the same change that we just made. + if checkForLayers && !avoidLookingCache { + cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage) + if err != nil { + return "", nil, false, fmt.Errorf("checking if cached image exists from a previous build: %w", err) + } + // All the best effort to find cache on localstorage have failed try pulling + // cache from remote repo if `--cache-from` was configured and cacheKey was + // generated again after adding content summary. + if cacheID == "" && len(s.executor.cacheFrom) != 0 { + // only attempt to use cache again if pulling was successful + // otherwise do nothing and attempt to run the step, err != nil + // is ignored and will be automatically logged for --log-level debug + if ref, id, err := s.pullCache(ctx, cacheKey); ref != nil && id != "" && err == nil { + logCachePulled(cacheKey, ref) + cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage) + if err != nil { + return "", nil, false, fmt.Errorf("checking if cached image exists from a previous build: %w", err) + } + if cacheID != "" { + pulledAndUsedCacheImage = true + } + } + } + } + } else { + // This log line is majorly here so we can verify in tests + // that our cache is performing in the most optimal way for + // various cases. + logrus.Debugf("Found a cache hit in the first iteration with id %s", cacheID) + // If the instruction would affect our configuration, + // process the configuration change so that, if we fall + // off the cache path, the filesystem changes from the + // last cache image will be all that we need, since we + // still don't want to restart using the image's + // configuration blob. + if !s.stepRequiresLayer(step) { + s.didExecute = true + err := ib.Run(step, s, noRunsRemaining) + if err != nil { + logrus.Debugf("Error building at step %+v: %v", *step, err) + return "", nil, false, fmt.Errorf("building at STEP \"%s\": %w", step.Message, err) + } + } + } + + // Note: If the build has squash, we must try to reuse as many layers as possible if cache is found. + // So only perform commit if it's the lastInstruction of lastStage. + if cacheID != "" { + logCacheHit(cacheID) + // A suitable cached image was found, so we can just + // reuse it. If we need to add a name to the resulting + // image because it's the last step in this stage, add + // the name to the image. + imgID = cacheID + if commitName != "" { + logCommit(commitName, i) + if imgID, ref, err = s.tagExistingImage(ctx, cacheID, commitName); err != nil { + return "", nil, false, err + } + } + } else { + // We're not going to find any more cache hits, so we + // can stop looking for them. + checkForLayers = false + createdBy, err := s.getCreatedBy(node, addedContentSummary, lastStage && lastInstruction) + if err != nil { + return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err) + } + // Create a new image, maybe with a new layer, with the + // name for this stage if it's the last instruction. + logCommit(s.output, i) + // While committing we always set squash to false here + // because at this point we want to save history for + // layers even if its a squashed build so that they + // can be part of the build cache. + imgID, ref, err = s.commit(ctx, createdBy, !s.stepRequiresLayer(step), commitName, false, lastStage && lastInstruction) + if err != nil { + return "", nil, false, fmt.Errorf("committing container for step %+v: %w", *step, err) + } + // Generate build output if needed. + for _, buildOutputOption := range buildOutputOptions { + if err := s.generateBuildOutput(buildOutputOption); err != nil { + return "", nil, false, err + } + } + } + + // Following step is just built and was not used from + // cache so check if --cache-to was specified if yes + // then attempt pushing this cache to remote repo and + // fail accordingly. + // + // Or + // + // Try to push this cache to remote repository only + // if cache was present on local storage and not + // pulled from remote source while processing this + if len(s.executor.cacheTo) != 0 && (!pulledAndUsedCacheImage || cacheID == "") && needsCacheKey { + logCachePush(cacheKey) + if err = s.pushCache(ctx, imgID, cacheKey); err != nil { + return "", nil, false, err + } + } + + if lastInstruction && lastStage { + if s.executor.squash || s.executor.confidentialWorkload.Convert || len(s.executor.sbomScanOptions) != 0 { + createdBy, err := s.getCreatedBy(node, addedContentSummary, lastStage && lastInstruction) + if err != nil { + return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err) + } + // If this is the last instruction of the last stage, + // create a squashed or confidential workload + // version of the image if that's what we're after, + // or a normal one if we need to scan the image while + // committing it. + imgID, ref, err = s.commit(ctx, createdBy, !s.stepRequiresLayer(step), commitName, s.executor.squash || s.executor.confidentialWorkload.Convert, lastStage && lastInstruction) + if err != nil { + return "", nil, false, fmt.Errorf("committing final squash step %+v: %w", *step, err) + } + // Generate build output if needed. + for _, buildOutputOption := range buildOutputOptions { + if err := s.generateBuildOutput(buildOutputOption); err != nil { + return "", nil, false, err + } + } + } else if cacheID != "" { + // If we found a valid cache hit and this is lastStage + // and not a squashed build then there is no opportunity + // for us to perform a `commit` later in the code since + // everything will be used from cache. + // + // If above statement is true and --output was provided + // then generate output manually since there is no opportunity + // for us to perform `commit` anywhere in the code. + // Generate build output if needed. + for _, buildOutputOption := range buildOutputOptions { + if err := s.generateBuildOutput(buildOutputOption); err != nil { + return "", nil, false, err + } + } + } + } + + logImageID(imgID) + + // Update our working container to be based off of the cached + // image, if we might need to use it as a basis for the next + // instruction, or if we need the root filesystem to match the + // image contents for the sake of a later stage that wants to + // copy content from it. + rebase = moreInstructions || rootfsIsUsedLater + + if rebase { + // Since we either committed the working container or + // are about to replace it with one based on a cached + // image, add the current working container's ID to the + // list of successful intermediate containers that + // we'll clean up later. + s.containerIDs = append(s.containerIDs, s.builder.ContainerID) + + // Prepare for the next step or subsequent phases by + // creating a new working container with the + // just-committed or updated cached image as its new + // base image. + // Enforce pull "never" since we already have an image + // ID that we really should not be pulling anymore (see + // containers/podman/issues/10307). + if _, err := s.prepare(ctx, imgID, false, true, true, define.PullNever); err != nil { + return "", nil, false, fmt.Errorf("preparing container for next step: %w", err) + } + } + + s.hasLink = false + } + + return imgID, ref, onlyBaseImage, nil +} + +func historyEntriesEqual(base, derived v1.History) bool { + if base.CreatedBy != derived.CreatedBy { + return false + } + if base.Comment != derived.Comment { + return false + } + if base.Author != derived.Author { + return false + } + if base.EmptyLayer != derived.EmptyLayer { + return false + } + if base.Created != nil && derived.Created == nil { + return false + } + if base.Created == nil && derived.Created != nil { + return false + } + if base.Created != nil && derived.Created != nil && !base.Created.Equal(*derived.Created) { + return false + } + return true +} + +// historyAndDiffIDsMatch returns true if a candidate history matches the +// history of our base image (if we have one), plus the current instruction, +// and if the list of diff IDs for the images do for the part of the history +// that we're comparing. +// Used to verify whether a cache of the intermediate image exists and whether +// to run the build again. +func (s *StageExecutor) historyAndDiffIDsMatch(baseHistory []v1.History, baseDiffIDs []digest.Digest, child *parser.Node, history []v1.History, diffIDs []digest.Digest, addedContentSummary string, buildAddsLayer bool, lastInstruction bool) (bool, error) { + // our history should be as long as the base's, plus one entry for what + // we're doing + if len(history) != len(baseHistory)+1 { + return false, nil + } + // check that each entry in the base history corresponds to an entry in + // our history, and count how many of them add a layer diff + expectedDiffIDs := 0 + for i := range baseHistory { + if !historyEntriesEqual(baseHistory[i], history[i]) { + return false, nil + } + if !baseHistory[i].EmptyLayer { + expectedDiffIDs++ + } + } + if len(baseDiffIDs) != expectedDiffIDs { + return false, nil + } + if buildAddsLayer { + // we're adding a layer, so we should have exactly one more + // layer than the base image + if len(diffIDs) != expectedDiffIDs+1 { + return false, nil + } + } else { + // we're not adding a layer, so we should have exactly the same + // layers as the base image + if len(diffIDs) != expectedDiffIDs { + return false, nil + } + } + // compare the diffs for the layers that we should have in common + for i := range baseDiffIDs { + if diffIDs[i] != baseDiffIDs[i] { + return false, nil + } + } + createdBy, err := s.getCreatedBy(child, addedContentSummary, lastInstruction) + if err != nil { + return false, fmt.Errorf("unable to get createdBy for the node: %w", err) + } + return history[len(baseHistory)].CreatedBy == createdBy, nil +} + +// getCreatedBy returns the value to store in the history entry for the node. +// If the the passed-in addedContentSummary is not an empty string, it is +// assumed to have the digest information for the content if the node is ADD or +// COPY. +// +// The metadata string which is appended to the instruction may need to +// indicate that certain last-minute changes (generally things which couldn't +// be done by appending to the parsed Dockerfile, such as modifying timestamps +// in the layer, unsetting labels, or anything having to do with annotations) +// were made so that a future build won't mistake this result for a cache hit +// unless the same flags are being used at that time. +func (s *StageExecutor) getCreatedBy(node *parser.Node, addedContentSummary string, isLastStep bool) (string, error) { + if node == nil { + return "/bin/sh", nil + } + + command := strings.ToUpper(node.Value) + addcopy := command == "ADD" || command == "COPY" + + labelsAndAnnotations := s.buildMetadata(isLastStep, addcopy) + + switch command { + case "ARG": + for variable := range strings.FieldsSeq(node.Original) { + if variable != "ARG" { + s.argsFromContainerfile = append(s.argsFromContainerfile, variable) + } + } + buildArgs := s.getBuildArgsKey() + return "/bin/sh -c #(nop) ARG " + buildArgs + labelsAndAnnotations, nil + case "RUN": + shArg := "" + buildArgs := s.getBuildArgsResolvedForRun() + appendCheckSum := "" + for _, flag := range node.Flags { + var err error + mountOptionSource := "" + mountOptionFrom := "" + mountCheckSum := "" + if strings.HasPrefix(flag, "--mount=") { + mountInfo := getFromAndSourceKeysFromMountFlag(flag) + if mountInfo.Type != "bind" { + continue + } + mountOptionSource = mountInfo.Source + mountOptionSource, err = imagebuilder.ProcessWord(mountOptionSource, s.stage.Builder.Arguments()) + if err != nil { + return "", fmt.Errorf("getCreatedBy: while replacing arg variables with values for format %q: %w", mountOptionSource, err) + } + mountOptionFrom = mountInfo.From + // If source is not specified then default is '.' + if mountOptionSource == "" { + mountOptionSource = "." + } + } + // Source specified is part of stage, image or additional-build-context. + if mountOptionFrom != "" { + // If this is not a stage then get digest of image or additional build context + if _, ok := s.executor.stages[mountOptionFrom]; !ok { + if builder, ok := s.executor.containerMap[mountOptionFrom]; ok { + // Found valid image, get image digest. + mountCheckSum = builder.FromImageDigest + } else { + if s.executor.additionalBuildContexts[mountOptionFrom].IsImage { + if builder, ok := s.executor.containerMap[s.executor.additionalBuildContexts[mountOptionFrom].Value]; ok { + // Found valid image, get image digest. + mountCheckSum = builder.FromImageDigest + } + } else { + // Found additional build context, get directory sha. + basePath := s.executor.additionalBuildContexts[mountOptionFrom].Value + if s.executor.additionalBuildContexts[mountOptionFrom].IsURL { + basePath = s.executor.additionalBuildContexts[mountOptionFrom].DownloadedCache + } + mountCheckSum, err = generatePathChecksum(filepath.Join(basePath, mountOptionSource)) + if err != nil { + return "", fmt.Errorf("generating checksum for directory %q in %q: %w", mountOptionSource, basePath, err) + } + } + } + } + } else { + if mountOptionSource != "" { + mountCheckSum, err = generatePathChecksum(filepath.Join(s.executor.contextDir, mountOptionSource)) + if err != nil { + return "", fmt.Errorf("generating checksum for directory %q in %q: %w", mountOptionSource, s.executor.contextDir, err) + } + } + } + if mountCheckSum != "" { + // add a separator to appendCheckSum + appendCheckSum += ":" + mountCheckSum + } + } + if len(node.Original) > 4 { + shArg = node.Original[4:] + } + + heredoc := "" + result := "" + if len(node.Heredocs) > 0 { + for _, doc := range node.Heredocs { + heredocContent := strings.TrimSpace(doc.Content) + heredoc = heredoc + "\n" + heredocContent + } + } + if buildArgs != "" { + result = result + "|" + strconv.Itoa(len(strings.Split(buildArgs, " "))) + " " + buildArgs + " " + } + result = result + "/bin/sh -c " + shArg + heredoc + appendCheckSum + labelsAndAnnotations + return result, nil + case "ADD", "COPY": + destination := node + for destination.Next != nil { + destination = destination.Next + } + hasLink := "" + if s.hasLink { + hasLink = " --link" + } + return "/bin/sh -c #(nop) " + strings.ToUpper(node.Value) + hasLink + " " + addedContentSummary + " in " + destination.Value + " " + labelsAndAnnotations, nil + default: + return "/bin/sh -c #(nop) " + node.Original + labelsAndAnnotations, nil + } +} + +// getBuildArgs returns a string of the build-args specified during the build process +// it excludes any build-args that were not used in the build process +// values for args are overridden by the values specified using ENV. +// Reason: Values from ENV will always override values specified arg. +func (s *StageExecutor) getBuildArgsResolvedForRun() string { + var envs []string + configuredEnvs := make(map[string]string) + dockerConfig := s.stage.Builder.Config() + + for _, env := range dockerConfig.Env { + key, val, hasVal := strings.Cut(env, "=") + if hasVal { + configuredEnvs[key] = val + } + } + + for key, value := range s.stage.Builder.Args { + if _, ok := s.stage.Builder.AllowedArgs[key]; ok { + // if value was in image it will be given higher priority + // so please embed that into build history + _, inImage := configuredEnvs[key] + if inImage { + envs = append(envs, fmt.Sprintf("%s=%s", key, configuredEnvs[key])) + } else { + // By default everything must be added to history. + // Following variable is configured to false only for special cases. + addToHistory := true + + // Following value is being assigned from build-args, + // check if this key belongs to any of the predefined allowlist args e.g Proxy Variables + // and if that arg is not manually set in Containerfile/Dockerfile + // then don't write its value to history. + // Following behaviour ensures parity with docker/buildkit. + for _, variable := range config.ProxyEnv { + if key == variable { + // found in predefined args + // so don't add to history + // unless user did explicit `ARG ` + addToHistory = false + for _, processedArg := range s.argsFromContainerfile { + if key == processedArg { + addToHistory = true + } + } + } + } + if addToHistory { + envs = append(envs, fmt.Sprintf("%s=%s", key, value)) + } + } + } + } + slices.Sort(envs) + return strings.Join(envs, " ") +} + +// getBuildArgs key returns the set of args which were specified during the +// build process, formatted for inclusion in the build history +func (s *StageExecutor) getBuildArgsKey() string { + var args []string + for key := range s.stage.Builder.Args { + if _, ok := s.stage.Builder.AllowedArgs[key]; ok { + args = append(args, key) + } + } + slices.Sort(args) + return strings.Join(args, " ") +} + +// tagExistingImage adds names to an image already in the store +func (s *StageExecutor) tagExistingImage(ctx context.Context, cacheID, output string) (string, reference.Canonical, error) { + // If we don't need to attach a name to the image, just return the cache ID. + if output == "" { + return cacheID, nil, nil + } + + // Get the destination image reference. + dest, err := s.executor.resolveNameToImageRef(output) + if err != nil { + return "", nil, err + } + + policyContext, err := util.GetPolicyContext(s.systemContext) + if err != nil { + return "", nil, err + } + defer func() { + if destroyErr := policyContext.Destroy(); destroyErr != nil { + if err == nil { + err = destroyErr + } else { + err = fmt.Errorf("%v: %w", destroyErr.Error(), err) + } + } + }() + + // Look up the source image, expecting it to be in local storage + src, err := is.Transport.ParseStoreReference(s.executor.store, cacheID) + if err != nil { + return "", nil, fmt.Errorf("getting source imageReference for %q: %w", cacheID, err) + } + options := cp.Options{ + RemoveSignatures: true, // more like "ignore signatures", since they don't get removed when src and dest are the same image + } + manifestBytes, err := cp.Image(ctx, policyContext, dest, src, &options) + if err != nil { + return "", nil, fmt.Errorf("copying image %q: %w", cacheID, err) + } + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return "", nil, fmt.Errorf("computing digest of manifest for image %q: %w", cacheID, err) + } + _, img, err := is.ResolveReference(dest) + if err != nil { + return "", nil, fmt.Errorf("locating new copy of image %q (i.e., %q): %w", cacheID, transports.ImageName(dest), err) + } + var ref reference.Canonical + if dref := dest.DockerReference(); dref != nil { + if ref, err = reference.WithDigest(dref, manifestDigest); err != nil { + return "", nil, fmt.Errorf("computing canonical reference for new image %q (i.e., %q): %w", cacheID, transports.ImageName(dest), err) + } + } + return img.ID, ref, nil +} + +// generateCacheKey returns a computed digest for the current STEP +// running its history and diff against a hash algorithm and this +// generated CacheKey is further used by buildah to lock and decide +// tag for the intermediate image which can be pushed and pulled to/from +// the remote repository. +func (s *StageExecutor) generateCacheKey(ctx context.Context, currNode *parser.Node, addedContentDigest string, buildAddsLayer bool, lastInstruction bool) (string, error) { + hash := sha256.New() + var baseHistory []v1.History + var diffIDs []digest.Digest + var manifestType string + var err error + if s.builder.FromImageID != "" { + _, _, manifestType, baseHistory, diffIDs, err = s.executor.getImageTypeAndHistoryAndDiffIDs(ctx, s.builder.FromImageID) + if err != nil { + return "", fmt.Errorf("getting history of base image %q: %w", s.builder.FromImageID, err) + } + for i := range len(diffIDs) { + fmt.Fprintln(hash, diffIDs[i].String()) + } + } + createdBy, err := s.getCreatedBy(currNode, addedContentDigest, lastInstruction) + if err != nil { + return "", err + } + fmt.Fprintf(hash, "%t", buildAddsLayer) + fmt.Fprintln(hash, createdBy) + fmt.Fprintln(hash, manifestType) + for _, element := range baseHistory { + fmt.Fprintln(hash, element.CreatedBy) + fmt.Fprintln(hash, element.Author) + fmt.Fprintln(hash, element.Comment) + fmt.Fprintln(hash, element.Created) + fmt.Fprintf(hash, "%t", element.EmptyLayer) + fmt.Fprintln(hash) + } + return fmt.Sprintf("%x", hash.Sum(nil)), nil +} + +// cacheImageReference is internal function which generates ImageReference from Named repo sources +// and a tag. +func cacheImageReferences(repos []reference.Named, cachekey string) ([]types.ImageReference, error) { + var result []types.ImageReference + for _, repo := range repos { + tagged, err := reference.WithTag(repo, cachekey) + if err != nil { + return nil, fmt.Errorf("failed generating tagged reference for %q: %w", repo, err) + } + dest, err := imagedocker.NewReference(tagged) + if err != nil { + return nil, fmt.Errorf("failed generating docker reference for %q: %w", tagged, err) + } + result = append(result, dest) + } + return result, nil +} + +// pushCache takes the image id of intermediate image and attempts +// to perform push at the remote repository with cacheKey as the tag. +// Returns error if fails otherwise returns nil. +func (s *StageExecutor) pushCache(ctx context.Context, src, cacheKey string) error { + destList, err := cacheImageReferences(s.executor.cacheTo, cacheKey) + if err != nil { + return err + } + for _, dest := range destList { + logrus.Debugf("trying to push cache to dest: %+v from src:%+v", dest, src) + options := buildah.PushOptions{ + Compression: s.executor.compression, + SignaturePolicyPath: s.executor.signaturePolicyPath, + Store: s.executor.store, + SystemContext: s.systemContext, + BlobDirectory: s.executor.blobDirectory, + SignBy: s.executor.signBy, + MaxRetries: s.executor.maxPullPushRetries, + RetryDelay: s.executor.retryPullPushDelay, + } + if s.executor.cachePushSourceLookupReferenceFunc != nil { + options.SourceLookupReferenceFunc = s.executor.cachePushSourceLookupReferenceFunc(dest) + } + if s.executor.cachePushDestinationLookupReferenceFunc != nil { + options.DestinationLookupReferenceFunc = s.executor.cachePushDestinationLookupReferenceFunc + } + ref, digest, err := buildah.Push(ctx, src, dest, options) + if err != nil { + return fmt.Errorf("failed pushing cache to %q: %w", dest, err) + } + logrus.Debugf("successfully pushed cache to dest: %+v with ref:%+v and digest: %v", dest, ref, digest) + } + return nil +} + +// pullCache takes the image source of the cache assuming tag +// already points to the valid cacheKey and pulls the image to +// local storage only if it was not already present on local storage +// or a newer version of cache was found in the upstream repo. If new +// image was pulled function returns image id otherwise returns empty +// string "" or error if any error was encontered while pulling the cache. +func (s *StageExecutor) pullCache(ctx context.Context, cacheKey string) (reference.Named, string, error) { + srcList, err := cacheImageReferences(s.executor.cacheFrom, cacheKey) + if err != nil { + return nil, "", err + } + for _, src := range srcList { + srcDockerRef := src.DockerReference() + logrus.Debugf("trying to pull cache from remote repo: %+v", srcDockerRef) + options := buildah.PullOptions{ + SignaturePolicyPath: s.executor.signaturePolicyPath, + Store: s.executor.store, + SystemContext: s.systemContext, + BlobDirectory: s.executor.blobDirectory, + MaxRetries: s.executor.maxPullPushRetries, + RetryDelay: s.executor.retryPullPushDelay, + AllTags: false, + ReportWriter: nil, + PullPolicy: define.PullIfNewer, + } + if s.executor.cachePullSourceLookupReferenceFunc != nil { + options.SourceLookupReferenceFunc = s.executor.cachePullSourceLookupReferenceFunc + } + if s.executor.cachePullDestinationLookupReferenceFunc != nil { + options.DestinationLookupReferenceFunc = s.executor.cachePullDestinationLookupReferenceFunc(src) + } + + id, err := buildah.Pull(ctx, srcDockerRef.String(), options) + if err != nil { + logrus.Debugf("failed pulling cache from source %s: %v", src, err) + continue // failed pulling this one try next + // return "", fmt.Errorf("failed while pulling cache from %q: %w", src, err) + } + logrus.Debugf("successfully pulled cache from repo %s: %s", src, id) + return src.DockerReference(), id, nil + } + return nil, "", fmt.Errorf("failed pulling cache from all available sources %q", srcList) +} + +// intermediateImageExists returns image ID if an intermediate image of currNode exists in the image store from a previous build. +// It verifies this by checking the parent of the top layer of the image and the history. +// If more than one image matches as potential candidates then priority is given to the most recently built image. +func (s *StageExecutor) intermediateImageExists(ctx context.Context, currNode *parser.Node, addedContentDigest string, buildAddsLayer bool, lastInstruction bool) (string, error) { + cacheCandidates := []storage.Image{} + // Get the list of images available in the image store + images, err := s.executor.store.Images() + if err != nil { + return "", fmt.Errorf("getting image list from store: %w", err) + } + var baseHistory []v1.History + var baseDiffIDs []digest.Digest + if s.builder.FromImageID != "" { + _, _, _, baseHistory, baseDiffIDs, err = s.executor.getImageTypeAndHistoryAndDiffIDs(ctx, s.builder.FromImageID) + if err != nil { + return "", fmt.Errorf("getting history of base image %q: %w", s.builder.FromImageID, err) + } + } + for _, image := range images { + // If s.executor.cacheTTL was specified + // then ignore processing image if it + // was created before the specified + // duration. + if int64(s.executor.cacheTTL) != 0 { + timeNow := time.Now() + imageDuration := timeNow.Sub(image.Created) + if s.executor.cacheTTL < imageDuration { + continue + } + } + var imageTopLayer *storage.Layer + var imageParentLayerID string + if image.TopLayer != "" { + imageTopLayer, err = s.executor.store.Layer(image.TopLayer) + if err != nil { + return "", fmt.Errorf("getting top layer info: %w", err) + } + // Figure out which layer from this image we should + // compare our container's base layer to. + imageParentLayerID = imageTopLayer.ID + // If we haven't added a layer here, then our base + // layer should be the same as the image's layer. If + // did add a layer, then our base layer should be the + // same as the parent of the image's layer. + if buildAddsLayer { + imageParentLayerID = imageTopLayer.Parent + } + } + // If the parent of the top layer of an image is equal to the current build image's top layer, + // it means that this image is potentially a cached intermediate image from a previous + // build. + if s.builder.TopLayer != imageParentLayerID { + continue + } + + // Next we double check that the history of this image is equivalent to the previous + // lines in the Dockerfile up till the point we are at in the build. + imageOS, imageArchitecture, manifestType, history, diffIDs, err := s.executor.getImageTypeAndHistoryAndDiffIDs(ctx, image.ID) + if err != nil { + // It's possible that this image is for another architecture, which results + // in a custom-crafted error message that we'd have to use substring matching + // to recognize. Instead, ignore the image. + logrus.Debugf("error getting history of %q (%v), ignoring it", image.ID, err) + continue + } + // If this candidate isn't of the type that we're building, then it may have lost + // some format-specific information that a building-without-cache run wouldn't lose. + if manifestType != s.executor.outputFormat { + continue + } + + // Compare the cached image's platform with the current build's target platform + currentArch := s.executor.architecture + currentOS := s.executor.os + if currentArch == "" && currentOS == "" { + currentOS, currentArch, _, err = parse.Platform(s.stage.Builder.Platform) + if err != nil { + logrus.Debugf("unable to parse default OS and Arch for the current build: %v", err) + } + } + if currentArch != "" && imageArchitecture != currentArch { + logrus.Debugf("cached image %q has architecture %q but current build targets %q, ignoring it", image.ID, imageArchitecture, currentArch) + continue + } + if currentOS != "" && imageOS != currentOS { + logrus.Debugf("cached image %q has OS %q but current build targets %q, ignoring it", image.ID, imageOS, currentOS) + continue + } + + // children + currNode is the point of the Dockerfile we are currently at. + foundMatch, err := s.historyAndDiffIDsMatch(baseHistory, baseDiffIDs, currNode, history, diffIDs, addedContentDigest, buildAddsLayer, lastInstruction) + if err != nil { + return "", err + } + if foundMatch { + cacheCandidates = append(cacheCandidates, image) + } + } + if len(cacheCandidates) > 0 { + slices.SortFunc(cacheCandidates, func(a, b storage.Image) int { return a.Created.Compare(b.Created) }) + return cacheCandidates[len(cacheCandidates)-1].ID, nil + } + return "", nil +} + +// commit writes the container's contents to an image, using a passed-in tag as +// the name if there is one, generating a unique ID-based one otherwise. +// or commit via any custom exporter if specified. +func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer bool, output string, squash, finalInstruction bool) (string, reference.Canonical, error) { + ib := s.stage.Builder + var imageRef types.ImageReference + if output != "" { + imageRef2, err := s.executor.resolveNameToImageRef(output) + if err != nil { + return "", nil, err + } + imageRef = imageRef2 + } + + if ib.Author != "" { + s.builder.SetMaintainer(ib.Author) + } + config := ib.Config() + if createdBy != "" { + s.builder.SetCreatedBy(createdBy) + } + s.builder.SetHostname(config.Hostname) + s.builder.SetDomainname(config.Domainname) + if s.executor.architecture != "" { + s.builder.SetArchitecture(s.executor.architecture) + } + if s.executor.os != "" { + s.builder.SetOS(s.executor.os) + } + if s.executor.osVersion != "" { + s.builder.SetOSVersion(s.executor.osVersion) + } + for _, osFeatureSpec := range s.executor.osFeatures { + switch { + case strings.HasSuffix(osFeatureSpec, "-"): + s.builder.UnsetOSFeature(strings.TrimSuffix(osFeatureSpec, "-")) + default: + s.builder.SetOSFeature(osFeatureSpec) + } + } + s.builder.SetUser(config.User) + s.builder.ClearPorts() + for p := range config.ExposedPorts { + s.builder.SetPort(string(p)) + } + for _, envSpec := range config.Env { + key, val, _ := strings.Cut(envSpec, "=") + s.builder.SetEnv(key, val) + } + for _, envSpec := range s.executor.unsetEnvs { + s.builder.UnsetEnv(envSpec) + } + s.builder.SetCmd(config.Cmd) + s.builder.ClearVolumes() + for v := range config.Volumes { + s.builder.AddVolume(v) + } + s.builder.ClearOnBuild() + for _, onBuildSpec := range config.OnBuild { + s.builder.SetOnBuild(onBuildSpec) + } + s.builder.SetWorkDir(config.WorkingDir) + s.builder.SetEntrypoint(config.Entrypoint) + s.builder.SetShell(config.Shell) + s.builder.SetStopSignal(config.StopSignal) + if config.Healthcheck != nil { + s.builder.SetHealthcheck(&buildahdocker.HealthConfig{ + Test: slices.Clone(config.Healthcheck.Test), + Interval: config.Healthcheck.Interval, + Timeout: config.Healthcheck.Timeout, + StartPeriod: config.Healthcheck.StartPeriod, + StartInterval: config.Healthcheck.StartInterval, + Retries: config.Healthcheck.Retries, + }) + } else { + s.builder.SetHealthcheck(nil) + } + s.builder.ClearLabels() + + if output == "" { + // If output is not set then we are committing + // an intermediate image, in such case we must + // honor layer labels if they are configured. + for _, labelString := range s.executor.layerLabels { + labelk, labelv, _ := strings.Cut(labelString, "=") + s.builder.SetLabel(labelk, labelv) + } + } + for k, v := range config.Labels { + s.builder.SetLabel(k, v) + } + switch s.executor.commonBuildOptions.IdentityLabel { + case types.OptionalBoolTrue: + s.builder.SetLabel(buildah.BuilderIdentityAnnotation, define.Version) + case types.OptionalBoolFalse: + // nothing - don't clear it if there's a value set in the base image + default: + if s.executor.timestamp == nil && s.executor.sourceDateEpoch == nil { + s.builder.SetLabel(buildah.BuilderIdentityAnnotation, define.Version) + } + } + for _, key := range s.executor.unsetLabels { + s.builder.UnsetLabel(key) + } + if finalInstruction { + if s.executor.inheritAnnotations == types.OptionalBoolFalse { + // If user has selected `--inherit-annotations=false` let's not + // inherit annotations from base image. + s.builder.ClearAnnotations() + } + // Add new annotations to the last step. + for _, annotationSpec := range s.executor.annotations { + annotationk, annotationv, _ := strings.Cut(annotationSpec, "=") + s.builder.SetAnnotation(annotationk, annotationv) + } + for _, key := range s.executor.unsetAnnotations { + s.builder.UnsetAnnotation(key) + } + } + if imageRef != nil { + logName := transports.ImageName(imageRef) + logrus.Debugf("COMMIT %q", logName) + } else { + logrus.Debugf("COMMIT") + } + writer := s.executor.reportWriter + if s.executor.layers || !s.executor.useCache { + writer = nil + } + options := buildah.CommitOptions{ + Compression: s.executor.compression, + SignaturePolicyPath: s.executor.signaturePolicyPath, + ReportWriter: writer, + PreferredManifestType: s.executor.outputFormat, + SystemContext: s.systemContext, + Squash: squash, + OmitHistory: s.executor.commonBuildOptions.OmitHistory, + EmptyLayer: emptyLayer, + OmitLayerHistoryEntry: s.hasLink, + BlobDirectory: s.executor.blobDirectory, + SignBy: s.executor.signBy, + MaxRetries: s.executor.maxPullPushRetries, + RetryDelay: s.executor.retryPullPushDelay, + HistoryTimestamp: s.executor.timestamp, + Manifest: s.executor.manifest, + CompatSetParent: s.executor.compatSetParent, + SourceDateEpoch: s.executor.sourceDateEpoch, + RewriteTimestamp: s.executor.rewriteTimestamp, + CompatLayerOmissions: s.executor.compatLayerOmissions, + UnsetAnnotations: s.executor.unsetAnnotations, + Annotations: s.executor.annotations, + CreatedAnnotation: s.executor.createdAnnotation, + } + if finalInstruction { + options.ConfidentialWorkloadOptions = s.executor.confidentialWorkload + options.SBOMScanOptions = s.executor.sbomScanOptions + } + imgID, _, manifestDigest, err := s.builder.Commit(ctx, imageRef, options) + if err != nil { + return "", nil, err + } + var ref reference.Canonical + if imageRef != nil { + if dref := imageRef.DockerReference(); dref != nil { + if ref, err = reference.WithDigest(dref, manifestDigest); err != nil { + return "", nil, fmt.Errorf("computing canonical reference for new image %q: %w", imgID, err) + } + } + } + return imgID, ref, nil +} + +func (s *StageExecutor) generateBuildOutput(buildOutputOpts define.BuildOutputOption) error { + forceTimestamp := s.executor.timestamp + if s.executor.sourceDateEpoch != nil { + forceTimestamp = s.executor.sourceDateEpoch + } + extractRootfsOpts := buildah.ExtractRootfsOptions{ + ForceTimestamp: forceTimestamp, + } + if unshare.IsRootless() { + // In order to maintain as much parity as possible + // with buildkit's version of --output and to avoid + // unsafe invocation of exported executables it was + // decided to strip setuid,setgid and extended attributes. + // Since modes like setuid,setgid leaves room for executable + // to get invoked with different file-system permission its safer + // to strip them off for unprivileged invocation. + // See: https://github.com/containers/buildah/pull/3823#discussion_r829376633 + extractRootfsOpts.StripSetuidBit = true + extractRootfsOpts.StripSetgidBit = true + extractRootfsOpts.StripXattrs = true + } + rc, errChan, err := s.builder.ExtractRootfs(buildah.CommitOptions{ + HistoryTimestamp: s.executor.timestamp, + SourceDateEpoch: s.executor.sourceDateEpoch, + RewriteTimestamp: s.executor.rewriteTimestamp, + CompatLayerOmissions: s.executor.compatLayerOmissions, + }, extractRootfsOpts) + if err != nil { + return fmt.Errorf("failed to extract rootfs from given container image: %w", err) + } + defer rc.Close() + err = internalUtil.ExportFromReader(rc, buildOutputOpts) + if err != nil { + return fmt.Errorf("failed to export build output: %w", err) + } + if errChan != nil { + err = <-errChan + if err != nil { + return err + } + } + return nil +} + +func (s *StageExecutor) EnsureContainerPath(path string) error { + logrus.Debugf("EnsureContainerPath %q in %q", path, s.builder.ContainerID) + return s.builder.EnsureContainerPathAs(path, "", nil) +} + +func (s *StageExecutor) EnsureContainerPathAs(path, user string, mode *os.FileMode) error { + logrus.Debugf("EnsureContainerPath %q (owner %q, mode %o) in %q", path, user, mode, s.builder.ContainerID) + return s.builder.EnsureContainerPathAs(path, user, mode) +} + +// buildMetadata constructs the text at the end of the createdBy value for the +// history entry that we'll generate for the instruction that we're currently +// processing. Any flags that affect the output image in a way that affects +// whether or not it should be used as a cache hit for another build with that +// flag set differently should be reflected in its result. Some build settings +// only take affect at the final step, so only note those when they're applied. +func (s *StageExecutor) buildMetadata(isLastStep bool, isAddOrCopy bool) string { + unsetLabels := "" + inheritLabels := "" + unsetAnnotations := "" + inheritAnnotations := "" + newAnnotations := "" + layerMutations := "" + + // If --inherit-label was manually set to false then update history. + if s.executor.inheritLabels == types.OptionalBoolFalse { + inheritLabels = "|inheritLabels=false" + } + // If --unsetlabel was used to clear a label, make a note of it. + for _, label := range s.executor.unsetLabels { + unsetLabels += "|unsetLabel=" + label + } + if isLastStep { + // If --unsetannotation was used to clear an annotation, make a note of it. + for _, annotation := range s.executor.unsetAnnotations { + unsetAnnotations += "|unsetAnnotation=" + annotation + } + // If --inherit-annotation was manually set to false then we cleared the inherited annotations. + if s.executor.inheritAnnotations == types.OptionalBoolFalse { + inheritAnnotations = "|inheritAnnotations=false" + } + // If new annotations are added, they must be added as part of the last step of the build, + // so mention in history that new annotations were added in order to make sure that subsequent builds + // only use this image as a cache hit if it was built with the same set of annotations. + if len(s.executor.annotations) > 0 { + newAnnotations += strings.Join(s.executor.annotations, ",") + } + } + // If we're messing with timestamps in layer contents, make a note of how we're doing it. + if s.executor.timestamp != nil || (s.executor.sourceDateEpoch != nil && s.executor.rewriteTimestamp) { + var t time.Time + modtype := "" + if s.executor.timestamp != nil { + t = s.executor.timestamp.UTC() + modtype = "force-mtime" + } + if s.executor.sourceDateEpoch != nil && s.executor.rewriteTimestamp { + t = s.executor.sourceDateEpoch.UTC() + modtype = "clamp-mtime" + if s.executor.timestamp != nil && s.executor.timestamp.Before(*s.executor.sourceDateEpoch) { + t = s.executor.timestamp.UTC() + modtype = "force-mtime" + } + } + layerMutations = "|" + modtype + "=" + strconv.FormatInt(t.Unix(), 10) + } + + if isAddOrCopy { + return unsetLabels + " " + inheritLabels + " " + unsetAnnotations + " " + inheritAnnotations + " " + layerMutations + " " + newAnnotations + } + return unsetLabels + inheritLabels + unsetAnnotations + inheritAnnotations + layerMutations + newAnnotations +} diff --git a/vendor/github.com/containers/buildah/imagebuildah/util.go b/vendor/github.com/containers/buildah/imagebuildah/util.go new file mode 100644 index 00000000..eb0f6c63 --- /dev/null +++ b/vendor/github.com/containers/buildah/imagebuildah/util.go @@ -0,0 +1,117 @@ +package imagebuildah + +import ( + "archive/tar" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/containers/buildah" + digest "github.com/opencontainers/go-digest" +) + +type mountInfo struct { + Type string + Source string + From string +} + +// Consumes mount flag in format of `--mount=type=bind,src=/path,from=image` and +// return mountInfo with values, otherwise values are empty if keys are not present in the option. +func getFromAndSourceKeysFromMountFlag(mount string) mountInfo { + tokens := strings.Split(strings.TrimPrefix(mount, "--mount="), ",") + source := "" + from := "" + mountType := "" + for _, option := range tokens { + if optionSplit := strings.Split(option, "="); len(optionSplit) == 2 { + if optionSplit[0] == "src" || optionSplit[0] == "source" { + source = optionSplit[1] + } + if optionSplit[0] == "from" { + from = optionSplit[1] + } + if optionSplit[0] == "type" { + mountType = optionSplit[1] + } + } + } + return mountInfo{Source: source, From: from, Type: mountType} +} + +// generatePathChecksum generates the SHA-256 checksum for a file or a directory. +func generatePathChecksum(sourcePath string) (string, error) { + digester := digest.SHA256.Digester() + tarWriter := tar.NewWriter(digester.Hash()) + + err := filepath.Walk(sourcePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + var linkTarget string + if info.Mode()&os.ModeSymlink != 0 { + // If the file is a symlink, get the target + linkTarget, err = os.Readlink(path) + if err != nil { + return err + } + } + + header, err := tar.FileInfoHeader(info, linkTarget) + if err != nil { + return err + } + + relPath, err := filepath.Rel(sourcePath, path) + if err != nil { + return err + } + header.Name = filepath.ToSlash(relPath) + + // Zero out timestamp fields to ignore modification time in checksum calculation + header.ModTime = time.Time{} + header.AccessTime = time.Time{} + header.ChangeTime = time.Time{} + + if err := tarWriter.WriteHeader(header); err != nil { + return err + } + + if !info.Mode().IsRegular() { + return nil + } + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(tarWriter, file) + return err + }) + tarWriter.Close() + if err != nil { + return "", err + } + return digester.Digest().String(), nil +} + +// InitReexec is a wrapper for buildah.InitReexec(). It should be called at +// the start of main(), and if it returns true, main() should return +// successfully immediately. +func InitReexec() bool { + return buildah.InitReexec() +} + +// argsMapToSlice returns the contents of a map[string]string as a slice of keys +// and values joined with "=". +func argsMapToSlice(m map[string]string) []string { + s := make([]string, 0, len(m)) + for k, v := range m { + s = append(s, k+"="+v) + } + return s +} diff --git a/vendor/github.com/containers/buildah/import.go b/vendor/github.com/containers/buildah/import.go new file mode 100644 index 00000000..d0b56553 --- /dev/null +++ b/vendor/github.com/containers/buildah/import.go @@ -0,0 +1,178 @@ +package buildah + +import ( + "context" + "errors" + "fmt" + + "github.com/containers/buildah/define" + "github.com/containers/buildah/docker" + "github.com/containers/buildah/util" + digest "github.com/opencontainers/go-digest" + "go.podman.io/image/v5/image" + "go.podman.io/image/v5/manifest" + is "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/transports" + "go.podman.io/image/v5/types" + "go.podman.io/storage" +) + +func importBuilderDataFromImage(ctx context.Context, store storage.Store, systemContext *types.SystemContext, imageID, containerName, containerID string) (*Builder, error) { + if imageID == "" { + return nil, errors.New("internal error: imageID is empty in importBuilderDataFromImage") + } + + storeopts, err := storage.DefaultStoreOptions() + if err != nil { + return nil, err + } + uidmap, gidmap := convertStorageIDMaps(storeopts.UIDMap, storeopts.GIDMap) + + ref, err := is.Transport.ParseStoreReference(store, imageID) + if err != nil { + return nil, fmt.Errorf("no such image %q: %w", imageID, err) + } + src, err := ref.NewImageSource(ctx, systemContext) + if err != nil { + return nil, fmt.Errorf("instantiating image source: %w", err) + } + defer src.Close() + + imageDigest := "" + unparsedTop := image.UnparsedInstance(src, nil) + manifestBytes, manifestType, err := unparsedTop.Manifest(ctx) + if err != nil { + return nil, fmt.Errorf("loading image manifest for %q: %w", transports.ImageName(ref), err) + } + if manifestDigest, err := manifest.Digest(manifestBytes); err == nil { + imageDigest = manifestDigest.String() + } + + var instanceDigest *digest.Digest + unparsedInstance := unparsedTop // for instanceDigest + if manifest.MIMETypeIsMultiImage(manifestType) { + list, err := manifest.ListFromBlob(manifestBytes, manifestType) + if err != nil { + return nil, fmt.Errorf("parsing image manifest for %q as list: %w", transports.ImageName(ref), err) + } + instance, err := list.ChooseInstance(systemContext) + if err != nil { + return nil, fmt.Errorf("finding an appropriate image in manifest list %q: %w", transports.ImageName(ref), err) + } + instanceDigest = &instance + unparsedInstance = image.UnparsedInstance(src, instanceDigest) + } + + image, err := image.FromUnparsedImage(ctx, systemContext, unparsedInstance) + if err != nil { + return nil, fmt.Errorf("instantiating image for %q instance %q: %w", transports.ImageName(ref), instanceDigest, err) + } + + imageName := "" + if img, err3 := store.Image(imageID); err3 == nil { + if len(img.Names) > 0 { + imageName = img.Names[0] + } + if img.TopLayer != "" { + layer, err4 := store.Layer(img.TopLayer) + if err4 != nil { + return nil, fmt.Errorf("reading information about image's top layer: %w", err4) + } + uidmap, gidmap = convertStorageIDMaps(layer.UIDMap, layer.GIDMap) + } + } + + defaultNamespaceOptions, err := DefaultNamespaceOptions() + if err != nil { + return nil, err + } + + netInt, err := getNetworkInterface(store, "", "") + if err != nil { + return nil, err + } + + builder := &Builder{ + store: store, + Type: containerType, + FromImage: imageName, + FromImageID: imageID, + FromImageDigest: imageDigest, + Container: containerName, + ContainerID: containerID, + ImageCreatedBy: "", + NamespaceOptions: defaultNamespaceOptions, + IDMappingOptions: define.IDMappingOptions{ + HostUIDMapping: len(uidmap) == 0, + HostGIDMapping: len(uidmap) == 0, + UIDMap: uidmap, + GIDMap: gidmap, + }, + NetworkInterface: netInt, + CommonBuildOpts: &CommonBuildOptions{}, + } + + if err := builder.initConfig(ctx, systemContext, image, nil); err != nil { + return nil, fmt.Errorf("preparing image configuration: %w", err) + } + + return builder, nil +} + +func importBuilder(ctx context.Context, store storage.Store, options ImportOptions) (*Builder, error) { + if options.Container == "" { + return nil, errors.New("container name must be specified") + } + + c, err := store.Container(options.Container) + if err != nil { + return nil, err + } + + systemContext := getSystemContext(store, &types.SystemContext{}, options.SignaturePolicyPath) + + builder, err := importBuilderDataFromImage(ctx, store, systemContext, c.ImageID, options.Container, c.ID) + if err != nil { + return nil, err + } + + if builder.FromImageID != "" { + if d, err2 := digest.Parse(builder.FromImageID); err2 == nil { + builder.Docker.Parent = docker.ID(d) + } else { + builder.Docker.Parent = docker.ID(digest.NewDigestFromHex(digest.Canonical.String(), builder.FromImageID)) + } + } + if builder.FromImage != "" { + builder.Docker.ContainerConfig.Image = builder.FromImage + } + builder.IDMappingOptions.UIDMap, builder.IDMappingOptions.GIDMap = convertStorageIDMaps(c.UIDMap, c.GIDMap) + + err = builder.Save() + if err != nil { + return nil, fmt.Errorf("saving builder state: %w", err) + } + + return builder, nil +} + +func importBuilderFromImage(ctx context.Context, store storage.Store, options ImportFromImageOptions) (*Builder, error) { + if options.Image == "" { + return nil, errors.New("image name must be specified") + } + + systemContext := getSystemContext(store, options.SystemContext, options.SignaturePolicyPath) + + _, img, err := util.FindImage(store, "", systemContext, options.Image) + if err != nil { + return nil, fmt.Errorf("importing settings: %w", err) + } + + builder, err := importBuilderDataFromImage(ctx, store, systemContext, img.ID, "", "") + if err != nil { + return nil, fmt.Errorf("importing build settings from image %q: %w", options.Image, err) + } + + builder.setupLogger() + return builder, nil +} diff --git a/vendor/github.com/containers/buildah/info.go b/vendor/github.com/containers/buildah/info.go new file mode 100644 index 00000000..d0dd4893 --- /dev/null +++ b/vendor/github.com/containers/buildah/info.go @@ -0,0 +1,194 @@ +package buildah + +import ( + "bufio" + "bytes" + "fmt" + "os" + "runtime" + "strconv" + "strings" + + internalUtil "github.com/containers/buildah/internal/util" + putil "github.com/containers/buildah/pkg/util" + "github.com/containers/buildah/util" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/storage" + "go.podman.io/storage/pkg/system" + "go.podman.io/storage/pkg/unshare" +) + +// InfoData holds the info type, i.e store, host etc and the data for each type +type InfoData struct { + Type string + Data map[string]any +} + +// Info returns the store and host information +func Info(store storage.Store) ([]InfoData, error) { + info := []InfoData{} + // get host information + hostInfo := hostInfo() + info = append(info, InfoData{Type: "host", Data: hostInfo}) + + // get store information + storeInfo, err := storeInfo(store) + if err != nil { + logrus.Error(err, "error getting store info") + } + info = append(info, InfoData{Type: "store", Data: storeInfo}) + return info, nil +} + +func hostInfo() map[string]any { + info := map[string]any{} + ps := internalUtil.NormalizePlatform(v1.Platform{OS: runtime.GOOS, Architecture: runtime.GOARCH}) + info["os"] = ps.OS + info["arch"] = ps.Architecture + info["variant"] = ps.Variant + info["cpus"] = runtime.NumCPU() + info["rootless"] = unshare.IsRootless() + + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + logrus.Error(err, "err reading cgroups mode") + } + cgroupVersion := "v1" + ociruntime := util.Runtime() + if unified { + cgroupVersion = "v2" + } + info["CgroupVersion"] = cgroupVersion + info["OCIRuntime"] = ociruntime + + mi, err := system.ReadMemInfo() + if err != nil { + logrus.Error(err, "err reading memory info") + info["MemTotal"] = "" + info["MemFree"] = "" + info["SwapTotal"] = "" + info["SwapFree"] = "" + } else { + info["MemTotal"] = mi.MemTotal + info["MemFree"] = mi.MemFree + info["SwapTotal"] = mi.SwapTotal + info["SwapFree"] = mi.SwapFree + } + hostDistributionInfo := getHostDistributionInfo() + info["Distribution"] = map[string]any{ + "distribution": hostDistributionInfo["Distribution"], + "version": hostDistributionInfo["Version"], + } + + kv, err := putil.ReadKernelVersion() + if err != nil { + logrus.Error(err, "error reading kernel version") + } + info["kernel"] = kv + + upDuration, err := putil.ReadUptime() + if err != nil { + logrus.Error(err, "error reading up time") + } + + hoursFound := false + var timeBuffer bytes.Buffer + var hoursBuffer bytes.Buffer + for _, elem := range upDuration.String() { + timeBuffer.WriteRune(elem) + if elem == 'h' || elem == 'm' { + timeBuffer.WriteRune(' ') + if elem == 'h' { + hoursFound = true + } + } + if !hoursFound { + hoursBuffer.WriteRune(elem) + } + } + + info["uptime"] = timeBuffer.String() + if hoursFound { + hours, err := strconv.ParseFloat(hoursBuffer.String(), 64) + if err == nil { + days := hours / 24 + info["uptime"] = fmt.Sprintf("%s (Approximately %.2f days)", info["uptime"], days) + } + } + + host, err := os.Hostname() + if err != nil { + logrus.Error(err, "error getting hostname") + } + info["hostname"] = host + + return info +} + +// top-level "store" info +func storeInfo(store storage.Store) (map[string]any, error) { + // lets say storage driver in use, number of images, number of containers + info := map[string]any{} + info["GraphRoot"] = store.GraphRoot() + info["RunRoot"] = store.RunRoot() + info["GraphImageStore"] = store.ImageStore() + info["GraphTransientStore"] = store.TransientStore() + info["GraphDriverName"] = store.GraphDriverName() + info["GraphOptions"] = store.GraphOptions() + statusPairs, err := store.Status() + if err != nil { + return nil, err + } + status := map[string]string{} + for _, pair := range statusPairs { + status[pair[0]] = pair[1] + } + info["GraphStatus"] = status + + images, err := store.Images() + if err != nil { + logrus.Error(err, "error getting number of images") + } + info["ImageStore"] = map[string]any{ + "number": len(images), + } + + containers, err := store.Containers() + if err != nil { + logrus.Error(err, "error getting number of containers") + } + info["ContainerStore"] = map[string]any{ + "number": len(containers), + } + + return info, nil +} + +// getHostDistributionInfo returns a map containing the host's distribution and version +func getHostDistributionInfo() map[string]string { + dist := make(map[string]string) + + // Populate values in case we cannot find the values + // or the file + dist["Distribution"] = "unknown" + dist["Version"] = "unknown" + + f, err := os.Open("/etc/os-release") + if err != nil { + return dist + } + defer f.Close() + + l := bufio.NewScanner(f) + for l.Scan() { + if after, ok := strings.CutPrefix(l.Text(), "ID="); ok { + dist["Distribution"] = after + } + if after, ok := strings.CutPrefix(l.Text(), "VERSION_ID="); ok { + dist["Version"] = strings.Trim(after, "\"") + } + } + return dist +} diff --git a/vendor/github.com/containers/buildah/install.md b/vendor/github.com/containers/buildah/install.md new file mode 100644 index 00000000..df8a8256 --- /dev/null +++ b/vendor/github.com/containers/buildah/install.md @@ -0,0 +1,391 @@ +![buildah logo](https://cdn.rawgit.com/containers/buildah/main/logos/buildah-logo_large.png) + +# Installation Instructions + +## Installing packaged versions of buildah + +### [Arch Linux](https://www.archlinux.org) + +```bash +sudo pacman -S buildah +``` + +### [CentOS](https://www.centos.org) + +Buildah is available in the default Extras repos for CentOS 7 and in +the AppStream repo for CentOS 8 and Stream, however the available version often +lags the upstream release. + +```bash +sudo yum -y install buildah +``` + +### [Debian](https://debian.org) + +The buildah package is available in +the [Bookworm](https://packages.debian.org/bookworm/buildah), which +is the current stable release (Debian 12), as well as Debian Unstable/Sid. + +```bash +# Debian Stable/Bookworm or Unstable/Sid +sudo apt-get update +sudo apt-get -y install buildah +``` + + +### [Fedora](https://www.fedoraproject.org) + +```bash +sudo dnf -y install buildah +``` + +### [Fedora SilverBlue](https://silverblue.fedoraproject.org) + +Installed by default + +### [Fedora CoreOS](https://coreos.fedoraproject.org) + +Not Available. Must be installed via package layering. + +rpm-ostree install buildah + +Note: [`podman`](https://podman.io) build is available by default. + +### [Gentoo](https://www.gentoo.org) +[app-containers/buildah](https://packages.gentoo.org/packages/app-containers/buildah) +```bash +sudo emerge app-containers/buildah +``` + +### [openSUSE](https://www.opensuse.org) + +```bash +sudo zypper install buildah +``` + +### [openSUSE Kubic](https://kubic.opensuse.org) + +transactional-update pkg in buildah + +### [RHEL7](https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux) + +Subscribe, then enable Extras channel and install buildah. + +```bash +sudo subscription-manager repos --enable=rhel-7-server-extras-rpms +sudo yum -y install buildah +``` + +#### [Raspberry Pi OS arm64 (beta)](https://downloads.raspberrypi.org/raspios_arm64/images/) + +Raspberry Pi OS use the standard Debian's repositories, +so it is fully compatible with Debian's arm64 repository. +You can simply follow the [steps for Debian](#debian) to install buildah. + + +### [RHEL8 Beta](https://www.redhat.com/en/blog/powering-its-future-while-preserving-present-introducing-red-hat-enterprise-linux-8-beta?intcmp=701f2000001Cz6OAAS) + +```bash +sudo yum module enable -y container-tools:1.0 +sudo yum module install -y buildah +``` + +### [Ubuntu](https://www.ubuntu.com) + +The buildah package is available in the official repositories for Ubuntu 20.10 +and newer. + +```bash +# Ubuntu 20.10 and newer +sudo apt-get -y update +sudo apt-get -y install buildah +``` + +# Building from scratch + +## System Requirements + +### Kernel Version Requirements +To run Buildah on Red Hat Enterprise Linux or CentOS, version 7.4 or higher is required. +On other Linux distributions Buildah requires a kernel version that supports the OverlayFS and/or fuse-overlayfs filesystem -- you'll need to consult your distribution's documentation to determine a minimum version number. + +### runc Requirement + +Buildah uses `runc` to run commands when `buildah run` is used, or when `buildah build` +encounters a `RUN` instruction, so you'll also need to build and install a compatible version of +[runc](https://github.com/opencontainers/runc) for Buildah to call for those cases. If Buildah is installed +via a package manager such as yum, dnf or apt-get, runc will be installed as part of that process. + +### CNI Requirement + +When Buildah uses `runc` to run commands, it defaults to running those commands +in the host's network namespace. If the command is being run in a separate +user namespace, though, for example when ID mapping is used, then the command +will also be run in a separate network namespace. + +A newly-created network namespace starts with no network interfaces, so +commands which are run in that namespace are effectively disconnected from the +network unless additional setup is done. Buildah relies on the CNI +[library](https://github.com/containernetworking/cni) and +[plugins](https://github.com/containernetworking/plugins) to set up interfaces +and routing for network namespaces. + +If Buildah is installed via a package manager such as yum, dnf or apt-get, a +package containing CNI plugins may be available (in Fedora, the package is +named `containernetworking-cni`). If not, they will need to be installed, +for example using: +``` + git clone https://github.com/containernetworking/plugins + ( cd ./plugins; ./build_linux.sh ) + sudo mkdir -p /opt/cni/bin + sudo install -v ./plugins/bin/* /opt/cni/bin +``` + +The CNI library needs to be configured so that it will know which plugins to +call to set up namespaces. Usually, this configuration takes the form of one +or more configuration files in the `/etc/cni/net.d` directory. A set of example +configuration files is included in the +[`docs/cni-examples`](https://github.com/containers/buildah/tree/main/docs/cni-examples) +directory of this source tree. + +## Package Installation + +Buildah is available on several software repositories and can be installed via a package manager such +as yum, dnf or apt-get on a number of Linux distributions. + +## Installation from GitHub + +Prior to installing Buildah, install the following packages on your Linux distro: +* make +* golang (Requires version 1.13 or higher.) +* bats +* btrfs-progs-devel +* bzip2 +* git +* go-md2man +* gpgme-devel +* glib2-devel +* libassuan-devel +* libseccomp-devel +* runc (Requires version 1.0 RC4 or higher.) +* containers-common + +### Fedora + +In Fedora, you can use this command: + +``` + dnf -y install \ + make \ + golang \ + bats \ + btrfs-progs-devel \ + glib2-devel \ + gpgme-devel \ + libassuan-devel \ + libseccomp-devel \ + git \ + bzip2 \ + go-md2man \ + runc \ + containers-common +``` + +Then to install Buildah on Fedora follow the steps in this example: + +``` + git clone https://github.com/containers/buildah + cd buildah + make + sudo make install + buildah --help +``` + +### RHEL, CentOS + +In RHEL and CentOS, run this command to install the build dependencies: + +``` + yum -y install \ + make \ + golang \ + bats \ + btrfs-progs-devel \ + glib2-devel \ + gpgme-devel \ + libassuan-devel \ + libseccomp-devel \ + git \ + bzip2 \ + go-md2man \ + runc \ + skopeo-containers +``` + +The build steps for Buildah on RHEL or CentOS are the same as for Fedora, above. + +### openSUSE + +On openSUSE Tumbleweed, install go via `zypper in go`, then run this command: + +``` + zypper in make \ + git \ + golang \ + runc \ + bzip2 \ + libgpgme-devel \ + libseccomp-devel \ + libbtrfs-devel \ + go-md2man +``` + +The build steps for Buildah on SUSE / openSUSE are the same as for Fedora, above. + + +### Ubuntu/Debian + +In Ubuntu 22.10 (Karmic) or Debian 12 (Bookworm) you can use these commands: + +``` + sudo apt-get -y -qq update + sudo apt-get -y install bats btrfs-progs git go-md2man golang libapparmor-dev libglib2.0-dev libgpgme11-dev libseccomp-dev libselinux1-dev make runc skopeo libbtrfs-dev +``` + +The build steps for Buildah on Debian or Ubuntu are the same as for Fedora, above. + +## Vendoring - Dependency Management + +This project is using [go modules](https://github.com/golang/go/wiki/Modules) for dependency management. If the CI is complaining about a pull request leaving behind an unclean state, it is very likely right about it. After changing dependencies, make sure to run `make vendor-in-container` to synchronize the code with the go module and repopulate the `./vendor` directory. + +## Configuration files + +The following configuration files are required in order for Buildah to run appropriately. The +majority of these files are commonly contained in the `containers-common` package. + +### [registries.conf](https://github.com/containers/buildah/blob/main/docs/samples/registries.conf) + +#### Man Page: [registries.conf.5](https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md) + +`/etc/containers/registries.conf` + +registries.conf is the configuration file which specifies which container registries should be consulted when completing image names which do not include a registry or domain portion. + +#### Example from the Fedora `containers-common` package + +``` +cat /etc/containers/registries.conf +# This is a system-wide configuration file used to +# keep track of registries for various container backends. +# It adheres to TOML format and does not support recursive +# lists of registries. + +# The default location for this configuration file is /etc/containers/registries.conf. + +# The only valid categories are: 'registries.search', 'registries.insecure', +# and 'registries.block'. + +[registries.search] +registries = ['docker.io', 'registry.fedoraproject.org', 'quay.io', 'registry.access.redhat.com'] + +# If you need to access insecure registries, add the registry's fully-qualified name. +# An insecure registry is one that does not have a valid SSL certificate or only does HTTP. +[registries.insecure] +registries = [] + + +# If you need to block pull access from a registry, uncomment the section below +# and add the registries fully-qualified name. +# +# Docker only +[registries.block] +registries = [] +``` + +### [mounts.conf](https://src.fedoraproject.org/rpms/skopeo/blob/main/f/mounts.conf) + +`/usr/share/containers/mounts.conf` and optionally `/etc/containers/mounts.conf` + +The mounts.conf files specify volume mount files or directories that are automatically mounted inside containers when executing the `buildah run` or `buildah build` commands. Container processes can then use this content. The volume mount content does not get committed to the final image. This file is usually provided by the containers-common package. + +Usually these directories are used for passing secrets or credentials required by the package software to access remote package repositories. + +For example, a mounts.conf with the line "`/usr/share/rhel/secrets:/run/secrets`", the content of `/usr/share/rhel/secrets` directory is mounted on `/run/secrets` inside the container. This mountpoint allows Red Hat Enterprise Linux subscriptions from the host to be used within the container. It is also possible to omit the destination if it's equal to the source path. For example, specifying `/var/lib/secrets` will mount the directory into the same container destination path `/var/lib/secrets`. + +Note this is not a volume mount. The content of the volumes is copied into container storage, not bind mounted directly from the host. + +#### Example from the Fedora `containers-common` package: + +``` +cat /usr/share/containers/mounts.conf +/usr/share/rhel/secrets:/run/secrets +``` + +### [seccomp.json](https://src.fedoraproject.org/rpms/skopeo/blob/main/f/seccomp.json) + +`/usr/share/containers/seccomp.json` + +seccomp.json contains the list of seccomp rules to be allowed inside of +containers. This file is usually provided by the containers-common package. + +The link above takes you to the seccomp.json + +### [policy.json](https://github.com/containers/skopeo/blob/main/default-policy.json) + +`/etc/containers/policy.json` + +#### Man Page: [policy.json.5](https://github.com/containers/image/blob/main/docs/policy.json.md) + + +#### Example from the Fedora `containers-common` package: + +``` +cat /etc/containers/policy.json +{ + "default": [ + { + "type": "insecureAcceptAnything" + } + ], + "transports": + { + "docker-daemon": + { + "": [{"type":"insecureAcceptAnything"}] + } + } +} +``` + +## Debug with Delve and the like + +To make a source debug build without optimizations use `BUILDDEBUG=1`, like: +``` +make all BUILDDEBUG=1 +``` + +## Vendoring + +Buildah uses Go Modules for vendoring purposes. If you need to update or add a vendored package into Buildah, please follow this procedure: + * Enter into your sandbox `src/github.com/containers/buildah` and ensure that the GOPATH variable is set to the directory prior as noted above. + * `export GO111MODULE=on` + * `go get` the needed version: + * Assuming you want to 'bump' the `github.com/containers/storage` package to version 1.12.13, use this command: `go get github.com/containers/storage@v1.12.13` + * Assuming that you want to 'bump' the `github.com/containers/storage` package to a particular commit, use this command: `go get github.com/containers/storage@e307568568533c4afccdf7b56df7b4493e4e9a7b` + * `make vendor-in-container` + * `make` + * `make install` + * Then add any updated or added files with `git add` then do a `git commit` and create a PR. + +### Vendor from your own fork + +If you wish to vendor in your personal fork to try changes out (assuming containers/storage in the below example): + + * `go mod edit -replace github.com/containers/storage=github.com/{mygithub_username}/storage@YOUR_BRANCH` + * `make vendor-in-container` + +To revert + * `go mod edit -dropreplace github.com/containers/storage` + * `make vendor-in-container` + +To speed up fetching dependencies, you can use a [Go Module Proxy](https://proxy.golang.org) by setting `GOPROXY=https://proxy.golang.org`. diff --git a/vendor/github.com/containers/buildah/internal/config/convert.go b/vendor/github.com/containers/buildah/internal/config/convert.go new file mode 100644 index 00000000..3f983ef9 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/config/convert.go @@ -0,0 +1,120 @@ +package config + +import ( + "maps" + "slices" + + dockerclient "github.com/fsouza/go-dockerclient" + "go.podman.io/image/v5/manifest" +) + +// Schema2ConfigFromGoDockerclientConfig converts a go-dockerclient Config +// structure to a manifest Schema2Config. +func Schema2ConfigFromGoDockerclientConfig(config *dockerclient.Config) *manifest.Schema2Config { + overrideExposedPorts := make(map[manifest.Schema2Port]struct{}) + for port := range config.ExposedPorts { + overrideExposedPorts[manifest.Schema2Port(port)] = struct{}{} + } + var overrideHealthCheck *manifest.Schema2HealthConfig + if config.Healthcheck != nil { + overrideHealthCheck = &manifest.Schema2HealthConfig{ + Test: config.Healthcheck.Test, + StartPeriod: config.Healthcheck.StartPeriod, + Interval: config.Healthcheck.Interval, + Timeout: config.Healthcheck.Timeout, + Retries: config.Healthcheck.Retries, + } + } + labels := make(map[string]string) + maps.Copy(labels, config.Labels) + volumes := make(map[string]struct{}) + for v := range config.Volumes { + volumes[v] = struct{}{} + } + s2config := &manifest.Schema2Config{ + Hostname: config.Hostname, + Domainname: config.Domainname, + User: config.User, + AttachStdin: config.AttachStdin, + AttachStdout: config.AttachStdout, + AttachStderr: config.AttachStderr, + ExposedPorts: overrideExposedPorts, + Tty: config.Tty, + OpenStdin: config.OpenStdin, + StdinOnce: config.StdinOnce, + Env: slices.Clone(config.Env), + Cmd: slices.Clone(config.Cmd), + Healthcheck: overrideHealthCheck, + ArgsEscaped: config.ArgsEscaped, + Image: config.Image, + Volumes: volumes, + WorkingDir: config.WorkingDir, + Entrypoint: slices.Clone(config.Entrypoint), + NetworkDisabled: config.NetworkDisabled, + MacAddress: config.MacAddress, + OnBuild: slices.Clone(config.OnBuild), + Labels: labels, + StopSignal: config.StopSignal, + Shell: config.Shell, + } + if config.StopTimeout != 0 { + s2config.StopTimeout = &config.StopTimeout + } + return s2config +} + +// GoDockerclientConfigFromSchema2Config converts a manifest Schema2Config +// to a go-dockerclient config structure. +func GoDockerclientConfigFromSchema2Config(s2config *manifest.Schema2Config) *dockerclient.Config { + overrideExposedPorts := make(map[dockerclient.Port]struct{}) + for port := range s2config.ExposedPorts { + overrideExposedPorts[dockerclient.Port(port)] = struct{}{} + } + var healthCheck *dockerclient.HealthConfig + if s2config.Healthcheck != nil { + healthCheck = &dockerclient.HealthConfig{ + Test: s2config.Healthcheck.Test, + StartPeriod: s2config.Healthcheck.StartPeriod, + Interval: s2config.Healthcheck.Interval, + Timeout: s2config.Healthcheck.Timeout, + Retries: s2config.Healthcheck.Retries, + } + } + labels := make(map[string]string) + maps.Copy(labels, s2config.Labels) + volumes := make(map[string]struct{}) + for v := range s2config.Volumes { + volumes[v] = struct{}{} + } + config := &dockerclient.Config{ + Hostname: s2config.Hostname, + Domainname: s2config.Domainname, + User: s2config.User, + AttachStdin: s2config.AttachStdin, + AttachStdout: s2config.AttachStdout, + AttachStderr: s2config.AttachStderr, + PortSpecs: nil, + ExposedPorts: overrideExposedPorts, + Tty: s2config.Tty, + OpenStdin: s2config.OpenStdin, + StdinOnce: s2config.StdinOnce, + Env: slices.Clone(s2config.Env), + Cmd: slices.Clone(s2config.Cmd), + Healthcheck: healthCheck, + ArgsEscaped: s2config.ArgsEscaped, + Image: s2config.Image, + Volumes: volumes, + WorkingDir: s2config.WorkingDir, + Entrypoint: slices.Clone(s2config.Entrypoint), + NetworkDisabled: s2config.NetworkDisabled, + MacAddress: s2config.MacAddress, + OnBuild: slices.Clone(s2config.OnBuild), + Labels: labels, + StopSignal: s2config.StopSignal, + Shell: s2config.Shell, + } + if s2config.StopTimeout != nil { + config.StopTimeout = *s2config.StopTimeout + } + return config +} diff --git a/vendor/github.com/containers/buildah/internal/config/executor.go b/vendor/github.com/containers/buildah/internal/config/executor.go new file mode 100644 index 00000000..932c656b --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/config/executor.go @@ -0,0 +1,45 @@ +package config + +import ( + "errors" + "fmt" + "os" + + dockerclient "github.com/fsouza/go-dockerclient" + "github.com/openshift/imagebuilder" +) + +// configOnlyExecutor implements the Executor interface that an +// imagebuilder.Builder expects to be able to call to do some heavy lifting, +// but it just refuses to do the work of ADD, COPY, or RUN. It also doesn't +// care if the working directory exists in a container, because it's really +// only concerned with letting the Builder's RunConfig get updated by changes +// from a Dockerfile. Try anything more than that and it'll return an error. +type configOnlyExecutor struct{} + +func (g *configOnlyExecutor) Preserve(_ string) error { + return errors.New("ADD/COPY/RUN not supported as changes") +} + +func (g *configOnlyExecutor) EnsureContainerPath(_ string) error { + return nil +} + +func (g *configOnlyExecutor) EnsureContainerPathAs(_, _ string, _ *os.FileMode) error { + return nil +} + +func (g *configOnlyExecutor) Copy(_ []string, copies ...imagebuilder.Copy) error { + if len(copies) == 0 { + return nil + } + return errors.New("ADD/COPY not supported as changes") +} + +func (g *configOnlyExecutor) Run(_ imagebuilder.Run, _ dockerclient.Config) error { + return errors.New("RUN not supported as changes") +} + +func (g *configOnlyExecutor) UnrecognizedInstruction(step *imagebuilder.Step) error { + return fmt.Errorf("did not understand change instruction %q", step.Original) +} diff --git a/vendor/github.com/containers/buildah/internal/config/override.go b/vendor/github.com/containers/buildah/internal/config/override.go new file mode 100644 index 00000000..55933de4 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/config/override.go @@ -0,0 +1,215 @@ +package config + +import ( + "fmt" + "maps" + "os" + "slices" + "strings" + + "github.com/containers/buildah/docker" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/openshift/imagebuilder" + "go.podman.io/image/v5/manifest" +) + +// firstStringElseSecondString takes two strings, and returns the first +// string if it isn't empty, else the second string +func firstStringElseSecondString(first, second string) string { + if first != "" { + return first + } + return second +} + +// firstSliceElseSecondSlice takes two string slices, and returns the first +// slice of strings if it has contents, else the second slice +func firstSliceElseSecondSlice(first, second []string) []string { + if len(first) > 0 { + return slices.Clone(first) + } + return slices.Clone(second) +} + +// firstSlicePairElseSecondSlicePair takes two pairs of string slices, and +// returns the first pair of slices if either has contents, else the second +// pair +func firstSlicePairElseSecondSlicePair(firstA, firstB, secondA, secondB []string) ([]string, []string) { + if len(firstA) > 0 || len(firstB) > 0 { + return slices.Clone(firstA), slices.Clone(firstB) + } + return slices.Clone(secondA), slices.Clone(secondB) +} + +// mergeEnv combines variables from a and b into a single environment slice. if +// a and b both provide values for the same variable, the value from b is +// preferred +func mergeEnv(a, b []string) []string { + index := make(map[string]int) + results := make([]string, 0, len(a)+len(b)) + for _, kv := range slices.Concat(a, b) { + k, _, specifiesValue := strings.Cut(kv, "=") + if !specifiesValue { + if value, ok := os.LookupEnv(kv); ok { + kv = kv + "=" + value + } else { + kv = kv + "=" + } + } + if i, seen := index[k]; seen { + results[i] = kv + } else { + index[k] = len(results) + results = append(results, kv) + } + } + return results +} + +func parseOverrideChanges(overrideChanges []string, overrideConfig *manifest.Schema2Config) (*manifest.Schema2Config, error) { + if len(overrideChanges) == 0 { + return overrideConfig, nil + } + if overrideConfig == nil { + overrideConfig = &manifest.Schema2Config{} + } + // Parse the set of changes as we would a Dockerfile. + changes := strings.Join(overrideChanges, "\n") + parsed, err := imagebuilder.ParseDockerfile(strings.NewReader(changes)) + if err != nil { + return overrideConfig, fmt.Errorf("parsing change set %+v: %w", changes, err) + } + // Create a dummy builder object to process configuration-related + // instructions. + subBuilder := imagebuilder.NewBuilder(nil) + // Convert the incoming data into an initial RunConfig. + subBuilder.RunConfig = *GoDockerclientConfigFromSchema2Config(overrideConfig) + // Process the change instructions one by one. + for _, node := range parsed.Children { + var step imagebuilder.Step + if err := step.Resolve(node); err != nil { + return overrideConfig, fmt.Errorf("resolving change %q: %w", node.Original, err) + } + if err := subBuilder.Run(&step, &configOnlyExecutor{}, true); err != nil { + return overrideConfig, fmt.Errorf("processing change %q: %w", node.Original, err) + } + } + // Pull settings out of the dummy builder's RunConfig. + return Schema2ConfigFromGoDockerclientConfig(&subBuilder.RunConfig), nil +} + +// OverrideOCI takes a buildah docker config and an OCI ImageConfig, and applies a +// mixture of a slice of Dockerfile-style instructions and fields from a config +// blob to them both +func OverrideOCI(oconfig *v1.ImageConfig, overrideChanges []string, overrideConfig *manifest.Schema2Config) error { + overrideConfig, err := parseOverrideChanges(overrideChanges, overrideConfig) + if err != nil { + return err + } + + if overrideConfig != nil { + // Apply changes from a possibly-provided possibly-changed config struct. + oconfig.User = firstStringElseSecondString(overrideConfig.User, oconfig.User) + if len(overrideConfig.ExposedPorts) > 0 { + oexposedPorts := make(map[string]struct{}) + for port := range oconfig.ExposedPorts { + oexposedPorts[port] = struct{}{} + } + for port := range overrideConfig.ExposedPorts { + oexposedPorts[string(port)] = struct{}{} + } + oconfig.ExposedPorts = oexposedPorts + } + if len(overrideConfig.Env) > 0 { + oconfig.Env = mergeEnv(oconfig.Env, overrideConfig.Env) + } + oconfig.Entrypoint, oconfig.Cmd = firstSlicePairElseSecondSlicePair(overrideConfig.Entrypoint, overrideConfig.Cmd, oconfig.Entrypoint, oconfig.Cmd) + if len(overrideConfig.Volumes) > 0 { + if oconfig.Volumes == nil { + oconfig.Volumes = make(map[string]struct{}) + } + for volume := range overrideConfig.Volumes { + oconfig.Volumes[volume] = struct{}{} + } + } + oconfig.WorkingDir = firstStringElseSecondString(overrideConfig.WorkingDir, oconfig.WorkingDir) + if len(overrideConfig.Labels) > 0 { + if oconfig.Labels == nil { + oconfig.Labels = make(map[string]string) + } + maps.Copy(oconfig.Labels, overrideConfig.Labels) + } + oconfig.StopSignal = overrideConfig.StopSignal + } + return nil +} + +// OverrideDocker takes a buildah docker config and an Docker Config, and applies a +// mixture of a slice of Dockerfile-style instructions and fields from a config +// blob to them both +func OverrideDocker(dconfig *docker.Config, overrideChanges []string, overrideConfig *manifest.Schema2Config) error { + overrideConfig, err := parseOverrideChanges(overrideChanges, overrideConfig) + if err != nil { + return err + } + + if overrideConfig != nil { + // Apply changes from a possibly-provided possibly-changed config struct. + dconfig.Hostname = firstStringElseSecondString(overrideConfig.Hostname, dconfig.Hostname) + dconfig.Domainname = firstStringElseSecondString(overrideConfig.Domainname, dconfig.Domainname) + dconfig.User = firstStringElseSecondString(overrideConfig.User, dconfig.User) + dconfig.AttachStdin = overrideConfig.AttachStdin + dconfig.AttachStdout = overrideConfig.AttachStdout + dconfig.AttachStderr = overrideConfig.AttachStderr + if len(overrideConfig.ExposedPorts) > 0 { + dexposedPorts := make(map[docker.Port]struct{}) + for port := range dconfig.ExposedPorts { + dexposedPorts[port] = struct{}{} + } + for port := range overrideConfig.ExposedPorts { + dexposedPorts[docker.Port(port)] = struct{}{} + } + dconfig.ExposedPorts = dexposedPorts + } + dconfig.Tty = overrideConfig.Tty + dconfig.OpenStdin = overrideConfig.OpenStdin + dconfig.StdinOnce = overrideConfig.StdinOnce + if len(overrideConfig.Env) > 0 { + dconfig.Env = mergeEnv(dconfig.Env, overrideConfig.Env) + } + dconfig.Entrypoint, dconfig.Cmd = firstSlicePairElseSecondSlicePair(overrideConfig.Entrypoint, overrideConfig.Cmd, dconfig.Entrypoint, dconfig.Cmd) + if overrideConfig.Healthcheck != nil { + dconfig.Healthcheck = &docker.HealthConfig{ + Test: slices.Clone(overrideConfig.Healthcheck.Test), + Interval: overrideConfig.Healthcheck.Interval, + Timeout: overrideConfig.Healthcheck.Timeout, + StartPeriod: overrideConfig.Healthcheck.StartPeriod, + Retries: overrideConfig.Healthcheck.Retries, + } + } + dconfig.ArgsEscaped = overrideConfig.ArgsEscaped + dconfig.Image = firstStringElseSecondString(overrideConfig.Image, dconfig.Image) + if len(overrideConfig.Volumes) > 0 { + if dconfig.Volumes == nil { + dconfig.Volumes = make(map[string]struct{}) + } + for volume := range overrideConfig.Volumes { + dconfig.Volumes[volume] = struct{}{} + } + } + dconfig.WorkingDir = firstStringElseSecondString(overrideConfig.WorkingDir, dconfig.WorkingDir) + dconfig.NetworkDisabled = overrideConfig.NetworkDisabled + dconfig.MacAddress = overrideConfig.MacAddress + dconfig.OnBuild = overrideConfig.OnBuild + if len(overrideConfig.Labels) > 0 { + if dconfig.Labels == nil { + dconfig.Labels = make(map[string]string) + } + maps.Copy(dconfig.Labels, overrideConfig.Labels) + } + dconfig.StopSignal = overrideConfig.StopSignal + dconfig.StopTimeout = overrideConfig.StopTimeout + dconfig.Shell = firstSliceElseSecondSlice(overrideConfig.Shell, dconfig.Shell) + } + return nil +} diff --git a/vendor/github.com/containers/buildah/internal/mkcw/archive.go b/vendor/github.com/containers/buildah/internal/mkcw/archive.go new file mode 100644 index 00000000..d1362986 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/mkcw/archive.go @@ -0,0 +1,570 @@ +package mkcw + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/containers/buildah/internal/tmpdir" + "github.com/containers/buildah/pkg/overlay" + "github.com/containers/luksy" + "github.com/docker/go-units" + digest "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/ioutils" + "go.podman.io/storage/pkg/mount" + "go.podman.io/storage/pkg/system" +) + +const minimumImageSize = 10 * 1024 * 1024 + +// ArchiveOptions includes optional settings for generating an archive. +type ArchiveOptions struct { + // If supplied, we'll register the workload with this server. + // Practically necessary if DiskEncryptionPassphrase is not set, in + // which case we'll generate one and throw it away after. + AttestationURL string + + // Used to measure the environment. If left unset (0, ""), defaults will be applied. + CPUs int + Memory int + + // Can be manually set. If left unset ("", false, nil), reasonable values will be used. + TempDir string + TeeType TeeType + IgnoreAttestationErrors bool + ImageSize int64 + WorkloadID string + Slop string + DiskEncryptionPassphrase string + FirmwareLibrary string + Logger *logrus.Logger + GraphOptions []string // passed in from a storage Store, probably + ExtraImageContent map[string]string +} + +type chainRetrievalError struct { + stderr string + err error +} + +func (c chainRetrievalError) Error() string { + if trimmed := strings.TrimSpace(c.stderr); trimmed != "" { + return fmt.Sprintf("retrieving SEV certificate chain: sevctl: %v: %v", strings.TrimSpace(c.stderr), c.err) + } + return fmt.Sprintf("retrieving SEV certificate chain: sevctl: %v", c.err) +} + +// Archive generates a WorkloadConfig for a specified directory and produces a +// tar archive of a container image's rootfs with the expected contents. +func Archive(rootfsPath string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadCloser, WorkloadConfig, error) { + const ( + teeDefaultCPUs = 2 + teeDefaultMemory = 512 + teeDefaultFilesystem = "ext4" + teeDefaultTeeType = SNP + ) + + if rootfsPath == "" { + return nil, WorkloadConfig{}, fmt.Errorf("required path not specified") + } + logger := options.Logger + if logger == nil { + logger = logrus.StandardLogger() + } + + teeType := options.TeeType + if teeType == "" { + teeType = teeDefaultTeeType + } + cpus := options.CPUs + if cpus == 0 { + cpus = teeDefaultCPUs + } + memory := options.Memory + if memory == 0 { + memory = teeDefaultMemory + } + filesystem := teeDefaultFilesystem + workloadID := options.WorkloadID + if workloadID == "" { + digestInput := rootfsPath + filesystem + time.Now().String() + workloadID = digest.Canonical.FromString(digestInput).Encoded() + } + workloadConfig := WorkloadConfig{ + Type: teeType, + WorkloadID: workloadID, + CPUs: cpus, + Memory: memory, + AttestationURL: options.AttestationURL, + } + if options.TempDir == "" { + options.TempDir = tmpdir.GetTempDir() + } + + // Do things which are specific to the type of TEE we're building for. + var chainBytes []byte + var chainBytesFile string + var chainInfo fs.FileInfo + switch teeType { + default: + return nil, WorkloadConfig{}, fmt.Errorf("don't know how to generate TeeData for TEE type %q", teeType) + case SEV, SEV_NO_ES: + // If we need a certificate chain, get it. + chain, err := os.CreateTemp(options.TempDir, "chain") + if err != nil { + return nil, WorkloadConfig{}, err + } + chain.Close() + defer func() { + if err := os.Remove(chain.Name()); err != nil { + logger.Warnf("error removing temporary file %q: %v", chain.Name(), err) + } + }() + logrus.Debugf("sevctl export -f %s", chain.Name()) + cmd := exec.Command("sevctl", "export", "-f", chain.Name()) + var stdout, stderr bytes.Buffer + cmd.Stdout, cmd.Stderr = &stdout, &stderr + if err := cmd.Run(); err != nil { + if !options.IgnoreAttestationErrors { + return nil, WorkloadConfig{}, chainRetrievalError{stderr.String(), err} + } + logger.Warn(chainRetrievalError{stderr.String(), err}.Error()) + } + if chainBytes, err = os.ReadFile(chain.Name()); err != nil { + chainBytes = []byte{} + } + var teeData SevWorkloadData + if len(chainBytes) > 0 { + chainBytesFile = "sev.chain" + chainInfo, err = os.Stat(chain.Name()) + if err != nil { + return nil, WorkloadConfig{}, err + } + teeData.VendorChain = "/" + chainBytesFile + } + encodedTeeData, err := json.Marshal(teeData) + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("encoding tee data: %w", err) + } + workloadConfig.TeeData = string(encodedTeeData) + case SNP: + teeData := SnpWorkloadData{ + Generation: "milan", + } + encodedTeeData, err := json.Marshal(teeData) + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("encoding tee data: %w", err) + } + workloadConfig.TeeData = string(encodedTeeData) + } + + // We're going to want to add some content to the rootfs, so set up an + // overlay that uses it as a lower layer so that we can write to it. + st, err := system.Stat(rootfsPath) + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("reading information about the container root filesystem: %w", err) + } + // Create a temporary directory to hold all of this. Use tmpdir.GetTempDir() + // instead of the passed-in location, which a crafty caller might have put in an + // overlay filesystem in storage because there tends to be more room there than + // in, say, /var/tmp, and the plaintext disk image, which we put in the passed-in + // location, can get quite large. + rootfsParentDir, err := os.MkdirTemp(tmpdir.GetTempDir(), "buildah-rootfs") + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("setting up parent for container root filesystem: %w", err) + } + defer func() { + if err := os.RemoveAll(rootfsParentDir); err != nil { + logger.Warnf("cleaning up parent for container root filesystem: %v", err) + } + }() + // Create a mountpoint for the new overlay, which we'll use as the rootfs. + rootfsDir := filepath.Join(rootfsParentDir, "rootfs") + if err := idtools.MkdirAndChown(rootfsDir, fs.FileMode(st.Mode()), idtools.IDPair{UID: int(st.UID()), GID: int(st.GID())}); err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("creating mount target for container root filesystem: %w", err) + } + defer func() { + if err := os.Remove(rootfsDir); err != nil { + logger.Warnf("removing mount target for container root filesystem: %v", err) + } + }() + // Create a directory to hold all of the overlay package's working state. + tempDir := filepath.Join(rootfsParentDir, "tmp") + if err = os.Mkdir(tempDir, 0o700); err != nil { + return nil, WorkloadConfig{}, err + } + // Create some working state in there. + overlayTempDir, err := overlay.TempDir(tempDir, int(st.UID()), int(st.GID())) + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("setting up mount of container root filesystem: %w", err) + } + defer func() { + if err := overlay.RemoveTemp(overlayTempDir); err != nil { + logger.Warnf("cleaning up mount of container root filesystem: %v", err) + } + }() + // Create a mount point using that working state. + rootfsMount, err := overlay.Mount(overlayTempDir, rootfsPath, rootfsDir, 0, 0, options.GraphOptions) + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("setting up support for overlay of container root filesystem: %w", err) + } + defer func() { + if err := overlay.Unmount(overlayTempDir); err != nil { + logger.Warnf("unmounting support for overlay of container root filesystem: %v", err) + } + }() + // Follow through on the overlay or bind mount, whatever the overlay package decided + // to leave to us to do. + rootfsMountOptions := strings.Join(rootfsMount.Options, ",") + logrus.Debugf("mounting %q to %q as %q with options %v", rootfsMount.Source, rootfsMount.Destination, rootfsMount.Type, rootfsMountOptions) + if err := mount.Mount(rootfsMount.Source, rootfsMount.Destination, rootfsMount.Type, rootfsMountOptions); err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("mounting overlay of container root filesystem: %w", err) + } + defer func() { + logrus.Debugf("unmounting %q", rootfsMount.Destination) + if err := mount.Unmount(rootfsMount.Destination); err != nil { + logger.Warnf("unmounting overlay of container root filesystem: %v", err) + } + }() + // Pretend that we didn't have to do any of the preceding. + rootfsPath = rootfsDir + + // Write extra content to the rootfs, creating intermediate directories if necessary. + for location, content := range options.ExtraImageContent { + err := func() error { + if err := idtools.MkdirAllAndChownNew(filepath.Dir(filepath.Join(rootfsPath, location)), 0o755, idtools.IDPair{UID: int(st.UID()), GID: int(st.GID())}); err != nil { + return fmt.Errorf("ensuring %q is present in container root filesystem: %w", filepath.Dir(location), err) + } + output, err := os.OpenFile(filepath.Join(rootfsPath, location), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) + if err != nil { + return fmt.Errorf("preparing to write %q to container root filesystem: %w", location, err) + } + defer output.Close() + input, err := os.Open(content) + if err != nil { + return err + } + defer input.Close() + if _, err := io.Copy(output, input); err != nil { + return fmt.Errorf("copying contents of %q to %q in container root filesystem: %w", content, location, err) + } + if err := output.Chown(int(st.UID()), int(st.GID())); err != nil { + return fmt.Errorf("setting owner of %q in the container root filesystem: %w", location, err) + } + if err := output.Chmod(0o644); err != nil { + return fmt.Errorf("setting permissions on %q in the container root filesystem: %w", location, err) + } + return nil + }() + if err != nil { + return nil, WorkloadConfig{}, err + } + } + + // Write part of the config blob where the krun init process will be + // looking for it. The oci2cw tool used `buildah inspect` output, but + // init is just looking for fields that have the right names in any + // object, and the image's config will have that, so let's try encoding + // it directly. + krunConfigPath := filepath.Join(rootfsPath, ".krun_config.json") + krunConfigBytes, err := json.Marshal(ociConfig) + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("creating .krun_config from image configuration: %w", err) + } + if err := ioutils.AtomicWriteFile(krunConfigPath, krunConfigBytes, 0o600); err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("saving krun config: %w", err) + } + + // Encode the workload config, in case it fails for any reason. + cleanedUpWorkloadConfig := workloadConfig + switch cleanedUpWorkloadConfig.Type { + default: + return nil, WorkloadConfig{}, fmt.Errorf("don't know how to canonicalize TEE type %q", cleanedUpWorkloadConfig.Type) + case SEV, SEV_NO_ES: + cleanedUpWorkloadConfig.Type = SEV + case SNP: + cleanedUpWorkloadConfig.Type = SNP + } + workloadConfigBytes, err := json.Marshal(cleanedUpWorkloadConfig) + if err != nil { + return nil, WorkloadConfig{}, err + } + + // Make sure we have the passphrase to use for encrypting the disk image. + diskEncryptionPassphrase := options.DiskEncryptionPassphrase + if diskEncryptionPassphrase == "" { + diskEncryptionPassphrase, err = GenerateDiskEncryptionPassphrase() + if err != nil { + return nil, WorkloadConfig{}, err + } + } + + // If we weren't told how big the image should be, get a rough estimate + // of the input data size, then add a hedge to it. + imageSize := slop(options.ImageSize, options.Slop) + if imageSize == 0 { + var sourceSize int64 + if err := filepath.WalkDir(rootfsPath, func(_ string, d fs.DirEntry, err error) error { + if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) { + return err + } + info, err := d.Info() + if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) { + return err + } + sourceSize += info.Size() + return nil + }); err != nil { + return nil, WorkloadConfig{}, err + } + imageSize = slop(sourceSize, options.Slop) + } + if imageSize%4096 != 0 { + imageSize += (4096 - (imageSize % 4096)) + } + if imageSize < minimumImageSize { + imageSize = minimumImageSize + } + + // Create a file to use as the unencrypted version of the disk image. + plain, err := os.CreateTemp(options.TempDir, "plain.img") + if err != nil { + return nil, WorkloadConfig{}, err + } + removePlain := true + defer func() { + if removePlain { + if err := os.Remove(plain.Name()); err != nil { + logger.Warnf("removing temporary file %q: %v", plain.Name(), err) + } + } + }() + + // Lengthen the plaintext disk image file. + if err := plain.Truncate(imageSize); err != nil { + plain.Close() + return nil, WorkloadConfig{}, err + } + plainInfo, err := plain.Stat() + plain.Close() + if err != nil { + return nil, WorkloadConfig{}, err + } + + // Format the disk image with the filesystem contents. + if _, stderr, err := MakeFS(rootfsPath, plain.Name(), filesystem); err != nil { + if strings.TrimSpace(stderr) != "" { + return nil, WorkloadConfig{}, fmt.Errorf("%s: %w", strings.TrimSpace(stderr), err) + } + return nil, WorkloadConfig{}, err + } + + // If we're registering the workload, we can do that now. + if workloadConfig.AttestationURL != "" { + if err := SendRegistrationRequest(workloadConfig, diskEncryptionPassphrase, options.FirmwareLibrary, options.IgnoreAttestationErrors, logger); err != nil { + return nil, WorkloadConfig{}, err + } + } + + // Try to encrypt on the fly. + pipeReader, pipeWriter := io.Pipe() + removePlain = false + go func() { + var err error + defer func() { + if err := os.Remove(plain.Name()); err != nil { + logger.Warnf("removing temporary file %q: %v", plain.Name(), err) + } + if err != nil { + pipeWriter.CloseWithError(err) + } else { + pipeWriter.Close() + } + }() + plain, err := os.Open(plain.Name()) + if err != nil { + logrus.Errorf("opening unencrypted disk image %q: %v", plain.Name(), err) + return + } + defer plain.Close() + tw := tar.NewWriter(pipeWriter) + defer tw.Flush() + + // Write /entrypoint + var decompressedEntrypoint bytes.Buffer + decompressor, err := gzip.NewReader(bytes.NewReader(entrypointCompressedBytes)) + if err != nil { + logrus.Errorf("decompressing copy of entrypoint: %v", err) + return + } + defer decompressor.Close() + if _, err = io.Copy(&decompressedEntrypoint, decompressor); err != nil { + logrus.Errorf("decompressing copy of entrypoint: %v", err) + return + } + entrypointHeader, err := tar.FileInfoHeader(plainInfo, "") + if err != nil { + logrus.Errorf("building header for entrypoint: %v", err) + return + } + entrypointHeader.Name = "entrypoint" + entrypointHeader.Mode = 0o755 + entrypointHeader.Uname, entrypointHeader.Gname = "", "" + entrypointHeader.Uid, entrypointHeader.Gid = 0, 0 + entrypointHeader.Size = int64(decompressedEntrypoint.Len()) + if err = tw.WriteHeader(entrypointHeader); err != nil { + logrus.Errorf("writing header for %q: %v", entrypointHeader.Name, err) + return + } + if _, err = io.Copy(tw, &decompressedEntrypoint); err != nil { + logrus.Errorf("writing %q: %v", entrypointHeader.Name, err) + return + } + + // Write /sev.chain + if chainInfo != nil { + chainHeader, err := tar.FileInfoHeader(chainInfo, "") + if err != nil { + logrus.Errorf("building header for %q: %v", chainInfo.Name(), err) + return + } + chainHeader.Name = chainBytesFile + chainHeader.Mode = 0o600 + chainHeader.Uname, chainHeader.Gname = "", "" + chainHeader.Uid, chainHeader.Gid = 0, 0 + chainHeader.Size = int64(len(chainBytes)) + if err = tw.WriteHeader(chainHeader); err != nil { + logrus.Errorf("writing header for %q: %v", chainHeader.Name, err) + return + } + if _, err = tw.Write(chainBytes); err != nil { + logrus.Errorf("writing %q: %v", chainHeader.Name, err) + return + } + } + + // Write /krun-sev.json. + workloadConfigHeader, err := tar.FileInfoHeader(plainInfo, "") + if err != nil { + logrus.Errorf("building header for %q: %v", plainInfo.Name(), err) + return + } + workloadConfigHeader.Name = "krun-sev.json" + workloadConfigHeader.Mode = 0o600 + workloadConfigHeader.Uname, workloadConfigHeader.Gname = "", "" + workloadConfigHeader.Uid, workloadConfigHeader.Gid = 0, 0 + workloadConfigHeader.Size = int64(len(workloadConfigBytes)) + if err = tw.WriteHeader(workloadConfigHeader); err != nil { + logrus.Errorf("writing header for %q: %v", workloadConfigHeader.Name, err) + return + } + if _, err = tw.Write(workloadConfigBytes); err != nil { + logrus.Errorf("writing %q: %v", workloadConfigHeader.Name, err) + return + } + + // Write /tmp. + tmpHeader, err := tar.FileInfoHeader(plainInfo, "") + if err != nil { + logrus.Errorf("building header for %q: %v", plainInfo.Name(), err) + return + } + tmpHeader.Name = "tmp/" + tmpHeader.Typeflag = tar.TypeDir + tmpHeader.Mode = 0o1777 + tmpHeader.Uname, tmpHeader.Gname = "", "" + tmpHeader.Uid, tmpHeader.Gid = 0, 0 + tmpHeader.Size = 0 + if err = tw.WriteHeader(tmpHeader); err != nil { + logrus.Errorf("writing header for %q: %v", tmpHeader.Name, err) + return + } + + // Now figure out the footer that we'll append to the encrypted disk. + var footer bytes.Buffer + lengthBuffer := make([]byte, 8) + footer.Write(workloadConfigBytes) + footer.WriteString("KRUN") + binary.LittleEndian.PutUint64(lengthBuffer, uint64(len(workloadConfigBytes))) + footer.Write(lengthBuffer) + + // Start encrypting and write /disk.img. + header, encrypt, blockSize, err := luksy.EncryptV1([]string{diskEncryptionPassphrase}, "") + paddingBoundary := int64(4096) + paddingNeeded := (paddingBoundary - ((int64(len(header)) + imageSize + int64(footer.Len())) % paddingBoundary)) % paddingBoundary + diskHeader := workloadConfigHeader + diskHeader.Name = "disk.img" + diskHeader.Mode = 0o600 + diskHeader.Size = int64(len(header)) + imageSize + paddingNeeded + int64(footer.Len()) + if err = tw.WriteHeader(diskHeader); err != nil { + logrus.Errorf("writing archive header for disk.img: %v", err) + return + } + if _, err = io.Copy(tw, bytes.NewReader(header)); err != nil { + logrus.Errorf("writing encryption header for disk.img: %v", err) + return + } + encryptWrapper := luksy.EncryptWriter(encrypt, tw, blockSize) + if _, err = io.Copy(encryptWrapper, plain); err != nil { + logrus.Errorf("encrypting disk.img: %v", err) + return + } + encryptWrapper.Close() + if _, err = tw.Write(make([]byte, paddingNeeded)); err != nil { + logrus.Errorf("writing padding for disk.img: %v", err) + return + } + if _, err = io.Copy(tw, &footer); err != nil { + logrus.Errorf("writing footer for disk.img: %v", err) + return + } + tw.Close() + }() + + return pipeReader, workloadConfig, nil +} + +func slop(size int64, slop string) int64 { + if slop == "" { + return size * 5 / 4 + } + for factor := range strings.SplitSeq(slop, "+") { + factor = strings.TrimSpace(factor) + if factor == "" { + continue + } + if strings.HasSuffix(factor, "%") { + percentage := strings.TrimSuffix(factor, "%") + percent, err := strconv.ParseInt(percentage, 10, 8) + if err != nil { + logrus.Warnf("parsing percentage %q: %v", factor, err) + } else { + size *= (percent + 100) + size /= 100 + } + } else { + more, err := units.RAMInBytes(factor) + if err != nil { + logrus.Warnf("parsing %q as a size: %v", factor, err) + } else { + size += more + } + } + } + return size +} diff --git a/vendor/github.com/containers/buildah/internal/mkcw/attest.go b/vendor/github.com/containers/buildah/internal/mkcw/attest.go new file mode 100644 index 00000000..b974ca52 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/mkcw/attest.go @@ -0,0 +1,251 @@ +package mkcw + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + "github.com/containers/buildah/internal/mkcw/types" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/fileutils" +) + +type ( + RegistrationRequest = types.RegistrationRequest + TeeConfig = types.TeeConfig + TeeConfigFlags = types.TeeConfigFlags + TeeConfigMinFW = types.TeeConfigMinFW +) + +type measurementError struct { + err error +} + +func (m measurementError) Error() string { + return fmt.Sprintf("generating measurement for attestation: %v", m.err) +} + +type attestationError struct { + err error +} + +func (a attestationError) Error() string { + return fmt.Sprintf("registering workload: %v", a.err) +} + +type httpError struct { + statusCode int +} + +func (h httpError) Error() string { + if statusText := http.StatusText(h.statusCode); statusText != "" { + return fmt.Sprintf("received server status %d (%q)", h.statusCode, statusText) + } + return fmt.Sprintf("received server status %d", h.statusCode) +} + +// SendRegistrationRequest registers a workload with the specified decryption +// passphrase with the service whose location is part of the WorkloadConfig. +func SendRegistrationRequest(workloadConfig WorkloadConfig, diskEncryptionPassphrase, firmwareLibrary string, ignoreAttestationErrors bool, logger *logrus.Logger) error { + if workloadConfig.AttestationURL == "" { + return errors.New("attestation URL not provided") + } + + // Measure the execution environment. + measurement, err := GenerateMeasurement(workloadConfig, firmwareLibrary) + if err != nil { + if !ignoreAttestationErrors { + return &measurementError{err} + } + logger.Warnf("generating measurement for attestation: %v", err) + } + + // Build the workload registration (attestation) request body. + var teeConfigBytes []byte + switch workloadConfig.Type { + case SEV, SEV_NO_ES, SNP: + var cbits types.TeeConfigFlagBits + switch workloadConfig.Type { + case SEV: + cbits = types.SEV_CONFIG_NO_DEBUG | + types.SEV_CONFIG_NO_KEY_SHARING | + types.SEV_CONFIG_ENCRYPTED_STATE | + types.SEV_CONFIG_NO_SEND | + types.SEV_CONFIG_DOMAIN | + types.SEV_CONFIG_SEV + case SEV_NO_ES: + cbits = types.SEV_CONFIG_NO_DEBUG | + types.SEV_CONFIG_NO_KEY_SHARING | + types.SEV_CONFIG_NO_SEND | + types.SEV_CONFIG_DOMAIN | + types.SEV_CONFIG_SEV + case SNP: + cbits = types.SNP_CONFIG_SMT | + types.SNP_CONFIG_MANDATORY | + types.SNP_CONFIG_MIGRATE_MA | + types.SNP_CONFIG_DEBUG + default: + panic("internal error") // shouldn't happen + } + teeConfig := TeeConfig{ + Flags: TeeConfigFlags{ + Bits: cbits, + }, + MinFW: TeeConfigMinFW{ + Major: 0, + Minor: 0, + }, + } + teeConfigBytes, err = json.Marshal(teeConfig) + if err != nil { + return err + } + default: + return fmt.Errorf("don't know how to generate tee_config for %q TEEs", workloadConfig.Type) + } + + registrationRequest := RegistrationRequest{ + WorkloadID: workloadConfig.WorkloadID, + LaunchMeasurement: measurement, + TeeConfig: string(teeConfigBytes), + Passphrase: diskEncryptionPassphrase, + } + registrationRequestBytes, err := json.Marshal(registrationRequest) + if err != nil { + return err + } + + // Register the workload. + parsedURL, err := url.Parse(workloadConfig.AttestationURL) + if err != nil { + return err + } + parsedURL.Path = path.Join(parsedURL.Path, "/kbs/v0/register_workload") + if err != nil { + return err + } + url := parsedURL.String() + requestContentType := "application/json" + requestBody := bytes.NewReader(registrationRequestBytes) + defer http.DefaultClient.CloseIdleConnections() + resp, err := http.Post(url, requestContentType, requestBody) + if resp != nil { + if resp.Body != nil { + resp.Body.Close() + } + switch resp.StatusCode { + default: + if !ignoreAttestationErrors { + return &attestationError{&httpError{resp.StatusCode}} + } + logger.Warn(attestationError{&httpError{resp.StatusCode}}.Error()) + case http.StatusOK, http.StatusAccepted: + // great! + } + } + if err != nil { + if !ignoreAttestationErrors { + return &attestationError{err} + } + logger.Warn(attestationError{err}.Error()) + } + return nil +} + +// GenerateMeasurement generates the runtime measurement using the CPU count, +// memory size, and the firmware shared library, whatever it's called, wherever +// it is. +// If firmwareLibrary is a path, it will be the only one checked. +// If firmwareLibrary is a filename, it will be checked for in a hard-coded set +// of directories. +// If firmwareLibrary is empty, both the filename and the directory it is in +// will be taken from a hard-coded set of candidates. +func GenerateMeasurement(workloadConfig WorkloadConfig, firmwareLibrary string) (string, error) { + cpuString := fmt.Sprintf("%d", workloadConfig.CPUs) + memoryString := fmt.Sprintf("%d", workloadConfig.Memory) + var prefix string + switch workloadConfig.Type { + case SEV: + prefix = "SEV-ES" + case SEV_NO_ES: + prefix = "SEV" + case SNP: + prefix = "SNP" + default: + return "", fmt.Errorf("don't know which measurement to use for TEE type %q", workloadConfig.Type) + } + + sharedLibraryDirs := []string{ + "/usr/local/lib64", + "/usr/local/lib", + "/lib64", + "/lib", + "/usr/lib64", + "/usr/lib", + } + if llp, ok := os.LookupEnv("LD_LIBRARY_PATH"); ok { + sharedLibraryDirs = append(sharedLibraryDirs, strings.Split(llp, ":")...) + } + libkrunfwNames := []string{ + "libkrunfw-sev.so.4", + "libkrunfw-sev.so.3", + "libkrunfw-sev.so", + } + var pathsToCheck []string + if firmwareLibrary == "" { + for _, sharedLibraryDir := range sharedLibraryDirs { + if sharedLibraryDir == "" { + continue + } + for _, libkrunfw := range libkrunfwNames { + candidate := filepath.Join(sharedLibraryDir, libkrunfw) + pathsToCheck = append(pathsToCheck, candidate) + } + } + } else { + if filepath.IsAbs(firmwareLibrary) { + pathsToCheck = append(pathsToCheck, firmwareLibrary) + } else { + for _, sharedLibraryDir := range sharedLibraryDirs { + if sharedLibraryDir == "" { + continue + } + candidate := filepath.Join(sharedLibraryDir, firmwareLibrary) + pathsToCheck = append(pathsToCheck, candidate) + } + } + } + for _, candidate := range pathsToCheck { + if err := fileutils.Lexists(candidate); err == nil { + var stdout, stderr bytes.Buffer + logrus.Debugf("krunfw_measurement -c %s -m %s %s", cpuString, memoryString, candidate) + cmd := exec.Command("krunfw_measurement", "-c", cpuString, "-m", memoryString, candidate) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + if stderr.Len() > 0 { + err = fmt.Errorf("krunfw_measurement: %s: %w", strings.TrimSpace(stderr.String()), err) + } + return "", err + } + scanner := bufio.NewScanner(&stdout) + for scanner.Scan() { + line := scanner.Text() + if after, ok := strings.CutPrefix(line, prefix+":"); ok { + return strings.TrimSpace(after), nil + } + } + return "", fmt.Errorf("generating measurement: no line starting with %q found in output from krunfw_measurement", prefix+":") + } + } + return "", fmt.Errorf("generating measurement: none of %v found: %w", pathsToCheck, os.ErrNotExist) +} diff --git a/vendor/github.com/containers/buildah/internal/mkcw/embed/entrypoint_amd64.gz b/vendor/github.com/containers/buildah/internal/mkcw/embed/entrypoint_amd64.gz new file mode 100644 index 00000000..95367081 Binary files /dev/null and b/vendor/github.com/containers/buildah/internal/mkcw/embed/entrypoint_amd64.gz differ diff --git a/vendor/github.com/containers/buildah/internal/mkcw/entrypoint.go b/vendor/github.com/containers/buildah/internal/mkcw/entrypoint.go new file mode 100644 index 00000000..c69a4a34 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/mkcw/entrypoint.go @@ -0,0 +1,6 @@ +package mkcw + +import _ "embed" + +//go:embed "embed/entrypoint_amd64.gz" +var entrypointCompressedBytes []byte diff --git a/vendor/github.com/containers/buildah/internal/mkcw/luks.go b/vendor/github.com/containers/buildah/internal/mkcw/luks.go new file mode 100644 index 00000000..0d795e6a --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/mkcw/luks.go @@ -0,0 +1,51 @@ +package mkcw + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "os" + + "github.com/containers/luksy" +) + +// CheckLUKSPassphrase checks that the specified LUKS-encrypted file can be +// decrypted using the specified passphrase. +func CheckLUKSPassphrase(path, decryptionPassphrase string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + v1header, v2headerA, v2headerB, v2json, err := luksy.ReadHeaders(f, luksy.ReadHeaderOptions{}) + if err != nil { + return err + } + if v1header != nil { + _, _, _, _, err = v1header.Decrypt(decryptionPassphrase, f) + return err + } + if v2headerA == nil && v2headerB == nil { + return fmt.Errorf("no LUKS headers read from %q", path) + } + if v2headerA != nil { + if _, _, _, _, err = v2headerA.Decrypt(decryptionPassphrase, f, *v2json); err != nil { + return err + } + } + if v2headerB != nil { + if _, _, _, _, err = v2headerB.Decrypt(decryptionPassphrase, f, *v2json); err != nil { + return err + } + } + return nil +} + +// GenerateDiskEncryptionPassphrase generates a random disk encryption password +func GenerateDiskEncryptionPassphrase() (string, error) { + randomizedBytes := make([]byte, 32) + if _, err := rand.Read(randomizedBytes); err != nil { + return "", err + } + return hex.EncodeToString(randomizedBytes), nil +} diff --git a/vendor/github.com/containers/buildah/internal/mkcw/makefs.go b/vendor/github.com/containers/buildah/internal/mkcw/makefs.go new file mode 100644 index 00000000..308f2a9d --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/mkcw/makefs.go @@ -0,0 +1,38 @@ +package mkcw + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/sirupsen/logrus" +) + +// MakeFS formats the imageFile as a filesystem of the specified type, +// populating it with the contents of the directory at sourcePath. +// Recognized filesystem types are "ext2", "ext3", "ext4", and "btrfs". +// Note that krun's init is currently hard-wired to assume "ext4". +// Returns the stdout, stderr, and any error returned by the mkfs command. +func MakeFS(sourcePath, imageFile, filesystem string) (string, string, error) { + var stdout, stderr strings.Builder + // N.B. mkfs.xfs can accept a protofile via its -p option, but the + // protofile format doesn't allow us to supply timestamp information or + // specify that files are hard linked + switch filesystem { + case "ext2", "ext3", "ext4": + logrus.Debugf("mkfs -t %s --rootdir %q %q", filesystem, sourcePath, imageFile) + cmd := exec.Command("mkfs", "-t", filesystem, "-d", sourcePath, imageFile) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + return stdout.String(), stderr.String(), err + case "btrfs": + logrus.Debugf("mkfs -t %s --rootdir %q %q", filesystem, sourcePath, imageFile) + cmd := exec.Command("mkfs", "-t", filesystem, "--rootdir", sourcePath, imageFile) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + return stdout.String(), stderr.String(), err + } + return "", "", fmt.Errorf("don't know how to make a %q filesystem with contents", filesystem) +} diff --git a/vendor/github.com/containers/buildah/internal/mkcw/types/attest.go b/vendor/github.com/containers/buildah/internal/mkcw/types/attest.go new file mode 100644 index 00000000..f92f72cb --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/mkcw/types/attest.go @@ -0,0 +1,48 @@ +package types + +// RegistrationRequest is the body of the request which we use for registering +// this confidential workload with the attestation server. +// https://github.com/virtee/reference-kbs/blob/10b2a4c0f8caf78a077210b172863bbae54f66aa/src/main.rs#L83 +type RegistrationRequest struct { + WorkloadID string `json:"workload_id"` + LaunchMeasurement string `json:"launch_measurement"` + Passphrase string `json:"passphrase"` + TeeConfig string `json:"tee_config"` // JSON-encoded teeConfig? or specific to the type of TEE? +} + +// TeeConfig contains information about a trusted execution environment. +type TeeConfig struct { + Flags TeeConfigFlags `json:"flags"` // runtime requirement bits + MinFW TeeConfigMinFW `json:"minfw"` // minimum platform firmware version +} + +// TeeConfigFlags is a bit field containing policy flags specific to the environment. +// https://github.com/virtee/sev/blob/d3e40917fd8531c69f47c2498e9667fe8a5303aa/src/launch/sev.rs#L172 +// https://github.com/virtee/sev/blob/d3e40917fd8531c69f47c2498e9667fe8a5303aa/src/launch/snp.rs#L114 +type TeeConfigFlags struct { + Bits TeeConfigFlagBits `json:"bits"` +} + +// TeeConfigFlagBits are bits representing run-time expectations. +type TeeConfigFlagBits int + +//nolint:revive,staticcheck // Don't warn about bad naming. +const ( + SEV_CONFIG_NO_DEBUG TeeConfigFlagBits = 0b00000001 // no debugging of guests + SEV_CONFIG_NO_KEY_SHARING TeeConfigFlagBits = 0b00000010 // no sharing keys between guests + SEV_CONFIG_ENCRYPTED_STATE TeeConfigFlagBits = 0b00000100 // requires SEV-ES + SEV_CONFIG_NO_SEND TeeConfigFlagBits = 0b00001000 // no transferring the guest to another platform + SEV_CONFIG_DOMAIN TeeConfigFlagBits = 0b00010000 // no transferring the guest out of the domain (?) + SEV_CONFIG_SEV TeeConfigFlagBits = 0b00100000 // no transferring the guest to non-SEV platforms + SNP_CONFIG_SMT TeeConfigFlagBits = 0b00000001 // SMT is enabled on the host machine + SNP_CONFIG_MANDATORY TeeConfigFlagBits = 0b00000010 // reserved bit which should always be set + SNP_CONFIG_MIGRATE_MA TeeConfigFlagBits = 0b00000100 // allowed to use a migration agent + SNP_CONFIG_DEBUG TeeConfigFlagBits = 0b00001000 // allow debugging +) + +// TeeConfigFlagMinFW corresponds to a minimum version of the kernel+initrd +// combination that should be booted. +type TeeConfigMinFW struct { + Major int `json:"major"` + Minor int `json:"minor"` +} diff --git a/vendor/github.com/containers/buildah/internal/mkcw/types/workload.go b/vendor/github.com/containers/buildah/internal/mkcw/types/workload.go new file mode 100644 index 00000000..a0e4f525 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/mkcw/types/workload.go @@ -0,0 +1,35 @@ +package types + +import "github.com/containers/buildah/define" + +// WorkloadConfig is the data type which is encoded and stored in /krun-sev.json in a container +// image, and included directly in the disk image. +// https://github.com/containers/libkrun/blob/57c59dc5359bdeeb8260b3493e9f63d3708f9ab9/src/vmm/src/resources.rs#L57 +type WorkloadConfig struct { + Type define.TeeType `json:"tee"` + TeeData string `json:"tee_data"` // Type == SEV: JSON-encoded SevWorkloadData, SNP: JSON-encoded SnpWorkloadData, others? + WorkloadID string `json:"workload_id"` + CPUs int `json:"cpus"` + Memory int `json:"ram_mib"` + AttestationURL string `json:"attestation_url"` +} + +// SevWorkloadData contains the path to the SEV certificate chain and optionally, +// the attestation server's public key(?) +// https://github.com/containers/libkrun/blob/d31747aa92cf83df2abaeb87e2a83311c135d003/src/vmm/src/linux/tee/amdsev.rs#L222 +type SevWorkloadData struct { + VendorChain string `json:"vendor_chain"` + AttestationServerPubkey string `json:"attestation_server_pubkey"` +} + +// SnpWorkloadData contains the required CPU generation name. +// https://github.com/virtee/oci2cw/blob/1502d5be33c2fa82d49aaa95781bbab2aa932781/examples/tee-config-snp.json +type SnpWorkloadData struct { + Generation string `json:"gen"` // "milan" (naples=1, rome=2, milan=3, genoa/bergamo/siena=4, turin=5) +} + +//nolint:revive,staticcheck // Don't warn about bad naming. +const ( + // SEV_NO_ES is a known trusted execution environment type: AMD-SEV (secure encrypted virtualization without encrypted state, requires epyc 1000 "naples") + SEV_NO_ES define.TeeType = "sev_no_es" +) diff --git a/vendor/github.com/containers/buildah/internal/mkcw/workload.go b/vendor/github.com/containers/buildah/internal/mkcw/workload.go new file mode 100644 index 00000000..e407b22f --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/mkcw/workload.go @@ -0,0 +1,228 @@ +package mkcw + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + "os" + + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal/mkcw/types" +) + +type ( + // WorkloadConfig is the data type which is encoded and stored in an image. + WorkloadConfig = types.WorkloadConfig + // SevWorkloadData is the type of data in WorkloadConfig.TeeData when the type is SEV. + SevWorkloadData = types.SevWorkloadData + // SnpWorkloadData is the type of data in WorkloadConfig.TeeData when the type is SNP. + SnpWorkloadData = types.SnpWorkloadData + // TeeType is one of the known types of trusted execution environments for which we + // can generate suitable image contents. + TeeType = define.TeeType +) + +const ( + maxWorkloadConfigSize = 1024 * 1024 + preferredPaddingBoundary = 4096 + + // krun looks for its configuration JSON directly in a disk image if the last twelve bytes + // of the disk image are this magic value followed by a little-endian 64-bit + // length-of-the-configuration + krunMagic = "KRUN" +) + +//nolint:revive,staticcheck +const ( + // SEV is a known trusted execution environment type: AMD-SEV + SEV = define.SEV + // SEV_NO_ES is a known trusted execution environment type: AMD-SEV without encrypted state + SEV_NO_ES = types.SEV_NO_ES + // SNP is a known trusted execution environment type: AMD-SNP + SNP = define.SNP +) + +// ReadWorkloadConfigFromImage reads the workload configuration from the +// specified disk image file +func ReadWorkloadConfigFromImage(path string) (WorkloadConfig, error) { + // Read the last 12 bytes, which should be "KRUN" followed by a 64-bit + // little-endian length. The (length) bytes immediately preceding + // these hold the JSON-encoded workloadConfig. + var wc WorkloadConfig + f, err := os.Open(path) + if err != nil { + return wc, err + } + defer f.Close() + + // Read those last 12 bytes. + finalTwelve := make([]byte, 12) + if _, err = f.Seek(-12, io.SeekEnd); err != nil { + return wc, fmt.Errorf("checking for workload config signature: %w", err) + } + if n, err := f.Read(finalTwelve); err != nil || n != len(finalTwelve) { + if err != nil && !errors.Is(err, io.EOF) { + return wc, fmt.Errorf("reading workload config signature (%d bytes read): %w", n, err) + } + if n != len(finalTwelve) { + return wc, fmt.Errorf("short read (expected 12 bytes at the end of %q, got %d)", path, n) + } + } + if magic := string(finalTwelve[0:4]); magic != "KRUN" { + return wc, fmt.Errorf("expected magic string KRUN in %q, found %q)", path, magic) + } + length := binary.LittleEndian.Uint64(finalTwelve[4:]) + if length > maxWorkloadConfigSize { + return wc, fmt.Errorf("workload config in %q is %d bytes long, which seems unreasonable (max allowed %d)", path, length, maxWorkloadConfigSize) + } + + // Read and decode the config. + configBytes := make([]byte, length) + if _, err = f.Seek(-(int64(length) + 12), io.SeekEnd); err != nil { + return wc, fmt.Errorf("looking for workload config from disk image: %w", err) + } + if n, err := f.Read(configBytes); err != nil || n != len(configBytes) { + if err != nil { + return wc, fmt.Errorf("reading workload config from disk image: %w", err) + } + return wc, fmt.Errorf("short read (expected %d bytes near the end of %q, got %d)", len(configBytes), path, n) + } + err = json.Unmarshal(configBytes, &wc) + if err != nil { + err = fmt.Errorf("unmarshalling configuration %q: %w", string(configBytes), err) + } + return wc, err +} + +// WriteWorkloadConfigToImage writes the workload configuration to the +// specified disk image file, overwriting a previous configuration if it's +// asked to and it finds one +func WriteWorkloadConfigToImage(imageFile *os.File, workloadConfigBytes []byte, overwrite bool) error { + // Read those last 12 bytes to check if there's a configuration there already, which we should overwrite. + var overwriteOffset int64 + if overwrite { + finalTwelve := make([]byte, 12) + if _, err := imageFile.Seek(-12, io.SeekEnd); err != nil { + return fmt.Errorf("checking for workload config signature: %w", err) + } + if n, err := imageFile.Read(finalTwelve); err != nil || n != len(finalTwelve) { + if err != nil && !errors.Is(err, io.EOF) { + return fmt.Errorf("reading workload config signature (%d bytes read): %w", n, err) + } + if n != len(finalTwelve) { + return fmt.Errorf("short read (expected 12 bytes at the end of %q, got %d)", imageFile.Name(), n) + } + } + if magic := string(finalTwelve[0:4]); magic == "KRUN" { + length := binary.LittleEndian.Uint64(finalTwelve[4:]) + if length < maxWorkloadConfigSize { + overwriteOffset = int64(length + 12) + } + } + } + // If we found a configuration in the file, try to figure out how much padding was used. + paddingSize := int64(preferredPaddingBoundary) + if overwriteOffset != 0 { + st, err := imageFile.Stat() + if err != nil { + return err + } + for _, possiblePaddingLength := range []int64{0x100000, 0x10000, 0x1000, 0x200, 0x100} { + if overwriteOffset > possiblePaddingLength { + continue + } + if st.Size()%possiblePaddingLength != 0 { + continue + } + if _, err := imageFile.Seek(-possiblePaddingLength, io.SeekEnd); err != nil { + return fmt.Errorf("checking size of padding at end of file: %w", err) + } + buf := make([]byte, possiblePaddingLength) + n, err := imageFile.Read(buf) + if err != nil { + return fmt.Errorf("reading possible padding at end of file: %w", err) + } + if n != len(buf) { + return fmt.Errorf("short read checking size of padding at end of file: %d != %d", n, len(buf)) + } + if bytes.Equal(buf[:possiblePaddingLength-overwriteOffset], make([]byte, possiblePaddingLength-overwriteOffset)) { + // everything up to the configuration was zero bytes, so it was padding + overwriteOffset = possiblePaddingLength + paddingSize = possiblePaddingLength + break + } + } + } + + // Append the krun configuration to a new buffer. + var formatted bytes.Buffer + nWritten, err := formatted.Write(workloadConfigBytes) + if err != nil { + return fmt.Errorf("building workload config: %w", err) + } + if nWritten != len(workloadConfigBytes) { + return fmt.Errorf("short write appending configuration to buffer: %d != %d", nWritten, len(workloadConfigBytes)) + } + // Append the magic string to the buffer. + nWritten, err = formatted.WriteString(krunMagic) + if err != nil { + return fmt.Errorf("building workload config signature: %w", err) + } + if nWritten != len(krunMagic) { + return fmt.Errorf("short write appending krun magic to buffer: %d != %d", nWritten, len(krunMagic)) + } + // Append the 64-bit little-endian length of the workload configuration to the buffer. + workloadConfigLengthBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(workloadConfigLengthBytes, uint64(len(workloadConfigBytes))) + nWritten, err = formatted.Write(workloadConfigLengthBytes) + if err != nil { + return fmt.Errorf("building workload config signature size: %w", err) + } + if nWritten != len(workloadConfigLengthBytes) { + return fmt.Errorf("short write appending configuration length to buffer: %d != %d", nWritten, len(workloadConfigLengthBytes)) + } + + // Build a copy of that data, with padding preceding it. + var padded bytes.Buffer + if int64(formatted.Len())%paddingSize != 0 { + extra := paddingSize - (int64(formatted.Len()) % paddingSize) + nWritten, err := padded.Write(make([]byte, extra)) + if err != nil { + return fmt.Errorf("buffering padding: %w", err) + } + if int64(nWritten) != extra { + return fmt.Errorf("short write buffering padding for disk image: %d != %d", nWritten, extra) + } + } + extra := int64(formatted.Len()) + nWritten, err = padded.Write(formatted.Bytes()) + if err != nil { + return fmt.Errorf("buffering workload config: %w", err) + } + if int64(nWritten) != extra { + return fmt.Errorf("short write buffering workload config: %d != %d", nWritten, extra) + } + + // Write the buffer to the file, starting with padding. + if _, err = imageFile.Seek(-overwriteOffset, io.SeekEnd); err != nil { + return fmt.Errorf("preparing to write workload config: %w", err) + } + nWritten, err = imageFile.Write(padded.Bytes()) + if err != nil { + return fmt.Errorf("writing workload config: %w", err) + } + if nWritten != padded.Len() { + return fmt.Errorf("short write writing configuration to disk image: %d != %d", nWritten, padded.Len()) + } + offset, err := imageFile.Seek(0, io.SeekCurrent) + if err != nil { + return fmt.Errorf("preparing mark end of disk image: %w", err) + } + if err = imageFile.Truncate(offset); err != nil { + return fmt.Errorf("marking end of disk image: %w", err) + } + return nil +} diff --git a/vendor/github.com/containers/buildah/internal/open/open.go b/vendor/github.com/containers/buildah/internal/open/open.go new file mode 100644 index 00000000..863dc79f --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open.go @@ -0,0 +1,39 @@ +package open + +import ( + "errors" + "fmt" + "syscall" +) + +// InChroot opens the file at `path` after chrooting to `root` and then +// changing its working directory to `wd`. Both `wd` and `path` are evaluated +// in the chroot. +// Returns a file handle, an Errno value if there was an error and the +// underlying error was a standard library error code, and a non-empty error if +// one was detected. +func InChroot(root, wd, path string, mode int, perm uint32) (fd int, errno syscall.Errno, err error) { + requests := requests{ + Root: root, + Wd: wd, + Open: []request{ + { + Path: path, + Mode: mode, + Perms: perm, + }, + }, + } + results := inChroot(requests) + if len(results.Open) != 1 { + return -1, 0, fmt.Errorf("got %d results back instead of 1", len(results.Open)) + } + if results.Open[0].Err != "" { + if results.Open[0].Errno != 0 { + err = fmt.Errorf("%s: %w", results.Open[0].Err, results.Open[0].Errno) + } else { + err = errors.New(results.Open[0].Err) + } + } + return int(results.Open[0].Fd), results.Open[0].Errno, err +} diff --git a/vendor/github.com/containers/buildah/internal/open/open_linux.go b/vendor/github.com/containers/buildah/internal/open/open_linux.go new file mode 100644 index 00000000..d93511dc --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open_linux.go @@ -0,0 +1,88 @@ +package open + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "os" + "strings" + + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/reexec" + "golang.org/x/sys/unix" +) + +const ( + bindFdToPathCommand = "buildah-bind-fd-to-path" +) + +func init() { + reexec.Register(bindFdToPathCommand, bindFdToPathMain) +} + +// BindFdToPath creates a bind mount from the open file (which is actually a +// directory) to the specified location. If it succeeds, the caller will need +// to unmount the targetPath when it's finished using it. Regardless, it +// closes the passed-in descriptor. +func BindFdToPath(fd uintptr, targetPath string) error { + f := os.NewFile(fd, "passed-in directory descriptor") + defer func() { + if err := f.Close(); err != nil { + logrus.Debugf("closing descriptor %d after attempting to bind to %q: %v", fd, targetPath, err) + } + }() + pipeReader, pipeWriter, err := os.Pipe() + if err != nil { + return err + } + cmd := reexec.Command(bindFdToPathCommand) + cmd.Stdin = pipeReader + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout, cmd.Stderr = &stdout, &stderr + cmd.ExtraFiles = append(cmd.ExtraFiles, f) + + err = cmd.Start() + pipeReader.Close() + if err != nil { + pipeWriter.Close() + return fmt.Errorf("starting child: %w", err) + } + + encoder := json.NewEncoder(pipeWriter) + if err := encoder.Encode(&targetPath); err != nil { + return fmt.Errorf("sending target path to child: %w", err) + } + pipeWriter.Close() + err = cmd.Wait() + trimmedOutput := strings.TrimSpace(stdout.String()) + strings.TrimSpace(stderr.String()) + if err != nil { + if len(trimmedOutput) > 0 { + err = fmt.Errorf("%s: %w", trimmedOutput, err) + } + } else { + if len(trimmedOutput) > 0 { + err = errors.New(trimmedOutput) + } + } + return err +} + +func bindFdToPathMain() { + var targetPath string + decoder := json.NewDecoder(os.Stdin) + if err := decoder.Decode(&targetPath); err != nil { + fmt.Fprintf(os.Stderr, "error decoding target path") + os.Exit(1) + } + if err := unix.Fchdir(3); err != nil { + fmt.Fprintf(os.Stderr, "fchdir(): %v", err) + os.Exit(1) + } + if err := unix.Mount(".", targetPath, "bind", unix.MS_BIND, ""); err != nil { + fmt.Fprintf(os.Stderr, "bind-mounting passed-in directory to %q: %v", targetPath, err) + os.Exit(1) + } + os.Exit(0) +} diff --git a/vendor/github.com/containers/buildah/internal/open/open_types.go b/vendor/github.com/containers/buildah/internal/open/open_types.go new file mode 100644 index 00000000..11dbe38d --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open_types.go @@ -0,0 +1,28 @@ +package open + +import ( + "syscall" +) + +type request struct { + Path string + Mode int + Perms uint32 +} + +type requests struct { + Root string + Wd string + Open []request +} + +type result struct { + Fd uintptr // as returned by open() + Err string // if err was not `nil`, err.Error() + Errno syscall.Errno // if err was not `nil` and included a syscall.Errno, its value +} + +type results struct { + Err string + Open []result +} diff --git a/vendor/github.com/containers/buildah/internal/open/open_unix.go b/vendor/github.com/containers/buildah/internal/open/open_unix.go new file mode 100644 index 00000000..274203d6 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open_unix.go @@ -0,0 +1,168 @@ +//go:build linux || freebsd || darwin + +package open + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "os" + "syscall" + + "go.podman.io/storage/pkg/reexec" + "golang.org/x/sys/unix" +) + +const ( + inChrootCommand = "buildah-open-in-chroot" +) + +func init() { + reexec.Register(inChrootCommand, inChrootMain) +} + +func inChroot(requests requests) results { + sock, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) + if err != nil { + return results{Err: fmt.Errorf("creating socket pair: %w", err).Error()} + } + parentSock := sock[0] + childSock := sock[1] + parentEnd := os.NewFile(uintptr(parentSock), "parent end of socket pair") + childEnd := os.NewFile(uintptr(childSock), "child end of socket pair") + cmd := reexec.Command(inChrootCommand) + cmd.ExtraFiles = append(cmd.ExtraFiles, childEnd) + err = cmd.Start() + childEnd.Close() + defer parentEnd.Close() + if err != nil { + return results{Err: err.Error()} + } + encoder := json.NewEncoder(parentEnd) + if err := encoder.Encode(&requests); err != nil { + return results{Err: fmt.Errorf("sending request down socket: %w", err).Error()} + } + if err := unix.Shutdown(parentSock, unix.SHUT_WR); err != nil { + return results{Err: fmt.Errorf("finishing sending request down socket: %w", err).Error()} + } + b := make([]byte, 65536) + oob := make([]byte, 65536) + n, oobn, _, _, err := unix.Recvmsg(parentSock, b, oob, 0) + if err != nil { + return results{Err: fmt.Errorf("receiving message: %w", err).Error()} + } + if err := unix.Shutdown(parentSock, unix.SHUT_RD); err != nil { + return results{Err: fmt.Errorf("finishing socket: %w", err).Error()} + } + if n > len(b) { + return results{Err: fmt.Errorf("too much regular data: %d > %d", n, len(b)).Error()} + } + if oobn > len(oob) { + return results{Err: fmt.Errorf("too much OOB data: %d > %d", oobn, len(oob)).Error()} + } + scms, err := unix.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + return results{Err: fmt.Errorf("parsing control message: %w", err).Error()} + } + var receivedFds []int + for i := range scms { + fds, err := unix.ParseUnixRights(&scms[i]) + if err != nil { + return results{Err: fmt.Errorf("parsing rights message %d: %w", i, err).Error()} + } + receivedFds = append(receivedFds, fds...) + } + decoder := json.NewDecoder(bytes.NewReader(b[:n])) + var result results + if err := decoder.Decode(&result); err != nil { + return results{Err: fmt.Errorf("decoding results: %w", err).Error()} + } + j := 0 + for i := range result.Open { + if result.Open[i].Err == "" { + if j >= len(receivedFds) { + for _, fd := range receivedFds { + unix.Close(fd) + } + return results{Err: fmt.Errorf("didn't receive enough FDs").Error()} + } + result.Open[i].Fd = uintptr(receivedFds[j]) + j++ + } + } + return result +} + +func inChrootMain() { + var theseRequests requests + var theseResults results + sockFd := 3 + sock := os.NewFile(uintptr(sockFd), "socket connection to parent process") + defer sock.Close() + encoder := json.NewEncoder(sock) + decoder := json.NewDecoder(sock) + if err := decoder.Decode(&theseRequests); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("decoding request: %w", err).Error()}); err != nil { + os.Exit(1) + } + } + if theseRequests.Root != "" { + if err := os.Chdir(theseRequests.Root); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("changing to %q: %w", theseRequests.Root, err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + if err := unix.Chroot(theseRequests.Root); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("chrooting to %q: %w", theseRequests.Root, err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + if err := os.Chdir("/"); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("changing to new root: %w", err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + } + if theseRequests.Wd != "" { + if err := os.Chdir(theseRequests.Wd); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("changing to %q in chroot: %w", theseRequests.Wd, err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + } + var fds []int + for _, request := range theseRequests.Open { + fd, err := unix.Open(request.Path, request.Mode, request.Perms) + thisResult := result{Fd: uintptr(fd)} + if err == nil { + fds = append(fds, fd) + } else { + var errno syscall.Errno + thisResult.Err = err.Error() + if errors.As(err, &errno) { + thisResult.Errno = errno + } + } + theseResults.Open = append(theseResults.Open, thisResult) + } + rights := unix.UnixRights(fds...) + inband, err := json.Marshal(&theseResults) + if err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("sending response: %w", err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + if err := unix.Sendmsg(sockFd, inband, rights, nil, 0); err != nil { + if err := encoder.Encode(results{Err: fmt.Errorf("sending response: %w", err).Error()}); err != nil { + os.Exit(1) + } + os.Exit(1) + } + os.Exit(0) +} diff --git a/vendor/github.com/containers/buildah/internal/open/open_unsupported.go b/vendor/github.com/containers/buildah/internal/open/open_unsupported.go new file mode 100644 index 00000000..111056a1 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/open/open_unsupported.go @@ -0,0 +1,7 @@ +//go:build !linux && !freebsd && !darwin + +package open + +func inChroot(requests requests) results { + return results{Err: "open-in-chroot not available on this platform"} +} diff --git a/vendor/github.com/containers/buildah/internal/parse/parse.go b/vendor/github.com/containers/buildah/internal/parse/parse.go new file mode 100644 index 00000000..bd618cb3 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/parse/parse.go @@ -0,0 +1,79 @@ +package parse + +import ( + "fmt" + "path/filepath" + "strings" + + specs "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/common/pkg/parse" + "go.podman.io/storage/pkg/fileutils" +) + +// ValidateVolumeMountHostDir validates the host path of buildah --volume +func ValidateVolumeMountHostDir(hostDir string) error { + if !filepath.IsAbs(hostDir) { + return fmt.Errorf("invalid host path, must be an absolute path %q", hostDir) + } + if err := fileutils.Exists(hostDir); err != nil { + return err + } + return nil +} + +// RevertEscapedColon converts "\:" to ":" +func RevertEscapedColon(source string) string { + return strings.ReplaceAll(source, "\\:", ":") +} + +// SplitStringWithColonEscape splits string into slice by colon. Backslash-escaped colon (i.e. "\:") will not be regarded as separator +func SplitStringWithColonEscape(str string) []string { + result := make([]string, 0, 3) + sb := &strings.Builder{} + for idx, r := range str { + if r == ':' { + // the colon is backslash-escaped + if idx-1 > 0 && str[idx-1] == '\\' { + sb.WriteRune(r) + } else { + // os.Stat will fail if path contains escaped colon + result = append(result, RevertEscapedColon(sb.String())) + sb.Reset() + } + } else { + sb.WriteRune(r) + } + } + if sb.Len() > 0 { + result = append(result, RevertEscapedColon(sb.String())) + } + return result +} + +// Volume parses the input of --volume +func Volume(volume string) (specs.Mount, error) { + mount := specs.Mount{} + arr := SplitStringWithColonEscape(volume) + if len(arr) < 2 { + return mount, fmt.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume) + } + if err := ValidateVolumeMountHostDir(arr[0]); err != nil { + return mount, err + } + if err := parse.ValidateVolumeCtrDir(arr[1]); err != nil { + return mount, err + } + mountOptions := "" + if len(arr) > 2 { + mountOptions = arr[2] + if _, err := parse.ValidateVolumeOpts(strings.Split(arr[2], ",")); err != nil { + return mount, err + } + } + mountOpts := strings.Split(mountOptions, ",") + mount.Source = arr[0] + mount.Destination = arr[1] + mount.Type = "rbind" + mount.Options = mountOpts + return mount, nil +} diff --git a/vendor/github.com/containers/buildah/internal/pty/pty_posix.go b/vendor/github.com/containers/buildah/internal/pty/pty_posix.go new file mode 100644 index 00000000..56383da4 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/pty/pty_posix.go @@ -0,0 +1,65 @@ +//go:build freebsd && cgo + +package pty + +// #include +// #include +import "C" + +import ( + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +func openpt() (int, error) { + fd, err := C.posix_openpt(C.O_RDWR) + if err != nil { + return -1, err + } + if _, err := C.grantpt(fd); err != nil { + return -1, err + } + return int(fd), nil +} + +func ptsname(fd int) (string, error) { + path, err := C.ptsname(C.int(fd)) + if err != nil { + return "", err + } + return C.GoString(path), nil +} + +func unlockpt(fd int) error { + if _, err := C.unlockpt(C.int(fd)); err != nil { + return err + } + return nil +} + +// GetPtyDescriptors allocates a new pseudoterminal and returns the control and +// pseudoterminal file descriptors. +func GetPtyDescriptors() (int, int, error) { + // Create a pseudo-terminal and open the control side + controlFd, err := openpt() + if err != nil { + logrus.Errorf("error opening PTY control side using posix_openpt: %v", err) + return -1, -1, err + } + if err = unlockpt(controlFd); err != nil { + logrus.Errorf("error unlocking PTY: %v", err) + return -1, -1, err + } + // Get a handle for the other end. + ptyName, err := ptsname(controlFd) + if err != nil { + logrus.Errorf("error getting PTY name: %v", err) + return -1, -1, err + } + ptyFd, err := unix.Open(ptyName, unix.O_RDWR, 0) + if err != nil { + logrus.Errorf("error opening PTY: %v", err) + return -1, -1, err + } + return controlFd, ptyFd, nil +} diff --git a/vendor/github.com/containers/buildah/internal/pty/pty_ptmx.go b/vendor/github.com/containers/buildah/internal/pty/pty_ptmx.go new file mode 100644 index 00000000..d60d2d81 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/pty/pty_ptmx.go @@ -0,0 +1,48 @@ +//go:build linux + +package pty + +import ( + "fmt" + "os" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +// GetPtyDescriptors allocates a new pseudoterminal and returns the control and +// pseudoterminal file descriptors. This implementation uses the /dev/ptmx +// device. The main advantage of using this instead of posix_openpt is that it +// avoids cgo. +func GetPtyDescriptors() (int, int, error) { + // Create a pseudo-terminal -- open a copy of the master side. + controlFd, err := unix.Open("/dev/ptmx", os.O_RDWR, 0o600) + if err != nil { + return -1, -1, fmt.Errorf("opening PTY master using /dev/ptmx: %v", err) + } + // Set the kernel's lock to "unlocked". + locked := 0 + if result, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(controlFd), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&locked))); int(result) == -1 { + return -1, -1, fmt.Errorf("unlocking PTY descriptor: %v", err) + } + // Get a handle for the other end. + ptyFd, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(controlFd), unix.TIOCGPTPEER, unix.O_RDWR|unix.O_NOCTTY) + if int(ptyFd) == -1 { + if errno, isErrno := err.(syscall.Errno); !isErrno || (errno != syscall.EINVAL && errno != syscall.ENOTTY) { + return -1, -1, fmt.Errorf("getting PTY descriptor: %v", err) + } + // EINVAL means the kernel's too old to understand TIOCGPTPEER. Try TIOCGPTN. + ptyN, err := unix.IoctlGetInt(controlFd, unix.TIOCGPTN) + if err != nil { + return -1, -1, fmt.Errorf("getting PTY number: %v", err) + } + ptyName := fmt.Sprintf("/dev/pts/%d", ptyN) + fd, err := unix.Open(ptyName, unix.O_RDWR|unix.O_NOCTTY, 0o620) + if err != nil { + return -1, -1, fmt.Errorf("opening PTY %q: %v", ptyName, err) + } + ptyFd = uintptr(fd) + } + return controlFd, int(ptyFd), nil +} diff --git a/vendor/github.com/containers/buildah/internal/pty/pty_unsupported.go b/vendor/github.com/containers/buildah/internal/pty/pty_unsupported.go new file mode 100644 index 00000000..10416394 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/pty/pty_unsupported.go @@ -0,0 +1,13 @@ +//go:build !linux && !(freebsd && cgo) + +package pty + +import ( + "errors" +) + +// GetPtyDescriptors would allocate a new pseudoterminal and return the control and +// pseudoterminal file descriptors, if only it could. +func GetPtyDescriptors() (int, int, error) { + return -1, -1, errors.New("GetPtyDescriptors not supported on this platform") +} diff --git a/vendor/github.com/containers/buildah/internal/sbom/merge.go b/vendor/github.com/containers/buildah/internal/sbom/merge.go new file mode 100644 index 00000000..57487b08 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/sbom/merge.go @@ -0,0 +1,296 @@ +package sbom + +import ( + "encoding/json" + "fmt" + "io" + "os" + "sort" + + "github.com/containers/buildah/define" +) + +// getComponentNameVersionPurl extracts the "name", "version", and "purl" +// fields of a CycloneDX component record +func getComponentNameVersionPurl(anyComponent any) (string, string, error) { + if component, ok := anyComponent.(map[string]any); ok { + // read the "name" field + anyName, ok := component["name"] + if !ok { + return "", "", fmt.Errorf("no name in component %v", anyComponent) + } + name, ok := anyName.(string) + if !ok { + return "", "", fmt.Errorf("name %v is not a string", anyName) + } + // read the optional "version" field + var version string + anyVersion, ok := component["version"] + if ok { + if version, ok = anyVersion.(string); !ok { + return "", "", fmt.Errorf("version %v is not a string", anyVersion) + } + } + // combine them + nameWithVersion := name + if version != "" { + nameWithVersion += ("@" + version) + } + // read the optional "purl" field + var purl string + anyPurl, ok := component["purl"] + if ok { + if purl, ok = anyPurl.(string); !ok { + return "", "", fmt.Errorf("purl %v is not a string", anyPurl) + } + } + return nameWithVersion, purl, nil + } + return "", "", fmt.Errorf("component %v is not an object", anyComponent) +} + +// getPackageNameVersionInfoPurl extracts the "name", "versionInfo", and "purl" +// fields of an SPDX package record +func getPackageNameVersionInfoPurl(anyPackage any) (string, string, error) { + if pkg, ok := anyPackage.(map[string]any); ok { + // read the "name" field + anyName, ok := pkg["name"] + if !ok { + return "", "", fmt.Errorf("no name in package %v", anyPackage) + } + name, ok := anyName.(string) + if !ok { + return "", "", fmt.Errorf("name %v is not a string", anyName) + } + // read the optional "versionInfo" field + var versionInfo string + if anyVersionInfo, ok := pkg["versionInfo"]; ok { + if versionInfo, ok = anyVersionInfo.(string); !ok { + return "", "", fmt.Errorf("versionInfo %v is not a string", anyVersionInfo) + } + } + // combine them + nameWithVersionInfo := name + if versionInfo != "" { + nameWithVersionInfo += ("@" + versionInfo) + } + // now look for optional externalRefs[].purl if "referenceCategory" + // is "PACKAGE-MANAGER" and "referenceType" is "purl" + var purl string + if anyExternalRefs, ok := pkg["externalRefs"]; ok { + if externalRefs, ok := anyExternalRefs.([]any); ok { + for _, anyExternalRef := range externalRefs { + if externalRef, ok := anyExternalRef.(map[string]any); ok { + anyReferenceCategory, ok := externalRef["referenceCategory"] + if !ok { + continue + } + if referenceCategory, ok := anyReferenceCategory.(string); !ok || referenceCategory != "PACKAGE-MANAGER" { + continue + } + anyReferenceType, ok := externalRef["referenceType"] + if !ok { + continue + } + if referenceType, ok := anyReferenceType.(string); !ok || referenceType != "purl" { + continue + } + if anyReferenceLocator, ok := externalRef["referenceLocator"]; ok { + if purl, ok = anyReferenceLocator.(string); !ok { + return "", "", fmt.Errorf("purl %v is not a string", anyReferenceLocator) + } + } + } + } + } + } + return nameWithVersionInfo, purl, nil + } + return "", "", fmt.Errorf("package %v is not an object", anyPackage) +} + +// getLicenseID extracts the "licenseId" field of an SPDX license record +func getLicenseID(anyLicense any) (string, error) { + var licenseID string + if lic, ok := anyLicense.(map[string]any); ok { + anyID, ok := lic["licenseId"] + if !ok { + return "", fmt.Errorf("no licenseId in license %v", anyID) + } + id, ok := anyID.(string) + if !ok { + return "", fmt.Errorf("licenseId %v is not a string", anyID) + } + licenseID = id + } + return licenseID, nil +} + +// mergeSlicesWithoutDuplicates merges a named slice in "base" with items from +// the same slice in "merge", so long as getKey() returns values for them that +// it didn't for items from the "base" slice +func mergeSlicesWithoutDuplicates(base, merge map[string]any, sliceField string, getKey func(record any) (string, error)) error { + uniqueKeys := make(map[string]struct{}) + // go through all of the values in the base slice, grab their + // keys, and note them + baseRecords := base[sliceField] + baseRecordsSlice, ok := baseRecords.([]any) + if !ok { + baseRecordsSlice = []any{} + } + for _, anyRecord := range baseRecordsSlice { + key, err := getKey(anyRecord) + if err != nil { + return err + } + uniqueKeys[key] = struct{}{} + } + // go through all of the record values in the merge doc, grab their + // associated keys, and append them to the base records slice if we + // haven't seen the key yet + mergeRecords := merge[sliceField] + mergeRecordsSlice, ok := mergeRecords.([]any) + if !ok { + mergeRecordsSlice = []any{} + } + for _, anyRecord := range mergeRecordsSlice { + key, err := getKey(anyRecord) + if err != nil { + return err + } + if _, present := uniqueKeys[key]; !present { + baseRecordsSlice = append(baseRecordsSlice, anyRecord) + uniqueKeys[key] = struct{}{} + } + } + if len(baseRecordsSlice) > 0 { + base[sliceField] = baseRecordsSlice + } + return nil +} + +// decodeJSON decodes a file into a map +func decodeJSON(inputFile string, document *map[string]any) error { + src, err := os.Open(inputFile) + if err != nil { + return err + } + defer src.Close() + if err = json.NewDecoder(src).Decode(document); err != nil { + return fmt.Errorf("decoding JSON document from %q: %w", inputFile, err) + } + return nil +} + +// encodeJSON encodes a map and saves it to a file +func encodeJSON(outputFile string, document any) error { + dst, err := os.Create(outputFile) + if err != nil { + return err + } + defer dst.Close() + if err = json.NewEncoder(dst).Encode(document); err != nil { + return fmt.Errorf("writing JSON document to %q: %w", outputFile, err) + } + return nil +} + +// Merge adds the contents of inputSBOM to inputOutputSBOM using one of a +// handful of named strategies. +func Merge(mergeStrategy define.SBOMMergeStrategy, inputOutputSBOM, inputSBOM, outputPURL string) (err error) { + type purlImageContents struct { + Dependencies []string `json:"dependencies,omitempty"` + } + type purlDocument struct { + ImageContents purlImageContents `json:"image_contents"` + } + purls := []string{} + seenPurls := make(map[string]struct{}) + + switch mergeStrategy { + case define.SBOMMergeStrategyCycloneDXByComponentNameAndVersion: + var base, merge map[string]any + if err = decodeJSON(inputOutputSBOM, &base); err != nil { + return fmt.Errorf("reading first SBOM to be merged from %q: %w", inputOutputSBOM, err) + } + if err = decodeJSON(inputSBOM, &merge); err != nil { + return fmt.Errorf("reading second SBOM to be merged from %q: %w", inputSBOM, err) + } + + // merge the "components" lists based on unique combinations of + // "name" and "version" fields, and save unique package URL + // values + err = mergeSlicesWithoutDuplicates(base, merge, "components", func(anyPackage any) (string, error) { + nameWithVersion, purl, err := getComponentNameVersionPurl(anyPackage) + if purl != "" { + if _, seen := seenPurls[purl]; !seen { + purls = append(purls, purl) + seenPurls[purl] = struct{}{} + } + } + return nameWithVersion, err + }) + if err != nil { + return fmt.Errorf("merging the %q field of CycloneDX SBOMs: %w", "components", err) + } + + // save the updated doc + err = encodeJSON(inputOutputSBOM, base) + + case define.SBOMMergeStrategySPDXByPackageNameAndVersionInfo: + var base, merge map[string]any + if err = decodeJSON(inputOutputSBOM, &base); err != nil { + return fmt.Errorf("reading first SBOM to be merged from %q: %w", inputOutputSBOM, err) + } + if err = decodeJSON(inputSBOM, &merge); err != nil { + return fmt.Errorf("reading second SBOM to be merged from %q: %w", inputSBOM, err) + } + + // merge the "packages" lists based on unique combinations of + // "name" and "versionInfo" fields, and save unique package URL + // values + err = mergeSlicesWithoutDuplicates(base, merge, "packages", func(anyPackage any) (string, error) { + nameWithVersionInfo, purl, err := getPackageNameVersionInfoPurl(anyPackage) + if purl != "" { + if _, seen := seenPurls[purl]; !seen { + purls = append(purls, purl) + seenPurls[purl] = struct{}{} + } + } + return nameWithVersionInfo, err + }) + if err != nil { + return fmt.Errorf("merging the %q field of SPDX SBOMs: %w", "packages", err) + } + + // merge the "hasExtractedLicensingInfos" lists based on unique + // "licenseId" values + err = mergeSlicesWithoutDuplicates(base, merge, "hasExtractedLicensingInfos", getLicenseID) + if err != nil { + return fmt.Errorf("merging the %q field of SPDX SBOMs: %w", "hasExtractedLicensingInfos", err) + } + + // save the updated doc + err = encodeJSON(inputOutputSBOM, base) + + case define.SBOMMergeStrategyCat: + dst, err := os.OpenFile(inputOutputSBOM, os.O_RDWR|os.O_APPEND, 0o644) + if err != nil { + return err + } + defer dst.Close() + src, err := os.Open(inputSBOM) + if err != nil { + return err + } + defer src.Close() + if _, err = io.Copy(dst, src); err != nil { + return err + } + } + if err == nil { + sort.Strings(purls) + err = encodeJSON(outputPURL, &purlDocument{purlImageContents{Dependencies: purls}}) + } + return err +} diff --git a/vendor/github.com/containers/buildah/internal/sbom/presets.go b/vendor/github.com/containers/buildah/internal/sbom/presets.go new file mode 100644 index 00000000..43ea9a81 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/sbom/presets.go @@ -0,0 +1,67 @@ +package sbom + +import ( + "slices" + + "github.com/containers/buildah/define" +) + +// Preset returns a predefined SBOMScanOptions structure that has the passed-in +// name as one of its "Type" values. +func Preset(name string) (preset *define.SBOMScanOptions, err error) { + // If you change these, make sure you update references in + // buildah-commit.1.md and buildah-build.1.md to match! + presets := []define.SBOMScanOptions{ + { + Type: []string{"", "syft", "syft-cyclonedx"}, + Image: "ghcr.io/anchore/syft", + Commands: []string{ + "/syft scan -q dir:{ROOTFS} --output cyclonedx-json={OUTPUT}", + "/syft scan -q dir:{CONTEXT} --output cyclonedx-json={OUTPUT}", + }, + // ImageSBOMOutput: "/root/buildinfo/content_manifests/sbom-cyclonedx.json", + // ImagePURLOutput: "/root/buildinfo/content_manifests/sbom-purl.json", + MergeStrategy: define.SBOMMergeStrategyCycloneDXByComponentNameAndVersion, + }, + { + Type: []string{"syft-spdx"}, + Image: "ghcr.io/anchore/syft", + Commands: []string{ + "/syft scan -q dir:{ROOTFS} --output spdx-json={OUTPUT}", + "/syft scan -q dir:{CONTEXT} --output spdx-json={OUTPUT}", + }, + // ImageSBOMOutput: "/root/buildinfo/content_manifests/sbom-spdx.json", + // ImagePURLOutput: "/root/buildinfo/content_manifests/sbom-purl.json", + MergeStrategy: define.SBOMMergeStrategySPDXByPackageNameAndVersionInfo, + }, + + { + Type: []string{"trivy", "trivy-cyclonedx"}, + Image: "ghcr.io/aquasecurity/trivy", + Commands: []string{ + "trivy filesystem -q {ROOTFS} --format cyclonedx --output {OUTPUT}", + "trivy filesystem -q {CONTEXT} --format cyclonedx --output {OUTPUT}", + }, + // ImageSBOMOutput: "/root/buildinfo/content_manifests/sbom-cyclonedx.json", + // ImagePURLOutput: "/root/buildinfo/content_manifests/sbom-purl.json", + MergeStrategy: define.SBOMMergeStrategyCycloneDXByComponentNameAndVersion, + }, + { + Type: []string{"trivy-spdx"}, + Image: "ghcr.io/aquasecurity/trivy", + Commands: []string{ + "trivy filesystem -q {ROOTFS} --format spdx-json --output {OUTPUT}", + "trivy filesystem -q {CONTEXT} --format spdx-json --output {OUTPUT}", + }, + // ImageSBOMOutput: "/root/buildinfo/content_manifests/sbom-spdx.json", + // ImagePURLOutput: "/root/buildinfo/content_manifests/sbom-purl.json", + MergeStrategy: define.SBOMMergeStrategySPDXByPackageNameAndVersionInfo, + }, + } + for _, preset := range presets { + if slices.Contains(preset.Type, name) { + return &preset, nil + } + } + return nil, nil +} diff --git a/vendor/github.com/containers/buildah/internal/tmpdir/tmpdir.go b/vendor/github.com/containers/buildah/internal/tmpdir/tmpdir.go new file mode 100644 index 00000000..27b435d2 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/tmpdir/tmpdir.go @@ -0,0 +1,26 @@ +package tmpdir + +import ( + "os" + "path/filepath" + + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/config" +) + +// GetTempDir returns the path of the preferred temporary directory on the host. +func GetTempDir() string { + if tmpdir, ok := os.LookupEnv("TMPDIR"); ok { + abs, err := filepath.Abs(tmpdir) + if err == nil { + return abs + } + logrus.Warnf("ignoring TMPDIR from environment, evaluating it: %v", err) + } + if containerConfig, err := config.Default(); err == nil { + if tmpdir, err := containerConfig.ImageCopyTmpDir(); err == nil { + return tmpdir + } + } + return "/var/tmp" +} diff --git a/vendor/github.com/containers/buildah/internal/types.go b/vendor/github.com/containers/buildah/internal/types.go new file mode 100644 index 00000000..47061c4a --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/types.go @@ -0,0 +1,25 @@ +package internal + +const ( + // BuildahExternalArtifactsDir is the pattern passed to os.MkdirTemp() + // to generate a temporary directory which will be used to hold + // external items which are downloaded for a build, typically a tarball + // being used as an additional build context. + BuildahExternalArtifactsDir = "buildah-external-artifacts" + // SourceDateEpochName is both the name of the SOURCE_DATE_EPOCH + // environment variable and the built-in ARG that carries its value, + // whether it's read from the environment by our main(), or passed in + // via CLI or API flags. + SourceDateEpochName = "SOURCE_DATE_EPOCH" +) + +// StageMountDetails holds the Stage/Image mountpoint returned by StageExecutor +// StageExecutor has ability to mount stages/images in current context and +// automatically clean them up. +type StageMountDetails struct { + DidExecute bool // true if this is a freshly-executed stage, or an image, possibly from a non-local cache + IsStage bool // true if the mountpoint is a stage's rootfs + IsImage bool // true if the mountpoint is an image's rootfs + IsAdditionalBuildContext bool // true if the mountpoint is an additional build context + MountPoint string // mountpoint of the stage or image's root directory or path of the additional build context +} diff --git a/vendor/github.com/containers/buildah/internal/util/util.go b/vendor/github.com/containers/buildah/internal/util/util.go new file mode 100644 index 00000000..b230d0e3 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/util/util.go @@ -0,0 +1,99 @@ +package util + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/containers/buildah/define" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "go.podman.io/common/libimage" + lplatform "go.podman.io/common/libimage/platform" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/chrootarchive" + "go.podman.io/storage/pkg/unshare" +) + +// LookupImage returns *Image to corresponding imagename or id +func LookupImage(ctx *types.SystemContext, store storage.Store, image string) (*libimage.Image, error) { + systemContext := ctx + if systemContext == nil { + systemContext = &types.SystemContext{} + } + runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) + if err != nil { + return nil, err + } + localImage, _, err := runtime.LookupImage(image, nil) + if err != nil { + return nil, err + } + return localImage, nil +} + +// NormalizePlatform validates and translate the platform to the canonical value. +// +// For example, if "Aarch64" is encountered, we change it to "arm64" or if +// "x86_64" is encountered, it becomes "amd64". +// +// Wrapper around libimage.NormalizePlatform to return and consume +// v1.Platform instead of independent os, arch and variant. +func NormalizePlatform(platform v1.Platform) v1.Platform { + os, arch, variant := lplatform.Normalize(platform.OS, platform.Architecture, platform.Variant) + return v1.Platform{ + OS: os, + Architecture: arch, + Variant: variant, + } +} + +// ExportFromReader reads bytes from given reader and exports to external tar, directory or stdout. +func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error { + var err error + if !filepath.IsAbs(opts.Path) { + if opts.Path, err = filepath.Abs(opts.Path); err != nil { + return err + } + } + if opts.IsDir { + // In order to keep this feature as close as possible to + // buildkit it was decided to preserve ownership when + // invoked as root since caller already has access to artifacts + // therefore we can preserve ownership as is, however for rootless users + // ownership has to be changed so exported artifacts can still + // be accessible by unprivileged users. + // See: https://github.com/containers/buildah/pull/3823#discussion_r829376633 + noLChown := false + if unshare.IsRootless() { + noLChown = true + } + + if err = os.MkdirAll(opts.Path, 0o700); err != nil { + return fmt.Errorf("failed while creating the destination path %q: %w", opts.Path, err) + } + + if err = chrootarchive.Untar(input, opts.Path, &archive.TarOptions{NoLchown: noLChown}); err != nil { + return fmt.Errorf("failed while performing untar at %q: %w", opts.Path, err) + } + } else { + outFile := os.Stdout + if !opts.IsStdout { + if outFile, err = os.Create(opts.Path); err != nil { + return fmt.Errorf("failed while creating destination tar at %q: %w", opts.Path, err) + } + defer outFile.Close() + } + if _, err = io.Copy(outFile, input); err != nil { + return fmt.Errorf("failed while performing copy to %q: %w", opts.Path, err) + } + } + return nil +} + +func SetHas[K comparable, V any](m map[K]V, k K) bool { + _, ok := m[k] + return ok +} diff --git a/vendor/github.com/containers/buildah/internal/volumes/bind_linux.go b/vendor/github.com/containers/buildah/internal/volumes/bind_linux.go new file mode 100644 index 00000000..dfd5373c --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/volumes/bind_linux.go @@ -0,0 +1,102 @@ +package volumes + +import ( + "errors" + "fmt" + "os" + + "github.com/containers/buildah/internal/open" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/mount" + "golang.org/x/sys/unix" +) + +// bindFromChroot opens "path" inside of "root" using a chrooted subprocess +// that returns a descriptor, then creates a uniquely-named temporary directory +// or file under "tmp" and bind-mounts the opened descriptor to it, returning +// the path of the temporary file or directory. The caller is responsible for +// unmounting and removing the temporary. +func bindFromChroot(root, path, tmp string) (string, error) { + fd, _, err := open.InChroot(root, "", path, unix.O_DIRECTORY|unix.O_RDONLY, 0) + if err != nil { + if !errors.Is(err, unix.ENOTDIR) { + return "", fmt.Errorf("opening directory %q under %q: %w", path, root, err) + } + fd, _, err = open.InChroot(root, "", path, unix.O_RDWR, 0) + if err != nil { + return "", fmt.Errorf("opening non-directory %q under %q: %w", path, root, err) + } + } + defer func() { + if err := unix.Close(fd); err != nil { + logrus.Debugf("closing %q under %q: %v", path, root, err) + } + }() + + succeeded := false + var dest string + var destF *os.File + defer func() { + if !succeeded { + if destF != nil { + if err := destF.Close(); err != nil { + logrus.Debugf("closing bind target %q: %v", dest, err) + } + } + if dest != "" { + if err := os.Remove(dest); err != nil { + logrus.Debugf("removing bind target %q: %v", dest, err) + } + } + } + }() + + var st unix.Stat_t + if err = unix.Fstat(fd, &st); err != nil { + return "", fmt.Errorf("checking if %q under %q was a directory: %w", path, root, err) + } + + if st.Mode&unix.S_IFDIR == unix.S_IFDIR { + if dest, err = os.MkdirTemp(tmp, "bind"); err != nil { + return "", fmt.Errorf("creating a bind target directory: %w", err) + } + } else { + if destF, err = os.CreateTemp(tmp, "bind"); err != nil { + return "", fmt.Errorf("creating a bind target non-directory: %w", err) + } + if err := destF.Close(); err != nil { + logrus.Debugf("closing bind target %q: %v", dest, err) + } + dest = destF.Name() + } + defer func() { + if !succeeded { + if err := os.Remove(dest); err != nil { + logrus.Debugf("removing bind target %q: %v", dest, err) + } + } + }() + + if err := unix.Mount(fmt.Sprintf("/proc/self/fd/%d", fd), dest, "bind", unix.MS_BIND, ""); err != nil { + return "", fmt.Errorf("bind-mounting passed-in descriptor to %q: %w", dest, err) + } + defer func() { + if !succeeded { + if err := mount.Unmount(dest); err != nil { + logrus.Debugf("unmounting bound target %q: %v", dest, err) + } + } + }() + + var st2 unix.Stat_t + if err = unix.Stat(dest, &st2); err != nil { + return "", fmt.Errorf("looking up device/inode of newly-bind-mounted %q: %w", dest, err) + } + + if st2.Dev != st.Dev || st2.Ino != st.Ino { + return "", fmt.Errorf("device/inode weren't what we expected after bind mounting: %w", err) + } + + succeeded = true + return dest, nil +} diff --git a/vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go b/vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go new file mode 100644 index 00000000..d9340c18 --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/volumes/bind_notlinux.go @@ -0,0 +1,15 @@ +//go:build !linux + +package volumes + +import "errors" + +// bindFromChroot would open "path" inside of "root" using a chrooted +// subprocess that returns a descriptor, then would create a uniquely-named +// temporary directory or file under "tmp" and bind-mount the opened descriptor +// to it, returning the path of the temporary file or directory. The caller +// would be responsible for unmounting and removing the temporary. For now, +// this just returns an error because it is not implemented for this platform. +func bindFromChroot(root, path, tmp string) (string, error) { + return "", errors.New("not available on this system") +} diff --git a/vendor/github.com/containers/buildah/internal/volumes/volumes.go b/vendor/github.com/containers/buildah/internal/volumes/volumes.go new file mode 100644 index 00000000..8b4b30cd --- /dev/null +++ b/vendor/github.com/containers/buildah/internal/volumes/volumes.go @@ -0,0 +1,942 @@ +package volumes + +import ( + "context" + "errors" + "fmt" + "os" + "path" + "path/filepath" + "slices" + "strconv" + "strings" + + "github.com/containers/buildah/copier" + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal" + internalParse "github.com/containers/buildah/internal/parse" + "github.com/containers/buildah/internal/tmpdir" + internalUtil "github.com/containers/buildah/internal/util" + "github.com/containers/buildah/pkg/overlay" + "github.com/containers/buildah/util" + digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/runtime-spec/specs-go" + selinux "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/parse" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/lockfile" + "go.podman.io/storage/pkg/mount" + "go.podman.io/storage/pkg/unshare" +) + +const ( + // TypeTmpfs is the type for mounting tmpfs + TypeTmpfs = "tmpfs" + // TypeCache is the type for mounting a common persistent cache from host + TypeCache = "cache" + // mount=type=cache must create a persistent directory on host so its available for all consecutive builds. + // Lifecycle of following directory will be inherited from how host machine treats temporary directory + buildahCacheDir = "buildah-cache" + // mount=type=cache allows users to lock a cache store while its being used by another build + BuildahCacheLockfile = "buildah-cache-lockfile" + // All the lockfiles are stored in a separate directory inside `BuildahCacheDir` + // Example `/var/tmp/buildah-cache//buildah-cache-lockfile` + BuildahCacheLockfileDir = "buildah-cache-lockfiles" +) + +var ( + errBadMntOption = errors.New("invalid mount option") + errBadOptionArg = errors.New("must provide an argument for option") + errBadOptionNoArg = errors.New("must not provide an argument for option") + errBadVolDest = errors.New("must set volume destination") + errBadVolSrc = errors.New("must set volume source") + errDuplicateDest = errors.New("duplicate mount destination") +) + +// CacheParent returns a cache parent for --mount=type=cache +func CacheParent() string { + return filepath.Join(tmpdir.GetTempDir(), buildahCacheDir+"-"+strconv.Itoa(unshare.GetRootlessUID())) +} + +func mountIsReadWrite(m specs.Mount) bool { + // in case of conflicts, the last one wins, so it's not enough + // to check for the presence of either "rw" or "ro" anywhere + // with e.g. slices.Contains() + rw := true + for _, option := range m.Options { + switch option { + case "rw": + rw = true + case "ro": + rw = false + } + } + return rw +} + +func convertToOverlay(m specs.Mount, store storage.Store, mountLabel, tmpDir string, uid, gid int) (specs.Mount, string, error) { + overlayDir, err := overlay.TempDir(tmpDir, uid, gid) + if err != nil { + return specs.Mount{}, "", fmt.Errorf("setting up overlay for %q: %w", m.Destination, err) + } + options := overlay.Options{GraphOpts: slices.Clone(store.GraphOptions()), ForceMount: true, MountLabel: mountLabel} + fileInfo, err := os.Stat(m.Source) + if err != nil { + return specs.Mount{}, "", fmt.Errorf("setting up overlay of %q: %w", m.Source, err) + } + // we might be trying to "overlay" for a non-directory, and the kernel doesn't like that very much + var mountThisInstead specs.Mount + if fileInfo.IsDir() { + // do the normal thing of mounting this directory as a lower with a temporary upper + mountThisInstead, err = overlay.MountWithOptions(overlayDir, m.Source, m.Destination, &options) + if err != nil { + return specs.Mount{}, "", fmt.Errorf("setting up overlay of %q: %w", m.Source, err) + } + } else { + // mount the parent directory as the lower with a temporary upper, and return a + // bind mount from the non-directory in the merged directory to the destination + sourceDir := filepath.Dir(m.Source) + sourceBase := filepath.Base(m.Source) + destination := m.Destination + mountedOverlay, err := overlay.MountWithOptions(overlayDir, sourceDir, destination, &options) + if err != nil { + return specs.Mount{}, "", fmt.Errorf("setting up overlay of %q: %w", sourceDir, err) + } + if mountedOverlay.Type != define.TypeBind { + if err2 := overlay.RemoveTemp(overlayDir); err2 != nil { + return specs.Mount{}, "", fmt.Errorf("cleaning up after failing to set up overlay: %v, while setting up overlay for %q: %w", err2, destination, err) + } + return specs.Mount{}, "", fmt.Errorf("setting up overlay for %q at %q: %w", mountedOverlay.Source, destination, err) + } + mountThisInstead = mountedOverlay + mountThisInstead.Source = filepath.Join(mountedOverlay.Source, sourceBase) + mountThisInstead.Destination = destination + } + return mountThisInstead, overlayDir, nil +} + +// FIXME: this code needs to be merged with pkg/parse/parse.go ValidateVolumeOpts +// +// GetBindMount parses a single bind mount entry from the --mount flag. +// +// Returns a Mount to add to the runtime spec's list of mounts, the ID of the +// image we mounted if we mounted one, the path of a mounted location if one +// needs to be unmounted and removed, and the path of an overlay mount if one +// needs to be cleaned up, or an error. +// +// The caller is expected to, after the command which uses the mount exits, +// clean up the overlay filesystem (if we provided a path to it), unmount and +// remove the mountpoint for the mounted filesystem (if we provided the path to +// its mountpoint), and then unmount the image (if we mounted one). +func GetBindMount(sys *types.SystemContext, args []string, contextDir string, store storage.Store, mountLabel string, additionalMountPoints map[string]internal.StageMountDetails, workDir, tmpDir string) (specs.Mount, string, string, string, error) { + newMount := specs.Mount{ + Type: define.TypeBind, + } + + setRelabel := "" + mountReadability := "" + setDest := "" + bindNonRecursive := false + fromWhere := "" + + for _, val := range args { + argName, argValue, hasArgValue := strings.Cut(val, "=") + switch argName { + case "type": + // This is already processed, and should be "bind" + continue + case "bind-nonrecursive": + if hasArgValue { + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, "bind") + bindNonRecursive = true + case "nosuid", "nodev", "noexec": + if hasArgValue { + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, argName) + case "rw", "readwrite": + if hasArgValue { + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, "rw") + mountReadability = "rw" + case "ro", "readonly": + if hasArgValue { + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, "ro") + mountReadability = "ro" + case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z", "U", "no-dereference": + if hasArgValue { + return newMount, "", "", "", fmt.Errorf("%v: %w", val, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, argName) + case "from": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + fromWhere = argValue + case "bind-propagation": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + switch argValue { + default: + return newMount, "", "", "", fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption) + case "shared", "rshared", "private", "rprivate", "slave", "rslave": + // this should be the relevant parts of the same list of options we accepted above + } + newMount.Options = append(newMount.Options, argValue) + case "src", "source": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + newMount.Source = argValue + case "target", "dst", "destination": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + targetPath := argValue + setDest = targetPath + if !path.IsAbs(targetPath) { + targetPath = filepath.Join(workDir, targetPath) + } + if err := parse.ValidateVolumeCtrDir(targetPath); err != nil { + return newMount, "", "", "", err + } + newMount.Destination = targetPath + case "relabel": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + if setRelabel != "" { + return newMount, "", "", "", fmt.Errorf("cannot pass 'relabel' option more than once: %w", errBadOptionArg) + } + setRelabel = argValue + switch argValue { + case "private": + newMount.Options = append(newMount.Options, "Z") + case "shared": + newMount.Options = append(newMount.Options, "z") + default: + return newMount, "", "", "", fmt.Errorf("%s mount option must be 'private' or 'shared': %w", argName, errBadMntOption) + } + case "consistency": + // Option for OS X only, has no meaning on other platforms + // and can thus be safely ignored. + // See also the handling of the equivalent "delegated" and "cached" in ValidateVolumeOpts + default: + return newMount, "", "", "", fmt.Errorf("%v: %w", argName, errBadMntOption) + } + } + + // default mount readability is always readonly + if mountReadability == "" { + newMount.Options = append(newMount.Options, "ro") + } + + // Following variable ensures that we return imagename only if we did additional mount + succeeded := false + mountedImage := "" + if fromWhere != "" { + mountPoint := "" + if additionalMountPoints != nil { + if val, ok := additionalMountPoints[fromWhere]; ok { + mountPoint = val.MountPoint + } + } + // if mountPoint of image was not found in additionalMap + // or additionalMap was nil, try mounting image + if mountPoint == "" { + image, err := internalUtil.LookupImage(sys, store, fromWhere) + if err != nil { + return newMount, "", "", "", err + } + + mountPoint, err = image.Mount(context.Background(), nil, mountLabel) + if err != nil { + return newMount, "", "", "", err + } + mountedImage = image.ID() + // unmount the image if we don't end up returning successfully + defer func() { + if !succeeded { + if _, err := store.UnmountImage(mountedImage, false); err != nil { + logrus.Debugf("unmounting bind-mounted image %q: %v", fromWhere, err) + } + } + }() + } + contextDir = mountPoint + } + + // buildkit parity: default bind option must be `rbind` + // unless specified + if !bindNonRecursive { + newMount.Options = append(newMount.Options, "rbind") + } + + if setDest == "" { + return newMount, "", "", "", errBadVolDest + } + + // buildkit parity: support absolute path for sources from current build context + if contextDir != "" { + // path should be /contextDir/specified path + evaluated, err := copier.Eval(contextDir, contextDir+string(filepath.Separator)+newMount.Source, copier.EvalOptions{}) + if err != nil { + return newMount, "", "", "", err + } + newMount.Source = evaluated + } else { + // looks like its coming from `build run --mount=type=bind` allow using absolute path + // error out if no source is set + if newMount.Source == "" { + return newMount, "", "", "", errBadVolSrc + } + if err := parse.ValidateVolumeHostDir(newMount.Source); err != nil { + return newMount, "", "", "", err + } + } + + opts, err := parse.ValidateVolumeOpts(newMount.Options) + if err != nil { + return newMount, "", "", "", err + } + newMount.Options = opts + + var intermediateMount string + if contextDir != "" && newMount.Source != contextDir { + rel, err := filepath.Rel(contextDir, newMount.Source) + if err != nil { + return newMount, "", "", "", fmt.Errorf("computing pathname of bind subdirectory: %w", err) + } + if rel != "." && rel != "/" { + mnt, err := bindFromChroot(contextDir, rel, tmpDir) + if err != nil { + return newMount, "", "", "", fmt.Errorf("sanitizing bind subdirectory %q: %w", newMount.Source, err) + } + logrus.Debugf("bind-mounted %q under %q to %q", rel, contextDir, mnt) + intermediateMount = mnt + newMount.Source = intermediateMount + } + } + + overlayDir := "" + if mountedImage != "" || mountIsReadWrite(newMount) { + if newMount, overlayDir, err = convertToOverlay(newMount, store, mountLabel, tmpDir, 0, 0); err != nil { + return newMount, "", "", "", err + } + } + + succeeded = true + return newMount, mountedImage, intermediateMount, overlayDir, nil +} + +// GetCacheMount parses a single cache mount entry from the --mount flag. +// +// Returns a Mount to add to the runtime spec's list of mounts, the ID of the +// image we mounted if we mounted one, the path of a mounted filesystem if one +// needs to be unmounted, the path of an overlay if one needs to be cleaned up, +// and an optional lock that needs to be released, or an error. +// +// The caller is expected to, after the command which uses the mount exits, +// clean up the overlay filesystem (if we provided the path of one), unmount +// and remove the mountpoint of the mounted filesystem (if we provided the path +// to its mountpoint), unmount the image (if we mounted one), and release the +// lock (if we took one). +func GetCacheMount(sys *types.SystemContext, args []string, store storage.Store, mountLabel string, additionalMountPoints map[string]internal.StageMountDetails, uidmap, gidmap []specs.LinuxIDMapping, workDir, tmpDir string) (specs.Mount, string, string, string, *lockfile.LockFile, error) { + var err error + var mode uint64 + var buildahLockFilesDir string + var setShared bool + setDest := "" + setRelabel := "" + setReadOnly := "" + fromWhere := "" + newMount := specs.Mount{ + Type: define.TypeBind, + } + // if id is set a new subdirectory with `id` will be created under /host-temp/buildah-build-cache/id + id := "" + // buildkit parity: cache directory defaults to 0o755 + mode = 0o755 + // buildkit parity: cache directory defaults to uid 0 if not specified + uid := uint64(0) + // buildkit parity: cache directory defaults to gid 0 if not specified + gid := uint64(0) + // sharing mode + sharing := "shared" + + for _, val := range args { + argName, argValue, hasArgValue := strings.Cut(val, "=") + switch argName { + case "type": + // This is already processed, and should be "cache" + continue + case "nosuid", "nodev", "noexec", "U": + if hasArgValue { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, argName) + case "rw", "readwrite": + if hasArgValue { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, "rw") + setReadOnly = "rw" + case "readonly", "ro": + if hasArgValue { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, "ro") + setReadOnly = "ro" + case "Z", "z": + if hasArgValue { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, argName) + setRelabel = argName + case "shared", "rshared", "private", "rprivate", "slave", "rslave": + if hasArgValue { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, argName) + setShared = true + case "sharing": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + sharing = argValue + case "bind-propagation": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + switch argValue { + default: + return newMount, "", "", "", nil, fmt.Errorf("%v: %q: %w", argName, argValue, errBadMntOption) + case "shared", "rshared", "private", "rprivate", "slave", "rslave": + // this should be the relevant parts of the same list of options we accepted above + } + newMount.Options = append(newMount.Options, argValue) + setShared = true + case "id": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + id = argValue + case "from": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + fromWhere = argValue + case "target", "dst", "destination": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + targetPath := argValue + if !path.IsAbs(targetPath) { + targetPath = filepath.Join(workDir, targetPath) + } + if err := parse.ValidateVolumeCtrDir(targetPath); err != nil { + return newMount, "", "", "", nil, err + } + newMount.Destination = targetPath + setDest = targetPath + case "src", "source": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + newMount.Source = argValue + case "mode": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + mode, err = strconv.ParseUint(argValue, 8, 32) + if err != nil { + return newMount, "", "", "", nil, fmt.Errorf("unable to parse cache mode %q: %w", argValue, err) + } + case "uid": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + uid, err = strconv.ParseUint(argValue, 10, 32) + if err != nil { + return newMount, "", "", "", nil, fmt.Errorf("unable to parse cache uid %q: %w", argValue, err) + } + case "gid": + if !hasArgValue || argValue == "" { + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + gid, err = strconv.ParseUint(argValue, 10, 32) + if err != nil { + return newMount, "", "", "", nil, fmt.Errorf("unable to parse cache gid %q: %w", argValue, err) + } + default: + return newMount, "", "", "", nil, fmt.Errorf("%v: %w", argName, errBadMntOption) + } + } + + // If selinux is enabled and no selinux option was configured + // default to `z` i.e shared content label. + if setRelabel == "" && (selinux.EnforceMode() != selinux.Disabled) && fromWhere == "" { + newMount.Options = append(newMount.Options, "z") + } + + if setDest == "" { + return newMount, "", "", "", nil, errBadVolDest + } + + hostUID, hostGID, err := util.GetHostIDs(uidmap, gidmap, uint32(uid), uint32(gid)) + if err != nil { + return newMount, "", "", "", nil, err + } + + succeeded := false + needToOverlay := false + mountedImage := "" + thisCacheRoot := "" + if fromWhere != "" { + // do not create and use a cache directory on the host, + // instead use the location in the mounted stage or + // temporary directory as the cache + mountPoint := "" + if additionalMountPoints != nil { + if val, ok := additionalMountPoints[fromWhere]; ok { + mountPoint = val.MountPoint + needToOverlay = val.IsImage + } + } + // it's not an additional build context, stage, or + // already-mounted image, but it might still be an image + if mountPoint == "" { + image, err := internalUtil.LookupImage(sys, store, fromWhere) + if err != nil { + return newMount, "", "", "", nil, err + } + + mountPoint, err = image.Mount(context.Background(), nil, mountLabel) + if err != nil { + return newMount, "", "", "", nil, err + } + // unmount the image if we don't end up returning successfully + mountedImage = image.ID() + defer func() { + if !succeeded { + if _, err := store.UnmountImage(mountedImage, false); err != nil { + logrus.Debugf("unmounting image %q: %v", fromWhere, err) + } + } + }() + needToOverlay = true + } + thisCacheRoot = mountPoint + + // decide where the lock file for this cache's root should go, if we need one + cacheParent := CacheParent() + mountPointID := digest.FromString(mountPoint).Encoded()[:16] + buildahLockFilesDir = filepath.Join(cacheParent, BuildahCacheLockfileDir, mountPointID) + } else { + // we need to create the cache directory on the host if no stage is being used + + // since type is cache and a cache can be reused by consecutive builds + // create a common cache directory, which persists on hosts within temp lifecycle + // add subdirectory if specified + + // cache parent directory: creates separate cache parent for each user. + cacheParent := CacheParent() + + // create cache on host if not present + err = os.MkdirAll(cacheParent, os.FileMode(0o755)) + if err != nil { + return newMount, "", "", "", nil, fmt.Errorf("unable to create build cache directory: %w", err) + } + + ownerInfo := fmt.Sprintf(":%d:%d", uid, gid) + if id != "" { + // Don't let the user try to inject pathname components by directly using + // the ID when constructing the cache directory location; distinguish + // between caches by ID and ownership + dirID := digest.FromString(id + ownerInfo).Encoded()[:16] + thisCacheRoot = filepath.Join(cacheParent, dirID) + buildahLockFilesDir = filepath.Join(cacheParent, BuildahCacheLockfileDir, dirID) + } else { + // Don't let the user try to inject pathname components by directly using + // the target path when constructing the cache directory location; + // distinguish between caches by mount target location and ownership + dirID := digest.FromString(newMount.Destination + ownerInfo).Encoded()[:16] + thisCacheRoot = filepath.Join(cacheParent, dirID) + buildahLockFilesDir = filepath.Join(cacheParent, BuildahCacheLockfileDir, dirID) + } + + idPair := idtools.IDPair{ + UID: int(hostUID), + GID: int(hostGID), + } + + // buildkit parity: change uid and gid if specified, otherwise keep `0` + err = idtools.MkdirAllAndChownNew(thisCacheRoot, os.FileMode(mode), idPair) + if err != nil { + return newMount, "", "", "", nil, fmt.Errorf("unable to change uid,gid of cache directory: %w", err) + } + } + + // path should be /mountPoint/specified path + evaluated, err := copier.Eval(thisCacheRoot, thisCacheRoot+string(filepath.Separator)+newMount.Source, copier.EvalOptions{}) + if err != nil { + return newMount, "", "", "", nil, err + } + newMount.Source = evaluated + + var targetLock *lockfile.LockFile + switch sharing { + case "locked": + // create cache parent directories on host if not already present + err = os.MkdirAll(buildahLockFilesDir, os.FileMode(0o755)) + if err != nil { + return newMount, "", "", "", nil, fmt.Errorf("unable to create build cache directory: %w", err) + } + + // lock parent cache + lockfile, err := lockfile.GetLockFile(filepath.Join(buildahLockFilesDir, BuildahCacheLockfile)) + if err != nil { + return newMount, "", "", "", nil, fmt.Errorf("unable to acquire lock when sharing mode is locked: %w", err) + } + + // will be unlocked after the RUN step is executed + lockfile.Lock() + targetLock = lockfile + defer func() { + if !succeeded { + targetLock.Unlock() + } + }() + case "shared": + // do nothing since default is `shared` + break + default: + // error out for unknown values + return newMount, "", "", "", nil, fmt.Errorf("unrecognized value %q for field `sharing`: %w", sharing, err) + } + + // buildkit parity: default sharing should be shared + // unless specified + if !setShared { + newMount.Options = append(newMount.Options, "shared") + } + + // buildkit parity: cache must be writable unless `ro` or `readonly` is configured explicitly + if setReadOnly == "" { + newMount.Options = append(newMount.Options, "rw") + } + + newMount.Options = append(newMount.Options, "bind") + + opts, err := parse.ValidateVolumeOpts(newMount.Options) + if err != nil { + return newMount, "", "", "", nil, err + } + newMount.Options = opts + + var intermediateMount string + if newMount.Source != thisCacheRoot { + rel, err := filepath.Rel(thisCacheRoot, newMount.Source) + if err != nil { + return newMount, "", "", "", nil, fmt.Errorf("computing pathname of cache subdirectory: %w", err) + } + if rel != "." && rel != "/" { + mnt, err := bindFromChroot(thisCacheRoot, rel, tmpDir) + if err != nil { + return newMount, "", "", "", nil, fmt.Errorf("sanitizing cache subdirectory %q: %w", newMount.Source, err) + } + logrus.Debugf("bind-mounted %q under %q to %q", rel, thisCacheRoot, mnt) + intermediateMount = mnt + newMount.Source = intermediateMount + } + } + + overlayDir := "" + if needToOverlay { + if newMount, overlayDir, err = convertToOverlay(newMount, store, mountLabel, tmpDir, 0, 0); err != nil { + return newMount, "", "", "", nil, err + } + } + + succeeded = true + return newMount, mountedImage, intermediateMount, overlayDir, targetLock, nil +} + +func getVolumeMounts(volumes []string) (map[string]specs.Mount, error) { + finalVolumeMounts := make(map[string]specs.Mount) + + for _, volume := range volumes { + volumeMount, err := internalParse.Volume(volume) + if err != nil { + return nil, err + } + if _, ok := finalVolumeMounts[volumeMount.Destination]; ok { + return nil, fmt.Errorf("%v: %w", volumeMount.Destination, errDuplicateDest) + } + finalVolumeMounts[volumeMount.Destination] = volumeMount + } + return finalVolumeMounts, nil +} + +// UnlockLockArray is a helper for cleaning up after GetVolumes and the like. +func UnlockLockArray(locks []*lockfile.LockFile) { + for _, lock := range locks { + lock.Unlock() + } +} + +// GetVolumes gets the volumes from --volume and --mount flags. +// +// Returns a slice of Mounts to add to the runtime spec's list of mounts, the +// IDs of any images we mounted, a slice of bind-mounted paths, a slice of +// overlay directories and a slice of locks that we acquired, or an error. +// +// The caller is expected to, after the command which uses the mounts and +// volumes exits, clean up the overlay directories, unmount and remove the +// mountpoints for the bind-mounted paths, unmount any images we mounted, and +// release the locks we returned (either using UnlockLockArray() or by +// iterating over them and unlocking them). +func GetVolumes(ctx *types.SystemContext, store storage.Store, mountLabel string, volumes []string, mounts []string, contextDir string, idMaps define.IDMappingOptions, workDir, tmpDir string) ([]specs.Mount, []string, []string, []string, []*lockfile.LockFile, error) { + unifiedMounts, mountedImages, intermediateMounts, overlayMounts, targetLocks, err := getMounts(ctx, store, mountLabel, mounts, contextDir, idMaps.UIDMap, idMaps.GIDMap, workDir, tmpDir) + if err != nil { + return nil, nil, nil, nil, nil, err + } + succeeded := false + defer func() { + if !succeeded { + for _, overlayMount := range overlayMounts { + if err := overlay.RemoveTemp(overlayMount); err != nil { + logrus.Debugf("unmounting overlay at %q: %v", overlayMount, err) + } + } + for _, intermediateMount := range intermediateMounts { + if err := mount.Unmount(intermediateMount); err != nil { + logrus.Debugf("unmounting intermediate mount point %q: %v", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil { + logrus.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err) + } + } + for _, image := range mountedImages { + if _, err := store.UnmountImage(image, false); err != nil { + logrus.Debugf("unmounting image %q: %v", image, err) + } + } + UnlockLockArray(targetLocks) + } + }() + volumeMounts, err := getVolumeMounts(volumes) + if err != nil { + return nil, nil, nil, nil, nil, err + } + for dest, mount := range volumeMounts { + if _, ok := unifiedMounts[dest]; ok { + return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", dest, errDuplicateDest) + } + unifiedMounts[dest] = mount + } + + finalMounts := make([]specs.Mount, 0, len(unifiedMounts)) + for _, mount := range unifiedMounts { + finalMounts = append(finalMounts, mount) + } + succeeded = true + return finalMounts, mountedImages, intermediateMounts, overlayMounts, targetLocks, nil +} + +// getMounts takes user-provided inputs from the --mount flag and returns a +// slice of OCI spec mounts, a slice of mounted image IDs, a slice of other +// mount locations, a slice of overlay mounts, and a slice of locks, or an +// error. +// +// buildah run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... +// buildah run --mount type=cache,target=/var/cache ... +// buildah run --mount type=tmpfs,target=/dev/shm ... +// +// The caller is expected to, after the command which uses the mounts exits, +// unmount the overlay filesystems (if we mounted any), unmount the other +// mounted filesystems and remove their mountpoints (if we provided any paths +// to mountpoints), unmount any mounted images (if we provided the IDs of any), +// and then unlock the locks we returned (either using UnlockLockArray() or by +// iterating over them and unlocking them). +func getMounts(ctx *types.SystemContext, store storage.Store, mountLabel string, mounts []string, contextDir string, uidmap, gidmap []specs.LinuxIDMapping, workDir, tmpDir string) (map[string]specs.Mount, []string, []string, []string, []*lockfile.LockFile, error) { + // If `type` is not set default to "bind" + mountType := define.TypeBind + finalMounts := make(map[string]specs.Mount, len(mounts)) + mountedImages := make([]string, 0, len(mounts)) + intermediateMounts := make([]string, 0, len(mounts)) + overlayMounts := make([]string, 0, len(mounts)) + targetLocks := make([]*lockfile.LockFile, 0, len(mounts)) + succeeded := false + defer func() { + if !succeeded { + for _, overlayDir := range overlayMounts { + if err := overlay.RemoveTemp(overlayDir); err != nil { + logrus.Debugf("unmounting overlay mount at %q: %v", overlayDir, err) + } + } + for _, intermediateMount := range intermediateMounts { + if err := mount.Unmount(intermediateMount); err != nil { + logrus.Debugf("unmounting intermediate mount point %q: %v", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil { + logrus.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err) + } + } + for _, image := range mountedImages { + if _, err := store.UnmountImage(image, false); err != nil { + logrus.Debugf("unmounting image %q: %v", image, err) + } + } + UnlockLockArray(targetLocks) + } + }() + + errInvalidSyntax := errors.New("incorrect mount format: should be --mount type=,[src=,]target=[,options]") + + for _, mount := range mounts { + tokens := strings.Split(mount, ",") + if len(tokens) < 2 { + return nil, nil, nil, nil, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax) + } + for _, field := range tokens { + if strings.HasPrefix(field, "type=") { + kv := strings.Split(field, "=") + if len(kv) != 2 { + return nil, nil, nil, nil, nil, fmt.Errorf("%q: %w", mount, errInvalidSyntax) + } + mountType = kv[1] + } + } + switch mountType { + case define.TypeBind: + mount, image, intermediateMount, overlayMount, err := GetBindMount(ctx, tokens, contextDir, store, mountLabel, nil, workDir, tmpDir) + if err != nil { + return nil, nil, nil, nil, nil, err + } + if image != "" { + mountedImages = append(mountedImages, image) + } + if intermediateMount != "" { + intermediateMounts = append(intermediateMounts, intermediateMount) + } + if overlayMount != "" { + overlayMounts = append(overlayMounts, overlayMount) + } + if _, ok := finalMounts[mount.Destination]; ok { + return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest) + } + finalMounts[mount.Destination] = mount + case TypeCache: + mount, image, intermediateMount, overlayMount, tl, err := GetCacheMount(ctx, tokens, store, "", nil, uidmap, gidmap, workDir, tmpDir) + if err != nil { + return nil, nil, nil, nil, nil, err + } + if image != "" { + mountedImages = append(mountedImages, image) + } + if intermediateMount != "" { + intermediateMounts = append(intermediateMounts, intermediateMount) + } + if overlayMount != "" { + overlayMounts = append(overlayMounts, overlayMount) + } + if tl != nil { + targetLocks = append(targetLocks, tl) + } + if _, ok := finalMounts[mount.Destination]; ok { + return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest) + } + finalMounts[mount.Destination] = mount + case TypeTmpfs: + mount, err := GetTmpfsMount(tokens, workDir) + if err != nil { + return nil, nil, nil, nil, nil, err + } + if _, ok := finalMounts[mount.Destination]; ok { + return nil, nil, nil, nil, nil, fmt.Errorf("%v: %w", mount.Destination, errDuplicateDest) + } + finalMounts[mount.Destination] = mount + default: + return nil, nil, nil, nil, nil, fmt.Errorf("invalid filesystem type %q", mountType) + } + } + + succeeded = true + return finalMounts, mountedImages, intermediateMounts, overlayMounts, targetLocks, nil +} + +// GetTmpfsMount parses a single tmpfs mount entry from the --mount flag +func GetTmpfsMount(args []string, workDir string) (specs.Mount, error) { + newMount := specs.Mount{ + Type: TypeTmpfs, + Source: TypeTmpfs, + } + + setDest := false + + for _, val := range args { + argName, argValue, hasArgValue := strings.Cut(val, "=") + switch argName { + case "type": + // This is already processed, and should be "tmpfs" + continue + case "nosuid", "nodev", "noexec": + if hasArgValue { + return newMount, fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, argName) + case "ro", "readonly": + if hasArgValue { + return newMount, fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + newMount.Options = append(newMount.Options, "ro") + case "tmpcopyup": + if hasArgValue { + return newMount, fmt.Errorf("%v: %w", argName, errBadOptionNoArg) + } + // the path that is shadowed by the tmpfs mount is recursively copied up to the tmpfs itself. + newMount.Options = append(newMount.Options, argName) + case "tmpfs-mode": + if !hasArgValue || argValue == "" { + return newMount, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", argValue)) + case "tmpfs-size": + if !hasArgValue || argValue == "" { + return newMount, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", argValue)) + case "target", "dst", "destination": + if !hasArgValue || argValue == "" { + return newMount, fmt.Errorf("%v: %w", argName, errBadOptionArg) + } + targetPath := argValue + if !path.IsAbs(targetPath) { + targetPath = filepath.Join(workDir, targetPath) + } + if err := parse.ValidateVolumeCtrDir(targetPath); err != nil { + return newMount, err + } + newMount.Destination = targetPath + setDest = true + default: + return newMount, fmt.Errorf("%v: %w", argName, errBadMntOption) + } + } + + if !setDest { + return newMount, errBadVolDest + } + + return newMount, nil +} diff --git a/vendor/github.com/containers/buildah/mount.go b/vendor/github.com/containers/buildah/mount.go new file mode 100644 index 00000000..932c1bb7 --- /dev/null +++ b/vendor/github.com/containers/buildah/mount.go @@ -0,0 +1,51 @@ +package buildah + +import "fmt" + +// Mount mounts a container's root filesystem in a location which can be +// accessed from the host, and returns the location. +func (b *Builder) Mount(label string) (string, error) { + mountpoint, err := b.store.Mount(b.ContainerID, label) + if err != nil { + return "", fmt.Errorf("mounting build container %q: %w", b.ContainerID, err) + } + b.MountPoint = mountpoint + + err = b.Save() + if err != nil { + return "", fmt.Errorf("saving updated state for build container %q: %w", b.ContainerID, err) + } + return mountpoint, nil +} + +func (b *Builder) setMountPoint(mountPoint string) error { + b.MountPoint = mountPoint + if err := b.Save(); err != nil { + return fmt.Errorf("saving updated state for build container %q: %w", b.ContainerID, err) + } + return nil +} + +// Mounted returns whether the container is mounted or not +func (b *Builder) Mounted() (bool, error) { + mountCnt, err := b.store.Mounted(b.ContainerID) + if err != nil { + return false, fmt.Errorf("determining if mounting build container %q is mounted: %w", b.ContainerID, err) + } + mounted := mountCnt > 0 + if mounted && b.MountPoint == "" { + ctr, err := b.store.Container(b.ContainerID) + if err != nil { + return mountCnt > 0, fmt.Errorf("determining if mounting build container %q is mounted: %w", b.ContainerID, err) + } + layer, err := b.store.Layer(ctr.LayerID) + if err != nil { + return mountCnt > 0, fmt.Errorf("determining if mounting build container %q is mounted: %w", b.ContainerID, err) + } + return mounted, b.setMountPoint(layer.MountPoint) + } + if !mounted && b.MountPoint != "" { + return mounted, b.setMountPoint("") + } + return mounted, nil +} diff --git a/vendor/github.com/containers/buildah/new.go b/vendor/github.com/containers/buildah/new.go new file mode 100644 index 00000000..1f8bb87b --- /dev/null +++ b/vendor/github.com/containers/buildah/new.go @@ -0,0 +1,358 @@ +package buildah + +import ( + "context" + "errors" + "fmt" + "maps" + "math/rand" + "slices" + "strings" + + "github.com/containers/buildah/define" + digest "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/openshift/imagebuilder" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + "go.podman.io/common/pkg/config" + "go.podman.io/image/v5/image" + "go.podman.io/image/v5/manifest" + "go.podman.io/image/v5/pkg/shortnames" + "go.podman.io/image/v5/transports" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/stringid" +) + +const ( + // BaseImageFakeName is the "name" of a source image which we interpret + // as "no image". + BaseImageFakeName = imagebuilder.NoBaseImageSpecifier +) + +func getImageName(name string, img *storage.Image) string { + imageName := name + if len(img.Names) > 0 { + imageName = img.Names[0] + // When the image used by the container is a tagged image + // the container name might be set to the original image instead of + // the image given in the "from" command line. + // This loop is supposed to fix this. + for _, n := range img.Names { + if strings.Contains(n, name) { + imageName = n + break + } + } + } + return imageName +} + +func imageNamePrefix(imageName string) string { + prefix := imageName + if d, err := digest.Parse(imageName); err == nil { + prefix = d.Encoded() + if len(prefix) > 12 { + prefix = prefix[:12] + } + } + if stringid.ValidateID(prefix) == nil { + prefix = stringid.TruncateID(prefix) + } + s := strings.Split(prefix, ":") + if len(s) > 0 { + prefix = s[0] + } + s = strings.Split(prefix, "/") + if len(s) > 0 { + prefix = s[len(s)-1] + } + s = strings.Split(prefix, "@") + if len(s) > 0 { + prefix = s[0] + } + return prefix +} + +func newContainerIDMappingOptions(idmapOptions *define.IDMappingOptions) storage.IDMappingOptions { + var options storage.IDMappingOptions + if idmapOptions != nil { + if idmapOptions.AutoUserNs { + options.AutoUserNs = true + options.AutoUserNsOpts = idmapOptions.AutoUserNsOpts + } else { + options.HostUIDMapping = idmapOptions.HostUIDMapping + options.HostGIDMapping = idmapOptions.HostGIDMapping + uidmap, gidmap := convertRuntimeIDMaps(idmapOptions.UIDMap, idmapOptions.GIDMap) + if len(uidmap) > 0 && len(gidmap) > 0 { + options.UIDMap = uidmap + options.GIDMap = gidmap + } else { + options.HostUIDMapping = true + options.HostGIDMapping = true + } + } + } + return options +} + +func containerNameExist(name string, containers []storage.Container) bool { + for _, container := range containers { + if slices.Contains(container.Names, name) { + return true + } + } + return false +} + +func findUnusedContainer(name string, containers []storage.Container) string { + suffix := 1 + tmpName := name + for containerNameExist(tmpName, containers) { + tmpName = fmt.Sprintf("%s-%d", name, suffix) + suffix++ + } + return tmpName +} + +func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions) (*Builder, error) { + var ( + ref types.ImageReference + img *storage.Image + err error + ) + + if options.FromImage == BaseImageFakeName { + options.FromImage = "" + } + + if options.NetworkInterface == nil { + // create the network interface + // Note: It is important to do this before we pull any images/create containers. + // The default backend detection logic needs an empty store to correctly detect + // that we can use netavark, if the store was not empty it will use CNI to not break existing installs. + options.NetworkInterface, err = getNetworkInterface(store, options.CNIConfigDir, options.CNIPluginPath) + if err != nil { + return nil, err + } + } + + systemContext := getSystemContext(store, options.SystemContext, options.SignaturePolicyPath) + + if options.FromImage != "" && options.FromImage != BaseImageFakeName { + imageRuntime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) + if err != nil { + return nil, err + } + + pullPolicy, err := config.ParsePullPolicy(options.PullPolicy.String()) + if err != nil { + return nil, err + } + + // Note: options.Format does *not* relate to the image we're + // about to pull (see tests/digests.bats). So we're not + // forcing a MIMEType in the pullOptions below. + pullOptions := libimage.PullOptions{} + pullOptions.RetryDelay = &options.PullRetryDelay + pullOptions.OciDecryptConfig = options.OciDecryptConfig + pullOptions.SignaturePolicyPath = options.SignaturePolicyPath + pullOptions.Writer = options.ReportWriter + pullOptions.DestinationLookupReferenceFunc = cacheLookupReferenceFunc(options.BlobDirectory, types.PreserveOriginal) + + maxRetries := uint(options.MaxPullRetries) + pullOptions.MaxRetries = &maxRetries + + pulledImages, err := imageRuntime.Pull(ctx, options.FromImage, pullPolicy, &pullOptions) + if err != nil { + return nil, err + } + if len(pulledImages) > 0 { + img = pulledImages[0].StorageImage() + ref, err = pulledImages[0].StorageReference() + if err != nil { + return nil, err + } + } + } + + imageSpec := options.FromImage + imageID := "" + imageDigest := "" + topLayer := "" + if img != nil { + imageSpec = getImageName(imageNamePrefix(imageSpec), img) + imageID = img.ID + topLayer = img.TopLayer + } + var src types.Image + if ref != nil { + srcSrc, err := ref.NewImageSource(ctx, systemContext) + if err != nil { + return nil, fmt.Errorf("instantiating image for %q: %w", transports.ImageName(ref), err) + } + defer srcSrc.Close() + unparsedTop := image.UnparsedInstance(srcSrc, nil) + manifestBytes, manifestType, err := unparsedTop.Manifest(ctx) + if err != nil { + return nil, fmt.Errorf("loading image manifest for %q: %w", transports.ImageName(ref), err) + } + if manifestDigest, err := manifest.Digest(manifestBytes); err == nil { + imageDigest = manifestDigest.String() + } + var instanceDigest *digest.Digest + unparsedInstance := unparsedTop // for instanceDigest + if manifest.MIMETypeIsMultiImage(manifestType) { + list, err := manifest.ListFromBlob(manifestBytes, manifestType) + if err != nil { + return nil, fmt.Errorf("parsing image manifest for %q as list: %w", transports.ImageName(ref), err) + } + instance, err := list.ChooseInstance(systemContext) + if err != nil { + return nil, fmt.Errorf("finding an appropriate image in manifest list %q: %w", transports.ImageName(ref), err) + } + instanceDigest = &instance + unparsedInstance = image.UnparsedInstance(srcSrc, instanceDigest) + } + src, err = image.FromUnparsedImage(ctx, systemContext, unparsedInstance) + if err != nil { + return nil, fmt.Errorf("instantiating image for %q instance %q: %w", transports.ImageName(ref), instanceDigest, err) + } + } + + name := "working-container" + if options.ContainerSuffix != "" { + name = options.ContainerSuffix + } + if options.Container != "" { + name = options.Container + } else { + if imageSpec != "" { + name = imageNamePrefix(imageSpec) + "-" + name + } + } + var container *storage.Container + tmpName := name + if options.Container == "" { + containers, err := store.Containers() + if err != nil { + return nil, fmt.Errorf("unable to check for container names: %w", err) + } + tmpName = findUnusedContainer(tmpName, containers) + } + + suffixDigitsModulo := 100 + for { + var flags map[string]any + // check if we have predefined ProcessLabel and MountLabel + // this could be true if this is another stage in a build + if options.ProcessLabel != "" && options.MountLabel != "" { + flags = map[string]any{ + "ProcessLabel": options.ProcessLabel, + "MountLabel": options.MountLabel, + } + } + coptions := storage.ContainerOptions{ + LabelOpts: options.CommonBuildOpts.LabelOpts, + IDMappingOptions: newContainerIDMappingOptions(options.IDMappingOptions), + Flags: flags, + Volatile: true, + } + container, err = store.CreateContainer("", []string{tmpName}, imageID, "", "", &coptions) + if err == nil { + name = tmpName + break + } + if !errors.Is(err, storage.ErrDuplicateName) || options.Container != "" { + return nil, fmt.Errorf("creating container: %w", err) + } + tmpName = fmt.Sprintf("%s-%d", name, rand.Int()%suffixDigitsModulo) + if suffixDigitsModulo < 1_000_000_000 { + suffixDigitsModulo *= 10 + } + } + defer func() { + if err != nil { + if err2 := store.DeleteContainer(container.ID); err2 != nil { + logrus.Errorf("error deleting container %q: %v", container.ID, err2) + } + } + }() + + uidmap, gidmap := convertStorageIDMaps(container.UIDMap, container.GIDMap) + + defaultNamespaceOptions, err := DefaultNamespaceOptions() + if err != nil { + return nil, err + } + + namespaceOptions := defaultNamespaceOptions + namespaceOptions.AddOrReplace(options.NamespaceOptions...) + + builder := &Builder{ + store: store, + Type: containerType, + FromImage: imageSpec, + FromImageID: imageID, + FromImageDigest: imageDigest, + GroupAdd: options.GroupAdd, + Container: name, + ContainerID: container.ID, + ImageAnnotations: map[string]string{}, + ImageCreatedBy: "", + ProcessLabel: container.ProcessLabel(), + MountLabel: container.MountLabel(), + DefaultMountsFilePath: options.DefaultMountsFilePath, + Isolation: options.Isolation, + NamespaceOptions: namespaceOptions, + ConfigureNetwork: options.ConfigureNetwork, + CNIPluginPath: options.CNIPluginPath, + CNIConfigDir: options.CNIConfigDir, + IDMappingOptions: define.IDMappingOptions{ + HostUIDMapping: len(uidmap) == 0, + HostGIDMapping: len(uidmap) == 0, + UIDMap: uidmap, + GIDMap: gidmap, + }, + Capabilities: slices.Clone(options.Capabilities), + CommonBuildOpts: options.CommonBuildOpts, + TopLayer: topLayer, + Args: maps.Clone(options.Args), + Format: options.Format, + Devices: options.Devices, + DeviceSpecs: options.DeviceSpecs, + Logger: options.Logger, + NetworkInterface: options.NetworkInterface, + CDIConfigDir: options.CDIConfigDir, + } + + if options.Mount { + _, err = builder.Mount(container.MountLabel()) + if err != nil { + return nil, fmt.Errorf("mounting build container %q: %w", builder.ContainerID, err) + } + } + + if err := builder.initConfig(ctx, systemContext, src, &options); err != nil { + return nil, fmt.Errorf("preparing image configuration: %w", err) + } + + if !options.PreserveBaseImageAnns { + builder.SetAnnotation(v1.AnnotationBaseImageDigest, imageDigest) + if !shortnames.IsShortName(imageSpec) { + // If the base image was specified as a fully-qualified + // image name, let's set it. + builder.SetAnnotation(v1.AnnotationBaseImageName, imageSpec) + } else { + builder.UnsetAnnotation(v1.AnnotationBaseImageName) + } + } + + err = builder.Save() + if err != nil { + return nil, fmt.Errorf("saving builder state for container %q: %w", builder.ContainerID, err) + } + + return builder, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/binfmt/binfmt.go b/vendor/github.com/containers/buildah/pkg/binfmt/binfmt.go new file mode 100644 index 00000000..45409f80 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/binfmt/binfmt.go @@ -0,0 +1,83 @@ +//go:build linux + +package binfmt + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/unshare" + "golang.org/x/sys/unix" +) + +// MaybeRegister() calls Register() if the current context is a rootless one, +// or if the "container" environment variable suggests that we're in a +// container. +func MaybeRegister(configurationSearchDirectories []string) error { + if unshare.IsRootless() || os.Getenv("container") != "" { // we _also_ own our own mount namespace + return Register(configurationSearchDirectories) + } + return nil +} + +// Register() registers binfmt.d emulators described by configuration files in +// the passed-in slice of directories, or in the union of /etc/binfmt.d, +// /run/binfmt.d, and /usr/lib/binfmt.d if the slice has no items. If any +// emulators are configured, it will attempt to mount a binfmt_misc filesystem +// in the current mount namespace first, ignoring only EPERM and EACCES errors. +func Register(configurationSearchDirectories []string) error { + if len(configurationSearchDirectories) == 0 { + configurationSearchDirectories = []string{"/etc/binfmt.d", "/run/binfmt.d", "/usr/lib/binfmt.d"} + } + mounted := false + for _, searchDir := range configurationSearchDirectories { + globs, err := filepath.Glob(filepath.Join(searchDir, "*.conf")) + if err != nil { + return fmt.Errorf("looking for binfmt.d configuration in %q: %w", searchDir, err) + } + for _, conf := range globs { + f, err := os.Open(conf) + if err != nil { + return fmt.Errorf("reading binfmt.d configuration: %w", err) + } + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if len(line) == 0 || line[0] == ';' || line[0] == '#' { + continue + } + if !mounted { + if err = unix.Mount("none", "/proc/sys/fs/binfmt_misc", "binfmt_misc", 0, ""); err != nil { + if errors.Is(err, syscall.EPERM) || errors.Is(err, syscall.EACCES) { + // well, we tried. no need to make a stink about it + return nil + } + return fmt.Errorf("mounting binfmt_misc: %w", err) + } + mounted = true + } + reg, err := os.Create("/proc/sys/fs/binfmt_misc/register") + if err != nil { + return fmt.Errorf("registering(open): %w", err) + } + if _, err = fmt.Fprintf(reg, "%s\n", line); err != nil { + return fmt.Errorf("registering(write): %w", err) + } + logrus.Tracef("registered binfmt %q", line) + if err = reg.Close(); err != nil { + return fmt.Errorf("registering(close): %w", err) + } + } + if err := f.Close(); err != nil { + return fmt.Errorf("reading binfmt.d configuration: %w", err) + } + } + } + return nil +} diff --git a/vendor/github.com/containers/buildah/pkg/binfmt/binfmt_unsupported.go b/vendor/github.com/containers/buildah/pkg/binfmt/binfmt_unsupported.go new file mode 100644 index 00000000..6bb1667e --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/binfmt/binfmt_unsupported.go @@ -0,0 +1,15 @@ +//go:build !linux + +package binfmt + +import "syscall" + +// MaybeRegister() returns no error. +func MaybeRegister(configurationSearchDirectories []string) error { + return nil +} + +// Register() returns an error. +func Register(configurationSearchDirectories []string) error { + return syscall.ENOSYS +} diff --git a/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go b/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go new file mode 100644 index 00000000..b2723c1b --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go @@ -0,0 +1,31 @@ +package blobcache + +import ( + imageBlobCache "go.podman.io/image/v5/pkg/blobcache" + "go.podman.io/image/v5/types" +) + +// BlobCache is an object which saves copies of blobs that are written to it while passing them +// through to some real destination, and which can be queried directly in order to read them +// back. +type BlobCache interface { + types.ImageReference + // HasBlob checks if a blob that matches the passed-in digest (and + // size, if not -1), is present in the cache. + HasBlob(types.BlobInfo) (bool, int64, error) + // Directories returns the list of cache directories. + Directory() string + // ClearCache() clears the contents of the cache directories. Note + // that this also clears content which was not placed there by this + // cache implementation. + ClearCache() error +} + +// NewBlobCache creates a new blob cache that wraps an image reference. Any blobs which are +// written to the destination image created from the resulting reference will also be stored +// as-is to the specified directory or a temporary directory. +// The compress argument controls whether or not the cache will try to substitute a compressed +// or different version of a blob when preparing the list of layers when reading an image. +func NewBlobCache(ref types.ImageReference, directory string, compress types.LayerCompression) (BlobCache, error) { + return imageBlobCache.NewBlobCache(ref, directory, compress) +} diff --git a/vendor/github.com/containers/buildah/pkg/chrootuser/user.go b/vendor/github.com/containers/buildah/pkg/chrootuser/user.go new file mode 100644 index 00000000..e81e01d4 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/chrootuser/user.go @@ -0,0 +1,114 @@ +package chrootuser + +import ( + "errors" + "fmt" + "os/user" + "strconv" + "strings" +) + +// ErrNoSuchUser indicates that the user provided by the caller does not +// exist in /etc/passws +var ErrNoSuchUser = errors.New("user does not exist in /etc/passwd") + +// GetUser will return the uid, gid of the user specified in the userspec +// it will use the /etc/passwd and /etc/group files inside of the rootdir +// to return this information. +// userspec format [user | user:group | uid | uid:gid | user:gid | uid:group ] +func GetUser(rootdir, userspec string) (uint32, uint32, string, error) { + var gid64 uint64 + var gerr error = user.UnknownGroupError("error looking up group") + + spec := strings.SplitN(userspec, ":", 2) + userspec = spec[0] + groupspec := "" + + if userspec == "" { + userspec = "0" + } + + if len(spec) > 1 { + groupspec = spec[1] + } + + uid64, uerr := strconv.ParseUint(userspec, 10, 32) + if uerr == nil && groupspec == "" { + // We parsed the user name as a number, and there's no group + // component, so try to look up the primary GID of the user who + // has this UID. + var name string + name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64) + if gerr == nil { + userspec = name + } else { + // Leave userspec alone, but swallow the error and just + // use GID 0. + gid64 = 0 + gerr = nil + } + } + if uerr != nil { + // The user ID couldn't be parsed as a number, so try to look + // up the user's UID and primary GID. + uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec) + gerr = uerr + } + + if groupspec != "" { + // We have a group name or number, so parse it. + gid64, gerr = strconv.ParseUint(groupspec, 10, 32) + if gerr != nil { + // The group couldn't be parsed as a number, so look up + // the group's GID. + gid64, gerr = lookupGroupInContainer(rootdir, groupspec) + } + } + + homedir, err := lookupHomedirInContainer(rootdir, uid64) + if err != nil { + homedir = "/" + } + + if uerr == nil && gerr == nil { + return uint32(uid64), uint32(gid64), homedir, nil + } + + err = fmt.Errorf("determining run uid: %w", uerr) + if uerr == nil { + err = fmt.Errorf("determining run gid: %w", gerr) + } + + return 0, 0, homedir, err +} + +// GetGroup returns the gid by looking it up in the /etc/group file +// groupspec format [ group | gid ] +func GetGroup(rootdir, groupspec string) (uint32, error) { + gid64, gerr := strconv.ParseUint(groupspec, 10, 32) + if gerr != nil { + // The group couldn't be parsed as a number, so look up + // the group's GID. + gid64, gerr = lookupGroupInContainer(rootdir, groupspec) + } + if gerr != nil { + return 0, fmt.Errorf("looking up group for gid %q: %w", groupspec, gerr) + } + return uint32(gid64), nil +} + +// GetAdditionalGroupsForUser returns a list of gids that userid is associated with +func GetAdditionalGroupsForUser(rootdir string, userid uint64) ([]uint32, error) { + gids, err := lookupAdditionalGroupsForUIDInContainer(rootdir, userid) + if err != nil { + return nil, fmt.Errorf("looking up supplemental groups for uid %d: %w", userid, err) + } + return gids, nil +} + +// LookupUIDInContainer returns username and gid associated with a UID in a container +// it will use the /etc/passwd files inside of the rootdir +// to return this information. +func LookupUIDInContainer(rootdir string, uid uint64) (user string, gid uint64, err error) { + return lookupUIDInContainer(rootdir, uid) +} diff --git a/vendor/github.com/containers/buildah/pkg/chrootuser/user_basic.go b/vendor/github.com/containers/buildah/pkg/chrootuser/user_basic.go new file mode 100644 index 00000000..b9706430 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/chrootuser/user_basic.go @@ -0,0 +1,31 @@ +//go:build !linux && !freebsd + +package chrootuser + +import ( + "errors" +) + +func lookupUserInContainer(rootdir, username string) (uint64, uint64, error) { + return 0, 0, errors.New("user lookup not supported") +} + +func lookupGroupInContainer(rootdir, groupname string) (uint64, error) { + return 0, errors.New("group lookup not supported") +} + +func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64, error) { + return "", 0, errors.New("primary group lookup by uid not supported") +} + +func lookupAdditionalGroupsForUIDInContainer(rootdir string, userid uint64) (gid []uint32, err error) { + return nil, errors.New("supplemental groups list lookup by uid not supported") +} + +func lookupUIDInContainer(rootdir string, uid uint64) (string, uint64, error) { + return "", 0, errors.New("UID lookup not supported") +} + +func lookupHomedirInContainer(rootdir string, uid uint64) (string, error) { + return "", errors.New("Home directory lookup not supported") +} diff --git a/vendor/github.com/containers/buildah/pkg/chrootuser/user_unix.go b/vendor/github.com/containers/buildah/pkg/chrootuser/user_unix.go new file mode 100644 index 00000000..ffd4e5f5 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/chrootuser/user_unix.go @@ -0,0 +1,311 @@ +//go:build linux || freebsd + +package chrootuser + +import ( + "bufio" + "flag" + "fmt" + "io" + "os" + "os/exec" + "os/user" + "strconv" + "strings" + "sync" + + "go.podman.io/storage/pkg/reexec" + "golang.org/x/sys/unix" +) + +const ( + openChrootedCommand = "chrootuser-open" +) + +func init() { + reexec.Register(openChrootedCommand, openChrootedFileMain) +} + +func openChrootedFileMain() { + status := 0 + flag.Parse() + if len(flag.Args()) < 1 { + os.Exit(1) + } + // Our first parameter is the directory to chroot into. + if err := unix.Chdir(flag.Arg(0)); err != nil { + fmt.Fprintf(os.Stderr, "chdir(): %v", err) + os.Exit(1) + } + if err := unix.Chroot(flag.Arg(0)); err != nil { + fmt.Fprintf(os.Stderr, "chroot(): %v", err) + os.Exit(1) + } + // Anything else is a file we want to dump out. + for _, filename := range flag.Args()[1:] { + f, err := os.Open(filename) + if err != nil { + fmt.Fprintf(os.Stderr, "open(%q): %v", filename, err) + status = 1 + continue + } + _, err = io.Copy(os.Stdout, f) + if err != nil { + fmt.Fprintf(os.Stderr, "read(%q): %v", filename, err) + } + f.Close() + } + os.Exit(status) +} + +func openChrootedFile(rootdir, filename string) (*exec.Cmd, io.ReadCloser, error) { + // The child process expects a chroot and one or more filenames that + // will be consulted relative to the chroot directory and concatenated + // to its stdout. Start it up. + cmd := reexec.Command(openChrootedCommand, rootdir, filename) + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, nil, err + } + err = cmd.Start() + if err != nil { + return nil, nil, err + } + // Hand back the child's stdout for reading, and the child to reap. + return cmd, stdout, nil +} + +var lookupUser, lookupGroup sync.Mutex + +type lookupPasswdEntry struct { + name string + uid uint64 + gid uint64 + home string +} +type lookupGroupEntry struct { + name string + gid uint64 + user string +} + +func scanWithoutComments(rc *bufio.Scanner) (string, bool) { + for { + if !rc.Scan() { + return "", false + } + line := rc.Text() + if strings.HasPrefix(strings.TrimSpace(line), "#") { + continue + } + return line, true + } +} + +func parseNextPasswd(rc *bufio.Scanner) *lookupPasswdEntry { + if !rc.Scan() { + return nil + } + line := rc.Text() + fields := strings.Split(line, ":") + if len(fields) != 7 { + return nil + } + uid, err := strconv.ParseUint(fields[2], 10, 32) + if err != nil { + return nil + } + gid, err := strconv.ParseUint(fields[3], 10, 32) + if err != nil { + return nil + } + return &lookupPasswdEntry{ + name: fields[0], + uid: uid, + gid: gid, + home: fields[5], + } +} + +func parseNextGroup(rc *bufio.Scanner) *lookupGroupEntry { + // On FreeBSD, /etc/group may contain comments: + // https://man.freebsd.org/cgi/man.cgi?query=group&sektion=5&format=html + // We need to ignore those lines rather than trying to parse them. + line, ok := scanWithoutComments(rc) + if !ok { + return nil + } + fields := strings.Split(line, ":") + if len(fields) != 4 { + return nil + } + gid, err := strconv.ParseUint(fields[2], 10, 32) + if err != nil { + return nil + } + return &lookupGroupEntry{ + name: fields[0], + gid: gid, + user: fields[3], + } +} + +func lookupUserInContainer(rootdir, username string) (uid uint64, gid uint64, err error) { + cmd, f, err := openChrootedFile(rootdir, "/etc/passwd") + if err != nil { + return 0, 0, err + } + defer func() { + _ = cmd.Wait() + }() + rc := bufio.NewScanner(f) + defer f.Close() + + lookupUser.Lock() + defer lookupUser.Unlock() + + pwd := parseNextPasswd(rc) + for pwd != nil { + if pwd.name != username { + pwd = parseNextPasswd(rc) + continue + } + return pwd.uid, pwd.gid, nil + } + + return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username)) +} + +func lookupGroupForUIDInContainer(rootdir string, userid uint64) (username string, gid uint64, err error) { + cmd, f, err := openChrootedFile(rootdir, "/etc/passwd") + if err != nil { + return "", 0, err + } + defer func() { + _ = cmd.Wait() + }() + rc := bufio.NewScanner(f) + defer f.Close() + + lookupUser.Lock() + defer lookupUser.Unlock() + + pwd := parseNextPasswd(rc) + for pwd != nil { + if pwd.uid != userid { + pwd = parseNextPasswd(rc) + continue + } + return pwd.name, pwd.gid, nil + } + + return "", 0, ErrNoSuchUser +} + +func lookupAdditionalGroupsForUIDInContainer(rootdir string, userid uint64) (gid []uint32, err error) { + // Get the username associated with userid + username, _, err := lookupGroupForUIDInContainer(rootdir, userid) + if err != nil { + return nil, err + } + + cmd, f, err := openChrootedFile(rootdir, "/etc/group") + if err != nil { + return nil, err + } + defer func() { + _ = cmd.Wait() + }() + rc := bufio.NewScanner(f) + defer f.Close() + + lookupGroup.Lock() + defer lookupGroup.Unlock() + + grp := parseNextGroup(rc) + for grp != nil { + if strings.Contains(grp.user, username) { + gid = append(gid, uint32(grp.gid)) + } + grp = parseNextGroup(rc) + } + return gid, nil +} + +func lookupGroupInContainer(rootdir, groupname string) (gid uint64, err error) { + cmd, f, err := openChrootedFile(rootdir, "/etc/group") + if err != nil { + return 0, err + } + defer func() { + _ = cmd.Wait() + }() + rc := bufio.NewScanner(f) + defer f.Close() + + lookupGroup.Lock() + defer lookupGroup.Unlock() + + grp := parseNextGroup(rc) + for grp != nil { + if grp.name != groupname { + grp = parseNextGroup(rc) + continue + } + return grp.gid, nil + } + + return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname)) +} + +func lookupUIDInContainer(rootdir string, uid uint64) (string, uint64, error) { + cmd, f, err := openChrootedFile(rootdir, "/etc/passwd") + if err != nil { + return "", 0, err + } + defer func() { + _ = cmd.Wait() + }() + rc := bufio.NewScanner(f) + defer f.Close() + + lookupUser.Lock() + defer lookupUser.Unlock() + + pwd := parseNextPasswd(rc) + for pwd != nil { + if pwd.uid != uid { + pwd = parseNextPasswd(rc) + continue + } + return pwd.name, pwd.gid, nil + } + + return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up uid %q", uid)) +} + +func lookupHomedirInContainer(rootdir string, uid uint64) (string, error) { + cmd, f, err := openChrootedFile(rootdir, "/etc/passwd") + if err != nil { + return "", err + } + defer func() { + _ = cmd.Wait() + }() + rc := bufio.NewScanner(f) + defer f.Close() + + lookupUser.Lock() + defer lookupUser.Unlock() + + pwd := parseNextPasswd(rc) + for pwd != nil { + if pwd.uid != uid { + pwd = parseNextPasswd(rc) + continue + } + return pwd.home, nil + } + + return "", user.UnknownUserError(fmt.Sprintf("error looking up uid %q for homedir", uid)) +} diff --git a/vendor/github.com/containers/buildah/pkg/jail/jail.go b/vendor/github.com/containers/buildah/pkg/jail/jail.go new file mode 100644 index 00000000..ad376348 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/jail/jail.go @@ -0,0 +1,246 @@ +//go:build freebsd +// +build freebsd + +package jail + +import ( + "fmt" + "strconv" + "strings" + "sync" + "syscall" + "unsafe" + + "github.com/containers/buildah/pkg/util" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +type NS int32 + +const ( + DISABLED NS = 0 + NEW NS = 1 + INHERIT NS = 2 + + JAIL_CREATE = 0x01 + JAIL_UPDATE = 0x02 + JAIL_ATTACH = 0x04 +) + +type config struct { + params map[string]any +} + +var ( + needVnetJailOnce sync.Once + needVnetJail bool +) + +func NewConfig() *config { + return &config{ + params: make(map[string]any), + } +} + +func handleBoolSetting(key string, val bool) (string, any) { + // jail doesn't deal with booleans - it uses paired parameter + // names, e.g. "persist"/"nopersist". If the key contains '.', + // the "no" prefix is applied to the last element. + if val == false { + parts := strings.Split(key, ".") + parts[len(parts)-1] = "no" + parts[len(parts)-1] + key = strings.Join(parts, ".") + } + return key, nil +} + +func (c *config) Set(key string, value any) { + // Normalise integer types to int32 + switch v := value.(type) { + case int: + value = int32(v) + case uint32: + value = int32(v) + } + + switch key { + case "jid", "devfs_ruleset", "enforce_statfs", "children.max", "securelevel": + if _, ok := value.(int32); !ok { + logrus.Fatalf("value for parameter %s must be an int32", key) + } + case "ip4", "ip6", "host", "vnet": + nsval, ok := value.(NS) + if !ok { + logrus.Fatalf("value for parameter %s must be a jail.NS", key) + } + if (key == "host" || key == "vnet") && nsval == DISABLED { + logrus.Fatalf("value for parameter %s cannot be DISABLED", key) + } + case "persist", "sysvmsg", "sysvsem", "sysvshm": + bval, ok := value.(bool) + if !ok { + logrus.Fatalf("value for parameter %s must be bool", key) + } + key, value = handleBoolSetting(key, bval) + default: + if strings.HasPrefix(key, "allow.") { + bval, ok := value.(bool) + if !ok { + logrus.Fatalf("value for parameter %s must be bool", key) + } + key, value = handleBoolSetting(key, bval) + } else { + if _, ok := value.(string); !ok { + logrus.Fatalf("value for parameter %s must be a string", key) + } + } + } + c.params[key] = value +} + +func (c *config) getIovec() ([]syscall.Iovec, error) { + jiov := make([]syscall.Iovec, 0) + for key, value := range c.params { + iov, err := stringToIovec(key) + if err != nil { + return nil, err + } + jiov = append(jiov, iov) + switch v := value.(type) { + case string: + iov, err := stringToIovec(v) + if err != nil { + return nil, err + } + jiov = append(jiov, iov) + case int32: + jiov = append(jiov, syscall.Iovec{ + Base: (*byte)(unsafe.Pointer(&v)), + Len: 4, + }) + case NS: + jiov = append(jiov, syscall.Iovec{ + Base: (*byte)(unsafe.Pointer(&v)), + Len: 4, + }) + default: + jiov = append(jiov, syscall.Iovec{ + Base: nil, + Len: 0, + }) + } + } + return jiov, nil +} + +type jail struct { + jid int32 +} + +func jailSet(jconf *config, flags int) (*jail, error) { + jiov, err := jconf.getIovec() + if err != nil { + return nil, err + } + + jid, _, errno := syscall.Syscall(unix.SYS_JAIL_SET, uintptr(unsafe.Pointer(&jiov[0])), uintptr(len(jiov)), uintptr(flags)) + if errno != 0 { + return nil, errno + } + return &jail{ + jid: int32(jid), + }, nil +} + +func jailGet(jconf *config, flags int) (*jail, error) { + jiov, err := jconf.getIovec() + if err != nil { + return nil, err + } + + jid, _, errno := syscall.Syscall(unix.SYS_JAIL_GET, uintptr(unsafe.Pointer(&jiov[0])), uintptr(len(jiov)), uintptr(flags)) + if errno != 0 { + return nil, errno + } + return &jail{ + jid: int32(jid), + }, nil +} + +func Create(jconf *config) (*jail, error) { + return jailSet(jconf, JAIL_CREATE) +} + +func CreateAndAttach(jconf *config) (*jail, error) { + return jailSet(jconf, JAIL_CREATE|JAIL_ATTACH) +} + +func FindByName(name string) (*jail, error) { + jconf := NewConfig() + jconf.Set("name", name) + return jailGet(jconf, 0) +} + +func (j *jail) Set(jconf *config) error { + jconf.Set("jid", j.jid) + _, err := jailSet(jconf, JAIL_UPDATE) + return err +} + +func parseVersion(version string) (string, int, int, int, error) { + // Expected formats: + // .-RELEASE optionally followed by -p + // -STABLE + // -CURRENT + parts := strings.Split(string(version), "-") + if len(parts) < 2 || len(parts) > 3 { + return "", -1, -1, -1, fmt.Errorf("unexpected OS version: %s", version) + } + ver := strings.Split(parts[0], ".") + + if len(ver) != 2 { + return "", -1, -1, -1, fmt.Errorf("unexpected OS version: %s", version) + } + major, err := strconv.Atoi(ver[0]) + if err != nil { + return "", -1, -1, -1, fmt.Errorf("unexpected OS version: %s", version) + } + minor, err := strconv.Atoi(ver[1]) + if err != nil { + return "", -1, -1, -1, fmt.Errorf("unexpected OS version: %s", version) + } + patchlevel := 0 + if len(parts) == 3 { + if parts[1] != "RELEASE" || !strings.HasPrefix(parts[2], "p") { + return "", -1, -1, -1, fmt.Errorf("unexpected OS version: %s", version) + } + patchlevel, err = strconv.Atoi(strings.TrimPrefix(parts[2], "p")) + if err != nil { + return "", -1, -1, -1, fmt.Errorf("unexpected OS version: %s", version) + } + } + return parts[1], major, minor, patchlevel, nil +} + +// Return true if its necessary to have a separate jail to own the vnet. For +// FreeBSD 13.3 and later, we don't need a separate vnet jail since it is +// possible to configure the network without either attaching to the container's +// jail or trusting the ifconfig and route utilities in the container. If for +// any reason, we fail to parse the OS version, we default to returning true. +func NeedVnetJail() bool { + needVnetJailOnce.Do(func() { + // FreeBSD 13.3 and later have support for 'ifconfig -j' and 'route -j' + needVnetJail = true + version, err := util.ReadKernelVersion() + if err != nil { + logrus.Errorf("failed to determine OS version: %v", err) + return + } + _, major, minor, _, err := parseVersion(version) + if major > 13 || (major == 13 && minor > 2) { + needVnetJail = false + } + }) + return needVnetJail +} diff --git a/vendor/github.com/containers/buildah/pkg/jail/jail_int32.go b/vendor/github.com/containers/buildah/pkg/jail/jail_int32.go new file mode 100644 index 00000000..3e56bb62 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/jail/jail_int32.go @@ -0,0 +1,20 @@ +//go:build (386 || arm) && freebsd +// +build 386 arm +// +build freebsd + +package jail + +import ( + "syscall" +) + +func stringToIovec(val string) (syscall.Iovec, error) { + bs, err := syscall.ByteSliceFromString(val) + if err != nil { + return syscall.Iovec{}, err + } + var res syscall.Iovec + res.Base = &bs[0] + res.Len = uint32(len(bs)) + return res, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/jail/jail_int64.go b/vendor/github.com/containers/buildah/pkg/jail/jail_int64.go new file mode 100644 index 00000000..dace13fb --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/jail/jail_int64.go @@ -0,0 +1,19 @@ +//go:build !(386 || arm) && freebsd +// +build !386,!arm,freebsd + +package jail + +import ( + "syscall" +) + +func stringToIovec(val string) (syscall.Iovec, error) { + bs, err := syscall.ByteSliceFromString(val) + if err != nil { + return syscall.Iovec{}, err + } + var res syscall.Iovec + res.Base = &bs[0] + res.Len = uint64(len(bs)) + return res, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go new file mode 100644 index 00000000..5607a0b1 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay.go @@ -0,0 +1,261 @@ +package overlay + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/mount" + "go.podman.io/storage/pkg/system" + "go.podman.io/storage/pkg/unshare" +) + +// Options for MountWithOptions(). +type Options struct { + // The Upper directory is normally writable layer in an overlay mount. + // Note!! : Following API does not handles escaping or validates correctness of the values + // passed to UpperDirOptionFragment instead API will try to pass values as is it + // to the `mount` command. It is user's responsibility to make sure they pre-validate + // these values. Invalid inputs may lead to undefined behaviour. + // This is provided as-is, use it if it works for you, we can/will change/break that in the future. + // See discussion here for more context: https://github.com/containers/buildah/pull/3715#discussion_r786036959 + // TODO: Should we address above comment and handle escaping of metacharacters like + // `comma`, `backslash` ,`colon` and any other special characters + UpperDirOptionFragment string + // The Workdir is used to prepare files as they are switched between the layers. + // Note!! : Following API does not handles escaping or validates correctness of the values + // passed to WorkDirOptionFragment instead API will try to pass values as is it + // to the `mount` command. It is user's responsibility to make sure they pre-validate + // these values. Invalid inputs may lead to undefined behaviour. + // This is provided as-is, use it if it works for you, we can/will change/break that in the future. + // See discussion here for more context: https://github.com/containers/buildah/pull/3715#discussion_r786036959 + // TODO: Should we address above comment and handle escaping of metacharacters like + // `comma`, `backslash` ,`colon` and any other special characters + WorkDirOptionFragment string + // Graph options being used by the caller, will be searched when choosing mount program + GraphOpts []string + // Mark if following overlay is read only + ReadOnly bool + // Deprecated: RootUID is not used + RootUID int + // Deprecated: RootGID is not used + RootGID int + // Force overlay mounting and return a bind mount, rather than + // attempting to optimize by having the runtime actually mount and + // manage the overlay filesystem. + ForceMount bool + // MountLabel is a label to force for the overlay filesystem. + MountLabel string +} + +// TempDir generates a uniquely-named directory under ${containerDir}/overlay +// which can be used as a parent directory for the upper and working +// directories for an overlay mount, creates "upper" and "work" directories +// beneath it, and then returns the path of the new directory. +func TempDir(containerDir string, rootUID, rootGID int) (string, error) { + contentDir := filepath.Join(containerDir, "overlay") + if err := idtools.MkdirAllAs(contentDir, 0o700, rootUID, rootGID); err != nil { + return "", fmt.Errorf("failed to create the overlay %s directory: %w", contentDir, err) + } + + contentDir, err := os.MkdirTemp(contentDir, "") + if err != nil { + return "", fmt.Errorf("failed to create the overlay tmpdir in %s directory: %w", contentDir, err) + } + + return contentDir, generateOverlayStructure(contentDir, rootUID, rootGID) +} + +// GenerateStructure generates an overlay directory structure for container content +func GenerateStructure(containerDir, containerID, name string, rootUID, rootGID int) (string, error) { + contentDir := filepath.Join(containerDir, "overlay-containers", containerID, name) + if err := idtools.MkdirAllAs(contentDir, 0o700, rootUID, rootGID); err != nil { + return "", fmt.Errorf("failed to create the overlay %s directory: %w", contentDir, err) + } + + return contentDir, generateOverlayStructure(contentDir, rootUID, rootGID) +} + +// generateOverlayStructure generates upper, work and merge directories under the specified directory +func generateOverlayStructure(containerDir string, rootUID, rootGID int) error { + upperDir := filepath.Join(containerDir, "upper") + workDir := filepath.Join(containerDir, "work") + if err := idtools.MkdirAllAs(upperDir, 0o700, rootUID, rootGID); err != nil { + return fmt.Errorf("creating overlay upper directory %s: %w", upperDir, err) + } + if err := idtools.MkdirAllAs(workDir, 0o700, rootUID, rootGID); err != nil { + return fmt.Errorf("creating overlay work directory %s: %w", workDir, err) + } + mergeDir := filepath.Join(containerDir, "merge") + if err := idtools.MkdirAllAs(mergeDir, 0o700, rootUID, rootGID); err != nil { + return fmt.Errorf("creating overlay merge directory %s: %w", mergeDir, err) + } + return nil +} + +// Mount creates a subdir of the contentDir based on the source directory +// from the source system. It then mounts up the source directory on to the +// generated mount point and returns the mount point to the caller. +func Mount(contentDir, source, dest string, rootUID, rootGID int, graphOptions []string) (mount specs.Mount, Err error) { + overlayOpts := Options{GraphOpts: graphOptions, ReadOnly: false, RootUID: rootUID, RootGID: rootGID} + return MountWithOptions(contentDir, source, dest, &overlayOpts) +} + +// MountReadOnly creates a subdir of the contentDir based on the source directory +// from the source system. It then mounts up the source directory on to the +// generated mount point and returns the mount point to the caller. Note that no +// upper layer will be created rendering it a read-only mount +func MountReadOnly(contentDir, source, dest string, rootUID, rootGID int, graphOptions []string) (mount specs.Mount, Err error) { + overlayOpts := Options{GraphOpts: graphOptions, ReadOnly: true, RootUID: rootUID, RootGID: rootGID} + return MountWithOptions(contentDir, source, dest, &overlayOpts) +} + +// findMountProgram finds if any mount program is specified in the graph options. +func findMountProgram(graphOptions []string) string { + mountMap := map[string]struct{}{ + ".mount_program": {}, + "overlay.mount_program": {}, + "overlay2.mount_program": {}, + } + + for _, i := range graphOptions { + s := strings.SplitN(i, "=", 2) + if len(s) != 2 { + continue + } + key := s[0] + val := s[1] + if _, has := mountMap[key]; has { + return val + } + } + + return "" +} + +// mountWithMountProgram mounts an overlay at mergeDir using the specified +// mount program and overlay options. +func mountWithMountProgram(mountProgram, overlayOptions, mergeDir string) error { + cmd := exec.Command(mountProgram, "-o", overlayOptions, mergeDir) + + if err := cmd.Run(); err != nil { + return fmt.Errorf("exec %s: %w", mountProgram, err) + } + return nil +} + +// mountNatively mounts an overlay at mergeDir using the kernel's mount() +// system call. +func mountNatively(overlayOptions, mergeDir string) error { + return mount.Mount("overlay", mergeDir, "overlay", overlayOptions) +} + +// Convert ":" to "\:", the path which will be overlay mounted need to be escaped +func escapeColon(source string) string { + return strings.ReplaceAll(source, ":", "\\:") +} + +// RemoveTemp unmounts a filesystem mounted at ${contentDir}/merge, and then +// removes ${contentDir}, which is typically a path returned by TempDir(), +// along with any contents it might still have. +func RemoveTemp(contentDir string) error { + if err := Unmount(contentDir); err != nil { + return err + } + + return os.RemoveAll(contentDir) +} + +// Unmount the overlay mountpoint at ${contentDir}/merge, where ${contentDir} +// is typically a path returned by TempDir(). The mountpoint itself is left +// unmodified. +func Unmount(contentDir string) error { + mergeDir := filepath.Join(contentDir, "merge") + + if unshare.IsRootless() { + // Attempt to unmount the FUSE mount using either fusermount or fusermount3. + // If they fail, fallback to unix.Unmount + for _, v := range []string{"fusermount3", "fusermount"} { + err := exec.Command(v, "-u", mergeDir).Run() + if err != nil && !errors.Is(err, exec.ErrNotFound) { + logrus.Debugf("Error unmounting %s with %s - %v", mergeDir, v, err) + } + if err == nil { + return nil + } + } + // If fusermount|fusermount3 failed to unmount the FUSE file system, attempt unmount + } + + // Ignore EINVAL as the specified merge dir is not a mount point + if err := system.Unmount(mergeDir); err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, syscall.EINVAL) { + return fmt.Errorf("unmount overlay %s: %w", mergeDir, err) + } + return nil +} + +// recreate removes a directory tree and then recreates the top of that tree +// with the same mode and ownership. +func recreate(contentDir string) error { + st, err := system.Stat(contentDir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return fmt.Errorf("failed to stat overlay upper directory: %w", err) + } + + if err := os.RemoveAll(contentDir); err != nil { + return err + } + + if err := idtools.MkdirAllAs(contentDir, os.FileMode(st.Mode()), int(st.UID()), int(st.GID())); err != nil { + return fmt.Errorf("failed to create overlay directory: %w", err) + } + return nil +} + +// CleanupMount removes all temporary mountpoint content +func CleanupMount(contentDir string) (Err error) { + if err := recreate(filepath.Join(contentDir, "upper")); err != nil { + return err + } + if err := recreate(filepath.Join(contentDir, "work")); err != nil { + return err + } + return nil +} + +// CleanupContent removes every temporary mountpoint created under +// ${containerDir}/overlay as a result of however many calls to TempDir(), +// roughly equivalent to calling RemoveTemp() for each of the directories whose +// paths it returned, and then removes ${containerDir} itself. +func CleanupContent(containerDir string) (Err error) { + contentDir := filepath.Join(containerDir, "overlay") + + files, err := os.ReadDir(contentDir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return fmt.Errorf("read directory: %w", err) + } + for _, f := range files { + dir := filepath.Join(contentDir, f.Name()) + if err := Unmount(dir); err != nil { + return err + } + } + + if err := os.RemoveAll(contentDir); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to cleanup overlay directory: %w", err) + } + return nil +} diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay_freebsd.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay_freebsd.go new file mode 100644 index 00000000..4dd1fb33 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay_freebsd.go @@ -0,0 +1,34 @@ +package overlay + +import ( + //"fmt" + //"os" + //"path/filepath" + //"strings" + //"syscall" + "errors" + + //"go.podman.io/storage/pkg/unshare" + "github.com/opencontainers/runtime-spec/specs-go" +) + +// MountWithOptions returns a specs.Mount which makes the contents of ${source} +// visible at ${dest} in the container. +// Options allows the caller to configure whether or not the mount should be +// read-only. +// This API is used by podman. +func MountWithOptions(contentDir, source, dest string, opts *Options) (mount specs.Mount, Err error) { + if opts == nil { + opts = &Options{} + } + if opts.ReadOnly { + // Read-only overlay mounts can be simulated with nullfs + mount.Source = source + mount.Destination = dest + mount.Type = "nullfs" + mount.Options = []string{"ro"} + return mount, nil + } else { + return mount, errors.New("read/write overlay mounts not supported on freebsd") + } +} diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go new file mode 100644 index 00000000..8f594147 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay_linux.go @@ -0,0 +1,109 @@ +package overlay + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux/label" + "go.podman.io/storage/pkg/unshare" +) + +// MountWithOptions creates ${contentDir}/merge, where ${contentDir} was +// presumably created and returned by a call to TempDir(), and either mounts a +// filesystem there and returns a mounts.Spec which bind-mounts the mountpoint +// to ${dest}, or returns a mounts.Spec which mounts a filesystem at ${dest}. +// Options allows the caller to configure a custom workdir and upperdir, +// indicate whether or not the overlay should be read-only, and provide the +// graph driver options that we'll search to determine whether or not we should +// be using a mount helper (i.e., fuse-overlayfs). +// This API is used by podman. +func MountWithOptions(contentDir, source, dest string, opts *Options) (mount specs.Mount, Err error) { + if opts == nil { + opts = &Options{} + } + mergeDir := filepath.Join(contentDir, "merge") + + // Create overlay mount options for rw/ro. + var overlayOptions string + if opts.ReadOnly { + // Read-only overlay mounts require two lower layers. + lowerTwo := filepath.Join(contentDir, "lower") + if err := os.Mkdir(lowerTwo, 0o755); err != nil { + return mount, err + } + overlayOptions = fmt.Sprintf("lowerdir=%s:%s,private", escapeColon(source), lowerTwo) + } else { + // Read-write overlay mounts want a lower, upper and a work layer. + workDir := filepath.Join(contentDir, "work") + upperDir := filepath.Join(contentDir, "upper") + + if opts.WorkDirOptionFragment != "" && opts.UpperDirOptionFragment != "" { + workDir = opts.WorkDirOptionFragment + if !filepath.IsAbs(workDir) { + workDir = filepath.Join(contentDir, workDir) + } + upperDir = opts.UpperDirOptionFragment + if !filepath.IsAbs(upperDir) { + upperDir = filepath.Join(contentDir, upperDir) + } + } + + st, err := os.Stat(source) + if err != nil { + return mount, err + } + if err := os.Chmod(upperDir, st.Mode()); err != nil { + return mount, err + } + if stat, ok := st.Sys().(*syscall.Stat_t); ok { + if err := os.Chown(upperDir, int(stat.Uid), int(stat.Gid)); err != nil { + return mount, err + } + } + overlayOptions = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s,private", escapeColon(source), upperDir, workDir) + } + if opts.MountLabel != "" { + overlayOptions = overlayOptions + "," + label.FormatMountLabel("", opts.MountLabel) + } + + mountProgram := findMountProgram(opts.GraphOpts) + if mountProgram != "" { + if err := mountWithMountProgram(mountProgram, overlayOptions, mergeDir); err != nil { + return mount, err + } + + mount.Source = mergeDir + mount.Destination = dest + mount.Type = "bind" + mount.Options = []string{"bind", "slave"} + return mount, nil + } + + if unshare.IsRootless() { + // If a mount_program is not specified, fallback to try mounting native overlay. + overlayOptions = fmt.Sprintf("%s,userxattr", overlayOptions) + } + + mount.Source = mergeDir + mount.Destination = dest + mount.Type = "overlay" + mount.Options = strings.Split(overlayOptions, ",") + + if opts.ForceMount { + if err := mountNatively(overlayOptions, mergeDir); err != nil { + return mount, err + } + + mount.Source = mergeDir + mount.Destination = dest + mount.Type = "bind" + mount.Options = []string{"bind", "slave"} + return mount, nil + } + + return mount, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/overlay/overlay_unsupported.go b/vendor/github.com/containers/buildah/pkg/overlay/overlay_unsupported.go new file mode 100644 index 00000000..de7ce7d2 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/overlay/overlay_unsupported.go @@ -0,0 +1,19 @@ +//go:build !freebsd && !linux + +package overlay + +import ( + "fmt" + "runtime" + + "github.com/opencontainers/runtime-spec/specs-go" +) + +// MountWithOptions creates a subdir of the contentDir based on the source directory +// from the source system. It then mounts up the source directory on to the +// generated mount point and returns the mount point to the caller. +// But allows api to set custom workdir, upperdir and other overlay options +// Following API is being used by podman at the moment +func MountWithOptions(contentDir, source, dest string, opts *Options) (mount specs.Mount, err error) { + return mount, fmt.Errorf("read/write overlay mounts not supported on %q", runtime.GOOS) +} diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go new file mode 100644 index 00000000..911d5ded --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go @@ -0,0 +1,1410 @@ +package parse + +// this package should contain functions that parse and validate +// user input and is shared either amongst buildah subcommands or +// would be useful to projects vendoring buildah + +import ( + "errors" + "fmt" + "io/fs" + "net" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + "unicode" + + "github.com/containerd/platforms" + "github.com/containers/buildah/define" + mkcwtypes "github.com/containers/buildah/internal/mkcw/types" + internalParse "github.com/containers/buildah/internal/parse" + "github.com/containers/buildah/internal/sbom" + "github.com/containers/buildah/internal/tmpdir" + "github.com/containers/buildah/pkg/sshagent" + securejoin "github.com/cyphar/filepath-securejoin" + units "github.com/docker/go-units" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux" + "github.com/openshift/imagebuilder" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "go.podman.io/common/libnetwork/etchosts" + "go.podman.io/common/pkg/auth" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/parse" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/unshare" + storageTypes "go.podman.io/storage/types" + "golang.org/x/term" +) + +const ( + // SeccompDefaultPath defines the default seccomp path + SeccompDefaultPath = config.SeccompDefaultPath + // SeccompOverridePath if this exists it overrides the default seccomp path + SeccompOverridePath = config.SeccompOverridePath + // TypeBind is the type for mounting host dir + TypeBind = "bind" + // TypeTmpfs is the type for mounting tmpfs + TypeTmpfs = "tmpfs" + // TypeCache is the type for mounting a common persistent cache from host + TypeCache = "cache" + // mount=type=cache must create a persistent directory on host so it's available for all consecutive builds. + // Lifecycle of following directory will be inherited from how host machine treats temporary directory + BuildahCacheDir = "buildah-cache" +) + +var errInvalidSecretSyntax = errors.New("incorrect secret flag format: should be --secret id=foo,src=bar[,env=ENV][,type=file|env]") + +// RepoNamesToNamedReferences parse the raw string to Named reference +func RepoNamesToNamedReferences(destList []string) ([]reference.Named, error) { + var result []reference.Named + for _, dest := range destList { + named, err := reference.ParseNormalizedNamed(dest) + if err != nil { + return nil, fmt.Errorf("invalid repo %q: must contain registry and repository: %w", dest, err) + } + if !reference.IsNameOnly(named) { + return nil, fmt.Errorf("repository must contain neither a tag nor digest: %v", named) + } + result = append(result, named) + } + return result, nil +} + +// CommonBuildOptions parses the build options from the bud cli +func CommonBuildOptions(c *cobra.Command) (*define.CommonBuildOptions, error) { + return CommonBuildOptionsFromFlagSet(c.Flags(), c.Flag) +} + +// If user selected to run with currentLabelOpts then append on the current user and role +func currentLabelOpts() ([]string, error) { + label, err := selinux.CurrentLabel() + if err != nil { + return nil, err + } + if label == "" { + return nil, nil + } + con, err := selinux.NewContext(label) + if err != nil { + return nil, err + } + return []string{ + fmt.Sprintf("label=user:%s", con["user"]), + fmt.Sprintf("label=role:%s", con["role"]), + }, nil +} + +// CommonBuildOptionsFromFlagSet parses the build options from the bud cli +func CommonBuildOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name string) *pflag.Flag) (*define.CommonBuildOptions, error) { + var ( + memoryLimit int64 + memorySwap int64 + noDNS bool + err error + ) + + memVal, _ := flags.GetString("memory") + if memVal != "" { + memoryLimit, err = units.RAMInBytes(memVal) + if err != nil { + return nil, fmt.Errorf("invalid value for memory: %w", err) + } + } + + memSwapValue, _ := flags.GetString("memory-swap") + if memSwapValue != "" { + if memSwapValue == "-1" { + memorySwap = -1 + } else { + memorySwap, err = units.RAMInBytes(memSwapValue) + if err != nil { + return nil, fmt.Errorf("invalid value for memory-swap: %w", err) + } + } + } + + noHostname, _ := flags.GetBool("no-hostname") + noHosts, _ := flags.GetBool("no-hosts") + + addHost, _ := flags.GetStringSlice("add-host") + if len(addHost) > 0 { + if noHosts { + return nil, errors.New("--no-hosts and --add-host conflict, can not be used together") + } + for _, host := range addHost { + if err := validateExtraHost(host); err != nil { + return nil, fmt.Errorf("invalid value for add-host: %w", err) + } + } + } + + noDNS = false + dnsServers := []string{} + if flags.Changed("dns") { + dnsServers, _ = flags.GetStringSlice("dns") + for _, server := range dnsServers { + if strings.ToLower(server) == "none" { + noDNS = true + } + } + if noDNS && len(dnsServers) > 1 { + return nil, errors.New("invalid --dns, --dns=none may not be used with any other --dns options") + } + } + + dnsSearch := []string{} + if flags.Changed("dns-search") { + dnsSearch, _ = flags.GetStringSlice("dns-search") + if noDNS && len(dnsSearch) > 0 { + return nil, errors.New("invalid --dns-search, --dns-search may not be used with --dns=none") + } + } + + dnsOptions := []string{} + if flags.Changed("dns-option") { + dnsOptions, _ = flags.GetStringSlice("dns-option") + if noDNS && len(dnsOptions) > 0 { + return nil, errors.New("invalid --dns-option, --dns-option may not be used with --dns=none") + } + } + + if _, err := units.FromHumanSize(findFlagFunc("shm-size").Value.String()); err != nil { + return nil, fmt.Errorf("invalid --shm-size: %w", err) + } + volumes, _ := flags.GetStringArray("volume") + cpuPeriod, _ := flags.GetUint64("cpu-period") + cpuQuota, _ := flags.GetInt64("cpu-quota") + cpuShares, _ := flags.GetUint64("cpu-shares") + httpProxy, _ := flags.GetBool("http-proxy") + var identityLabel types.OptionalBool + if flags.Changed("identity-label") { + b, _ := flags.GetBool("identity-label") + identityLabel = types.NewOptionalBool(b) + } + omitHistory, _ := flags.GetBool("omit-history") + + ulimit := []string{} + if flags.Changed("ulimit") { + ulimit, _ = flags.GetStringSlice("ulimit") + } + + secrets, _ := flags.GetStringArray("secret") + sshsources, _ := flags.GetStringArray("ssh") + ociHooks, _ := flags.GetStringArray("hooks-dir") + + commonOpts := &define.CommonBuildOptions{ + AddHost: addHost, + CPUPeriod: cpuPeriod, + CPUQuota: cpuQuota, + CPUSetCPUs: findFlagFunc("cpuset-cpus").Value.String(), + CPUSetMems: findFlagFunc("cpuset-mems").Value.String(), + CPUShares: cpuShares, + CgroupParent: findFlagFunc("cgroup-parent").Value.String(), + DNSOptions: dnsOptions, + DNSSearch: dnsSearch, + DNSServers: dnsServers, + HTTPProxy: httpProxy, + IdentityLabel: identityLabel, + Memory: memoryLimit, + MemorySwap: memorySwap, + NoHostname: noHostname, + NoHosts: noHosts, + OmitHistory: omitHistory, + ShmSize: findFlagFunc("shm-size").Value.String(), + Ulimit: ulimit, + Volumes: volumes, + Secrets: secrets, + SSHSources: sshsources, + OCIHooksDir: ociHooks, + } + securityOpts, _ := flags.GetStringArray("security-opt") + defConfig, err := config.Default() + if err != nil { + return nil, fmt.Errorf("failed to get container config: %w", err) + } + if defConfig.Containers.EnableLabeledUsers { + defSecurityOpts, err := currentLabelOpts() + if err != nil { + return nil, err + } + + securityOpts = append(defSecurityOpts, securityOpts...) + } + if err := parseSecurityOpts(securityOpts, commonOpts); err != nil { + return nil, err + } + return commonOpts, nil +} + +// GetAdditionalBuildContext consumes raw string and returns parsed AdditionalBuildContext +func GetAdditionalBuildContext(value string) (define.AdditionalBuildContext, error) { + ret := define.AdditionalBuildContext{IsURL: false, IsImage: false, Value: value} + if strings.HasPrefix(value, "docker-image://") { + ret.IsImage = true + ret.Value = strings.TrimPrefix(value, "docker-image://") + } else if strings.HasPrefix(value, "container-image://") { + ret.IsImage = true + ret.Value = strings.TrimPrefix(value, "container-image://") + } else if strings.HasPrefix(value, "docker://") { + ret.IsImage = true + ret.Value = strings.TrimPrefix(value, "docker://") + } else if strings.HasPrefix(value, "http://") || strings.HasPrefix(value, "https://") { + ret.IsImage = false + ret.IsURL = true + } else { + path, err := filepath.Abs(value) + if err != nil { + return define.AdditionalBuildContext{}, fmt.Errorf("unable to convert additional build-context %q path to absolute: %w", value, err) + } + ret.Value = path + } + return ret, nil +} + +func parseSecurityOpts(securityOpts []string, commonOpts *define.CommonBuildOptions) error { + for _, opt := range securityOpts { + if opt == "no-new-privileges" { + commonOpts.NoNewPrivileges = true + continue + } + + con := strings.SplitN(opt, "=", 2) + if len(con) != 2 { + return fmt.Errorf("invalid --security-opt name=value pair: %q", opt) + } + switch con[0] { + case "label": + commonOpts.LabelOpts = append(commonOpts.LabelOpts, con[1]) + case "apparmor": + commonOpts.ApparmorProfile = con[1] + case "seccomp": + commonOpts.SeccompProfilePath = con[1] + case "mask": + commonOpts.Masks = append(commonOpts.Masks, strings.Split(con[1], ":")...) + case "unmask": + unmasks := strings.Split(con[1], ":") + for _, unmask := range unmasks { + matches, _ := filepath.Glob(unmask) + if len(matches) > 0 { + commonOpts.Unmasks = append(commonOpts.Unmasks, matches...) + continue + } + commonOpts.Unmasks = append(commonOpts.Unmasks, unmask) + } + default: + return fmt.Errorf("invalid --security-opt 2: %q", opt) + } + } + + if commonOpts.SeccompProfilePath == "" { + if err := fileutils.Exists(SeccompOverridePath); err == nil { + commonOpts.SeccompProfilePath = SeccompOverridePath + } else { + if !errors.Is(err, fs.ErrNotExist) { + return err + } + if err := fileutils.Exists(SeccompDefaultPath); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } + } else { + commonOpts.SeccompProfilePath = SeccompDefaultPath + } + } + } + return nil +} + +// Split string into slice by colon. Backslash-escaped colon (i.e. "\:") will not be regarded as separator +func SplitStringWithColonEscape(str string) []string { + return internalParse.SplitStringWithColonEscape(str) +} + +// Volume parses the input of --volume +func Volume(volume string) (specs.Mount, error) { + return internalParse.Volume(volume) +} + +// Volumes validates the host and container paths passed in to the --volume flag +func Volumes(volumes []string) error { + if len(volumes) == 0 { + return nil + } + for _, volume := range volumes { + if _, err := Volume(volume); err != nil { + return err + } + } + return nil +} + +// ValidateVolumeHostDir validates a volume mount's source directory +func ValidateVolumeHostDir(hostDir string) error { + return parse.ValidateVolumeHostDir(hostDir) +} + +// ValidateVolumeCtrDir validates a volume mount's destination directory. +func ValidateVolumeCtrDir(ctrDir string) error { + return parse.ValidateVolumeCtrDir(ctrDir) +} + +// ValidateVolumeOpts validates a volume's options +func ValidateVolumeOpts(options []string) ([]string, error) { + return parse.ValidateVolumeOpts(options) +} + +// validateExtraHost validates that the specified string is a valid extrahost and returns it. +// ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6). +// for add-host flag +func validateExtraHost(val string) error { + // allow for IPv6 addresses in extra hosts by only splitting on first ":" + arr := strings.SplitN(val, ":", 2) + if len(arr) != 2 || len(arr[0]) == 0 { + return fmt.Errorf("bad format for add-host: %q", val) + } + if arr[1] == etchosts.HostGateway { + return nil + } + if _, err := validateIPAddress(arr[1]); err != nil { + return fmt.Errorf("invalid IP address in add-host: %q", arr[1]) + } + return nil +} + +// validateIPAddress validates an Ip address. +// for dns, ip, and ip6 flags also +func validateIPAddress(val string) (string, error) { + ip := net.ParseIP(strings.TrimSpace(val)) + if ip != nil { + return ip.String(), nil + } + return "", fmt.Errorf("%s is not an ip address", val) +} + +// SystemContextFromOptions returns a SystemContext populated with values +// per the input parameters provided by the caller for the use in authentication. +func SystemContextFromOptions(c *cobra.Command) (*types.SystemContext, error) { + return SystemContextFromFlagSet(c.Flags(), c.Flag) +} + +// SystemContextFromFlagSet returns a SystemContext populated with values +// per the input parameters provided by the caller for the use in authentication. +func SystemContextFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name string) *pflag.Flag) (*types.SystemContext, error) { + certDir, err := flags.GetString("cert-dir") + if err != nil { + certDir = "" + } + ctx := &types.SystemContext{ + DockerCertPath: certDir, + } + tlsVerify, err := flags.GetBool("tls-verify") + if err == nil && findFlagFunc("tls-verify").Changed { + ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!tlsVerify) + ctx.OCIInsecureSkipTLSVerify = !tlsVerify + ctx.DockerDaemonInsecureSkipTLSVerify = !tlsVerify + } + insecure, err := flags.GetBool("insecure") + if err == nil && findFlagFunc("insecure").Changed { + if ctx.DockerInsecureSkipTLSVerify != types.OptionalBoolUndefined { + return nil, errors.New("--insecure may not be used with --tls-verify") + } + ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(insecure) + ctx.OCIInsecureSkipTLSVerify = insecure + ctx.DockerDaemonInsecureSkipTLSVerify = insecure + } + disableCompression, err := flags.GetBool("disable-compression") + if err == nil { + if disableCompression { + ctx.OCIAcceptUncompressedLayers = true + } else { + ctx.DirForceCompress = true + } + } + creds, err := flags.GetString("creds") + if err == nil && findFlagFunc("creds").Changed { + var err error + ctx.DockerAuthConfig, err = AuthConfig(creds) + if err != nil { + return nil, err + } + } + sigPolicy, err := flags.GetString("signature-policy") + if err == nil && findFlagFunc("signature-policy").Changed { + ctx.SignaturePolicyPath = sigPolicy + } + authfile, err := flags.GetString("authfile") + if err == nil { + ctx.AuthFilePath = getAuthFile(authfile) + } + regConf, err := flags.GetString("registries-conf") + if err == nil && findFlagFunc("registries-conf").Changed { + ctx.SystemRegistriesConfPath = regConf + } + regConfDir, err := flags.GetString("registries-conf-dir") + if err == nil && findFlagFunc("registries-conf-dir").Changed { + ctx.RegistriesDirPath = regConfDir + } + shortNameAliasConf, err := flags.GetString("short-name-alias-conf") + if err == nil && findFlagFunc("short-name-alias-conf").Changed { + ctx.UserShortNameAliasConfPath = shortNameAliasConf + } + ctx.DockerRegistryUserAgent = fmt.Sprintf("Buildah/%s", define.Version) + if findFlagFunc("os") != nil && findFlagFunc("os").Changed { + var os string + if os, err = flags.GetString("os"); err != nil { + return nil, err + } + ctx.OSChoice = os + } + if findFlagFunc("arch") != nil && findFlagFunc("arch").Changed { + var arch string + if arch, err = flags.GetString("arch"); err != nil { + return nil, err + } + ctx.ArchitectureChoice = arch + } + if findFlagFunc("variant") != nil && findFlagFunc("variant").Changed { + var variant string + if variant, err = flags.GetString("variant"); err != nil { + return nil, err + } + ctx.VariantChoice = variant + } + if findFlagFunc("platform") != nil && findFlagFunc("platform").Changed { + var specs []string + if specs, err = flags.GetStringSlice("platform"); err != nil { + return nil, err + } + if len(specs) == 0 || specs[0] == "" { + return nil, fmt.Errorf("unable to parse --platform value %v", specs) + } + platform := specs[0] + os, arch, variant, err := Platform(platform) + if err != nil { + return nil, err + } + if ctx.OSChoice != "" || ctx.ArchitectureChoice != "" || ctx.VariantChoice != "" { + return nil, errors.New("invalid --platform may not be used with --os, --arch, or --variant") + } + ctx.OSChoice = os + ctx.ArchitectureChoice = arch + ctx.VariantChoice = variant + } + + ctx.BigFilesTemporaryDir = GetTempDir() + return ctx, nil +} + +// pullPolicyWithFlags parses a string value of a pull policy, evaluating it in +// combination with "always" and "never" boolean flags. +// Allow for: +// * --pull +// * --pull="" +// * --pull=true +// * --pull=false +// * --pull=never +// * --pull=always +// * --pull=ifmissing +// * --pull=missing +// * --pull=notpresent +// * --pull=newer +// * --pull=ifnewer +// and --pull-always and --pull-never as boolean flags. +func pullPolicyWithFlags(policySpec string, always, never bool) (define.PullPolicy, error) { + if always { + return define.PullAlways, nil + } + if never { + return define.PullNever, nil + } + policy := strings.ToLower(policySpec) + switch policy { + case "missing", "ifmissing", "notpresent": + return define.PullIfMissing, nil + case "true", "always": + return define.PullAlways, nil + case "false", "never": + return define.PullNever, nil + case "ifnewer", "newer": + return define.PullIfNewer, nil + } + return 0, fmt.Errorf("unrecognized pull policy %q", policySpec) +} + +// PullPolicyFromOptions returns a PullPolicy that reflects the combination of +// the specified "pull" and undocumented "pull-always" and "pull-never" flags. +func PullPolicyFromOptions(c *cobra.Command) (define.PullPolicy, error) { + return PullPolicyFromFlagSet(c.Flags(), c.Flag) +} + +// PullPolicyFromFlagSet returns a PullPolicy that reflects the combination of +// the specified "pull" and undocumented "pull-always" and "pull-never" flags. +func PullPolicyFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name string) *pflag.Flag) (define.PullPolicy, error) { + pullFlagsCount := 0 + + if findFlagFunc("pull").Changed { + pullFlagsCount++ + } + if findFlagFunc("pull-always").Changed { + pullFlagsCount++ + } + if findFlagFunc("pull-never").Changed { + pullFlagsCount++ + } + + if pullFlagsCount > 1 { + return 0, errors.New("can only set one of 'pull' or 'pull-always' or 'pull-never'") + } + + // The --pull-never and --pull-always options will not be documented. + pullAlwaysFlagValue, err := flags.GetBool("pull-always") + if err != nil { + return 0, fmt.Errorf("checking the --pull-always flag value: %w", err) + } + pullNeverFlagValue, err := flags.GetBool("pull-never") + if err != nil { + return 0, fmt.Errorf("checking the --pull-never flag value: %w", err) + } + + // The --pull[=...] flag is the one we really care about. + pullFlagValue := findFlagFunc("pull").Value.String() + pullPolicy, err := pullPolicyWithFlags(pullFlagValue, pullAlwaysFlagValue, pullNeverFlagValue) + if err != nil { + return 0, err + } + + logrus.Debugf("Pull Policy for pull [%v]", pullPolicy) + + return pullPolicy, nil +} + +func getAuthFile(authfile string) string { + if authfile != "" { + absAuthfile, err := filepath.Abs(authfile) + if err == nil { + return absAuthfile + } + logrus.Warnf("ignoring passed-in auth file path, evaluating it: %v", err) + } + return auth.GetDefaultAuthFile() +} + +// PlatformFromOptions parses the operating system (os) and architecture (arch) +// from the provided command line options. Deprecated in favor of +// PlatformsFromOptions(), but kept here because it's part of our API. +func PlatformFromOptions(c *cobra.Command) (os, arch string, err error) { + platforms, err := PlatformsFromOptions(c) + if err != nil { + return "", "", err + } + if len(platforms) < 1 { + return "", "", errors.New("invalid platform syntax for --platform (use OS/ARCH[/VARIANT])") + } + return platforms[0].OS, platforms[0].Arch, nil +} + +// PlatformsFromOptions parses the operating system (os) and architecture +// (arch) from the provided command line options. If --platform used, it +// also returns the list of platforms that were passed in as its argument. +func PlatformsFromOptions(c *cobra.Command) (platforms []struct{ OS, Arch, Variant string }, err error) { + var os, arch, variant string + if c.Flag("os").Changed { + if os, err = c.Flags().GetString("os"); err != nil { + return nil, err + } + } + if c.Flag("arch").Changed { + if arch, err = c.Flags().GetString("arch"); err != nil { + return nil, err + } + } + if c.Flag("variant").Changed { + if variant, err = c.Flags().GetString("variant"); err != nil { + return nil, err + } + } + platforms = []struct{ OS, Arch, Variant string }{{os, arch, variant}} + if c.Flag("platform").Changed { + platforms = nil + platformSpecs, err := c.Flags().GetStringSlice("platform") + if err != nil { + return nil, fmt.Errorf("unable to parse platform: %w", err) + } + if os != "" || arch != "" || variant != "" { + return nil, fmt.Errorf("invalid --platform may not be used with --os, --arch, or --variant") + } + for _, pf := range platformSpecs { + if os, arch, variant, err = Platform(pf); err != nil { + return nil, fmt.Errorf("unable to parse platform %q: %w", pf, err) + } + platforms = append(platforms, struct{ OS, Arch, Variant string }{os, arch, variant}) + } + } + return platforms, nil +} + +// DefaultPlatform returns the standard platform for the current system +func DefaultPlatform() string { + return platforms.DefaultString() +} + +// Platform separates the platform string into os, arch and variant, +// accepting any of $arch, $os/$arch, or $os/$arch/$variant. +func Platform(platform string) (os, arch, variant string, err error) { + platform = strings.Trim(platform, "/") + if platform == "local" || platform == "" { + return Platform(DefaultPlatform()) + } + platformSpec, err := platforms.Parse(platform) + if err != nil { + return "", "", "", fmt.Errorf("invalid platform syntax for --platform=%q: %w", platform, err) + } + return platformSpec.OS, platformSpec.Architecture, platformSpec.Variant, nil +} + +func parseCreds(creds string) (string, string) { + if creds == "" { + return "", "" + } + up := strings.SplitN(creds, ":", 2) + if len(up) == 1 { + return up[0], "" + } + if up[0] == "" { + return "", up[1] + } + return up[0], up[1] +} + +// AuthConfig parses the creds in format [username[:password] into an auth +// config. +func AuthConfig(creds string) (*types.DockerAuthConfig, error) { + username, password := parseCreds(creds) + if username == "" { + fmt.Print("Username: ") + if _, err := fmt.Scanln(&username); err != nil { + return nil, fmt.Errorf("reading user name: %w", err) + } + } + if password == "" { + fmt.Print("Password: ") + termPassword, err := term.ReadPassword(0) + if err != nil { + return nil, fmt.Errorf("could not read password from terminal: %w", err) + } + password = string(termPassword) + } + + return &types.DockerAuthConfig{ + Username: username, + Password: password, + }, nil +} + +// GetBuildOutput is responsible for parsing custom build output argument i.e `build --output` flag. +// Takes `buildOutput` as string and returns BuildOutputOption +func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) { + if buildOutput == "-" { + // Feature parity with buildkit, output tar to stdout + // Read more here: https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs + return define.BuildOutputOption{ + Path: "", + IsDir: false, + IsStdout: true, + }, nil + } + if !strings.Contains(buildOutput, ",") { + // expect default --output + return define.BuildOutputOption{ + Path: buildOutput, + IsDir: true, + IsStdout: false, + }, nil + } + isDir := true + isStdout := false + typeSelected := "" + pathSelected := "" + for option := range strings.SplitSeq(buildOutput, ",") { + key, value, found := strings.Cut(option, "=") + if !found { + return define.BuildOutputOption{}, fmt.Errorf("invalid build output options %q, expected format key=value", buildOutput) + } + switch key { + case "type": + if typeSelected != "" { + return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", key) + } + typeSelected = value + switch typeSelected { + case "local": + isDir = true + case "tar": + isDir = false + default: + return define.BuildOutputOption{}, fmt.Errorf("invalid type %q selected for build output options %q", value, buildOutput) + } + case "dest": + if pathSelected != "" { + return define.BuildOutputOption{}, fmt.Errorf("duplicate %q not supported", key) + } + pathSelected = value + default: + return define.BuildOutputOption{}, fmt.Errorf("unrecognized key %q in build output option: %q", key, buildOutput) + } + } + + if typeSelected == "" || pathSelected == "" { + return define.BuildOutputOption{}, fmt.Errorf(`invalid build output option %q, accepted keys are "type" and "dest" must be present`, buildOutput) + } + + if pathSelected == "-" { + if isDir { + return define.BuildOutputOption{}, fmt.Errorf(`invalid build output option %q, "type=local" can not be used with "dest=-"`, buildOutput) + } + } + + return define.BuildOutputOption{Path: pathSelected, IsDir: isDir, IsStdout: isStdout}, nil +} + +// TeeType parses a string value and returns a TeeType +func TeeType(teeType string) define.TeeType { + return define.TeeType(strings.ToLower(teeType)) +} + +// GetConfidentialWorkloadOptions parses a confidential workload settings +// argument, which controls both whether or not we produce an image that +// expects to be run using krun, and how we handle things like encrypting +// the disk image that the container image will contain. +func GetConfidentialWorkloadOptions(arg string) (define.ConfidentialWorkloadOptions, error) { + options := define.ConfidentialWorkloadOptions{ + TempDir: GetTempDir(), + } + defaults := options + for option := range strings.SplitSeq(arg, ",") { + var err error + switch { + case strings.HasPrefix(option, "type="): + options.TeeType = TeeType(strings.TrimPrefix(option, "type=")) + switch options.TeeType { + case define.SEV, define.SNP, mkcwtypes.SEV_NO_ES: + default: + return options, fmt.Errorf("parsing type= value %q: unrecognized value", options.TeeType) + } + case strings.HasPrefix(option, "attestation_url="), strings.HasPrefix(option, "attestation-url="): + options.Convert = true + options.AttestationURL = strings.TrimPrefix(option, "attestation_url=") + if options.AttestationURL == option { + options.AttestationURL = strings.TrimPrefix(option, "attestation-url=") + } + case strings.HasPrefix(option, "passphrase="): + options.Convert = true + options.DiskEncryptionPassphrase = strings.TrimPrefix(option, "passphrase=") + case strings.HasPrefix(option, "workload_id="), strings.HasPrefix(option, "workload-id="): + options.WorkloadID = strings.TrimPrefix(option, "workload_id=") + if options.WorkloadID == option { + options.WorkloadID = strings.TrimPrefix(option, "workload-id=") + } + case strings.HasPrefix(option, "cpus="): + options.CPUs, err = strconv.Atoi(strings.TrimPrefix(option, "cpus=")) + if err != nil { + return options, fmt.Errorf("parsing cpus= value %q: %w", strings.TrimPrefix(option, "cpus="), err) + } + case strings.HasPrefix(option, "memory="): + options.Memory, err = strconv.Atoi(strings.TrimPrefix(option, "memory=")) + if err != nil { + return options, fmt.Errorf("parsing memory= value %q: %w", strings.TrimPrefix(option, "memorys"), err) + } + case option == "ignore_attestation_errors", option == "ignore-attestation-errors": + options.IgnoreAttestationErrors = true + case strings.HasPrefix(option, "ignore_attestation_errors="), strings.HasPrefix(option, "ignore-attestation-errors="): + val := strings.TrimPrefix(option, "ignore_attestation_errors=") + if val == option { + val = strings.TrimPrefix(option, "ignore-attestation-errors=") + } + options.IgnoreAttestationErrors = val == "true" || val == "yes" || val == "on" || val == "1" + case strings.HasPrefix(option, "firmware-library="), strings.HasPrefix(option, "firmware_library="): + val := strings.TrimPrefix(option, "firmware-library=") + if val == option { + val = strings.TrimPrefix(option, "firmware_library=") + } + options.FirmwareLibrary = val + case strings.HasPrefix(option, "slop="): + options.Slop = strings.TrimPrefix(option, "slop=") + default: + knownOptions := []string{"type", "attestation_url", "passphrase", "workload_id", "cpus", "memory", "firmware_library", "slop"} + return options, fmt.Errorf("expected one or more of %q as arguments for --cw, not %q", knownOptions, option) + } + } + if options != defaults && !options.Convert { + return options, fmt.Errorf("--cw arguments missing one or more of (%q, %q)", "passphrase", "attestation_url") + } + return options, nil +} + +// SBOMScanOptions parses the build options from the cli +func SBOMScanOptions(c *cobra.Command) (*define.SBOMScanOptions, error) { + return SBOMScanOptionsFromFlagSet(c.Flags(), c.Flag) +} + +// SBOMScanOptionsFromFlagSet parses scan settings from the cli +func SBOMScanOptionsFromFlagSet(flags *pflag.FlagSet, _ func(name string) *pflag.Flag) (*define.SBOMScanOptions, error) { + preset, err := flags.GetString("sbom") + if err != nil { + return nil, fmt.Errorf("invalid value for --sbom: %w", err) + } + + options, err := sbom.Preset(preset) + if err != nil { + return nil, err + } + if options == nil { + return nil, fmt.Errorf("parsing --sbom flag: unrecognized preset name %q", preset) + } + image, err := flags.GetString("sbom-scanner-image") + if err != nil { + return nil, fmt.Errorf("invalid value for --sbom-scanner-image: %w", err) + } + commands, err := flags.GetStringArray("sbom-scanner-command") + if err != nil { + return nil, fmt.Errorf("invalid value for --sbom-scanner-command: %w", err) + } + mergeStrategy, err := flags.GetString("sbom-merge-strategy") + if err != nil { + return nil, fmt.Errorf("invalid value for --sbom-merge-strategy: %w", err) + } + + if image != "" || len(commands) > 0 || mergeStrategy != "" { + options = &define.SBOMScanOptions{ + Image: image, + Commands: slices.Clone(commands), + MergeStrategy: define.SBOMMergeStrategy(mergeStrategy), + } + } + if options.ImageSBOMOutput, err = flags.GetString("sbom-image-output"); err != nil { + return nil, fmt.Errorf("invalid value for --sbom-image-output: %w", err) + } + if options.SBOMOutput, err = flags.GetString("sbom-output"); err != nil { + return nil, fmt.Errorf("invalid value for --sbom-output: %w", err) + } + if options.ImagePURLOutput, err = flags.GetString("sbom-image-purl-output"); err != nil { + return nil, fmt.Errorf("invalid value for --sbom-image-purl-output: %w", err) + } + if options.PURLOutput, err = flags.GetString("sbom-purl-output"); err != nil { + return nil, fmt.Errorf("invalid value for --sbom-purl-output: %w", err) + } + + if options.Image == "" || len(options.Commands) == 0 { + return options, fmt.Errorf("sbom configuration missing one or more of (%q or %q)", "--sbom-scanner-image", "--sbom-scanner-command") + } + if options.SBOMOutput == "" && options.ImageSBOMOutput == "" && options.PURLOutput == "" && options.ImagePURLOutput == "" { + return options, fmt.Errorf("sbom configuration missing one or more of (%q, %q, %q or %q)", "--sbom-output", "--sbom-image-output", "--sbom-purl-output", "--sbom-image-purl-output") + } + if len(options.Commands) > 1 && options.MergeStrategy == "" { + return options, fmt.Errorf("sbom configuration included multiple %q values but no %q value", "--sbom-scanner-command", "--sbom-merge-strategy") + } + switch options.MergeStrategy { + default: + return options, fmt.Errorf("sbom arguments included unrecognized merge strategy %q", string(options.MergeStrategy)) + case define.SBOMMergeStrategyCat, define.SBOMMergeStrategyCycloneDXByComponentNameAndVersion, define.SBOMMergeStrategySPDXByPackageNameAndVersionInfo: + // all good here + } + return options, nil +} + +// IDMappingOptions parses the build options related to user namespaces and ID mapping. +func IDMappingOptions(c *cobra.Command, _ define.Isolation) (usernsOptions define.NamespaceOptions, idmapOptions *define.IDMappingOptions, err error) { + return IDMappingOptionsFromFlagSet(c.Flags(), c.PersistentFlags(), c.Flag) +} + +// GetAutoOptions returns a AutoUserNsOptions with the settings to setup automatically +// a user namespace. +func GetAutoOptions(base string) (*storageTypes.AutoUserNsOptions, error) { + parts := strings.SplitN(base, ":", 2) + if parts[0] != "auto" { + return nil, errors.New("wrong user namespace mode") + } + options := storageTypes.AutoUserNsOptions{} + if len(parts) == 1 { + return &options, nil + } + for o := range strings.SplitSeq(parts[1], ",") { + v := strings.SplitN(o, "=", 2) + if len(v) != 2 { + return nil, fmt.Errorf("invalid option specified: %q", o) + } + switch v[0] { + case "size": + s, err := strconv.ParseUint(v[1], 10, 32) + if err != nil { + return nil, err + } + options.Size = uint32(s) + case "uidmapping": + mapping, err := storageTypes.ParseIDMapping([]string{v[1]}, nil, "", "") + if err != nil { + return nil, err + } + options.AdditionalUIDMappings = append(options.AdditionalUIDMappings, mapping.UIDMap...) + case "gidmapping": + mapping, err := storageTypes.ParseIDMapping(nil, []string{v[1]}, "", "") + if err != nil { + return nil, err + } + options.AdditionalGIDMappings = append(options.AdditionalGIDMappings, mapping.GIDMap...) + default: + return nil, fmt.Errorf("unknown option specified: %q", v[0]) + } + } + return &options, nil +} + +// IDMappingOptionsFromFlagSet parses the build options related to user namespaces and ID mapping. +func IDMappingOptionsFromFlagSet(flags *pflag.FlagSet, persistentFlags *pflag.FlagSet, findFlagFunc func(name string) *pflag.Flag) (usernsOptions define.NamespaceOptions, idmapOptions *define.IDMappingOptions, err error) { + isAuto := false + autoOpts := &storageTypes.AutoUserNsOptions{} + user := findFlagFunc("userns-uid-map-user").Value.String() + group := findFlagFunc("userns-gid-map-group").Value.String() + // If only the user or group was specified, use the same value for the + // other, since we need both in order to initialize the maps using the + // names. + if user == "" && group != "" { + user = group + } + if group == "" && user != "" { + group = user + } + // Either start with empty maps or the name-based maps. + mappings := idtools.NewIDMappingsFromMaps(nil, nil) + if user != "" && group != "" { + submappings, err := idtools.NewIDMappings(user, group) + if err != nil { + return nil, nil, err + } + mappings = submappings + } + globalOptions := persistentFlags + // We'll parse the UID and GID mapping options the same way. + buildIDMap := func(basemap []idtools.IDMap, option string) ([]specs.LinuxIDMapping, error) { + outmap := make([]specs.LinuxIDMapping, 0, len(basemap)) + // Start with the name-based map entries. + for _, m := range basemap { + outmap = append(outmap, specs.LinuxIDMapping{ + ContainerID: uint32(m.ContainerID), + HostID: uint32(m.HostID), + Size: uint32(m.Size), + }) + } + // Parse the flag's value as one or more triples (if it's even + // been set), and append them. + var spec []string + if globalOptions.Lookup(option) != nil && globalOptions.Lookup(option).Changed { + spec, _ = globalOptions.GetStringSlice(option) + } + if findFlagFunc(option).Changed { + spec, _ = flags.GetStringSlice(option) + } + idmap, err := parseIDMap(spec) + if err != nil { + return nil, err + } + for _, m := range idmap { + outmap = append(outmap, specs.LinuxIDMapping{ + ContainerID: m[0], + HostID: m[1], + Size: m[2], + }) + } + return outmap, nil + } + uidmap, err := buildIDMap(mappings.UIDs(), "userns-uid-map") + if err != nil { + return nil, nil, err + } + gidmap, err := buildIDMap(mappings.GIDs(), "userns-gid-map") + if err != nil { + return nil, nil, err + } + // If we only have one map or the other populated at this point, then + // use the same mapping for both, since we know that no user or group + // name was specified, but a specific mapping was for one or the other. + if len(uidmap) == 0 && len(gidmap) != 0 { + uidmap = gidmap + } + if len(gidmap) == 0 && len(uidmap) != 0 { + gidmap = uidmap + } + + // By default, having mappings configured means we use a user + // namespace. Otherwise, we don't. + usernsOption := define.NamespaceOption{ + Name: string(specs.UserNamespace), + Host: len(uidmap) == 0 && len(gidmap) == 0, + } + // If the user specifically requested that we either use or don't use + // user namespaces, override that default. + if findFlagFunc("userns").Changed { + how := findFlagFunc("userns").Value.String() + if strings.HasPrefix(how, "auto") { + autoOpts, err = GetAutoOptions(how) + if err != nil { + return nil, nil, err + } + isAuto = true + usernsOption.Host = false + } else { + switch how { + case "", "container", "private": + usernsOption.Host = false + case "host": + usernsOption.Host = true + default: + how = strings.TrimPrefix(how, "ns:") + if err := fileutils.Exists(how); err != nil { + return nil, nil, fmt.Errorf("checking %s namespace: %w", string(specs.UserNamespace), err) + } + logrus.Debugf("setting %q namespace to %q", string(specs.UserNamespace), how) + usernsOption.Path = how + } + } + } + usernsOptions = define.NamespaceOptions{usernsOption} + + // If the user requested that we use the host namespace, but also that + // we use mappings, that's not going to work. + if (len(uidmap) != 0 || len(gidmap) != 0) && usernsOption.Host { + return nil, nil, fmt.Errorf("can not specify ID mappings while using host's user namespace") + } + return usernsOptions, &define.IDMappingOptions{ + HostUIDMapping: usernsOption.Host, + HostGIDMapping: usernsOption.Host, + UIDMap: uidmap, + GIDMap: gidmap, + AutoUserNs: isAuto, + AutoUserNsOpts: *autoOpts, + }, nil +} + +func parseIDMap(spec []string) (m [][3]uint32, err error) { + for _, s := range spec { + args := strings.FieldsFunc(s, func(r rune) bool { return !unicode.IsDigit(r) }) + if len(args)%3 != 0 { + return nil, fmt.Errorf("mapping %q is not in the form containerid:hostid:size[,...]", s) + } + for len(args) >= 3 { + cid, err := strconv.ParseUint(args[0], 10, 32) + if err != nil { + return nil, fmt.Errorf("parsing container ID %q from mapping %q as a number: %w", args[0], s, err) + } + hostid, err := strconv.ParseUint(args[1], 10, 32) + if err != nil { + return nil, fmt.Errorf("parsing host ID %q from mapping %q as a number: %w", args[1], s, err) + } + size, err := strconv.ParseUint(args[2], 10, 32) + if err != nil { + return nil, fmt.Errorf("parsing %q from mapping %q as a number: %w", args[2], s, err) + } + m = append(m, [3]uint32{uint32(cid), uint32(hostid), uint32(size)}) + args = args[3:] + } + } + return m, nil +} + +// NamespaceOptions parses the build options for all namespaces except for user namespace. +func NamespaceOptions(c *cobra.Command) (namespaceOptions define.NamespaceOptions, networkPolicy define.NetworkConfigurationPolicy, err error) { + return NamespaceOptionsFromFlagSet(c.Flags(), c.Flag) +} + +// NamespaceOptionsFromFlagSet parses the build options for all namespaces except for user namespace. +func NamespaceOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name string) *pflag.Flag) (namespaceOptions define.NamespaceOptions, networkPolicy define.NetworkConfigurationPolicy, err error) { + options := make(define.NamespaceOptions, 0, 7) + policy := define.NetworkDefault + for _, what := range []string{"cgroupns", string(specs.IPCNamespace), "network", string(specs.PIDNamespace), string(specs.UTSNamespace)} { + if flags.Lookup(what) != nil && findFlagFunc(what).Changed { + how := findFlagFunc(what).Value.String() + switch what { + case "cgroupns": + what = string(specs.CgroupNamespace) + } + switch how { + case "", "container", "private": + logrus.Debugf("setting %q namespace to %q", what, "") + policy = define.NetworkEnabled + options.AddOrReplace(define.NamespaceOption{ + Name: what, + }) + case "host": + logrus.Debugf("setting %q namespace to host", what) + policy = define.NetworkEnabled + options.AddOrReplace(define.NamespaceOption{ + Name: what, + Host: true, + }) + default: + if what == string(specs.NetworkNamespace) { + if how == "none" { + options.AddOrReplace(define.NamespaceOption{ + Name: what, + }) + policy = define.NetworkDisabled + logrus.Debugf("setting network to disabled") + break + } + } + how = strings.TrimPrefix(how, "ns:") + // if not a path we assume it is a comma separated network list, see setupNamespaces() in run_linux.go + if filepath.IsAbs(how) || what != string(specs.NetworkNamespace) { + if err := fileutils.Exists(how); err != nil { + return nil, define.NetworkDefault, fmt.Errorf("checking %s namespace: %w", what, err) + } + } + policy = define.NetworkEnabled + logrus.Debugf("setting %q namespace to %q", what, how) + options.AddOrReplace(define.NamespaceOption{ + Name: what, + Path: how, + }) + } + } + } + return options, policy, nil +} + +func defaultIsolation() (define.Isolation, error) { + isolation, isSet := os.LookupEnv("BUILDAH_ISOLATION") + if isSet { + switch strings.ToLower(isolation) { + case "oci": + return define.IsolationOCI, nil + case "rootless": + return define.IsolationOCIRootless, nil + case "chroot": + return define.IsolationChroot, nil + default: + return 0, fmt.Errorf("unrecognized $BUILDAH_ISOLATION value %q", isolation) + } + } + if unshare.IsRootless() { + return define.IsolationOCIRootless, nil + } + return define.IsolationDefault, nil +} + +// IsolationOption parses the --isolation flag. +func IsolationOption(isolation string) (define.Isolation, error) { + if isolation != "" { + switch strings.ToLower(isolation) { + case "oci", "default": + return define.IsolationOCI, nil + case "rootless": + return define.IsolationOCIRootless, nil + case "chroot": + return define.IsolationChroot, nil + default: + return 0, fmt.Errorf("unrecognized isolation type %q", isolation) + } + } + return defaultIsolation() +} + +// Device parses device mapping string to a src, dest & permissions string +// Valid values for device look like: +// +// '/dev/sdc" +// '/dev/sdc:/dev/xvdc" +// '/dev/sdc:/dev/xvdc:rwm" +// '/dev/sdc:rm" +func Device(device string) (string, string, string, error) { + src := "" + dst := "" + permissions := "rwm" + arr := strings.Split(device, ":") + switch len(arr) { + case 3: + if !isValidDeviceMode(arr[2]) { + return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2]) + } + permissions = arr[2] + fallthrough + case 2: + if isValidDeviceMode(arr[1]) { + permissions = arr[1] + } else { + if len(arr[1]) == 0 || arr[1][0] != '/' { + return "", "", "", fmt.Errorf("invalid device mode: %s", arr[1]) + } + dst = arr[1] + } + fallthrough + case 1: + if len(arr[0]) > 0 { + src = arr[0] + break + } + fallthrough + default: + return "", "", "", fmt.Errorf("invalid device specification: %s", device) + } + + if dst == "" { + dst = src + } + return src, dst, permissions, nil +} + +// isValidDeviceMode checks if the mode for device is valid or not. +// isValid mode is a composition of r (read), w (write), and m (mknod). +func isValidDeviceMode(mode string) bool { + legalDeviceMode := map[rune]struct{}{ + 'r': {}, + 'w': {}, + 'm': {}, + } + if mode == "" { + return false + } + for _, c := range mode { + if _, has := legalDeviceMode[c]; !has { + return false + } + delete(legalDeviceMode, c) + } + return true +} + +// GetTempDir returns the path of the preferred temporary directory on the host. +func GetTempDir() string { + return tmpdir.GetTempDir() +} + +// Secrets parses the --secret flag +func Secrets(secrets []string) (map[string]define.Secret, error) { + parsed := make(map[string]define.Secret) + for _, secret := range secrets { + tokens := strings.Split(secret, ",") + var id, src, typ string + for _, val := range tokens { + kv := strings.SplitN(val, "=", 2) + switch kv[0] { + case "id": + id = kv[1] + case "src": + src = kv[1] + case "env": + src = kv[1] + typ = "env" + case "type": + if kv[1] != "file" && kv[1] != "env" { + return nil, errors.New("invalid secret type, must be file or env") + } + typ = kv[1] + default: + return nil, errInvalidSecretSyntax + } + } + if id == "" { + return nil, errInvalidSecretSyntax + } + if src == "" { + src = id + } + if typ == "" { + if _, ok := os.LookupEnv(id); ok { + typ = "env" + } else { + typ = "file" + } + } + + if typ == "file" { + fullPath, err := filepath.Abs(src) + if err != nil { + return nil, fmt.Errorf("could not parse secrets: %w", err) + } + err = fileutils.Exists(fullPath) + if err != nil { + return nil, fmt.Errorf("could not parse secrets: %w", err) + } + src = fullPath + } + newSecret := define.Secret{ + ID: id, + Source: src, + SourceType: typ, + } + parsed[id] = newSecret + } + return parsed, nil +} + +// SSH parses the --ssh flag +func SSH(sshSources []string) (map[string]*sshagent.Source, error) { + parsed := make(map[string]*sshagent.Source) + var paths []string + for _, v := range sshSources { + parts := strings.SplitN(v, "=", 2) + if len(parts) > 1 { + paths = strings.Split(parts[1], ",") + } + + source, err := sshagent.NewSource(paths) + if err != nil { + return nil, err + } + parsed[parts[0]] = source + } + return parsed, nil +} + +// ContainerIgnoreFile consumes path to `dockerignore` or `containerignore` +// and returns list of files to exclude along with the path to processed ignore +// file. Deprecated since this might become internal only, please avoid relying +// on this function. +func ContainerIgnoreFile(contextDir, path string, containerFiles []string) ([]string, string, error) { + if path != "" { + excludes, err := imagebuilder.ParseIgnore(path) + return excludes, path, err + } + // If path was not supplied give priority to `.containerignore` first. + for _, containerfile := range containerFiles { + if !filepath.IsAbs(containerfile) { + containerfile = filepath.Join(contextDir, containerfile) + } + containerfileIgnore := "" + if err := fileutils.Exists(containerfile + ".containerignore"); err == nil { + containerfileIgnore = containerfile + ".containerignore" + } + if err := fileutils.Exists(containerfile + ".dockerignore"); err == nil { + containerfileIgnore = containerfile + ".dockerignore" + } + if containerfileIgnore != "" { + excludes, err := imagebuilder.ParseIgnore(containerfileIgnore) + return excludes, containerfileIgnore, err + } + } + path, symlinkErr := securejoin.SecureJoin(contextDir, ".containerignore") + if symlinkErr != nil { + return nil, "", symlinkErr + } + excludes, err := imagebuilder.ParseIgnore(path) + if errors.Is(err, os.ErrNotExist) { + path, symlinkErr = securejoin.SecureJoin(contextDir, ".dockerignore") + if symlinkErr != nil { + return nil, "", symlinkErr + } + excludes, err = imagebuilder.ParseIgnore(path) + } + if errors.Is(err, os.ErrNotExist) { + return excludes, "", nil + } + return excludes, path, err +} diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse_unix.go b/vendor/github.com/containers/buildah/pkg/parse/parse_unix.go new file mode 100644 index 00000000..e3ac5a6a --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/parse/parse_unix.go @@ -0,0 +1,56 @@ +//go:build linux || darwin + +package parse + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/containers/buildah/define" + "github.com/opencontainers/cgroups/devices/config" + "github.com/opencontainers/runc/libcontainer/devices" +) + +func DeviceFromPath(device string) (define.ContainerDevices, error) { + var devs define.ContainerDevices + src, dst, permissions, err := Device(device) + if err != nil { + return nil, err + } + if linkTarget, err := os.Readlink(src); err == nil { + if filepath.IsAbs(linkTarget) { + src = linkTarget + } else { + src = filepath.Join(filepath.Dir(src), linkTarget) + } + } + srcInfo, err := os.Stat(src) + if err != nil { + return nil, fmt.Errorf("getting info of source device %s: %w", src, err) + } + + if !srcInfo.IsDir() { + dev, err := devices.DeviceFromPath(src, permissions) + if err != nil { + return nil, fmt.Errorf("%s is not a valid device: %w", src, err) + } + dev.Path = dst + device := define.BuildahDevice{Device: *dev, Source: src, Destination: dst} + devs = append(devs, device) + return devs, nil + } + + // If source device is a directory + srcDevices, err := devices.GetDevices(src) + if err != nil { + return nil, fmt.Errorf("getting source devices from directory %s: %w", src, err) + } + for _, d := range srcDevices { + d.Path = filepath.Join(dst, filepath.Base(d.Path)) + d.Permissions = config.Permissions(permissions) + device := define.BuildahDevice{Device: *d, Source: src, Destination: dst} + devs = append(devs, device) + } + return devs, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse_unsupported.go b/vendor/github.com/containers/buildah/pkg/parse/parse_unsupported.go new file mode 100644 index 00000000..634220f5 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/parse/parse_unsupported.go @@ -0,0 +1,17 @@ +//go:build !linux && !darwin + +package parse + +import ( + "errors" + + "github.com/containers/buildah/define" +) + +func getDefaultProcessLimits() []string { + return []string{} +} + +func DeviceFromPath(device string) (define.ContainerDevices, error) { + return nil, errors.New("devices not supported") +} diff --git a/vendor/github.com/containers/buildah/pkg/rusage/rusage.go b/vendor/github.com/containers/buildah/pkg/rusage/rusage.go new file mode 100644 index 00000000..7b1226d2 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/rusage/rusage.go @@ -0,0 +1,48 @@ +package rusage + +import ( + "fmt" + "time" + + units "github.com/docker/go-units" +) + +// Rusage is a subset of a Unix-style resource usage counter for the current +// process and its children. The counters are always 0 on platforms where the +// system call is not available (i.e., systems where getrusage() doesn't +// exist). +type Rusage struct { + Date time.Time + Elapsed time.Duration + Utime, Stime time.Duration + Inblock, Outblock int64 +} + +// FormatDiff formats the result of rusage.Rusage.Subtract() for logging. +func FormatDiff(diff Rusage) string { + return fmt.Sprintf("%s(system) %s(user) %s(elapsed) %s input %s output", diff.Stime.Round(time.Millisecond), diff.Utime.Round(time.Millisecond), diff.Elapsed.Round(time.Millisecond), units.HumanSize(float64(diff.Inblock*512)), units.HumanSize(float64(diff.Outblock*512))) +} + +// Subtract subtracts the items in delta from r, and returns the difference. +// The Date field is zeroed for easier comparison with the zero value for the +// Rusage type. +func (r Rusage) Subtract(baseline Rusage) Rusage { + return Rusage{ + Elapsed: r.Date.Sub(baseline.Date), + Utime: r.Utime - baseline.Utime, + Stime: r.Stime - baseline.Stime, + Inblock: r.Inblock - baseline.Inblock, + Outblock: r.Outblock - baseline.Outblock, + } +} + +// Get returns the counters for the current process and its children, +// subtracting any values in the passed in "since" value, or an error. +// The Elapsed field will always be set to zero. +func Get() (Rusage, error) { + counters, err := get() + if err != nil { + return Rusage{}, err + } + return counters, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/rusage/rusage_unix.go b/vendor/github.com/containers/buildah/pkg/rusage/rusage_unix.go new file mode 100644 index 00000000..415c9493 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/rusage/rusage_unix.go @@ -0,0 +1,34 @@ +//go:build !windows + +package rusage + +import ( + "fmt" + "syscall" + "time" +) + +func mkduration(tv syscall.Timeval) time.Duration { + return time.Duration(tv.Sec)*time.Second + time.Duration(tv.Usec)*time.Microsecond +} + +func get() (Rusage, error) { + var rusage syscall.Rusage + err := syscall.Getrusage(syscall.RUSAGE_CHILDREN, &rusage) + if err != nil { + return Rusage{}, fmt.Errorf("getting resource usage: %w", err) + } + r := Rusage{ + Date: time.Now(), + Utime: mkduration(rusage.Utime), + Stime: mkduration(rusage.Stime), + Inblock: int64(rusage.Inblock), //nolint:unconvert + Outblock: int64(rusage.Oublock), //nolint:unconvert + } + return r, nil +} + +// Supported returns true if resource usage counters are supported on this OS. +func Supported() bool { + return true +} diff --git a/vendor/github.com/containers/buildah/pkg/rusage/rusage_unsupported.go b/vendor/github.com/containers/buildah/pkg/rusage/rusage_unsupported.go new file mode 100644 index 00000000..47676dd7 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/rusage/rusage_unsupported.go @@ -0,0 +1,17 @@ +//go:build windows + +package rusage + +import ( + "fmt" + "syscall" +) + +func get() (Rusage, error) { + return Rusage{}, fmt.Errorf("getting resource usage: %w", syscall.ENOTSUP) +} + +// Supported returns true if resource usage counters are supported on this OS. +func Supported() bool { + return false +} diff --git a/vendor/github.com/containers/buildah/pkg/sshagent/sshagent.go b/vendor/github.com/containers/buildah/pkg/sshagent/sshagent.go new file mode 100644 index 00000000..36ff6562 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/sshagent/sshagent.go @@ -0,0 +1,253 @@ +package sshagent + +import ( + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "runtime" + "sync" + "time" + + "github.com/containers/buildah/internal/tmpdir" + "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" +) + +// AgentServer is an ssh agent that can be served and shutdown at a later time +type AgentServer struct { + agent agent.Agent + wg sync.WaitGroup + conn *net.Conn + listener net.Listener + shutdown chan bool + servePath string + serveDir string +} + +// NewAgentServer creates a new agent on the host +func NewAgentServer(source *Source) (*AgentServer, error) { + if source.Keys != nil { + return newAgentServerKeyring(source.Keys) + } + return newAgentServerSocket(source.Socket) +} + +// newAgentServerKeyring creates a new agent from scratch and adds keys +func newAgentServerKeyring(keys []any) (*AgentServer, error) { + a := agent.NewKeyring() + for _, k := range keys { + if err := a.Add(agent.AddedKey{PrivateKey: k}); err != nil { + return nil, fmt.Errorf("failed to create ssh agent: %w", err) + } + } + return &AgentServer{ + agent: a, + shutdown: make(chan bool, 1), + }, nil +} + +// newAgentServerSocket creates a new agent from an existing agent on the host +func newAgentServerSocket(socketPath string) (*AgentServer, error) { + conn, err := net.Dial("unix", socketPath) + if err != nil { + return nil, err + } + a := &readOnlyAgent{agent.NewClient(conn)} + + return &AgentServer{ + agent: a, + conn: &conn, + shutdown: make(chan bool, 1), + }, nil +} + +// Serve starts the SSH agent on the host and returns the path of the socket where the agent is serving +func (a *AgentServer) Serve(processLabel string) (string, error) { + // Calls to `selinux.SetSocketLabel` should be wrapped in + // runtime.LockOSThread()/runtime.UnlockOSThread() until + // the the socket is created to guarantee another goroutine + // does not migrate to the current thread before execution + // is complete. + // Ref: https://github.com/opencontainers/selinux/blob/main/go-selinux/selinux.go#L158 + runtime.LockOSThread() + err := selinux.SetSocketLabel(processLabel) + if err != nil { + return "", err + } + serveDir, err := os.MkdirTemp(tmpdir.GetTempDir(), ".buildah-ssh-sock") + if err != nil { + return "", err + } + servePath := filepath.Join(serveDir, "ssh_auth_sock") + a.serveDir = serveDir + a.servePath = servePath + listener, err := net.Listen("unix", servePath) + if err != nil { + return "", err + } + // Reset socket label. + err = selinux.SetSocketLabel("") + // Unlock the thread only if the process label could be restored + // successfully. Otherwise leave the thread locked and the Go runtime + // will terminate it once it returns to the threads pool. + runtime.UnlockOSThread() + if err != nil { + return "", err + } + a.listener = listener + + go func() { + for { + // listener.Accept blocks + c, err := listener.Accept() + if err != nil { + select { + case <-a.shutdown: + return + default: + logrus.Errorf("error accepting SSH connection: %v", err) + continue + } + } + a.wg.Add(1) + go func() { + // agent.ServeAgent will only ever return with error, + err := agent.ServeAgent(a.agent, c) + if err != io.EOF { + logrus.Errorf("error serving agent: %v", err) + } + a.wg.Done() + }() + // the only way to get agent.ServeAgent is to close the connection it's serving on + // TODO: ideally we should use some sort of forwarding mechanism for output instead of manually closing connection. + go func() { + time.Sleep(2000 * time.Millisecond) + c.Close() + }() + } + }() + return a.servePath, nil +} + +// Shutdown shuts down the agent and closes the socket +func (a *AgentServer) Shutdown() error { + if a.listener != nil { + a.shutdown <- true + a.listener.Close() + } + if a.conn != nil { + conn := *a.conn + conn.Close() + } + a.wg.Wait() + err := os.RemoveAll(a.serveDir) + if err != nil { + return err + } + a.serveDir = "" + a.servePath = "" + return nil +} + +// ServePath returns the path where the agent is serving +func (a *AgentServer) ServePath() string { + return a.servePath +} + +// readOnlyAgent and its functions originally from github.com/mopby/buildkit/session/sshforward/sshprovider/agentprovider.go + +// readOnlyAgent implemetnts the agent.Agent interface +// readOnlyAgent allows reads only to prevent keys from being added from the build to the forwarded ssh agent on the host +type readOnlyAgent struct { + agent.ExtendedAgent +} + +func (a *readOnlyAgent) Add(_ agent.AddedKey) error { + return errors.New("adding new keys not allowed by buildah") +} + +func (a *readOnlyAgent) Remove(_ ssh.PublicKey) error { + return errors.New("removing keys not allowed by buildah") +} + +func (a *readOnlyAgent) RemoveAll() error { + return errors.New("removing keys not allowed by buildah") +} + +func (a *readOnlyAgent) Lock(_ []byte) error { + return errors.New("locking agent not allowed by buildah") +} + +func (a *readOnlyAgent) Extension(_ string, _ []byte) ([]byte, error) { + return nil, errors.New("extensions not allowed by buildah") +} + +// Source is what the forwarded agent's source is +// The source of the forwarded agent can be from a socket on the host, or from individual key files +type Source struct { + Socket string + Keys []any +} + +// NewSource takes paths and checks of they are keys or sockets, and creates a source +func NewSource(paths []string) (*Source, error) { + var keys []any + var socket string + if len(paths) == 0 { + socket = os.Getenv("SSH_AUTH_SOCK") + if socket == "" { + return nil, errors.New("SSH_AUTH_SOCK not set in environment") + } + absSocket, err := filepath.Abs(socket) + if err != nil { + return nil, fmt.Errorf("evaluating SSH_AUTH_SOCK in environment: %w", err) + } + socket = absSocket + } + for _, p := range paths { + if socket != "" { + return nil, errors.New("only one socket is allowed") + } + + fi, err := os.Stat(p) + if err != nil { + return nil, err + } + if fi.Mode()&os.ModeSocket > 0 { + if len(keys) == 0 { + socket = p + } else { + return nil, errors.New("cannot mix keys and socket file") + } + continue + } + + f, err := os.Open(p) + if err != nil { + return nil, err + } + dt, err := io.ReadAll(&io.LimitedReader{R: f, N: 100 * 1024}) + if err != nil { + return nil, err + } + + k, err := ssh.ParseRawPrivateKey(dt) + if err != nil { + return nil, fmt.Errorf("cannot parse ssh key: %w", err) + } + keys = append(keys, k) + } + if socket != "" { + return &Source{ + Socket: socket, + }, nil + } + return &Source{ + Keys: keys, + }, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/util/resource_unix.go b/vendor/github.com/containers/buildah/pkg/util/resource_unix.go new file mode 100644 index 00000000..cc62f860 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/resource_unix.go @@ -0,0 +1,37 @@ +//go:build linux || freebsd || darwin + +package util + +import ( + "fmt" + "syscall" + + "github.com/docker/go-units" +) + +func ParseUlimit(ulimit string) (*units.Ulimit, error) { + ul, err := units.ParseUlimit(ulimit) + if err != nil { + return nil, fmt.Errorf("ulimit option %q requires name=SOFT:HARD, failed to be parsed: %w", ulimit, err) + } + + if ul.Hard != -1 && ul.Soft == -1 { + return ul, nil + } + + rl, err := ul.GetRlimit() + if err != nil { + return nil, err + } + var limit syscall.Rlimit + if err := syscall.Getrlimit(rl.Type, &limit); err != nil { + return nil, err + } + if ul.Soft == -1 { + ul.Soft = int64(limit.Cur) + } + if ul.Hard == -1 { + ul.Hard = int64(limit.Max) + } + return ul, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/util/resource_windows.go b/vendor/github.com/containers/buildah/pkg/util/resource_windows.go new file mode 100644 index 00000000..37170918 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/resource_windows.go @@ -0,0 +1,16 @@ +package util + +import ( + "fmt" + + "github.com/docker/go-units" +) + +func ParseUlimit(ulimit string) (*units.Ulimit, error) { + ul, err := units.ParseUlimit(ulimit) + if err != nil { + return nil, fmt.Errorf("ulimit option %q requires name=SOFT:HARD, failed to be parsed: %w", ulimit, err) + } + + return ul, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/util/uptime_darwin.go b/vendor/github.com/containers/buildah/pkg/util/uptime_darwin.go new file mode 100644 index 00000000..d185cb45 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/uptime_darwin.go @@ -0,0 +1,10 @@ +package util + +import ( + "errors" + "time" +) + +func ReadUptime() (time.Duration, error) { + return 0, errors.New("readUptime not supported on darwin") +} diff --git a/vendor/github.com/containers/buildah/pkg/util/uptime_freebsd.go b/vendor/github.com/containers/buildah/pkg/util/uptime_freebsd.go new file mode 100644 index 00000000..7112aba3 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/uptime_freebsd.go @@ -0,0 +1,25 @@ +package util + +import ( + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +// For some reason, unix.ClockGettime isn't implemented by x/sys/unix on FreeBSD +func clockGettime(clockid int32, time *unix.Timespec) (err error) { + _, _, e1 := unix.Syscall(unix.SYS_CLOCK_GETTIME, uintptr(clockid), uintptr(unsafe.Pointer(time)), 0) + if e1 != 0 { + return e1 + } + return nil +} + +func ReadUptime() (time.Duration, error) { + var uptime unix.Timespec + if err := clockGettime(unix.CLOCK_UPTIME, &uptime); err != nil { + return 0, err + } + return time.Duration(unix.TimespecToNsec(uptime)), nil +} diff --git a/vendor/github.com/containers/buildah/pkg/util/uptime_linux.go b/vendor/github.com/containers/buildah/pkg/util/uptime_linux.go new file mode 100644 index 00000000..163e5762 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/uptime_linux.go @@ -0,0 +1,28 @@ +package util + +import ( + "bytes" + "errors" + "os" + "time" +) + +func ReadUptime() (time.Duration, error) { + buf, err := os.ReadFile("/proc/uptime") + if err != nil { + return 0, err + } + f := bytes.Fields(buf) + if len(f) < 1 { + return 0, errors.New("invalid uptime") + } + + // Convert uptime in seconds to a human-readable format + up := string(f[0]) + upSeconds := up + "s" + upDuration, err := time.ParseDuration(upSeconds) + if err != nil { + return 0, err + } + return upDuration, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/util/uptime_netbsd.go b/vendor/github.com/containers/buildah/pkg/util/uptime_netbsd.go new file mode 100644 index 00000000..fc981ac0 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/uptime_netbsd.go @@ -0,0 +1,25 @@ +package util + +import ( + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +func clockGettime(clockid int32, time *unix.Timespec) (err error) { + _, _, e1 := unix.Syscall(unix.SYS_CLOCK_GETTIME, uintptr(clockid), uintptr(unsafe.Pointer(time)), 0) + if e1 != 0 { + return e1 + } + return nil +} + +func ReadUptime() (time.Duration, error) { + tv, err := unix.SysctlTimeval("kern.boottime") + if err != nil { + return 0, err + } + sec, nsec := tv.Unix() + return time.Now().Sub(time.Unix(sec, nsec)), nil +} diff --git a/vendor/github.com/containers/buildah/pkg/util/uptime_windows.go b/vendor/github.com/containers/buildah/pkg/util/uptime_windows.go new file mode 100644 index 00000000..ef3adac2 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/uptime_windows.go @@ -0,0 +1,10 @@ +package util + +import ( + "errors" + "time" +) + +func ReadUptime() (time.Duration, error) { + return 0, errors.New("readUptime not supported on windows") +} diff --git a/vendor/github.com/containers/buildah/pkg/util/util.go b/vendor/github.com/containers/buildah/pkg/util/util.go new file mode 100644 index 00000000..17ad3605 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/util.go @@ -0,0 +1,82 @@ +package util + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/buildah/pkg/parse" +) + +// Mirrors path to a tmpfile if path points to a +// file descriptor instead of actual file on filesystem +// reason: operations with file descriptors are can lead +// to edge cases where content on FD is not in a consumable +// state after first consumption. +// returns path as string and bool to confirm if temp file +// was created and needs to be cleaned up. +func MirrorToTempFileIfPathIsDescriptor(file string) (string, bool) { + // one use-case is discussed here + // https://github.com/containers/buildah/issues/3070 + if !strings.HasPrefix(file, "/dev/fd/") { + return file, false + } + b, err := os.ReadFile(file) + if err != nil { + // if anything goes wrong return original path + return file, false + } + tmpfile, err := os.CreateTemp(parse.GetTempDir(), "buildah-temp-file") + if err != nil { + return file, false + } + defer tmpfile.Close() + if _, err := tmpfile.Write(b); err != nil { + // if anything goes wrong return original path + return file, false + } + + return tmpfile.Name(), true +} + +// DiscoverContainerfile tries to find a Containerfile or a Dockerfile within the provided `path`. +func DiscoverContainerfile(path string) (foundCtrFile string, err error) { + // Test for existence of the file + target, err := os.Stat(path) + if err != nil { + return "", fmt.Errorf("discovering Containerfile: %w", err) + } + + switch mode := target.Mode(); { + case mode.IsDir(): + // If the path is a real directory, we assume a Containerfile or a Dockerfile within it + ctrfile := filepath.Join(path, "Containerfile") + + // Test for existence of the Containerfile file + file, err := os.Stat(ctrfile) + if err != nil { + // See if we have a Dockerfile within it + ctrfile = filepath.Join(path, "Dockerfile") + + // Test for existence of the Dockerfile file + file, err = os.Stat(ctrfile) + if err != nil { + return "", fmt.Errorf("cannot find Containerfile or Dockerfile in context directory: %w", err) + } + } + + // The file exists, now verify the correct mode + if mode := file.Mode(); mode.IsRegular() { + foundCtrFile = ctrfile + } else { + return "", fmt.Errorf("assumed Containerfile %q is not a file", ctrfile) + } + + case mode.IsRegular(): + // If the context dir is a file, we assume this as Containerfile + foundCtrFile = path + } + + return foundCtrFile, nil +} diff --git a/vendor/github.com/containers/buildah/pkg/util/version_unix.go b/vendor/github.com/containers/buildah/pkg/util/version_unix.go new file mode 100644 index 00000000..c8b7002a --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/version_unix.go @@ -0,0 +1,18 @@ +//go:build !windows + +package util + +import ( + "bytes" + + "golang.org/x/sys/unix" +) + +func ReadKernelVersion() (string, error) { + var uname unix.Utsname + if err := unix.Uname(&uname); err != nil { + return "", err + } + n := bytes.IndexByte(uname.Release[:], 0) + return string(uname.Release[:n]), nil +} diff --git a/vendor/github.com/containers/buildah/pkg/util/version_windows.go b/vendor/github.com/containers/buildah/pkg/util/version_windows.go new file mode 100644 index 00000000..35a1e348 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/util/version_windows.go @@ -0,0 +1,9 @@ +package util + +import ( + "errors" +) + +func ReadKernelVersion() (string, error) { + return "", errors.New("readKernelVersion not supported on windows") +} diff --git a/vendor/github.com/containers/buildah/pull.go b/vendor/github.com/containers/buildah/pull.go new file mode 100644 index 00000000..8fd71579 --- /dev/null +++ b/vendor/github.com/containers/buildah/pull.go @@ -0,0 +1,113 @@ +package buildah + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/containers/buildah/define" + encconfig "github.com/containers/ocicrypt/config" + "go.podman.io/common/libimage" + "go.podman.io/common/pkg/config" + "go.podman.io/image/v5/types" + "go.podman.io/storage" +) + +// PullOptions can be used to alter how an image is copied in from somewhere. +type PullOptions struct { + // SignaturePolicyPath specifies an override location for the signature + // policy which should be used for verifying the new image as it is + // being written. Except in specific circumstances, no value should be + // specified, indicating that the shared, system-wide default policy + // should be used. + SignaturePolicyPath string + // ReportWriter is an io.Writer which will be used to log the writing + // of the new image. + ReportWriter io.Writer + // Store is the local storage store which holds the source image. + Store storage.Store + // github.com/containers/image/types SystemContext to hold credentials + // and other authentication/authorization information. + SystemContext *types.SystemContext + // BlobDirectory is the name of a directory in which we'll attempt to + // store copies of layer blobs that we pull down, if any. It should + // already exist. + // + // Not applicable if DestinationLookupReferenceFunc is set. + BlobDirectory string + // AllTags is a boolean value that determines if all tagged images + // will be downloaded from the repository. The default is false. + AllTags bool + // RemoveSignatures causes any existing signatures for the image to be + // discarded when pulling it. + RemoveSignatures bool + // MaxRetries is the maximum number of attempts we'll make to pull any + // one image from the external registry if the first attempt fails. + MaxRetries int + // RetryDelay is how long to wait before retrying a pull attempt. + RetryDelay time.Duration + // OciDecryptConfig contains the config that can be used to decrypt an image if it is + // encrypted if non-nil. If nil, it does not attempt to decrypt an image. + OciDecryptConfig *encconfig.DecryptConfig + // PullPolicy takes the value PullIfMissing, PullAlways, PullIfNewer, or PullNever. + PullPolicy define.PullPolicy + // SourceLookupReference provides a function to look up source + // references. + SourceLookupReferenceFunc libimage.LookupReferenceFunc + // DestinationLookupReference provides a function to look up destination + // references. Overrides BlobDirectory, if set. + DestinationLookupReferenceFunc libimage.LookupReferenceFunc +} + +// Pull copies the contents of the image from somewhere else to local storage. Returns the +// ID of the local image or an error. +func Pull(_ context.Context, imageName string, options PullOptions) (imageID string, err error) { + libimageOptions := &libimage.PullOptions{} + libimageOptions.SignaturePolicyPath = options.SignaturePolicyPath + libimageOptions.Writer = options.ReportWriter + libimageOptions.RemoveSignatures = options.RemoveSignatures + libimageOptions.OciDecryptConfig = options.OciDecryptConfig + libimageOptions.AllTags = options.AllTags + libimageOptions.RetryDelay = &options.RetryDelay + libimageOptions.SourceLookupReferenceFunc = options.SourceLookupReferenceFunc + if options.DestinationLookupReferenceFunc != nil { + libimageOptions.DestinationLookupReferenceFunc = options.DestinationLookupReferenceFunc + } else { + libimageOptions.DestinationLookupReferenceFunc = cacheLookupReferenceFunc(options.BlobDirectory, types.PreserveOriginal) + } + + if options.MaxRetries > 0 { + retries := uint(options.MaxRetries) + libimageOptions.MaxRetries = &retries + } + + pullPolicy, err := config.ParsePullPolicy(options.PullPolicy.String()) + if err != nil { + return "", err + } + + // Note: It is important to do this before we pull any images/create containers. + // The default backend detection logic needs an empty store to correctly detect + // that we can use netavark, if the store was not empty it will use CNI to not break existing installs. + _, err = getNetworkInterface(options.Store, "", "") + if err != nil { + return "", err + } + + runtime, err := libimage.RuntimeFromStore(options.Store, &libimage.RuntimeOptions{SystemContext: options.SystemContext}) + if err != nil { + return "", err + } + + pulledImages, err := runtime.Pull(context.Background(), imageName, pullPolicy, libimageOptions) + if err != nil { + return "", err + } + + if len(pulledImages) == 0 { + return "", fmt.Errorf("internal error pulling %s: no image pulled and no error", imageName) + } + + return pulledImages[0].ID(), nil +} diff --git a/vendor/github.com/containers/buildah/push.go b/vendor/github.com/containers/buildah/push.go new file mode 100644 index 00000000..8d69ce8c --- /dev/null +++ b/vendor/github.com/containers/buildah/push.go @@ -0,0 +1,168 @@ +package buildah + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/containers/buildah/pkg/blobcache" + encconfig "github.com/containers/ocicrypt/config" + digest "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/manifest" + "go.podman.io/image/v5/pkg/compression" + "go.podman.io/image/v5/transports" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/archive" +) + +// cacheLookupReferenceFunc wraps a BlobCache into a +// libimage.LookupReferenceFunc to allow for using a BlobCache during +// image-copy operations. +func cacheLookupReferenceFunc(directory string, compress types.LayerCompression) libimage.LookupReferenceFunc { + // Using a closure here allows us to reference a BlobCache without + // having to explicitly maintain it in the libimage API. + return func(ref types.ImageReference) (types.ImageReference, error) { + if directory == "" { + return ref, nil + } + ref, err := blobcache.NewBlobCache(ref, directory, compress) + if err != nil { + return nil, fmt.Errorf("using blobcache %q: %w", directory, err) + } + return ref, nil + } +} + +// PushOptions can be used to alter how an image is copied somewhere. +type PushOptions struct { + // Compression specifies the type of compression which is applied to + // layer blobs. The default is to not use compression, but + // archive.Gzip is recommended. + // OBSOLETE: Use CompressionFormat instead. + Compression archive.Compression + // SignaturePolicyPath specifies an override location for the signature + // policy which should be used for verifying the new image as it is + // being written. Except in specific circumstances, no value should be + // specified, indicating that the shared, system-wide default policy + // should be used. + SignaturePolicyPath string + // ReportWriter is an io.Writer which will be used to log the writing + // of the new image. + ReportWriter io.Writer + // Store is the local storage store which holds the source image. + Store storage.Store + // github.com/containers/image/types SystemContext to hold credentials + // and other authentication/authorization information. + SystemContext *types.SystemContext + // ManifestType is the format to use + // possible options are oci, v2s1, and v2s2 + ManifestType string + // BlobDirectory is the name of a directory in which we'll look for + // prebuilt copies of layer blobs that we might otherwise need to + // regenerate from on-disk layers, substituting them in the list of + // blobs to copy whenever possible. + // + // Not applicable if SourceLookupReferenceFunc is set. + BlobDirectory string + // Quiet is a boolean value that determines if minimal output to + // the user will be displayed, this is best used for logging. + // The default is false. + Quiet bool + // SignBy is the fingerprint of a GPG key to use for signing the image. + SignBy string + // RemoveSignatures causes any existing signatures for the image to be + // discarded for the pushed copy. + RemoveSignatures bool + // MaxRetries is the maximum number of attempts we'll make to push any + // one image to the external registry if the first attempt fails. + MaxRetries int + // RetryDelay is how long to wait before retrying a push attempt. + RetryDelay time.Duration + // OciEncryptConfig when non-nil indicates that an image should be encrypted. + // The encryption options is derived from the construction of EncryptConfig object. + OciEncryptConfig *encconfig.EncryptConfig + // OciEncryptLayers represents the list of layers to encrypt. + // If nil, don't encrypt any layers. + // If non-nil and len==0, denotes encrypt all layers. + // integers in the slice represent 0-indexed layer indices, with support for negative + // indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer. + OciEncryptLayers *[]int + // SourceLookupReference provides a function to look up source + // references. Overrides BlobDirectory, if set. + SourceLookupReferenceFunc libimage.LookupReferenceFunc + // DestinationLookupReference provides a function to look up destination + // references. + DestinationLookupReferenceFunc libimage.LookupReferenceFunc + + // CompressionFormat is the format to use for the compression of the blobs + CompressionFormat *compression.Algorithm + // CompressionLevel specifies what compression level is used + CompressionLevel *int + // ForceCompressionFormat ensures that the compression algorithm set in + // CompressionFormat is used exclusively, and blobs of other compression + // algorithms are not reused. + ForceCompressionFormat bool +} + +// Push copies the contents of the image to a new location. +func Push(ctx context.Context, image string, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error) { + libimageOptions := &libimage.PushOptions{} + libimageOptions.SignaturePolicyPath = options.SignaturePolicyPath + libimageOptions.Writer = options.ReportWriter + libimageOptions.ManifestMIMEType = options.ManifestType + libimageOptions.SignBy = options.SignBy + libimageOptions.RemoveSignatures = options.RemoveSignatures + libimageOptions.RetryDelay = &options.RetryDelay + libimageOptions.OciEncryptConfig = options.OciEncryptConfig + libimageOptions.OciEncryptLayers = options.OciEncryptLayers + libimageOptions.CompressionFormat = options.CompressionFormat + libimageOptions.CompressionLevel = options.CompressionLevel + libimageOptions.ForceCompressionFormat = options.ForceCompressionFormat + libimageOptions.PolicyAllowStorage = true + + if options.Quiet { + libimageOptions.Writer = nil + } + + compress := types.PreserveOriginal + if options.Compression == archive.Gzip { + compress = types.Compress + } + if options.SourceLookupReferenceFunc != nil { + libimageOptions.SourceLookupReferenceFunc = options.SourceLookupReferenceFunc + } else { + libimageOptions.SourceLookupReferenceFunc = cacheLookupReferenceFunc(options.BlobDirectory, compress) + } + libimageOptions.DestinationLookupReferenceFunc = options.DestinationLookupReferenceFunc + + runtime, err := libimage.RuntimeFromStore(options.Store, &libimage.RuntimeOptions{SystemContext: options.SystemContext}) + if err != nil { + return nil, "", err + } + + destString := fmt.Sprintf("%s:%s", dest.Transport().Name(), dest.StringWithinTransport()) + manifestBytes, err := runtime.Push(ctx, image, destString, libimageOptions) + if err != nil { + return nil, "", err + } + + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return nil, "", fmt.Errorf("computing digest of manifest of new image %q: %w", transports.ImageName(dest), err) + } + + var ref reference.Canonical + if name := dest.DockerReference(); name != nil { + ref, err = reference.WithDigest(name, manifestDigest) + if err != nil { + logrus.Warnf("error generating canonical reference with name %q and digest %s: %v", name, manifestDigest.String(), err) + } + } + + return ref, manifestDigest, nil +} diff --git a/vendor/github.com/containers/buildah/release.sh b/vendor/github.com/containers/buildah/release.sh new file mode 100644 index 00000000..508ae90d --- /dev/null +++ b/vendor/github.com/containers/buildah/release.sh @@ -0,0 +1,81 @@ +#!/bin/sh +# +# Cut a buildah release. Usage: +# +# $ hack/release.sh +# +# For example: +# +# $ hack/release.sh 1.2.3 1.3.0 +# +# for "I'm cutting 1.2.3, and want to use 1.3.0-dev for future work". + +VERSION="$1" +NEXT_VERSION="$2" +DATE=$(date '+%Y-%m-%d') +LAST_TAG=$(git describe --tags --abbrev=0) + +write_go_version() +{ + LOCAL_VERSION="$1" + sed -i "s/^\(.*Version = \"\).*/\1${LOCAL_VERSION}\"/" define/types.go +} + +write_makefile_epoch() +{ + LOCAL_EPOCH="$1" + sed -i "s/^\(EPOCH_TEST_COMMIT ?= \).*/\1${LOCAL_EPOCH}/" Makefile +} + +write_changelog() +{ + echo "- Changelog for v${VERSION} (${DATE})" >.changelog.txt && + git log --no-merges --format=' * %s' "${LAST_TAG}..HEAD" >>.changelog.txt && + echo >>.changelog.txt && + cat changelog.txt >>.changelog.txt && + mv -f .changelog.txt changelog.txt + + echo " +## v${VERSION} (${DATE}) +" >.CHANGELOG.md && + git log --no-merges --format=' %s' "${LAST_TAG}..HEAD" >>.CHANGELOG.md && + sed -i -e '/# Changelog/r .CHANGELOG.md' CHANGELOG.md && + rm -f .CHANGELOG.md +} + +release_commit() +{ + write_go_version "${VERSION}" && + write_changelog && + git commit -asm "Bump to v${VERSION} + +[NO TESTS NEEDED] +" +} + +dev_version_commit() +{ + write_go_version "${NEXT_VERSION}-dev" && + git commit -asm "Bump to v${NEXT_VERSION}-dev + +[NO TESTS NEEDED] +" +} + +epoch_commit() +{ + LOCAL_EPOCH="$1" + write_makefile_epoch "${LOCAL_EPOCH}" && + git commit -asm 'Bump gitvalidation epoch + + [NO TESTS NEEDED] +' +} + +git fetch origin && +git checkout -b "bump-${VERSION}" origin/main && +EPOCH=$(git rev-parse HEAD) && +release_commit && +git tag -s -m "version ${VERSION}" "v${VERSION}" && +dev_version_commit && +epoch_commit "${EPOCH}" diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go new file mode 100644 index 00000000..a5e3a00a --- /dev/null +++ b/vendor/github.com/containers/buildah/run.go @@ -0,0 +1,242 @@ +package buildah + +import ( + "fmt" + "io" + "net" + + "github.com/containers/buildah/copier" + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal" + "github.com/containers/buildah/pkg/sshagent" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/etchosts" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/lockfile" +) + +const ( + // runUsingRuntimeCommand is a command we use as a key for reexec + runUsingRuntimeCommand = define.Package + "-oci-runtime" +) + +// compatLayerExclusions is the set of items to omit from layers if +// options.CompatLayerOmissions is set to true. For whatever reason, the +// classic builder didn't bake these into images, but BuildKit does. +var compatLayerExclusions = []copier.ConditionalRemovePath{ + {Path: "dev", Owner: &idtools.IDPair{UID: 0, GID: 0}}, + {Path: "proc", Owner: &idtools.IDPair{UID: 0, GID: 0}}, + {Path: "sys", Owner: &idtools.IDPair{UID: 0, GID: 0}}, +} + +// TerminalPolicy takes the value DefaultTerminal, WithoutTerminal, or WithTerminal. +type TerminalPolicy int + +const ( + // DefaultTerminal indicates that this Run invocation should be + // connected to a pseudoterminal if we're connected to a terminal. + DefaultTerminal TerminalPolicy = iota + // WithoutTerminal indicates that this Run invocation should NOT be + // connected to a pseudoterminal. + WithoutTerminal + // WithTerminal indicates that this Run invocation should be connected + // to a pseudoterminal. + WithTerminal +) + +// String converts a TerminalPolicy into a string. +func (t TerminalPolicy) String() string { + switch t { + case DefaultTerminal: + return "DefaultTerminal" + case WithoutTerminal: + return "WithoutTerminal" + case WithTerminal: + return "WithTerminal" + } + return fmt.Sprintf("unrecognized terminal setting %d", t) +} + +// NamespaceOption controls how we set up a namespace when launching processes. +type NamespaceOption = define.NamespaceOption + +// NamespaceOptions provides some helper methods for a slice of NamespaceOption +// structs. +type NamespaceOptions = define.NamespaceOptions + +// IDMappingOptions controls how we set up UID/GID mapping when we set up a +// user namespace. +type IDMappingOptions = define.IDMappingOptions + +// Isolation provides a way to specify whether we're supposed to use a proper +// OCI runtime, or some other method for running commands. +type Isolation = define.Isolation + +const ( + // IsolationDefault is whatever we think will work best. + IsolationDefault = define.IsolationDefault + // IsolationOCI is a proper OCI runtime. + IsolationOCI = define.IsolationOCI + // IsolationChroot is a more chroot-like environment: less isolation, + // but with fewer requirements. + IsolationChroot = define.IsolationChroot + // IsolationOCIRootless is a proper OCI runtime in rootless mode. + IsolationOCIRootless = define.IsolationOCIRootless +) + +// RunOptions can be used to alter how a command is run in the container. +type RunOptions struct { + // Logger is the logrus logger to write log messages with + Logger *logrus.Logger `json:"-"` + // Hostname is the hostname we set for the running container. + Hostname string + // Isolation is either IsolationDefault, IsolationOCI, IsolationChroot, or IsolationOCIRootless. + Isolation define.Isolation + // Runtime is the name of the runtime to run. It should accept the + // same arguments that runc does, and produce similar output. + Runtime string + // Args adds global arguments for the runtime. + Args []string + // NoHostname won't create new /etc/hostname file + NoHostname bool + // NoHosts won't create new /etc/hosts file + NoHosts bool + // NoPivot adds the --no-pivot runtime flag. + NoPivot bool + // Mounts are additional mount points which we want to provide. + Mounts []specs.Mount + // Env is additional environment variables to set. + Env []string + // User is the user as whom to run the command. + User string + // WorkingDir is an override for the working directory. + WorkingDir string + // ContextDir is used as the root directory for the source location for mounts that are of type "bind". + ContextDir string + // Shell is default shell to run in a container. + Shell string + // Cmd is an override for the configured default command. + Cmd []string + // Entrypoint is an override for the configured entry point. + Entrypoint []string + // NamespaceOptions controls how we set up the namespaces for the process. + NamespaceOptions define.NamespaceOptions + // ConfigureNetwork controls whether or not network interfaces and + // routing are configured for a new network namespace (i.e., when not + // joining another's namespace and not just using the host's + // namespace), effectively deciding whether or not the process has a + // usable network. + ConfigureNetwork define.NetworkConfigurationPolicy + // CNIPluginPath is the location of CNI plugin helpers, if they should be + // run from a location other than the default location. + CNIPluginPath string + // CNIConfigDir is the location of CNI configuration files, if the files in + // the default configuration directory shouldn't be used. + CNIConfigDir string + // Terminal provides a way to specify whether or not the command should + // be run with a pseudoterminal. By default (DefaultTerminal), a + // terminal is used if os.Stdout is connected to a terminal, but that + // decision can be overridden by specifying either WithTerminal or + // WithoutTerminal. + Terminal TerminalPolicy + // TerminalSize provides a way to set the number of rows and columns in + // a pseudo-terminal, if we create one, and Stdin/Stdout/Stderr aren't + // connected to a terminal. + TerminalSize *specs.Box + // The stdin/stdout/stderr descriptors to use. If set to nil, the + // corresponding files in the "os" package are used as defaults. + Stdin io.Reader `json:"-"` + Stdout io.Writer `json:"-"` + Stderr io.Writer `json:"-"` + // Quiet tells the run to turn off output to stdout. + Quiet bool + // AddCapabilities is a list of capabilities to add to the default set. + AddCapabilities []string + // DropCapabilities is a list of capabilities to remove from the default set, + // after processing the AddCapabilities set. If a capability appears in both + // lists, it will be dropped. + DropCapabilities []string + // Devices are parsed additional devices to add + Devices define.ContainerDevices + // DeviceSpecs are unparsed additional devices to add + DeviceSpecs []string + // Secrets are the available secrets to use + Secrets map[string]define.Secret + // SSHSources is the available ssh agents to use + SSHSources map[string]*sshagent.Source `json:"-"` + // RunMounts are unparsed mounts to be added for this run + RunMounts []string + // Map of stages and container mountpoint if any from stage executor + StageMountPoints map[string]internal.StageMountDetails + // IDs of mounted images to be unmounted before returning + // Deprecated: before 1.39, these images would not be consistently + // unmounted if Run() returned an error + ExternalImageMounts []string + // System context of current build + SystemContext *types.SystemContext + // CgroupManager to use for running OCI containers + CgroupManager string + // CDIConfigDir is the location of CDI configuration files, if the files in + // the default configuration locations shouldn't be used. + CDIConfigDir string + // CompatBuiltinVolumes causes the contents of locations marked as + // volumes in the container's configuration to be set up as bind mounts to + // directories which are not in the container's rootfs, hiding changes + // made to contents of those changes when the container is subsequently + // committed. + CompatBuiltinVolumes types.OptionalBool +} + +// RunMountArtifacts are the artifacts created when using a run mount. +type runMountArtifacts struct { + // RunOverlayDirs are overlay directories which will need to be cleaned up using overlay.RemoveTemp() + RunOverlayDirs []string + // Any images which were mounted, which should be unmounted + MountedImages []string + // Agents are the ssh agents started, which should have their Shutdown() methods called + Agents []*sshagent.AgentServer + // SSHAuthSock is the path to the ssh auth sock inside the container + SSHAuthSock string + // Lock files, which should have their Unlock() methods called + TargetLocks []*lockfile.LockFile + // Intermediate mount points, which should be Unmount()ed and Removed()d + IntermediateMounts []string +} + +// RunMountInfo are the available run mounts for this run +type runMountInfo struct { + // WorkDir is the current working directory inside the container. + WorkDir string + // ContextDir is the root directory for the source location for bind mounts. + ContextDir string + // Secrets are the available secrets to use in a RUN + Secrets map[string]define.Secret + // SSHSources is the available ssh agents to use in a RUN + SSHSources map[string]*sshagent.Source `json:"-"` + // Map of stages and container mountpoint if any from stage executor + StageMountPoints map[string]internal.StageMountDetails + // System context of current build + SystemContext *types.SystemContext +} + +// IDMaps are the UIDs, GID, and maps for the run +type IDMaps struct { + uidmap []specs.LinuxIDMapping + gidmap []specs.LinuxIDMapping + rootUID int + rootGID int + processUID int + processGID int +} + +// netResult type to hold network info for hosts/resolv.conf +type netResult struct { + entries etchosts.HostEntries + dnsServers []string + excludeIPs []net.IP + ipv6 bool + keepHostResolvers bool + preferredHostContainersInternalIP string +} diff --git a/vendor/github.com/containers/buildah/run_common.go b/vendor/github.com/containers/buildah/run_common.go new file mode 100644 index 00000000..bd501f3b --- /dev/null +++ b/vendor/github.com/containers/buildah/run_common.go @@ -0,0 +1,2204 @@ +//go:build linux || freebsd + +package buildah + +import ( + "archive/tar" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "net" + "os" + "os/exec" + "os/signal" + "path" + "path/filepath" + "runtime" + "slices" + "strconv" + "strings" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/containers/buildah/bind" + "github.com/containers/buildah/copier" + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal" + "github.com/containers/buildah/internal/tmpdir" + "github.com/containers/buildah/internal/volumes" + "github.com/containers/buildah/pkg/overlay" + "github.com/containers/buildah/pkg/sshagent" + "github.com/containers/buildah/util" + "github.com/opencontainers/go-digest" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/etchosts" + "go.podman.io/common/libnetwork/network" + "go.podman.io/common/libnetwork/resolvconf" + netTypes "go.podman.io/common/libnetwork/types" + netUtil "go.podman.io/common/libnetwork/util" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/subscriptions" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/ioutils" + "go.podman.io/storage/pkg/lockfile" + "go.podman.io/storage/pkg/mount" + "go.podman.io/storage/pkg/reexec" + "go.podman.io/storage/pkg/regexp" + "go.podman.io/storage/pkg/unshare" + "golang.org/x/sys/unix" + "golang.org/x/term" +) + +const maxHostnameLen = 64 + +var validHostnames = regexp.Delayed("[A-Za-z0-9][A-Za-z0-9.-]+") + +func (b *Builder) createResolvConf(rdir string, chownOpts *idtools.IDPair) (string, error) { + cfile := filepath.Join(rdir, "resolv.conf") + f, err := os.Create(cfile) + if err != nil { + return "", err + } + defer f.Close() + + uid := 0 + gid := 0 + if chownOpts != nil { + uid = chownOpts.UID + gid = chownOpts.GID + } + if err = f.Chown(uid, gid); err != nil { + return "", err + } + + if err := relabel(cfile, b.MountLabel, false); err != nil { + return "", err + } + return cfile, nil +} + +// addResolvConf copies files from host and sets them up to bind mount into container +func (b *Builder) addResolvConfEntries(file string, networkNameServer []string, + spec *specs.Spec, keepHostServers, ipv6 bool, +) error { + defaultConfig, err := config.Default() + if err != nil { + return fmt.Errorf("failed to get config: %w", err) + } + + var namespaces []specs.LinuxNamespace + if spec.Linux != nil { + namespaces = spec.Linux.Namespaces + } + + dnsServers, dnsSearch, dnsOptions := b.CommonBuildOpts.DNSServers, b.CommonBuildOpts.DNSSearch, b.CommonBuildOpts.DNSOptions + nameservers := make([]string, 0, len(defaultConfig.Containers.DNSServers.Get())+len(dnsServers)) + nameservers = append(nameservers, defaultConfig.Containers.DNSServers.Get()...) + nameservers = append(nameservers, dnsServers...) + + searches := make([]string, 0, len(defaultConfig.Containers.DNSSearches.Get())+len(dnsSearch)) + searches = append(searches, defaultConfig.Containers.DNSSearches.Get()...) + searches = append(searches, dnsSearch...) + + options := make([]string, 0, len(defaultConfig.Containers.DNSOptions.Get())+len(dnsOptions)) + options = append(options, defaultConfig.Containers.DNSOptions.Get()...) + options = append(options, dnsOptions...) + + if len(nameservers) == 0 { + nameservers = networkNameServer + } + + if err := resolvconf.New(&resolvconf.Params{ + Path: file, + Namespaces: namespaces, + IPv6Enabled: ipv6, + KeepHostServers: keepHostServers, + Nameservers: nameservers, + Searches: searches, + Options: options, + }); err != nil { + return fmt.Errorf("building resolv.conf for container %s: %w", b.ContainerID, err) + } + + return nil +} + +// createHostsFile creates a containers hosts file +func (b *Builder) createHostsFile(rdir string, chownOpts *idtools.IDPair) (string, error) { + targetfile := filepath.Join(rdir, "hosts") + f, err := os.Create(targetfile) + if err != nil { + return "", err + } + defer f.Close() + uid := 0 + gid := 0 + if chownOpts != nil { + uid = chownOpts.UID + gid = chownOpts.GID + } + if err := f.Chown(uid, gid); err != nil { + return "", err + } + if err := relabel(targetfile, b.MountLabel, false); err != nil { + return "", err + } + + return targetfile, nil +} + +func (b *Builder) addHostsEntries(file, imageRoot string, entries etchosts.HostEntries, exclude []net.IP, preferIP string) error { + conf, err := config.Default() + if err != nil { + return err + } + + base, err := etchosts.GetBaseHostFile(conf.Containers.BaseHostsFile, imageRoot) + if err != nil { + return err + } + return etchosts.New(&etchosts.Params{ + BaseFile: base, + ExtraHosts: b.CommonBuildOpts.AddHost, + HostContainersInternalIP: etchosts.GetHostContainersInternalIP(etchosts.HostContainersInternalOptions{ + Conf: conf, + Exclude: exclude, + PreferIP: preferIP, + }), + TargetFile: file, + ContainerIPs: entries, + }) +} + +// generateHostname creates a containers /etc/hostname file +func (b *Builder) generateHostname(rdir, hostname string, chownOpts *idtools.IDPair) (string, error) { + cfile := filepath.Join(rdir, "hostname") + if err := ioutils.AtomicWriteFile(cfile, append([]byte(hostname), '\n'), 0o644); err != nil { + return "", fmt.Errorf("writing /etc/hostname into the container: %w", err) + } + + uid := 0 + gid := 0 + if chownOpts != nil { + uid = chownOpts.UID + gid = chownOpts.GID + } + if err := os.Chown(cfile, uid, gid); err != nil { + return "", err + } + if err := relabel(cfile, b.MountLabel, false); err != nil { + return "", err + } + + return cfile, nil +} + +func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy, terminalSize *specs.Box) { + switch terminalPolicy { + case DefaultTerminal: + onTerminal := term.IsTerminal(unix.Stdin) && term.IsTerminal(unix.Stdout) && term.IsTerminal(unix.Stderr) + if onTerminal { + logrus.Debugf("stdio is a terminal, defaulting to using a terminal") + } else { + logrus.Debugf("stdio is not a terminal, defaulting to not using a terminal") + } + g.SetProcessTerminal(onTerminal) + case WithTerminal: + g.SetProcessTerminal(true) + case WithoutTerminal: + g.SetProcessTerminal(false) + } + if terminalSize != nil { + g.SetProcessConsoleSize(terminalSize.Width, terminalSize.Height) + } +} + +// Search for a command that isn't given as an absolute path using the $PATH +// under the rootfs. We can't resolve absolute symbolic links without +// chroot()ing, which we may not be able to do, so just accept a link as a +// valid resolution. +func runLookupPath(g *generate.Generator, command []string) []string { + // Look for the configured $PATH. + spec := g.Config + envPath := "" + for i := range spec.Process.Env { + if strings.HasPrefix(spec.Process.Env[i], "PATH=") { + envPath = spec.Process.Env[i] + } + } + // If there is no configured $PATH, supply one. + if envPath == "" { + defaultPath := "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin" + envPath = "PATH=" + defaultPath + g.AddProcessEnv("PATH", defaultPath) + } + // No command, nothing to do. + if len(command) == 0 { + return command + } + // Command is already an absolute path, use it as-is. + if filepath.IsAbs(command[0]) { + return command + } + // For each element in the PATH, + for _, pathEntry := range filepath.SplitList(envPath[5:]) { + // if it's the empty string, it's ".", which is the Cwd, + if pathEntry == "" { + pathEntry = spec.Process.Cwd + } + // build the absolute path which it might be, + candidate := filepath.Join(pathEntry, command[0]) + // check if it's there, + if fi, err := os.Lstat(filepath.Join(spec.Root.Path, candidate)); fi != nil && err == nil { + // and if it's not a directory, and either a symlink or executable, + if !fi.IsDir() && ((fi.Mode()&os.ModeSymlink != 0) || (fi.Mode()&0o111 != 0)) { + // use that. + return append([]string{candidate}, command[1:]...) + } + } + } + return command +} + +func (b *Builder) configureUIDGID(g *generate.Generator, mountPoint string, options RunOptions) (string, error) { + // Set the user UID/GID/supplemental group list/capabilities lists. + user, homeDir, err := b.userForRun(mountPoint, options.User) + if err != nil { + return "", err + } + if err := setupCapabilities(g, b.Capabilities, options.AddCapabilities, options.DropCapabilities); err != nil { + return "", err + } + g.SetProcessUID(user.UID) + g.SetProcessGID(user.GID) + g.AddProcessAdditionalGid(user.GID) + for _, gid := range user.AdditionalGids { + g.AddProcessAdditionalGid(gid) + } + for _, group := range b.GroupAdd { + if group == "keep-groups" { + if len(b.GroupAdd) > 1 { + return "", errors.New("the '--group-add keep-groups' option is not allowed with any other --group-add options") + } + g.AddAnnotation("run.oci.keep_original_groups", "1") + continue + } + gid, err := strconv.ParseUint(group, 10, 32) + if err != nil { + return "", err + } + g.AddProcessAdditionalGid(uint32(gid)) + } + + // Remove capabilities if not running as root except Bounding set + if user.UID != 0 && g.Config.Process.Capabilities != nil { + bounding := g.Config.Process.Capabilities.Bounding + g.ClearProcessCapabilities() + g.Config.Process.Capabilities.Bounding = bounding + } + + return homeDir, nil +} + +func (b *Builder) configureEnvironment(g *generate.Generator, options RunOptions, defaultEnv []string) { + g.ClearProcessEnv() + + if b.CommonBuildOpts.HTTPProxy { + for _, envSpec := range config.ProxyEnv { + if envVal, ok := os.LookupEnv(envSpec); ok { + g.AddProcessEnv(envSpec, envVal) + } + } + } + + for _, envSpec := range util.MergeEnv(util.MergeEnv(defaultEnv, b.Env()), options.Env) { + env := strings.SplitN(envSpec, "=", 2) + if len(env) > 1 { + g.AddProcessEnv(env[0], env[1]) + } + } +} + +// getNetworkInterface creates the network interface +func getNetworkInterface(store storage.Store, cniConfDir, cniPluginPath string) (netTypes.ContainerNetwork, error) { + conf, err := config.Default() + if err != nil { + return nil, err + } + // copy the config to not modify the default by accident + newconf := *conf + if len(cniConfDir) > 0 { + newconf.Network.NetworkConfigDir = cniConfDir + } + if len(cniPluginPath) > 0 { + plugins := strings.Split(cniPluginPath, string(os.PathListSeparator)) + newconf.Network.CNIPluginDirs.Set(plugins) + } + + _, netInt, err := network.NetworkBackend(store, &newconf, false) + if err != nil { + return nil, err + } + return netInt, nil +} + +func netStatusToNetResult(netStatus map[string]netTypes.StatusBlock, hostnames []string) *netResult { + result := &netResult{ + keepHostResolvers: false, + } + for _, status := range netStatus { + for _, dns := range status.DNSServerIPs { + result.dnsServers = append(result.dnsServers, dns.String()) + } + for _, netInt := range status.Interfaces { + for _, netAddress := range netInt.Subnets { + e := etchosts.HostEntry{IP: netAddress.IPNet.IP.String(), Names: hostnames} + result.entries = append(result.entries, e) + if !result.ipv6 && netUtil.IsIPv6(netAddress.IPNet.IP) { + result.ipv6 = true + } + } + } + } + return result +} + +// DefaultNamespaceOptions returns the default namespace settings from the +// runtime-tools generator library. +func DefaultNamespaceOptions() (define.NamespaceOptions, error) { + cfg, err := config.Default() + if err != nil { + return nil, fmt.Errorf("failed to get container config: %w", err) + } + options := define.NamespaceOptions{ + {Name: string(specs.CgroupNamespace), Host: cfg.CgroupNS() == "host"}, + {Name: string(specs.IPCNamespace), Host: cfg.IPCNS() == "host"}, + {Name: string(specs.MountNamespace), Host: false}, + {Name: string(specs.NetworkNamespace), Host: cfg.NetNS() == "host"}, + {Name: string(specs.PIDNamespace), Host: cfg.PidNS() == "host"}, + {Name: string(specs.UserNamespace), Host: cfg.Containers.UserNS == "" || cfg.Containers.UserNS == "host"}, + {Name: string(specs.UTSNamespace), Host: cfg.UTSNS() == "host"}, + } + return options, nil +} + +func checkAndOverrideIsolationOptions(isolation define.Isolation, options *RunOptions) error { + switch isolation { + case IsolationOCIRootless: + // only change the netns if the caller did not set it + if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns == nil { + if _, err := exec.LookPath("slirp4netns"); err != nil { + // if slirp4netns is not installed we have to use the hosts net namespace + options.NamespaceOptions.AddOrReplace(define.NamespaceOption{Name: string(specs.NetworkNamespace), Host: true}) + } + } + fallthrough + case IsolationOCI: + pidns := options.NamespaceOptions.Find(string(specs.PIDNamespace)) + userns := options.NamespaceOptions.Find(string(specs.UserNamespace)) + if (pidns != nil && pidns.Host) && (userns != nil && !userns.Host) { + return fmt.Errorf("not allowed to mix host PID namespace with container user namespace") + } + case IsolationChroot: + logrus.Info("network namespace isolation not supported with chroot isolation, forcing host network") + options.NamespaceOptions.AddOrReplace(define.NamespaceOption{Name: string(specs.NetworkNamespace), Host: true}) + } + return nil +} + +// fileCloser is a helper struct to prevent closing the file twice in the code +// users must call (fileCloser).Close() and not fileCloser.File.Close() +type fileCloser struct { + file *os.File + closed bool +} + +func (f *fileCloser) Close() { + if !f.closed { + if err := f.file.Close(); err != nil { + logrus.Errorf("failed to close file: %v", err) + } + f.closed = true + } +} + +// waitForSync waits for a maximum of 4 minutes to read something from the file +func waitForSync(pipeR *os.File) error { + if err := pipeR.SetDeadline(time.Now().Add(4 * time.Minute)); err != nil { + return err + } + b := make([]byte, 16) + _, err := pipeR.Read(b) + return err +} + +func runUsingRuntime(options RunOptions, configureNetwork bool, moreCreateArgs []string, spec *specs.Spec, bundlePath, containerName string, + containerCreateW io.WriteCloser, containerStartR io.ReadCloser, +) (wstatus unix.WaitStatus, err error) { + if options.Logger == nil { + options.Logger = logrus.StandardLogger() + } + + // Lock the caller to a single OS-level thread. + runtime.LockOSThread() + defer reapStrays() + + // Set up bind mounts for things that a namespaced user might not be able to get to directly. + unmountAll, err := bind.SetupIntermediateMountNamespace(spec, bundlePath) + if unmountAll != nil { + defer func() { + if err := unmountAll(); err != nil { + options.Logger.Error(err) + } + }() + } + if err != nil { + return 1, err + } + + // Write the runtime configuration. + specbytes, err := json.Marshal(spec) + if err != nil { + return 1, fmt.Errorf("encoding configuration %#v as json: %w", spec, err) + } + if err = ioutils.AtomicWriteFile(filepath.Join(bundlePath, "config.json"), specbytes, 0o600); err != nil { + return 1, fmt.Errorf("storing runtime configuration: %w", err) + } + + logrus.Debugf("config = %v", string(specbytes)) + + // Decide which runtime to use. + runtime := options.Runtime + if runtime == "" { + runtime = util.Runtime() + } + localRuntime := util.FindLocalRuntime(runtime) + if localRuntime != "" { + runtime = localRuntime + } + + // Default to just passing down our stdio. + getCreateStdio := func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) { + return os.Stdin, os.Stdout, os.Stderr + } + + // Figure out how we're doing stdio handling, and create pipes and sockets. + var stdio sync.WaitGroup + var consoleListener *net.UnixListener + var errorFds, closeBeforeReadingErrorFds []int + stdioPipe := make([][]int, 3) + copyConsole := false + copyPipes := false + finishCopy := make([]int, 2) + if err = unix.Pipe(finishCopy); err != nil { + return 1, fmt.Errorf("creating pipe for notifying to stop stdio: %w", err) + } + finishedCopy := make(chan struct{}, 1) + var pargs []string + if spec.Process != nil { + pargs = spec.Process.Args + if spec.Process.Terminal { + copyConsole = true + // Create a listening socket for accepting the container's terminal's PTY master. + socketPath := filepath.Join(bundlePath, "console.sock") + consoleListener, err = net.ListenUnix("unix", &net.UnixAddr{Name: socketPath, Net: "unix"}) + if err != nil { + return 1, fmt.Errorf("creating socket %q to receive terminal descriptor: %w", consoleListener.Addr(), err) + } + // Add console socket arguments. + moreCreateArgs = append(moreCreateArgs, "--console-socket", socketPath) + } else { + copyPipes = true + // Figure out who should own the pipes. + uid, gid, err := util.GetHostRootIDs(spec) + if err != nil { + return 1, err + } + // Create stdio pipes. + if stdioPipe, err = runMakeStdioPipe(int(uid), int(gid)); err != nil { + return 1, err + } + if spec.Linux != nil { + if err = runLabelStdioPipes(stdioPipe, spec.Process.SelinuxLabel, spec.Linux.MountLabel); err != nil { + return 1, err + } + } + errorFds = []int{stdioPipe[unix.Stdout][0], stdioPipe[unix.Stderr][0]} + closeBeforeReadingErrorFds = []int{stdioPipe[unix.Stdout][1], stdioPipe[unix.Stderr][1]} + // Set stdio to our pipes. + getCreateStdio = func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) { + stdin := os.NewFile(uintptr(stdioPipe[unix.Stdin][0]), "/dev/stdin") + stdout := os.NewFile(uintptr(stdioPipe[unix.Stdout][1]), "/dev/stdout") + stderr := os.NewFile(uintptr(stdioPipe[unix.Stderr][1]), "/dev/stderr") + return stdin, stdout, stderr + } + } + } else { + if options.Quiet { + // Discard stdout. + getCreateStdio = func() (io.ReadCloser, io.WriteCloser, io.WriteCloser) { + return os.Stdin, nil, os.Stderr + } + } + } + + runtimeArgs := slices.Clone(options.Args) + if options.CgroupManager == config.SystemdCgroupsManager { + runtimeArgs = append(runtimeArgs, "--systemd-cgroup") + } + + // Build the commands that we'll execute. + pidFile := filepath.Join(bundlePath, "pid") + args := append(append(append(runtimeArgs, "create", "--bundle", bundlePath, "--pid-file", pidFile), moreCreateArgs...), containerName) + create := exec.Command(runtime, args...) + setPdeathsig(create) + create.Dir = bundlePath + stdin, stdout, stderr := getCreateStdio() + create.Stdin, create.Stdout, create.Stderr = stdin, stdout, stderr + + args = append(options.Args, "start", containerName) + start := exec.Command(runtime, args...) + setPdeathsig(start) + start.Dir = bundlePath + start.Stderr = os.Stderr + + kill := func(signal string) *exec.Cmd { + args := append(options.Args, "kill", containerName) + if signal != "" { + args = append(args, signal) + } + kill := exec.Command(runtime, args...) + kill.Dir = bundlePath + kill.Stderr = os.Stderr + return kill + } + + args = append(options.Args, "delete", containerName) + del := exec.Command(runtime, args...) + del.Dir = bundlePath + del.Stderr = os.Stderr + + // Actually create the container. + logrus.Debugf("Running %q", create.Args) + err = create.Run() + if err != nil { + return 1, fmt.Errorf("from %s creating container for %v: %s: %w", runtime, pargs, runCollectOutput(options.Logger, errorFds, closeBeforeReadingErrorFds), err) + } + defer func() { + err2 := del.Run() + if err2 != nil { + if err == nil { + err = fmt.Errorf("deleting container: %w", err2) + } else { + options.Logger.Infof("error from %s deleting container: %v", runtime, err2) + } + } + }() + + // Make sure we read the container's exit status when it exits. + pidValue, err := os.ReadFile(pidFile) + if err != nil { + return 1, err + } + pid, err := strconv.Atoi(strings.TrimSpace(string(pidValue))) + if err != nil { + return 1, fmt.Errorf("parsing pid %s as a number: %w", string(pidValue), err) + } + var stopped uint32 + var reaping sync.WaitGroup + reaping.Add(1) + go func() { + defer reaping.Done() + var err error + _, err = unix.Wait4(pid, &wstatus, 0, nil) + if err != nil { + wstatus = 0 + options.Logger.Errorf("error waiting for container child process %d: %v\n", pid, err) + } + atomic.StoreUint32(&stopped, 1) + }() + + if configureNetwork { + if _, err := containerCreateW.Write([]byte{1}); err != nil { + return 1, err + } + containerCreateW.Close() + logrus.Debug("waiting for parent start message") + b := make([]byte, 1) + if _, err := containerStartR.Read(b); err != nil { + return 1, fmt.Errorf("did not get container start message from parent: %w", err) + } + containerStartR.Close() + } + + if copyPipes { + // We don't need the ends of the pipes that belong to the container. + stdin.Close() + if stdout != nil { + stdout.Close() + } + stderr.Close() + } + + // Handle stdio for the container in the background. + stdio.Add(1) + go runCopyStdio(options.Logger, &stdio, copyPipes, stdioPipe, copyConsole, consoleListener, finishCopy, finishedCopy, spec) + + // Start the container. + logrus.Debugf("Running %q", start.Args) + err = start.Run() + if err != nil { + return 1, fmt.Errorf("from %s starting container: %w", runtime, err) + } + defer func() { + if atomic.LoadUint32(&stopped) == 0 { + if err := kill("").Run(); err != nil { + options.Logger.Infof("error from %s stopping container: %v", runtime, err) + } + atomic.StoreUint32(&stopped, 1) + } + }() + + // Wait for the container to exit. + interrupted := make(chan os.Signal, 100) + go func() { + for range interrupted { + if err := kill("SIGKILL").Run(); err != nil { + logrus.Errorf("%v sending SIGKILL", err) + } + } + }() + signal.Notify(interrupted, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + for { + now := time.Now() + var state specs.State + args = append(options.Args, "state", containerName) + stat := exec.Command(runtime, args...) + stat.Dir = bundlePath + stat.Stderr = os.Stderr + stateOutput, err := stat.Output() + if err != nil { + if atomic.LoadUint32(&stopped) != 0 { + // container exited + break + } + return 1, fmt.Errorf("reading container state from %s (got output: %q): %w", runtime, string(stateOutput), err) + } + if err = json.Unmarshal(stateOutput, &state); err != nil { + return 1, fmt.Errorf("parsing container state %q from %s: %w", string(stateOutput), runtime, err) + } + switch state.Status { + case specs.StateCreating, specs.StateCreated, specs.StateRunning: + // all fine + case specs.StateStopped: + atomic.StoreUint32(&stopped, 1) + default: + return 1, fmt.Errorf("container status unexpectedly changed to %q", state.Status) + } + if atomic.LoadUint32(&stopped) != 0 { + break + } + select { + case <-finishedCopy: + atomic.StoreUint32(&stopped, 1) + case <-time.After(time.Until(now.Add(100 * time.Millisecond))): + continue + } + if atomic.LoadUint32(&stopped) != 0 { + break + } + } + signal.Stop(interrupted) + close(interrupted) + + // Close the writing end of the stop-handling-stdio notification pipe. + unix.Close(finishCopy[1]) + // Wait for the stdio copy goroutine to flush. + stdio.Wait() + // Wait until we finish reading the exit status. + reaping.Wait() + + return wstatus, nil +} + +func runCollectOutput(logger *logrus.Logger, fds, closeBeforeReadingFds []int) string { + for _, fd := range closeBeforeReadingFds { + unix.Close(fd) + } + var b bytes.Buffer + buf := make([]byte, 8192) + for _, fd := range fds { + nread, err := unix.Read(fd, buf) + if err != nil { + if errno, isErrno := err.(syscall.Errno); isErrno { + switch errno { + default: + logger.Errorf("error reading from pipe %d: %v", fd, err) + case syscall.EINTR, syscall.EAGAIN: + } + } else { + logger.Errorf("unable to wait for data from pipe %d: %v", fd, err) + } + continue + } + for nread > 0 { + r := buf[:nread] + if nwritten, err := b.Write(r); err != nil || nwritten != len(r) { + if nwritten != len(r) { + logger.Errorf("error buffering data from pipe %d: %v", fd, err) + break + } + } + nread, err = unix.Read(fd, buf) + if err != nil { + if errno, isErrno := err.(syscall.Errno); isErrno { + switch errno { + default: + logger.Errorf("error reading from pipe %d: %v", fd, err) + case syscall.EINTR, syscall.EAGAIN: + } + } else { + logger.Errorf("unable to wait for data from pipe %d: %v", fd, err) + } + break + } + } + } + return b.String() +} + +func setNonblock(logger *logrus.Logger, fd int, description string, nonblocking bool) (bool, error) { + mask, err := unix.FcntlInt(uintptr(fd), unix.F_GETFL, 0) + if err != nil { + return false, err + } + blocked := mask&unix.O_NONBLOCK == 0 + + if err := unix.SetNonblock(fd, nonblocking); err != nil { + if nonblocking { + logger.Errorf("error setting %s to nonblocking: %v", description, err) + } else { + logger.Errorf("error setting descriptor %s blocking: %v", description, err) + } + } + return blocked, err +} + +func runCopyStdio(logger *logrus.Logger, stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copyConsole bool, consoleListener *net.UnixListener, finishCopy []int, finishedCopy chan struct{}, spec *specs.Spec) { + defer func() { + unix.Close(finishCopy[0]) + if copyPipes { + unix.Close(stdioPipe[unix.Stdin][1]) + unix.Close(stdioPipe[unix.Stdout][0]) + unix.Close(stdioPipe[unix.Stderr][0]) + } + stdio.Done() + finishedCopy <- struct{}{} + close(finishedCopy) + }() + // Map describing where data on an incoming descriptor should go. + relayMap := make(map[int]int) + // Map describing incoming and outgoing descriptors. + readDesc := make(map[int]string) + writeDesc := make(map[int]string) + // Buffers. + relayBuffer := make(map[int]*bytes.Buffer) + // Set up the terminal descriptor or pipes for polling. + if copyConsole { + // Accept a connection over our listening socket. + fd, err := runAcceptTerminal(logger, consoleListener, spec.Process.ConsoleSize) + if err != nil { + logger.Errorf("%v", err) + return + } + terminalFD := fd + // Input from our stdin, output from the terminal descriptor. + relayMap[unix.Stdin] = terminalFD + readDesc[unix.Stdin] = "stdin" + relayBuffer[terminalFD] = new(bytes.Buffer) + writeDesc[terminalFD] = "container terminal input" + relayMap[terminalFD] = unix.Stdout + readDesc[terminalFD] = "container terminal output" + relayBuffer[unix.Stdout] = new(bytes.Buffer) + writeDesc[unix.Stdout] = "output" + // Set our terminal's mode to raw, to pass handling of special + // terminal input to the terminal in the container. + if term.IsTerminal(unix.Stdin) { + if state, err := term.MakeRaw(unix.Stdin); err != nil { + logger.Warnf("error setting terminal state: %v", err) + } else { + defer func() { + if err = term.Restore(unix.Stdin, state); err != nil { + logger.Errorf("unable to restore terminal state: %v", err) + } + }() + } + } + } + if copyPipes { + // Input from our stdin, output from the stdout and stderr pipes. + relayMap[unix.Stdin] = stdioPipe[unix.Stdin][1] + readDesc[unix.Stdin] = "stdin" + relayBuffer[stdioPipe[unix.Stdin][1]] = new(bytes.Buffer) + writeDesc[stdioPipe[unix.Stdin][1]] = "container stdin" + relayMap[stdioPipe[unix.Stdout][0]] = unix.Stdout + readDesc[stdioPipe[unix.Stdout][0]] = "container stdout" + relayBuffer[unix.Stdout] = new(bytes.Buffer) + writeDesc[unix.Stdout] = "stdout" + relayMap[stdioPipe[unix.Stderr][0]] = unix.Stderr + readDesc[stdioPipe[unix.Stderr][0]] = "container stderr" + relayBuffer[unix.Stderr] = new(bytes.Buffer) + writeDesc[unix.Stderr] = "stderr" + } + // Set our reading descriptors to non-blocking. + for rfd, wfd := range relayMap { + blocked, err := setNonblock(logger, rfd, readDesc[rfd], true) + if err != nil { + return + } + if blocked { + defer setNonblock(logger, rfd, readDesc[rfd], false) //nolint:errcheck + } + setNonblock(logger, wfd, writeDesc[wfd], false) //nolint:errcheck + } + + if copyPipes { + setNonblock(logger, stdioPipe[unix.Stdin][1], writeDesc[stdioPipe[unix.Stdin][1]], true) //nolint:errcheck + } + + runCopyStdioPassData(copyPipes, stdioPipe, finishCopy, relayMap, relayBuffer, readDesc, writeDesc) +} + +func canRetry(err error) bool { + if errno, isErrno := err.(syscall.Errno); isErrno { + return errno == syscall.EINTR || errno == syscall.EAGAIN + } + return false +} + +func runCopyStdioPassData(copyPipes bool, stdioPipe [][]int, finishCopy []int, relayMap map[int]int, relayBuffer map[int]*bytes.Buffer, readDesc map[int]string, writeDesc map[int]string) { + closeStdin := false + + // Pass data back and forth. + pollTimeout := -1 + for len(relayMap) > 0 { + // Start building the list of descriptors to poll. + pollFds := make([]unix.PollFd, 0, len(relayMap)+1) + // Poll for a notification that we should stop handling stdio. + pollFds = append(pollFds, unix.PollFd{Fd: int32(finishCopy[0]), Events: unix.POLLIN | unix.POLLHUP}) + // Poll on our reading descriptors. + for rfd := range relayMap { + pollFds = append(pollFds, unix.PollFd{Fd: int32(rfd), Events: unix.POLLIN | unix.POLLHUP}) + } + buf := make([]byte, 8192) + // Wait for new data from any input descriptor, or a notification that we're done. + _, err := unix.Poll(pollFds, pollTimeout) + if !util.LogIfNotRetryable(err, fmt.Sprintf("error waiting for stdio/terminal data to relay: %v", err)) { + return + } + removes := make(map[int]struct{}) + for _, pollFd := range pollFds { + // If this descriptor's just been closed from the other end, mark it for + // removal from the set that we're checking for. + if pollFd.Revents&unix.POLLHUP == unix.POLLHUP { + removes[int(pollFd.Fd)] = struct{}{} + } + // If the descriptor was closed elsewhere, remove it from our list. + if pollFd.Revents&unix.POLLNVAL != 0 { + logrus.Debugf("error polling descriptor %s: closed?", readDesc[int(pollFd.Fd)]) + removes[int(pollFd.Fd)] = struct{}{} + } + // If the POLLIN flag isn't set, then there's no data to be read from this descriptor. + if pollFd.Revents&unix.POLLIN == 0 { + continue + } + // Read whatever there is to be read. + readFD := int(pollFd.Fd) + writeFD, needToRelay := relayMap[readFD] + if needToRelay { + n, err := unix.Read(readFD, buf) + if !util.LogIfNotRetryable(err, fmt.Sprintf("unable to read %s data: %v", readDesc[readFD], err)) { + return + } + // If it's zero-length on our stdin and we're + // using pipes, it's an EOF, so close the stdin + // pipe's writing end. + if n == 0 && !canRetry(err) && int(pollFd.Fd) == unix.Stdin { + removes[int(pollFd.Fd)] = struct{}{} + } else if n > 0 { + // Buffer the data in case we get blocked on where they need to go. + nwritten, err := relayBuffer[writeFD].Write(buf[:n]) + if err != nil { + logrus.Debugf("buffer: %v", err) + continue + } + if nwritten != n { + logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", n, nwritten) + continue + } + // If this is the last of the data we'll be able to read from this + // descriptor, read all that there is to read. + for pollFd.Revents&unix.POLLHUP == unix.POLLHUP { + nr, err := unix.Read(readFD, buf) + util.LogIfUnexpectedWhileDraining(err, fmt.Sprintf("read %s: %v", readDesc[readFD], err)) + if nr <= 0 { + break + } + nwritten, err := relayBuffer[writeFD].Write(buf[:nr]) + if err != nil { + logrus.Debugf("buffer: %v", err) + break + } + if nwritten != nr { + logrus.Debugf("buffer: expected to buffer %d bytes, wrote %d", nr, nwritten) + break + } + } + } + } + } + // Try to drain the output buffers. Set the default timeout + // for the next poll() to 100ms if we still have data to write. + pollTimeout = -1 + for writeFD := range relayBuffer { + if relayBuffer[writeFD].Len() > 0 { + n, err := unix.Write(writeFD, relayBuffer[writeFD].Bytes()) + if !util.LogIfNotRetryable(err, fmt.Sprintf("unable to write %s data: %v", writeDesc[writeFD], err)) { + return + } + if n > 0 { + relayBuffer[writeFD].Next(n) + } + if closeStdin && writeFD == stdioPipe[unix.Stdin][1] && stdioPipe[unix.Stdin][1] >= 0 && relayBuffer[stdioPipe[unix.Stdin][1]].Len() == 0 { + logrus.Debugf("closing stdin") + unix.Close(stdioPipe[unix.Stdin][1]) + stdioPipe[unix.Stdin][1] = -1 + } + } + if relayBuffer[writeFD].Len() > 0 { + pollTimeout = 100 + } + } + // Remove any descriptors which we don't need to poll any more from the poll descriptor list. + for remove := range removes { + if copyPipes && remove == unix.Stdin { + closeStdin = true + if relayBuffer[stdioPipe[unix.Stdin][1]].Len() == 0 { + logrus.Debugf("closing stdin") + unix.Close(stdioPipe[unix.Stdin][1]) + stdioPipe[unix.Stdin][1] = -1 + } + } + delete(relayMap, remove) + } + // If the we-can-return pipe had anything for us, we're done. + for _, pollFd := range pollFds { + if int(pollFd.Fd) == finishCopy[0] && pollFd.Revents != 0 { + // The pipe is closed, indicating that we can stop now. + return + } + } + } +} + +func runAcceptTerminal(logger *logrus.Logger, consoleListener *net.UnixListener, terminalSize *specs.Box) (int, error) { + defer consoleListener.Close() + c, err := consoleListener.AcceptUnix() + if err != nil { + return -1, fmt.Errorf("accepting socket descriptor connection: %w", err) + } + defer c.Close() + // Expect a control message over our new connection. + b := make([]byte, 8192) + oob := make([]byte, 8192) + n, oobn, _, _, err := c.ReadMsgUnix(b, oob) + if err != nil { + return -1, fmt.Errorf("reading socket descriptor: %w", err) + } + if n > 0 { + logrus.Debugf("socket descriptor is for %q", string(b[:n])) + } + if oobn > len(oob) { + return -1, fmt.Errorf("too much out-of-bounds data (%d bytes)", oobn) + } + // Parse the control message. + scm, err := unix.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + return -1, fmt.Errorf("parsing out-of-bound data as a socket control message: %w", err) + } + logrus.Debugf("control messages: %v", scm) + // Expect to get a descriptor. + terminalFD := -1 + for i := range scm { + fds, err := unix.ParseUnixRights(&scm[i]) + if err != nil { + return -1, fmt.Errorf("parsing unix rights control message: %v: %w", &scm[i], err) + } + logrus.Debugf("fds: %v", fds) + if len(fds) == 0 { + continue + } + terminalFD = fds[0] + break + } + if terminalFD == -1 { + return -1, fmt.Errorf("unable to read terminal descriptor") + } + // Set the pseudoterminal's size to the configured size, or our own. + winsize := &unix.Winsize{} + if terminalSize != nil { + // Use configured sizes. + winsize.Row = uint16(terminalSize.Height) + winsize.Col = uint16(terminalSize.Width) + } else { + if term.IsTerminal(unix.Stdin) { + // Use the size of our terminal. + if winsize, err = unix.IoctlGetWinsize(unix.Stdin, unix.TIOCGWINSZ); err != nil { + logger.Warnf("error reading size of controlling terminal: %v", err) + winsize.Row = 0 + winsize.Col = 0 + } + } + } + if winsize.Row != 0 && winsize.Col != 0 { + if err = unix.IoctlSetWinsize(terminalFD, unix.TIOCSWINSZ, winsize); err != nil { + logger.Warnf("error setting size of container pseudoterminal: %v", err) + } + // FIXME - if we're connected to a terminal, we should + // be passing the updated terminal size down when we + // receive a SIGWINCH. + } + return terminalFD, nil +} + +func reapStrays() { + // Reap the exit status of anything that was reparented to us, not that + // we care about their exit status. + logrus.Debugf("checking for reparented child processes") + for range 100 { + wpid, err := unix.Wait4(-1, nil, unix.WNOHANG, nil) + if err != nil { + break + } + if wpid == 0 { + time.Sleep(100 * time.Millisecond) + } else { + logrus.Debugf("caught reparented child process %d", wpid) + } + } +} + +func runUsingRuntimeMain() { + var options runUsingRuntimeSubprocOptions + // Set logging. + if level := os.Getenv("LOGLEVEL"); level != "" { + if ll, err := strconv.Atoi(level); err == nil { + logrus.SetLevel(logrus.Level(ll)) + } + } + // Unpack our configuration. + confPipe := os.NewFile(3, "confpipe") + if confPipe == nil { + fmt.Fprintf(os.Stderr, "error reading options pipe\n") + os.Exit(1) + } + defer confPipe.Close() + if err := json.NewDecoder(confPipe).Decode(&options); err != nil { + fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err) + os.Exit(1) + } + // Set ourselves up to read the container's exit status. We're doing this in a child process + // so that we won't mess with the setting in a caller of the library. + if err := setChildProcess(); err != nil { + os.Exit(1) + } + ospec := options.Spec + if ospec == nil { + fmt.Fprintf(os.Stderr, "options spec not specified\n") + os.Exit(1) + } + + // open the pipes used to communicate with the parent process + var containerCreateW *os.File + var containerStartR *os.File + if options.ConfigureNetwork { + containerCreateW = os.NewFile(4, "containercreatepipe") + if containerCreateW == nil { + fmt.Fprintf(os.Stderr, "could not open fd 4\n") + os.Exit(1) + } + containerStartR = os.NewFile(5, "containerstartpipe") + if containerStartR == nil { + fmt.Fprintf(os.Stderr, "could not open fd 5\n") + os.Exit(1) + } + } + + // Run the container, start to finish. + status, err := runUsingRuntime(options.Options, options.ConfigureNetwork, options.MoreCreateArgs, ospec, options.BundlePath, options.ContainerName, containerCreateW, containerStartR) + reapStrays() + if err != nil { + fmt.Fprintf(os.Stderr, "error running container: %v\n", err) + os.Exit(1) + } + // Pass the container's exit status back to the caller by exiting with the same status. + if status.Exited() { + os.Exit(status.ExitStatus()) + } else if status.Signaled() { + fmt.Fprintf(os.Stderr, "container exited on %s\n", status.Signal()) + os.Exit(1) + } + os.Exit(1) +} + +func (b *Builder) runUsingRuntimeSubproc(isolation define.Isolation, options RunOptions, configureNetwork bool, networkString string, + moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName, buildContainerName, hostsFile, resolvFile string, +) (err error) { + // Lock the caller to a single OS-level thread. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var confwg sync.WaitGroup + config, conferr := json.Marshal(runUsingRuntimeSubprocOptions{ + Options: options, + Spec: spec, + RootPath: rootPath, + BundlePath: bundlePath, + ConfigureNetwork: configureNetwork, + MoreCreateArgs: moreCreateArgs, + ContainerName: containerName, + Isolation: isolation, + }) + if conferr != nil { + return fmt.Errorf("encoding configuration for %q: %w", runUsingRuntimeCommand, conferr) + } + cmd := reexec.Command(runUsingRuntimeCommand) + setPdeathsig(cmd) + cmd.Dir = bundlePath + cmd.Stdin = options.Stdin + if cmd.Stdin == nil { + cmd.Stdin = os.Stdin + } + cmd.Stdout = options.Stdout + if cmd.Stdout == nil { + cmd.Stdout = os.Stdout + } + cmd.Stderr = options.Stderr + if cmd.Stderr == nil { + cmd.Stderr = os.Stderr + } + cmd.Env = util.MergeEnv(os.Environ(), []string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}) + preader, pwriter, err := os.Pipe() + if err != nil { + return fmt.Errorf("creating configuration pipe: %w", err) + } + confwg.Add(1) + go func() { + _, conferr = io.Copy(pwriter, bytes.NewReader(config)) + if conferr != nil { + conferr = fmt.Errorf("while copying configuration down pipe to child process: %w", conferr) + } + confwg.Done() + }() + + // create network configuration pipes + var containerCreateR, containerCreateW fileCloser + var containerStartR, containerStartW fileCloser + if configureNetwork { + containerCreateR.file, containerCreateW.file, err = os.Pipe() + if err != nil { + return fmt.Errorf("creating container create pipe: %w", err) + } + defer containerCreateR.Close() + defer containerCreateW.Close() + + containerStartR.file, containerStartW.file, err = os.Pipe() + if err != nil { + return fmt.Errorf("creating container start pipe: %w", err) + } + defer containerStartR.Close() + defer containerStartW.Close() + cmd.ExtraFiles = []*os.File{containerCreateW.file, containerStartR.file} + } + + cmd.ExtraFiles = append([]*os.File{preader}, cmd.ExtraFiles...) + defer preader.Close() + defer pwriter.Close() + if err := cmd.Start(); err != nil { + return fmt.Errorf("while starting runtime: %w", err) + } + + interrupted := make(chan os.Signal, 100) + go func() { + for receivedSignal := range interrupted { + if err := cmd.Process.Signal(receivedSignal); err != nil { + logrus.Infof("%v while attempting to forward %v to child process", err, receivedSignal) + } + } + }() + signal.Notify(interrupted, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + + if configureNetwork { + // we already passed the fd to the child, now close the writer so we do not hang if the child closes it + containerCreateW.Close() + if err := waitForSync(containerCreateR.file); err != nil { + // we do not want to return here since we want to capture the exit code from the child via cmd.Wait() + // close the pipes here so that the child will not hang forever + containerCreateR.Close() + containerStartW.Close() + logrus.Errorf("did not get container create message from subprocess: %v", err) + } else { + pidFile := filepath.Join(bundlePath, "pid") + pidValue, err := os.ReadFile(pidFile) + if err != nil { + return err + } + pid, err := strconv.Atoi(strings.TrimSpace(string(pidValue))) + if err != nil { + return fmt.Errorf("parsing pid %s as a number: %w", string(pidValue), err) + } + + teardown, netResult, err := b.runConfigureNetwork(pid, isolation, options, networkString, containerName, []string{spec.Hostname, buildContainerName}) + if teardown != nil { + defer teardown() + } + if err != nil { + return fmt.Errorf("setup network: %w", err) + } + + // only add hosts if we manage the hosts file + if hostsFile != "" { + err = b.addHostsEntries(hostsFile, rootPath, netResult.entries, netResult.excludeIPs, netResult.preferredHostContainersInternalIP) + if err != nil { + return err + } + } + + if resolvFile != "" { + err = b.addResolvConfEntries(resolvFile, netResult.dnsServers, spec, netResult.keepHostResolvers, netResult.ipv6) + if err != nil { + return err + } + } + + logrus.Debug("network namespace successfully setup, send start message to child") + _, err = containerStartW.file.Write([]byte{1}) + if err != nil { + return err + } + } + } + + if err := cmd.Wait(); err != nil { + return fmt.Errorf("while running runtime: %w", err) + } + confwg.Wait() + signal.Stop(interrupted) + close(interrupted) + if err == nil { + return conferr + } + if conferr != nil { + logrus.Debugf("%v", conferr) + } + return err +} + +type runUsingRuntimeSubprocOptions struct { + Options RunOptions + Spec *specs.Spec + RootPath string + BundlePath string + ConfigureNetwork bool + MoreCreateArgs []string + ContainerName string + Isolation define.Isolation +} + +func init() { + reexec.Register(runUsingRuntimeCommand, runUsingRuntimeMain) +} + +// If this succeeds, after the command which uses the spec finishes running, +// the caller must call b.cleanupRunMounts() on the returned runMountArtifacts +// structure. +func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath string, optionMounts []specs.Mount, bindFiles map[string]string, builtinVolumes []string, compatBuiltinVolumes types.OptionalBool, volumeMounts []string, runFileMounts []string, runMountInfo runMountInfo) (*runMountArtifacts, error) { + // Start building a new list of mounts. + var mounts []specs.Mount + haveMount := func(destination string) bool { + for _, mount := range mounts { + if mount.Destination == destination { + // Already have something to mount there. + return true + } + } + return false + } + + specMounts, err := setupSpecialMountSpecChanges(spec, b.CommonBuildOpts.ShmSize) + if err != nil { + return nil, err + } + + // Get the list of files we need to bind into the container. + bindFileMounts := runSetupBoundFiles(bundlePath, bindFiles) + + // After this point we need to know the per-container persistent storage directory. + cdir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return nil, fmt.Errorf("determining work directory for container %q: %w", b.ContainerID, err) + } + + // Figure out which UID and GID to tell the subscriptions package to use + // for files that it creates. + rootUID, rootGID, err := util.GetHostRootIDs(spec) + if err != nil { + return nil, err + } + + // Get host UID and GID of the container process. + uidMap := []specs.LinuxIDMapping{} + gidMap := []specs.LinuxIDMapping{} + if spec.Linux != nil { + uidMap = spec.Linux.UIDMappings + gidMap = spec.Linux.GIDMappings + } + processUID, processGID, err := util.GetHostIDs(uidMap, gidMap, spec.Process.User.UID, spec.Process.User.GID) + if err != nil { + return nil, err + } + + // Get the list of subscriptions mounts. + subscriptionMounts := subscriptions.MountsWithUIDGID(b.MountLabel, cdir, b.DefaultMountsFilePath, mountPoint, int(rootUID), int(rootGID), unshare.IsRootless(), false) + + idMaps := IDMaps{ + uidmap: uidMap, + gidmap: gidMap, + rootUID: int(rootUID), + rootGID: int(rootGID), + processUID: int(processUID), + processGID: int(processGID), + } + // Get the list of mounts that are just for this Run() call. + runMounts, mountArtifacts, err := b.runSetupRunMounts(bundlePath, runFileMounts, runMountInfo, idMaps) + if err != nil { + return nil, err + } + succeeded := false + defer func() { + if !succeeded { + if err := b.cleanupRunMounts(mountArtifacts); err != nil { + b.Logger.Debugf("cleaning up run mounts: %v", err) + } + } + }() + // Add temporary copies of the contents of volume locations at the + // volume locations, unless we already have something there. + builtins, err := runSetupBuiltinVolumes(b.MountLabel, mountPoint, cdir, builtinVolumes, compatBuiltinVolumes, int(rootUID), int(rootGID)) + if err != nil { + return nil, err + } + + // Get the list of explicitly-specified volume mounts. + mountLabel := "" + if spec.Linux != nil { + mountLabel = spec.Linux.MountLabel + } + volumes, overlayDirs, err := b.runSetupVolumeMounts(mountLabel, volumeMounts, optionMounts, idMaps) + if err != nil { + return nil, err + } + mountArtifacts.RunOverlayDirs = append(mountArtifacts.RunOverlayDirs, overlayDirs...) + + allMounts := util.SortMounts(append(append(append(append(append(volumes, builtins...), runMounts...), subscriptionMounts...), bindFileMounts...), specMounts...)) + + // Add them all, in the preferred order, except where they conflict with something that was previously added. + for _, mount := range allMounts { + if haveMount(mount.Destination) { + // Already mounting something there, no need to bother with this one. + continue + } + // Add the mount. + mounts = append(mounts, mount) + } + + // Set the list in the spec. + spec.Mounts = mounts + succeeded = true + return mountArtifacts, nil +} + +func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, builtinVolumes []string, compatBuiltinVolumes types.OptionalBool, rootUID, rootGID int) ([]specs.Mount, error) { + var mounts []specs.Mount + hostOwner := idtools.IDPair{UID: rootUID, GID: rootGID} + // Add temporary copies of the contents of volume locations at the + // volume locations, unless we already have something there. + for _, volume := range builtinVolumes { + // Make sure the volume exists in the rootfs. + createDirPerms := os.FileMode(0o755) + err := copier.Mkdir(mountPoint, filepath.Join(mountPoint, volume), copier.MkdirOptions{ + ChownNew: &hostOwner, + ChmodNew: &createDirPerms, + }) + if err != nil { + return nil, fmt.Errorf("ensuring volume path %q: %w", filepath.Join(mountPoint, volume), err) + } + // If we're not being asked to bind mount anonymous volumes + // onto the volume paths, we're done here. + if compatBuiltinVolumes != types.OptionalBoolTrue { + continue + } + // If we need to, create the directory that we'll use to hold + // the volume contents. If we do need to create it, then we'll + // need to populate it, too, so make a note of that. + volumePath := filepath.Join(containerDir, "buildah-volumes", digest.Canonical.FromString(volume).Hex()) + initializeVolume := false + if err := fileutils.Exists(volumePath); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return nil, err + } + logrus.Debugf("setting up built-in volume path at %q for %q", volumePath, volume) + if err = os.MkdirAll(volumePath, 0o755); err != nil { + return nil, err + } + if err = relabel(volumePath, mountLabel, false); err != nil { + return nil, err + } + initializeVolume = true + } + // Read the attributes of the volume's location in the rootfs. + srcPath, err := copier.Eval(mountPoint, filepath.Join(mountPoint, volume), copier.EvalOptions{}) + if err != nil { + return nil, fmt.Errorf("evaluating path %q: %w", srcPath, err) + } + stat, err := os.Stat(srcPath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, err + } + // If we need to populate the mounted volume's contents with + // content from the rootfs, set it up now. + if initializeVolume { + if err = os.Chmod(volumePath, stat.Mode().Perm()); err != nil { + return nil, err + } + if err = os.Chown(volumePath, int(stat.Sys().(*syscall.Stat_t).Uid), int(stat.Sys().(*syscall.Stat_t).Gid)); err != nil { + return nil, err + } + logrus.Debugf("populating directory %q for volume %q using contents of %q", volumePath, volume, srcPath) + if err = extractWithTar(mountPoint, srcPath, volumePath); err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("populating directory %q for volume %q using contents of %q: %w", volumePath, volume, srcPath, err) + } + } + // Add the bind mount. + mounts = append(mounts, specs.Mount{ + Source: volumePath, + Destination: volume, + Type: define.TypeBind, + Options: define.BindOptions, + }) + } + return mounts, nil +} + +// runSetupRunMounts sets up mounts that exist only in this RUN, not in subsequent runs +// +// If this function succeeds, the caller must free the returned +// runMountArtifacts by calling b.cleanupRunMounts() after the command being +// executed with those mounts has finished. +func (b *Builder) runSetupRunMounts(bundlePath string, mounts []string, sources runMountInfo, idMaps IDMaps) ([]specs.Mount, *runMountArtifacts, error) { + tmpFiles := make([]string, 0, len(mounts)) + mountImages := make([]string, 0, len(mounts)) + intermediateMounts := make([]string, 0, len(mounts)) + finalMounts := make([]specs.Mount, 0, len(mounts)) + agents := make([]*sshagent.AgentServer, 0, len(mounts)) + defaultSSHSock := "" + targetLocks := []*lockfile.LockFile{} + var overlayDirs []string + succeeded := false + defer func() { + if !succeeded { + for _, agent := range agents { + servePath := agent.ServePath() + if err := agent.Shutdown(); err != nil { + b.Logger.Errorf("shutting down SSH agent at %q: %v", servePath, err) + } + } + for _, overlayDir := range overlayDirs { + if err := overlay.RemoveTemp(overlayDir); err != nil { + b.Logger.Error(err.Error()) + } + } + for _, intermediateMount := range intermediateMounts { + if err := mount.Unmount(intermediateMount); err != nil { + b.Logger.Errorf("unmounting %q: %v", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil { + b.Logger.Errorf("removing should-be-empty directory %q: %v", intermediateMount, err) + } + } + for _, mountImage := range mountImages { + if _, err := b.store.UnmountImage(mountImage, false); err != nil { + b.Logger.Error(err.Error()) + } + } + for _, tmpFile := range tmpFiles { + if err := os.Remove(tmpFile); err != nil && !errors.Is(err, os.ErrNotExist) { + b.Logger.Error(err.Error()) + } + } + volumes.UnlockLockArray(targetLocks) + } + }() + for _, mount := range mounts { + var mountSpec *specs.Mount + var err error + var envFile, image, bundleMountsDir, overlayDir, intermediateMount string + var agent *sshagent.AgentServer + var tl *lockfile.LockFile + + tokens := strings.Split(mount, ",") + + // If `type` is not set default to TypeBind + mountType := define.TypeBind + + for _, field := range tokens { + if strings.HasPrefix(field, "type=") { + kv := strings.Split(field, "=") + if len(kv) != 2 { + return nil, nil, errors.New("invalid mount type") + } + mountType = kv[1] + } + } + switch mountType { + case "secret": + mountSpec, envFile, err = b.getSecretMount(tokens, sources.Secrets, idMaps, sources.WorkDir) + if err != nil { + return nil, nil, err + } + if mountSpec != nil { + finalMounts = append(finalMounts, *mountSpec) + if envFile != "" { + tmpFiles = append(tmpFiles, envFile) + } + } + case "ssh": + mountSpec, agent, err = b.getSSHMount(tokens, len(agents), sources.SSHSources, idMaps) + if err != nil { + return nil, nil, err + } + if mountSpec != nil { + finalMounts = append(finalMounts, *mountSpec) + if len(agents) == 0 { + defaultSSHSock = mountSpec.Destination + } + agents = append(agents, agent) + } + case define.TypeBind: + if bundleMountsDir == "" { + if bundleMountsDir, err = os.MkdirTemp(bundlePath, "mounts"); err != nil { + return nil, nil, err + } + } + mountSpec, image, intermediateMount, overlayDir, err = b.getBindMount(tokens, sources.SystemContext, sources.ContextDir, sources.StageMountPoints, idMaps, sources.WorkDir, bundleMountsDir) + if err != nil { + return nil, nil, err + } + if image != "" { + mountImages = append(mountImages, image) + } + if intermediateMount != "" { + intermediateMounts = append(intermediateMounts, intermediateMount) + } + if overlayDir != "" { + overlayDirs = append(overlayDirs, overlayDir) + } + finalMounts = append(finalMounts, *mountSpec) + case "tmpfs": + mountSpec, err = b.getTmpfsMount(tokens, idMaps, sources.WorkDir) + if err != nil { + return nil, nil, err + } + finalMounts = append(finalMounts, *mountSpec) + case "cache": + if bundleMountsDir == "" { + if bundleMountsDir, err = os.MkdirTemp(bundlePath, "mounts"); err != nil { + return nil, nil, err + } + } + mountSpec, image, intermediateMount, overlayDir, tl, err = b.getCacheMount(tokens, sources.SystemContext, sources.StageMountPoints, idMaps, sources.WorkDir, bundleMountsDir) + if err != nil { + return nil, nil, err + } + if image != "" { + mountImages = append(mountImages, image) + } + if intermediateMount != "" { + intermediateMounts = append(intermediateMounts, intermediateMount) + } + if overlayDir != "" { + overlayDirs = append(overlayDirs, overlayDir) + } + if tl != nil { + targetLocks = append(targetLocks, tl) + } + finalMounts = append(finalMounts, *mountSpec) + default: + return nil, nil, fmt.Errorf("invalid mount type %q", mountType) + } + } + succeeded = true + artifacts := &runMountArtifacts{ + RunOverlayDirs: overlayDirs, + Agents: agents, + MountedImages: mountImages, + SSHAuthSock: defaultSSHSock, + TargetLocks: targetLocks, + IntermediateMounts: intermediateMounts, + } + return finalMounts, artifacts, nil +} + +func (b *Builder) getBindMount(tokens []string, sys *types.SystemContext, contextDir string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir, tmpDir string) (*specs.Mount, string, string, string, error) { + if contextDir == "" { + return nil, "", "", "", errors.New("context directory for current run invocation is not configured") + } + var optionMounts []specs.Mount + optionMount, image, intermediateMount, overlayMount, err := volumes.GetBindMount(sys, tokens, contextDir, b.store, b.MountLabel, stageMountPoints, workDir, tmpDir) + if err != nil { + return nil, "", "", "", err + } + succeeded := false + defer func() { + if !succeeded { + if overlayMount != "" { + if err := overlay.RemoveTemp(overlayMount); err != nil { + b.Logger.Debug(err.Error()) + } + } + if intermediateMount != "" { + if err := mount.Unmount(intermediateMount); err != nil { + b.Logger.Debugf("unmounting %q: %v", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil { + b.Logger.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err) + } + } + if image != "" { + if _, err := b.store.UnmountImage(image, false); err != nil { + b.Logger.Debugf("unmounting image %q: %v", image, err) + } + } + } + }() + optionMounts = append(optionMounts, optionMount) + volumes, overlayDirs, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps) + if err != nil { + return nil, "", "", "", err + } + if len(overlayDirs) != 0 { + return nil, "", "", "", errors.New("internal error: did not expect a resolved bind mount to use the O flag") + } + succeeded = true + return &volumes[0], image, intermediateMount, overlayMount, nil +} + +func (b *Builder) getTmpfsMount(tokens []string, idMaps IDMaps, workDir string) (*specs.Mount, error) { + var optionMounts []specs.Mount + mount, err := volumes.GetTmpfsMount(tokens, workDir) + if err != nil { + return nil, err + } + optionMounts = append(optionMounts, mount) + volumes, overlayDirs, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps) + if err != nil { + return nil, err + } + if len(overlayDirs) != 0 { + return nil, errors.New("internal error: did not expect a resolved tmpfs mount to use the O flag") + } + return &volumes[0], nil +} + +func (b *Builder) getSecretMount(tokens []string, secrets map[string]define.Secret, idMaps IDMaps, workdir string) (_ *specs.Mount, _ string, retErr error) { + errInvalidSyntax := errors.New("secret should have syntax id=id[,target=path,required=bool,mode=uint,uid=uint,gid=uint") + if len(tokens) == 0 { + return nil, "", errInvalidSyntax + } + var err error + var id, target string + var required bool + var uid, gid uint32 + var mode uint32 = 0o400 + for _, val := range tokens { + kv := strings.SplitN(val, "=", 2) + switch kv[0] { + case "type": + // This is already processed + continue + case "id": + id = kv[1] + case "target", "dst", "destination": + target = kv[1] + if !filepath.IsAbs(target) { + target = filepath.Join(workdir, target) + } + case "required": + required = true + if len(kv) > 1 { + required, err = strconv.ParseBool(kv[1]) + if err != nil { + return nil, "", errInvalidSyntax + } + } + case "mode": + mode64, err := strconv.ParseUint(kv[1], 8, 32) + if err != nil { + return nil, "", errInvalidSyntax + } + mode = uint32(mode64) + case "uid": + uid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, "", errInvalidSyntax + } + uid = uint32(uid64) + case "gid": + gid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, "", errInvalidSyntax + } + gid = uint32(gid64) + default: + return nil, "", errInvalidSyntax + } + } + + if id == "" { + return nil, "", errInvalidSyntax + } + // Default location for secrets is /run/secrets/id + if target == "" { + target = "/run/secrets/" + id + } + + secr, ok := secrets[id] + if !ok { + if required { + return nil, "", fmt.Errorf("secret required but no secret with id %q found", id) + } + return nil, "", nil + } + var data []byte + var envFile string + var ctrFileOnHost string + + switch secr.SourceType { + case "env": + data = []byte(os.Getenv(secr.Source)) + tmpFile, err := os.CreateTemp(tmpdir.GetTempDir(), "buildah*") + if err != nil { + return nil, "", err + } + defer func() { + if retErr != nil { + os.Remove(tmpFile.Name()) + } + }() + envFile = tmpFile.Name() + ctrFileOnHost = tmpFile.Name() + case "file": + containerWorkingDir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return nil, "", err + } + data, err = os.ReadFile(secr.Source) + if err != nil { + return nil, "", err + } + ctrFileOnHost = filepath.Join(containerWorkingDir, "secrets", digest.FromString(id).Encoded()[:16]) + default: + return nil, "", errors.New("invalid source secret type") + } + + // Copy secrets to container working dir (or tmp dir if it's an env), since we need to chmod, + // chown and relabel it for the container user and we don't want to mess with the original file + if err := os.MkdirAll(filepath.Dir(ctrFileOnHost), 0o755); err != nil { + return nil, "", err + } + if err := os.WriteFile(ctrFileOnHost, data, 0o644); err != nil { + return nil, "", err + } + + if err := relabel(ctrFileOnHost, b.MountLabel, false); err != nil { + return nil, "", err + } + hostUID, hostGID, err := util.GetHostIDs(idMaps.uidmap, idMaps.gidmap, uid, gid) + if err != nil { + return nil, "", err + } + if err := os.Lchown(ctrFileOnHost, int(hostUID), int(hostGID)); err != nil { + return nil, "", err + } + if err := os.Chmod(ctrFileOnHost, os.FileMode(mode)); err != nil { + return nil, "", err + } + newMount := specs.Mount{ + Destination: target, + Type: define.TypeBind, + Source: ctrFileOnHost, + Options: append(define.BindOptions, "rprivate", "ro"), + } + return &newMount, envFile, nil +} + +// getSSHMount parses the --mount type=ssh flag in the Containerfile, checks if there's an ssh source provided, and creates and starts an ssh-agent to be forwarded into the container +func (b *Builder) getSSHMount(tokens []string, count int, sshsources map[string]*sshagent.Source, idMaps IDMaps) (*specs.Mount, *sshagent.AgentServer, error) { + errInvalidSyntax := errors.New("ssh should have syntax id=id[,target=path,required=bool,mode=uint,uid=uint,gid=uint") + + var err error + var id, target string + var required bool + var uid, gid uint32 + var mode uint32 = 0o600 + for _, val := range tokens { + kv := strings.SplitN(val, "=", 2) + if len(kv) < 2 { + return nil, nil, errInvalidSyntax + } + switch kv[0] { + case "type": + // This is already processed + continue + case "id": + id = kv[1] + case "target", "dst", "destination": + target = kv[1] + case "required": + required, err = strconv.ParseBool(kv[1]) + if err != nil { + return nil, nil, errInvalidSyntax + } + case "mode": + mode64, err := strconv.ParseUint(kv[1], 8, 32) + if err != nil { + return nil, nil, errInvalidSyntax + } + mode = uint32(mode64) + case "uid": + uid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, nil, errInvalidSyntax + } + uid = uint32(uid64) + case "gid": + gid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, nil, errInvalidSyntax + } + gid = uint32(gid64) + default: + return nil, nil, errInvalidSyntax + } + } + + if id == "" { + id = "default" + } + // Default location for secrets is /run/buildkit/ssh_agent.{i} + if target == "" { + target = fmt.Sprintf("/run/buildkit/ssh_agent.%d", count) + } + + sshsource, ok := sshsources[id] + if !ok { + if required { + return nil, nil, fmt.Errorf("ssh required but no ssh with id %s found", id) + } + return nil, nil, nil + } + // Create new agent from keys or socket + fwdAgent, err := sshagent.NewAgentServer(sshsource) + if err != nil { + return nil, nil, err + } + // Start ssh server, and get the host sock we're mounting in the container + hostSock, err := fwdAgent.Serve(b.ProcessLabel) + if err != nil { + return nil, nil, err + } + + if err := relabel(filepath.Dir(hostSock), b.MountLabel, false); err != nil { + if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil { + b.Logger.Errorf("error shutting down agent: %v", shutdownErr) + } + return nil, nil, err + } + if err := relabel(hostSock, b.MountLabel, false); err != nil { + if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil { + b.Logger.Errorf("error shutting down agent: %v", shutdownErr) + } + return nil, nil, err + } + hostUID, hostGID, err := util.GetHostIDs(idMaps.uidmap, idMaps.gidmap, uid, gid) + if err != nil { + if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil { + b.Logger.Errorf("error shutting down agent: %v", shutdownErr) + } + return nil, nil, err + } + if err := os.Lchown(hostSock, int(hostUID), int(hostGID)); err != nil { + if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil { + b.Logger.Errorf("error shutting down agent: %v", shutdownErr) + } + return nil, nil, err + } + if err := os.Chmod(hostSock, os.FileMode(mode)); err != nil { + if shutdownErr := fwdAgent.Shutdown(); shutdownErr != nil { + b.Logger.Errorf("error shutting down agent: %v", shutdownErr) + } + return nil, nil, err + } + newMount := specs.Mount{ + Destination: target, + Type: define.TypeBind, + Source: hostSock, + Options: append(define.BindOptions, "rprivate", "ro"), + } + return &newMount, fwdAgent, nil +} + +// cleanupRunMounts cleans up run mounts so they only appear in this run. +func (b *Builder) cleanupRunMounts(artifacts *runMountArtifacts) error { + for _, agent := range artifacts.Agents { + servePath := agent.ServePath() + if err := agent.Shutdown(); err != nil { + return fmt.Errorf("shutting down SSH agent at %q: %v", servePath, err) + } + } + // clean up any overlays we mounted + for _, overlayDirectory := range artifacts.RunOverlayDirs { + if err := overlay.RemoveTemp(overlayDirectory); err != nil { + return err + } + } + // unmount anything that needs unmounting + for _, intermediateMount := range artifacts.IntermediateMounts { + if err := mount.Unmount(intermediateMount); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("unmounting %q: %w", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("removing should-be-empty directory %q: %w", intermediateMount, err) + } + } + // unmount any images we mounted for this run + for _, image := range artifacts.MountedImages { + if _, err := b.store.UnmountImage(image, false); err != nil { + logrus.Debugf("umounting image %q: %v", image, err) + } + } + // unlock locks we took, most likely for cache mounts + volumes.UnlockLockArray(artifacts.TargetLocks) + return nil +} + +// setPdeathsig sets a parent-death signal for the process +// the goroutine that starts the child process should lock itself to +// a native thread using runtime.LockOSThread() until the child exits +func setPdeathsig(cmd *exec.Cmd) { + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL +} + +func relabel(path, mountLabel string, shared bool) error { + if err := label.Relabel(path, mountLabel, shared); err != nil { + if !errors.Is(err, syscall.ENOTSUP) { + return err + } + logrus.Debugf("Labeling not supported on %q", path) + } + return nil +} + +// mapContainerNameToHostname returns the passed-in string with characters that +// don't match validHostnames (defined above) stripped out. +func mapContainerNameToHostname(containerName string) string { + match := validHostnames.FindStringIndex(containerName) + if match == nil { + return "" + } + trimmed := containerName[match[0]:] + match[1] -= match[0] + match[0] = 0 + for match[1] != len(trimmed) && match[1] < match[0]+maxHostnameLen { + trimmed = trimmed[:match[1]] + trimmed[match[1]+1:] + match = validHostnames.FindStringIndex(trimmed) + match[1] = min(match[1], maxHostnameLen) + } + return trimmed[:match[1]] +} + +// createMountTargets creates empty files or directories that are used as +// targets for mounts in the spec, and makes a note of what it created. +func (b *Builder) createMountTargets(spec *specs.Spec) ([]copier.ConditionalRemovePath, error) { + // Avoid anything weird happening, just in case. + if spec == nil || spec.Root == nil { + return nil, nil + } + rootfsPath := spec.Root.Path + then := time.Unix(0, 0) + exemptFromTimesPreservation := map[string]struct{}{ + "dev": {}, + "proc": {}, + "sys": {}, + } + exemptFromRemoval := map[string]struct{}{ + "dev": {}, + "proc": {}, + "sys": {}, + } + overridePermissions := map[string]os.FileMode{ + "dev": 0o755, + "proc": 0o755, + "sys": 0o755, + } + uidmap, gidmap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) + targets := copier.EnsureOptions{ + UIDMap: uidmap, + GIDMap: gidmap, + } + for _, mnt := range spec.Mounts { + typeFlag := byte(tar.TypeDir) + // If the mount is a "bind" or "rbind" mount, then it's a bind + // mount, which means the target _could_ be a non-directory. + // Check the source and make a note. + if mnt.Type == define.TypeBind || slices.Contains(mnt.Options, "bind") || slices.Contains(mnt.Options, "rbind") { + if st, err := os.Stat(mnt.Source); err == nil { + if !st.IsDir() { + typeFlag = tar.TypeReg + } + } + } + // Walk the path components from the root all the way down to + // the target mountpoint and build a list of pathnames that we + // need to ensure exist. If we might need to remove them, give + // them a conspicuous mtime, so that we can detect if they were + // unmounted and then modified, in which case we'll want to + // preserve those changes. + destination := mnt.Destination + for destination != "" { + cleanedDestination := strings.Trim(path.Clean(filepath.ToSlash(destination)), "/") + modTime := &then + if _, ok := exemptFromTimesPreservation[cleanedDestination]; ok { + // don't force a timestamp for this path + modTime = nil + } + var mode *os.FileMode + if _, ok := exemptFromRemoval[cleanedDestination]; ok { + // we're not going to filter this out later, + // so don't make it look weird + perms := os.FileMode(0o755) + if typeFlag == tar.TypeReg { + perms = 0o644 + } + mode = &perms + modTime = nil + } + if perms, ok := overridePermissions[cleanedDestination]; ok { + // forced permissions + mode = &perms + } + if mode == nil && destination != cleanedDestination { + // parent directories default to 0o755, for + // the sake of commands running as UID != 0 + perms := os.FileMode(0o755) + mode = &perms + } + targets.Paths = append(targets.Paths, copier.EnsurePath{ + Path: destination, + Typeflag: typeFlag, + ModTime: modTime, + Chmod: mode, + }) + typeFlag = tar.TypeDir + dir, _ := filepath.Split(destination) + if destination == dir { + break + } + destination = dir + } + } + if len(targets.Paths) == 0 { + return nil, nil + } + created, noted, err := copier.Ensure(rootfsPath, rootfsPath, targets) + if err != nil { + return nil, err + } + logrus.Debugf("created mount targets at %v", created) + logrus.Debugf("parents of mount targets at %+v", noted) + var remove []copier.ConditionalRemovePath + for _, target := range created { + cleanedTarget := strings.Trim(path.Clean(filepath.ToSlash(target)), "/") + if _, ok := exemptFromRemoval[cleanedTarget]; ok { + continue + } + modTime := &then + if _, ok := exemptFromTimesPreservation[cleanedTarget]; ok { + modTime = nil + } + condition := copier.ConditionalRemovePath{ + Path: cleanedTarget, + ModTime: modTime, + Owner: &idtools.IDPair{UID: 0, GID: 0}, + } + remove = append(remove, condition) + } + if len(remove) == 0 { + return nil, nil + } + // encode the set of paths we might need to filter out at commit-time + // in a way that hopefully doesn't break long-running concurrent Run() + // calls, that lets us also not have to manage any locking for them + cdir, err := b.store.ContainerDirectory(b.Container) + if err != nil { + return nil, fmt.Errorf("finding working container bookkeeping directory: %w", err) + } + for excludesDir, exclusions := range map[string][]copier.ConditionalRemovePath{ + containerExcludesDir: remove, + containerPulledUpDir: noted, + } { + if err := os.Mkdir(filepath.Join(cdir, excludesDir), 0o700); err != nil && !errors.Is(err, os.ErrExist) { + return nil, fmt.Errorf("creating exclusions directory: %w", err) + } + encoded, err := json.Marshal(exclusions) + if err != nil { + return nil, fmt.Errorf("encoding list of items to exclude at commit-time: %w", err) + } + f, err := os.CreateTemp(filepath.Join(cdir, excludesDir), "filter*"+containerExcludesSubstring) + if err != nil { + return nil, fmt.Errorf("creating exclusions file: %w", err) + } + defer os.Remove(f.Name()) + defer f.Close() + if err := ioutils.AtomicWriteFile(strings.TrimSuffix(f.Name(), containerExcludesSubstring), encoded, 0o600); err != nil { + return nil, fmt.Errorf("writing exclusions file: %w", err) + } + } + // return the set of to-remove-now paths directly, in case the caller would prefer + // to clear them out itself now instead of waiting until commit-time + return remove, nil +} diff --git a/vendor/github.com/containers/buildah/run_freebsd.go b/vendor/github.com/containers/buildah/run_freebsd.go new file mode 100644 index 00000000..74771a26 --- /dev/null +++ b/vendor/github.com/containers/buildah/run_freebsd.go @@ -0,0 +1,674 @@ +//go:build freebsd + +package buildah + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + "unsafe" + + "github.com/containers/buildah/bind" + "github.com/containers/buildah/chroot" + "github.com/containers/buildah/copier" + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal" + "github.com/containers/buildah/internal/tmpdir" + "github.com/containers/buildah/pkg/jail" + "github.com/containers/buildah/pkg/overlay" + "github.com/containers/buildah/pkg/parse" + butil "github.com/containers/buildah/pkg/util" + "github.com/containers/buildah/util" + "github.com/docker/go-units" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/etchosts" + "go.podman.io/common/libnetwork/resolvconf" + nettypes "go.podman.io/common/libnetwork/types" + netUtil "go.podman.io/common/libnetwork/util" + "go.podman.io/common/pkg/config" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/lockfile" + "go.podman.io/storage/pkg/stringid" + "golang.org/x/sys/unix" +) + +const ( + P_PID = 0 + P_PGID = 2 + PROC_REAP_ACQUIRE = 2 + PROC_REAP_RELEASE = 3 +) + +func procctl(idtype int, id int, cmd int, arg *byte) error { + _, _, e1 := unix.Syscall6( + unix.SYS_PROCCTL, uintptr(idtype), uintptr(id), + uintptr(cmd), uintptr(unsafe.Pointer(arg)), 0, 0) + if e1 != 0 { + return unix.Errno(e1) + } + return nil +} + +func setChildProcess() error { + if err := procctl(P_PID, unix.Getpid(), PROC_REAP_ACQUIRE, nil); err != nil { + fmt.Fprintf(os.Stderr, "procctl(PROC_REAP_ACQUIRE): %v\n", err) + return err + } + return nil +} + +func (b *Builder) Run(command []string, options RunOptions) error { + var runArtifacts *runMountArtifacts + if len(options.ExternalImageMounts) > 0 { + defer func() { + if runArtifacts == nil { + // we didn't add ExternalImageMounts to the + // list of images that we're going to unmount + // yet and make a deferred call that cleans + // them up, but the caller is expecting us to + // unmount these for them because we offered to + for _, image := range options.ExternalImageMounts { + if _, err := b.store.UnmountImage(image, false); err != nil { + logrus.Debugf("umounting image %q: %v", image, err) + } + } + } + }() + } + + p, err := os.MkdirTemp(tmpdir.GetTempDir(), define.Package) + if err != nil { + return err + } + // On some hosts like AH, /tmp is a symlink and we need an + // absolute path. + path, err := filepath.EvalSymlinks(p) + if err != nil { + return err + } + logrus.Debugf("using %q to hold bundle data", path) + defer func() { + if err2 := os.RemoveAll(path); err2 != nil { + logrus.Errorf("error removing %q: %v", path, err2) + } + }() + + gp, err := generate.New("freebsd") + if err != nil { + return fmt.Errorf("generating new 'freebsd' runtime spec: %w", err) + } + g := &gp + + isolation := options.Isolation + if isolation == IsolationDefault { + isolation = b.Isolation + if isolation == IsolationDefault { + isolation, err = parse.IsolationOption("") + if err != nil { + logrus.Debugf("got %v while trying to determine default isolation, guessing OCI", err) + isolation = IsolationOCI + } else if isolation == IsolationDefault { + isolation = IsolationOCI + } + } + } + if err := checkAndOverrideIsolationOptions(isolation, &options); err != nil { + return err + } + + // hardwire the environment to match docker build to avoid subtle and hard-to-debug differences due to containers.conf + b.configureEnvironment(g, options, []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}) + + if b.CommonBuildOpts == nil { + return fmt.Errorf("invalid format on container you must recreate the container") + } + + if err := addCommonOptsToSpec(b.CommonBuildOpts, g); err != nil { + return err + } + + workDir := b.WorkDir() + if options.WorkingDir != "" { + g.SetProcessCwd(options.WorkingDir) + workDir = options.WorkingDir + } else if b.WorkDir() != "" { + g.SetProcessCwd(b.WorkDir()) + workDir = b.WorkDir() + } + if workDir == "" { + workDir = string(os.PathSeparator) + } + mountPoint, err := b.Mount(b.MountLabel) + if err != nil { + return fmt.Errorf("mounting container %q: %w", b.ContainerID, err) + } + defer func() { + if err := b.Unmount(); err != nil { + logrus.Errorf("error unmounting container: %v", err) + } + }() + g.SetRootPath(mountPoint) + if len(command) > 0 { + command = runLookupPath(g, command) + g.SetProcessArgs(command) + } else { + g.SetProcessArgs(nil) + } + + setupTerminal(g, options.Terminal, options.TerminalSize) + + configureNetwork, networkString, err := b.configureNamespaces(g, &options) + if err != nil { + return err + } + + containerName := Package + "-" + filepath.Base(path) + if configureNetwork { + if jail.NeedVnetJail() { + g.AddAnnotation("org.freebsd.parentJail", containerName+"-vnet") + } else { + g.AddAnnotation("org.freebsd.jail.vnet", "new") + } + } + + homeDir, err := b.configureUIDGID(g, mountPoint, options) + if err != nil { + return err + } + + // Now grab the spec from the generator. Set the generator to nil so that future contributors + // will quickly be able to tell that they're supposed to be modifying the spec directly from here. + spec := g.Config + g = nil + + // Set the seccomp configuration using the specified profile name. Some syscalls are + // allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot), + // so we sorted out the capabilities lists first. + if err = setupSeccomp(spec, b.CommonBuildOpts.SeccompProfilePath); err != nil { + return err + } + + uid, gid := spec.Process.User.UID, spec.Process.User.GID + idPair := &idtools.IDPair{UID: int(uid), GID: int(gid)} + + mode := os.FileMode(0o755) + coptions := copier.MkdirOptions{ + ChownNew: idPair, + ChmodNew: &mode, + } + if err := copier.Mkdir(mountPoint, filepath.Join(mountPoint, spec.Process.Cwd), coptions); err != nil { + return err + } + + bindFiles := make(map[string]string) + volumes := b.Volumes() + + // Figure out who owns files that will appear to be owned by UID/GID 0 in the container. + rootUID, rootGID, err := util.GetHostRootIDs(spec) + if err != nil { + return err + } + rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)} + + hostsFile := "" + if !options.NoHosts && !slices.Contains(volumes, config.DefaultHostsFile) && options.ConfigureNetwork != define.NetworkDisabled { + hostsFile, err = b.createHostsFile(path, rootIDPair) + if err != nil { + return err + } + bindFiles[config.DefaultHostsFile] = hostsFile + + // Only add entries here if we do not have to setup network, + // if we do we have to do it much later after the network setup. + if !configureNetwork { + var entries etchosts.HostEntries + // add host entry for local ip when running in host network + if spec.Hostname != "" { + ip := netUtil.GetLocalIP() + if ip != "" { + entries = append(entries, etchosts.HostEntry{ + Names: []string{spec.Hostname}, + IP: ip, + }) + } + } + err = b.addHostsEntries(hostsFile, mountPoint, entries, nil, "") + if err != nil { + return err + } + } + } + + resolvFile := "" + if !slices.Contains(volumes, resolvconf.DefaultResolvConf) && options.ConfigureNetwork != define.NetworkDisabled && !(len(b.CommonBuildOpts.DNSServers) == 1 && strings.ToLower(b.CommonBuildOpts.DNSServers[0]) == "none") { + resolvFile, err = b.createResolvConf(path, rootIDPair) + if err != nil { + return err + } + bindFiles[resolvconf.DefaultResolvConf] = resolvFile + + // Only add entries here if we do not have to do setup network, + // if we do we have to do it much later after the network setup. + if !configureNetwork { + err = b.addResolvConfEntries(resolvFile, nil, spec, false, true) + if err != nil { + return err + } + } + } + + runMountInfo := runMountInfo{ + WorkDir: workDir, + ContextDir: options.ContextDir, + Secrets: options.Secrets, + SSHSources: options.SSHSources, + StageMountPoints: options.StageMountPoints, + SystemContext: options.SystemContext, + } + + runArtifacts, err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, options.CompatBuiltinVolumes, b.CommonBuildOpts.Volumes, options.RunMounts, runMountInfo) + if err != nil { + return fmt.Errorf("resolving mountpoints for container %q: %w", b.ContainerID, err) + } + if runArtifacts.SSHAuthSock != "" { + sshenv := "SSH_AUTH_SOCK=" + runArtifacts.SSHAuthSock + spec.Process.Env = append(spec.Process.Env, sshenv) + } + + // following run was called from `buildah run` + // and some images were mounted for this run + // add them to cleanup artifacts + if len(options.ExternalImageMounts) > 0 { + runArtifacts.MountedImages = append(runArtifacts.MountedImages, options.ExternalImageMounts...) + } + + defer func() { + if err := b.cleanupRunMounts(runArtifacts); err != nil { + options.Logger.Errorf("unable to cleanup run mounts %v", err) + } + }() + + // If we are creating a network, make the vnet here so that we can + // execute the OCI runtime inside it. For FreeBSD-13.3 and later, we can + // configure the container network settings from outside the jail, which + // removes the need for a separate jail to manage the vnet. + if configureNetwork && jail.NeedVnetJail() { + mynetns := containerName + "-vnet" + + jconf := jail.NewConfig() + jconf.Set("name", mynetns) + jconf.Set("vnet", jail.NEW) + jconf.Set("children.max", 1) + jconf.Set("persist", true) + jconf.Set("enforce_statfs", 0) + jconf.Set("devfs_ruleset", 4) + jconf.Set("allow.raw_sockets", true) + jconf.Set("allow.chflags", true) + jconf.Set("securelevel", -1) + netjail, err := jail.Create(jconf) + if err != nil { + return err + } + defer func() { + jconf := jail.NewConfig() + jconf.Set("persist", false) + err2 := netjail.Set(jconf) + if err2 != nil { + logrus.Errorf("error releasing vnet jail %q: %v", mynetns, err2) + } + }() + } + + // Create any mount points that we need that aren't already present in + // the rootfs. + createdMountTargets, err := b.createMountTargets(spec) + if err != nil { + return fmt.Errorf("ensuring mount targets for container %q: %w", b.ContainerID, err) + } + defer func() { + // Attempt to clean up mount targets for the sake of builds + // that don't commit and rebase at each step, and people using + // `buildah run` more than once, who don't expect empty mount + // points to stick around. They'll still get filtered out at + // commit-time if another concurrent Run() is keeping something + // busy. + if _, err := copier.ConditionalRemove(mountPoint, mountPoint, copier.ConditionalRemoveOptions{ + UIDMap: b.store.UIDMap(), + GIDMap: b.store.GIDMap(), + Paths: createdMountTargets, + }); err != nil { + options.Logger.Errorf("unable to cleanup run mount targets %v", err) + } + }() + + switch isolation { + case IsolationOCI: + var moreCreateArgs []string + if options.NoPivot { + moreCreateArgs = []string{"--no-pivot"} + } else { + moreCreateArgs = nil + } + err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, networkString, moreCreateArgs, spec, mountPoint, path, containerName, b.Container, hostsFile, resolvFile) + case IsolationChroot: + err = chroot.RunUsingChroot(spec, path, homeDir, options.Stdin, options.Stdout, options.Stderr, options.NoPivot) + default: + err = errors.New("don't know how to run this command") + } + return err +} + +func addCommonOptsToSpec(commonOpts *define.CommonBuildOptions, g *generate.Generator) error { + defaultContainerConfig, err := config.Default() + if err != nil { + return fmt.Errorf("failed to get container config: %w", err) + } + // Other process resource limits + if err := addRlimits(commonOpts.Ulimit, g, defaultContainerConfig.Containers.DefaultUlimits.Get()); err != nil { + return err + } + + logrus.Debugf("Resources: %#v", commonOpts) + return nil +} + +// setupSpecialMountSpecChanges creates special mounts for depending +// on the namespaces - nothing yet for freebsd +func setupSpecialMountSpecChanges(spec *specs.Spec, shmSize string) ([]specs.Mount, error) { + return spec.Mounts, nil +} + +// If this succeeded, the caller would be expected to, after the command which +// uses the mount exits, clean up the overlay filesystem (if we returned one), +// unmount the mounted filesystem (if we provided the path to its mountpoint) +// and remove its mountpoint, unmount the image (if we mounted one), and +// release the lock (if we took one). +func (b *Builder) getCacheMount(tokens []string, sys *types.SystemContext, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir, tmpDir string) (*specs.Mount, string, string, string, *lockfile.LockFile, error) { + return nil, "", "", "", nil, errors.New("cache mounts not supported on freebsd") +} + +func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount, idMaps IDMaps) (mounts []specs.Mount, overlayDirs []string, Err error) { + // Make sure the overlay directory is clean before running + _, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return nil, nil, fmt.Errorf("looking up container directory for %s: %w", b.ContainerID, err) + } + + parseMount := func(mountType, host, container string, options []string) (specs.Mount, error) { + var foundrw, foundro, foundO bool + var upperDir string + for _, opt := range options { + switch opt { + case "rw": + foundrw = true + case "ro": + foundro = true + case "O": + foundO = true + } + if strings.HasPrefix(opt, "upperdir") { + splitOpt := strings.SplitN(opt, "=", 2) + if len(splitOpt) > 1 { + upperDir = splitOpt[1] + } + } + } + if !foundrw && !foundro { + options = append(options, "rw") + } + if mountType == "bind" || mountType == "rbind" { + mountType = "nullfs" + } + if foundO { + containerDir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return specs.Mount{}, err + } + + contentDir, err := overlay.TempDir(containerDir, idMaps.rootUID, idMaps.rootGID) + if err != nil { + return specs.Mount{}, fmt.Errorf("failed to create TempDir in the %s directory: %w", containerDir, err) + } + + overlayOpts := overlay.Options{ + RootUID: idMaps.rootUID, + RootGID: idMaps.rootGID, + UpperDirOptionFragment: upperDir, + GraphOpts: b.store.GraphOptions(), + } + + overlayMount, err := overlay.MountWithOptions(contentDir, host, container, &overlayOpts) + if err == nil { + overlayDirs = append(overlayDirs, contentDir) + } + return overlayMount, err + } + return specs.Mount{ + Destination: container, + Type: mountType, + Source: host, + Options: options, + }, nil + } + + // Bind mount volumes specified for this particular Run() invocation + for _, i := range optionMounts { + logrus.Debugf("setting up mounted volume at %q", i.Destination) + mount, err := parseMount(i.Type, i.Source, i.Destination, i.Options) + if err != nil { + return nil, nil, err + } + mounts = append(mounts, mount) + } + // Bind mount volumes given by the user when the container was created + for _, i := range volumeMounts { + var options []string + spliti := strings.Split(i, ":") + if len(spliti) > 2 { + options = strings.Split(spliti[2], ",") + } + mount, err := parseMount("nullfs", spliti[0], spliti[1], options) + if err != nil { + return nil, nil, err + } + mounts = append(mounts, mount) + } + return mounts, overlayDirs, nil +} + +func setupCapabilities(g *generate.Generator, defaultCapabilities, adds, drops []string) error { + return nil +} + +func (b *Builder) runConfigureNetwork(pid int, isolation define.Isolation, options RunOptions, networkString string, containerName string, hostnames []string) (func(), *netResult, error) { + //if isolation == IsolationOCIRootless { + //return setupRootlessNetwork(pid) + //} + + var configureNetworks []string + if len(networkString) > 0 { + configureNetworks = strings.Split(networkString, ",") + } + + if len(configureNetworks) == 0 { + configureNetworks = []string{b.NetworkInterface.DefaultNetworkName()} + } + logrus.Debugf("configureNetworks: %v", configureNetworks) + + var mynetns string + if jail.NeedVnetJail() { + mynetns = containerName + "-vnet" + } else { + mynetns = containerName + } + + networks := make(map[string]nettypes.PerNetworkOptions, len(configureNetworks)) + for i, network := range configureNetworks { + networks[network] = nettypes.PerNetworkOptions{ + InterfaceName: fmt.Sprintf("eth%d", i), + } + } + + opts := nettypes.NetworkOptions{ + ContainerID: containerName, + ContainerName: containerName, + Networks: networks, + } + netStatus, err := b.NetworkInterface.Setup(mynetns, nettypes.SetupOptions{NetworkOptions: opts}) + if err != nil { + return nil, nil, err + } + + teardown := func() { + err := b.NetworkInterface.Teardown(mynetns, nettypes.TeardownOptions{NetworkOptions: opts}) + if err != nil { + logrus.Errorf("failed to cleanup network: %v", err) + } + } + + return teardown, netStatusToNetResult(netStatus, hostnames), nil +} + +func setupNamespaces(logger *logrus.Logger, g *generate.Generator, namespaceOptions define.NamespaceOptions, idmapOptions define.IDMappingOptions, policy define.NetworkConfigurationPolicy) (configureNetwork bool, networkString string, configureUTS bool, err error) { + // Set namespace options in the container configuration. + for _, namespaceOption := range namespaceOptions { + switch namespaceOption.Name { + case string(specs.NetworkNamespace): + configureNetwork = false + if !namespaceOption.Host && (namespaceOption.Path == "" || !filepath.IsAbs(namespaceOption.Path)) { + if namespaceOption.Path != "" && !filepath.IsAbs(namespaceOption.Path) { + networkString = namespaceOption.Path + namespaceOption.Path = "" + } + configureNetwork = (policy != define.NetworkDisabled) + } + case string(specs.UTSNamespace): + configureUTS = false + if !namespaceOption.Host && namespaceOption.Path == "" { + configureUTS = true + } + } + // TODO: re-visit this when there is consensus on a + // FreeBSD runtime-spec. FreeBSD jails have rough + // equivalents for UTS and and network namespaces. + } + + return configureNetwork, networkString, configureUTS, nil +} + +func (b *Builder) configureNamespaces(g *generate.Generator, options *RunOptions) (bool, string, error) { + defaultNamespaceOptions, err := DefaultNamespaceOptions() + if err != nil { + return false, "", err + } + + namespaceOptions := defaultNamespaceOptions + namespaceOptions.AddOrReplace(b.NamespaceOptions...) + namespaceOptions.AddOrReplace(options.NamespaceOptions...) + + networkPolicy := options.ConfigureNetwork + // Nothing was specified explicitly so network policy should be inherited from builder + if networkPolicy == NetworkDefault { + networkPolicy = b.ConfigureNetwork + + // If builder policy was NetworkDisabled and + // we want to disable network for this run. + // reset options.ConfigureNetwork to NetworkDisabled + // since it will be treated as source of truth later. + if networkPolicy == NetworkDisabled { + options.ConfigureNetwork = networkPolicy + } + } + + configureNetwork, networkString, configureUTS, err := setupNamespaces(options.Logger, g, namespaceOptions, b.IDMappingOptions, networkPolicy) + if err != nil { + return false, "", err + } + + if configureUTS { + if options.Hostname != "" { + g.SetHostname(options.Hostname) + } else if b.Hostname() != "" { + g.SetHostname(b.Hostname()) + } else { + hostname := stringid.TruncateID(b.ContainerID) + defConfig, err := config.Default() + if err != nil { + return false, "", fmt.Errorf("failed to get container config: %w", err) + } + if defConfig.Containers.ContainerNameAsHostName { + if mapped := mapContainerNameToHostname(b.Container); mapped != "" { + hostname = mapped + } + } + g.SetHostname(hostname) + } + } else { + g.SetHostname("") + } + + found := false + spec := g.Config + for i := range spec.Process.Env { + if strings.HasPrefix(spec.Process.Env[i], "HOSTNAME=") { + found = true + break + } + } + if !found { + spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("HOSTNAME=%s", spec.Hostname)) + } + + return configureNetwork, networkString, nil +} + +func runSetupBoundFiles(bundlePath string, bindFiles map[string]string) (mounts []specs.Mount) { + for dest, src := range bindFiles { + options := []string{} + if strings.HasPrefix(src, bundlePath) { + options = append(options, bind.NoBindOption) + } + mounts = append(mounts, specs.Mount{ + Source: src, + Destination: dest, + Type: "nullfs", + Options: options, + }) + } + return mounts +} + +func addRlimits(ulimit []string, g *generate.Generator, defaultUlimits []string) error { + var ( + ul *units.Ulimit + err error + ) + + ulimit = append(defaultUlimits, ulimit...) + for _, u := range ulimit { + if ul, err = butil.ParseUlimit(u); err != nil { + return fmt.Errorf("ulimit option %q requires name=SOFT:HARD, failed to be parsed: %w", u, err) + } + + g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Hard), uint64(ul.Soft)) + } + return nil +} + +// Create pipes to use for relaying stdio. +func runMakeStdioPipe(uid, gid int) ([][]int, error) { + stdioPipe := make([][]int, 3) + for i := range stdioPipe { + stdioPipe[i] = make([]int, 2) + if err := unix.Pipe(stdioPipe[i]); err != nil { + return nil, fmt.Errorf("creating pipe for container FD %d: %w", i, err) + } + } + return stdioPipe, nil +} diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go new file mode 100644 index 00000000..e84cba96 --- /dev/null +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -0,0 +1,1539 @@ +//go:build linux + +package buildah + +import ( + "context" + "errors" + "fmt" + "maps" + "os" + "path/filepath" + "slices" + "strings" + "sync" + "syscall" + + "github.com/containers/buildah/bind" + "github.com/containers/buildah/chroot" + "github.com/containers/buildah/copier" + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal" + "github.com/containers/buildah/internal/tmpdir" + "github.com/containers/buildah/internal/volumes" + "github.com/containers/buildah/pkg/binfmt" + "github.com/containers/buildah/pkg/overlay" + "github.com/containers/buildah/pkg/parse" + butil "github.com/containers/buildah/pkg/util" + "github.com/containers/buildah/util" + "github.com/docker/go-units" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/etchosts" + "go.podman.io/common/libnetwork/pasta" + "go.podman.io/common/libnetwork/resolvconf" + "go.podman.io/common/libnetwork/slirp4netns" + nettypes "go.podman.io/common/libnetwork/types" + netUtil "go.podman.io/common/libnetwork/util" + "go.podman.io/common/pkg/capabilities" + "go.podman.io/common/pkg/chown" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/hooks" + hooksExec "go.podman.io/common/pkg/hooks/exec" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/ioutils" + "go.podman.io/storage/pkg/lockfile" + "go.podman.io/storage/pkg/mount" + "go.podman.io/storage/pkg/stringid" + "go.podman.io/storage/pkg/unshare" + "golang.org/x/sys/unix" + "tags.cncf.io/container-device-interface/pkg/cdi" + "tags.cncf.io/container-device-interface/pkg/parser" +) + +// binfmtRegistered makes sure we only try to register binfmt_misc +// interpreters once, the first time we handle a RUN instruction. +var binfmtRegistered sync.Once + +func setChildProcess() error { + if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(1), 0, 0, 0); err != nil { + fmt.Fprintf(os.Stderr, "prctl(PR_SET_CHILD_SUBREAPER, 1): %v\n", err) + return err + } + return nil +} + +func (b *Builder) cdiSetupDevicesInSpec(deviceSpecs []string, configDir string, spec *specs.Spec) ([]string, error) { + var configDirs []string + defConfig, err := config.Default() + if err != nil { + return nil, fmt.Errorf("failed to get container config: %w", err) + } + // The CDI cache prioritizes entries from directories that are later in + // the list of ones it scans, so start with our general config, then + // append values passed to us through API layers. + configDirs = slices.Clone(defConfig.Engine.CdiSpecDirs.Get()) + if b.CDIConfigDir != "" { + configDirs = append(configDirs, b.CDIConfigDir) + } + if configDir != "" { + configDirs = append(configDirs, configDir) + } + if len(configDirs) == 0 { + // No directories to scan for CDI configuration means that CDI + // won't have any details for setting up any devices, so we + // don't need to be doing anything here. + return deviceSpecs, nil + } + var qualifiedDeviceSpecs, unqualifiedDeviceSpecs []string + for _, deviceSpec := range deviceSpecs { + if parser.IsQualifiedName(deviceSpec) { + qualifiedDeviceSpecs = append(qualifiedDeviceSpecs, deviceSpec) + } else { + unqualifiedDeviceSpecs = append(unqualifiedDeviceSpecs, deviceSpec) + } + } + if len(qualifiedDeviceSpecs) == 0 { + // None of the specified devices were in the form that would be + // handled by CDI, so we don't need to do anything here. + return deviceSpecs, nil + } + if err := cdi.Configure(cdi.WithSpecDirs(configDirs...)); err != nil { + return nil, fmt.Errorf("CDI default registry ignored configured directories %v: %w", configDirs, err) + } + leftoverDevices := slices.Clone(deviceSpecs) + if err := cdi.Refresh(); err != nil { + logrus.Warnf("CDI default registry refresh: %v", err) + } else { + leftoverDevices, err = cdi.InjectDevices(spec, qualifiedDeviceSpecs...) + if err != nil { + return nil, fmt.Errorf("CDI device injection (leftover devices: %v): %w", leftoverDevices, err) + } + } + removed := slices.DeleteFunc(slices.Clone(deviceSpecs), func(t string) bool { return slices.Contains(leftoverDevices, t) }) + logrus.Debugf("CDI taking care of devices %v, leaving devices %v, skipped %v", removed, leftoverDevices, unqualifiedDeviceSpecs) + return append(leftoverDevices, unqualifiedDeviceSpecs...), nil +} + +// Extract the device list so that we can still try to make it work if +// we're running rootless and can't just mknod() the device nodes. +func separateDevicesFromRuntimeSpec(g *generate.Generator) define.ContainerDevices { + var result define.ContainerDevices + if g.Config != nil && g.Config.Linux != nil { + for _, device := range g.Config.Linux.Devices { + var bDevice define.BuildahDevice + bDevice.Path = device.Path + switch device.Type { + case "b": + bDevice.Type = 'b' + case "c": + bDevice.Type = 'c' + case "u": + bDevice.Type = 'u' + case "p": + bDevice.Type = 'p' + } + bDevice.Major = device.Major + bDevice.Minor = device.Minor + if device.FileMode != nil { + bDevice.FileMode = *device.FileMode + } + if device.UID != nil { + bDevice.Uid = *device.UID + } + if device.GID != nil { + bDevice.Gid = *device.GID + } + bDevice.Source = device.Path + bDevice.Destination = device.Path + result = append(result, bDevice) + } + } + g.ClearLinuxDevices() + return result +} + +// Run runs the specified command in the container's root filesystem. +func (b *Builder) Run(command []string, options RunOptions) error { + var runArtifacts *runMountArtifacts + if len(options.ExternalImageMounts) > 0 { + defer func() { + if runArtifacts == nil { + // we didn't add ExternalImageMounts to the + // list of images that we're going to unmount + // yet and make a deferred call that cleans + // them up, but the caller is expecting us to + // unmount these for them because we offered to + for _, image := range options.ExternalImageMounts { + if _, err := b.store.UnmountImage(image, false); err != nil { + logrus.Debugf("umounting image %q: %v", image, err) + } + } + } + }() + } + + if os.Getenv("container") != "" { + os, arch, variant, err := parse.Platform("") + if err != nil { + return fmt.Errorf("reading the current default platform") + } + platform := b.OCIv1.Platform + if os != platform.OS || arch != platform.Architecture || variant != platform.Variant { + binfmtRegistered.Do(func() { + if err := binfmt.Register(nil); err != nil { + logrus.Warnf("registering binfmt_misc interpreters: %v", err) + } + }) + } + } + + p, err := os.MkdirTemp(tmpdir.GetTempDir(), define.Package) + if err != nil { + return err + } + // On some hosts like AH, /tmp is a symlink and we need an + // absolute path. + path, err := filepath.EvalSymlinks(p) + if err != nil { + return err + } + logrus.Debugf("using %q to hold bundle data", path) + defer func() { + if err2 := os.RemoveAll(path); err2 != nil { + options.Logger.Error(err2) + } + }() + + gp, err := generate.New("linux") + if err != nil { + return fmt.Errorf("generating new 'linux' runtime spec: %w", err) + } + g := &gp + + isolation := options.Isolation + if isolation == define.IsolationDefault { + isolation = b.Isolation + if isolation == define.IsolationDefault { + isolation, err = parse.IsolationOption("") + if err != nil { + logrus.Debugf("got %v while trying to determine default isolation, guessing OCI", err) + isolation = IsolationOCI + } else if isolation == IsolationDefault { + isolation = IsolationOCI + } + } + } + if err := checkAndOverrideIsolationOptions(isolation, &options); err != nil { + return err + } + + // hardwire the environment to match docker build to avoid subtle and hard-to-debug differences due to containers.conf + b.configureEnvironment(g, options, []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}) + + if b.CommonBuildOpts == nil { + return fmt.Errorf("invalid format on container you must recreate the container") + } + + if err := addCommonOptsToSpec(b.CommonBuildOpts, g); err != nil { + return err + } + + workDir := b.WorkDir() + if options.WorkingDir != "" { + g.SetProcessCwd(options.WorkingDir) + workDir = options.WorkingDir + } else if b.WorkDir() != "" { + g.SetProcessCwd(b.WorkDir()) + workDir = b.WorkDir() + } + if workDir == "" { + workDir = string(os.PathSeparator) + } + setupSelinux(g, b.ProcessLabel, b.MountLabel) + mountPoint, err := b.Mount(b.MountLabel) + if err != nil { + return fmt.Errorf("mounting container %q: %w", b.ContainerID, err) + } + defer func() { + if err := b.Unmount(); err != nil { + options.Logger.Errorf("error unmounting container: %v", err) + } + }() + g.SetRootPath(mountPoint) + if len(command) > 0 { + command = runLookupPath(g, command) + g.SetProcessArgs(command) + } else { + g.SetProcessArgs(nil) + } + + // Combine the working container's set of devices with the ones for just this run. + deviceSpecs := slices.Concat(options.DeviceSpecs, b.DeviceSpecs) + deviceSpecs, err = b.cdiSetupDevicesInSpec(deviceSpecs, options.CDIConfigDir, g.Config) // makes changes to more than just the device list + if err != nil { + return err + } + devices := separateDevicesFromRuntimeSpec(g) + for _, deviceSpec := range deviceSpecs { + device, err := parse.DeviceFromPath(deviceSpec) + if err != nil { + return fmt.Errorf("setting up device %q: %w", deviceSpec, err) + } + devices = append(devices, device...) + } + devices = append(append(devices, options.Devices...), b.Devices...) + + // Mount devices, if any, and if we're rootless attempt to work around not + // being able to create device nodes by bind-mounting them from the host, like podman does. + if unshare.IsRootless() { + // We are going to create bind mounts for devices + // but we need to make sure that we don't override + // anything which is already in OCI spec. + mounts := make(map[string]any) + for _, m := range g.Mounts() { + mounts[m.Destination] = true + } + newMounts := []specs.Mount{} + for _, d := range devices { + // Default permission is read-only. + perm := "ro" + // Get permission configured for this device but only process `write` + // permission in rootless since `mknod` is not supported anyways. + if strings.Contains(string(d.Rule.Permissions), "w") { + perm = "rw" + } + devMnt := specs.Mount{ + Destination: d.Destination, + Type: parse.TypeBind, + Source: d.Source, + Options: []string{"slave", "nosuid", "noexec", perm, "rbind"}, + } + // Podman parity: podman skips these two devices hence we do the same. + if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { + continue + } + // Device is already in OCI spec do not re-mount. + if _, found := mounts[d.Path]; found { + continue + } + newMounts = append(newMounts, devMnt) + } + g.Config.Mounts = append(newMounts, g.Config.Mounts...) + } else { + for _, d := range devices { + sDev := specs.LinuxDevice{ + Type: string(d.Type), + Path: d.Path, + Major: d.Major, + Minor: d.Minor, + FileMode: &d.FileMode, + UID: &d.Uid, + GID: &d.Gid, + } + g.AddDevice(sDev) + g.AddLinuxResourcesDevice(true, string(d.Type), &d.Major, &d.Minor, string(d.Permissions)) + } + } + + setupMaskedPaths(g, b.CommonBuildOpts) + setupReadOnlyPaths(g) + + setupTerminal(g, options.Terminal, options.TerminalSize) + + configureNetwork, networkString, err := b.configureNamespaces(g, &options) + if err != nil { + return err + } + + homeDir, err := b.configureUIDGID(g, mountPoint, options) + if err != nil { + return err + } + + g.SetProcessNoNewPrivileges(b.CommonBuildOpts.NoNewPrivileges) + + g.SetProcessApparmorProfile(b.CommonBuildOpts.ApparmorProfile) + + // Now grab the spec from the generator. Set the generator to nil so that future contributors + // will quickly be able to tell that they're supposed to be modifying the spec directly from here. + spec := g.Config + g = nil + + // Set the seccomp configuration using the specified profile name. Some syscalls are + // allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot), + // so we sorted out the capabilities lists first. + if err = setupSeccomp(spec, b.CommonBuildOpts.SeccompProfilePath); err != nil { + return err + } + + uid, gid := spec.Process.User.UID, spec.Process.User.GID + if spec.Linux != nil { + uid, gid, err = util.GetHostIDs(spec.Linux.UIDMappings, spec.Linux.GIDMappings, uid, gid) + if err != nil { + return err + } + } + + idPair := &idtools.IDPair{UID: int(uid), GID: int(gid)} + + mode := os.FileMode(0o755) + coptions := copier.MkdirOptions{ + ChownNew: idPair, + ChmodNew: &mode, + } + if err := copier.Mkdir(mountPoint, filepath.Join(mountPoint, spec.Process.Cwd), coptions); err != nil { + return err + } + + bindFiles := make(map[string]string) + volumes := b.Volumes() + + // Figure out who owns files that will appear to be owned by UID/GID 0 in the container. + rootUID, rootGID, err := util.GetHostRootIDs(spec) + if err != nil { + return err + } + rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)} + + hostsFile := "" + if !options.NoHosts && !slices.Contains(volumes, config.DefaultHostsFile) && options.ConfigureNetwork != define.NetworkDisabled { + hostsFile, err = b.createHostsFile(path, rootIDPair) + if err != nil { + return err + } + bindFiles[config.DefaultHostsFile] = hostsFile + + // Only add entries here if we do not have to do setup network, + // if we do we have to do it much later after the network setup. + if !configureNetwork { + var entries etchosts.HostEntries + isHost := true + if spec.Linux != nil { + for _, ns := range spec.Linux.Namespaces { + if ns.Type == specs.NetworkNamespace { + isHost = false + break + } + } + } + // add host entry for local ip when running in host network + if spec.Hostname != "" && isHost { + ip := netUtil.GetLocalIP() + if ip != "" { + entries = append(entries, etchosts.HostEntry{ + Names: []string{spec.Hostname}, + IP: ip, + }) + } + } + err = b.addHostsEntries(hostsFile, mountPoint, entries, nil, "") + if err != nil { + return err + } + } + } + + if !options.NoHostname && !(slices.Contains(volumes, "/etc/hostname")) { + hostnameFile, err := b.generateHostname(path, spec.Hostname, rootIDPair) + if err != nil { + return err + } + // Bind /etc/hostname + bindFiles["/etc/hostname"] = hostnameFile + } + + resolvFile := "" + if !slices.Contains(volumes, resolvconf.DefaultResolvConf) && options.ConfigureNetwork != define.NetworkDisabled && (len(b.CommonBuildOpts.DNSServers) != 1 || strings.ToLower(b.CommonBuildOpts.DNSServers[0]) != "none") { + resolvFile, err = b.createResolvConf(path, rootIDPair) + if err != nil { + return err + } + bindFiles[resolvconf.DefaultResolvConf] = resolvFile + + // Only add entries here if we do not have to do setup network, + // if we do we have to do it much later after the network setup. + if !configureNetwork { + err = b.addResolvConfEntries(resolvFile, nil, spec, false, true) + if err != nil { + return err + } + } + } + // Empty file, so no need to recreate if it exists + if _, ok := bindFiles["/run/.containerenv"]; !ok { + containerenvPath := filepath.Join(path, "/run/.containerenv") + if err = os.MkdirAll(filepath.Dir(containerenvPath), 0o755); err != nil { + return err + } + + rootless := 0 + if unshare.IsRootless() { + rootless = 1 + } + // Populate the .containerenv with container information + containerenv := fmt.Sprintf(` +engine="buildah-%s" +name=%q +id=%q +image=%q +imageid=%q +rootless=%d +`, define.Version, b.Container, b.ContainerID, b.FromImage, b.FromImageID, rootless) + + if err = ioutils.AtomicWriteFile(containerenvPath, []byte(containerenv), 0o755); err != nil { + return err + } + if err := relabel(containerenvPath, b.MountLabel, false); err != nil { + return err + } + + bindFiles["/run/.containerenv"] = containerenvPath + } + + // Setup OCI hooks + _, err = b.setupOCIHooks(spec, (len(options.Mounts) > 0 || len(volumes) > 0)) + if err != nil { + return fmt.Errorf("unable to setup OCI hooks: %w", err) + } + + runMountInfo := runMountInfo{ + WorkDir: workDir, + ContextDir: options.ContextDir, + Secrets: options.Secrets, + SSHSources: options.SSHSources, + StageMountPoints: options.StageMountPoints, + SystemContext: options.SystemContext, + } + + runArtifacts, err = b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, options.CompatBuiltinVolumes, b.CommonBuildOpts.Volumes, options.RunMounts, runMountInfo) + if err != nil { + return fmt.Errorf("resolving mountpoints for container %q: %w", b.ContainerID, err) + } + if runArtifacts.SSHAuthSock != "" { + sshenv := "SSH_AUTH_SOCK=" + runArtifacts.SSHAuthSock + spec.Process.Env = append(spec.Process.Env, sshenv) + } + + // Create any mount points that we need that aren't already present in + // the rootfs. + createdMountTargets, err := b.createMountTargets(spec) + if err != nil { + return fmt.Errorf("ensuring mount targets for container %q: %w", b.ContainerID, err) + } + defer func() { + // Attempt to clean up mount targets for the sake of builds + // that don't commit and rebase at each step, and people using + // `buildah run` more than once, who don't expect empty mount + // points to stick around. They'll still get filtered out at + // commit-time if another concurrent Run() is keeping something + // busy. + if _, err := copier.ConditionalRemove(mountPoint, mountPoint, copier.ConditionalRemoveOptions{ + UIDMap: b.store.UIDMap(), + GIDMap: b.store.GIDMap(), + Paths: createdMountTargets, + }); err != nil { + options.Logger.Errorf("unable to cleanup run mount targets %v", err) + } + }() + + // following run was called from `buildah run` + // and some images were mounted for this run + // add them to cleanup artifacts + if len(options.ExternalImageMounts) > 0 { + runArtifacts.MountedImages = append(runArtifacts.MountedImages, options.ExternalImageMounts...) + } + + defer func() { + if err := b.cleanupRunMounts(runArtifacts); err != nil { + options.Logger.Errorf("unable to cleanup run mounts %v", err) + } + }() + + // Handle mount flags that request that the source locations for "bind" mountpoints be + // relabeled, and filter those flags out of the list of mount options we pass to the + // runtime. + for i := range spec.Mounts { + switch spec.Mounts[i].Type { + default: + continue + case "bind", "rbind": + // all good, keep going + } + zflag := "" + for _, opt := range spec.Mounts[i].Options { + if opt == "z" || opt == "Z" { + zflag = opt + } + } + if zflag == "" { + continue + } + spec.Mounts[i].Options = slices.DeleteFunc(spec.Mounts[i].Options, func(opt string) bool { + return opt == "z" || opt == "Z" + }) + if err := relabel(spec.Mounts[i].Source, b.MountLabel, zflag == "z"); err != nil { + return fmt.Errorf("setting file label %q on %q: %w", b.MountLabel, spec.Mounts[i].Source, err) + } + } + + switch isolation { + case define.IsolationOCI: + var moreCreateArgs []string + if options.NoPivot { + moreCreateArgs = append(moreCreateArgs, "--no-pivot") + } + err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, networkString, moreCreateArgs, spec, + mountPoint, path, define.Package+"-"+filepath.Base(path), b.Container, hostsFile, resolvFile) + case IsolationChroot: + err = chroot.RunUsingChroot(spec, path, homeDir, options.Stdin, options.Stdout, options.Stderr, options.NoPivot) + case IsolationOCIRootless: + moreCreateArgs := []string{"--no-new-keyring"} + if options.NoPivot { + moreCreateArgs = append(moreCreateArgs, "--no-pivot") + } + err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, networkString, moreCreateArgs, spec, + mountPoint, path, define.Package+"-"+filepath.Base(path), b.Container, hostsFile, resolvFile) + default: + err = errors.New("don't know how to run this command") + } + return err +} + +func (b *Builder) setupOCIHooks(config *specs.Spec, hasVolumes bool) (map[string][]specs.Hook, error) { + allHooks := make(map[string][]specs.Hook) + if len(b.CommonBuildOpts.OCIHooksDir) == 0 { + if unshare.IsRootless() { + return nil, nil + } + for _, hDir := range []string{hooks.DefaultDir, hooks.OverrideDir} { + manager, err := hooks.New(context.Background(), []string{hDir}, []string{}) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + return nil, err + } + ociHooks, err := manager.Hooks(config, b.ImageAnnotations, hasVolumes) + if err != nil { + return nil, err + } + if len(ociHooks) > 0 || config.Hooks != nil { + logrus.Warnf("Implicit hook directories are deprecated; set --hooks-dir=%q explicitly to continue to load ociHooks from this directory", hDir) + } + maps.Copy(allHooks, ociHooks) + } + } else { + manager, err := hooks.New(context.Background(), b.CommonBuildOpts.OCIHooksDir, []string{}) + if err != nil { + return nil, err + } + + allHooks, err = manager.Hooks(config, b.ImageAnnotations, hasVolumes) + if err != nil { + return nil, err + } + } + + hookErr, err := hooksExec.RuntimeConfigFilter(context.Background(), allHooks["precreate"], config, hooksExec.DefaultPostKillTimeout) //nolint:staticcheck + if err != nil { + logrus.Warnf("Container: precreate hook: %v", err) + if hookErr != nil && hookErr != err { + logrus.Debugf("container: precreate hook (hook error): %v", hookErr) + } + return nil, err + } + return allHooks, nil +} + +func addCommonOptsToSpec(commonOpts *define.CommonBuildOptions, g *generate.Generator) error { + // Resources - CPU + if commonOpts.CPUPeriod != 0 { + g.SetLinuxResourcesCPUPeriod(commonOpts.CPUPeriod) + } + if commonOpts.CPUQuota != 0 { + g.SetLinuxResourcesCPUQuota(commonOpts.CPUQuota) + } + if commonOpts.CPUShares != 0 { + g.SetLinuxResourcesCPUShares(commonOpts.CPUShares) + } + if commonOpts.CPUSetCPUs != "" { + g.SetLinuxResourcesCPUCpus(commonOpts.CPUSetCPUs) + } + if commonOpts.CPUSetMems != "" { + g.SetLinuxResourcesCPUMems(commonOpts.CPUSetMems) + } + + // Resources - Memory + if commonOpts.Memory != 0 { + g.SetLinuxResourcesMemoryLimit(commonOpts.Memory) + } + if commonOpts.MemorySwap != 0 { + g.SetLinuxResourcesMemorySwap(commonOpts.MemorySwap) + } + + // cgroup membership + if commonOpts.CgroupParent != "" { + g.SetLinuxCgroupsPath(commonOpts.CgroupParent) + } + + defaultContainerConfig, err := config.Default() + if err != nil { + return fmt.Errorf("failed to get container config: %w", err) + } + // Other process resource limits + if err := addRlimits(commonOpts.Ulimit, g, defaultContainerConfig.Containers.DefaultUlimits.Get()); err != nil { + return err + } + + logrus.Debugf("Resources: %#v", commonOpts) + return nil +} + +func setupSlirp4netnsNetwork(config *config.Config, netns, cid string, options, hostnames []string) (func(), *netResult, error) { + // we need the TmpDir for the slirp4netns code + if err := os.MkdirAll(config.Engine.TmpDir, 0o751); err != nil { + return nil, nil, fmt.Errorf("failed to create tempdir: %w", err) + } + res, err := slirp4netns.Setup(&slirp4netns.SetupOptions{ + Config: config, + ContainerID: cid, + Netns: netns, + ExtraOptions: options, + Pdeathsig: syscall.SIGKILL, + }) + if err != nil { + return nil, nil, err + } + + ip, err := slirp4netns.GetIP(res.Subnet) + if err != nil { + return nil, nil, fmt.Errorf("get slirp4netns ip: %w", err) + } + + dns, err := slirp4netns.GetDNS(res.Subnet) + if err != nil { + return nil, nil, fmt.Errorf("get slirp4netns dns ip: %w", err) + } + + result := &netResult{ + entries: etchosts.HostEntries{{IP: ip.String(), Names: hostnames}}, + dnsServers: []string{dns.String()}, + ipv6: res.IPv6, + keepHostResolvers: true, + } + + return func() { + syscall.Kill(res.Pid, syscall.SIGKILL) //nolint:errcheck + var status syscall.WaitStatus + syscall.Wait4(res.Pid, &status, 0, nil) //nolint:errcheck + }, result, nil +} + +func setupPasta(config *config.Config, netns string, options, hostnames []string) (func(), *netResult, error) { + res, err := pasta.Setup(&pasta.SetupOptions{ + Config: config, + Netns: netns, + ExtraOptions: options, + }) + if err != nil { + return nil, nil, err + } + + var entries etchosts.HostEntries + if len(res.IPAddresses) > 0 { + entries = etchosts.HostEntries{{IP: res.IPAddresses[0].String(), Names: hostnames}} + } + + mappedIP := "" + if len(res.MapGuestAddrIPs) > 0 { + mappedIP = res.MapGuestAddrIPs[0] + } + + result := &netResult{ + entries: entries, + dnsServers: res.DNSForwardIPs, + excludeIPs: res.IPAddresses, + ipv6: res.IPv6, + keepHostResolvers: true, + preferredHostContainersInternalIP: mappedIP, + } + + return nil, result, nil +} + +func (b *Builder) runConfigureNetwork(pid int, isolation define.Isolation, options RunOptions, network, containerName string, hostnames []string) (func(), *netResult, error) { + netns := fmt.Sprintf("/proc/%d/ns/net", pid) + var configureNetworks []string + defConfig, err := config.Default() + if err != nil { + return nil, nil, fmt.Errorf("failed to get container config: %w", err) + } + + name, networkOpts, hasOpts := strings.Cut(network, ":") + var netOpts []string + if hasOpts { + netOpts = strings.Split(networkOpts, ",") + } + if isolation == IsolationOCIRootless && name == "" { + switch defConfig.Network.DefaultRootlessNetworkCmd { + case slirp4netns.BinaryName, "": + name = slirp4netns.BinaryName + case pasta.BinaryName: + name = pasta.BinaryName + default: + return nil, nil, fmt.Errorf("invalid default_rootless_network_cmd option %q", + defConfig.Network.DefaultRootlessNetworkCmd) + } + } + + switch { + case name == slirp4netns.BinaryName: + return setupSlirp4netnsNetwork(defConfig, netns, containerName, netOpts, hostnames) + case name == pasta.BinaryName: + return setupPasta(defConfig, netns, netOpts, hostnames) + + // Basically default case except we make sure to not split an empty + // name as this would return a slice with one empty string which is + // not a valid network name. + case len(network) > 0: + // old syntax allow comma separated network names + configureNetworks = strings.Split(network, ",") + } + + if isolation == IsolationOCIRootless { + return nil, nil, errors.New("cannot use networks as rootless") + } + + if len(configureNetworks) == 0 { + configureNetworks = []string{b.NetworkInterface.DefaultNetworkName()} + } + + // Make sure we can access the container's network namespace, + // even after it exits, to successfully tear down the + // interfaces. Ensure this by opening a handle to the network + // namespace, and using our copy to both configure and + // deconfigure it. + netFD, err := unix.Open(netns, unix.O_RDONLY, 0) + if err != nil { + return nil, nil, fmt.Errorf("opening network namespace: %w", err) + } + mynetns := fmt.Sprintf("/proc/%d/fd/%d", unix.Getpid(), netFD) + + networks := make(map[string]nettypes.PerNetworkOptions, len(configureNetworks)) + for i, network := range configureNetworks { + networks[network] = nettypes.PerNetworkOptions{ + InterfaceName: fmt.Sprintf("eth%d", i), + } + } + + opts := nettypes.NetworkOptions{ + ContainerID: containerName, + ContainerName: containerName, + Networks: networks, + } + netStatus, err := b.NetworkInterface.Setup(mynetns, nettypes.SetupOptions{NetworkOptions: opts}) + if err != nil { + return nil, nil, err + } + + teardown := func() { + err := b.NetworkInterface.Teardown(mynetns, nettypes.TeardownOptions{NetworkOptions: opts}) + if err != nil { + options.Logger.Errorf("failed to cleanup network: %v", err) + } + } + + return teardown, netStatusToNetResult(netStatus, hostnames), nil +} + +// Create pipes to use for relaying stdio. +func runMakeStdioPipe(uid, gid int) ([][]int, error) { + stdioPipe := make([][]int, 3) + for i := range stdioPipe { + stdioPipe[i] = make([]int, 2) + if err := unix.Pipe(stdioPipe[i]); err != nil { + return nil, fmt.Errorf("creating pipe for container FD %d: %w", i, err) + } + } + if err := unix.Fchown(stdioPipe[unix.Stdin][0], uid, gid); err != nil { + return nil, fmt.Errorf("setting owner of stdin pipe descriptor: %w", err) + } + if err := unix.Fchown(stdioPipe[unix.Stdout][1], uid, gid); err != nil { + return nil, fmt.Errorf("setting owner of stdout pipe descriptor: %w", err) + } + if err := unix.Fchown(stdioPipe[unix.Stderr][1], uid, gid); err != nil { + return nil, fmt.Errorf("setting owner of stderr pipe descriptor: %w", err) + } + return stdioPipe, nil +} + +func setupNamespaces(_ *logrus.Logger, g *generate.Generator, namespaceOptions define.NamespaceOptions, idmapOptions define.IDMappingOptions, policy define.NetworkConfigurationPolicy) (configureNetwork bool, networkString string, configureUTS bool, err error) { + defaultContainerConfig, err := config.Default() + if err != nil { + return false, "", false, fmt.Errorf("failed to get container config: %w", err) + } + + addSysctl := func(prefixes []string) error { + for _, sysctl := range defaultContainerConfig.Sysctls() { + splitn := strings.SplitN(sysctl, "=", 2) + if len(splitn) > 2 { + return fmt.Errorf("sysctl %q defined in containers.conf must be formatted name=value", sysctl) + } + for _, prefix := range prefixes { + if strings.HasPrefix(splitn[0], prefix) { + g.AddLinuxSysctl(splitn[0], splitn[1]) + } + } + } + return nil + } + + // Set namespace options in the container configuration. + configureUserns := false + specifiedNetwork := false + for _, namespaceOption := range namespaceOptions { + switch namespaceOption.Name { + case string(specs.IPCNamespace): + if !namespaceOption.Host { + if err := addSysctl([]string{"fs.mqueue"}); err != nil { + return false, "", false, err + } + } + case string(specs.UserNamespace): + configureUserns = false + if !namespaceOption.Host && namespaceOption.Path == "" { + configureUserns = true + } + case string(specs.NetworkNamespace): + specifiedNetwork = true + configureNetwork = false + if !namespaceOption.Host && (namespaceOption.Path == "" || !filepath.IsAbs(namespaceOption.Path)) { + if namespaceOption.Path != "" && !filepath.IsAbs(namespaceOption.Path) { + networkString = namespaceOption.Path + namespaceOption.Path = "" + } + configureNetwork = (policy != define.NetworkDisabled) + } + case string(specs.UTSNamespace): + configureUTS = false + if !namespaceOption.Host { + if namespaceOption.Path == "" { + configureUTS = true + } + if err := addSysctl([]string{"kernel.hostname", "kernel.domainame"}); err != nil { + return false, "", false, err + } + } + } + if namespaceOption.Host { + if err := g.RemoveLinuxNamespace(namespaceOption.Name); err != nil { + return false, "", false, fmt.Errorf("removing %q namespace for run: %w", namespaceOption.Name, err) + } + } else if err := g.AddOrReplaceLinuxNamespace(namespaceOption.Name, namespaceOption.Path); err != nil { + if namespaceOption.Path == "" { + return false, "", false, fmt.Errorf("adding new %q namespace for run: %w", namespaceOption.Name, err) + } + return false, "", false, fmt.Errorf("adding %q namespace %q for run: %w", namespaceOption.Name, namespaceOption.Path, err) + } + } + + // If we've got mappings, we're going to have to create a user namespace. + if len(idmapOptions.UIDMap) > 0 || len(idmapOptions.GIDMap) > 0 || configureUserns { + if err := g.AddOrReplaceLinuxNamespace(string(specs.UserNamespace), ""); err != nil { + return false, "", false, fmt.Errorf("adding new %q namespace for run: %w", string(specs.UserNamespace), err) + } + hostUidmap, hostGidmap, err := unshare.GetHostIDMappings("") + if err != nil { + return false, "", false, err + } + for _, m := range idmapOptions.UIDMap { + g.AddLinuxUIDMapping(m.HostID, m.ContainerID, m.Size) + } + if len(idmapOptions.UIDMap) == 0 { + for _, m := range hostUidmap { + g.AddLinuxUIDMapping(m.ContainerID, m.ContainerID, m.Size) + } + } + for _, m := range idmapOptions.GIDMap { + g.AddLinuxGIDMapping(m.HostID, m.ContainerID, m.Size) + } + if len(idmapOptions.GIDMap) == 0 { + for _, m := range hostGidmap { + g.AddLinuxGIDMapping(m.ContainerID, m.ContainerID, m.Size) + } + } + if !specifiedNetwork { + if err := g.AddOrReplaceLinuxNamespace(string(specs.NetworkNamespace), ""); err != nil { + return false, "", false, fmt.Errorf("adding new %q namespace for run: %w", string(specs.NetworkNamespace), err) + } + configureNetwork = (policy != define.NetworkDisabled) + } + } else { + if err := g.RemoveLinuxNamespace(string(specs.UserNamespace)); err != nil { + return false, "", false, fmt.Errorf("removing %q namespace for run: %w", string(specs.UserNamespace), err) + } + if !specifiedNetwork { + if err := g.RemoveLinuxNamespace(string(specs.NetworkNamespace)); err != nil { + return false, "", false, fmt.Errorf("removing %q namespace for run: %w", string(specs.NetworkNamespace), err) + } + } + } + if configureNetwork { + if err := addSysctl([]string{"net"}); err != nil { + return false, "", false, err + } + } + return configureNetwork, networkString, configureUTS, nil +} + +func (b *Builder) configureNamespaces(g *generate.Generator, options *RunOptions) (bool, string, error) { + defaultNamespaceOptions, err := DefaultNamespaceOptions() + if err != nil { + return false, "", err + } + + namespaceOptions := defaultNamespaceOptions + namespaceOptions.AddOrReplace(b.NamespaceOptions...) + namespaceOptions.AddOrReplace(options.NamespaceOptions...) + + networkPolicy := options.ConfigureNetwork + // Nothing was specified explicitly so network policy should be inherited from builder + if networkPolicy == NetworkDefault { + networkPolicy = b.ConfigureNetwork + + // If builder policy was NetworkDisabled and + // we want to disable network for this run. + // reset options.ConfigureNetwork to NetworkDisabled + // since it will be treated as source of truth later. + if networkPolicy == NetworkDisabled { + options.ConfigureNetwork = networkPolicy + } + } + if networkPolicy == NetworkDisabled { + namespaceOptions.AddOrReplace(define.NamespaceOptions{{Name: string(specs.NetworkNamespace), Host: false}}...) + } + configureNetwork, networkString, configureUTS, err := setupNamespaces(options.Logger, g, namespaceOptions, b.IDMappingOptions, networkPolicy) + if err != nil { + return false, "", err + } + + if configureUTS { + if options.Hostname != "" { + g.SetHostname(options.Hostname) + } else if b.Hostname() != "" { + g.SetHostname(b.Hostname()) + } else { + hostname := stringid.TruncateID(b.ContainerID) + defConfig, err := config.Default() + if err != nil { + return false, "", fmt.Errorf("failed to get container config: %w", err) + } + if defConfig.Containers.ContainerNameAsHostName { + if mapped := mapContainerNameToHostname(b.Container); mapped != "" { + hostname = mapped + } + } + g.SetHostname(hostname) + } + } else { + g.SetHostname("") + } + + found := false + spec := g.Config + for i := range spec.Process.Env { + if strings.HasPrefix(spec.Process.Env[i], "HOSTNAME=") { + found = true + break + } + } + if !found { + spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("HOSTNAME=%s", spec.Hostname)) + } + + return configureNetwork, networkString, nil +} + +func runSetupBoundFiles(bundlePath string, bindFiles map[string]string) (mounts []specs.Mount) { + for dest, src := range bindFiles { + options := []string{"rbind"} + if strings.HasPrefix(src, bundlePath) { + options = append(options, bind.NoBindOption) + } + mounts = append(mounts, specs.Mount{ + Source: src, + Destination: dest, + Type: "bind", + Options: options, + }) + } + return mounts +} + +func addRlimits(ulimit []string, g *generate.Generator, defaultUlimits []string) error { + var ( + ul *units.Ulimit + err error + // setup rlimits + nofileSet bool + nprocSet bool + ) + + ulimit = append(defaultUlimits, ulimit...) + for _, u := range ulimit { + if ul, err = butil.ParseUlimit(u); err != nil { + return fmt.Errorf("ulimit option %q requires name=SOFT:HARD, failed to be parsed: %w", u, err) + } + + if strings.ToUpper(ul.Name) == "NOFILE" { + nofileSet = true + } + if strings.ToUpper(ul.Name) == "NPROC" { + nprocSet = true + } + g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Hard), uint64(ul.Soft)) + } + if !nofileSet { + lim := define.RLimitDefaultValue + var rlimit unix.Rlimit + if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err == nil { + if lim < rlimit.Max || unshare.IsRootless() { + lim = rlimit.Max + } + } else { + logrus.Warnf("Failed to return RLIMIT_NOFILE ulimit %q", err) + } + g.AddProcessRlimits("RLIMIT_NOFILE", lim, lim) + } + if !nprocSet { + lim := define.RLimitDefaultValue + var rlimit unix.Rlimit + if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err == nil { + if lim < rlimit.Max || unshare.IsRootless() { + lim = rlimit.Max + } + } else { + logrus.Warnf("Failed to return RLIMIT_NPROC ulimit %q", err) + } + g.AddProcessRlimits("RLIMIT_NPROC", lim, lim) + } + + return nil +} + +func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount, idMaps IDMaps) (mounts []specs.Mount, overlayDirs []string, Err error) { + // Make sure the overlay directory is clean before running + containerDir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return nil, nil, fmt.Errorf("looking up container directory for %s: %w", b.ContainerID, err) + } + if err := overlay.CleanupContent(containerDir); err != nil { + return nil, nil, fmt.Errorf("cleaning up overlay content for %s: %w", b.ContainerID, err) + } + + parseMount := func(mountType, host, container string, options []string) (specs.Mount, error) { + var foundrw, foundro, foundz, foundZ, foundO, foundU bool + var rootProp, upperDir, workDir string + for _, opt := range options { + switch opt { + case "rw": + foundrw = true + case "ro": + foundro = true + case "z": + foundz = true + case "Z": + foundZ = true + case "O": + foundO = true + case "U": + foundU = true + case "private", "rprivate", "slave", "rslave", "shared", "rshared": + rootProp = opt + } + + if strings.HasPrefix(opt, "upperdir") { + splitOpt := strings.SplitN(opt, "=", 2) + if len(splitOpt) > 1 { + upperDir = splitOpt[1] + } + } + if strings.HasPrefix(opt, "workdir") { + splitOpt := strings.SplitN(opt, "=", 2) + if len(splitOpt) > 1 { + workDir = splitOpt[1] + } + } + } + if !foundrw && !foundro { + options = append(options, "rw") + } + if foundz { + if err := relabel(host, mountLabel, true); err != nil { + return specs.Mount{}, err + } + options = slices.DeleteFunc(options, func(o string) bool { return o == "z" }) + } + if foundZ { + if err := relabel(host, mountLabel, false); err != nil { + return specs.Mount{}, err + } + options = slices.DeleteFunc(options, func(o string) bool { return o == "Z" }) + } + if foundU { + if err := chown.ChangeHostPathOwnership(host, true, idMaps.processUID, idMaps.processGID); err != nil { + return specs.Mount{}, err + } + options = slices.DeleteFunc(options, func(o string) bool { return o == "U" }) + } + if foundO { + if (upperDir != "" && workDir == "") || (workDir != "" && upperDir == "") { + return specs.Mount{}, errors.New("if specifying upperdir then workdir must be specified or vice versa") + } + + containerDir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return specs.Mount{}, err + } + + contentDir, err := overlay.TempDir(containerDir, idMaps.rootUID, idMaps.rootGID) + if err != nil { + return specs.Mount{}, fmt.Errorf("failed to create TempDir in the %s directory: %w", containerDir, err) + } + + overlayOpts := overlay.Options{ + RootUID: idMaps.rootUID, + RootGID: idMaps.rootGID, + UpperDirOptionFragment: upperDir, + WorkDirOptionFragment: workDir, + GraphOpts: slices.Clone(b.store.GraphOptions()), + } + + overlayMount, err := overlay.MountWithOptions(contentDir, host, container, &overlayOpts) + if err == nil { + overlayDirs = append(overlayDirs, contentDir) + } + + // If chown true, add correct ownership to the overlay temp directories. + if err == nil && foundU { + if err := chown.ChangeHostPathOwnership(contentDir, true, idMaps.processUID, idMaps.processGID); err != nil { + return specs.Mount{}, err + } + } + + return overlayMount, err + } + if rootProp == "" { + options = append(options, "private") + } + if mountType != "tmpfs" { + mountType = "bind" + options = append(options, "rbind") + } + return specs.Mount{ + Destination: container, + Type: mountType, + Source: host, + Options: options, + }, nil + } + + // Bind mount volumes specified for this particular Run() invocation + for _, i := range optionMounts { + logrus.Debugf("setting up mounted volume at %q", i.Destination) + mount, err := parseMount(i.Type, i.Source, i.Destination, i.Options) + if err != nil { + return nil, nil, err + } + mounts = append(mounts, mount) + } + // Bind mount volumes given by the user when the container was created + for _, i := range volumeMounts { + var options []string + spliti := parse.SplitStringWithColonEscape(i) + if len(spliti) > 2 { + options = strings.Split(spliti[2], ",") + } + options = append(options, "rbind") + mount, err := parseMount("bind", spliti[0], spliti[1], options) + if err != nil { + return nil, nil, err + } + mounts = append(mounts, mount) + } + return mounts, overlayDirs, nil +} + +func setupMaskedPaths(g *generate.Generator, opts *define.CommonBuildOptions) { + if slices.Contains(opts.Unmasks, "all") { + return + } +nextMaskedPath: + for _, mp := range append(config.DefaultMaskedPaths(), opts.Masks...) { + for _, unmask := range opts.Unmasks { + match, err := filepath.Match(unmask, mp) + if err != nil { + logrus.Warnf("Invalid unmask pattern %q: %v", unmask, err) + continue + } + if match { + continue nextMaskedPath + } + } + g.AddLinuxMaskedPaths(mp) + } +} + +func setupReadOnlyPaths(g *generate.Generator) { + for _, rp := range config.DefaultReadOnlyPaths { + g.AddLinuxReadonlyPaths(rp) + } +} + +func setupCapAdd(g *generate.Generator, caps ...string) error { + for _, cap := range caps { + if err := g.AddProcessCapabilityBounding(cap); err != nil { + return fmt.Errorf("adding %q to the bounding capability set: %w", cap, err) + } + if err := g.AddProcessCapabilityEffective(cap); err != nil { + return fmt.Errorf("adding %q to the effective capability set: %w", cap, err) + } + if err := g.AddProcessCapabilityPermitted(cap); err != nil { + return fmt.Errorf("adding %q to the permitted capability set: %w", cap, err) + } + } + return nil +} + +func setupCapDrop(g *generate.Generator, caps ...string) error { + for _, cap := range caps { + if err := g.DropProcessCapabilityBounding(cap); err != nil { + return fmt.Errorf("removing %q from the bounding capability set: %w", cap, err) + } + if err := g.DropProcessCapabilityEffective(cap); err != nil { + return fmt.Errorf("removing %q from the effective capability set: %w", cap, err) + } + if err := g.DropProcessCapabilityPermitted(cap); err != nil { + return fmt.Errorf("removing %q from the permitted capability set: %w", cap, err) + } + } + return nil +} + +func setupCapabilities(g *generate.Generator, defaultCapabilities, adds, drops []string) error { + g.ClearProcessCapabilities() + if err := setupCapAdd(g, defaultCapabilities...); err != nil { + return err + } + for _, c := range adds { + if strings.ToLower(c) == "all" { + adds = capabilities.AllCapabilities() + break + } + } + for _, c := range drops { + if strings.ToLower(c) == "all" { + g.ClearProcessCapabilities() + return nil + } + } + if err := setupCapAdd(g, adds...); err != nil { + return err + } + return setupCapDrop(g, drops...) +} + +func addOrReplaceMount(mounts []specs.Mount, mount specs.Mount) []specs.Mount { + for i := range mounts { + if mounts[i].Destination == mount.Destination { + mounts[i] = mount + return mounts + } + } + return append(mounts, mount) +} + +// setupSpecialMountSpecChanges creates special mounts for depending on the namespaces +// logic taken from podman and adapted for buildah +// https://github.com/containers/podman/blob/4ba71f955a944790edda6e007e6d074009d437a7/pkg/specgen/generate/oci.go#L178 +func setupSpecialMountSpecChanges(spec *specs.Spec, shmSize string) ([]specs.Mount, error) { + mounts := spec.Mounts + isRootless := unshare.IsRootless() + isNewUserns := false + isNetns := false + isPidns := false + isIpcns := false + + for _, namespace := range spec.Linux.Namespaces { + switch namespace.Type { + case specs.NetworkNamespace: + isNetns = true + case specs.UserNamespace: + isNewUserns = true + case specs.PIDNamespace: + isPidns = true + case specs.IPCNamespace: + isIpcns = true + } + } + + addCgroup := true + // mount sys when root and no userns or when a new netns is created + canMountSys := (!isRootless && !isNewUserns) || isNetns + if !canMountSys { + addCgroup = false + sys := "/sys" + sysMnt := specs.Mount{ + Destination: sys, + Type: "bind", + Source: sys, + Options: []string{bind.NoBindOption, "rprivate", "nosuid", "noexec", "nodev", "ro", "rbind"}, + } + mounts = addOrReplaceMount(mounts, sysMnt) + } + + gid5Available := true + if isRootless { + _, gids, err := unshare.GetHostIDMappings("") + if err != nil { + return nil, err + } + gid5Available = checkIDsGreaterThan5(gids) + } + if gid5Available && len(spec.Linux.GIDMappings) > 0 { + gid5Available = checkIDsGreaterThan5(spec.Linux.GIDMappings) + } + if !gid5Available { + // If we have no GID mappings, the gid=5 default option would fail, so drop it. + devPts := specs.Mount{ + Destination: "/dev/pts", + Type: "devpts", + Source: "devpts", + Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}, + } + mounts = addOrReplaceMount(mounts, devPts) + } + + isUserns := isNewUserns || isRootless + + if isUserns && !isIpcns { + devMqueue := "/dev/mqueue" + devMqueueMnt := specs.Mount{ + Destination: devMqueue, + Type: "bind", + Source: devMqueue, + Options: []string{bind.NoBindOption, "bind", "nosuid", "noexec", "nodev"}, + } + mounts = addOrReplaceMount(mounts, devMqueueMnt) + } + if isUserns && !isPidns { + proc := "/proc" + procMount := specs.Mount{ + Destination: proc, + Type: "bind", + Source: proc, + Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"}, + } + mounts = addOrReplaceMount(mounts, procMount) + } + + if addCgroup { + cgroupMnt := specs.Mount{ + Destination: "/sys/fs/cgroup", + Type: "cgroup", + Source: "cgroup", + Options: []string{"rprivate", "nosuid", "noexec", "nodev", "relatime", "rw"}, + } + mounts = addOrReplaceMount(mounts, cgroupMnt) + } + + // if userns and host ipc bind mount shm + if isUserns && !isIpcns { + // bind mount /dev/shm when it exists + if err := fileutils.Exists("/dev/shm"); err == nil { + shmMount := specs.Mount{ + Source: "/dev/shm", + Type: "bind", + Destination: "/dev/shm", + Options: []string{bind.NoBindOption, "rbind", "nosuid", "noexec", "nodev"}, + } + mounts = addOrReplaceMount(mounts, shmMount) + } + } else if shmSize != "" { + shmMount := specs.Mount{ + Source: "shm", + Destination: "/dev/shm", + Type: "tmpfs", + Options: []string{"private", "nodev", "noexec", "nosuid", "mode=1777", "size=" + shmSize}, + } + mounts = addOrReplaceMount(mounts, shmMount) + } + + return mounts, nil +} + +func checkIDsGreaterThan5(ids []specs.LinuxIDMapping) bool { + for _, r := range ids { + if r.ContainerID <= 5 && 5 < r.ContainerID+r.Size { + return true + } + } + return false +} + +// Returns a Mount to add to the runtime spec's list of mounts, the ID of an +// image, the path to a mounted filesystem, and the path to an overlay +// filesystem, and an optional lock, or an error. +// +// The caller is expected to, after the command which uses the mount exits, +// clean up the overlay filesystem (if we returned one), unmount the mounted +// filesystem (if we provided the path to its mountpoint) and remove its +// mountpoint, unmount the image (if we mounted one), and release the lock (if +// we took one). +func (b *Builder) getCacheMount(tokens []string, sys *types.SystemContext, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir, tmpDir string) (*specs.Mount, string, string, string, *lockfile.LockFile, error) { + var optionMounts []specs.Mount + optionMount, mountedImage, intermediateMount, overlayMount, targetLock, err := volumes.GetCacheMount(sys, tokens, b.store, b.MountLabel, stageMountPoints, idMaps.uidmap, idMaps.gidmap, workDir, tmpDir) + if err != nil { + return nil, "", "", "", nil, err + } + succeeded := false + defer func() { + if !succeeded { + if overlayMount != "" { + if err := overlay.RemoveTemp(overlayMount); err != nil { + b.Logger.Debug(err.Error()) + } + } + if intermediateMount != "" { + if err := mount.Unmount(intermediateMount); err != nil { + b.Logger.Debugf("unmounting %q: %v", intermediateMount, err) + } + if err := os.Remove(intermediateMount); err != nil { + b.Logger.Debugf("removing should-be-empty directory %q: %v", intermediateMount, err) + } + } + if mountedImage != "" { + if _, err := b.store.UnmountImage(mountedImage, false); err != nil { + b.Logger.Debugf("unmounting image %q: %v", mountedImage, err) + } + } + if targetLock != nil { + targetLock.Unlock() + } + } + }() + optionMounts = append(optionMounts, optionMount) + volumes, overlayDirs, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, idMaps) + if err != nil { + return nil, "", "", "", nil, err + } + if len(overlayDirs) != 0 { + return nil, "", "", "", nil, errors.New("internal error: did not expect a resolved cache mount to use the O flag") + } + succeeded = true + return &volumes[0], mountedImage, intermediateMount, overlayMount, targetLock, nil +} diff --git a/vendor/github.com/containers/buildah/run_unix.go b/vendor/github.com/containers/buildah/run_unix.go new file mode 100644 index 00000000..61e18910 --- /dev/null +++ b/vendor/github.com/containers/buildah/run_unix.go @@ -0,0 +1,43 @@ +//go:build darwin + +package buildah + +import ( + "errors" + + "github.com/containers/buildah/define" + "github.com/opencontainers/runtime-spec/specs-go" + nettypes "go.podman.io/common/libnetwork/types" + "go.podman.io/storage" +) + +// ContainerDevices is an alias for a slice of github.com/opencontainers/runc/libcontainer/configs.Device structures. +type ContainerDevices define.ContainerDevices + +func setChildProcess() error { + return errors.New("function not supported on non-linux systems") +} + +func runUsingRuntimeMain() {} + +func (b *Builder) Run(command []string, options RunOptions) error { + return errors.New("function not supported on non-linux systems") +} + +func DefaultNamespaceOptions() (NamespaceOptions, error) { + options := NamespaceOptions{ + {Name: string(specs.CgroupNamespace), Host: false}, + {Name: string(specs.IPCNamespace), Host: false}, + {Name: string(specs.MountNamespace), Host: false}, + {Name: string(specs.NetworkNamespace), Host: false}, + {Name: string(specs.PIDNamespace), Host: false}, + {Name: string(specs.UserNamespace), Host: false}, + {Name: string(specs.UTSNamespace), Host: false}, + } + return options, nil +} + +// getNetworkInterface creates the network interface +func getNetworkInterface(store storage.Store, cniConfDir, cniPluginPath string) (nettypes.ContainerNetwork, error) { + return nil, nil +} diff --git a/vendor/github.com/containers/buildah/run_unsupported.go b/vendor/github.com/containers/buildah/run_unsupported.go new file mode 100644 index 00000000..5530a465 --- /dev/null +++ b/vendor/github.com/containers/buildah/run_unsupported.go @@ -0,0 +1,29 @@ +//go:build !linux && !darwin && !freebsd + +package buildah + +import ( + "errors" + + nettypes "go.podman.io/common/libnetwork/types" + "go.podman.io/storage" +) + +func setChildProcess() error { + return errors.New("function not supported on non-linux systems") +} + +func runUsingRuntimeMain() {} + +func (b *Builder) Run(command []string, options RunOptions) error { + return errors.New("function not supported on non-linux systems") +} + +func DefaultNamespaceOptions() (NamespaceOptions, error) { + return NamespaceOptions{}, errors.New("function not supported on non-linux systems") +} + +// getNetworkInterface creates the network interface +func getNetworkInterface(store storage.Store, cniConfDir, cniPluginPath string) (nettypes.ContainerNetwork, error) { + return nil, errors.New("function not supported on non-linux systems") +} diff --git a/vendor/github.com/containers/buildah/scan.go b/vendor/github.com/containers/buildah/scan.go new file mode 100644 index 00000000..c01baace --- /dev/null +++ b/vendor/github.com/containers/buildah/scan.go @@ -0,0 +1,302 @@ +package buildah + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal/sbom" + "github.com/mattn/go-shellwords" + rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +func stringSliceReplaceAll(slice []string, replacements map[string]string, important []string) (built []string, replacedAnImportantValue bool) { + built = make([]string, 0, len(slice)) + for i := range slice { + element := slice[i] + for from, to := range replacements { + previous := element + if element = strings.ReplaceAll(previous, from, to); element != previous { + if len(important) == 0 || slices.Contains(important, from) { + replacedAnImportantValue = true + } + } + } + built = append(built, element) + } + return built, replacedAnImportantValue +} + +// sbomScan iterates through the scanning configuration settings, generating +// SBOM files and storing them either in the rootfs or in a local file path. +func (b *Builder) sbomScan(ctx context.Context, options CommitOptions) (imageFiles, localFiles map[string]string, scansDir string, err error) { + // We'll use a temporary per-container directory for this one. + cdir, err := b.store.ContainerDirectory(b.ContainerID) + if err != nil { + return nil, nil, "", err + } + scansDir, err = os.MkdirTemp(cdir, "buildah-scan") + if err != nil { + return nil, nil, "", err + } + defer func() { + if err != nil { + if err := os.RemoveAll(scansDir); err != nil { + logrus.Warnf("removing temporary directory %q: %v", scansDir, err) + } + } + }() + scansSubdir := filepath.Join(scansDir, "scans") + if err = os.Mkdir(scansSubdir, 0o700); err != nil { + return nil, nil, "", err + } + if err = os.Chmod(scansSubdir, 0o777); err != nil { + return nil, nil, "", err + } + + // We may be producing sets of outputs using temporary containers, and + // there's no need to create more than one container for any one + // specific scanner image. + scanners := make(map[string]*Builder) + defer func() { + for _, scanner := range scanners { + scannerID := scanner.ContainerID + if err := scanner.Delete(); err != nil { + logrus.Warnf("removing temporary scanner container %q: %v", scannerID, err) + } + } + }() + + // Just assume that every scanning method will be looking at the rootfs. + rootfs, err := b.Mount(b.MountLabel) + if err != nil { + return nil, nil, "", err + } + defer func(b *Builder) { + if err := b.Unmount(); err != nil { + logrus.Warnf("unmounting temporary scanner container %q: %v", b.ContainerID, err) + } + }(b) + + // Iterate through all of the scanning strategies. + for _, scanSpec := range options.SBOMScanOptions { + // Pull the image and create a container we can run the scanner + // in, unless we've done that already for this scanner image. + scanBuilder, ok := scanners[scanSpec.Image] + if !ok { + builderOptions := BuilderOptions{ + FromImage: scanSpec.Image, + ContainerSuffix: "scanner", + PullPolicy: scanSpec.PullPolicy, + BlobDirectory: options.BlobDirectory, + Logger: b.Logger, + SystemContext: options.SystemContext, + MountLabel: b.MountLabel, + ProcessLabel: b.ProcessLabel, + IDMappingOptions: &b.IDMappingOptions, + } + if scanBuilder, err = NewBuilder(ctx, b.store, builderOptions); err != nil { + return nil, nil, "", fmt.Errorf("creating temporary working container to run scanner: %w", err) + } + scanners[scanSpec.Image] = scanBuilder + } + // Now figure out which commands we need to run. First, try to + // parse a command ourselves, because syft's image (at least) + // doesn't include a shell. Build a slice of command slices. + var commands [][]string + for _, commandSpec := range scanSpec.Commands { + // Start by assuming it's shell -c $whatever. + parsedCommand := []string{"/bin/sh", "-c", commandSpec} + if shell := scanBuilder.Shell(); len(shell) != 0 { + parsedCommand = append(slices.Clone(shell), commandSpec) + } + if !strings.ContainsAny(commandSpec, "<>|") { // An imperfect check for shell redirection being used. + // If we can parse it ourselves, though, prefer to use that result, + // in case the scanner image doesn't include a shell. + if parsed, err := shellwords.Parse(commandSpec); err == nil { + parsedCommand = parsed + } + } + commands = append(commands, parsedCommand) + } + // Set up a list of mounts for the rootfs and whichever context + // directories we're told were used. + const rootfsTargetDir = "/.rootfs" + const scansTargetDir = "/.scans" + const contextsTargetDirPrefix = "/.context" + runMounts := []rspec.Mount{ + // Our temporary directory, read-write. + { + Type: define.TypeBind, + Source: scansSubdir, + Destination: scansTargetDir, + Options: []string{"rw", "z"}, + }, + // The rootfs, read-only. + { + Type: define.TypeBind, + Source: rootfs, + Destination: rootfsTargetDir, + Options: []string{"ro"}, + }, + } + // Each context directory, also read-only. + for i := range scanSpec.ContextDir { + contextMount := rspec.Mount{ + Type: define.TypeBind, + Source: scanSpec.ContextDir[i], + Destination: fmt.Sprintf("%s%d", contextsTargetDirPrefix, i), + Options: []string{"ro"}, + } + runMounts = append(runMounts, contextMount) + } + // Set up run options and mounts one time, and reuse it. + runOptions := RunOptions{ + Logger: b.Logger, + Isolation: b.Isolation, + SystemContext: options.SystemContext, + Mounts: runMounts, + } + // We'll have to do some text substitutions so that we run the + // right commands, in the right order, pointing at the right + // mount points. + var resolvedCommands [][]string + var resultFiles []string + for _, command := range commands { + // Each command gets to produce its own file that we'll + // combine later if there's more than one of them. + contextDirScans := 0 + for i := range scanSpec.ContextDir { + resultFile := filepath.Join(scansTargetDir, fmt.Sprintf("scan%d.json", len(resultFiles))) + // If the command mentions {CONTEXT}... + resolvedCommand, scansContext := stringSliceReplaceAll(command, + map[string]string{ + "{CONTEXT}": fmt.Sprintf("%s%d", contextsTargetDirPrefix, i), + "{OUTPUT}": resultFile, + }, + []string{"{CONTEXT}"}, + ) + if !scansContext { + break + } + // ... resolve the path references and add it to the list of commands. + resolvedCommands = append(resolvedCommands, resolvedCommand) + resultFiles = append(resultFiles, resultFile) + contextDirScans++ + } + if contextDirScans == 0 { + resultFile := filepath.Join(scansTargetDir, fmt.Sprintf("scan%d.json", len(resultFiles))) + // If the command didn't mention {CONTEXT}, but does mention {ROOTFS}... + resolvedCommand, scansRootfs := stringSliceReplaceAll(command, + map[string]string{ + "{ROOTFS}": rootfsTargetDir, + "{OUTPUT}": resultFile, + }, + []string{"{ROOTFS}"}, + ) + // ... resolve the path references and add that to the list of commands. + if scansRootfs { + resolvedCommands = append(resolvedCommands, resolvedCommand) + resultFiles = append(resultFiles, resultFile) + } + } + } + // Run all of the commands, one after the other, producing one + // or more files named "scan%d.json" in our temporary directory. + for _, resolvedCommand := range resolvedCommands { + logrus.Debugf("Running scan command %q", resolvedCommand) + if err = scanBuilder.Run(resolvedCommand, runOptions); err != nil { + return nil, nil, "", fmt.Errorf("running scanning command %v: %w", resolvedCommand, err) + } + } + // Produce the combined output files that we need to create, if there are any. + var sbomResult, purlResult string + switch { + case scanSpec.ImageSBOMOutput != "": + sbomResult = filepath.Join(scansSubdir, filepath.Base(scanSpec.ImageSBOMOutput)) + case scanSpec.SBOMOutput != "": + sbomResult = filepath.Join(scansSubdir, filepath.Base(scanSpec.SBOMOutput)) + default: + sbomResult = filepath.Join(scansSubdir, "sbom-result") + } + switch { + case scanSpec.ImagePURLOutput != "": + purlResult = filepath.Join(scansSubdir, filepath.Base(scanSpec.ImagePURLOutput)) + case scanSpec.PURLOutput != "": + purlResult = filepath.Join(scansSubdir, filepath.Base(scanSpec.PURLOutput)) + default: + purlResult = filepath.Join(scansSubdir, "purl-result") + } + copyFile := func(destination, source string) error { + dst, err := os.Create(destination) + if err != nil { + return err + } + defer dst.Close() + src, err := os.Open(source) + if err != nil { + return err + } + defer src.Close() + if _, err = io.Copy(dst, src); err != nil { + return fmt.Errorf("copying %q to %q: %w", source, destination, err) + } + return nil + } + err = func() error { + for i := range resultFiles { + thisResultFile := filepath.Join(scansSubdir, filepath.Base(resultFiles[i])) + switch i { + case 0: + // Straight-up copy to create the first version of the final output. + if err = copyFile(sbomResult, thisResultFile); err != nil { + return err + } + // This shouldn't change any contents, but lets us generate the purl file. + err = sbom.Merge(scanSpec.MergeStrategy, thisResultFile, sbomResult, purlResult) + default: + // Hopefully we know how to merge information from the new one into the final output. + err = sbom.Merge(scanSpec.MergeStrategy, sbomResult, thisResultFile, purlResult) + } + } + return err + }() + if err != nil { + return nil, nil, "", err + } + // If these files are supposed to be written to the local filesystem, add + // their contents to the map of files we expect our caller to write. + if scanSpec.SBOMOutput != "" || scanSpec.PURLOutput != "" { + if localFiles == nil { + localFiles = make(map[string]string) + } + if scanSpec.SBOMOutput != "" { + localFiles[scanSpec.SBOMOutput] = sbomResult + } + if scanSpec.PURLOutput != "" { + localFiles[scanSpec.PURLOutput] = purlResult + } + } + // If these files are supposed to be written to the image, create a map of + // their contents so that we can either create a layer diff for them (or + // slipstream them into a squashed layer diff) later. + if scanSpec.ImageSBOMOutput != "" || scanSpec.ImagePURLOutput != "" { + if imageFiles == nil { + imageFiles = make(map[string]string) + } + if scanSpec.ImageSBOMOutput != "" { + imageFiles[scanSpec.ImageSBOMOutput] = sbomResult + } + if scanSpec.ImagePURLOutput != "" { + imageFiles[scanSpec.ImagePURLOutput] = purlResult + } + } + } + return imageFiles, localFiles, scansDir, nil +} diff --git a/vendor/github.com/containers/buildah/seccomp.go b/vendor/github.com/containers/buildah/seccomp.go new file mode 100644 index 00000000..f84353ab --- /dev/null +++ b/vendor/github.com/containers/buildah/seccomp.go @@ -0,0 +1,35 @@ +//go:build seccomp && linux + +package buildah + +import ( + "fmt" + "os" + + "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/common/pkg/seccomp" +) + +func setupSeccomp(spec *specs.Spec, seccompProfilePath string) error { + switch seccompProfilePath { + case "unconfined": + spec.Linux.Seccomp = nil + case "": + seccompConfig, err := seccomp.GetDefaultProfile(spec) + if err != nil { + return fmt.Errorf("loading default seccomp profile failed: %w", err) + } + spec.Linux.Seccomp = seccompConfig + default: + seccompProfile, err := os.ReadFile(seccompProfilePath) + if err != nil { + return fmt.Errorf("opening seccomp profile failed: %w", err) + } + seccompConfig, err := seccomp.LoadProfile(string(seccompProfile), spec) + if err != nil { + return fmt.Errorf("loading seccomp profile (%s) failed: %w", seccompProfilePath, err) + } + spec.Linux.Seccomp = seccompConfig + } + return nil +} diff --git a/vendor/github.com/containers/buildah/seccomp_unsupported.go b/vendor/github.com/containers/buildah/seccomp_unsupported.go new file mode 100644 index 00000000..3a6801e8 --- /dev/null +++ b/vendor/github.com/containers/buildah/seccomp_unsupported.go @@ -0,0 +1,15 @@ +//go:build !seccomp || !linux + +package buildah + +import ( + "github.com/opencontainers/runtime-spec/specs-go" +) + +func setupSeccomp(spec *specs.Spec, seccompProfilePath string) error { + if spec.Linux != nil { + // runtime-tools may have supplied us with a default filter + spec.Linux.Seccomp = nil + } + return nil +} diff --git a/vendor/github.com/containers/buildah/selinux.go b/vendor/github.com/containers/buildah/selinux.go new file mode 100644 index 00000000..bd28118c --- /dev/null +++ b/vendor/github.com/containers/buildah/selinux.go @@ -0,0 +1,41 @@ +//go:build linux + +package buildah + +import ( + "errors" + "fmt" + "os" + + "github.com/opencontainers/runtime-tools/generate" + selinux "github.com/opencontainers/selinux/go-selinux" +) + +func selinuxGetEnabled() bool { + return selinux.GetEnabled() +} + +func setupSelinux(g *generate.Generator, processLabel, mountLabel string) { + if processLabel != "" && selinux.GetEnabled() { + g.SetProcessSelinuxLabel(processLabel) + g.SetLinuxMountLabel(mountLabel) + } +} + +func runLabelStdioPipes(stdioPipe [][]int, processLabel, mountLabel string) error { + if !selinuxGetEnabled() || processLabel == "" || mountLabel == "" { + // SELinux is completely disabled, or we're not doing anything at all with labeling + return nil + } + pipeContext, err := selinux.ComputeCreateContext(processLabel, mountLabel, "fifo_file") + if err != nil { + return fmt.Errorf("computing file creation context for pipes: %w", err) + } + for i := range stdioPipe { + pipeFdName := fmt.Sprintf("/proc/self/fd/%d", stdioPipe[i][0]) + if err := selinux.SetFileLabel(pipeFdName, pipeContext); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("setting file label on %q: %w", pipeFdName, err) + } + } + return nil +} diff --git a/vendor/github.com/containers/buildah/selinux_unsupported.go b/vendor/github.com/containers/buildah/selinux_unsupported.go new file mode 100644 index 00000000..4519935b --- /dev/null +++ b/vendor/github.com/containers/buildah/selinux_unsupported.go @@ -0,0 +1,18 @@ +//go:build !linux + +package buildah + +import ( + "github.com/opencontainers/runtime-tools/generate" +) + +func selinuxGetEnabled() bool { + return false +} + +func setupSelinux(g *generate.Generator, processLabel, mountLabel string) { +} + +func runLabelStdioPipes(stdioPipe [][]int, processLabel, mountLabel string) error { + return nil +} diff --git a/vendor/github.com/containers/buildah/troubleshooting.md b/vendor/github.com/containers/buildah/troubleshooting.md new file mode 100644 index 00000000..1299b354 --- /dev/null +++ b/vendor/github.com/containers/buildah/troubleshooting.md @@ -0,0 +1,158 @@ +![buildah logo](https://cdn.rawgit.com/containers/buildah/main/logos/buildah-logo_large.png) + +# Troubleshooting + +## A list of common issues and solutions for Buildah + +--- +### 1) No such image + +When doing a `buildah pull` or `buildah build` command and a "common" image can not be pulled, +it is likely that the `/etc/containers/registries.conf` file is either not installed or possibly +misconfigured. This issue might also indicate that other required files as listed in the +[Configuration Files](https://github.com/containers/buildah/blob/main/install.md#configuration-files) +section of the Installation Instructions are also not installed. + +#### Symptom +```console +$ sudo buildah build -f Dockerfile . +STEP 1: FROM alpine +error creating build container: 2 errors occurred: + +* Error determining manifest MIME type for docker://localhost/alpine:latest: pinging docker registry returned: Get https://localhost/v2/: dial tcp [::1]:443: connect: connection refused +* Error determining manifest MIME type for docker://registry.access.redhat.com/alpine:latest: Error reading manifest latest in registry.access.redhat.com/alpine: unknown: Not Found +error building: error creating build container: no such image "alpine" in registry: image not known +``` + +#### Solution + + * Verify that the `/etc/containers/registries.conf` file exists. If not, verify that the containers-common package is installed. + * Verify that the entries in the `[registries.search]` section of the /etc/containers/registries file are valid and reachable. + * Verify that the image you requested is either fully qualified, or that it exists on one of your search registries. + * Verify that the image is public or that you have logged in to at least one search registry which contains the private image. + * Verify that the other required [Configuration Files](https://github.com/containers/buildah/blob/main/install.md#configuration-files) are installed. + +--- +### 2) http: server gave HTTP response to HTTPS client + +When doing a Buildah command such as `build`, `commit`, `from`, or `push` to a registry, +tls verification is turned on by default. If authentication is not used with +those commands, this error can occur. + +#### Symptom +```console +# buildah push alpine docker://localhost:5000/myalpine:latest +Getting image source signatures +Get https://localhost:5000/v2/: http: server gave HTTP response to HTTPS client +``` + +#### Solution + +By default tls verification is turned on when communicating to registries from +Buildah. If the registry does not require authentication the Buildah commands +such as `build`, `commit`, `from` and `pull` will fail unless tls verification is turned +off using the `--tls-verify` option. **NOTE:** It is not at all recommended to +communicate with a registry and not use tls verification. + + * Turn off tls verification by passing false to the tls-verification option. + * I.e. `buildah push --tls-verify=false alpine docker://localhost:5000/myalpine:latest` + +--- +### 3) `buildah run` command fails with pipe or output redirection + +When doing a `buildah run` command while using a pipe ('|') or output redirection ('>>'), +the command will fail, often times with a `command not found` type of error. + +#### Symptom +When executing a `buildah run` command with a pipe or output redirection such as the +following commands: + +```console +# buildah run $whalecontainer /usr/games/fortune -a | cowsay +# buildah run $newcontainer echo "daemon off;" >> /etc/nginx/nginx.conf +# buildah run $newcontainer echo "nginx on Fedora" > /usr/share/nginx/html/index.html +``` +the `buildah run` command will not complete and an error will be raised. + +#### Solution +There are two solutions to this problem. The +[`podman run`](https://github.com/containers/podman/blob/main/docs/podman-run.1.md) +command can be used in place of `buildah run`. To still use `buildah run`, surround +the command with single quotes and use `bash -c`. The previous examples would be +changed to: + +```console +# buildah run $whalecontainer bash -c '/usr/games/fortune -a | cowsay' +# buildah run $newcontainer bash -c 'echo "daemon off;" >> /etc/nginx/nginx.conf' +# buildah run $newcontainer bash -c 'echo "nginx on Fedora" > /usr/share/nginx/html/index.html' +``` + +--- +### 4) `buildah push alpine oci:~/myalpine:latest` fails with lstat error + +When doing a `buildah push` command and the target image has a tilde (`~`) character +in it, an lstat error will be raised stating there is no such file or directory. +This is expected behavior for shell expansion of the tilde character as it is only +expanded at the start of a word. This behavior is documented +[here](https://www.gnu.org/software/libc/manual/html_node/Tilde-Expansion.html). + +#### Symptom +```console +$ sudo pull alpine +$ sudo buildah push alpine oci:~/myalpine:latest +lstat /home/myusername/~: no such file or directory +``` + +#### Solution + + * Replace `~` with `$HOME` or the fully specified directory `/home/myusername`. + * `$ sudo buildah push alpine oci:${HOME}/myalpine:latest` + + +--- +### 5) Rootless buildah build fails EPERM on NFS: + +NFS enforces file creation on different UIDs on the server side and does not understand user namespace, which rootless Podman requires. When a container root process like YUM attempts to create a file owned by a different UID, NFS Server denies the creation. NFS is also a problem for the file locks when the storage is on it. Other distributed file systems (for example: Lustre, Spectrum Scale, the General Parallel File System (GPFS)) are also not supported when running in rootless mode as these file systems do not understand user namespace. + +#### Symptom +```console +$ buildah build . +ERRO[0014] Error while applying layer: ApplyLayer exit status 1 stdout: stderr: open /root/.bash_logout: permission denied +error creating build container: Error committing the finished image: error adding layer with blob "sha256:a02a4930cb5d36f3290eb84f4bfa30668ef2e9fe3a1fb73ec015fc58b9958b17": ApplyLayer exit status 1 stdout: stderr: open /root/.bash_logout: permission denied +``` + +#### Solution +Choose one of the following: + * Setup containers/storage in a different directory, not on an NFS share. + * Otherwise just run buildah as root, via `sudo buildah` +--- +### 6) Rootless buildah build fails when using OverlayFS: + +The Overlay file system (OverlayFS) requires the ability to call the `mknod` command when creating whiteout files +when extracting an image. However, a rootless user does not have the privileges to use `mknod` in this capacity. + +#### Symptom +```console +buildah build --storage-driver overlay . +STEP 1: FROM docker.io/ubuntu:xenial +Getting image source signatures +Copying blob edf72af6d627 done +Copying blob 3e4f86211d23 done +Copying blob 8d3eac894db4 done +Copying blob f7277927d38a done +Copying config 5e13f8dd4c done +Writing manifest to image destination +Storing signatures +Error: error creating build container: Error committing the finished image: error adding layer with blob "sha256:8d3eac894db4dc4154377ad28643dfe6625ff0e54bcfa63e0d04921f1a8ef7f8": Error processing tar file(exit status 1): operation not permitted +$ buildah build . +ERRO[0014] Error while applying layer: ApplyLayer exit status 1 stdout: stderr: open /root/.bash_logout: permission denied +error creating build container: Error committing the finished image: error adding layer with blob "sha256:a02a4930cb5d36f3290eb84f4bfa30668ef2e9fe3a1fb73ec015fc58b9958b17": ApplyLayer exit status 1 stdout: stderr: open /root/.bash_logout: permission denied +``` + +#### Solution +Choose one of the following: + * Complete the build operation as a privileged user. + * Install and configure fuse-overlayfs. + * Install the fuse-overlayfs package for your Linux Distribution. + * Add `mount_program = "/usr/bin/fuse-overlayfs"` under `[storage.options]` in your `~/.config/containers/storage.conf` file. +--- diff --git a/vendor/github.com/containers/buildah/unmount.go b/vendor/github.com/containers/buildah/unmount.go new file mode 100644 index 00000000..66c8ce41 --- /dev/null +++ b/vendor/github.com/containers/buildah/unmount.go @@ -0,0 +1,17 @@ +package buildah + +import "fmt" + +// Unmount unmounts a build container. +func (b *Builder) Unmount() error { + _, err := b.store.Unmount(b.ContainerID, false) + if err != nil { + return fmt.Errorf("unmounting build container %q: %w", b.ContainerID, err) + } + b.MountPoint = "" + err = b.Save() + if err != nil { + return fmt.Errorf("saving updated state for build container %q: %w", b.ContainerID, err) + } + return nil +} diff --git a/vendor/github.com/containers/buildah/util.go b/vendor/github.com/containers/buildah/util.go new file mode 100644 index 00000000..10cd6f59 --- /dev/null +++ b/vendor/github.com/containers/buildah/util.go @@ -0,0 +1,211 @@ +package buildah + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sync" + + "github.com/containers/buildah/copier" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/pkg/sysregistriesv2" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/reexec" +) + +// InitReexec is a wrapper for reexec.Init(). It should be called at +// the start of main(), and if it returns true, main() should return +// immediately. +func InitReexec() bool { + return reexec.Init() +} + +func copyHistory(history []v1.History) []v1.History { + if len(history) == 0 { + return nil + } + h := make([]v1.History, 0, len(history)) + for _, entry := range history { + created := entry.Created + if created != nil { + timestamp := *created + created = ×tamp + } + h = append(h, v1.History{ + Created: created, + CreatedBy: entry.CreatedBy, + Author: entry.Author, + Comment: entry.Comment, + EmptyLayer: entry.EmptyLayer, + }) + } + return h +} + +func convertStorageIDMaps(UIDMap, GIDMap []idtools.IDMap) ([]rspec.LinuxIDMapping, []rspec.LinuxIDMapping) { + uidmap := make([]rspec.LinuxIDMapping, 0, len(UIDMap)) + gidmap := make([]rspec.LinuxIDMapping, 0, len(GIDMap)) + for _, m := range UIDMap { + uidmap = append(uidmap, rspec.LinuxIDMapping{ + HostID: uint32(m.HostID), + ContainerID: uint32(m.ContainerID), + Size: uint32(m.Size), + }) + } + for _, m := range GIDMap { + gidmap = append(gidmap, rspec.LinuxIDMapping{ + HostID: uint32(m.HostID), + ContainerID: uint32(m.ContainerID), + Size: uint32(m.Size), + }) + } + return uidmap, gidmap +} + +func convertRuntimeIDMaps(UIDMap, GIDMap []rspec.LinuxIDMapping) ([]idtools.IDMap, []idtools.IDMap) { + uidmap := make([]idtools.IDMap, 0, len(UIDMap)) + gidmap := make([]idtools.IDMap, 0, len(GIDMap)) + for _, m := range UIDMap { + uidmap = append(uidmap, idtools.IDMap{ + HostID: int(m.HostID), + ContainerID: int(m.ContainerID), + Size: int(m.Size), + }) + } + for _, m := range GIDMap { + gidmap = append(gidmap, idtools.IDMap{ + HostID: int(m.HostID), + ContainerID: int(m.ContainerID), + Size: int(m.Size), + }) + } + return uidmap, gidmap +} + +// isRegistryBlocked checks if the named registry is marked as blocked +func isRegistryBlocked(registry string, sc *types.SystemContext) (bool, error) { + reginfo, err := sysregistriesv2.FindRegistry(sc, registry) + if err != nil { + return false, fmt.Errorf("unable to parse the registries configuration (%s): %w", sysregistriesv2.ConfigPath(sc), err) + } + if reginfo != nil { + if reginfo.Blocked { + logrus.Debugf("registry %q is marked as blocked in registries configuration %q", registry, sysregistriesv2.ConfigPath(sc)) + } else { + logrus.Debugf("registry %q is not marked as blocked in registries configuration %q", registry, sysregistriesv2.ConfigPath(sc)) + } + return reginfo.Blocked, nil + } + logrus.Debugf("registry %q is not listed in registries configuration %q, assuming it's not blocked", registry, sysregistriesv2.ConfigPath(sc)) + return false, nil +} + +// isReferenceSomething checks if the registry part of a reference is insecure or blocked +func isReferenceSomething(ref types.ImageReference, sc *types.SystemContext, what func(string, *types.SystemContext) (bool, error)) (bool, error) { + if ref != nil { + if named := ref.DockerReference(); named != nil { + if domain := reference.Domain(named); domain != "" { + return what(domain, sc) + } + } + } + return false, nil +} + +// isReferenceBlocked checks if the registry part of a reference is blocked +func isReferenceBlocked(ref types.ImageReference, sc *types.SystemContext) (bool, error) { + if ref != nil && ref.Transport() != nil { + switch ref.Transport().Name() { + case "docker": + return isReferenceSomething(ref, sc, isRegistryBlocked) + } + } + return false, nil +} + +// ReserveSELinuxLabels reads containers storage and reserves SELinux contexts +// which are already being used by buildah containers. +func ReserveSELinuxLabels(store storage.Store, id string) error { + if selinuxGetEnabled() { + containers, err := store.Containers() + if err != nil { + return fmt.Errorf("getting list of containers: %w", err) + } + + for _, c := range containers { + if id == c.ID { + continue + } + b, err := OpenBuilder(store, c.ID) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // Ignore not exist errors since containers probably created by other tool + // TODO, we need to read other containers json data to reserve their SELinux labels + continue + } + return err + } + // Prevent different containers from using same MCS label + selinux.ReserveLabel(b.ProcessLabel) + } + } + return nil +} + +// IsContainer identifies if the specified container id is a buildah container +// in the specified store. +func IsContainer(id string, store storage.Store) (bool, error) { + cdir, err := store.ContainerDirectory(id) + if err != nil { + return false, err + } + // Assuming that if the stateFile exists, that this is a Buildah + // container. + if _, err = os.Stat(filepath.Join(cdir, stateFile)); err != nil { + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + return false, err + } + return true, nil +} + +// Copy content from the directory "src" to the directory "dest", ensuring that +// content from outside of "root" (which is a parent of "src" or "src" itself) +// isn't read. +func extractWithTar(root, src, dest string) error { + var getErr, putErr error + var wg sync.WaitGroup + + pipeReader, pipeWriter := io.Pipe() + + wg.Add(1) + go func() { + getErr = copier.Get(root, src, copier.GetOptions{}, []string{"."}, pipeWriter) + pipeWriter.Close() + wg.Done() + }() + wg.Add(1) + go func() { + putErr = copier.Put(dest, dest, copier.PutOptions{}, pipeReader) + pipeReader.Close() + wg.Done() + }() + wg.Wait() + + if getErr != nil { + return fmt.Errorf("reading %q: %w", src, getErr) + } + if putErr != nil { + return fmt.Errorf("copying contents of %q to %q: %w", src, dest, putErr) + } + return nil +} diff --git a/vendor/github.com/containers/buildah/util/types.go b/vendor/github.com/containers/buildah/util/types.go new file mode 100644 index 00000000..91c9ace1 --- /dev/null +++ b/vendor/github.com/containers/buildah/util/types.go @@ -0,0 +1,20 @@ +package util + +import ( + "github.com/containers/buildah/define" +) + +const ( + // DefaultRuntime if containers.conf fails. + DefaultRuntime = define.DefaultRuntime +) + +var ( + // Deprecated: DefaultCapabilities values should be retrieved from + // github.com/containers/common/pkg/config + DefaultCapabilities = define.DefaultCapabilities //nolint + + // Deprecated: DefaultNetworkSysctl values should be retrieved from + // github.com/containers/common/pkg/config + DefaultNetworkSysctl = define.DefaultNetworkSysctl //nolint +) diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go new file mode 100644 index 00000000..0b2339e1 --- /dev/null +++ b/vendor/github.com/containers/buildah/util/util.go @@ -0,0 +1,461 @@ +package util + +import ( + "errors" + "fmt" + "io" + "net/url" + "os" + "path/filepath" + "slices" + "sort" + "strings" + "syscall" + + "github.com/containers/buildah/define" + "github.com/docker/distribution/registry/api/errcode" + "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + "go.podman.io/common/pkg/config" + "go.podman.io/image/v5/docker/reference" + "go.podman.io/image/v5/pkg/shortnames" + "go.podman.io/image/v5/signature" + "go.podman.io/image/v5/transports/alltransports" + "go.podman.io/image/v5/types" + "go.podman.io/storage" +) + +const ( + minimumTruncatedIDLength = 3 + // DefaultTransport is a prefix that we apply to an image name if we + // can't find one in the local Store, in order to generate a source + // reference for the image that we can then copy to the local Store. + DefaultTransport = "docker://" +) + +// RegistryDefaultPathPrefix contains a per-registry listing of default prefixes +// to prepend to image names that only contain a single path component. +var RegistryDefaultPathPrefix = map[string]string{ + "index.docker.io": "library", + "docker.io": "library", +} + +// StringInSlice is deprecated, use slices.Contains +func StringInSlice(s string, slice []string) bool { + return slices.Contains(slice, s) +} + +// resolveName checks if name is a valid image name, and if that name doesn't +// include a domain portion, returns a list of the names which it might +// correspond to in the set of configured registries, and the transport used to +// pull the image. +// +// The returned image names never include a transport: prefix, and if transport != "", +// (transport, image) should be a valid input to alltransports.ParseImageName. +// transport == "" indicates that image that already exists in a local storage, +// and the name is valid for store.Image() / storage.Transport.ParseStoreReference(). +// +// NOTE: The "list of search registries is empty" check does not count blocked registries, +// and neither the implied "localhost" nor a possible firstRegistry are counted +func resolveName(name string, sc *types.SystemContext, store storage.Store) ([]string, string, error) { + if name == "" { + return nil, "", nil + } + + // Maybe it's a truncated image ID. Don't prepend a registry name, then. + if len(name) >= minimumTruncatedIDLength { + if img, err := store.Image(name); err == nil && img != nil && strings.HasPrefix(img.ID, name) { + // It's a truncated version of the ID of an image that's present in local storage; + // we need only expand the ID. + return []string{img.ID}, "", nil + } + } + // If we're referring to an image by digest, it *must* be local and we + // should not have any fall through/back logic. + if strings.HasPrefix(name, "sha256:") { + d, err := digest.Parse(name) + if err != nil { + return nil, "", err + } + img, err := store.Image(d.Encoded()) + if err != nil { + return nil, "", err + } + return []string{img.ID}, "", nil + } + + // Transports are not supported for local image look ups. + srcRef, err := alltransports.ParseImageName(name) + if err == nil { + return []string{srcRef.StringWithinTransport()}, srcRef.Transport().Name(), nil + } + + var candidates []string + // Local short-name resolution. + namedCandidates, err := shortnames.ResolveLocally(sc, name) + if err != nil { + return nil, "", err + } + for _, named := range namedCandidates { + candidates = append(candidates, named.String()) + } + + return candidates, DefaultTransport, nil +} + +// ExpandNames takes unqualified names, parses them as image names, and returns +// the fully expanded result, including a tag. Names which don't include a registry +// name will be marked for the most-preferred registry (i.e., the first one in our +// configuration). +func ExpandNames(names []string, systemContext *types.SystemContext, store storage.Store) ([]string, error) { + expanded := make([]string, 0, len(names)) + for _, n := range names { + var name reference.Named + nameList, _, err := resolveName(n, systemContext, store) + if err != nil { + return nil, fmt.Errorf("parsing name %q: %w", n, err) + } + if len(nameList) == 0 { + named, err := reference.ParseNormalizedNamed(n) + if err != nil { + return nil, fmt.Errorf("parsing name %q: %w", n, err) + } + name = named + } else { + named, err := reference.ParseNormalizedNamed(nameList[0]) + if err != nil { + return nil, fmt.Errorf("parsing name %q: %w", nameList[0], err) + } + name = named + } + name = reference.TagNameOnly(name) + expanded = append(expanded, name.String()) + } + return expanded, nil +} + +// FindImage locates the locally-stored image which corresponds to a given +// name. Please note that the second argument has been deprecated and has no +// effect anymore. +func FindImage(store storage.Store, _ string, systemContext *types.SystemContext, image string) (types.ImageReference, *storage.Image, error) { + runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) + if err != nil { + return nil, nil, err + } + + localImage, _, err := runtime.LookupImage(image, nil) + if err != nil { + return nil, nil, err + } + ref, err := localImage.StorageReference() + if err != nil { + return nil, nil, err + } + + return ref, localImage.StorageImage(), nil +} + +// resolveNameToReferences tries to create a list of possible references +// (including their transports) from the provided image name. +func ResolveNameToReferences( + store storage.Store, + systemContext *types.SystemContext, + image string, +) (refs []types.ImageReference, err error) { + names, transport, err := resolveName(image, systemContext, store) + if err != nil { + return nil, fmt.Errorf("parsing name %q: %w", image, err) + } + + if transport != DefaultTransport { + transport += ":" + } + + for _, name := range names { + ref, err := alltransports.ParseImageName(transport + name) + if err != nil { + logrus.Debugf("error parsing reference to image %q: %v", name, err) + continue + } + refs = append(refs, ref) + } + if len(refs) == 0 { + return nil, fmt.Errorf("locating images with names %v", names) + } + return refs, nil +} + +// AddImageNames adds the specified names to the specified image. Please note +// that the second argument has been deprecated and has no effect anymore. +func AddImageNames(store storage.Store, _ string, systemContext *types.SystemContext, image *storage.Image, addNames []string) error { + runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) + if err != nil { + return err + } + + localImage, _, err := runtime.LookupImage(image.ID, nil) + if err != nil { + return err + } + + for _, tag := range addNames { + if err := localImage.Tag(tag); err != nil { + return fmt.Errorf("tagging image %s: %w", image.ID, err) + } + } + + return nil +} + +// GetFailureCause checks the type of the error "err" and returns a new +// error message that reflects the reason of the failure. +// In case err type is not a familiar one the error "defaultError" is returned. +func GetFailureCause(err, defaultError error) error { + switch nErr := err.(type) { + case errcode.Errors: + return err + case errcode.Error, *url.Error: + return nErr + default: + return defaultError + } +} + +// WriteError writes `lastError` into `w` if not nil and return the next error `err` +func WriteError(w io.Writer, err error, lastError error) error { + if lastError != nil { + fmt.Fprintln(w, lastError) + } + return err +} + +// Runtime is the default command to use to run the container. +func Runtime() string { + runtime := os.Getenv("BUILDAH_RUNTIME") + if runtime != "" { + return runtime + } + + conf, err := config.Default() + if err != nil { + logrus.Warnf("Error loading default container config when searching for local runtime: %v", err) + return define.DefaultRuntime + } + return conf.Engine.OCIRuntime +} + +// GetContainerIDs uses ID mappings to compute the container-level IDs that will +// correspond to a UID/GID pair on the host. +func GetContainerIDs(uidmap, gidmap []specs.LinuxIDMapping, uid, gid uint32) (uint32, uint32, error) { + uidMapped := true + for _, m := range uidmap { + uidMapped = false + if uid >= m.HostID && uid < m.HostID+m.Size { + uid = (uid - m.HostID) + m.ContainerID + uidMapped = true + break + } + } + if !uidMapped { + return 0, 0, fmt.Errorf("container uses ID mappings (%#v), but doesn't map UID %d", uidmap, uid) + } + gidMapped := true + for _, m := range gidmap { + gidMapped = false + if gid >= m.HostID && gid < m.HostID+m.Size { + gid = (gid - m.HostID) + m.ContainerID + gidMapped = true + break + } + } + if !gidMapped { + return 0, 0, fmt.Errorf("container uses ID mappings (%#v), but doesn't map GID %d", gidmap, gid) + } + return uid, gid, nil +} + +// GetHostIDs uses ID mappings to compute the host-level IDs that will +// correspond to a UID/GID pair in the container. +func GetHostIDs(uidmap, gidmap []specs.LinuxIDMapping, uid, gid uint32) (uint32, uint32, error) { + uidMapped := true + for _, m := range uidmap { + uidMapped = false + if uid >= m.ContainerID && uid < m.ContainerID+m.Size { + uid = (uid - m.ContainerID) + m.HostID + uidMapped = true + break + } + } + if !uidMapped { + return 0, 0, fmt.Errorf("container uses ID mappings (%#v), but doesn't map UID %d", uidmap, uid) + } + gidMapped := true + for _, m := range gidmap { + gidMapped = false + if gid >= m.ContainerID && gid < m.ContainerID+m.Size { + gid = (gid - m.ContainerID) + m.HostID + gidMapped = true + break + } + } + if !gidMapped { + return 0, 0, fmt.Errorf("container uses ID mappings (%#v), but doesn't map GID %d", gidmap, gid) + } + return uid, gid, nil +} + +// GetHostRootIDs uses ID mappings in spec to compute the host-level IDs that will +// correspond to UID/GID 0/0 in the container. +func GetHostRootIDs(spec *specs.Spec) (uint32, uint32, error) { + if spec == nil || spec.Linux == nil { + return 0, 0, nil + } + return GetHostIDs(spec.Linux.UIDMappings, spec.Linux.GIDMappings, 0, 0) +} + +// GetPolicyContext sets up, initializes and returns a new context for the specified policy +func GetPolicyContext(ctx *types.SystemContext) (*signature.PolicyContext, error) { + policy, err := signature.DefaultPolicy(ctx) + if err != nil { + return nil, err + } + + policyContext, err := signature.NewPolicyContext(policy) + if err != nil { + return nil, err + } + return policyContext, nil +} + +// logIfNotErrno logs the error message unless err is either nil or one of the +// listed syscall.Errno values. It returns true if it logged an error. +func logIfNotErrno(err error, what string, ignores ...syscall.Errno) (logged bool) { + if err == nil { + return false + } + if errno, ok := err.(syscall.Errno); ok && slices.Contains(ignores, errno) { + return false + } + logrus.Error(what) + return true +} + +// LogIfNotRetryable logs "what" if err is set and is not an EINTR or EAGAIN +// syscall.Errno. Returns "true" if we can continue. +func LogIfNotRetryable(err error, what string) (retry bool) { + return !logIfNotErrno(err, what, syscall.EINTR, syscall.EAGAIN) +} + +// LogIfUnexpectedWhileDraining logs "what" if err is set and is not an EINTR +// or EAGAIN or EIO syscall.Errno. +func LogIfUnexpectedWhileDraining(err error, what string) { + logIfNotErrno(err, what, syscall.EINTR, syscall.EAGAIN, syscall.EIO) +} + +// TruncateString trims the given string to the provided maximum amount of +// characters and shortens it with `...`. +func TruncateString(str string, to int) string { + newStr := str + if len(str) > to { + const tr = "..." + if to > len(tr) { + to -= len(tr) + } + newStr = str[0:to] + tr + } + return newStr +} + +// fileExistsAndNotADir - Check to see if a file exists +// and that it is not a directory. +func fileExistsAndNotADir(path string) (bool, error) { + file, err := os.Stat(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + return false, err + } + return !file.IsDir(), nil +} + +// FindLocalRuntime find the local runtime of the +// system searching through the config file for +// possible locations. +func FindLocalRuntime(runtime string) string { + var localRuntime string + conf, err := config.Default() + if err != nil { + logrus.Debugf("Error loading container config when searching for local runtime.") + return localRuntime + } + for _, val := range conf.Engine.OCIRuntimes[runtime] { + exists, err := fileExistsAndNotADir(val) + if err != nil { + logrus.Errorf("Failed to determine if file exists and is not a directory: %v", err) + } + if exists { + localRuntime = val + break + } + } + return localRuntime +} + +// MergeEnv merges two lists of environment variables, avoiding duplicates. +func MergeEnv(defaults, overrides []string) []string { + s := make([]string, 0, len(defaults)+len(overrides)) + index := make(map[string]int) + for _, envSpec := range append(defaults, overrides...) { + envVar := strings.SplitN(envSpec, "=", 2) + if i, ok := index[envVar[0]]; ok { + s[i] = envSpec + continue + } + s = append(s, envSpec) + index[envVar[0]] = len(s) - 1 + } + return s +} + +type byDestination []specs.Mount + +func (m byDestination) Len() int { + return len(m) +} + +func (m byDestination) Less(i, j int) bool { + iparts, jparts := m.parts(i), m.parts(j) + switch { + case iparts < jparts: + return true + case iparts > jparts: + return false + } + return filepath.Clean(m[i].Destination) < filepath.Clean(m[j].Destination) +} + +func (m byDestination) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} + +func (m byDestination) parts(i int) int { + return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator)) +} + +func SortMounts(m []specs.Mount) []specs.Mount { + sort.Stable(byDestination(m)) + return m +} + +func VerifyTagName(imageSpec string) (types.ImageReference, error) { + ref, err := alltransports.ParseImageName(imageSpec) + if err != nil { + if ref, err = alltransports.ParseImageName(DefaultTransport + imageSpec); err != nil { + return nil, err + } + } + return ref, nil +} diff --git a/vendor/github.com/containers/buildah/util/util_unix.go b/vendor/github.com/containers/buildah/util/util_unix.go new file mode 100644 index 00000000..507e4892 --- /dev/null +++ b/vendor/github.com/containers/buildah/util/util_unix.go @@ -0,0 +1,16 @@ +//go:build linux || darwin || freebsd || netbsd + +package util + +import ( + "os" + "syscall" +) + +func UID(st os.FileInfo) int { + return int(st.Sys().(*syscall.Stat_t).Uid) +} + +func GID(st os.FileInfo) int { + return int(st.Sys().(*syscall.Stat_t).Gid) +} diff --git a/vendor/github.com/containers/buildah/util/util_unsupported.go b/vendor/github.com/containers/buildah/util/util_unsupported.go new file mode 100644 index 00000000..36d0664d --- /dev/null +++ b/vendor/github.com/containers/buildah/util/util_unsupported.go @@ -0,0 +1,8 @@ +//go:build !linux + +package util + +// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode. +func IsCgroup2UnifiedMode() (bool, error) { + return false, nil +} diff --git a/vendor/github.com/containers/buildah/util/util_windows.go b/vendor/github.com/containers/buildah/util/util_windows.go new file mode 100644 index 00000000..0a8fa594 --- /dev/null +++ b/vendor/github.com/containers/buildah/util/util_windows.go @@ -0,0 +1,15 @@ +//go:build !linux && !darwin + +package util + +import ( + "os" +) + +func UID(st os.FileInfo) int { + return 0 +} + +func GID(st os.FileInfo) int { + return 0 +} diff --git a/vendor/github.com/containers/conmon/LICENSE b/vendor/github.com/containers/conmon/LICENSE new file mode 100644 index 00000000..28df615c --- /dev/null +++ b/vendor/github.com/containers/conmon/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2018-2019 github.com/containers authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/conmon/runner/config/config.go b/vendor/github.com/containers/conmon/runner/config/config.go new file mode 100644 index 00000000..cb70e932 --- /dev/null +++ b/vendor/github.com/containers/conmon/runner/config/config.go @@ -0,0 +1,19 @@ +package config + +const ( + // BufSize is the size of buffers passed in to sockets + BufSize = 8192 + // ConnSockBufSize is the size of the socket used for + // to attach to the container + ConnSockBufSize = 32768 + // WinResizeEvent is the event code the caller program will + // send along the ctrl fd to signal conmon to resize + // the pty window + WinResizeEvent = 1 + // ReopenLogsEvent is the event code the caller program will + // send along the ctrl fd to signal conmon to reopen the log files + ReopenLogsEvent = 2 + // TimedOutMessage is the message sent back to the caller by conmon + // when a container times out + TimedOutMessage = "command timed out" +) diff --git a/vendor/github.com/containers/conmon/runner/config/config_unix.go b/vendor/github.com/containers/conmon/runner/config/config_unix.go new file mode 100644 index 00000000..5caaca7c --- /dev/null +++ b/vendor/github.com/containers/conmon/runner/config/config_unix.go @@ -0,0 +1,7 @@ +// +build !windows + +package config + +const ( + ContainerAttachSocketDir = "/var/run/crio" +) diff --git a/vendor/github.com/containers/conmon/runner/config/config_windows.go b/vendor/github.com/containers/conmon/runner/config/config_windows.go new file mode 100644 index 00000000..2161b944 --- /dev/null +++ b/vendor/github.com/containers/conmon/runner/config/config_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package config + +const ( + ContainerAttachSocketDir = "C:\\crio\\run\\" +) diff --git a/vendor/github.com/containers/libtrust/CODE-OF-CONDUCT.md b/vendor/github.com/containers/libtrust/CODE-OF-CONDUCT.md new file mode 100644 index 00000000..a7d8acbf --- /dev/null +++ b/vendor/github.com/containers/libtrust/CODE-OF-CONDUCT.md @@ -0,0 +1,3 @@ +## The libtrust Project Community Code of Conduct + +The libtrust project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/main/CODE-OF-CONDUCT.md). diff --git a/vendor/github.com/containers/libtrust/CONTRIBUTING.md b/vendor/github.com/containers/libtrust/CONTRIBUTING.md new file mode 100644 index 00000000..05be0f8a --- /dev/null +++ b/vendor/github.com/containers/libtrust/CONTRIBUTING.md @@ -0,0 +1,13 @@ +# Contributing to libtrust + +Want to hack on libtrust? Awesome! Here are instructions to get you +started. + +libtrust is a part of the [Docker](https://www.docker.com) project, and follows +the same rules and principles. If you're already familiar with the way +Docker does things, you'll feel right at home. + +Otherwise, go read +[Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md). + +Happy hacking! diff --git a/vendor/github.com/containers/libtrust/LICENSE b/vendor/github.com/containers/libtrust/LICENSE new file mode 100644 index 00000000..27448585 --- /dev/null +++ b/vendor/github.com/containers/libtrust/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2014 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/libtrust/MAINTAINERS b/vendor/github.com/containers/libtrust/MAINTAINERS new file mode 100644 index 00000000..9768175f --- /dev/null +++ b/vendor/github.com/containers/libtrust/MAINTAINERS @@ -0,0 +1,3 @@ +Solomon Hykes +Josh Hawn (github: jlhawn) +Derek McGowan (github: dmcgowan) diff --git a/vendor/github.com/containers/libtrust/README.md b/vendor/github.com/containers/libtrust/README.md new file mode 100644 index 00000000..dcffb31a --- /dev/null +++ b/vendor/github.com/containers/libtrust/README.md @@ -0,0 +1,22 @@ +# libtrust + +> **WARNING** this library is no longer actively developed, and will be integrated +> in the [docker/distribution][https://www.github.com/docker/distribution] +> repository in future. + +Libtrust is library for managing authentication and authorization using public key cryptography. + +Authentication is handled using the identity attached to the public key. +Libtrust provides multiple methods to prove possession of the private key associated with an identity. + - TLS x509 certificates + - Signature verification + - Key Challenge + +Authorization and access control is managed through a distributed trust graph. +Trust servers are used as the authorities of the trust graph and allow caching portions of the graph for faster access. + +## Copyright and license + +Code and documentation copyright 2014 Docker, inc. Code released under the Apache 2.0 license. +Docs released under Creative commons. + diff --git a/vendor/github.com/containers/libtrust/SECURITY.md b/vendor/github.com/containers/libtrust/SECURITY.md new file mode 100644 index 00000000..966f4f05 --- /dev/null +++ b/vendor/github.com/containers/libtrust/SECURITY.md @@ -0,0 +1,3 @@ +## Security and Disclosure Information Policy for the libtrust Project + +The libtrust Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/main/SECURITY.md) for the Containers Projects. diff --git a/vendor/github.com/containers/libtrust/certificates.go b/vendor/github.com/containers/libtrust/certificates.go new file mode 100644 index 00000000..3dcca33c --- /dev/null +++ b/vendor/github.com/containers/libtrust/certificates.go @@ -0,0 +1,175 @@ +package libtrust + +import ( + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "net" + "time" +) + +type certTemplateInfo struct { + commonName string + domains []string + ipAddresses []net.IP + isCA bool + clientAuth bool + serverAuth bool +} + +func generateCertTemplate(info *certTemplateInfo) *x509.Certificate { + // Generate a certificate template which is valid from the past week to + // 10 years from now. The usage of the certificate depends on the + // specified fields in the given certTempInfo object. + var ( + keyUsage x509.KeyUsage + extKeyUsage []x509.ExtKeyUsage + ) + + if info.isCA { + keyUsage = x509.KeyUsageCertSign + } + + if info.clientAuth { + extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageClientAuth) + } + + if info.serverAuth { + extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageServerAuth) + } + + return &x509.Certificate{ + SerialNumber: big.NewInt(0), + Subject: pkix.Name{ + CommonName: info.commonName, + }, + NotBefore: time.Now().Add(-time.Hour * 24 * 7), + NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10), + DNSNames: info.domains, + IPAddresses: info.ipAddresses, + IsCA: info.isCA, + KeyUsage: keyUsage, + ExtKeyUsage: extKeyUsage, + BasicConstraintsValid: info.isCA, + } +} + +func generateCert(pub PublicKey, priv PrivateKey, subInfo, issInfo *certTemplateInfo) (cert *x509.Certificate, err error) { + pubCertTemplate := generateCertTemplate(subInfo) + privCertTemplate := generateCertTemplate(issInfo) + + certDER, err := x509.CreateCertificate( + rand.Reader, pubCertTemplate, privCertTemplate, + pub.CryptoPublicKey(), priv.CryptoPrivateKey(), + ) + if err != nil { + return nil, fmt.Errorf("failed to create certificate: %s", err) + } + + cert, err = x509.ParseCertificate(certDER) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %s", err) + } + + return +} + +// GenerateSelfSignedServerCert creates a self-signed certificate for the +// given key which is to be used for TLS servers with the given domains and +// IP addresses. +func GenerateSelfSignedServerCert(key PrivateKey, domains []string, ipAddresses []net.IP) (*x509.Certificate, error) { + info := &certTemplateInfo{ + commonName: key.KeyID(), + domains: domains, + ipAddresses: ipAddresses, + serverAuth: true, + } + + return generateCert(key.PublicKey(), key, info, info) +} + +// GenerateSelfSignedClientCert creates a self-signed certificate for the +// given key which is to be used for TLS clients. +func GenerateSelfSignedClientCert(key PrivateKey) (*x509.Certificate, error) { + info := &certTemplateInfo{ + commonName: key.KeyID(), + clientAuth: true, + } + + return generateCert(key.PublicKey(), key, info, info) +} + +// GenerateCACert creates a certificate which can be used as a trusted +// certificate authority. +func GenerateCACert(signer PrivateKey, trustedKey PublicKey) (*x509.Certificate, error) { + subjectInfo := &certTemplateInfo{ + commonName: trustedKey.KeyID(), + isCA: true, + } + issuerInfo := &certTemplateInfo{ + commonName: signer.KeyID(), + } + + return generateCert(trustedKey, signer, subjectInfo, issuerInfo) +} + +// GenerateCACertPool creates a certificate authority pool to be used for a +// TLS configuration. Any self-signed certificates issued by the specified +// trusted keys will be verified during a TLS handshake +func GenerateCACertPool(signer PrivateKey, trustedKeys []PublicKey) (*x509.CertPool, error) { + certPool := x509.NewCertPool() + + for _, trustedKey := range trustedKeys { + cert, err := GenerateCACert(signer, trustedKey) + if err != nil { + return nil, fmt.Errorf("failed to generate CA certificate: %s", err) + } + + certPool.AddCert(cert) + } + + return certPool, nil +} + +// LoadCertificateBundle loads certificates from the given file. The file should be pem encoded +// containing one or more certificates. The expected pem type is "CERTIFICATE". +func LoadCertificateBundle(filename string) ([]*x509.Certificate, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + certificates := []*x509.Certificate{} + var block *pem.Block + block, b = pem.Decode(b) + for ; block != nil; block, b = pem.Decode(b) { + if block.Type == "CERTIFICATE" { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + certificates = append(certificates, cert) + } else { + return nil, fmt.Errorf("invalid pem block type: %s", block.Type) + } + } + + return certificates, nil +} + +// LoadCertificatePool loads a CA pool from the given file. The file should be pem encoded +// containing one or more certificates. The expected pem type is "CERTIFICATE". +func LoadCertificatePool(filename string) (*x509.CertPool, error) { + certs, err := LoadCertificateBundle(filename) + if err != nil { + return nil, err + } + pool := x509.NewCertPool() + for _, cert := range certs { + pool.AddCert(cert) + } + return pool, nil +} diff --git a/vendor/github.com/containers/libtrust/doc.go b/vendor/github.com/containers/libtrust/doc.go new file mode 100644 index 00000000..ec5d2159 --- /dev/null +++ b/vendor/github.com/containers/libtrust/doc.go @@ -0,0 +1,9 @@ +/* +Package libtrust provides an interface for managing authentication and +authorization using public key cryptography. Authentication is handled +using the identity attached to the public key and verified through TLS +x509 certificates, a key challenge, or signature. Authorization and +access control is managed through a trust graph distributed between +both remote trust servers and locally cached and managed data. +*/ +package libtrust diff --git a/vendor/github.com/containers/libtrust/ec_key.go b/vendor/github.com/containers/libtrust/ec_key.go new file mode 100644 index 00000000..0ee1b911 --- /dev/null +++ b/vendor/github.com/containers/libtrust/ec_key.go @@ -0,0 +1,422 @@ +package libtrust + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" + "math/big" +) + +/* + * EC DSA PUBLIC KEY + */ + +// ecPublicKey implements a libtrust.PublicKey using elliptic curve digital +// signature algorithms. +type ecPublicKey struct { + *ecdsa.PublicKey + curveName string + signatureAlgorithm *signatureAlgorithm + extended map[string]interface{} +} + +func fromECPublicKey(cryptoPublicKey *ecdsa.PublicKey) (*ecPublicKey, error) { + curve := cryptoPublicKey.Curve + + switch { + case curve == elliptic.P256(): + return &ecPublicKey{cryptoPublicKey, "P-256", es256, map[string]interface{}{}}, nil + case curve == elliptic.P384(): + return &ecPublicKey{cryptoPublicKey, "P-384", es384, map[string]interface{}{}}, nil + case curve == elliptic.P521(): + return &ecPublicKey{cryptoPublicKey, "P-521", es512, map[string]interface{}{}}, nil + default: + return nil, errors.New("unsupported elliptic curve") + } +} + +// KeyType returns the key type for elliptic curve keys, i.e., "EC". +func (k *ecPublicKey) KeyType() string { + return "EC" +} + +// CurveName returns the elliptic curve identifier. +// Possible values are "P-256", "P-384", and "P-521". +func (k *ecPublicKey) CurveName() string { + return k.curveName +} + +// KeyID returns a distinct identifier which is unique to this Public Key. +func (k *ecPublicKey) KeyID() string { + return keyIDFromCryptoKey(k) +} + +func (k *ecPublicKey) String() string { + return fmt.Sprintf("EC Public Key <%s>", k.KeyID()) +} + +// Verify verifyies the signature of the data in the io.Reader using this +// PublicKey. The alg parameter should identify the digital signature +// algorithm which was used to produce the signature and should be supported +// by this public key. Returns a nil error if the signature is valid. +func (k *ecPublicKey) Verify(data io.Reader, alg string, signature []byte) error { + // For EC keys there is only one supported signature algorithm depending + // on the curve parameters. + if k.signatureAlgorithm.HeaderParam() != alg { + return fmt.Errorf("unable to verify signature: EC Public Key with curve %q does not support signature algorithm %q", k.curveName, alg) + } + + // signature is the concatenation of (r, s), base64Url encoded. + sigLength := len(signature) + expectedOctetLength := 2 * ((k.Params().BitSize + 7) >> 3) + if sigLength != expectedOctetLength { + return fmt.Errorf("signature length is %d octets long, should be %d", sigLength, expectedOctetLength) + } + + rBytes, sBytes := signature[:sigLength/2], signature[sigLength/2:] + r := new(big.Int).SetBytes(rBytes) + s := new(big.Int).SetBytes(sBytes) + + hasher := k.signatureAlgorithm.HashID().New() + _, err := io.Copy(hasher, data) + if err != nil { + return fmt.Errorf("error reading data to sign: %s", err) + } + hash := hasher.Sum(nil) + + if !ecdsa.Verify(k.PublicKey, hash, r, s) { + return errors.New("invalid signature") + } + + return nil +} + +// CryptoPublicKey returns the internal object which can be used as a +// crypto.PublicKey for use with other standard library operations. The type +// is either *rsa.PublicKey or *ecdsa.PublicKey +func (k *ecPublicKey) CryptoPublicKey() crypto.PublicKey { + return k.PublicKey +} + +func (k *ecPublicKey) toMap() map[string]interface{} { + jwk := make(map[string]interface{}) + for k, v := range k.extended { + jwk[k] = v + } + jwk["kty"] = k.KeyType() + jwk["kid"] = k.KeyID() + jwk["crv"] = k.CurveName() + + xBytes := k.X.Bytes() + yBytes := k.Y.Bytes() + octetLength := (k.Params().BitSize + 7) >> 3 + // MUST include leading zeros in the output so that x, y are each + // *octetLength* bytes long. + xBuf := make([]byte, octetLength-len(xBytes), octetLength) + yBuf := make([]byte, octetLength-len(yBytes), octetLength) + xBuf = append(xBuf, xBytes...) + yBuf = append(yBuf, yBytes...) + + jwk["x"] = joseBase64UrlEncode(xBuf) + jwk["y"] = joseBase64UrlEncode(yBuf) + + return jwk +} + +// MarshalJSON serializes this Public Key using the JWK JSON serialization format for +// elliptic curve keys. +func (k *ecPublicKey) MarshalJSON() (data []byte, err error) { + return json.Marshal(k.toMap()) +} + +// PEMBlock serializes this Public Key to DER-encoded PKIX format. +func (k *ecPublicKey) PEMBlock() (*pem.Block, error) { + derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey) + if err != nil { + return nil, fmt.Errorf("unable to serialize EC PublicKey to DER-encoded PKIX format: %s", err) + } + k.extended["kid"] = k.KeyID() // For display purposes. + return createPemBlock("PUBLIC KEY", derBytes, k.extended) +} + +func (k *ecPublicKey) AddExtendedField(field string, value interface{}) { + k.extended[field] = value +} + +func (k *ecPublicKey) GetExtendedField(field string) interface{} { + v, ok := k.extended[field] + if !ok { + return nil + } + return v +} + +func ecPublicKeyFromMap(jwk map[string]interface{}) (*ecPublicKey, error) { + // JWK key type (kty) has already been determined to be "EC". + // Need to extract 'crv', 'x', 'y', and 'kid' and check for + // consistency. + + // Get the curve identifier value. + crv, err := stringFromMap(jwk, "crv") + if err != nil { + return nil, fmt.Errorf("JWK EC Public Key curve identifier: %s", err) + } + + var ( + curve elliptic.Curve + sigAlg *signatureAlgorithm + ) + + switch { + case crv == "P-256": + curve = elliptic.P256() + sigAlg = es256 + case crv == "P-384": + curve = elliptic.P384() + sigAlg = es384 + case crv == "P-521": + curve = elliptic.P521() + sigAlg = es512 + default: + return nil, fmt.Errorf("JWK EC Public Key curve identifier not supported: %q\n", crv) + } + + // Get the X and Y coordinates for the public key point. + xB64Url, err := stringFromMap(jwk, "x") + if err != nil { + return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err) + } + x, err := parseECCoordinate(xB64Url, curve) + if err != nil { + return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err) + } + + yB64Url, err := stringFromMap(jwk, "y") + if err != nil { + return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err) + } + y, err := parseECCoordinate(yB64Url, curve) + if err != nil { + return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err) + } + + key := &ecPublicKey{ + PublicKey: &ecdsa.PublicKey{Curve: curve, X: x, Y: y}, + curveName: crv, signatureAlgorithm: sigAlg, + } + + // Key ID is optional too, but if it exists, it should match the key. + _, ok := jwk["kid"] + if ok { + kid, err := stringFromMap(jwk, "kid") + if err != nil { + return nil, fmt.Errorf("JWK EC Public Key ID: %s", err) + } + if kid != key.KeyID() { + return nil, fmt.Errorf("JWK EC Public Key ID does not match: %s", kid) + } + } + + key.extended = jwk + + return key, nil +} + +/* + * EC DSA PRIVATE KEY + */ + +// ecPrivateKey implements a JWK Private Key using elliptic curve digital signature +// algorithms. +type ecPrivateKey struct { + ecPublicKey + *ecdsa.PrivateKey +} + +func fromECPrivateKey(cryptoPrivateKey *ecdsa.PrivateKey) (*ecPrivateKey, error) { + publicKey, err := fromECPublicKey(&cryptoPrivateKey.PublicKey) + if err != nil { + return nil, err + } + + return &ecPrivateKey{*publicKey, cryptoPrivateKey}, nil +} + +// PublicKey returns the Public Key data associated with this Private Key. +func (k *ecPrivateKey) PublicKey() PublicKey { + return &k.ecPublicKey +} + +func (k *ecPrivateKey) String() string { + return fmt.Sprintf("EC Private Key <%s>", k.KeyID()) +} + +// Sign signs the data read from the io.Reader using a signature algorithm supported +// by the elliptic curve private key. If the specified hashing algorithm is +// supported by this key, that hash function is used to generate the signature +// otherwise the the default hashing algorithm for this key is used. Returns +// the signature and the name of the JWK signature algorithm used, e.g., +// "ES256", "ES384", "ES512". +func (k *ecPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) { + // Generate a signature of the data using the internal alg. + // The given hashId is only a suggestion, and since EC keys only support + // on signature/hash algorithm given the curve name, we disregard it for + // the elliptic curve JWK signature implementation. + r, s, err := k.sign(data, hashID) + if err != nil { + return nil, "", fmt.Errorf("error producing signature: %s", err) + } + + rBytes, sBytes := r.Bytes(), s.Bytes() + octetLength := (k.ecPublicKey.Params().BitSize + 7) >> 3 + // MUST include leading zeros in the output + rBuf := make([]byte, octetLength-len(rBytes), octetLength) + sBuf := make([]byte, octetLength-len(sBytes), octetLength) + + rBuf = append(rBuf, rBytes...) + sBuf = append(sBuf, sBytes...) + + signature = append(rBuf, sBuf...) + alg = k.signatureAlgorithm.HeaderParam() + + return +} + +// CryptoPrivateKey returns the internal object which can be used as a +// crypto.PublicKey for use with other standard library operations. The type +// is either *rsa.PublicKey or *ecdsa.PublicKey +func (k *ecPrivateKey) CryptoPrivateKey() crypto.PrivateKey { + return k.PrivateKey +} + +func (k *ecPrivateKey) toMap() map[string]interface{} { + jwk := k.ecPublicKey.toMap() + + dBytes := k.D.Bytes() + // The length of this octet string MUST be ceiling(log-base-2(n)/8) + // octets (where n is the order of the curve). This is because the private + // key d must be in the interval [1, n-1] so the bitlength of d should be + // no larger than the bitlength of n-1. The easiest way to find the octet + // length is to take bitlength(n-1), add 7 to force a carry, and shift this + // bit sequence right by 3, which is essentially dividing by 8 and adding + // 1 if there is any remainder. Thus, the private key value d should be + // output to (bitlength(n-1)+7)>>3 octets. + n := k.ecPublicKey.Params().N + octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3 + // Create a buffer with the necessary zero-padding. + dBuf := make([]byte, octetLength-len(dBytes), octetLength) + dBuf = append(dBuf, dBytes...) + + jwk["d"] = joseBase64UrlEncode(dBuf) + + return jwk +} + +// MarshalJSON serializes this Private Key using the JWK JSON serialization format for +// elliptic curve keys. +func (k *ecPrivateKey) MarshalJSON() (data []byte, err error) { + return json.Marshal(k.toMap()) +} + +// PEMBlock serializes this Private Key to DER-encoded PKIX format. +func (k *ecPrivateKey) PEMBlock() (*pem.Block, error) { + derBytes, err := x509.MarshalECPrivateKey(k.PrivateKey) + if err != nil { + return nil, fmt.Errorf("unable to serialize EC PrivateKey to DER-encoded PKIX format: %s", err) + } + k.extended["keyID"] = k.KeyID() // For display purposes. + return createPemBlock("EC PRIVATE KEY", derBytes, k.extended) +} + +func ecPrivateKeyFromMap(jwk map[string]interface{}) (*ecPrivateKey, error) { + dB64Url, err := stringFromMap(jwk, "d") + if err != nil { + return nil, fmt.Errorf("JWK EC Private Key: %s", err) + } + + // JWK key type (kty) has already been determined to be "EC". + // Need to extract the public key information, then extract the private + // key value 'd'. + publicKey, err := ecPublicKeyFromMap(jwk) + if err != nil { + return nil, err + } + + d, err := parseECPrivateParam(dB64Url, publicKey.Curve) + if err != nil { + return nil, fmt.Errorf("JWK EC Private Key d-param: %s", err) + } + + key := &ecPrivateKey{ + ecPublicKey: *publicKey, + PrivateKey: &ecdsa.PrivateKey{ + PublicKey: *publicKey.PublicKey, + D: d, + }, + } + + return key, nil +} + +/* + * Key Generation Functions. + */ + +func generateECPrivateKey(curve elliptic.Curve) (k *ecPrivateKey, err error) { + k = new(ecPrivateKey) + k.PrivateKey, err = ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + return nil, err + } + + k.ecPublicKey.PublicKey = &k.PrivateKey.PublicKey + k.extended = make(map[string]interface{}) + + return +} + +// GenerateECP256PrivateKey generates a key pair using elliptic curve P-256. +func GenerateECP256PrivateKey() (PrivateKey, error) { + k, err := generateECPrivateKey(elliptic.P256()) + if err != nil { + return nil, fmt.Errorf("error generating EC P-256 key: %s", err) + } + + k.curveName = "P-256" + k.signatureAlgorithm = es256 + + return k, nil +} + +// GenerateECP384PrivateKey generates a key pair using elliptic curve P-384. +func GenerateECP384PrivateKey() (PrivateKey, error) { + k, err := generateECPrivateKey(elliptic.P384()) + if err != nil { + return nil, fmt.Errorf("error generating EC P-384 key: %s", err) + } + + k.curveName = "P-384" + k.signatureAlgorithm = es384 + + return k, nil +} + +// GenerateECP521PrivateKey generates aß key pair using elliptic curve P-521. +func GenerateECP521PrivateKey() (PrivateKey, error) { + k, err := generateECPrivateKey(elliptic.P521()) + if err != nil { + return nil, fmt.Errorf("error generating EC P-521 key: %s", err) + } + + k.curveName = "P-521" + k.signatureAlgorithm = es512 + + return k, nil +} diff --git a/vendor/github.com/containers/libtrust/ec_key_no_openssl.go b/vendor/github.com/containers/libtrust/ec_key_no_openssl.go new file mode 100644 index 00000000..d6cdaca3 --- /dev/null +++ b/vendor/github.com/containers/libtrust/ec_key_no_openssl.go @@ -0,0 +1,23 @@ +// +build !libtrust_openssl + +package libtrust + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "fmt" + "io" + "math/big" +) + +func (k *ecPrivateKey) sign(data io.Reader, hashID crypto.Hash) (r, s *big.Int, err error) { + hasher := k.signatureAlgorithm.HashID().New() + _, err = io.Copy(hasher, data) + if err != nil { + return nil, nil, fmt.Errorf("error reading data to sign: %s", err) + } + hash := hasher.Sum(nil) + + return ecdsa.Sign(rand.Reader, k.PrivateKey, hash) +} diff --git a/vendor/github.com/containers/libtrust/ec_key_openssl.go b/vendor/github.com/containers/libtrust/ec_key_openssl.go new file mode 100644 index 00000000..4137511f --- /dev/null +++ b/vendor/github.com/containers/libtrust/ec_key_openssl.go @@ -0,0 +1,24 @@ +// +build libtrust_openssl + +package libtrust + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/rand" + "fmt" + "io" + "math/big" +) + +func (k *ecPrivateKey) sign(data io.Reader, hashID crypto.Hash) (r, s *big.Int, err error) { + hId := k.signatureAlgorithm.HashID() + buf := new(bytes.Buffer) + _, err = buf.ReadFrom(data) + if err != nil { + return nil, nil, fmt.Errorf("error reading data: %s", err) + } + + return ecdsa.HashSign(rand.Reader, k.PrivateKey, buf.Bytes(), hId) +} diff --git a/vendor/github.com/containers/libtrust/filter.go b/vendor/github.com/containers/libtrust/filter.go new file mode 100644 index 00000000..5b2b4fca --- /dev/null +++ b/vendor/github.com/containers/libtrust/filter.go @@ -0,0 +1,50 @@ +package libtrust + +import ( + "path/filepath" +) + +// FilterByHosts filters the list of PublicKeys to only those which contain a +// 'hosts' pattern which matches the given host. If *includeEmpty* is true, +// then keys which do not specify any hosts are also returned. +func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKey, error) { + filtered := make([]PublicKey, 0, len(keys)) + + for _, pubKey := range keys { + var hosts []string + switch v := pubKey.GetExtendedField("hosts").(type) { + case []string: + hosts = v + case []interface{}: + for _, value := range v { + h, ok := value.(string) + if !ok { + continue + } + hosts = append(hosts, h) + } + } + + if len(hosts) == 0 { + if includeEmpty { + filtered = append(filtered, pubKey) + } + continue + } + + // Check if any hosts match pattern + for _, hostPattern := range hosts { + match, err := filepath.Match(hostPattern, host) + if err != nil { + return nil, err + } + + if match { + filtered = append(filtered, pubKey) + continue + } + } + } + + return filtered, nil +} diff --git a/vendor/github.com/containers/libtrust/hash.go b/vendor/github.com/containers/libtrust/hash.go new file mode 100644 index 00000000..a2df787d --- /dev/null +++ b/vendor/github.com/containers/libtrust/hash.go @@ -0,0 +1,56 @@ +package libtrust + +import ( + "crypto" + _ "crypto/sha256" // Registrer SHA224 and SHA256 + _ "crypto/sha512" // Registrer SHA384 and SHA512 + "fmt" +) + +type signatureAlgorithm struct { + algHeaderParam string + hashID crypto.Hash +} + +func (h *signatureAlgorithm) HeaderParam() string { + return h.algHeaderParam +} + +func (h *signatureAlgorithm) HashID() crypto.Hash { + return h.hashID +} + +var ( + rs256 = &signatureAlgorithm{"RS256", crypto.SHA256} + rs384 = &signatureAlgorithm{"RS384", crypto.SHA384} + rs512 = &signatureAlgorithm{"RS512", crypto.SHA512} + es256 = &signatureAlgorithm{"ES256", crypto.SHA256} + es384 = &signatureAlgorithm{"ES384", crypto.SHA384} + es512 = &signatureAlgorithm{"ES512", crypto.SHA512} +) + +func rsaSignatureAlgorithmByName(alg string) (*signatureAlgorithm, error) { + switch { + case alg == "RS256": + return rs256, nil + case alg == "RS384": + return rs384, nil + case alg == "RS512": + return rs512, nil + default: + return nil, fmt.Errorf("RSA Digital Signature Algorithm %q not supported", alg) + } +} + +func rsaPKCS1v15SignatureAlgorithmForHashID(hashID crypto.Hash) *signatureAlgorithm { + switch { + case hashID == crypto.SHA512: + return rs512 + case hashID == crypto.SHA384: + return rs384 + case hashID == crypto.SHA256: + fallthrough + default: + return rs256 + } +} diff --git a/vendor/github.com/containers/libtrust/jsonsign.go b/vendor/github.com/containers/libtrust/jsonsign.go new file mode 100644 index 00000000..cb2ca9a7 --- /dev/null +++ b/vendor/github.com/containers/libtrust/jsonsign.go @@ -0,0 +1,657 @@ +package libtrust + +import ( + "bytes" + "crypto" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "sort" + "time" + "unicode" +) + +var ( + // ErrInvalidSignContent is used when the content to be signed is invalid. + ErrInvalidSignContent = errors.New("invalid sign content") + + // ErrInvalidJSONContent is used when invalid json is encountered. + ErrInvalidJSONContent = errors.New("invalid json content") + + // ErrMissingSignatureKey is used when the specified signature key + // does not exist in the JSON content. + ErrMissingSignatureKey = errors.New("missing signature key") +) + +type jsHeader struct { + JWK PublicKey `json:"jwk,omitempty"` + Algorithm string `json:"alg"` + Chain []string `json:"x5c,omitempty"` +} + +type jsSignature struct { + Header jsHeader `json:"header"` + Signature string `json:"signature"` + Protected string `json:"protected,omitempty"` +} + +type jsSignaturesSorted []jsSignature + +func (jsbkid jsSignaturesSorted) Swap(i, j int) { jsbkid[i], jsbkid[j] = jsbkid[j], jsbkid[i] } +func (jsbkid jsSignaturesSorted) Len() int { return len(jsbkid) } + +func (jsbkid jsSignaturesSorted) Less(i, j int) bool { + ki, kj := jsbkid[i].Header.JWK.KeyID(), jsbkid[j].Header.JWK.KeyID() + si, sj := jsbkid[i].Signature, jsbkid[j].Signature + + if ki == kj { + return si < sj + } + + return ki < kj +} + +type signKey struct { + PrivateKey + Chain []*x509.Certificate +} + +// JSONSignature represents a signature of a json object. +type JSONSignature struct { + payload string + signatures []jsSignature + indent string + formatLength int + formatTail []byte +} + +func newJSONSignature() *JSONSignature { + return &JSONSignature{ + signatures: make([]jsSignature, 0, 1), + } +} + +// Payload returns the encoded payload of the signature. This +// payload should not be signed directly +func (js *JSONSignature) Payload() ([]byte, error) { + return joseBase64UrlDecode(js.payload) +} + +func (js *JSONSignature) protectedHeader() (string, error) { + protected := map[string]interface{}{ + "formatLength": js.formatLength, + "formatTail": joseBase64UrlEncode(js.formatTail), + "time": time.Now().UTC().Format(time.RFC3339), + } + protectedBytes, err := json.Marshal(protected) + if err != nil { + return "", err + } + + return joseBase64UrlEncode(protectedBytes), nil +} + +func (js *JSONSignature) signBytes(protectedHeader string) ([]byte, error) { + buf := make([]byte, len(js.payload)+len(protectedHeader)+1) + copy(buf, protectedHeader) + buf[len(protectedHeader)] = '.' + copy(buf[len(protectedHeader)+1:], js.payload) + return buf, nil +} + +// Sign adds a signature using the given private key. +func (js *JSONSignature) Sign(key PrivateKey) error { + protected, err := js.protectedHeader() + if err != nil { + return err + } + signBytes, err := js.signBytes(protected) + if err != nil { + return err + } + sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256) + if err != nil { + return err + } + + js.signatures = append(js.signatures, jsSignature{ + Header: jsHeader{ + JWK: key.PublicKey(), + Algorithm: algorithm, + }, + Signature: joseBase64UrlEncode(sigBytes), + Protected: protected, + }) + + return nil +} + +// SignWithChain adds a signature using the given private key +// and setting the x509 chain. The public key of the first element +// in the chain must be the public key corresponding with the sign key. +func (js *JSONSignature) SignWithChain(key PrivateKey, chain []*x509.Certificate) error { + // Ensure key.Chain[0] is public key for key + //key.Chain.PublicKey + //key.PublicKey().CryptoPublicKey() + + // Verify chain + protected, err := js.protectedHeader() + if err != nil { + return err + } + signBytes, err := js.signBytes(protected) + if err != nil { + return err + } + sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256) + if err != nil { + return err + } + + header := jsHeader{ + Chain: make([]string, len(chain)), + Algorithm: algorithm, + } + + for i, cert := range chain { + header.Chain[i] = base64.StdEncoding.EncodeToString(cert.Raw) + } + + js.signatures = append(js.signatures, jsSignature{ + Header: header, + Signature: joseBase64UrlEncode(sigBytes), + Protected: protected, + }) + + return nil +} + +// Verify verifies all the signatures and returns the list of +// public keys used to sign. Any x509 chains are not checked. +func (js *JSONSignature) Verify() ([]PublicKey, error) { + keys := make([]PublicKey, len(js.signatures)) + for i, signature := range js.signatures { + signBytes, err := js.signBytes(signature.Protected) + if err != nil { + return nil, err + } + var publicKey PublicKey + if len(signature.Header.Chain) > 0 { + certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0]) + if err != nil { + return nil, err + } + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + return nil, err + } + publicKey, err = FromCryptoPublicKey(cert.PublicKey) + if err != nil { + return nil, err + } + } else if signature.Header.JWK != nil { + publicKey = signature.Header.JWK + } else { + return nil, errors.New("missing public key") + } + + sigBytes, err := joseBase64UrlDecode(signature.Signature) + if err != nil { + return nil, err + } + + err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes) + if err != nil { + return nil, err + } + + keys[i] = publicKey + } + return keys, nil +} + +// VerifyChains verifies all the signatures and the chains associated +// with each signature and returns the list of verified chains. +// Signatures without an x509 chain are not checked. +func (js *JSONSignature) VerifyChains(ca *x509.CertPool) ([][]*x509.Certificate, error) { + chains := make([][]*x509.Certificate, 0, len(js.signatures)) + for _, signature := range js.signatures { + signBytes, err := js.signBytes(signature.Protected) + if err != nil { + return nil, err + } + var publicKey PublicKey + if len(signature.Header.Chain) > 0 { + certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0]) + if err != nil { + return nil, err + } + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + return nil, err + } + publicKey, err = FromCryptoPublicKey(cert.PublicKey) + if err != nil { + return nil, err + } + intermediates := x509.NewCertPool() + if len(signature.Header.Chain) > 1 { + intermediateChain := signature.Header.Chain[1:] + for i := range intermediateChain { + certBytes, err := base64.StdEncoding.DecodeString(intermediateChain[i]) + if err != nil { + return nil, err + } + intermediate, err := x509.ParseCertificate(certBytes) + if err != nil { + return nil, err + } + intermediates.AddCert(intermediate) + } + } + + verifyOptions := x509.VerifyOptions{ + Intermediates: intermediates, + Roots: ca, + } + + verifiedChains, err := cert.Verify(verifyOptions) + if err != nil { + return nil, err + } + chains = append(chains, verifiedChains...) + + sigBytes, err := joseBase64UrlDecode(signature.Signature) + if err != nil { + return nil, err + } + + err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes) + if err != nil { + return nil, err + } + } + + } + return chains, nil +} + +// JWS returns JSON serialized JWS according to +// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-7.2 +func (js *JSONSignature) JWS() ([]byte, error) { + if len(js.signatures) == 0 { + return nil, errors.New("missing signature") + } + + sort.Sort(jsSignaturesSorted(js.signatures)) + + jsonMap := map[string]interface{}{ + "payload": js.payload, + "signatures": js.signatures, + } + + return json.MarshalIndent(jsonMap, "", " ") +} + +func notSpace(r rune) bool { + return !unicode.IsSpace(r) +} + +func detectJSONIndent(jsonContent []byte) (indent string) { + if len(jsonContent) > 2 && jsonContent[0] == '{' && jsonContent[1] == '\n' { + quoteIndex := bytes.IndexRune(jsonContent[1:], '"') + if quoteIndex > 0 { + indent = string(jsonContent[2 : quoteIndex+1]) + } + } + return +} + +type jsParsedHeader struct { + JWK json.RawMessage `json:"jwk"` + Algorithm string `json:"alg"` + Chain []string `json:"x5c"` +} + +type jsParsedSignature struct { + Header jsParsedHeader `json:"header"` + Signature string `json:"signature"` + Protected string `json:"protected"` +} + +// ParseJWS parses a JWS serialized JSON object into a Json Signature. +func ParseJWS(content []byte) (*JSONSignature, error) { + type jsParsed struct { + Payload string `json:"payload"` + Signatures []jsParsedSignature `json:"signatures"` + } + parsed := &jsParsed{} + err := json.Unmarshal(content, parsed) + if err != nil { + return nil, err + } + if len(parsed.Signatures) == 0 { + return nil, errors.New("missing signatures") + } + payload, err := joseBase64UrlDecode(parsed.Payload) + if err != nil { + return nil, err + } + + js, err := NewJSONSignature(payload) + if err != nil { + return nil, err + } + js.signatures = make([]jsSignature, len(parsed.Signatures)) + for i, signature := range parsed.Signatures { + header := jsHeader{ + Algorithm: signature.Header.Algorithm, + } + if signature.Header.Chain != nil { + header.Chain = signature.Header.Chain + } + if signature.Header.JWK != nil { + publicKey, err := UnmarshalPublicKeyJWK([]byte(signature.Header.JWK)) + if err != nil { + return nil, err + } + header.JWK = publicKey + } + js.signatures[i] = jsSignature{ + Header: header, + Signature: signature.Signature, + Protected: signature.Protected, + } + } + + return js, nil +} + +// NewJSONSignature returns a new unsigned JWS from a json byte array. +// JSONSignature will need to be signed before serializing or storing. +// Optionally, one or more signatures can be provided as byte buffers, +// containing serialized JWS signatures, to assemble a fully signed JWS +// package. It is the callers responsibility to ensure uniqueness of the +// provided signatures. +func NewJSONSignature(content []byte, signatures ...[]byte) (*JSONSignature, error) { + var dataMap map[string]interface{} + err := json.Unmarshal(content, &dataMap) + if err != nil { + return nil, err + } + + js := newJSONSignature() + js.indent = detectJSONIndent(content) + + js.payload = joseBase64UrlEncode(content) + + // Find trailing } and whitespace, put in protected header + closeIndex := bytes.LastIndexFunc(content, notSpace) + if content[closeIndex] != '}' { + return nil, ErrInvalidJSONContent + } + lastRuneIndex := bytes.LastIndexFunc(content[:closeIndex], notSpace) + if content[lastRuneIndex] == ',' { + return nil, ErrInvalidJSONContent + } + js.formatLength = lastRuneIndex + 1 + js.formatTail = content[js.formatLength:] + + if len(signatures) > 0 { + for _, signature := range signatures { + var parsedJSig jsParsedSignature + + if err := json.Unmarshal(signature, &parsedJSig); err != nil { + return nil, err + } + + // TODO(stevvooe): A lot of the code below is repeated in + // ParseJWS. It will require more refactoring to fix that. + jsig := jsSignature{ + Header: jsHeader{ + Algorithm: parsedJSig.Header.Algorithm, + }, + Signature: parsedJSig.Signature, + Protected: parsedJSig.Protected, + } + + if parsedJSig.Header.Chain != nil { + jsig.Header.Chain = parsedJSig.Header.Chain + } + + if parsedJSig.Header.JWK != nil { + publicKey, err := UnmarshalPublicKeyJWK([]byte(parsedJSig.Header.JWK)) + if err != nil { + return nil, err + } + jsig.Header.JWK = publicKey + } + + js.signatures = append(js.signatures, jsig) + } + } + + return js, nil +} + +// NewJSONSignatureFromMap returns a new unsigned JSONSignature from a map or +// struct. JWS will need to be signed before serializing or storing. +func NewJSONSignatureFromMap(content interface{}) (*JSONSignature, error) { + switch content.(type) { + case map[string]interface{}: + case struct{}: + default: + return nil, errors.New("invalid data type") + } + + js := newJSONSignature() + js.indent = " " + + payload, err := json.MarshalIndent(content, "", js.indent) + if err != nil { + return nil, err + } + js.payload = joseBase64UrlEncode(payload) + + // Remove '\n}' from formatted section, put in protected header + js.formatLength = len(payload) - 2 + js.formatTail = payload[js.formatLength:] + + return js, nil +} + +func readIntFromMap(key string, m map[string]interface{}) (int, bool) { + value, ok := m[key] + if !ok { + return 0, false + } + switch v := value.(type) { + case int: + return v, true + case float64: + return int(v), true + default: + return 0, false + } +} + +func readStringFromMap(key string, m map[string]interface{}) (v string, ok bool) { + value, ok := m[key] + if !ok { + return "", false + } + v, ok = value.(string) + return +} + +// ParsePrettySignature parses a formatted signature into a +// JSON signature. If the signatures are missing the format information +// an error is thrown. The formatted signature must be created by +// the same method as format signature. +func ParsePrettySignature(content []byte, signatureKey string) (*JSONSignature, error) { + var contentMap map[string]json.RawMessage + err := json.Unmarshal(content, &contentMap) + if err != nil { + return nil, fmt.Errorf("error unmarshalling content: %s", err) + } + sigMessage, ok := contentMap[signatureKey] + if !ok { + return nil, ErrMissingSignatureKey + } + + var signatureBlocks []jsParsedSignature + err = json.Unmarshal([]byte(sigMessage), &signatureBlocks) + if err != nil { + return nil, fmt.Errorf("error unmarshalling signatures: %s", err) + } + + js := newJSONSignature() + js.signatures = make([]jsSignature, len(signatureBlocks)) + + for i, signatureBlock := range signatureBlocks { + protectedBytes, err := joseBase64UrlDecode(signatureBlock.Protected) + if err != nil { + return nil, fmt.Errorf("base64 decode error: %s", err) + } + var protectedHeader map[string]interface{} + err = json.Unmarshal(protectedBytes, &protectedHeader) + if err != nil { + return nil, fmt.Errorf("error unmarshalling protected header: %s", err) + } + + formatLength, ok := readIntFromMap("formatLength", protectedHeader) + if !ok { + return nil, errors.New("missing formatted length") + } + encodedTail, ok := readStringFromMap("formatTail", protectedHeader) + if !ok { + return nil, errors.New("missing formatted tail") + } + formatTail, err := joseBase64UrlDecode(encodedTail) + if err != nil { + return nil, fmt.Errorf("base64 decode error on tail: %s", err) + } + if js.formatLength == 0 { + js.formatLength = formatLength + } else if js.formatLength != formatLength { + return nil, errors.New("conflicting format length") + } + if len(js.formatTail) == 0 { + js.formatTail = formatTail + } else if bytes.Compare(js.formatTail, formatTail) != 0 { + return nil, errors.New("conflicting format tail") + } + + header := jsHeader{ + Algorithm: signatureBlock.Header.Algorithm, + Chain: signatureBlock.Header.Chain, + } + if signatureBlock.Header.JWK != nil { + publicKey, err := UnmarshalPublicKeyJWK([]byte(signatureBlock.Header.JWK)) + if err != nil { + return nil, fmt.Errorf("error unmarshalling public key: %s", err) + } + header.JWK = publicKey + } + js.signatures[i] = jsSignature{ + Header: header, + Signature: signatureBlock.Signature, + Protected: signatureBlock.Protected, + } + } + if js.formatLength > len(content) { + return nil, errors.New("invalid format length") + } + formatted := make([]byte, js.formatLength+len(js.formatTail)) + copy(formatted, content[:js.formatLength]) + copy(formatted[js.formatLength:], js.formatTail) + js.indent = detectJSONIndent(formatted) + js.payload = joseBase64UrlEncode(formatted) + + return js, nil +} + +// PrettySignature formats a json signature into an easy to read +// single json serialized object. +func (js *JSONSignature) PrettySignature(signatureKey string) ([]byte, error) { + if len(js.signatures) == 0 { + return nil, errors.New("no signatures") + } + payload, err := joseBase64UrlDecode(js.payload) + if err != nil { + return nil, err + } + payload = payload[:js.formatLength] + + sort.Sort(jsSignaturesSorted(js.signatures)) + + var marshalled []byte + var marshallErr error + if js.indent != "" { + marshalled, marshallErr = json.MarshalIndent(js.signatures, js.indent, js.indent) + } else { + marshalled, marshallErr = json.Marshal(js.signatures) + } + if marshallErr != nil { + return nil, marshallErr + } + + buf := bytes.NewBuffer(make([]byte, 0, len(payload)+len(marshalled)+34)) + buf.Write(payload) + buf.WriteByte(',') + if js.indent != "" { + buf.WriteByte('\n') + buf.WriteString(js.indent) + buf.WriteByte('"') + buf.WriteString(signatureKey) + buf.WriteString("\": ") + buf.Write(marshalled) + buf.WriteByte('\n') + } else { + buf.WriteByte('"') + buf.WriteString(signatureKey) + buf.WriteString("\":") + buf.Write(marshalled) + } + buf.WriteByte('}') + + return buf.Bytes(), nil +} + +// Signatures provides the signatures on this JWS as opaque blobs, sorted by +// keyID. These blobs can be stored and reassembled with payloads. Internally, +// they are simply marshaled json web signatures but implementations should +// not rely on this. +func (js *JSONSignature) Signatures() ([][]byte, error) { + sort.Sort(jsSignaturesSorted(js.signatures)) + + var sb [][]byte + for _, jsig := range js.signatures { + p, err := json.Marshal(jsig) + if err != nil { + return nil, err + } + + sb = append(sb, p) + } + + return sb, nil +} + +// Merge combines the signatures from one or more other signatures into the +// method receiver. If the payloads differ for any argument, an error will be +// returned and the receiver will not be modified. +func (js *JSONSignature) Merge(others ...*JSONSignature) error { + merged := js.signatures + for _, other := range others { + if js.payload != other.payload { + return fmt.Errorf("payloads differ from merge target") + } + merged = append(merged, other.signatures...) + } + + js.signatures = merged + return nil +} diff --git a/vendor/github.com/containers/libtrust/key.go b/vendor/github.com/containers/libtrust/key.go new file mode 100644 index 00000000..73642db2 --- /dev/null +++ b/vendor/github.com/containers/libtrust/key.go @@ -0,0 +1,253 @@ +package libtrust + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" +) + +// PublicKey is a generic interface for a Public Key. +type PublicKey interface { + // KeyType returns the key type for this key. For elliptic curve keys, + // this value should be "EC". For RSA keys, this value should be "RSA". + KeyType() string + // KeyID returns a distinct identifier which is unique to this Public Key. + // The format generated by this library is a base32 encoding of a 240 bit + // hash of the public key data divided into 12 groups like so: + // ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP + KeyID() string + // Verify verifyies the signature of the data in the io.Reader using this + // Public Key. The alg parameter should identify the digital signature + // algorithm which was used to produce the signature and should be + // supported by this public key. Returns a nil error if the signature + // is valid. + Verify(data io.Reader, alg string, signature []byte) error + // CryptoPublicKey returns the internal object which can be used as a + // crypto.PublicKey for use with other standard library operations. The type + // is either *rsa.PublicKey or *ecdsa.PublicKey + CryptoPublicKey() crypto.PublicKey + // These public keys can be serialized to the standard JSON encoding for + // JSON Web Keys. See section 6 of the IETF draft RFC for JOSE JSON Web + // Algorithms. + MarshalJSON() ([]byte, error) + // These keys can also be serialized to the standard PEM encoding. + PEMBlock() (*pem.Block, error) + // The string representation of a key is its key type and ID. + String() string + AddExtendedField(string, interface{}) + GetExtendedField(string) interface{} +} + +// PrivateKey is a generic interface for a Private Key. +type PrivateKey interface { + // A PrivateKey contains all fields and methods of a PublicKey of the + // same type. The MarshalJSON method also outputs the private key as a + // JSON Web Key, and the PEMBlock method outputs the private key as a + // PEM block. + PublicKey + // PublicKey returns the PublicKey associated with this PrivateKey. + PublicKey() PublicKey + // Sign signs the data read from the io.Reader using a signature algorithm + // supported by the private key. If the specified hashing algorithm is + // supported by this key, that hash function is used to generate the + // signature otherwise the the default hashing algorithm for this key is + // used. Returns the signature and identifier of the algorithm used. + Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) + // CryptoPrivateKey returns the internal object which can be used as a + // crypto.PublicKey for use with other standard library operations. The + // type is either *rsa.PublicKey or *ecdsa.PublicKey + CryptoPrivateKey() crypto.PrivateKey +} + +// FromCryptoPublicKey returns a libtrust PublicKey representation of the given +// *ecdsa.PublicKey or *rsa.PublicKey. Returns a non-nil error when the given +// key is of an unsupported type. +func FromCryptoPublicKey(cryptoPublicKey crypto.PublicKey) (PublicKey, error) { + switch cryptoPublicKey := cryptoPublicKey.(type) { + case *ecdsa.PublicKey: + return fromECPublicKey(cryptoPublicKey) + case *rsa.PublicKey: + return fromRSAPublicKey(cryptoPublicKey), nil + default: + return nil, fmt.Errorf("public key type %T is not supported", cryptoPublicKey) + } +} + +// FromCryptoPrivateKey returns a libtrust PrivateKey representation of the given +// *ecdsa.PrivateKey or *rsa.PrivateKey. Returns a non-nil error when the given +// key is of an unsupported type. +func FromCryptoPrivateKey(cryptoPrivateKey crypto.PrivateKey) (PrivateKey, error) { + switch cryptoPrivateKey := cryptoPrivateKey.(type) { + case *ecdsa.PrivateKey: + return fromECPrivateKey(cryptoPrivateKey) + case *rsa.PrivateKey: + return fromRSAPrivateKey(cryptoPrivateKey), nil + default: + return nil, fmt.Errorf("private key type %T is not supported", cryptoPrivateKey) + } +} + +// UnmarshalPublicKeyPEM parses the PEM encoded data and returns a libtrust +// PublicKey or an error if there is a problem with the encoding. +func UnmarshalPublicKeyPEM(data []byte) (PublicKey, error) { + pemBlock, _ := pem.Decode(data) + if pemBlock == nil { + return nil, errors.New("unable to find PEM encoded data") + } else if pemBlock.Type != "PUBLIC KEY" { + return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type) + } + + return pubKeyFromPEMBlock(pemBlock) +} + +// UnmarshalPublicKeyPEMBundle parses the PEM encoded data as a bundle of +// PEM blocks appended one after the other and returns a slice of PublicKey +// objects that it finds. +func UnmarshalPublicKeyPEMBundle(data []byte) ([]PublicKey, error) { + pubKeys := []PublicKey{} + + for { + var pemBlock *pem.Block + pemBlock, data = pem.Decode(data) + if pemBlock == nil { + break + } else if pemBlock.Type != "PUBLIC KEY" { + return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type) + } + + pubKey, err := pubKeyFromPEMBlock(pemBlock) + if err != nil { + return nil, err + } + + pubKeys = append(pubKeys, pubKey) + } + + return pubKeys, nil +} + +// UnmarshalPrivateKeyPEM parses the PEM encoded data and returns a libtrust +// PrivateKey or an error if there is a problem with the encoding. +func UnmarshalPrivateKeyPEM(data []byte) (PrivateKey, error) { + pemBlock, _ := pem.Decode(data) + if pemBlock == nil { + return nil, errors.New("unable to find PEM encoded data") + } + + var key PrivateKey + + switch { + case pemBlock.Type == "RSA PRIVATE KEY": + rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to decode RSA Private Key PEM data: %s", err) + } + key = fromRSAPrivateKey(rsaPrivateKey) + case pemBlock.Type == "EC PRIVATE KEY": + ecPrivateKey, err := x509.ParseECPrivateKey(pemBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to decode EC Private Key PEM data: %s", err) + } + key, err = fromECPrivateKey(ecPrivateKey) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unable to get PrivateKey from PEM type: %s", pemBlock.Type) + } + + addPEMHeadersToKey(pemBlock, key.PublicKey()) + + return key, nil +} + +// UnmarshalPublicKeyJWK unmarshals the given JSON Web Key into a generic +// Public Key to be used with libtrust. +func UnmarshalPublicKeyJWK(data []byte) (PublicKey, error) { + jwk := make(map[string]interface{}) + + err := json.Unmarshal(data, &jwk) + if err != nil { + return nil, fmt.Errorf( + "decoding JWK Public Key JSON data: %s\n", err, + ) + } + + // Get the Key Type value. + kty, err := stringFromMap(jwk, "kty") + if err != nil { + return nil, fmt.Errorf("JWK Public Key type: %s", err) + } + + switch { + case kty == "EC": + // Call out to unmarshal EC public key. + return ecPublicKeyFromMap(jwk) + case kty == "RSA": + // Call out to unmarshal RSA public key. + return rsaPublicKeyFromMap(jwk) + default: + return nil, fmt.Errorf( + "JWK Public Key type not supported: %q\n", kty, + ) + } +} + +// UnmarshalPublicKeyJWKSet parses the JSON encoded data as a JSON Web Key Set +// and returns a slice of Public Key objects. +func UnmarshalPublicKeyJWKSet(data []byte) ([]PublicKey, error) { + rawKeys, err := loadJSONKeySetRaw(data) + if err != nil { + return nil, err + } + + pubKeys := make([]PublicKey, 0, len(rawKeys)) + + for _, rawKey := range rawKeys { + pubKey, err := UnmarshalPublicKeyJWK(rawKey) + if err != nil { + return nil, err + } + pubKeys = append(pubKeys, pubKey) + } + + return pubKeys, nil +} + +// UnmarshalPrivateKeyJWK unmarshals the given JSON Web Key into a generic +// Private Key to be used with libtrust. +func UnmarshalPrivateKeyJWK(data []byte) (PrivateKey, error) { + jwk := make(map[string]interface{}) + + err := json.Unmarshal(data, &jwk) + if err != nil { + return nil, fmt.Errorf( + "decoding JWK Private Key JSON data: %s\n", err, + ) + } + + // Get the Key Type value. + kty, err := stringFromMap(jwk, "kty") + if err != nil { + return nil, fmt.Errorf("JWK Private Key type: %s", err) + } + + switch { + case kty == "EC": + // Call out to unmarshal EC private key. + return ecPrivateKeyFromMap(jwk) + case kty == "RSA": + // Call out to unmarshal RSA private key. + return rsaPrivateKeyFromMap(jwk) + default: + return nil, fmt.Errorf( + "JWK Private Key type not supported: %q\n", kty, + ) + } +} diff --git a/vendor/github.com/containers/libtrust/key_files.go b/vendor/github.com/containers/libtrust/key_files.go new file mode 100644 index 00000000..c526de54 --- /dev/null +++ b/vendor/github.com/containers/libtrust/key_files.go @@ -0,0 +1,255 @@ +package libtrust + +import ( + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "os" + "strings" +) + +var ( + // ErrKeyFileDoesNotExist indicates that the private key file does not exist. + ErrKeyFileDoesNotExist = errors.New("key file does not exist") +) + +func readKeyFileBytes(filename string) ([]byte, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + if os.IsNotExist(err) { + err = ErrKeyFileDoesNotExist + } else { + err = fmt.Errorf("unable to read key file %s: %s", filename, err) + } + + return nil, err + } + + return data, nil +} + +/* + Loading and Saving of Public and Private Keys in either PEM or JWK format. +*/ + +// LoadKeyFile opens the given filename and attempts to read a Private Key +// encoded in either PEM or JWK format (if .json or .jwk file extension). +func LoadKeyFile(filename string) (PrivateKey, error) { + contents, err := readKeyFileBytes(filename) + if err != nil { + return nil, err + } + + var key PrivateKey + + if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { + key, err = UnmarshalPrivateKeyJWK(contents) + if err != nil { + return nil, fmt.Errorf("unable to decode private key JWK: %s", err) + } + } else { + key, err = UnmarshalPrivateKeyPEM(contents) + if err != nil { + return nil, fmt.Errorf("unable to decode private key PEM: %s", err) + } + } + + return key, nil +} + +// LoadPublicKeyFile opens the given filename and attempts to read a Public Key +// encoded in either PEM or JWK format (if .json or .jwk file extension). +func LoadPublicKeyFile(filename string) (PublicKey, error) { + contents, err := readKeyFileBytes(filename) + if err != nil { + return nil, err + } + + var key PublicKey + + if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { + key, err = UnmarshalPublicKeyJWK(contents) + if err != nil { + return nil, fmt.Errorf("unable to decode public key JWK: %s", err) + } + } else { + key, err = UnmarshalPublicKeyPEM(contents) + if err != nil { + return nil, fmt.Errorf("unable to decode public key PEM: %s", err) + } + } + + return key, nil +} + +// SaveKey saves the given key to a file using the provided filename. +// This process will overwrite any existing file at the provided location. +func SaveKey(filename string, key PrivateKey) error { + var encodedKey []byte + var err error + + if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { + // Encode in JSON Web Key format. + encodedKey, err = json.MarshalIndent(key, "", " ") + if err != nil { + return fmt.Errorf("unable to encode private key JWK: %s", err) + } + } else { + // Encode in PEM format. + pemBlock, err := key.PEMBlock() + if err != nil { + return fmt.Errorf("unable to encode private key PEM: %s", err) + } + encodedKey = pem.EncodeToMemory(pemBlock) + } + + err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0600)) + if err != nil { + return fmt.Errorf("unable to write private key file %s: %s", filename, err) + } + + return nil +} + +// SavePublicKey saves the given public key to the file. +func SavePublicKey(filename string, key PublicKey) error { + var encodedKey []byte + var err error + + if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { + // Encode in JSON Web Key format. + encodedKey, err = json.MarshalIndent(key, "", " ") + if err != nil { + return fmt.Errorf("unable to encode public key JWK: %s", err) + } + } else { + // Encode in PEM format. + pemBlock, err := key.PEMBlock() + if err != nil { + return fmt.Errorf("unable to encode public key PEM: %s", err) + } + encodedKey = pem.EncodeToMemory(pemBlock) + } + + err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0644)) + if err != nil { + return fmt.Errorf("unable to write public key file %s: %s", filename, err) + } + + return nil +} + +// Public Key Set files + +type jwkSet struct { + Keys []json.RawMessage `json:"keys"` +} + +// LoadKeySetFile loads a key set +func LoadKeySetFile(filename string) ([]PublicKey, error) { + if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { + return loadJSONKeySetFile(filename) + } + + // Must be a PEM format file + return loadPEMKeySetFile(filename) +} + +func loadJSONKeySetRaw(data []byte) ([]json.RawMessage, error) { + if len(data) == 0 { + // This is okay, just return an empty slice. + return []json.RawMessage{}, nil + } + + keySet := jwkSet{} + + err := json.Unmarshal(data, &keySet) + if err != nil { + return nil, fmt.Errorf("unable to decode JSON Web Key Set: %s", err) + } + + return keySet.Keys, nil +} + +func loadJSONKeySetFile(filename string) ([]PublicKey, error) { + contents, err := readKeyFileBytes(filename) + if err != nil && err != ErrKeyFileDoesNotExist { + return nil, err + } + + return UnmarshalPublicKeyJWKSet(contents) +} + +func loadPEMKeySetFile(filename string) ([]PublicKey, error) { + data, err := readKeyFileBytes(filename) + if err != nil && err != ErrKeyFileDoesNotExist { + return nil, err + } + + return UnmarshalPublicKeyPEMBundle(data) +} + +// AddKeySetFile adds a key to a key set +func AddKeySetFile(filename string, key PublicKey) error { + if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { + return addKeySetJSONFile(filename, key) + } + + // Must be a PEM format file + return addKeySetPEMFile(filename, key) +} + +func addKeySetJSONFile(filename string, key PublicKey) error { + encodedKey, err := json.Marshal(key) + if err != nil { + return fmt.Errorf("unable to encode trusted client key: %s", err) + } + + contents, err := readKeyFileBytes(filename) + if err != nil && err != ErrKeyFileDoesNotExist { + return err + } + + rawEntries, err := loadJSONKeySetRaw(contents) + if err != nil { + return err + } + + rawEntries = append(rawEntries, json.RawMessage(encodedKey)) + entriesWrapper := jwkSet{Keys: rawEntries} + + encodedEntries, err := json.MarshalIndent(entriesWrapper, "", " ") + if err != nil { + return fmt.Errorf("unable to encode trusted client keys: %s", err) + } + + err = ioutil.WriteFile(filename, encodedEntries, os.FileMode(0644)) + if err != nil { + return fmt.Errorf("unable to write trusted client keys file %s: %s", filename, err) + } + + return nil +} + +func addKeySetPEMFile(filename string, key PublicKey) error { + // Encode to PEM, open file for appending, write PEM. + file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.FileMode(0644)) + if err != nil { + return fmt.Errorf("unable to open trusted client keys file %s: %s", filename, err) + } + defer file.Close() + + pemBlock, err := key.PEMBlock() + if err != nil { + return fmt.Errorf("unable to encoded trusted key: %s", err) + } + + _, err = file.Write(pem.EncodeToMemory(pemBlock)) + if err != nil { + return fmt.Errorf("unable to write trusted keys file: %s", err) + } + + return nil +} diff --git a/vendor/github.com/containers/libtrust/key_manager.go b/vendor/github.com/containers/libtrust/key_manager.go new file mode 100644 index 00000000..9a98ae35 --- /dev/null +++ b/vendor/github.com/containers/libtrust/key_manager.go @@ -0,0 +1,175 @@ +package libtrust + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "os" + "path" + "sync" +) + +// ClientKeyManager manages client keys on the filesystem +type ClientKeyManager struct { + key PrivateKey + clientFile string + clientDir string + + clientLock sync.RWMutex + clients []PublicKey + + configLock sync.Mutex + configs []*tls.Config +} + +// NewClientKeyManager loads a new manager from a set of key files +// and managed by the given private key. +func NewClientKeyManager(trustKey PrivateKey, clientFile, clientDir string) (*ClientKeyManager, error) { + m := &ClientKeyManager{ + key: trustKey, + clientFile: clientFile, + clientDir: clientDir, + } + if err := m.loadKeys(); err != nil { + return nil, err + } + // TODO Start watching file and directory + + return m, nil +} + +func (c *ClientKeyManager) loadKeys() (err error) { + // Load authorized keys file + var clients []PublicKey + if c.clientFile != "" { + clients, err = LoadKeySetFile(c.clientFile) + if err != nil { + return fmt.Errorf("unable to load authorized keys: %s", err) + } + } + + // Add clients from authorized keys directory + files, err := ioutil.ReadDir(c.clientDir) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("unable to open authorized keys directory: %s", err) + } + for _, f := range files { + if !f.IsDir() { + publicKey, err := LoadPublicKeyFile(path.Join(c.clientDir, f.Name())) + if err != nil { + return fmt.Errorf("unable to load authorized key file: %s", err) + } + clients = append(clients, publicKey) + } + } + + c.clientLock.Lock() + c.clients = clients + c.clientLock.Unlock() + + return nil +} + +// RegisterTLSConfig registers a tls configuration to manager +// such that any changes to the keys may be reflected in +// the tls client CA pool +func (c *ClientKeyManager) RegisterTLSConfig(tlsConfig *tls.Config) error { + c.clientLock.RLock() + certPool, err := GenerateCACertPool(c.key, c.clients) + if err != nil { + return fmt.Errorf("CA pool generation error: %s", err) + } + c.clientLock.RUnlock() + + tlsConfig.ClientCAs = certPool + + c.configLock.Lock() + c.configs = append(c.configs, tlsConfig) + c.configLock.Unlock() + + return nil +} + +// NewIdentityAuthTLSConfig creates a tls.Config for the server to use for +// libtrust identity authentication for the domain specified +func NewIdentityAuthTLSConfig(trustKey PrivateKey, clients *ClientKeyManager, addr string, domain string) (*tls.Config, error) { + tlsConfig := newTLSConfig() + + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + if err := clients.RegisterTLSConfig(tlsConfig); err != nil { + return nil, err + } + + // Generate cert + ips, domains, err := parseAddr(addr) + if err != nil { + return nil, err + } + // add domain that it expects clients to use + domains = append(domains, domain) + x509Cert, err := GenerateSelfSignedServerCert(trustKey, domains, ips) + if err != nil { + return nil, fmt.Errorf("certificate generation error: %s", err) + } + tlsConfig.Certificates = []tls.Certificate{{ + Certificate: [][]byte{x509Cert.Raw}, + PrivateKey: trustKey.CryptoPrivateKey(), + Leaf: x509Cert, + }} + + return tlsConfig, nil +} + +// NewCertAuthTLSConfig creates a tls.Config for the server to use for +// certificate authentication +func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) { + tlsConfig := newTLSConfig() + + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", certPath, keyPath, err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + + // Verify client certificates against a CA? + if caPath != "" { + certPool := x509.NewCertPool() + file, err := ioutil.ReadFile(caPath) + if err != nil { + return nil, fmt.Errorf("Couldn't read CA certificate: %s", err) + } + certPool.AppendCertsFromPEM(file) + + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = certPool + } + + return tlsConfig, nil +} + +func newTLSConfig() *tls.Config { + return &tls.Config{ + NextProtos: []string{"http/1.1"}, + // Avoid fallback on insecure SSL protocols + MinVersion: tls.VersionTLS10, + } +} + +// parseAddr parses an address into an array of IPs and domains +func parseAddr(addr string) ([]net.IP, []string, error) { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, nil, err + } + var domains []string + var ips []net.IP + ip := net.ParseIP(host) + if ip != nil { + ips = []net.IP{ip} + } else { + domains = []string{host} + } + return ips, domains, nil +} diff --git a/vendor/github.com/containers/libtrust/rsa_key.go b/vendor/github.com/containers/libtrust/rsa_key.go new file mode 100644 index 00000000..dac4cacf --- /dev/null +++ b/vendor/github.com/containers/libtrust/rsa_key.go @@ -0,0 +1,427 @@ +package libtrust + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" + "math/big" +) + +/* + * RSA DSA PUBLIC KEY + */ + +// rsaPublicKey implements a JWK Public Key using RSA digital signature algorithms. +type rsaPublicKey struct { + *rsa.PublicKey + extended map[string]interface{} +} + +func fromRSAPublicKey(cryptoPublicKey *rsa.PublicKey) *rsaPublicKey { + return &rsaPublicKey{cryptoPublicKey, map[string]interface{}{}} +} + +// KeyType returns the JWK key type for RSA keys, i.e., "RSA". +func (k *rsaPublicKey) KeyType() string { + return "RSA" +} + +// KeyID returns a distinct identifier which is unique to this Public Key. +func (k *rsaPublicKey) KeyID() string { + return keyIDFromCryptoKey(k) +} + +func (k *rsaPublicKey) String() string { + return fmt.Sprintf("RSA Public Key <%s>", k.KeyID()) +} + +// Verify verifyies the signature of the data in the io.Reader using this Public Key. +// The alg parameter should be the name of the JWA digital signature algorithm +// which was used to produce the signature and should be supported by this +// public key. Returns a nil error if the signature is valid. +func (k *rsaPublicKey) Verify(data io.Reader, alg string, signature []byte) error { + // Verify the signature of the given date, return non-nil error if valid. + sigAlg, err := rsaSignatureAlgorithmByName(alg) + if err != nil { + return fmt.Errorf("unable to verify Signature: %s", err) + } + + hasher := sigAlg.HashID().New() + _, err = io.Copy(hasher, data) + if err != nil { + return fmt.Errorf("error reading data to sign: %s", err) + } + hash := hasher.Sum(nil) + + err = rsa.VerifyPKCS1v15(k.PublicKey, sigAlg.HashID(), hash, signature) + if err != nil { + return fmt.Errorf("invalid %s signature: %s", sigAlg.HeaderParam(), err) + } + + return nil +} + +// CryptoPublicKey returns the internal object which can be used as a +// crypto.PublicKey for use with other standard library operations. The type +// is either *rsa.PublicKey or *ecdsa.PublicKey +func (k *rsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return k.PublicKey +} + +func (k *rsaPublicKey) toMap() map[string]interface{} { + jwk := make(map[string]interface{}) + for k, v := range k.extended { + jwk[k] = v + } + jwk["kty"] = k.KeyType() + jwk["kid"] = k.KeyID() + jwk["n"] = joseBase64UrlEncode(k.N.Bytes()) + jwk["e"] = joseBase64UrlEncode(serializeRSAPublicExponentParam(k.E)) + + return jwk +} + +// MarshalJSON serializes this Public Key using the JWK JSON serialization format for +// RSA keys. +func (k *rsaPublicKey) MarshalJSON() (data []byte, err error) { + return json.Marshal(k.toMap()) +} + +// PEMBlock serializes this Public Key to DER-encoded PKIX format. +func (k *rsaPublicKey) PEMBlock() (*pem.Block, error) { + derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey) + if err != nil { + return nil, fmt.Errorf("unable to serialize RSA PublicKey to DER-encoded PKIX format: %s", err) + } + k.extended["kid"] = k.KeyID() // For display purposes. + return createPemBlock("PUBLIC KEY", derBytes, k.extended) +} + +func (k *rsaPublicKey) AddExtendedField(field string, value interface{}) { + k.extended[field] = value +} + +func (k *rsaPublicKey) GetExtendedField(field string) interface{} { + v, ok := k.extended[field] + if !ok { + return nil + } + return v +} + +func rsaPublicKeyFromMap(jwk map[string]interface{}) (*rsaPublicKey, error) { + // JWK key type (kty) has already been determined to be "RSA". + // Need to extract 'n', 'e', and 'kid' and check for + // consistency. + + // Get the modulus parameter N. + nB64Url, err := stringFromMap(jwk, "n") + if err != nil { + return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err) + } + + n, err := parseRSAModulusParam(nB64Url) + if err != nil { + return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err) + } + + // Get the public exponent E. + eB64Url, err := stringFromMap(jwk, "e") + if err != nil { + return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err) + } + + e, err := parseRSAPublicExponentParam(eB64Url) + if err != nil { + return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err) + } + + key := &rsaPublicKey{ + PublicKey: &rsa.PublicKey{N: n, E: e}, + } + + // Key ID is optional, but if it exists, it should match the key. + _, ok := jwk["kid"] + if ok { + kid, err := stringFromMap(jwk, "kid") + if err != nil { + return nil, fmt.Errorf("JWK RSA Public Key ID: %s", err) + } + if kid != key.KeyID() { + return nil, fmt.Errorf("JWK RSA Public Key ID does not match: %s", kid) + } + } + + if _, ok := jwk["d"]; ok { + return nil, fmt.Errorf("JWK RSA Public Key cannot contain private exponent") + } + + key.extended = jwk + + return key, nil +} + +/* + * RSA DSA PRIVATE KEY + */ + +// rsaPrivateKey implements a JWK Private Key using RSA digital signature algorithms. +type rsaPrivateKey struct { + rsaPublicKey + *rsa.PrivateKey +} + +func fromRSAPrivateKey(cryptoPrivateKey *rsa.PrivateKey) *rsaPrivateKey { + return &rsaPrivateKey{ + *fromRSAPublicKey(&cryptoPrivateKey.PublicKey), + cryptoPrivateKey, + } +} + +// PublicKey returns the Public Key data associated with this Private Key. +func (k *rsaPrivateKey) PublicKey() PublicKey { + return &k.rsaPublicKey +} + +func (k *rsaPrivateKey) String() string { + return fmt.Sprintf("RSA Private Key <%s>", k.KeyID()) +} + +// Sign signs the data read from the io.Reader using a signature algorithm supported +// by the RSA private key. If the specified hashing algorithm is supported by +// this key, that hash function is used to generate the signature otherwise the +// the default hashing algorithm for this key is used. Returns the signature +// and the name of the JWK signature algorithm used, e.g., "RS256", "RS384", +// "RS512". +func (k *rsaPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) { + // Generate a signature of the data using the internal alg. + sigAlg := rsaPKCS1v15SignatureAlgorithmForHashID(hashID) + hasher := sigAlg.HashID().New() + + _, err = io.Copy(hasher, data) + if err != nil { + return nil, "", fmt.Errorf("error reading data to sign: %s", err) + } + hash := hasher.Sum(nil) + + signature, err = rsa.SignPKCS1v15(rand.Reader, k.PrivateKey, sigAlg.HashID(), hash) + if err != nil { + return nil, "", fmt.Errorf("error producing signature: %s", err) + } + + alg = sigAlg.HeaderParam() + + return +} + +// CryptoPrivateKey returns the internal object which can be used as a +// crypto.PublicKey for use with other standard library operations. The type +// is either *rsa.PublicKey or *ecdsa.PublicKey +func (k *rsaPrivateKey) CryptoPrivateKey() crypto.PrivateKey { + return k.PrivateKey +} + +func (k *rsaPrivateKey) toMap() map[string]interface{} { + k.Precompute() // Make sure the precomputed values are stored. + jwk := k.rsaPublicKey.toMap() + + jwk["d"] = joseBase64UrlEncode(k.D.Bytes()) + jwk["p"] = joseBase64UrlEncode(k.Primes[0].Bytes()) + jwk["q"] = joseBase64UrlEncode(k.Primes[1].Bytes()) + jwk["dp"] = joseBase64UrlEncode(k.Precomputed.Dp.Bytes()) + jwk["dq"] = joseBase64UrlEncode(k.Precomputed.Dq.Bytes()) + jwk["qi"] = joseBase64UrlEncode(k.Precomputed.Qinv.Bytes()) + + otherPrimes := k.Primes[2:] + + if len(otherPrimes) > 0 { + otherPrimesInfo := make([]interface{}, len(otherPrimes)) + for i, r := range otherPrimes { + otherPrimeInfo := make(map[string]string, 3) + otherPrimeInfo["r"] = joseBase64UrlEncode(r.Bytes()) + crtVal := k.Precomputed.CRTValues[i] + otherPrimeInfo["d"] = joseBase64UrlEncode(crtVal.Exp.Bytes()) + otherPrimeInfo["t"] = joseBase64UrlEncode(crtVal.Coeff.Bytes()) + otherPrimesInfo[i] = otherPrimeInfo + } + jwk["oth"] = otherPrimesInfo + } + + return jwk +} + +// MarshalJSON serializes this Private Key using the JWK JSON serialization format for +// RSA keys. +func (k *rsaPrivateKey) MarshalJSON() (data []byte, err error) { + return json.Marshal(k.toMap()) +} + +// PEMBlock serializes this Private Key to DER-encoded PKIX format. +func (k *rsaPrivateKey) PEMBlock() (*pem.Block, error) { + derBytes := x509.MarshalPKCS1PrivateKey(k.PrivateKey) + k.extended["keyID"] = k.KeyID() // For display purposes. + return createPemBlock("RSA PRIVATE KEY", derBytes, k.extended) +} + +func rsaPrivateKeyFromMap(jwk map[string]interface{}) (*rsaPrivateKey, error) { + // The JWA spec for RSA Private Keys (draft rfc section 5.3.2) states that + // only the private key exponent 'd' is REQUIRED, the others are just for + // signature/decryption optimizations and SHOULD be included when the JWK + // is produced. We MAY choose to accept a JWK which only includes 'd', but + // we're going to go ahead and not choose to accept it without the extra + // fields. Only the 'oth' field will be optional (for multi-prime keys). + privateExponent, err := parseRSAPrivateKeyParamFromMap(jwk, "d") + if err != nil { + return nil, fmt.Errorf("JWK RSA Private Key exponent: %s", err) + } + firstPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "p") + if err != nil { + return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err) + } + secondPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "q") + if err != nil { + return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err) + } + firstFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dp") + if err != nil { + return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err) + } + secondFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dq") + if err != nil { + return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err) + } + crtCoeff, err := parseRSAPrivateKeyParamFromMap(jwk, "qi") + if err != nil { + return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err) + } + + var oth interface{} + if _, ok := jwk["oth"]; ok { + oth = jwk["oth"] + delete(jwk, "oth") + } + + // JWK key type (kty) has already been determined to be "RSA". + // Need to extract the public key information, then extract the private + // key values. + publicKey, err := rsaPublicKeyFromMap(jwk) + if err != nil { + return nil, err + } + + privateKey := &rsa.PrivateKey{ + PublicKey: *publicKey.PublicKey, + D: privateExponent, + Primes: []*big.Int{firstPrimeFactor, secondPrimeFactor}, + Precomputed: rsa.PrecomputedValues{ + Dp: firstFactorCRT, + Dq: secondFactorCRT, + Qinv: crtCoeff, + }, + } + + if oth != nil { + // Should be an array of more JSON objects. + otherPrimesInfo, ok := oth.([]interface{}) + if !ok { + return nil, errors.New("JWK RSA Private Key: Invalid other primes info: must be an array") + } + numOtherPrimeFactors := len(otherPrimesInfo) + if numOtherPrimeFactors == 0 { + return nil, errors.New("JWK RSA Privake Key: Invalid other primes info: must be absent or non-empty") + } + otherPrimeFactors := make([]*big.Int, numOtherPrimeFactors) + productOfPrimes := new(big.Int).Mul(firstPrimeFactor, secondPrimeFactor) + crtValues := make([]rsa.CRTValue, numOtherPrimeFactors) + + for i, val := range otherPrimesInfo { + otherPrimeinfo, ok := val.(map[string]interface{}) + if !ok { + return nil, errors.New("JWK RSA Private Key: Invalid other prime info: must be a JSON object") + } + + otherPrimeFactor, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "r") + if err != nil { + return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err) + } + otherFactorCRT, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "d") + if err != nil { + return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err) + } + otherCrtCoeff, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "t") + if err != nil { + return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err) + } + + crtValue := crtValues[i] + crtValue.Exp = otherFactorCRT + crtValue.Coeff = otherCrtCoeff + crtValue.R = productOfPrimes + otherPrimeFactors[i] = otherPrimeFactor + productOfPrimes = new(big.Int).Mul(productOfPrimes, otherPrimeFactor) + } + + privateKey.Primes = append(privateKey.Primes, otherPrimeFactors...) + privateKey.Precomputed.CRTValues = crtValues + } + + key := &rsaPrivateKey{ + rsaPublicKey: *publicKey, + PrivateKey: privateKey, + } + + return key, nil +} + +/* + * Key Generation Functions. + */ + +func generateRSAPrivateKey(bits int) (k *rsaPrivateKey, err error) { + k = new(rsaPrivateKey) + k.PrivateKey, err = rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, err + } + + k.rsaPublicKey.PublicKey = &k.PrivateKey.PublicKey + k.extended = make(map[string]interface{}) + + return +} + +// GenerateRSA2048PrivateKey generates a key pair using 2048-bit RSA. +func GenerateRSA2048PrivateKey() (PrivateKey, error) { + k, err := generateRSAPrivateKey(2048) + if err != nil { + return nil, fmt.Errorf("error generating RSA 2048-bit key: %s", err) + } + + return k, nil +} + +// GenerateRSA3072PrivateKey generates a key pair using 3072-bit RSA. +func GenerateRSA3072PrivateKey() (PrivateKey, error) { + k, err := generateRSAPrivateKey(3072) + if err != nil { + return nil, fmt.Errorf("error generating RSA 3072-bit key: %s", err) + } + + return k, nil +} + +// GenerateRSA4096PrivateKey generates a key pair using 4096-bit RSA. +func GenerateRSA4096PrivateKey() (PrivateKey, error) { + k, err := generateRSAPrivateKey(4096) + if err != nil { + return nil, fmt.Errorf("error generating RSA 4096-bit key: %s", err) + } + + return k, nil +} diff --git a/vendor/github.com/containers/libtrust/util.go b/vendor/github.com/containers/libtrust/util.go new file mode 100644 index 00000000..a5a101d3 --- /dev/null +++ b/vendor/github.com/containers/libtrust/util.go @@ -0,0 +1,363 @@ +package libtrust + +import ( + "bytes" + "crypto" + "crypto/elliptic" + "crypto/tls" + "crypto/x509" + "encoding/base32" + "encoding/base64" + "encoding/binary" + "encoding/pem" + "errors" + "fmt" + "math/big" + "net/url" + "os" + "path/filepath" + "strings" + "time" +) + +// LoadOrCreateTrustKey will load a PrivateKey from the specified path +func LoadOrCreateTrustKey(trustKeyPath string) (PrivateKey, error) { + if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil { + return nil, err + } + + trustKey, err := LoadKeyFile(trustKeyPath) + if err == ErrKeyFileDoesNotExist { + trustKey, err = GenerateECP256PrivateKey() + if err != nil { + return nil, fmt.Errorf("error generating key: %s", err) + } + + if err := SaveKey(trustKeyPath, trustKey); err != nil { + return nil, fmt.Errorf("error saving key file: %s", err) + } + + dir, file := filepath.Split(trustKeyPath) + if err := SavePublicKey(filepath.Join(dir, "public-"+file), trustKey.PublicKey()); err != nil { + return nil, fmt.Errorf("error saving public key file: %s", err) + } + } else if err != nil { + return nil, fmt.Errorf("error loading key file: %s", err) + } + return trustKey, nil +} + +// NewIdentityAuthTLSClientConfig returns a tls.Config configured to use identity +// based authentication from the specified dockerUrl, the rootConfigPath and +// the server name to which it is connecting. +// If trustUnknownHosts is true it will automatically add the host to the +// known-hosts.json in rootConfigPath. +func NewIdentityAuthTLSClientConfig(dockerUrl string, trustUnknownHosts bool, rootConfigPath string, serverName string) (*tls.Config, error) { + tlsConfig := newTLSConfig() + + trustKeyPath := filepath.Join(rootConfigPath, "key.json") + knownHostsPath := filepath.Join(rootConfigPath, "known-hosts.json") + + u, err := url.Parse(dockerUrl) + if err != nil { + return nil, fmt.Errorf("unable to parse machine url") + } + + if u.Scheme == "unix" { + return nil, nil + } + + addr := u.Host + proto := "tcp" + + trustKey, err := LoadOrCreateTrustKey(trustKeyPath) + if err != nil { + return nil, fmt.Errorf("unable to load trust key: %s", err) + } + + knownHosts, err := LoadKeySetFile(knownHostsPath) + if err != nil { + return nil, fmt.Errorf("could not load trusted hosts file: %s", err) + } + + allowedHosts, err := FilterByHosts(knownHosts, addr, false) + if err != nil { + return nil, fmt.Errorf("error filtering hosts: %s", err) + } + + certPool, err := GenerateCACertPool(trustKey, allowedHosts) + if err != nil { + return nil, fmt.Errorf("Could not create CA pool: %s", err) + } + + tlsConfig.ServerName = serverName + tlsConfig.RootCAs = certPool + + x509Cert, err := GenerateSelfSignedClientCert(trustKey) + if err != nil { + return nil, fmt.Errorf("certificate generation error: %s", err) + } + + tlsConfig.Certificates = []tls.Certificate{{ + Certificate: [][]byte{x509Cert.Raw}, + PrivateKey: trustKey.CryptoPrivateKey(), + Leaf: x509Cert, + }} + + tlsConfig.InsecureSkipVerify = true + + testConn, err := tls.Dial(proto, addr, tlsConfig) + if err != nil { + return nil, fmt.Errorf("tls Handshake error: %s", err) + } + + opts := x509.VerifyOptions{ + Roots: tlsConfig.RootCAs, + CurrentTime: time.Now(), + DNSName: tlsConfig.ServerName, + Intermediates: x509.NewCertPool(), + } + + certs := testConn.ConnectionState().PeerCertificates + for i, cert := range certs { + if i == 0 { + continue + } + opts.Intermediates.AddCert(cert) + } + + if _, err := certs[0].Verify(opts); err != nil { + if _, ok := err.(x509.UnknownAuthorityError); ok { + if trustUnknownHosts { + pubKey, err := FromCryptoPublicKey(certs[0].PublicKey) + if err != nil { + return nil, fmt.Errorf("error extracting public key from cert: %s", err) + } + + pubKey.AddExtendedField("hosts", []string{addr}) + + if err := AddKeySetFile(knownHostsPath, pubKey); err != nil { + return nil, fmt.Errorf("error adding machine to known hosts: %s", err) + } + } else { + return nil, fmt.Errorf("unable to connect. unknown host: %s", addr) + } + } + } + + testConn.Close() + tlsConfig.InsecureSkipVerify = false + + return tlsConfig, nil +} + +// joseBase64UrlEncode encodes the given data using the standard base64 url +// encoding format but with all trailing '=' characters omitted in accordance +// with the jose specification. +// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2 +func joseBase64UrlEncode(b []byte) string { + return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") +} + +// joseBase64UrlDecode decodes the given string using the standard base64 url +// decoder but first adds the appropriate number of trailing '=' characters in +// accordance with the jose specification. +// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2 +func joseBase64UrlDecode(s string) ([]byte, error) { + s = strings.Replace(s, "\n", "", -1) + s = strings.Replace(s, " ", "", -1) + switch len(s) % 4 { + case 0: + case 2: + s += "==" + case 3: + s += "=" + default: + return nil, errors.New("illegal base64url string") + } + return base64.URLEncoding.DecodeString(s) +} + +func keyIDEncode(b []byte) string { + s := strings.TrimRight(base32.StdEncoding.EncodeToString(b), "=") + var buf bytes.Buffer + var i int + for i = 0; i < len(s)/4-1; i++ { + start := i * 4 + end := start + 4 + buf.WriteString(s[start:end] + ":") + } + buf.WriteString(s[i*4:]) + return buf.String() +} + +func keyIDFromCryptoKey(pubKey PublicKey) string { + // Generate and return a 'libtrust' fingerprint of the public key. + // For an RSA key this should be: + // SHA256(DER encoded ASN1) + // Then truncated to 240 bits and encoded into 12 base32 groups like so: + // ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP + derBytes, err := x509.MarshalPKIXPublicKey(pubKey.CryptoPublicKey()) + if err != nil { + return "" + } + hasher := crypto.SHA256.New() + hasher.Write(derBytes) + return keyIDEncode(hasher.Sum(nil)[:30]) +} + +func stringFromMap(m map[string]interface{}, key string) (string, error) { + val, ok := m[key] + if !ok { + return "", fmt.Errorf("%q value not specified", key) + } + + str, ok := val.(string) + if !ok { + return "", fmt.Errorf("%q value must be a string", key) + } + delete(m, key) + + return str, nil +} + +func parseECCoordinate(cB64Url string, curve elliptic.Curve) (*big.Int, error) { + curveByteLen := (curve.Params().BitSize + 7) >> 3 + + cBytes, err := joseBase64UrlDecode(cB64Url) + if err != nil { + return nil, fmt.Errorf("invalid base64 URL encoding: %s", err) + } + cByteLength := len(cBytes) + if cByteLength != curveByteLen { + return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", cByteLength, curveByteLen) + } + return new(big.Int).SetBytes(cBytes), nil +} + +func parseECPrivateParam(dB64Url string, curve elliptic.Curve) (*big.Int, error) { + dBytes, err := joseBase64UrlDecode(dB64Url) + if err != nil { + return nil, fmt.Errorf("invalid base64 URL encoding: %s", err) + } + + // The length of this octet string MUST be ceiling(log-base-2(n)/8) + // octets (where n is the order of the curve). This is because the private + // key d must be in the interval [1, n-1] so the bitlength of d should be + // no larger than the bitlength of n-1. The easiest way to find the octet + // length is to take bitlength(n-1), add 7 to force a carry, and shift this + // bit sequence right by 3, which is essentially dividing by 8 and adding + // 1 if there is any remainder. Thus, the private key value d should be + // output to (bitlength(n-1)+7)>>3 octets. + n := curve.Params().N + octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3 + dByteLength := len(dBytes) + + if dByteLength != octetLength { + return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", dByteLength, octetLength) + } + + return new(big.Int).SetBytes(dBytes), nil +} + +func parseRSAModulusParam(nB64Url string) (*big.Int, error) { + nBytes, err := joseBase64UrlDecode(nB64Url) + if err != nil { + return nil, fmt.Errorf("invalid base64 URL encoding: %s", err) + } + + return new(big.Int).SetBytes(nBytes), nil +} + +func serializeRSAPublicExponentParam(e int) []byte { + // We MUST use the minimum number of octets to represent E. + // E is supposed to be 65537 for performance and security reasons + // and is what golang's rsa package generates, but it might be + // different if imported from some other generator. + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, uint32(e)) + var i int + for i = 0; i < 8; i++ { + if buf[i] != 0 { + break + } + } + return buf[i:] +} + +func parseRSAPublicExponentParam(eB64Url string) (int, error) { + eBytes, err := joseBase64UrlDecode(eB64Url) + if err != nil { + return 0, fmt.Errorf("invalid base64 URL encoding: %s", err) + } + // Only the minimum number of bytes were used to represent E, but + // binary.BigEndian.Uint32 expects at least 4 bytes, so we need + // to add zero padding if necassary. + byteLen := len(eBytes) + buf := make([]byte, 4-byteLen, 4) + eBytes = append(buf, eBytes...) + + return int(binary.BigEndian.Uint32(eBytes)), nil +} + +func parseRSAPrivateKeyParamFromMap(m map[string]interface{}, key string) (*big.Int, error) { + b64Url, err := stringFromMap(m, key) + if err != nil { + return nil, err + } + + paramBytes, err := joseBase64UrlDecode(b64Url) + if err != nil { + return nil, fmt.Errorf("invaled base64 URL encoding: %s", err) + } + + return new(big.Int).SetBytes(paramBytes), nil +} + +func createPemBlock(name string, derBytes []byte, headers map[string]interface{}) (*pem.Block, error) { + pemBlock := &pem.Block{Type: name, Bytes: derBytes, Headers: map[string]string{}} + for k, v := range headers { + switch val := v.(type) { + case string: + pemBlock.Headers[k] = val + case []string: + if k == "hosts" { + pemBlock.Headers[k] = strings.Join(val, ",") + } else { + // Return error, non-encodable type + } + default: + // Return error, non-encodable type + } + } + + return pemBlock, nil +} + +func pubKeyFromPEMBlock(pemBlock *pem.Block) (PublicKey, error) { + cryptoPublicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to decode Public Key PEM data: %s", err) + } + + pubKey, err := FromCryptoPublicKey(cryptoPublicKey) + if err != nil { + return nil, err + } + + addPEMHeadersToKey(pemBlock, pubKey) + + return pubKey, nil +} + +func addPEMHeadersToKey(pemBlock *pem.Block, pubKey PublicKey) { + for key, value := range pemBlock.Headers { + var safeVal interface{} + if key == "hosts" { + safeVal = strings.Split(value, ",") + } else { + safeVal = value + } + pubKey.AddExtendedField(key, safeVal) + } +} diff --git a/vendor/github.com/containers/luksy/.cirrus.yml b/vendor/github.com/containers/luksy/.cirrus.yml new file mode 100644 index 00000000..f9f701dd --- /dev/null +++ b/vendor/github.com/containers/luksy/.cirrus.yml @@ -0,0 +1,34 @@ +docker_builder: + name: CI + env: + HOME: /root + DEBIAN_FRONTEND: noninteractive + CIRRUS_LOG_TIMESTAMP: true + GOVERSION: 1.23 + PATH: /usr/lib/go-1.23/bin:$PATH + setup_script: | + apt-get -q update + apt-get -q install -y bats cryptsetup golang-${GOVERSION} + go version + make + unit_test_script: | + go test -timeout 45m -v -cover + case $(go env GOARCH) in + amd64) + otherarch=386;; + arm64) + otherarch=arm;; + mips64) + otherarch=mips;; + mips64le) + otherarch=mipsle;; + esac + if test -n "$otherarch" ; then + echo running unit tests again with GOARCH=$otherarch + GOARCH=$otherarch go test -timeout 45m -v -cover + fi + : + defaults_script: | + bats -f defaults ./tests + aes_script: | + bats -f aes ./tests diff --git a/vendor/github.com/containers/luksy/.dockerignore b/vendor/github.com/containers/luksy/.dockerignore new file mode 100644 index 00000000..24276303 --- /dev/null +++ b/vendor/github.com/containers/luksy/.dockerignore @@ -0,0 +1,2 @@ +lukstool +lukstool.test diff --git a/vendor/github.com/containers/luksy/.gitignore b/vendor/github.com/containers/luksy/.gitignore new file mode 100644 index 00000000..3b735ec4 --- /dev/null +++ b/vendor/github.com/containers/luksy/.gitignore @@ -0,0 +1,21 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work diff --git a/vendor/github.com/containers/luksy/Dockerfile b/vendor/github.com/containers/luksy/Dockerfile new file mode 100644 index 00000000..16ca5c7c --- /dev/null +++ b/vendor/github.com/containers/luksy/Dockerfile @@ -0,0 +1,7 @@ +FROM registry.fedoraproject.org/fedora +RUN dnf -y install golang make +WORKDIR /go/src/github.com/containers/luksy/ +COPY / /go/src/github.com/containers/luksy/ +RUN make clean all +FROM registry.fedoraproject.org/fedora-minimal +COPY --from=0 /go/src/github.com/containers/luksy/ /usr/local/bin/ diff --git a/vendor/github.com/containers/luksy/LICENSE b/vendor/github.com/containers/luksy/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/containers/luksy/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/luksy/Makefile b/vendor/github.com/containers/luksy/Makefile new file mode 100644 index 00000000..b3563c2e --- /dev/null +++ b/vendor/github.com/containers/luksy/Makefile @@ -0,0 +1,14 @@ +GO = go +BATS = bats + +all: luksy + +luksy: cmd/luksy/*.go *.go + $(GO) build -o luksy$(shell go env GOEXE) ./cmd/luksy + +clean: + $(RM) luksy$(shell go env GOEXE) luksy.test + +test: + $(GO) test -timeout 45m -v -cover + $(BATS) ./tests diff --git a/vendor/github.com/containers/luksy/OWNERS b/vendor/github.com/containers/luksy/OWNERS new file mode 100644 index 00000000..eca8673e --- /dev/null +++ b/vendor/github.com/containers/luksy/OWNERS @@ -0,0 +1,4 @@ +approvers: + - nalind +reviewers: + - nalind diff --git a/vendor/github.com/containers/luksy/README.md b/vendor/github.com/containers/luksy/README.md new file mode 100644 index 00000000..2bf3a438 --- /dev/null +++ b/vendor/github.com/containers/luksy/README.md @@ -0,0 +1,10 @@ +luksy: offline encryption/decryption using LUKS formats [![Cirrus CI Status](https://img.shields.io/cirrus/github/containers/luksy/main)](https://cirrus-ci.com/github/containers/luksy/main) +- +luksy implements encryption and decryption using LUKSv1 and LUKSv2 formats. +Think of it as a clunkier cousin of gzip/bzip2/xz that doesn't actually produce +smaller output than input, but it encrypts, and that's nice. + +* The main goal is to be able to encrypt/decrypt when we don't have access to + the Linux device mapper. Duplicating functions of cryptsetup that it can + perform without accessing the Linux device mapper is not a priority. +* If you can use cryptsetup instead, use cryptsetup instead. diff --git a/vendor/github.com/containers/luksy/decrypt.go b/vendor/github.com/containers/luksy/decrypt.go new file mode 100644 index 00000000..b36c4f50 --- /dev/null +++ b/vendor/github.com/containers/luksy/decrypt.go @@ -0,0 +1,255 @@ +package luksy + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "strconv" + + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/pbkdf2" +) + +// ReaderAtSeekCloser is a combination of io.ReaderAt, io.Seeker, and io.Closer, +// which is all we really need from an encrypted file. +type ReaderAtSeekCloser interface { + io.ReaderAt + io.Seeker + io.Closer +} + +// Decrypt attempts to verify the specified password using information from the +// header and read from the specified file. +// +// Returns a function which will decrypt payload blocks in succession, the size +// of chunks of data that the function expects, the offset in the file where +// the payload begins, and the size of the payload, assuming the payload runs +// to the end of the file. +func (h V1Header) Decrypt(password string, f ReaderAtSeekCloser) (func([]byte) ([]byte, error), int, int64, int64, error) { + size, err := f.Seek(0, io.SeekEnd) + if err != nil { + return nil, -1, -1, -1, err + } + hasher, err := hasherByName(h.HashSpec()) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", h.HashSpec(), err) + } + + activeKeys := 0 + for k := 0; k < v1NumKeys; k++ { + keyslot, err := h.KeySlot(k) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("reading key slot %d: %w", k, err) + } + active, err := keyslot.Active() + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("checking if key slot %d is active: %w", k, err) + } + if !active { + continue + } + activeKeys++ + + passwordDerived := pbkdf2.Key([]byte(password), keyslot.KeySlotSalt(), int(keyslot.Iterations()), int(h.KeyBytes()), hasher) + striped := make([]byte, h.KeyBytes()*keyslot.Stripes()) + n, err := f.ReadAt(striped, int64(keyslot.KeyMaterialOffset())*V1SectorSize) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("reading diffuse material for keyslot %d: %w", k, err) + } + if n != len(striped) { + return nil, -1, -1, -1, fmt.Errorf("short read while reading diffuse material for keyslot %d: expected %d, got %d", k, len(striped), n) + } + splitKey, err := v1decrypt(h.CipherName(), h.CipherMode(), 0, passwordDerived, striped, V1SectorSize, false) + if err != nil { + fmt.Fprintf(os.Stderr, "error attempting to decrypt main key: %v\n", err) + continue + } + mkCandidate, err := afMerge(splitKey, hasher(), int(h.KeyBytes()), int(keyslot.Stripes())) + if err != nil { + fmt.Fprintf(os.Stderr, "error attempting to compute main key: %v\n", err) + continue + } + mkcandidateDerived := pbkdf2.Key(mkCandidate, h.MKDigestSalt(), int(h.MKDigestIter()), v1DigestSize, hasher) + ivTweak := 0 + decryptStream := func(ciphertext []byte) ([]byte, error) { + plaintext, err := v1decrypt(h.CipherName(), h.CipherMode(), ivTweak, mkCandidate, ciphertext, V1SectorSize, false) + ivTweak += len(ciphertext) / V1SectorSize + return plaintext, err + } + if bytes.Equal(mkcandidateDerived, h.MKDigest()) { + payloadOffset := int64(h.PayloadOffset() * V1SectorSize) + return decryptStream, V1SectorSize, payloadOffset, size - payloadOffset, nil + } + } + if activeKeys == 0 { + return nil, -1, -1, -1, errors.New("no passwords set on LUKS1 volume") + } + return nil, -1, -1, -1, errors.New("decryption error: incorrect password") +} + +// Decrypt attempts to verify the specified password using information from the +// header, JSON block, and read from the specified file. +// +// Returns a function which will decrypt payload blocks in succession, the size +// of chunks of data that the function expects, the offset in the file where +// the payload begins, and the size of the payload, assuming the payload runs +// to the end of the file. +func (h V2Header) Decrypt(password string, f ReaderAtSeekCloser, j V2JSON) (func([]byte) ([]byte, error), int, int64, int64, error) { + foundDigests := 0 + for d, digest := range j.Digests { + if digest.Type != "pbkdf2" { + continue + } + if digest.V2JSONDigestPbkdf2 == nil { + return nil, -1, -1, -1, fmt.Errorf("digest %q is corrupt: no pbkdf2 parameters", d) + } + foundDigests++ + if len(digest.Segments) == 0 || len(digest.Digest) == 0 { + continue + } + payloadOffset := int64(-1) + payloadSectorSize := V1SectorSize + payloadEncryption := "" + payloadSize := int64(0) + ivTweak := 0 + for _, segmentID := range digest.Segments { + segment, ok := j.Segments[segmentID] + if !ok { + continue // well, that was misleading + } + if segment.Type != "crypt" { + continue + } + tmp, err := strconv.ParseInt(segment.Offset, 10, 64) + if err != nil { + continue + } + payloadOffset = tmp + if segment.Size == "dynamic" { + size, err := f.Seek(0, io.SeekEnd) + if err != nil { + continue + } + payloadSize = size - payloadOffset + } else { + payloadSize, err = strconv.ParseInt(segment.Size, 10, 64) + if err != nil { + continue + } + } + payloadSectorSize = segment.SectorSize + payloadEncryption = segment.Encryption + ivTweak = segment.IVTweak + break + } + if payloadEncryption == "" { + continue + } + activeKeys := 0 + for k, keyslot := range j.Keyslots { + if keyslot.Priority != nil && *keyslot.Priority == V2JSONKeyslotPriorityIgnore { + continue + } + applicable := true + if len(digest.Keyslots) > 0 { + applicable = false + for i := 0; i < len(digest.Keyslots); i++ { + if k == digest.Keyslots[i] { + applicable = true + break + } + } + } + if !applicable { + continue + } + if keyslot.Type != "luks2" { + continue + } + if keyslot.V2JSONKeyslotLUKS2 == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt", k) + } + if keyslot.V2JSONKeyslotLUKS2.AF.Type != "luks1" { + continue + } + if keyslot.V2JSONKeyslotLUKS2.AF.V2JSONAFLUKS1 == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no AF parameters", k) + } + if keyslot.Area.Type != "raw" { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: key data area is not raw", k) + } + if keyslot.Area.KeySize*V2SectorSize < keyslot.KeySize*keyslot.AF.Stripes { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: key data area is too small (%d < %d)", k, keyslot.Area.KeySize*V2SectorSize, keyslot.KeySize*keyslot.AF.Stripes) + } + var passwordDerived []byte + switch keyslot.V2JSONKeyslotLUKS2.Kdf.Type { + default: + continue + case "pbkdf2": + if keyslot.V2JSONKeyslotLUKS2.Kdf.V2JSONKdfPbkdf2 == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no pbkdf2 parameters", k) + } + hasher, err := hasherByName(keyslot.Kdf.Hash) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", keyslot.Kdf.Hash, err) + } + passwordDerived = pbkdf2.Key([]byte(password), keyslot.Kdf.Salt, keyslot.Kdf.Iterations, keyslot.KeySize, hasher) + case "argon2i": + if keyslot.V2JSONKeyslotLUKS2.Kdf.V2JSONKdfArgon2i == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no argon2i parameters", k) + } + passwordDerived = argon2.Key([]byte(password), keyslot.Kdf.Salt, uint32(keyslot.Kdf.Time), uint32(keyslot.Kdf.Memory), uint8(keyslot.Kdf.CPUs), uint32(keyslot.KeySize)) + case "argon2id": + if keyslot.V2JSONKeyslotLUKS2.Kdf.V2JSONKdfArgon2i == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no argon2id parameters", k) + } + passwordDerived = argon2.IDKey([]byte(password), keyslot.Kdf.Salt, uint32(keyslot.Kdf.Time), uint32(keyslot.Kdf.Memory), uint8(keyslot.Kdf.CPUs), uint32(keyslot.KeySize)) + } + striped := make([]byte, keyslot.KeySize*keyslot.AF.Stripes) + n, err := f.ReadAt(striped, int64(keyslot.Area.Offset)) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("reading diffuse material for keyslot %q: %w", k, err) + } + if n != len(striped) { + return nil, -1, -1, -1, fmt.Errorf("short read while reading diffuse material for keyslot %q: expected %d, got %d", k, len(striped), n) + } + splitKey, err := v2decrypt(keyslot.Area.Encryption, 0, passwordDerived, striped, V1SectorSize, false) + if err != nil { + fmt.Fprintf(os.Stderr, "error attempting to decrypt main key: %v\n", err) + continue + } + afhasher, err := hasherByName(keyslot.AF.Hash) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", keyslot.AF.Hash, err) + } + mkCandidate, err := afMerge(splitKey, afhasher(), int(keyslot.KeySize), int(keyslot.AF.Stripes)) + if err != nil { + fmt.Fprintf(os.Stderr, "error attempting to compute main key: %v\n", err) + continue + } + digester, err := hasherByName(digest.Hash) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", digest.Hash, err) + } + mkcandidateDerived := pbkdf2.Key(mkCandidate, digest.Salt, digest.Iterations, len(digest.Digest), digester) + decryptStream := func(ciphertext []byte) ([]byte, error) { + plaintext, err := v2decrypt(payloadEncryption, ivTweak, mkCandidate, ciphertext, payloadSectorSize, true) + ivTweak += len(ciphertext) / payloadSectorSize + return plaintext, err + } + if bytes.Equal(mkcandidateDerived, digest.Digest) { + return decryptStream, payloadSectorSize, payloadOffset, payloadSize, nil + } + activeKeys++ + } + if activeKeys == 0 { + return nil, -1, -1, -1, fmt.Errorf("no passwords set on LUKS2 volume for digest %q", d) + } + } + if foundDigests == 0 { + return nil, -1, -1, -1, errors.New("no usable password-verification digests set on LUKS2 volume") + } + return nil, -1, -1, -1, errors.New("decryption error: incorrect password") +} diff --git a/vendor/github.com/containers/luksy/encrypt.go b/vendor/github.com/containers/luksy/encrypt.go new file mode 100644 index 00000000..63b345eb --- /dev/null +++ b/vendor/github.com/containers/luksy/encrypt.go @@ -0,0 +1,421 @@ +package luksy + +import ( + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/google/uuid" + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/pbkdf2" +) + +// EncryptV1 prepares to encrypt data using one or more passwords and the +// specified cipher (or a default, if the specified cipher is ""). +// +// Returns a fixed LUKSv1 header which contains keying information, a function +// which will encrypt blocks of data in succession, and the size of chunks of +// data that it expects. +func EncryptV1(password []string, cipher string) ([]byte, func([]byte) ([]byte, error), int, error) { + if len(password) == 0 { + return nil, nil, -1, errors.New("at least one password is required") + } + if len(password) > v1NumKeys { + return nil, nil, -1, fmt.Errorf("attempted to use %d passwords, only %d possible", len(password), v1NumKeys) + } + if cipher == "" { + cipher = "aes-xts-plain64" + } + + salt := make([]byte, v1SaltSize) + n, err := rand.Read(salt) + if err != nil { + return nil, nil, -1, fmt.Errorf("reading random data: %w", err) + } + if n != len(salt) { + return nil, nil, -1, errors.New("short read") + } + + cipherSpec := strings.SplitN(cipher, "-", 3) + if len(cipherSpec) != 3 || len(cipherSpec[0]) == 0 || len(cipherSpec[1]) == 0 || len(cipherSpec[2]) == 0 { + return nil, nil, -1, fmt.Errorf("invalid cipher %q", cipher) + } + + var h V1Header + if err := h.SetMagic(V1Magic); err != nil { + return nil, nil, -1, fmt.Errorf("setting magic to v1: %w", err) + } + if err := h.SetVersion(1); err != nil { + return nil, nil, -1, fmt.Errorf("setting version to 1: %w", err) + } + h.SetCipherName(cipherSpec[0]) + h.SetCipherMode(cipherSpec[1] + "-" + cipherSpec[2]) + h.SetHashSpec("sha256") + h.SetKeyBytes(32) + if cipherSpec[1] == "xts" { + h.SetKeyBytes(64) + } + h.SetMKDigestSalt(salt) + h.SetMKDigestIter(V1Stripes) + h.SetUUID(uuid.NewString()) + + mkey := make([]byte, h.KeyBytes()) + n, err = rand.Read(mkey) + if err != nil { + return nil, nil, -1, fmt.Errorf("reading random data: %w", err) + } + if n != len(mkey) { + return nil, nil, -1, errors.New("short read") + } + + hasher, err := hasherByName(h.HashSpec()) + if err != nil { + return nil, nil, -1, errors.New("internal error") + } + + mkdigest := pbkdf2.Key(mkey, h.MKDigestSalt(), int(h.MKDigestIter()), v1DigestSize, hasher) + h.SetMKDigest(mkdigest) + + headerLength := roundUpToMultiple(v1HeaderStructSize, V1AlignKeyslots) + iterations := IterationsPBKDF2(salt, int(h.KeyBytes()), hasher) + var stripes [][]byte + ksSalt := make([]byte, v1KeySlotSaltLength) + for i := 0; i < v1NumKeys; i++ { + n, err = rand.Read(ksSalt) + if err != nil { + return nil, nil, -1, fmt.Errorf("reading random data: %w", err) + } + if n != len(ksSalt) { + return nil, nil, -1, errors.New("short read") + } + var keyslot V1KeySlot + keyslot.SetActive(i < len(password)) + keyslot.SetIterations(uint32(iterations)) + keyslot.SetStripes(V1Stripes) + keyslot.SetKeySlotSalt(ksSalt) + if i < len(password) { + splitKey, err := afSplit(mkey, hasher(), int(h.MKDigestIter())) + if err != nil { + return nil, nil, -1, fmt.Errorf("splitting key: %w", err) + } + passwordDerived := pbkdf2.Key([]byte(password[i]), keyslot.KeySlotSalt(), int(keyslot.Iterations()), int(h.KeyBytes()), hasher) + striped, err := v1encrypt(h.CipherName(), h.CipherMode(), 0, passwordDerived, splitKey, V1SectorSize, false) + if err != nil { + return nil, nil, -1, fmt.Errorf("encrypting split key with password: %w", err) + } + if len(striped) != len(mkey)*int(keyslot.Stripes()) { + return nil, nil, -1, fmt.Errorf("internal error: got %d stripe bytes, expected %d", len(striped), len(mkey)*int(keyslot.Stripes())) + } + stripes = append(stripes, striped) + } + keyslot.SetKeyMaterialOffset(uint32(headerLength / V1SectorSize)) + if err := h.SetKeySlot(i, keyslot); err != nil { + return nil, nil, -1, fmt.Errorf("internal error: setting value for key slot %d: %w", i, err) + } + headerLength += len(mkey) * int(keyslot.Stripes()) + headerLength = roundUpToMultiple(headerLength, V1AlignKeyslots) + } + headerLength = roundUpToMultiple(headerLength, V1SectorSize) + + h.SetPayloadOffset(uint32(headerLength / V1SectorSize)) + head := make([]byte, headerLength) + offset := copy(head, h[:]) + offset = roundUpToMultiple(offset, V1AlignKeyslots) + for _, stripe := range stripes { + copy(head[offset:], stripe) + offset = roundUpToMultiple(offset+len(stripe), V1AlignKeyslots) + } + ivTweak := 0 + encryptStream := func(plaintext []byte) ([]byte, error) { + ciphertext, err := v1encrypt(h.CipherName(), h.CipherMode(), ivTweak, mkey, plaintext, V1SectorSize, true) + ivTweak += len(plaintext) / V1SectorSize + return ciphertext, err + } + return head, encryptStream, V1SectorSize, nil +} + +// EncryptV2 prepares to encrypt data using one or more passwords and the +// specified cipher (or a default, if the specified cipher is ""). +// +// Returns a fixed LUKSv2 header which contains keying information, a +// function which will encrypt blocks of data in succession, and the size of +// chunks of data that it expects. +func EncryptV2(password []string, cipher string, payloadSectorSize int) ([]byte, func([]byte) ([]byte, error), int, error) { + if len(password) == 0 { + return nil, nil, -1, errors.New("at least one password is required") + } + if cipher == "" { + cipher = "aes-xts-plain64" + } + cipherSpec := strings.SplitN(cipher, "-", 3) + if len(cipherSpec) != 3 || len(cipherSpec[0]) == 0 || len(cipherSpec[1]) == 0 || len(cipherSpec[2]) == 0 { + return nil, nil, -1, fmt.Errorf("invalid cipher %q", cipher) + } + if payloadSectorSize == 0 { + payloadSectorSize = V2SectorSize + } + switch payloadSectorSize { + default: + return nil, nil, -1, fmt.Errorf("invalid sector size %d", payloadSectorSize) + case 512, 1024, 2048, 4096: + } + + headerSalts := make([]byte, v1SaltSize*3) + n, err := rand.Read(headerSalts) + if err != nil { + return nil, nil, -1, err + } + if n != len(headerSalts) { + return nil, nil, -1, errors.New("short read") + } + hSalt1 := headerSalts[:v1SaltSize] + hSalt2 := headerSalts[v1SaltSize : v1SaltSize*2] + mkeySalt := headerSalts[v1SaltSize*2:] + + roundHeaderSize := func(size int) (int, error) { + switch { + case size < 0x4000: + return 0x4000, nil + case size < 0x8000: + return 0x8000, nil + case size < 0x10000: + return 0x10000, nil + case size < 0x20000: + return 0x20000, nil + case size < 0x40000: + return 0x40000, nil + case size < 0x80000: + return 0x80000, nil + case size < 0x100000: + return 0x100000, nil + case size < 0x200000: + return 0x200000, nil + case size < 0x400000: + return 0x400000, nil + } + return 0, fmt.Errorf("internal error: unsupported header size %d", size) + } + + var h1, h2 V2Header + if err := h1.SetMagic(V2Magic1); err != nil { + return nil, nil, -1, fmt.Errorf("setting magic to v2: %w", err) + } + if err := h2.SetMagic(V2Magic2); err != nil { + return nil, nil, -1, fmt.Errorf("setting magic to v2: %w", err) + } + if err := h1.SetVersion(2); err != nil { + return nil, nil, -1, fmt.Errorf("setting version to 2: %w", err) + } + if err := h2.SetVersion(2); err != nil { + return nil, nil, -1, fmt.Errorf("setting version to 2: %w", err) + } + h1.SetSequenceID(1) + h2.SetSequenceID(1) + h1.SetLabel("") + h2.SetLabel("") + h1.SetChecksumAlgorithm("sha256") + h2.SetChecksumAlgorithm("sha256") + h1.SetSalt(hSalt1) + h2.SetSalt(hSalt2) + uuidString := uuid.NewString() + h1.SetUUID(uuidString) + h2.SetUUID(uuidString) + h1.SetHeaderOffset(0) + h2.SetHeaderOffset(0) + h1.SetChecksum(nil) + h2.SetChecksum(nil) + + mkey := make([]byte, 32) + if cipherSpec[1] == "xts" { + mkey = make([]byte, 64) + } + n, err = rand.Read(mkey) + if err != nil { + return nil, nil, -1, fmt.Errorf("reading random data: %w", err) + } + if n != len(mkey) { + return nil, nil, -1, errors.New("short read") + } + + tuningSalt := make([]byte, v1SaltSize) + hasher, err := hasherByName(h1.ChecksumAlgorithm()) + if err != nil { + return nil, nil, -1, errors.New("internal error") + } + iterations := IterationsPBKDF2(tuningSalt, len(mkey), hasher) + timeCost := 16 + threadsCost := 16 + memoryCost := MemoryCostArgon2(tuningSalt, len(mkey), timeCost, threadsCost) + priority := V2JSONKeyslotPriorityNormal + var stripes [][]byte + var keyslots []V2JSONKeyslot + + mdigest := pbkdf2.Key(mkey, mkeySalt, iterations, len(hasher().Sum([]byte{})), hasher) + digest0 := V2JSONDigest{ + Type: "pbkdf2", + Salt: mkeySalt, + Digest: mdigest, + Segments: []string{"0"}, + V2JSONDigestPbkdf2: &V2JSONDigestPbkdf2{ + Hash: h1.ChecksumAlgorithm(), + Iterations: iterations, + }, + } + + for i := range password { + keyslotSalt := make([]byte, v1SaltSize) + n, err := rand.Read(keyslotSalt) + if err != nil { + return nil, nil, -1, err + } + if n != len(keyslotSalt) { + return nil, nil, -1, errors.New("short read") + } + key := argon2.Key([]byte(password[i]), keyslotSalt, uint32(timeCost), uint32(memoryCost), uint8(threadsCost), uint32(len(mkey))) + split, err := afSplit(mkey, hasher(), V2Stripes) + if err != nil { + return nil, nil, -1, fmt.Errorf("splitting: %w", err) + } + striped, err := v2encrypt(cipher, 0, key, split, V1SectorSize, false) + if err != nil { + return nil, nil, -1, fmt.Errorf("encrypting: %w", err) + } + stripes = append(stripes, striped) + keyslot := V2JSONKeyslot{ + Type: "luks2", + KeySize: len(mkey), + Area: V2JSONArea{ + Type: "raw", + Offset: 10000000, // gets updated later + Size: int64(roundUpToMultiple(len(striped), V2AlignKeyslots)), + V2JSONAreaRaw: &V2JSONAreaRaw{ + Encryption: cipher, + KeySize: len(key), + }, + }, + Priority: &priority, + V2JSONKeyslotLUKS2: &V2JSONKeyslotLUKS2{ + AF: V2JSONAF{ + Type: "luks1", + V2JSONAFLUKS1: &V2JSONAFLUKS1{ + Stripes: V2Stripes, + Hash: h1.ChecksumAlgorithm(), + }, + }, + Kdf: V2JSONKdf{ + Type: "argon2i", + Salt: keyslotSalt, + V2JSONKdfArgon2i: &V2JSONKdfArgon2i{ + Time: timeCost, + Memory: memoryCost, + CPUs: threadsCost, + }, + }, + }, + } + keyslots = append(keyslots, keyslot) + digest0.Keyslots = append(digest0.Keyslots, strconv.Itoa(i)) + } + + segment0 := V2JSONSegment{ + Type: "crypt", + Offset: "10000000", // gets updated later + Size: "dynamic", + V2JSONSegmentCrypt: &V2JSONSegmentCrypt{ + IVTweak: 0, + Encryption: cipher, + SectorSize: payloadSectorSize, + }, + } + + j := V2JSON{ + Config: V2JSONConfig{}, + Keyslots: map[string]V2JSONKeyslot{}, + Digests: map[string]V2JSONDigest{}, + Segments: map[string]V2JSONSegment{}, + Tokens: map[string]V2JSONToken{}, + } +rebuild: + j.Digests["0"] = digest0 + j.Segments["0"] = segment0 + encodedJSON, err := json.Marshal(j) + if err != nil { + return nil, nil, -1, err + } + headerPlusPaddedJsonSize, err := roundHeaderSize(int(V2SectorSize) /* binary header */ + len(encodedJSON) + 1) + if err != nil { + return nil, nil, -1, err + } + if j.Config.JsonSize != headerPlusPaddedJsonSize-V2SectorSize { + j.Config.JsonSize = headerPlusPaddedJsonSize - V2SectorSize + goto rebuild + } + + if h1.HeaderSize() != uint64(headerPlusPaddedJsonSize) { + h1.SetHeaderSize(uint64(headerPlusPaddedJsonSize)) + h2.SetHeaderSize(uint64(headerPlusPaddedJsonSize)) + h1.SetHeaderOffset(0) + h2.SetHeaderOffset(uint64(headerPlusPaddedJsonSize)) + goto rebuild + } + + keyslotsOffset := h2.HeaderOffset() * 2 + maxKeys := len(password) + if maxKeys < 64 { + maxKeys = 64 + } + for i := 0; i < len(password); i++ { + oldOffset := keyslots[i].Area.Offset + keyslots[i].Area.Offset = int64(keyslotsOffset) + int64(roundUpToMultiple(len(mkey)*V2Stripes, V2AlignKeyslots))*int64(i) + j.Keyslots[strconv.Itoa(i)] = keyslots[i] + if keyslots[i].Area.Offset != oldOffset { + goto rebuild + } + } + keyslotsSize := roundUpToMultiple(len(mkey)*V2Stripes, V2AlignKeyslots) * maxKeys + if j.Config.KeyslotsSize != keyslotsSize { + j.Config.KeyslotsSize = keyslotsSize + goto rebuild + } + + segmentOffsetInt := roundUpToMultiple(int(keyslotsOffset)+j.Config.KeyslotsSize, V2SectorSize) + segmentOffset := strconv.Itoa(segmentOffsetInt) + if segment0.Offset != segmentOffset { + segment0.Offset = segmentOffset + goto rebuild + } + + d1 := hasher() + h1.SetChecksum(nil) + d1.Write(h1[:]) + d1.Write(encodedJSON) + zeropad := make([]byte, headerPlusPaddedJsonSize-len(h1)-len(encodedJSON)) + d1.Write(zeropad) + h1.SetChecksum(d1.Sum(nil)) + d2 := hasher() + h2.SetChecksum(nil) + d2.Write(h2[:]) + d2.Write(encodedJSON) + d1.Write(zeropad) + h2.SetChecksum(d2.Sum(nil)) + + head := make([]byte, segmentOffsetInt) + copy(head, h1[:]) + copy(head[V2SectorSize:], encodedJSON) + copy(head[h2.HeaderOffset():], h2[:]) + copy(head[h2.HeaderOffset()+V2SectorSize:], encodedJSON) + for i := 0; i < len(password); i++ { + iAsString := strconv.Itoa(i) + copy(head[j.Keyslots[iAsString].Area.Offset:], stripes[i]) + } + ivTweak := 0 + encryptStream := func(plaintext []byte) ([]byte, error) { + ciphertext, err := v2encrypt(cipher, ivTweak, mkey, plaintext, payloadSectorSize, true) + ivTweak += len(plaintext) / payloadSectorSize + return ciphertext, err + } + return head, encryptStream, segment0.SectorSize, nil +} diff --git a/vendor/github.com/containers/luksy/encryption.go b/vendor/github.com/containers/luksy/encryption.go new file mode 100644 index 00000000..242bceb3 --- /dev/null +++ b/vendor/github.com/containers/luksy/encryption.go @@ -0,0 +1,572 @@ +package luksy + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/binary" + "errors" + "fmt" + "hash" + "io" + "strings" + + "github.com/aead/serpent" + "golang.org/x/crypto/cast5" + "golang.org/x/crypto/ripemd160" + "golang.org/x/crypto/twofish" + "golang.org/x/crypto/xts" +) + +func v1encrypt(cipherName, cipherMode string, ivTweak int, key []byte, plaintext []byte, sectorSize int, bulk bool) ([]byte, error) { + var err error + var newBlockCipher func([]byte) (cipher.Block, error) + ciphertext := make([]byte, len(plaintext)) + + switch cipherName { + case "aes": + newBlockCipher = aes.NewCipher + case "twofish": + newBlockCipher = func(key []byte) (cipher.Block, error) { return twofish.NewCipher(key) } + case "cast5": + newBlockCipher = func(key []byte) (cipher.Block, error) { return cast5.NewCipher(key) } + case "serpent": + newBlockCipher = serpent.NewCipher + default: + return nil, fmt.Errorf("unsupported cipher %s", cipherName) + } + if sectorSize == 0 { + sectorSize = V1SectorSize + } + switch sectorSize { + default: + return nil, fmt.Errorf("invalid sector size %d", sectorSize) + case 512, 1024, 2048, 4096: + } + + switch cipherMode { + case "ecb": + cipher, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += cipher.BlockSize() { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + cipher.Encrypt(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft]) + } + case "cbc-plain": + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := processed/sectorSize + ivTweak + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + iv0 := make([]byte, block.BlockSize()) + binary.LittleEndian.PutUint32(iv0, uint32(ivValue)) + cipher := cipher.NewCBCEncrypter(block, iv0) + cipher.CryptBlocks(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft]) + } + case "cbc-plain64": + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := processed/sectorSize + ivTweak + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + iv0 := make([]byte, block.BlockSize()) + binary.LittleEndian.PutUint64(iv0, uint64(ivValue)) + cipher := cipher.NewCBCEncrypter(block, iv0) + cipher.CryptBlocks(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft]) + } + case "cbc-essiv:sha256": + hasherName := strings.TrimPrefix(cipherMode, "cbc-essiv:") + hasher, err := hasherByName(hasherName) + if err != nil { + return nil, fmt.Errorf("initializing encryption using hash %s: %w", hasherName, err) + } + h := hasher() + h.Write(key) + makeiv, err := newBlockCipher(h.Sum(nil)) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := (processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + plain0 := make([]byte, makeiv.BlockSize()) + binary.LittleEndian.PutUint64(plain0, uint64(ivValue)) + iv0 := make([]byte, makeiv.BlockSize()) + makeiv.Encrypt(iv0, plain0) + cipher := cipher.NewCBCEncrypter(block, iv0) + cipher.CryptBlocks(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft]) + } + case "xts-plain": + cipher, err := xts.NewCipher(newBlockCipher, key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + sector := uint64(processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + sector *= uint64(sectorSize / V1SectorSize) + } + sector = sector % 0x100000000 + cipher.Encrypt(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft], sector) + } + case "xts-plain64": + cipher, err := xts.NewCipher(newBlockCipher, key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + sector := uint64(processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + sector *= uint64(sectorSize / V1SectorSize) + } + cipher.Encrypt(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft], sector) + } + default: + return nil, fmt.Errorf("unsupported cipher mode %s", cipherMode) + } + + if err != nil { + return nil, fmt.Errorf("cipher error: %w", err) + } + return ciphertext, nil +} + +func v1decrypt(cipherName, cipherMode string, ivTweak int, key []byte, ciphertext []byte, sectorSize int, bulk bool) ([]byte, error) { + var err error + var newBlockCipher func([]byte) (cipher.Block, error) + plaintext := make([]byte, len(ciphertext)) + + switch cipherName { + case "aes": + newBlockCipher = aes.NewCipher + case "twofish": + newBlockCipher = func(key []byte) (cipher.Block, error) { return twofish.NewCipher(key) } + case "cast5": + newBlockCipher = func(key []byte) (cipher.Block, error) { return cast5.NewCipher(key) } + case "serpent": + newBlockCipher = serpent.NewCipher + default: + return nil, fmt.Errorf("unsupported cipher %s", cipherName) + } + if sectorSize == 0 { + sectorSize = V1SectorSize + } + switch sectorSize { + default: + return nil, fmt.Errorf("invalid sector size %d", sectorSize) + case 512, 1024, 2048, 4096: + } + + switch cipherMode { + case "ecb": + cipher, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(ciphertext); processed += cipher.BlockSize() { + blockLeft := sectorSize + if processed+blockLeft > len(ciphertext) { + blockLeft = len(ciphertext) - processed + } + cipher.Decrypt(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft]) + } + case "cbc-plain": + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := processed/sectorSize + ivTweak + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + iv0 := make([]byte, block.BlockSize()) + binary.LittleEndian.PutUint32(iv0, uint32(ivValue)) + cipher := cipher.NewCBCDecrypter(block, iv0) + cipher.CryptBlocks(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft]) + } + case "cbc-plain64": + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := processed/sectorSize + ivTweak + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + iv0 := make([]byte, block.BlockSize()) + binary.LittleEndian.PutUint64(iv0, uint64(ivValue)) + cipher := cipher.NewCBCDecrypter(block, iv0) + cipher.CryptBlocks(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft]) + } + case "cbc-essiv:sha256": + hasherName := strings.TrimPrefix(cipherMode, "cbc-essiv:") + hasher, err := hasherByName(hasherName) + if err != nil { + return nil, fmt.Errorf("initializing decryption using hash %s: %w", hasherName, err) + } + h := hasher() + h.Write(key) + makeiv, err := newBlockCipher(h.Sum(nil)) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := (processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + plain0 := make([]byte, makeiv.BlockSize()) + binary.LittleEndian.PutUint64(plain0, uint64(ivValue)) + iv0 := make([]byte, makeiv.BlockSize()) + makeiv.Encrypt(iv0, plain0) + cipher := cipher.NewCBCDecrypter(block, iv0) + cipher.CryptBlocks(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft]) + } + case "xts-plain": + cipher, err := xts.NewCipher(newBlockCipher, key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(ciphertext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(ciphertext) { + blockLeft = len(ciphertext) - processed + } + sector := uint64(processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + sector *= uint64(sectorSize / V1SectorSize) + } + sector = sector % 0x100000000 + cipher.Decrypt(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft], sector) + } + case "xts-plain64": + cipher, err := xts.NewCipher(newBlockCipher, key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(ciphertext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(ciphertext) { + blockLeft = len(ciphertext) - processed + } + sector := uint64(processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + sector *= uint64(sectorSize / V1SectorSize) + } + cipher.Decrypt(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft], sector) + } + default: + return nil, fmt.Errorf("unsupported cipher mode %s", cipherMode) + } + + if err != nil { + return nil, fmt.Errorf("cipher error: %w", err) + } + return plaintext, nil +} + +func v2encrypt(cipherSuite string, ivTweak int, key []byte, ciphertext []byte, sectorSize int, bulk bool) ([]byte, error) { + var cipherName, cipherMode string + switch { + default: + cipherSpec := strings.SplitN(cipherSuite, "-", 2) + if len(cipherSpec) < 2 { + return nil, fmt.Errorf("unrecognized cipher suite %q", cipherSuite) + } + cipherName = cipherSpec[0] + cipherMode = cipherSpec[1] + } + return v1encrypt(cipherName, cipherMode, ivTweak, key, ciphertext, sectorSize, bulk) +} + +func v2decrypt(cipherSuite string, ivTweak int, key []byte, ciphertext []byte, sectorSize int, bulk bool) ([]byte, error) { + var cipherName, cipherMode string + switch { + default: + cipherSpec := strings.SplitN(cipherSuite, "-", 2) + if len(cipherSpec) < 2 { + return nil, fmt.Errorf("unrecognized cipher suite %q", cipherSuite) + } + cipherName = cipherSpec[0] + cipherMode = cipherSpec[1] + } + return v1decrypt(cipherName, cipherMode, ivTweak, key, ciphertext, sectorSize, bulk) +} + +func diffuse(key []byte, h hash.Hash) []byte { + sum := make([]byte, len(key)) + counter := uint32(0) + for summed := 0; summed < len(key); summed += h.Size() { + h.Reset() + var buf [4]byte + binary.BigEndian.PutUint32(buf[:], counter) + h.Write(buf[:]) + needed := len(key) - summed + if needed > h.Size() { + needed = h.Size() + } + h.Write(key[summed : summed+needed]) + partial := h.Sum(nil) + copy(sum[summed:summed+needed], partial) + counter++ + } + return sum +} + +func afMerge(splitKey []byte, h hash.Hash, keysize int, stripes int) ([]byte, error) { + if len(splitKey) != keysize*stripes { + return nil, fmt.Errorf("expected %d af bytes, got %d", keysize*stripes, len(splitKey)) + } + d := make([]byte, keysize) + for i := 0; i < stripes-1; i++ { + for j := 0; j < keysize; j++ { + d[j] = d[j] ^ splitKey[i*keysize+j] + } + d = diffuse(d, h) + } + for j := 0; j < keysize; j++ { + d[j] = d[j] ^ splitKey[(stripes-1)*keysize+j] + } + return d, nil +} + +func afSplit(key []byte, h hash.Hash, stripes int) ([]byte, error) { + keysize := len(key) + s := make([]byte, keysize*stripes) + d := make([]byte, keysize) + n, err := rand.Read(s[0 : (keysize-1)*stripes]) + if err != nil { + return nil, err + } + if n != (keysize-1)*stripes { + return nil, fmt.Errorf("short read when attempting to read random data: %d < %d", n, (keysize-1)*stripes) + } + for i := 0; i < stripes-1; i++ { + for j := 0; j < keysize; j++ { + d[j] = d[j] ^ s[i*keysize+j] + } + d = diffuse(d, h) + } + for j := 0; j < keysize; j++ { + s[(stripes-1)*keysize+j] = d[j] ^ key[j] + } + return s, nil +} + +func roundUpToMultiple(i, factor int) int { + if i < 0 { + return 0 + } + if factor < 1 { + return i + } + return i + ((factor - (i % factor)) % factor) +} + +func roundDownToMultiple(i, factor int) int { + if i < 0 { + return 0 + } + if factor < 1 { + return i + } + return i - (i % factor) +} + +func hasherByName(name string) (func() hash.Hash, error) { + switch name { + case "sha1": + return sha1.New, nil + case "sha256": + return sha256.New, nil + case "sha512": + return sha512.New, nil + case "ripemd160": + return ripemd160.New, nil + default: + return nil, fmt.Errorf("unsupported digest algorithm %q", name) + } +} + +type wrapper struct { + fn func(plaintext []byte) ([]byte, error) + blockSize int + buf []byte + buffered int + processed int + reader io.Reader + eof bool + writer io.Writer +} + +func (w *wrapper) partialWrite() error { + if w.buffered-w.processed >= w.blockSize { + toProcess := roundDownToMultiple(w.buffered-w.processed, w.blockSize) + processed, err := w.fn(w.buf[w.processed : w.processed+toProcess]) + if err != nil { + return err + } + nProcessed := copy(w.buf[w.processed:], processed) + w.processed += nProcessed + } + if w.processed >= w.blockSize { + nWritten, err := w.writer.Write(w.buf[:w.processed]) + if err != nil { + return err + } + copy(w.buf, w.buf[nWritten:w.buffered]) + w.buffered -= nWritten + w.processed -= nWritten + if w.processed != 0 { + return fmt.Errorf("short write: %d != %d", nWritten, nWritten+w.processed) + } + } + return nil +} + +func (w *wrapper) Write(buf []byte) (int, error) { + n := 0 + for n < len(buf) { + nBuffered := copy(w.buf[w.buffered:], buf[n:]) + w.buffered += nBuffered + n += nBuffered + if err := w.partialWrite(); err != nil { + return n, err + } + } + return n, nil +} + +func (w *wrapper) Read(buf []byte) (int, error) { + n := 0 + for n < len(buf) { + if !w.eof { + nRead, err := w.reader.Read(w.buf[w.buffered:]) + if err != nil { + if !errors.Is(err, io.EOF) { + w.buffered += nRead + return n, err + } + w.eof = true + } + w.buffered += nRead + } + if w.buffered == 0 && w.eof { + return n, io.EOF + } + if w.buffered-w.processed >= w.blockSize { + toProcess := roundDownToMultiple(w.buffered-w.processed, w.blockSize) + processed, err := w.fn(w.buf[w.processed : w.processed+toProcess]) + if err != nil { + return n, err + } + nProcessed := copy(w.buf[w.processed:], processed) + w.processed += nProcessed + } + nRead := copy(buf[n:], w.buf[:w.processed]) + n += nRead + copy(w.buf, w.buf[nRead:w.buffered]) + w.processed -= nRead + w.buffered -= nRead + if w.buffered-w.processed < w.blockSize { + break + } + } + return n, nil +} + +func (w *wrapper) Close() error { + if w.writer != nil { + if w.buffered%w.blockSize != 0 { + nPadding := w.blockSize - w.buffered%w.blockSize + nWritten, err := w.Write(make([]byte, nPadding)) + if err != nil { + return fmt.Errorf("flushing write: %v", err) + } + if nWritten < nPadding { + return fmt.Errorf("flushing write: %d != %d", nPadding, nWritten) + } + } + } + return nil +} + +// EncryptWriter creates an io.WriteCloser which buffers writes through an +// encryption function, transforming and writing multiples of the blockSize. +// After writing a final block, the returned writer should be closed. +// If only a partial block has been written when Close() is called, a final +// block with its length padded with zero bytes will be transformed and +// written. +func EncryptWriter(fn func(plaintext []byte) ([]byte, error), writer io.Writer, blockSize int) io.WriteCloser { + bufferSize := roundUpToMultiple(1024*1024, blockSize) + return &wrapper{fn: fn, blockSize: blockSize, buf: make([]byte, bufferSize), writer: writer} +} + +// DecryptReader creates an io.ReadCloser which buffers reads through a +// decryption function, decrypting and returning multiples of the blockSize +// until it reaches the end of the file. When data will no longer be read, the +// returned reader should be closed. +func DecryptReader(fn func(ciphertext []byte) ([]byte, error), reader io.Reader, blockSize int) io.ReadCloser { + bufferSize := roundUpToMultiple(1024*1024, blockSize) + return &wrapper{fn: fn, blockSize: blockSize, buf: make([]byte, bufferSize), reader: reader} +} diff --git a/vendor/github.com/containers/luksy/luks.go b/vendor/github.com/containers/luksy/luks.go new file mode 100644 index 00000000..c584c5f3 --- /dev/null +++ b/vendor/github.com/containers/luksy/luks.go @@ -0,0 +1,75 @@ +package luksy + +import ( + "bytes" + "encoding/json" + "fmt" + "io" +) + +// ReadHeaderOptions can control some of what ReadHeaders() does. +type ReadHeaderOptions struct{} + +// ReadHeaders reads LUKS headers from the specified file, returning either a +// LUKSv1 header, or two LUKSv2 headers and a LUKSv2 JSON block, depending on +// which format is detected. +func ReadHeaders(f io.ReaderAt, options ReadHeaderOptions) (*V1Header, *V2Header, *V2Header, *V2JSON, error) { + var v1 V1Header + var v2a, v2b V2Header + n, err := f.ReadAt(v2a[:], 0) + if err != nil { + return nil, nil, nil, nil, err + } + if n != len(v2a) { + return nil, nil, nil, nil, fmt.Errorf("only able to read %d bytes - file truncated?", n) + } + if n, err = f.ReadAt(v1[:], 0); err != nil { + return nil, nil, nil, nil, err + } + if n != len(v1) { + return nil, nil, nil, nil, fmt.Errorf("only able to read %d bytes - file truncated?", n) + } + if v2a.Magic() != V2Magic1 { + return nil, nil, nil, nil, fmt.Errorf("internal error: magic mismatch in LUKS header (%q)", v2a.Magic()) + } + switch v2a.Version() { // is it a v1 header, or the first v2 header? + case 1: + return &v1, nil, nil, nil, nil + case 2: + size := v2a.HeaderSize() + if size > 0x7fffffffffffffff { + return nil, nil, nil, nil, fmt.Errorf("unsupported header size while looking for second header") + } + if size < 4096 { + return nil, nil, nil, nil, fmt.Errorf("unsupported header size while looking for JSON data") + } + if n, err = f.ReadAt(v2b[:], int64(size)); err != nil || n != len(v2b) { + if err == nil && n != len(v2b) { + err = fmt.Errorf("short read: read only %d bytes, should have read %d", n, len(v2b)) + } + return nil, nil, nil, nil, err + } + if v2b.Magic() != V2Magic2 { + return nil, nil, nil, nil, fmt.Errorf("internal error: magic mismatch in second LUKS header (%q)", v2b.Magic()) + } + jsonSize := size - 4096 + buf := make([]byte, jsonSize) + n, err = f.ReadAt(buf[:], 4096) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("internal error: while reading JSON data: %w", err) + } + if n < 0 || uint64(n) != jsonSize { + return nil, nil, nil, nil, fmt.Errorf("internal error: short read while reading JSON data (wanted %d, got %d)", jsonSize, n) + } + var jsonData V2JSON + buf = bytes.TrimRightFunc(buf, func(r rune) bool { return r == 0 }) + if err = json.Unmarshal(buf, &jsonData); err != nil { + return nil, nil, nil, nil, fmt.Errorf("internal error: decoding JSON data: %w", err) + } + if uint64(jsonData.Config.JsonSize) != jsonSize { + return nil, nil, nil, nil, fmt.Errorf("internal error: JSON data size mismatch: (expected %d, used %d)", jsonData.Config.JsonSize, jsonSize) + } + return nil, &v2a, &v2b, &jsonData, nil + } + return nil, nil, nil, nil, fmt.Errorf("error reading LUKS header - magic identifier not found") +} diff --git a/vendor/github.com/containers/luksy/tune.go b/vendor/github.com/containers/luksy/tune.go new file mode 100644 index 00000000..6624f882 --- /dev/null +++ b/vendor/github.com/containers/luksy/tune.go @@ -0,0 +1,55 @@ +package luksy + +import ( + "hash" + "time" + + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/pbkdf2" +) + +func durationOf(f func()) time.Duration { + start := time.Now() + f() + return time.Since(start) +} + +func IterationsPBKDF2(salt []byte, keyLen int, h func() hash.Hash) int { + iterations := 2 + var d time.Duration + for d < time.Second { + d = durationOf(func() { + _ = pbkdf2.Key([]byte{}, salt, iterations, keyLen, h) + }) + if d < time.Second/10 { + iterations *= 2 + } else { + return iterations * int(time.Second) / int(d) + } + } + return iterations +} + +func memoryCostArgon2(salt []byte, keyLen, timeCost, threadsCost int, kdf func([]byte, []byte, uint32, uint32, uint8, uint32) []byte) int { + memoryCost := 2 + var d time.Duration + for d < time.Second { + d = durationOf(func() { + _ = kdf([]byte{}, salt, uint32(timeCost), uint32(memoryCost), uint8(threadsCost), uint32(keyLen)) + }) + if d < time.Second/10 { + memoryCost *= 2 + } else { + return memoryCost * int(float64(time.Second)/float64(d)) + } + } + return memoryCost +} + +func MemoryCostArgon2(salt []byte, keyLen, timeCost, threadsCost int) int { + return memoryCostArgon2(salt, keyLen, timeCost, threadsCost, argon2.Key) +} + +func MemoryCostArgon2i(salt []byte, keyLen, timeCost, threadsCost int) int { + return memoryCostArgon2(salt, keyLen, timeCost, threadsCost, argon2.IDKey) +} diff --git a/vendor/github.com/containers/luksy/v1header.go b/vendor/github.com/containers/luksy/v1header.go new file mode 100644 index 00000000..ded4a61d --- /dev/null +++ b/vendor/github.com/containers/luksy/v1header.go @@ -0,0 +1,321 @@ +package luksy + +import ( + "encoding/binary" + "fmt" + "syscall" +) + +type ( + V1Header [592]uint8 + V1KeySlot [48]uint8 +) + +const ( + // Mostly verbatim from LUKS1 On-Disk Format Specification version 1.2.3 + V1Magic = "LUKS\xba\xbe" + v1MagicStart = 0 + v1MagicLength = 6 + v1VersionStart = v1MagicStart + v1MagicLength + v1VersionLength = 2 + v1CipherNameStart = v1VersionStart + v1VersionLength + v1CipherNameLength = 32 + v1CipherModeStart = v1CipherNameStart + v1CipherNameLength + v1CipherModeLength = 32 + v1HashSpecStart = v1CipherModeStart + v1CipherModeLength + v1HashSpecLength = 32 + v1PayloadOffsetStart = v1HashSpecStart + v1HashSpecLength + v1PayloadOffsetLength = 4 + v1KeyBytesStart = v1PayloadOffsetStart + v1PayloadOffsetLength + v1KeyBytesLength = 4 + v1MKDigestStart = v1KeyBytesStart + v1KeyBytesLength + v1MKDigestLength = v1DigestSize + v1MKDigestSaltStart = v1MKDigestStart + v1MKDigestLength + v1MKDigestSaltLength = v1SaltSize + v1MKDigestIterStart = v1MKDigestSaltStart + v1MKDigestSaltLength + v1MKDigestIterLength = 4 + v1UUIDStart = v1MKDigestIterStart + v1MKDigestIterLength + v1UUIDLength = 40 + v1KeySlot1Start = v1UUIDStart + v1UUIDLength + v1KeySlot1Length = 48 + v1KeySlot2Start = v1KeySlot1Start + v1KeySlot1Length + v1KeySlot2Length = 48 + v1KeySlot3Start = v1KeySlot2Start + v1KeySlot2Length + v1KeySlot3Length = 48 + v1KeySlot4Start = v1KeySlot3Start + v1KeySlot3Length + v1KeySlot4Length = 48 + v1KeySlot5Start = v1KeySlot4Start + v1KeySlot4Length + v1KeySlot5Length = 48 + v1KeySlot6Start = v1KeySlot5Start + v1KeySlot5Length + v1KeySlot6Length = 48 + v1KeySlot7Start = v1KeySlot6Start + v1KeySlot6Length + v1KeySlot7Length = 48 + v1KeySlot8Start = v1KeySlot7Start + v1KeySlot7Length + v1KeySlot8Length = 48 + v1HeaderStructSize = v1KeySlot8Start + v1KeySlot8Length + + v1KeySlotActiveStart = 0 + v1KeySlotActiveLength = 4 + v1KeySlotIterationsStart = v1KeySlotActiveStart + v1KeySlotActiveLength + v1KeySlotIterationsLength = 4 + v1KeySlotSaltStart = v1KeySlotIterationsStart + v1KeySlotIterationsLength + v1KeySlotSaltLength = v1SaltSize + v1KeySlotKeyMaterialOffsetStart = v1KeySlotSaltStart + v1KeySlotSaltLength + v1KeySlotKeyMaterialOffsetLength = 4 + v1KeySlotStripesStart = v1KeySlotKeyMaterialOffsetStart + v1KeySlotKeyMaterialOffsetLength + v1KeySlotStripesLength = 4 + v1KeySlotStructSize = v1KeySlotStripesStart + v1KeySlotStripesLength + + v1DigestSize = 20 + v1SaltSize = 32 + v1NumKeys = 8 + v1KeySlotActiveKeyDisabled = 0x0000dead + v1KeySlotActiveKeyEnabled = 0x00ac71f3 + V1Stripes = 4000 + V1AlignKeyslots = 4096 + V1SectorSize = 512 +) + +func (h V1Header) readu2(offset int) uint16 { + return binary.BigEndian.Uint16(h[offset:]) +} + +func (h V1Header) readu4(offset int) uint32 { + return binary.BigEndian.Uint32(h[offset:]) +} + +func (h *V1Header) writeu2(offset int, value uint16) { + binary.BigEndian.PutUint16(h[offset:], value) +} + +func (h *V1Header) writeu4(offset int, value uint32) { + binary.BigEndian.PutUint32(h[offset:], value) +} + +func (h V1Header) Magic() string { + return trimZeroPad(string(h[v1MagicStart : v1MagicStart+v1MagicLength])) +} + +func (h *V1Header) SetMagic(magic string) error { + switch magic { + case V1Magic: + copy(h[v1MagicStart:v1MagicStart+v1MagicLength], []uint8(magic)) + return nil + } + return fmt.Errorf("magic %q not acceptable, only %q is an acceptable magic value: %w", magic, V1Magic, syscall.EINVAL) +} + +func (h V1Header) Version() uint16 { + return h.readu2(v1VersionStart) +} + +func (h *V1Header) SetVersion(version uint16) error { + switch version { + case 1: + h.writeu2(v1VersionStart, version) + return nil + } + return fmt.Errorf("version %d not acceptable, only 1 is an acceptable version: %w", version, syscall.EINVAL) +} + +func (h *V1Header) setZeroString(offset int, value string, length int) { + for len(value) < length { + value = value + "\000" + } + copy(h[offset:offset+length], []uint8(value)) +} + +func (h *V1Header) setInt8(offset int, s []uint8, length int) { + t := make([]byte, length) + copy(t, s) + copy(h[offset:offset+length], s) +} + +func (h V1Header) CipherName() string { + return trimZeroPad(string(h[v1CipherNameStart : v1CipherNameStart+v1CipherNameLength])) +} + +func (h *V1Header) SetCipherName(name string) { + h.setZeroString(v1CipherNameStart, name, v1CipherNameLength) +} + +func (h V1Header) CipherMode() string { + return trimZeroPad(string(h[v1CipherModeStart : v1CipherModeStart+v1CipherModeLength])) +} + +func (h *V1Header) SetCipherMode(mode string) { + h.setZeroString(v1CipherModeStart, mode, v1CipherModeLength) +} + +func (h V1Header) HashSpec() string { + return trimZeroPad(string(h[v1HashSpecStart : v1HashSpecStart+v1HashSpecLength])) +} + +func (h *V1Header) SetHashSpec(spec string) { + h.setZeroString(v1HashSpecStart, spec, v1HashSpecLength) +} + +func (h V1Header) PayloadOffset() uint32 { + return h.readu4(v1PayloadOffsetStart) +} + +func (h *V1Header) SetPayloadOffset(offset uint32) { + h.writeu4(v1PayloadOffsetStart, offset) +} + +func (h V1Header) KeyBytes() uint32 { + return h.readu4(v1KeyBytesStart) +} + +func (h *V1Header) SetKeyBytes(bytes uint32) { + h.writeu4(v1KeyBytesStart, bytes) +} + +func (h *V1Header) KeySlot(slot int) (V1KeySlot, error) { + var ks V1KeySlot + if slot < 0 || slot >= v1NumKeys { + return ks, fmt.Errorf("invalid key slot number (must be 0..%d)", v1NumKeys-1) + } + switch slot { + case 0: + copy(ks[:], h[v1KeySlot1Start:v1KeySlot1Start+v1KeySlot1Length]) + case 1: + copy(ks[:], h[v1KeySlot2Start:v1KeySlot2Start+v1KeySlot2Length]) + case 2: + copy(ks[:], h[v1KeySlot3Start:v1KeySlot3Start+v1KeySlot3Length]) + case 3: + copy(ks[:], h[v1KeySlot4Start:v1KeySlot4Start+v1KeySlot4Length]) + case 4: + copy(ks[:], h[v1KeySlot5Start:v1KeySlot5Start+v1KeySlot5Length]) + case 5: + copy(ks[:], h[v1KeySlot6Start:v1KeySlot6Start+v1KeySlot6Length]) + case 6: + copy(ks[:], h[v1KeySlot7Start:v1KeySlot7Start+v1KeySlot7Length]) + case 7: + copy(ks[:], h[v1KeySlot8Start:v1KeySlot8Start+v1KeySlot8Length]) + } + return ks, nil +} + +func (h *V1Header) SetKeySlot(slot int, ks V1KeySlot) error { + if slot < 0 || slot >= v1NumKeys { + return fmt.Errorf("invalid key slot number (must be 0..%d)", v1NumKeys-1) + } + switch slot { + case 0: + copy(h[v1KeySlot1Start:v1KeySlot1Start+v1KeySlot1Length], ks[:]) + case 1: + copy(h[v1KeySlot2Start:v1KeySlot2Start+v1KeySlot2Length], ks[:]) + case 2: + copy(h[v1KeySlot3Start:v1KeySlot3Start+v1KeySlot3Length], ks[:]) + case 3: + copy(h[v1KeySlot4Start:v1KeySlot4Start+v1KeySlot4Length], ks[:]) + case 4: + copy(h[v1KeySlot5Start:v1KeySlot5Start+v1KeySlot5Length], ks[:]) + case 5: + copy(h[v1KeySlot6Start:v1KeySlot6Start+v1KeySlot6Length], ks[:]) + case 6: + copy(h[v1KeySlot7Start:v1KeySlot7Start+v1KeySlot7Length], ks[:]) + case 7: + copy(h[v1KeySlot8Start:v1KeySlot8Start+v1KeySlot8Length], ks[:]) + } + return nil +} + +func (h V1Header) MKDigest() []uint8 { + return dupInt8(h[v1MKDigestStart : v1MKDigestStart+v1MKDigestLength]) +} + +func (h *V1Header) SetMKDigest(digest []uint8) { + h.setInt8(v1MKDigestStart, digest, v1MKDigestLength) +} + +func (h V1Header) MKDigestSalt() []uint8 { + return dupInt8(h[v1MKDigestSaltStart : v1MKDigestSaltStart+v1MKDigestSaltLength]) +} + +func (h *V1Header) SetMKDigestSalt(salt []uint8) { + h.setInt8(v1MKDigestSaltStart, salt, v1MKDigestSaltLength) +} + +func (h V1Header) MKDigestIter() uint32 { + return h.readu4(v1MKDigestIterStart) +} + +func (h *V1Header) SetMKDigestIter(bytes uint32) { + h.writeu4(v1MKDigestIterStart, bytes) +} + +func (h V1Header) UUID() string { + return trimZeroPad(string(h[v1UUIDStart : v1UUIDStart+v1UUIDLength])) +} + +func (h *V1Header) SetUUID(uuid string) { + h.setZeroString(v1UUIDStart, uuid, v1UUIDLength) +} + +func (s V1KeySlot) readu4(offset int) uint32 { + return binary.BigEndian.Uint32(s[offset:]) +} + +func (s *V1KeySlot) writeu4(offset int, value uint32) { + binary.BigEndian.PutUint32(s[offset:], value) +} + +func (s *V1KeySlot) setInt8(offset int, i []uint8, length int) { + for len(s) < length { + i = append(i, 0) + } + copy(s[offset:offset+length], i) +} + +func (s V1KeySlot) Active() (bool, error) { + active := s.readu4(v1KeySlotActiveStart) + switch active { + case v1KeySlotActiveKeyDisabled: + return false, nil + case v1KeySlotActiveKeyEnabled: + return true, nil + } + return false, fmt.Errorf("got invalid active value %#0x: %w", active, syscall.EINVAL) +} + +func (s *V1KeySlot) SetActive(active bool) { + if active { + s.writeu4(v1KeySlotActiveStart, v1KeySlotActiveKeyEnabled) + return + } + s.writeu4(v1KeySlotActiveStart, v1KeySlotActiveKeyDisabled) +} + +func (s V1KeySlot) Iterations() uint32 { + return s.readu4(v1KeySlotIterationsStart) +} + +func (s *V1KeySlot) SetIterations(iterations uint32) { + s.writeu4(v1KeySlotIterationsStart, iterations) +} + +func (s V1KeySlot) KeySlotSalt() []uint8 { + return dupInt8(s[v1KeySlotSaltStart : v1KeySlotSaltStart+v1KeySlotSaltLength]) +} + +func (s *V1KeySlot) SetKeySlotSalt(salt []uint8) { + s.setInt8(v1KeySlotSaltStart, salt, v1KeySlotSaltLength) +} + +func (s V1KeySlot) KeyMaterialOffset() uint32 { + return s.readu4(v1KeySlotKeyMaterialOffsetStart) +} + +func (s *V1KeySlot) SetKeyMaterialOffset(material uint32) { + s.writeu4(v1KeySlotKeyMaterialOffsetStart, material) +} + +func (s V1KeySlot) Stripes() uint32 { + return s.readu4(v1KeySlotStripesStart) +} + +func (s *V1KeySlot) SetStripes(stripes uint32) { + s.writeu4(v1KeySlotStripesStart, stripes) +} diff --git a/vendor/github.com/containers/luksy/v2header.go b/vendor/github.com/containers/luksy/v2header.go new file mode 100644 index 00000000..4f94a05e --- /dev/null +++ b/vendor/github.com/containers/luksy/v2header.go @@ -0,0 +1,203 @@ +package luksy + +import ( + "fmt" + "strings" + "syscall" +) + +type V2Header [4096]uint8 + +const ( + // Mostly verbatim from LUKS2 On-Disk Format Specification version 1.1.1 + V2Magic1 = V1Magic + V2Magic2 = "SKUL\xba\xbe" + v2MagicStart = 0 + v2MagicLength = 6 + v2VersionStart = v2MagicStart + v2MagicLength + v2VersionLength = 2 + v2HeaderSizeStart = v2VersionStart + v2VersionLength + v2HeaderSizeLength = 8 + v2SequenceIDStart = v2HeaderSizeStart + v2HeaderSizeLength + v2SequenceIDLength = 8 + v2LabelStart = v2SequenceIDStart + v2SequenceIDLength + v2LabelLength = 48 + v2ChecksumAlgorithmStart = v2LabelStart + v2LabelLength + v2ChecksumAlgorithmLength = 32 + v2SaltStart = v2ChecksumAlgorithmStart + v2ChecksumAlgorithmLength + v2SaltLength = 64 + v2UUIDStart = v2SaltStart + v2SaltLength + v2UUIDLength = 40 + v2SubsystemStart = v2UUIDStart + v2UUIDLength + v2SubsystemLength = v2LabelLength + v2HeaderOffsetStart = v2SubsystemStart + v2SubsystemLength + v2HeaderOffsetLength = 8 + v2Padding1Start = v2HeaderOffsetStart + v2HeaderOffsetLength + v2Padding1Length = 184 + v2ChecksumStart = v2Padding1Start + v2Padding1Length + v2ChecksumLength = 64 + v2Padding4096Start = v2ChecksumStart + v2ChecksumLength + v2Padding4096Length = 7 * 512 + v2HeaderStructSize = v2Padding4096Start + v2Padding4096Length + + V2Stripes = 4000 + V2AlignKeyslots = 4096 + V2SectorSize = 4096 +) + +func (h V2Header) Magic() string { + return string(h[v2MagicStart : v2MagicStart+v2MagicLength]) +} + +func (h *V2Header) SetMagic(magic string) error { + switch magic { + case V2Magic1, V2Magic2: + copy(h[v2MagicStart:v2MagicStart+v2MagicLength], []uint8(magic)) + return nil + } + return fmt.Errorf("magic %q not acceptable, only %q and %q are acceptable magic values: %w", magic, V2Magic1, V2Magic2, syscall.EINVAL) +} + +func (h V2Header) readu2(offset int) uint16 { + t := uint16(0) + for i := 0; i < 2; i++ { + t = (t << 8) + uint16(h[offset+i]) + } + return t +} + +func (h V2Header) readu8(offset int) uint64 { + t := uint64(0) + for i := 0; i < 8; i++ { + t = (t << 8) + uint64(h[offset+i]) + } + return t +} + +func (h *V2Header) writeu2(offset int, value uint16) { + t := value + for i := 0; i < 2; i++ { + h[offset+1-i] = uint8(uint64(t) & 0xff) + t >>= 8 + } +} + +func (h *V2Header) writeu8(offset int, value uint64) { + t := value + for i := 0; i < 8; i++ { + h[offset+7-i] = uint8(uint64(t) & 0xff) + t >>= 8 + } +} + +func (h V2Header) Version() uint16 { + return h.readu2(v2VersionStart) +} + +func (h *V2Header) SetVersion(version uint16) error { + switch version { + case 2: + h.writeu2(v2VersionStart, version) + return nil + } + return fmt.Errorf("version %d not acceptable, only 2 is an acceptable version: %w", version, syscall.EINVAL) +} + +func (h V2Header) HeaderSize() uint64 { + return h.readu8(v2HeaderSizeStart) +} + +func (h *V2Header) SetHeaderSize(size uint64) { + h.writeu8(v2HeaderSizeStart, size) +} + +func (h V2Header) SequenceID() uint64 { + return h.readu8(v2SequenceIDStart) +} + +func (h *V2Header) SetSequenceID(id uint64) { + h.writeu8(v2SequenceIDStart, id) +} + +func trimZeroPad(s string) string { + return strings.TrimRightFunc(s, func(r rune) bool { return r == 0 }) +} + +func (h V2Header) Label() string { + return trimZeroPad(string(h[v2LabelStart : v2LabelStart+v2LabelLength])) +} + +func (h *V2Header) setZeroString(offset int, value string, length int) { + for len(value) < length { + value = value + "\000" + } + copy(h[offset:offset+length], []uint8(value)) +} + +func (h *V2Header) SetLabel(label string) { + h.setZeroString(v2LabelStart, label, v2LabelLength) +} + +func (h V2Header) ChecksumAlgorithm() string { + return trimZeroPad(string(h[v2ChecksumAlgorithmStart : v2ChecksumAlgorithmStart+v2ChecksumAlgorithmLength])) +} + +func (h *V2Header) SetChecksumAlgorithm(alg string) { + h.setZeroString(v2ChecksumAlgorithmStart, alg, v2ChecksumAlgorithmLength) +} + +func dupInt8(s []uint8) []uint8 { + c := make([]uint8, len(s)) + copy(c, s) + return c +} + +func (h *V2Header) setInt8(offset int, s []uint8, length int) { + t := make([]byte, length) + copy(t, s) + copy(h[offset:offset+length], t) +} + +func (h V2Header) Salt() []uint8 { + return dupInt8(h[v2SaltStart : v2SaltStart+v2SaltLength]) +} + +func (h *V2Header) SetSalt(salt []uint8) { + h.setInt8(v2SaltStart, salt, v2SaltLength) +} + +func (h V2Header) UUID() string { + return trimZeroPad(string(h[v2UUIDStart : v2UUIDStart+v2UUIDLength])) +} + +func (h *V2Header) SetUUID(uuid string) { + h.setZeroString(v2UUIDStart, uuid, v2UUIDLength) +} + +func (h V2Header) Subsystem() string { + return trimZeroPad(string(h[v2SubsystemStart : v2SubsystemStart+v2SubsystemLength])) +} + +func (h *V2Header) SetSubsystem(ss string) { + h.setZeroString(v2SubsystemStart, ss, v2SubsystemLength) +} + +func (h V2Header) HeaderOffset() uint64 { + return h.readu8(v2HeaderOffsetStart) +} + +func (h *V2Header) SetHeaderOffset(o uint64) { + h.writeu8(v2HeaderOffsetStart, o) +} + +func (h V2Header) Checksum() []uint8 { + hasher, err := hasherByName(h.ChecksumAlgorithm()) + if err == nil { + return dupInt8(h[v2ChecksumStart : v2ChecksumStart+hasher().Size()]) + } + return dupInt8(h[v2ChecksumStart : v2ChecksumStart+v2ChecksumLength]) +} + +func (h *V2Header) SetChecksum(sum []uint8) { + h.setInt8(v2ChecksumStart, sum, v2ChecksumLength) +} diff --git a/vendor/github.com/containers/luksy/v2json.go b/vendor/github.com/containers/luksy/v2json.go new file mode 100644 index 00000000..5d7650d3 --- /dev/null +++ b/vendor/github.com/containers/luksy/v2json.go @@ -0,0 +1,157 @@ +package luksy + +type V2JSON struct { + Config V2JSONConfig `json:"config"` + Keyslots map[string]V2JSONKeyslot `json:"keyslots"` + Digests map[string]V2JSONDigest `json:"digests"` + Segments map[string]V2JSONSegment `json:"segments"` + Tokens map[string]V2JSONToken `json:"tokens"` +} + +type V2JSONKeyslotPriority int + +func (p V2JSONKeyslotPriority) String() string { + switch p { + case V2JSONKeyslotPriorityIgnore: + return "ignore" + case V2JSONKeyslotPriorityNormal: + return "normal" + case V2JSONKeyslotPriorityHigh: + return "high" + } + return "unknown" +} + +const ( + V2JSONKeyslotPriorityIgnore = V2JSONKeyslotPriority(0) + V2JSONKeyslotPriorityNormal = V2JSONKeyslotPriority(1) + V2JSONKeyslotPriorityHigh = V2JSONKeyslotPriority(2) +) + +type V2JSONKeyslot struct { + Type string `json:"type"` + KeySize int `json:"key_size"` + Area V2JSONArea `json:"area"` + Priority *V2JSONKeyslotPriority `json:"priority,omitempty"` + *V2JSONKeyslotLUKS2 // type = "luks2" + *V2JSONKeyslotReencrypt // type = "reencrypt" +} + +type V2JSONKeyslotLUKS2 struct { + AF V2JSONAF `json:"af"` + Kdf V2JSONKdf `json:"kdf"` +} + +type V2JSONKeyslotReencrypt struct { + Mode string `json:"mode"` // only "reencrypt", "encrypt", "decrypt" + Direction string `json:"direction"` // only "forward", "backward" +} + +type V2JSONArea struct { + Type string `json:"type"` // only "raw", "none", "journal", "checksum", "datashift", "datashift-journal", "datashift-checksum" + Offset int64 `json:"offset,string"` + Size int64 `json:"size,string"` + *V2JSONAreaRaw // type = "raw" + *V2JSONAreaChecksum // type = "checksum" + *V2JSONAreaDatashift // type = "datashift" + *V2JSONAreaDatashiftChecksum // type = "datashift-checksum" +} + +type V2JSONAreaRaw struct { + Encryption string `json:"encryption"` + KeySize int `json:"key_size"` +} + +type V2JSONAreaChecksum struct { + Hash string `json:"hash"` + SectorSize int `json:"sector_size"` +} + +type V2JSONAreaDatashift struct { + ShiftSize int `json:"shift_size,string"` +} + +type V2JSONAreaDatashiftChecksum struct { + V2JSONAreaChecksum + V2JSONAreaDatashift +} + +type V2JSONAF struct { + Type string `json:"type"` // "luks1" + *V2JSONAFLUKS1 // type == "luks1" +} + +type V2JSONAFLUKS1 struct { + Stripes int `json:"stripes"` // 4000 + Hash string `json:"hash"` // "sha256" +} + +type V2JSONKdf struct { + Type string `json:"type"` + Salt []byte `json:"salt"` + *V2JSONKdfPbkdf2 // type = "pbkdf2" + *V2JSONKdfArgon2i // type = "argon2i" or type = "argon2id" +} + +type V2JSONKdfPbkdf2 struct { + Hash string `json:"hash"` + Iterations int `json:"iterations"` +} + +type V2JSONKdfArgon2i struct { + Time int `json:"time"` + Memory int `json:"memory"` + CPUs int `json:"cpus"` +} + +type V2JSONSegment struct { + Type string `json:"type"` // only "linear", "crypt" + Offset string `json:"offset"` + Size string `json:"size"` // numeric value or "dynamic" + Flags []string `json:"flags,omitempty"` + *V2JSONSegmentCrypt `json:",omitempty"` // type = "crypt" +} + +type V2JSONSegmentCrypt struct { + IVTweak int `json:"iv_tweak,string"` + Encryption string `json:"encryption"` + SectorSize int `json:"sector_size"` // 512 or 1024 or 2048 or 4096 + Integrity *V2JSONSegmentIntegrity `json:"integrity,omitempty"` +} + +type V2JSONSegmentIntegrity struct { + Type string `json:"type"` + JournalEncryption string `json:"journal_encryption"` + JournalIntegrity string `json:"journal_integrity"` +} + +type V2JSONDigest struct { + Type string `json:"type"` + Keyslots []string `json:"keyslots"` + Segments []string `json:"segments"` + Salt []byte `json:"salt"` + Digest []byte `json:"digest"` + *V2JSONDigestPbkdf2 // type == "pbkdf2" +} + +type V2JSONDigestPbkdf2 struct { + Hash string `json:"hash"` + Iterations int `json:"iterations"` +} + +type V2JSONConfig struct { + JsonSize int `json:"json_size,string"` + KeyslotsSize int `json:"keyslots_size,string,omitempty"` + Flags []string `json:"flags,omitempty"` // one or more of "allow-discards", "same-cpu-crypt", "submit-from-crypt-cpus", "no-journal", "no-read-workqueue", "no-write-workqueue" + Requirements []string `json:"requirements,omitempty"` +} + +type V2JSONToken struct { + Type string `json:"type"` // "luks2-keyring" + Keyslots []string `json:"keyslots,omitempty"` + *V2JSONTokenLUKS2Keyring // type == "luks2-keyring" +} + +type V2JSONTokenLUKS2Keyring struct { + KeyDescription string `json:"key_description"` +} diff --git a/vendor/github.com/containers/ocicrypt/.gitignore b/vendor/github.com/containers/ocicrypt/.gitignore new file mode 100644 index 00000000..b25c15b8 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/vendor/github.com/containers/ocicrypt/.golangci.yml b/vendor/github.com/containers/ocicrypt/.golangci.yml new file mode 100644 index 00000000..bf39af83 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/.golangci.yml @@ -0,0 +1,35 @@ +linters: + enable: + - depguard + - staticcheck + - unconvert + - gofmt + - goimports + - revive + - ineffassign + - govet + - unused + - misspell + +linters-settings: + depguard: + rules: + main: + files: + - $all + deny: + - pkg: "io/ioutil" + + revive: + severity: error + rules: + - name: indent-error-flow + severity: warning + disabled: false + + - name: error-strings + disabled: false + + staticcheck: + # Suppress reports of deprecated packages + checks: ["-SA1019"] diff --git a/vendor/github.com/containers/ocicrypt/ADOPTERS.md b/vendor/github.com/containers/ocicrypt/ADOPTERS.md new file mode 100644 index 00000000..fa4b03bb --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/ADOPTERS.md @@ -0,0 +1,10 @@ +Below are list of adopters of the `ocicrypt` library or supports use of OCI encrypted images: +- [skopeo](https://github.com/containers/skopeo) +- [buildah](https://github.com/containers/buildah) +- [containerd](https://github.com/containerd/imgcrypt) +- [nerdctl](https://github.com/containerd/nerdctl) +- [distribution](https://github.com/distribution/distribution) + +Below are the list of projects that are in the process of adopting support: +- [quay](https://github.com/quay/quay) +- [kata-containers](https://github.com/kata-containers/kata-containers) diff --git a/vendor/github.com/containers/ocicrypt/CODE-OF-CONDUCT.md b/vendor/github.com/containers/ocicrypt/CODE-OF-CONDUCT.md new file mode 100644 index 00000000..d68f8dbd --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/CODE-OF-CONDUCT.md @@ -0,0 +1,3 @@ +## The OCIcrypt Library Project Community Code of Conduct + +The OCIcrypt Library project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/main/CODE-OF-CONDUCT.md). diff --git a/vendor/github.com/containers/ocicrypt/LICENSE b/vendor/github.com/containers/ocicrypt/LICENSE new file mode 100644 index 00000000..95356353 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/LICENSE @@ -0,0 +1,189 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/ocicrypt/MAINTAINERS b/vendor/github.com/containers/ocicrypt/MAINTAINERS new file mode 100644 index 00000000..af38d03b --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/MAINTAINERS @@ -0,0 +1,6 @@ +# ocicrypt maintainers +# +# Github ID, Name, Email Address +lumjjb, Brandon Lum, lumjjb@gmail.com +stefanberger, Stefan Berger, stefanb@linux.ibm.com +arronwy, Arron Wang, arron.wang@intel.com diff --git a/vendor/github.com/containers/ocicrypt/Makefile b/vendor/github.com/containers/ocicrypt/Makefile new file mode 100644 index 00000000..97ddeefb --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/Makefile @@ -0,0 +1,35 @@ +# Copyright The containerd Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.PHONY: check build decoder generate-protobuf + +all: build + +FORCE: + +check: + golangci-lint run + +build: vendor + go build ./... + +vendor: + go mod tidy + +test: + go clean -testcache + go test ./... -test.v + +generate-protobuf: + protoc -I utils/keyprovider/ utils/keyprovider/keyprovider.proto --go_out=plugins=grpc:utils/keyprovider diff --git a/vendor/github.com/containers/ocicrypt/README.md b/vendor/github.com/containers/ocicrypt/README.md new file mode 100644 index 00000000..b69d14e3 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/README.md @@ -0,0 +1,50 @@ +# OCIcrypt Library + +The `ocicrypt` library is the OCI image spec implementation of container image encryption. More details of the spec can be seen in the [OCI repository](https://github.com/opencontainers/image-spec/pull/775). The purpose of this library is to encode spec structures and consts in code, as well as provide a consistent implementation of image encryption across container runtimes and build tools. + +Consumers of OCIcrypt: + +- [containerd/imgcrypt](https://github.com/containerd/imgcrypt) +- [cri-o](https://github.com/cri-o/cri-o) +- [skopeo](https://github.com/containers/skopeo) + + +## Usage + +There are various levels of usage for this library. The main consumers of these would be runtime/build tools, and a more specific use would be in the ability to extend cryptographic function. + +### Runtime/Build tool usage + +The general exposed interface a runtime/build tool would use, would be to perform encryption or decryption of layers: + +``` +package "github.com/containers/ocicrypt" +func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, desc ocispec.Descriptor) (io.Reader, EncryptLayerFinalizer, error) +func DecryptLayer(dc *config.DecryptConfig, encLayerReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool) (io.Reader, digest.Digest, error) +``` + +The settings/parameters to these functions can be specified via creation of an encryption config with the `github.com/containers/ocicrypt/config` package. We note that because setting of annotations and other fields of the layer descriptor is done through various means in different runtimes/build tools, it is the responsibility of the caller to still ensure that the layer descriptor follows the OCI specification (i.e. encoding, setting annotations, etc.). + + +### Crypto Agility and Extensibility + +The implementation for both symmetric and asymmetric encryption used in this library are behind 2 main interfaces, which users can extend if need be. These are in the following packages: +- github.com/containers/ocicrypt/blockcipher - LayerBlockCipher interface for block ciphers +- github.com/containers/ocicrypt/keywrap - KeyWrapper interface for key wrapping + +We note that adding interfaces here is risky outside the OCI spec is not recommended, unless for very specialized and confined usecases. Please open an issue or PR if there is a general usecase that could be added to the OCI spec. + + +#### Keyprovider interface + +As part of the keywrap interface, there is a [keyprovider](https://github.com/containers/ocicrypt/blob/main/docs/keyprovider.md) implementation that allows one to call out to a binary or service. + + +## Security Issues + +We consider security issues related to this library critical. Please report and security related issues by emailing maintainers in the [MAINTAINERS](MAINTAINERS) file. + + +## Ocicrypt Pkcs11 Support + +Ocicrypt Pkcs11 support is currently experiemental. For more details, please refer to the [this document](docs/pkcs11.md). diff --git a/vendor/github.com/containers/ocicrypt/SECURITY.md b/vendor/github.com/containers/ocicrypt/SECURITY.md new file mode 100644 index 00000000..ea98cb12 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/SECURITY.md @@ -0,0 +1,3 @@ +## Security and Disclosure Information Policy for the OCIcrypt Library Project + +The OCIcrypt Library Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/main/SECURITY.md) for the Containers Projects. diff --git a/vendor/github.com/containers/ocicrypt/blockcipher/blockcipher.go b/vendor/github.com/containers/ocicrypt/blockcipher/blockcipher.go new file mode 100644 index 00000000..b8436a8d --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/blockcipher/blockcipher.go @@ -0,0 +1,160 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package blockcipher + +import ( + "errors" + "fmt" + "io" + + "github.com/opencontainers/go-digest" +) + +// LayerCipherType is the ciphertype as specified in the layer metadata +type LayerCipherType string + +// TODO: Should be obtained from OCI spec once included +const ( + AES256CTR LayerCipherType = "AES_256_CTR_HMAC_SHA256" +) + +// PrivateLayerBlockCipherOptions includes the information required to encrypt/decrypt +// an image which are sensitive and should not be in plaintext +type PrivateLayerBlockCipherOptions struct { + // SymmetricKey represents the symmetric key used for encryption/decryption + // This field should be populated by Encrypt/Decrypt calls + SymmetricKey []byte `json:"symkey"` + + // Digest is the digest of the original data for verification. + // This is NOT populated by Encrypt/Decrypt calls + Digest digest.Digest `json:"digest"` + + // CipherOptions contains the cipher metadata used for encryption/decryption + // This field should be populated by Encrypt/Decrypt calls + CipherOptions map[string][]byte `json:"cipheroptions"` +} + +// PublicLayerBlockCipherOptions includes the information required to encrypt/decrypt +// an image which are public and can be deduplicated in plaintext across multiple +// recipients +type PublicLayerBlockCipherOptions struct { + // CipherType denotes the cipher type according to the list of OCI suppported + // cipher types. + CipherType LayerCipherType `json:"cipher"` + + // Hmac contains the hmac string to help verify encryption + Hmac []byte `json:"hmac"` + + // CipherOptions contains the cipher metadata used for encryption/decryption + // This field should be populated by Encrypt/Decrypt calls + CipherOptions map[string][]byte `json:"cipheroptions"` +} + +// LayerBlockCipherOptions contains the public and private LayerBlockCipherOptions +// required to encrypt/decrypt an image +type LayerBlockCipherOptions struct { + Public PublicLayerBlockCipherOptions + Private PrivateLayerBlockCipherOptions +} + +// LayerBlockCipher returns a provider for encrypt/decrypt functionality +// for handling the layer data for a specific algorithm +type LayerBlockCipher interface { + // GenerateKey creates a symmetric key + GenerateKey() ([]byte, error) + // Encrypt takes in layer data and returns the ciphertext and relevant LayerBlockCipherOptions + Encrypt(layerDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, Finalizer, error) + // Decrypt takes in layer ciphertext data and returns the plaintext and relevant LayerBlockCipherOptions + Decrypt(layerDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error) +} + +// LayerBlockCipherHandler is the handler for encrypt/decrypt for layers +type LayerBlockCipherHandler struct { + cipherMap map[LayerCipherType]LayerBlockCipher +} + +// Finalizer is called after data blobs are written, and returns the LayerBlockCipherOptions for the encrypted blob +type Finalizer func() (LayerBlockCipherOptions, error) + +// GetOpt returns the value of the cipher option and if the option exists +func (lbco LayerBlockCipherOptions) GetOpt(key string) (value []byte, ok bool) { + if v, ok := lbco.Public.CipherOptions[key]; ok { + return v, ok + } else if v, ok := lbco.Private.CipherOptions[key]; ok { + return v, ok + } + return nil, false +} + +func wrapFinalizerWithType(fin Finalizer, typ LayerCipherType) Finalizer { + return func() (LayerBlockCipherOptions, error) { + lbco, err := fin() + if err != nil { + return LayerBlockCipherOptions{}, err + } + lbco.Public.CipherType = typ + return lbco, err + } +} + +// Encrypt is the handler for the layer decryption routine +func (h *LayerBlockCipherHandler) Encrypt(plainDataReader io.Reader, typ LayerCipherType) (io.Reader, Finalizer, error) { + if c, ok := h.cipherMap[typ]; ok { + sk, err := c.GenerateKey() + if err != nil { + return nil, nil, err + } + opt := LayerBlockCipherOptions{ + Private: PrivateLayerBlockCipherOptions{ + SymmetricKey: sk, + }, + } + encDataReader, fin, err := c.Encrypt(plainDataReader, opt) + if err == nil { + fin = wrapFinalizerWithType(fin, typ) + } + return encDataReader, fin, err + } + return nil, nil, fmt.Errorf("unsupported cipher type: %s", typ) +} + +// Decrypt is the handler for the layer decryption routine +func (h *LayerBlockCipherHandler) Decrypt(encDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error) { + typ := opt.Public.CipherType + if typ == "" { + return nil, LayerBlockCipherOptions{}, errors.New("no cipher type provided") + } + if c, ok := h.cipherMap[typ]; ok { + return c.Decrypt(encDataReader, opt) + } + return nil, LayerBlockCipherOptions{}, fmt.Errorf("unsupported cipher type: %s", typ) +} + +// NewLayerBlockCipherHandler returns a new default handler +func NewLayerBlockCipherHandler() (*LayerBlockCipherHandler, error) { + h := LayerBlockCipherHandler{ + cipherMap: map[LayerCipherType]LayerBlockCipher{}, + } + + var err error + h.cipherMap[AES256CTR], err = NewAESCTRLayerBlockCipher(256) + if err != nil { + return nil, fmt.Errorf("unable to set up Cipher AES-256-CTR: %w", err) + } + + return &h, nil +} diff --git a/vendor/github.com/containers/ocicrypt/blockcipher/blockcipher_aes_ctr.go b/vendor/github.com/containers/ocicrypt/blockcipher/blockcipher_aes_ctr.go new file mode 100644 index 00000000..7db03e2e --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/blockcipher/blockcipher_aes_ctr.go @@ -0,0 +1,193 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package blockcipher + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "errors" + "fmt" + "hash" + "io" + + "github.com/containers/ocicrypt/utils" +) + +// AESCTRLayerBlockCipher implements the AES CTR stream cipher +type AESCTRLayerBlockCipher struct { + keylen int // in bytes + reader io.Reader + encrypt bool + stream cipher.Stream + err error + hmac hash.Hash + expHmac []byte + doneEncrypting bool +} + +type aesctrcryptor struct { + bc *AESCTRLayerBlockCipher +} + +// NewAESCTRLayerBlockCipher returns a new AES SIV block cipher of 256 or 512 bits +func NewAESCTRLayerBlockCipher(bits int) (LayerBlockCipher, error) { + if bits != 256 { + return nil, errors.New("AES CTR bit count not supported") + } + return &AESCTRLayerBlockCipher{keylen: bits / 8}, nil +} + +func (r *aesctrcryptor) Read(p []byte) (int, error) { + var ( + o int + ) + + if r.bc.err != nil { + return 0, r.bc.err + } + + o, err := utils.FillBuffer(r.bc.reader, p) + if err != nil { + if err == io.EOF { + r.bc.err = err + } else { + return 0, err + } + } + + if !r.bc.encrypt { + if _, err := r.bc.hmac.Write(p[:o]); err != nil { + r.bc.err = fmt.Errorf("could not write to hmac: %w", err) + return 0, r.bc.err + } + + if r.bc.err == io.EOF { + // Before we return EOF we let the HMAC comparison + // provide a verdict + if !hmac.Equal(r.bc.hmac.Sum(nil), r.bc.expHmac) { + r.bc.err = fmt.Errorf("could not properly decrypt byte stream; exp hmac: '%x', actual hmac: '%s'", r.bc.expHmac, r.bc.hmac.Sum(nil)) + return 0, r.bc.err + } + } + } + + r.bc.stream.XORKeyStream(p[:o], p[:o]) + + if r.bc.encrypt { + if _, err := r.bc.hmac.Write(p[:o]); err != nil { + r.bc.err = fmt.Errorf("could not write to hmac: %w", err) + return 0, r.bc.err + } + + if r.bc.err == io.EOF { + // Final data encrypted; Do the 'then-MAC' part + r.bc.doneEncrypting = true + } + } + + return o, r.bc.err +} + +// init initializes an instance +func (bc *AESCTRLayerBlockCipher) init(encrypt bool, reader io.Reader, opts LayerBlockCipherOptions) (LayerBlockCipherOptions, error) { + var ( + err error + ) + + key := opts.Private.SymmetricKey + if len(key) != bc.keylen { + return LayerBlockCipherOptions{}, fmt.Errorf("invalid key length of %d bytes; need %d bytes", len(key), bc.keylen) + } + + nonce, ok := opts.GetOpt("nonce") + if !ok { + nonce = make([]byte, aes.BlockSize) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return LayerBlockCipherOptions{}, fmt.Errorf("unable to generate random nonce: %w", err) + } + } + + block, err := aes.NewCipher(key) + if err != nil { + return LayerBlockCipherOptions{}, fmt.Errorf("aes.NewCipher failed: %w", err) + } + + bc.reader = reader + bc.encrypt = encrypt + bc.stream = cipher.NewCTR(block, nonce) + bc.err = nil + bc.hmac = hmac.New(sha256.New, key) + bc.expHmac = opts.Public.Hmac + bc.doneEncrypting = false + + if !encrypt && len(bc.expHmac) == 0 { + return LayerBlockCipherOptions{}, errors.New("HMAC is not provided for decryption process") + } + + lbco := LayerBlockCipherOptions{ + Private: PrivateLayerBlockCipherOptions{ + SymmetricKey: key, + CipherOptions: map[string][]byte{ + "nonce": nonce, + }, + }, + } + + return lbco, nil +} + +// GenerateKey creates a synmmetric key +func (bc *AESCTRLayerBlockCipher) GenerateKey() ([]byte, error) { + key := make([]byte, bc.keylen) + if _, err := io.ReadFull(rand.Reader, key); err != nil { + return nil, err + } + return key, nil +} + +// Encrypt takes in layer data and returns the ciphertext and relevant LayerBlockCipherOptions +func (bc *AESCTRLayerBlockCipher) Encrypt(plainDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, Finalizer, error) { + lbco, err := bc.init(true, plainDataReader, opt) + if err != nil { + return nil, nil, err + } + + finalizer := func() (LayerBlockCipherOptions, error) { + if !bc.doneEncrypting { + return LayerBlockCipherOptions{}, errors.New("Read()ing not complete, unable to finalize") + } + if lbco.Public.CipherOptions == nil { + lbco.Public.CipherOptions = map[string][]byte{} + } + lbco.Public.Hmac = bc.hmac.Sum(nil) + return lbco, nil + } + return &aesctrcryptor{bc}, finalizer, nil +} + +// Decrypt takes in layer ciphertext data and returns the plaintext and relevant LayerBlockCipherOptions +func (bc *AESCTRLayerBlockCipher) Decrypt(encDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error) { + lbco, err := bc.init(false, encDataReader, opt) + if err != nil { + return nil, LayerBlockCipherOptions{}, err + } + + return utils.NewDelayedReader(&aesctrcryptor{bc}, 1024*10), lbco, nil +} diff --git a/vendor/github.com/containers/ocicrypt/config/config.go b/vendor/github.com/containers/ocicrypt/config/config.go new file mode 100644 index 00000000..d960766e --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/config/config.go @@ -0,0 +1,114 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package config + +// EncryptConfig is the container image PGP encryption configuration holding +// the identifiers of those that will be able to decrypt the container and +// the PGP public keyring file data that contains their public keys. +type EncryptConfig struct { + // map holding 'gpg-recipients', 'gpg-pubkeyringfile', 'pubkeys', 'x509s' + Parameters map[string][][]byte + + DecryptConfig DecryptConfig +} + +// DecryptConfig wraps the Parameters map that holds the decryption key +type DecryptConfig struct { + // map holding 'privkeys', 'x509s', 'gpg-privatekeys' + Parameters map[string][][]byte +} + +// CryptoConfig is a common wrapper for EncryptConfig and DecrypConfig that can +// be passed through functions that share much code for encryption and decryption +type CryptoConfig struct { + EncryptConfig *EncryptConfig + DecryptConfig *DecryptConfig +} + +// InitDecryption initialized a CryptoConfig object with parameters used for decryption +func InitDecryption(dcparameters map[string][][]byte) CryptoConfig { + return CryptoConfig{ + DecryptConfig: &DecryptConfig{ + Parameters: dcparameters, + }, + } +} + +// InitEncryption initializes a CryptoConfig object with parameters used for encryption +// It also takes dcparameters that may be needed for decryption when adding a recipient +// to an already encrypted image +func InitEncryption(parameters, dcparameters map[string][][]byte) CryptoConfig { + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: parameters, + DecryptConfig: DecryptConfig{ + Parameters: dcparameters, + }, + }, + } +} + +// CombineCryptoConfigs takes a CryptoConfig list and creates a single CryptoConfig +// containing the crypto configuration of all the key bundles +func CombineCryptoConfigs(ccs []CryptoConfig) CryptoConfig { + ecparam := map[string][][]byte{} + ecdcparam := map[string][][]byte{} + dcparam := map[string][][]byte{} + + for _, cc := range ccs { + if ec := cc.EncryptConfig; ec != nil { + addToMap(ecparam, ec.Parameters) + addToMap(ecdcparam, ec.DecryptConfig.Parameters) + } + + if dc := cc.DecryptConfig; dc != nil { + addToMap(dcparam, dc.Parameters) + } + } + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ecparam, + DecryptConfig: DecryptConfig{ + Parameters: ecdcparam, + }, + }, + DecryptConfig: &DecryptConfig{ + Parameters: dcparam, + }, + } + +} + +// AttachDecryptConfig adds DecryptConfig to the field of EncryptConfig so that +// the decryption parameters can be used to add recipients to an existing image +// if the user is able to decrypt it. +func (ec *EncryptConfig) AttachDecryptConfig(dc *DecryptConfig) { + if dc != nil { + addToMap(ec.DecryptConfig.Parameters, dc.Parameters) + } +} + +func addToMap(orig map[string][][]byte, add map[string][][]byte) { + for k, v := range add { + if ov, ok := orig[k]; ok { + orig[k] = append(ov, v...) + } else { + orig[k] = v + } + } +} diff --git a/vendor/github.com/containers/ocicrypt/config/constructors.go b/vendor/github.com/containers/ocicrypt/config/constructors.go new file mode 100644 index 00000000..f7f29cd8 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/config/constructors.go @@ -0,0 +1,246 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package config + +import ( + "errors" + "fmt" + "strings" + + "github.com/containers/ocicrypt/crypto/pkcs11" + "gopkg.in/yaml.v3" +) + +// EncryptWithJwe returns a CryptoConfig to encrypt with jwe public keys +func EncryptWithJwe(pubKeys [][]byte) (CryptoConfig, error) { + dc := DecryptConfig{} + ep := map[string][][]byte{ + "pubkeys": pubKeys, + } + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} + +// EncryptWithPkcs7 returns a CryptoConfig to encrypt with pkcs7 x509 certs +func EncryptWithPkcs7(x509s [][]byte) (CryptoConfig, error) { + dc := DecryptConfig{} + + ep := map[string][][]byte{ + "x509s": x509s, + } + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} + +// EncryptWithGpg returns a CryptoConfig to encrypt with configured gpg parameters +func EncryptWithGpg(gpgRecipients [][]byte, gpgPubRingFile []byte) (CryptoConfig, error) { + dc := DecryptConfig{} + ep := map[string][][]byte{ + "gpg-recipients": gpgRecipients, + "gpg-pubkeyringfile": {gpgPubRingFile}, + } + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} + +// EncryptWithPkcs11 returns a CryptoConfig to encrypt with configured pkcs11 parameters +func EncryptWithPkcs11(pkcs11Config *pkcs11.Pkcs11Config, pkcs11Pubkeys, pkcs11Yamls [][]byte) (CryptoConfig, error) { + dc := DecryptConfig{} + ep := map[string][][]byte{} + + if len(pkcs11Yamls) > 0 { + if pkcs11Config == nil { + return CryptoConfig{}, errors.New("pkcs11Config must not be nil") + } + p11confYaml, err := yaml.Marshal(pkcs11Config) + if err != nil { + return CryptoConfig{}, fmt.Errorf("Could not marshal Pkcs11Config to Yaml: %w", err) + } + + dc = DecryptConfig{ + Parameters: map[string][][]byte{ + "pkcs11-config": {p11confYaml}, + }, + } + ep["pkcs11-yamls"] = pkcs11Yamls + } + if len(pkcs11Pubkeys) > 0 { + ep["pkcs11-pubkeys"] = pkcs11Pubkeys + } + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} + +// EncryptWithKeyProvider returns a CryptoConfig to encrypt with configured keyprovider parameters +func EncryptWithKeyProvider(keyProviders [][]byte) (CryptoConfig, error) { + dc := DecryptConfig{} + ep := make(map[string][][]byte) + for _, keyProvider := range keyProviders { + keyProvidersStr := string(keyProvider) + idx := strings.Index(keyProvidersStr, ":") + if idx > 0 { + ep[keyProvidersStr[:idx]] = append(ep[keyProvidersStr[:idx]], []byte(keyProvidersStr[idx+1:])) + } else { + ep[keyProvidersStr] = append(ep[keyProvidersStr], []byte("Enabled")) + } + } + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} + +// DecryptWithKeyProvider returns a CryptoConfig to decrypt with configured keyprovider parameters +func DecryptWithKeyProvider(keyProviders [][]byte) (CryptoConfig, error) { + dp := make(map[string][][]byte) + ep := map[string][][]byte{} + for _, keyProvider := range keyProviders { + keyProvidersStr := string(keyProvider) + idx := strings.Index(keyProvidersStr, ":") + if idx > 0 { + dp[keyProvidersStr[:idx]] = append(dp[keyProvidersStr[:idx]], []byte(keyProvidersStr[idx+1:])) + } else { + dp[keyProvidersStr] = append(dp[keyProvidersStr], []byte("Enabled")) + } + } + dc := DecryptConfig{ + Parameters: dp, + } + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} + +// DecryptWithPrivKeys returns a CryptoConfig to decrypt with configured private keys +func DecryptWithPrivKeys(privKeys [][]byte, privKeysPasswords [][]byte) (CryptoConfig, error) { + if len(privKeys) != len(privKeysPasswords) { + return CryptoConfig{}, errors.New("Length of privKeys should match length of privKeysPasswords") + } + + dc := DecryptConfig{ + Parameters: map[string][][]byte{ + "privkeys": privKeys, + "privkeys-passwords": privKeysPasswords, + }, + } + + ep := map[string][][]byte{} + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} + +// DecryptWithX509s returns a CryptoConfig to decrypt with configured x509 certs +func DecryptWithX509s(x509s [][]byte) (CryptoConfig, error) { + dc := DecryptConfig{ + Parameters: map[string][][]byte{ + "x509s": x509s, + }, + } + + ep := map[string][][]byte{} + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} + +// DecryptWithGpgPrivKeys returns a CryptoConfig to decrypt with configured gpg private keys +func DecryptWithGpgPrivKeys(gpgPrivKeys, gpgPrivKeysPwds [][]byte) (CryptoConfig, error) { + dc := DecryptConfig{ + Parameters: map[string][][]byte{ + "gpg-privatekeys": gpgPrivKeys, + "gpg-privatekeys-passwords": gpgPrivKeysPwds, + }, + } + + ep := map[string][][]byte{} + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} + +// DecryptWithPkcs11Yaml returns a CryptoConfig to decrypt with pkcs11 YAML formatted key files +func DecryptWithPkcs11Yaml(pkcs11Config *pkcs11.Pkcs11Config, pkcs11Yamls [][]byte) (CryptoConfig, error) { + p11confYaml, err := yaml.Marshal(pkcs11Config) + if err != nil { + return CryptoConfig{}, fmt.Errorf("Could not marshal Pkcs11Config to Yaml: %w", err) + } + + dc := DecryptConfig{ + Parameters: map[string][][]byte{ + "pkcs11-yamls": pkcs11Yamls, + "pkcs11-config": {p11confYaml}, + }, + } + + ep := map[string][][]byte{} + + return CryptoConfig{ + EncryptConfig: &EncryptConfig{ + Parameters: ep, + DecryptConfig: dc, + }, + DecryptConfig: &dc, + }, nil +} diff --git a/vendor/github.com/containers/ocicrypt/config/keyprovider-config/config.go b/vendor/github.com/containers/ocicrypt/config/keyprovider-config/config.go new file mode 100644 index 00000000..4785a831 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/config/keyprovider-config/config.go @@ -0,0 +1,80 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package config + +import ( + "encoding/json" + "fmt" + "os" +) + +// Command describes the structure of command, it consist of path and args, where path defines the location of +// binary executable and args are passed on to the binary executable +type Command struct { + Path string `json:"path,omitempty"` + Args []string `json:"args,omitempty"` +} + +// KeyProviderAttrs describes the structure of key provider, it defines the way of invocation to key provider +type KeyProviderAttrs struct { + Command *Command `json:"cmd,omitempty"` + Grpc string `json:"grpc,omitempty"` +} + +// OcicryptConfig represents the format of an ocicrypt_provider.conf config file +type OcicryptConfig struct { + KeyProviderConfig map[string]KeyProviderAttrs `json:"key-providers"` +} + +const ENVVARNAME = "OCICRYPT_KEYPROVIDER_CONFIG" + +// parseConfigFile parses a configuration file; it is not an error if the configuration file does +// not exist, so no error is returned. +func parseConfigFile(filename string) (*OcicryptConfig, error) { + // a non-existent config file is not an error + _, err := os.Stat(filename) + if os.IsNotExist(err) { + return nil, nil + } + + data, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + + ic := &OcicryptConfig{} + err = json.Unmarshal(data, ic) + return ic, err +} + +// getConfiguration tries to read the configuration file at the following locations +// ${OCICRYPT_KEYPROVIDER_CONFIG} == "/etc/ocicrypt_keyprovider.yaml" +// If no configuration file could be found or read a null pointer is returned +func GetConfiguration() (*OcicryptConfig, error) { + var ic *OcicryptConfig + var err error + filename := os.Getenv(ENVVARNAME) + if len(filename) > 0 { + ic, err = parseConfigFile(filename) + if err != nil { + return nil, fmt.Errorf("Error while parsing keyprovider config file: %w", err) + } + } else { + return nil, nil + } + return ic, nil +} diff --git a/vendor/github.com/containers/ocicrypt/crypto/pkcs11/common.go b/vendor/github.com/containers/ocicrypt/crypto/pkcs11/common.go new file mode 100644 index 00000000..072d7fe1 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/crypto/pkcs11/common.go @@ -0,0 +1,134 @@ +/* + Copyright The ocicrypt Authors. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package pkcs11 + +import ( + "fmt" + + pkcs11uri "github.com/stefanberger/go-pkcs11uri" + "gopkg.in/yaml.v3" +) + +// Pkcs11KeyFile describes the format of the pkcs11 (private) key file. +// It also carries pkcs11 module related environment variables that are transferred to the +// Pkcs11URI object and activated when the pkcs11 module is used. +type Pkcs11KeyFile struct { + Pkcs11 struct { + Uri string `yaml:"uri"` + } `yaml:"pkcs11"` + Module struct { + Env map[string]string `yaml:"env,omitempty"` + } `yaml:"module"` +} + +// Pkcs11KeyFileObject is a representation of the Pkcs11KeyFile with the pkcs11 URI as an object +type Pkcs11KeyFileObject struct { + Uri *pkcs11uri.Pkcs11URI +} + +// ParsePkcs11Uri parses a pkcs11 URI +func ParsePkcs11Uri(uri string) (*pkcs11uri.Pkcs11URI, error) { + p11uri := pkcs11uri.New() + err := p11uri.Parse(uri) + if err != nil { + return nil, fmt.Errorf("Could not parse Pkcs11URI from file: %w", err) + } + return p11uri, err +} + +// ParsePkcs11KeyFile parses a pkcs11 key file holding a pkcs11 URI describing a private key. +// The file has the following yaml format: +// pkcs11: +// - uri : +// An error is returned if the pkcs11 URI is malformed +func ParsePkcs11KeyFile(yamlstr []byte) (*Pkcs11KeyFileObject, error) { + p11keyfile := Pkcs11KeyFile{} + + err := yaml.Unmarshal(yamlstr, &p11keyfile) + if err != nil { + return nil, fmt.Errorf("Could not unmarshal pkcs11 keyfile: %w", err) + } + + p11uri, err := ParsePkcs11Uri(p11keyfile.Pkcs11.Uri) + if err != nil { + return nil, err + } + p11uri.SetEnvMap(p11keyfile.Module.Env) + + return &Pkcs11KeyFileObject{Uri: p11uri}, err +} + +// IsPkcs11PrivateKey checks whether the given YAML represents a Pkcs11 private key +func IsPkcs11PrivateKey(yamlstr []byte) bool { + _, err := ParsePkcs11KeyFile(yamlstr) + return err == nil +} + +// IsPkcs11PublicKey checks whether the given YAML represents a Pkcs11 public key +func IsPkcs11PublicKey(yamlstr []byte) bool { + _, err := ParsePkcs11KeyFile(yamlstr) + return err == nil +} + +// Pkcs11Config describes the layout of a pkcs11 config file +// The file has the following yaml format: +// module-directories: +// - /usr/lib64/pkcs11/ +// allowd-module-paths +// - /usr/lib64/pkcs11/libsofthsm2.so +type Pkcs11Config struct { + ModuleDirectories []string `yaml:"module-directories"` + AllowedModulePaths []string `yaml:"allowed-module-paths"` +} + +// GetDefaultModuleDirectories returns module directories covering +// a variety of Linux distros +func GetDefaultModuleDirectories() []string { + dirs := []string{ + "/usr/lib64/pkcs11/", // Fedora,RHEL,openSUSE + "/usr/lib/pkcs11/", // Fedora,ArchLinux + "/usr/local/lib/pkcs11/", + "/usr/lib/softhsm/", // Debian,Ubuntu + } + + // Debian directory: /usr/lib/(x86_64|aarch64|arm|powerpc64le|riscv64|s390x)-linux-gnu/ + hosttype, ostype, q := getHostAndOsType() + if len(hosttype) > 0 { + dir := fmt.Sprintf("/usr/lib/%s-%s-%s/", hosttype, ostype, q) + dirs = append(dirs, dir) + } + return dirs +} + +// GetDefaultModuleDirectoresFormatted returns the default module directories formatted for YAML +func GetDefaultModuleDirectoriesYaml(indent string) string { + res := "" + + for _, dir := range GetDefaultModuleDirectories() { + res += indent + "- " + dir + "\n" + } + return res +} + +// ParsePkcs11ConfigFile parses a pkcs11 config file hat influences the module search behavior +// as well as the set of modules that users are allowed to use +func ParsePkcs11ConfigFile(yamlstr []byte) (*Pkcs11Config, error) { + p11conf := Pkcs11Config{} + + err := yaml.Unmarshal(yamlstr, &p11conf) + if err != nil { + return &p11conf, fmt.Errorf("Could not parse Pkcs11Config: %w", err) + } + return &p11conf, nil +} diff --git a/vendor/github.com/containers/ocicrypt/crypto/pkcs11/pkcs11helpers.go b/vendor/github.com/containers/ocicrypt/crypto/pkcs11/pkcs11helpers.go new file mode 100644 index 00000000..fe047a1e --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/crypto/pkcs11/pkcs11helpers.go @@ -0,0 +1,485 @@ +//go:build cgo +// +build cgo + +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package pkcs11 + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "hash" + "net/url" + "os" + "strconv" + "strings" + + "github.com/miekg/pkcs11" + pkcs11uri "github.com/stefanberger/go-pkcs11uri" +) + +var ( + // OAEPLabel defines the label we use for OAEP encryption; this cannot be changed + OAEPLabel = []byte("") + + // OAEPSha1Params describes the OAEP parameters with sha1 hash algorithm; needed by SoftHSM + OAEPSha1Params = &pkcs11.OAEPParams{ + HashAlg: pkcs11.CKM_SHA_1, + MGF: pkcs11.CKG_MGF1_SHA1, + SourceType: pkcs11.CKZ_DATA_SPECIFIED, + SourceData: OAEPLabel, + } + // OAEPSha256Params describes the OAEP parameters with sha256 hash algorithm + OAEPSha256Params = &pkcs11.OAEPParams{ + HashAlg: pkcs11.CKM_SHA256, + MGF: pkcs11.CKG_MGF1_SHA256, + SourceType: pkcs11.CKZ_DATA_SPECIFIED, + SourceData: OAEPLabel, + } +) + +// rsaPublicEncryptOAEP encrypts the given plaintext with the given *rsa.PublicKey; the +// environment variable OCICRYPT_OAEP_HASHALG can be set to 'sha1' to force usage of sha1 for OAEP (SoftHSM). +// This function is needed by clients who are using a public key file for pkcs11 encryption +func rsaPublicEncryptOAEP(pubKey *rsa.PublicKey, plaintext []byte) ([]byte, string, error) { + var ( + hashfunc hash.Hash + hashalg string + ) + + oaephash := os.Getenv("OCICRYPT_OAEP_HASHALG") + // The default is sha256 (previously was sha1) + switch strings.ToLower(oaephash) { + case "sha1": + hashfunc = sha1.New() + hashalg = "sha1" + case "sha256", "": + hashfunc = sha256.New() + hashalg = "sha256" + default: + return nil, "", fmt.Errorf("Unsupported OAEP hash '%s'", oaephash) + } + ciphertext, err := rsa.EncryptOAEP(hashfunc, rand.Reader, pubKey, plaintext, OAEPLabel) + if err != nil { + return nil, "", fmt.Errorf("rss.EncryptOAEP failed: %w", err) + } + + return ciphertext, hashalg, nil +} + +// pkcs11UriGetLoginParameters gets the parameters necessary for login from the Pkcs11URI +// PIN and module are mandatory; slot-id is optional and if not found -1 will be returned +// For a privateKeyOperation a PIN is required and if none is given, this function will return an error +func pkcs11UriGetLoginParameters(p11uri *pkcs11uri.Pkcs11URI, privateKeyOperation bool) (string, string, int64, error) { + var ( + pin string + err error + ) + if privateKeyOperation { + if !p11uri.HasPIN() { + return "", "", 0, errors.New("Missing PIN for private key operation") + } + } + // some devices require a PIN to find a *public* key object, others don't + pin, _ = p11uri.GetPIN() + + module, err := p11uri.GetModule() + if err != nil { + return "", "", 0, fmt.Errorf("No module available in pkcs11 URI: %w", err) + } + + slotid := int64(-1) + + slot, ok := p11uri.GetPathAttribute("slot-id", false) + if ok { + slotid, err = strconv.ParseInt(slot, 10, 64) + if err != nil { + return "", "", 0, fmt.Errorf("slot-id is not a valid number: %w", err) + } + if slotid < 0 { + return "", "", 0, fmt.Errorf("slot-id is a negative number") + } + if uint64(slotid) > 0xffffffff { + return "", "", 0, fmt.Errorf("slot-id is larger than 32 bit") + } + } + + return pin, module, slotid, nil +} + +// pkcs11UriGetKeyIdAndLabel gets the key label by retrieving the value of the 'object' attribute +func pkcs11UriGetKeyIdAndLabel(p11uri *pkcs11uri.Pkcs11URI) (string, string, error) { + keyid, ok2 := p11uri.GetPathAttribute("id", false) + label, ok1 := p11uri.GetPathAttribute("object", false) + if !ok1 && !ok2 { + return "", "", errors.New("Neither 'id' nor 'object' attributes were found in pkcs11 URI") + } + return keyid, label, nil +} + +// pkcs11OpenSession opens a session with a pkcs11 device at the given slot and logs in with the given PIN +func pkcs11OpenSession(p11ctx *pkcs11.Ctx, slotid uint, pin string) (session pkcs11.SessionHandle, err error) { + session, err = p11ctx.OpenSession(slotid, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION) + if err != nil { + return 0, fmt.Errorf("OpenSession to slot %d failed: %w", slotid, err) + } + if len(pin) > 0 { + err = p11ctx.Login(session, pkcs11.CKU_USER, pin) + if err != nil { + _ = p11ctx.CloseSession(session) + return 0, fmt.Errorf("Could not login to device: %w", err) + } + } + return session, nil +} + +// pkcs11UriLogin uses the given pkcs11 URI to select the pkcs11 module (shared library) and to get +// the PIN to use for login; if the URI contains a slot-id, the given slot-id will be used, otherwise +// one slot after the other will be attempted and the first one where login succeeds will be used +func pkcs11UriLogin(p11uri *pkcs11uri.Pkcs11URI, privateKeyOperation bool) (ctx *pkcs11.Ctx, session pkcs11.SessionHandle, err error) { + pin, module, slotid, err := pkcs11UriGetLoginParameters(p11uri, privateKeyOperation) + if err != nil { + return nil, 0, err + } + + p11ctx := pkcs11.New(module) + if p11ctx == nil { + return nil, 0, errors.New("Please check module path, input is: " + module) + } + + err = p11ctx.Initialize() + if err != nil { + p11Err := err.(pkcs11.Error) + if p11Err != pkcs11.CKR_CRYPTOKI_ALREADY_INITIALIZED { + return nil, 0, fmt.Errorf("Initialize failed: %w", err) + } + } + + if slotid >= 0 { + session, err := pkcs11OpenSession(p11ctx, uint(slotid), pin) + return p11ctx, session, err + } + + slots, err := p11ctx.GetSlotList(true) + if err != nil { + return nil, 0, fmt.Errorf("GetSlotList failed: %w", err) + } + + tokenlabel, ok := p11uri.GetPathAttribute("token", false) + if !ok { + return nil, 0, errors.New("Missing 'token' attribute since 'slot-id' was not given") + } + + for _, slot := range slots { + ti, err := p11ctx.GetTokenInfo(slot) + if err != nil || ti.Label != tokenlabel { + continue + } + + session, err = pkcs11OpenSession(p11ctx, slot, pin) + if err == nil { + return p11ctx, session, err + } + } + if len(pin) > 0 { + return nil, 0, errors.New("Could not create session to any slot and/or log in") + } + return nil, 0, errors.New("Could not create session to any slot") +} + +func pkcs11Logout(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) { + _ = ctx.Logout(session) + _ = ctx.CloseSession(session) + _ = ctx.Finalize() + ctx.Destroy() +} + +// findObject finds an object of the given class with the given keyid and/or label +func findObject(p11ctx *pkcs11.Ctx, session pkcs11.SessionHandle, class uint, keyid, label string) (pkcs11.ObjectHandle, error) { + msg := "" + + template := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_CLASS, class), + } + if len(label) > 0 { + template = append(template, pkcs11.NewAttribute(pkcs11.CKA_LABEL, label)) + msg = fmt.Sprintf("label '%s'", label) + } + if len(keyid) > 0 { + template = append(template, pkcs11.NewAttribute(pkcs11.CKA_ID, keyid)) + if len(msg) > 0 { + msg += " and " + } + msg += url.PathEscape(keyid) + } + + if err := p11ctx.FindObjectsInit(session, template); err != nil { + return 0, fmt.Errorf("FindObjectsInit failed: %w", err) + } + + obj, _, err := p11ctx.FindObjects(session, 100) + if err != nil { + return 0, fmt.Errorf("FindObjects failed: %w", err) + } + + if err := p11ctx.FindObjectsFinal(session); err != nil { + return 0, fmt.Errorf("FindObjectsFinal failed: %w", err) + } + if len(obj) > 1 { + return 0, fmt.Errorf("There are too many (=%d) keys with %s", len(obj), msg) + } else if len(obj) == 1 { + return obj[0], nil + } + + return 0, fmt.Errorf("Could not find any object with %s", msg) +} + +// publicEncryptOAEP uses a public key described by a pkcs11 URI to OAEP encrypt the given plaintext +func publicEncryptOAEP(pubKey *Pkcs11KeyFileObject, plaintext []byte) ([]byte, string, error) { + oldenv, err := setEnvVars(pubKey.Uri.GetEnvMap()) + if err != nil { + return nil, "", err + } + defer restoreEnv(oldenv) + + p11ctx, session, err := pkcs11UriLogin(pubKey.Uri, false) + if err != nil { + return nil, "", err + } + defer pkcs11Logout(p11ctx, session) + + keyid, label, err := pkcs11UriGetKeyIdAndLabel(pubKey.Uri) + if err != nil { + return nil, "", err + } + + p11PubKey, err := findObject(p11ctx, session, pkcs11.CKO_PUBLIC_KEY, keyid, label) + if err != nil { + return nil, "", err + } + + var hashalg string + + var oaep *pkcs11.OAEPParams + oaephash := os.Getenv("OCICRYPT_OAEP_HASHALG") + // The default is sha256 (previously was sha1) + switch strings.ToLower(oaephash) { + case "sha1": + oaep = OAEPSha1Params + hashalg = "sha1" + case "sha256", "": + oaep = OAEPSha256Params + hashalg = "sha256" + default: + return nil, "", fmt.Errorf("Unsupported OAEP hash '%s'", oaephash) + } + + err = p11ctx.EncryptInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_OAEP, oaep)}, p11PubKey) + if err != nil { + return nil, "", fmt.Errorf("EncryptInit error: %w", err) + } + + ciphertext, err := p11ctx.Encrypt(session, plaintext) + if err != nil { + return nil, "", fmt.Errorf("Encrypt failed: %w", err) + } + return ciphertext, hashalg, nil +} + +// privateDecryptOAEP uses a pkcs11 URI describing a private key to OAEP decrypt a ciphertext +func privateDecryptOAEP(privKeyObj *Pkcs11KeyFileObject, ciphertext []byte, hashalg string) ([]byte, error) { + oldenv, err := setEnvVars(privKeyObj.Uri.GetEnvMap()) + if err != nil { + return nil, err + } + defer restoreEnv(oldenv) + + p11ctx, session, err := pkcs11UriLogin(privKeyObj.Uri, true) + if err != nil { + return nil, err + } + defer pkcs11Logout(p11ctx, session) + + keyid, label, err := pkcs11UriGetKeyIdAndLabel(privKeyObj.Uri) + if err != nil { + return nil, err + } + + p11PrivKey, err := findObject(p11ctx, session, pkcs11.CKO_PRIVATE_KEY, keyid, label) + if err != nil { + return nil, err + } + + var oaep *pkcs11.OAEPParams + + // An empty string from the Hash in the JSON historically defaults to sha1. + switch hashalg { + case "sha1", "": + oaep = OAEPSha1Params + case "sha256": + oaep = OAEPSha256Params + default: + return nil, fmt.Errorf("Unsupported hash algorithm '%s' for decryption", hashalg) + } + + err = p11ctx.DecryptInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_OAEP, oaep)}, p11PrivKey) + if err != nil { + return nil, fmt.Errorf("DecryptInit failed: %w", err) + } + plaintext, err := p11ctx.Decrypt(session, ciphertext) + if err != nil { + return nil, fmt.Errorf("Decrypt failed: %w", err) + } + return plaintext, err +} + +// +// The following part deals with the JSON formatted message for multiple pkcs11 recipients +// + +// Pkcs11Blob holds the encrypted blobs for all recipients; this is what we will put into the image's annotations +type Pkcs11Blob struct { + Version uint `json:"version"` + Recipients []Pkcs11Recipient `json:"recipients"` +} + +// Pkcs11Recipient holds the b64-encoded and encrypted blob for a particular recipient +type Pkcs11Recipient struct { + Version uint `json:"version"` + Blob string `json:"blob"` + Hash string `json:"hash,omitempty"` +} + +// EncryptMultiple encrypts for one or multiple pkcs11 devices; the public keys passed to this function +// may either be *rsa.PublicKey or *pkcs11uri.Pkcs11URI; the returned byte array is a JSON string of the +// following format: +// { +// recipients: [ // recipient list +// { +// "version": 0, +// "blob": , +// "hash": +// } , +// { +// "version": 0, +// "blob": , +// "hash": +// } , +// [...] +// ] +// } +func EncryptMultiple(pubKeys []interface{}, data []byte) ([]byte, error) { + var ( + ciphertext []byte + err error + pkcs11blob Pkcs11Blob = Pkcs11Blob{Version: 0} + hashalg string + ) + + for _, pubKey := range pubKeys { + switch pkey := pubKey.(type) { + case *rsa.PublicKey: + ciphertext, hashalg, err = rsaPublicEncryptOAEP(pkey, data) + case *Pkcs11KeyFileObject: + ciphertext, hashalg, err = publicEncryptOAEP(pkey, data) + default: + err = fmt.Errorf("Unsupported key object type for pkcs11 public key") + } + if err != nil { + return nil, err + } + + recipient := Pkcs11Recipient{ + Version: 0, + Blob: base64.StdEncoding.EncodeToString(ciphertext), + Hash: hashalg, + } + + pkcs11blob.Recipients = append(pkcs11blob.Recipients, recipient) + } + return json.Marshal(&pkcs11blob) +} + +// Decrypt tries to decrypt one of the recipients' blobs using a pkcs11 private key. +// The input pkcs11blobstr is a string with the following format: +// { +// recipients: [ // recipient list +// { +// "version": 0, +// "blob": , +// "hash": +// } , +// { +// "version": 0, +// "blob": , +// "hash": +// } , +// [...] +// } +// Note: More recent versions of this code explicitly write 'sha1' +// while older versions left it empty in case of 'sha1'. +func Decrypt(privKeyObjs []*Pkcs11KeyFileObject, pkcs11blobstr []byte) ([]byte, error) { + pkcs11blob := Pkcs11Blob{} + err := json.Unmarshal(pkcs11blobstr, &pkcs11blob) + if err != nil { + return nil, fmt.Errorf("Could not parse Pkcs11Blob: %w", err) + } + switch pkcs11blob.Version { + case 0: + // latest supported version + default: + return nil, fmt.Errorf("found Pkcs11Blob with version %d but maximum supported version is 0", pkcs11blob.Version) + } + // since we do trial and error, collect all encountered errors + errs := "" + + for _, recipient := range pkcs11blob.Recipients { + switch recipient.Version { + case 0: + // last supported version + default: + return nil, fmt.Errorf("found Pkcs11Recipient with version %d but maximum supported version is 0", recipient.Version) + } + + ciphertext, err := base64.StdEncoding.DecodeString(recipient.Blob) + if err != nil || len(ciphertext) == 0 { + // This should never happen... we skip over decoding issues + errs += fmt.Sprintf("Base64 decoding failed: %s\n", err) + continue + } + // try all keys until one works + for _, privKeyObj := range privKeyObjs { + plaintext, err := privateDecryptOAEP(privKeyObj, ciphertext, recipient.Hash) + if err == nil { + return plaintext, nil + } + if uri, err2 := privKeyObj.Uri.Format(); err2 == nil { + errs += fmt.Sprintf("%s : %s\n", uri, err) + } else { + errs += fmt.Sprintf("%s\n", err) + } + } + } + + return nil, fmt.Errorf("Could not find a pkcs11 key for decryption:\n%s", errs) +} diff --git a/vendor/github.com/containers/ocicrypt/crypto/pkcs11/pkcs11helpers_nocgo.go b/vendor/github.com/containers/ocicrypt/crypto/pkcs11/pkcs11helpers_nocgo.go new file mode 100644 index 00000000..6cf0aa2a --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/crypto/pkcs11/pkcs11helpers_nocgo.go @@ -0,0 +1,30 @@ +//go:build !cgo +// +build !cgo + +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package pkcs11 + +import "fmt" + +func EncryptMultiple(pubKeys []interface{}, data []byte) ([]byte, error) { + return nil, fmt.Errorf("ocicrypt pkcs11 not supported on this build") +} + +func Decrypt(privKeyObjs []*Pkcs11KeyFileObject, pkcs11blobstr []byte) ([]byte, error) { + return nil, fmt.Errorf("ocicrypt pkcs11 not supported on this build") +} diff --git a/vendor/github.com/containers/ocicrypt/crypto/pkcs11/utils.go b/vendor/github.com/containers/ocicrypt/crypto/pkcs11/utils.go new file mode 100644 index 00000000..231da231 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/crypto/pkcs11/utils.go @@ -0,0 +1,115 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package pkcs11 + +import ( + "fmt" + "os" + "runtime" + "strings" + "sync" +) + +var ( + envLock sync.Mutex +) + +// setEnvVars sets the environment variables given in the map and locks the environment from +// modification with the same function; if successful, you *must* call restoreEnv with the return +// value from this function +func setEnvVars(env map[string]string) ([]string, error) { + envLock.Lock() + + if len(env) == 0 { + return nil, nil + } + + oldenv := os.Environ() + + for k, v := range env { + err := os.Setenv(k, v) + if err != nil { + restoreEnv(oldenv) + return nil, fmt.Errorf("Could not set environment variable '%s' to '%s': %w", k, v, err) + } + } + + return oldenv, nil +} + +func arrayToMap(elements []string) map[string]string { + o := make(map[string]string) + + for _, element := range elements { + p := strings.SplitN(element, "=", 2) + if len(p) == 2 { + o[p[0]] = p[1] + } + } + + return o +} + +// restoreEnv restores the environment to be exactly as given in the array of strings +// and unlocks the lock +func restoreEnv(envs []string) { + if envs != nil && len(envs) >= 0 { + target := arrayToMap(envs) + curr := arrayToMap(os.Environ()) + + for nc, vc := range curr { + vt, ok := target[nc] + if !ok { + os.Unsetenv(nc) + } else if vc == vt { + delete(target, nc) + } + } + + for nt, vt := range target { + os.Setenv(nt, vt) + } + } + + envLock.Unlock() +} + +func getHostAndOsType() (string, string, string) { + ht := "" + ot := "" + st := "" + switch runtime.GOOS { + case "linux": + ot = "linux" + st = "gnu" + switch runtime.GOARCH { + case "arm": + ht = "arm" + case "arm64": + ht = "aarch64" + case "amd64": + ht = "x86_64" + case "ppc64le": + ht = "powerpc64le" + case "riscv64": + ht = "riscv64" + case "s390x": + ht = "s390x" + } + } + return ht, ot, st +} diff --git a/vendor/github.com/containers/ocicrypt/encryption.go b/vendor/github.com/containers/ocicrypt/encryption.go new file mode 100644 index 00000000..b6fa9db4 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/encryption.go @@ -0,0 +1,356 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ocicrypt + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "strings" + + "github.com/containers/ocicrypt/blockcipher" + "github.com/containers/ocicrypt/config" + keyproviderconfig "github.com/containers/ocicrypt/config/keyprovider-config" + "github.com/containers/ocicrypt/keywrap" + "github.com/containers/ocicrypt/keywrap/jwe" + "github.com/containers/ocicrypt/keywrap/keyprovider" + "github.com/containers/ocicrypt/keywrap/pgp" + "github.com/containers/ocicrypt/keywrap/pkcs11" + "github.com/containers/ocicrypt/keywrap/pkcs7" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + log "github.com/sirupsen/logrus" +) + +// EncryptLayerFinalizer is a finalizer run to return the annotations to set for +// the encrypted layer +type EncryptLayerFinalizer func() (map[string]string, error) + +func init() { + keyWrappers = make(map[string]keywrap.KeyWrapper) + keyWrapperAnnotations = make(map[string]string) + RegisterKeyWrapper("pgp", pgp.NewKeyWrapper()) + RegisterKeyWrapper("jwe", jwe.NewKeyWrapper()) + RegisterKeyWrapper("pkcs7", pkcs7.NewKeyWrapper()) + RegisterKeyWrapper("pkcs11", pkcs11.NewKeyWrapper()) + ic, err := keyproviderconfig.GetConfiguration() + if err != nil { + log.Error(err) + } else if ic != nil { + for provider, attrs := range ic.KeyProviderConfig { + RegisterKeyWrapper("provider."+provider, keyprovider.NewKeyWrapper(provider, attrs)) + } + } +} + +var keyWrappers map[string]keywrap.KeyWrapper +var keyWrapperAnnotations map[string]string + +// RegisterKeyWrapper allows to register key wrappers by their encryption scheme +func RegisterKeyWrapper(scheme string, iface keywrap.KeyWrapper) { + keyWrappers[scheme] = iface + keyWrapperAnnotations[iface.GetAnnotationID()] = scheme +} + +// GetKeyWrapper looks up the encryptor interface given an encryption scheme (gpg, jwe) +func GetKeyWrapper(scheme string) keywrap.KeyWrapper { + return keyWrappers[scheme] +} + +// GetWrappedKeysMap returns a map of wrappedKeys as values in a +// map with the encryption scheme(s) as the key(s) +func GetWrappedKeysMap(desc ocispec.Descriptor) map[string]string { + wrappedKeysMap := make(map[string]string) + + for annotationsID, scheme := range keyWrapperAnnotations { + if annotation, ok := desc.Annotations[annotationsID]; ok { + wrappedKeysMap[scheme] = annotation + } + } + return wrappedKeysMap +} + +// EncryptLayer encrypts the layer by running one encryptor after the other +func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, desc ocispec.Descriptor) (io.Reader, EncryptLayerFinalizer, error) { + var ( + encLayerReader io.Reader + err error + encrypted bool + bcFin blockcipher.Finalizer + privOptsData []byte + pubOptsData []byte + ) + + if ec == nil { + return nil, nil, errors.New("EncryptConfig must not be nil") + } + + for annotationsID := range keyWrapperAnnotations { + annotation := desc.Annotations[annotationsID] + if annotation != "" { + privOptsData, err = decryptLayerKeyOptsData(&ec.DecryptConfig, desc) + if err != nil { + return nil, nil, err + } + pubOptsData, err = getLayerPubOpts(desc) + if err != nil { + return nil, nil, err + } + // already encrypted! + encrypted = true + } + } + + if !encrypted { + encLayerReader, bcFin, err = commonEncryptLayer(encOrPlainLayerReader, desc.Digest, blockcipher.AES256CTR) + if err != nil { + return nil, nil, err + } + } + + encLayerFinalizer := func() (map[string]string, error) { + // If layer was already encrypted, bcFin should be nil, use existing optsData + if bcFin != nil { + opts, err := bcFin() + if err != nil { + return nil, err + } + privOptsData, err = json.Marshal(opts.Private) + if err != nil { + return nil, fmt.Errorf("could not JSON marshal opts: %w", err) + } + pubOptsData, err = json.Marshal(opts.Public) + if err != nil { + return nil, fmt.Errorf("could not JSON marshal opts: %w", err) + } + } + + newAnnotations := make(map[string]string) + keysWrapped := false + if len(keyWrapperAnnotations) == 0 { + return nil, errors.New("missing Annotations needed for decryption") + } + for annotationsID, scheme := range keyWrapperAnnotations { + b64Annotations := desc.Annotations[annotationsID] + keywrapper := GetKeyWrapper(scheme) + b64Annotations, err = preWrapKeys(keywrapper, ec, b64Annotations, privOptsData) + if err != nil { + return nil, err + } + if b64Annotations != "" { + keysWrapped = true + newAnnotations[annotationsID] = b64Annotations + } + } + + if !keysWrapped { + return nil, errors.New("no wrapped keys produced by encryption") + } + newAnnotations["org.opencontainers.image.enc.pubopts"] = base64.StdEncoding.EncodeToString(pubOptsData) + + if len(newAnnotations) == 0 { + return nil, errors.New("no encryptor found to handle encryption") + } + + return newAnnotations, err + } + + // if nothing was encrypted, we just return encLayer = nil + return encLayerReader, encLayerFinalizer, err + +} + +// preWrapKeys calls WrapKeys and handles the base64 encoding and concatenation of the +// annotation data +func preWrapKeys(keywrapper keywrap.KeyWrapper, ec *config.EncryptConfig, b64Annotations string, optsData []byte) (string, error) { + newAnnotation, err := keywrapper.WrapKeys(ec, optsData) + if err != nil || len(newAnnotation) == 0 { + return b64Annotations, err + } + b64newAnnotation := base64.StdEncoding.EncodeToString(newAnnotation) + if b64Annotations == "" { + return b64newAnnotation, nil + } + return b64Annotations + "," + b64newAnnotation, nil +} + +// DecryptLayer decrypts a layer trying one keywrap.KeyWrapper after the other to see whether it +// can apply the provided private key +// If unwrapOnly is set we will only try to decrypt the layer encryption key and return +func DecryptLayer(dc *config.DecryptConfig, encLayerReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool) (io.Reader, digest.Digest, error) { + if dc == nil { + return nil, "", errors.New("DecryptConfig must not be nil") + } + privOptsData, err := decryptLayerKeyOptsData(dc, desc) + if err != nil || unwrapOnly { + return nil, "", err + } + + var pubOptsData []byte + pubOptsData, err = getLayerPubOpts(desc) + if err != nil { + return nil, "", err + } + + return commonDecryptLayer(encLayerReader, privOptsData, pubOptsData) +} + +func decryptLayerKeyOptsData(dc *config.DecryptConfig, desc ocispec.Descriptor) ([]byte, error) { + privKeyGiven := false + errs := "" + if len(keyWrapperAnnotations) == 0 { + return nil, errors.New("missing Annotations needed for decryption") + } + for annotationsID, scheme := range keyWrapperAnnotations { + b64Annotation := desc.Annotations[annotationsID] + if b64Annotation != "" { + keywrapper := GetKeyWrapper(scheme) + + if keywrapper.NoPossibleKeys(dc.Parameters) { + continue + } + + if len(keywrapper.GetPrivateKeys(dc.Parameters)) > 0 { + privKeyGiven = true + } + optsData, err := preUnwrapKey(keywrapper, dc, b64Annotation) + if err != nil { + // try next keywrap.KeyWrapper + errs += fmt.Sprintf("%s\n", err) + continue + } + if optsData == nil { + // try next keywrap.KeyWrapper + continue + } + return optsData, nil + } + } + if !privKeyGiven { + return nil, fmt.Errorf("missing private key needed for decryption:\n%s", errs) + } + return nil, fmt.Errorf("no suitable key unwrapper found or none of the private keys could be used for decryption:\n%s", errs) +} + +func getLayerPubOpts(desc ocispec.Descriptor) ([]byte, error) { + pubOptsString := desc.Annotations["org.opencontainers.image.enc.pubopts"] + if pubOptsString == "" { + return json.Marshal(blockcipher.PublicLayerBlockCipherOptions{}) + } + return base64.StdEncoding.DecodeString(pubOptsString) +} + +// preUnwrapKey decodes the comma separated base64 strings and calls the Unwrap function +// of the given keywrapper with it and returns the result in case the Unwrap functions +// does not return an error. If all attempts fail, an error is returned. +func preUnwrapKey(keywrapper keywrap.KeyWrapper, dc *config.DecryptConfig, b64Annotations string) ([]byte, error) { + if b64Annotations == "" { + return nil, nil + } + errs := "" + for _, b64Annotation := range strings.Split(b64Annotations, ",") { + annotation, err := base64.StdEncoding.DecodeString(b64Annotation) + if err != nil { + return nil, errors.New("could not base64 decode the annotation") + } + optsData, err := keywrapper.UnwrapKey(dc, annotation) + if err != nil { + errs += fmt.Sprintf("- %s\n", err) + continue + } + return optsData, nil + } + return nil, fmt.Errorf("no suitable key found for decrypting layer key:\n%s", errs) +} + +// commonEncryptLayer is a function to encrypt the plain layer using a new random +// symmetric key and return the LayerBlockCipherHandler's JSON in string form for +// later use during decryption +func commonEncryptLayer(plainLayerReader io.Reader, d digest.Digest, typ blockcipher.LayerCipherType) (io.Reader, blockcipher.Finalizer, error) { + lbch, err := blockcipher.NewLayerBlockCipherHandler() + if err != nil { + return nil, nil, err + } + + encLayerReader, bcFin, err := lbch.Encrypt(plainLayerReader, typ) + if err != nil { + return nil, nil, err + } + + newBcFin := func() (blockcipher.LayerBlockCipherOptions, error) { + lbco, err := bcFin() + if err != nil { + return blockcipher.LayerBlockCipherOptions{}, err + } + lbco.Private.Digest = d + return lbco, nil + } + + return encLayerReader, newBcFin, err +} + +// commonDecryptLayer decrypts an encrypted layer previously encrypted with commonEncryptLayer +// by passing along the optsData +func commonDecryptLayer(encLayerReader io.Reader, privOptsData []byte, pubOptsData []byte) (io.Reader, digest.Digest, error) { + privOpts := blockcipher.PrivateLayerBlockCipherOptions{} + err := json.Unmarshal(privOptsData, &privOpts) + if err != nil { + return nil, "", fmt.Errorf("could not JSON unmarshal privOptsData: %w", err) + } + + lbch, err := blockcipher.NewLayerBlockCipherHandler() + if err != nil { + return nil, "", err + } + + pubOpts := blockcipher.PublicLayerBlockCipherOptions{} + if len(pubOptsData) > 0 { + err := json.Unmarshal(pubOptsData, &pubOpts) + if err != nil { + return nil, "", fmt.Errorf("could not JSON unmarshal pubOptsData: %w", err) + } + } + + opts := blockcipher.LayerBlockCipherOptions{ + Private: privOpts, + Public: pubOpts, + } + + plainLayerReader, opts, err := lbch.Decrypt(encLayerReader, opts) + if err != nil { + return nil, "", err + } + + return plainLayerReader, opts.Private.Digest, nil +} + +// FilterOutAnnotations filters out the annotations belonging to the image encryption 'namespace' +// and returns a map with those taken out +func FilterOutAnnotations(annotations map[string]string) map[string]string { + a := make(map[string]string) + if len(annotations) > 0 { + for k, v := range annotations { + if strings.HasPrefix(k, "org.opencontainers.image.enc.") { + continue + } + a[k] = v + } + } + return a +} diff --git a/vendor/github.com/containers/ocicrypt/gpg.go b/vendor/github.com/containers/ocicrypt/gpg.go new file mode 100644 index 00000000..3bba4669 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/gpg.go @@ -0,0 +1,431 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ocicrypt + +import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + "regexp" + "strconv" + "strings" + "sync" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "golang.org/x/term" +) + +// GPGVersion enum representing the GPG client version to use. +type GPGVersion int + +const ( + // GPGv2 signifies gpgv2+ + GPGv2 GPGVersion = iota + // GPGv1 signifies gpgv1+ + GPGv1 + // GPGVersionUndetermined signifies gpg client version undetermined + GPGVersionUndetermined +) + +// GPGClient defines an interface for wrapping the gpg command line tools +type GPGClient interface { + // ReadGPGPubRingFile gets the byte sequence of the gpg public keyring + ReadGPGPubRingFile() ([]byte, error) + // GetGPGPrivateKey gets the private key bytes of a keyid given a passphrase + GetGPGPrivateKey(keyid uint64, passphrase string) ([]byte, error) + // GetSecretKeyDetails gets the details of a secret key + GetSecretKeyDetails(keyid uint64) ([]byte, bool, error) + // GetKeyDetails gets the details of a public key + GetKeyDetails(keyid uint64) ([]byte, bool, error) + // ResolveRecipients resolves PGP key ids to user names + ResolveRecipients([]string) []string +} + +// gpgClient contains generic gpg client information +type gpgClient struct { + gpgHomeDir string +} + +// gpgv2Client is a gpg2 client +type gpgv2Client struct { + gpgClient +} + +// gpgv1Client is a gpg client +type gpgv1Client struct { + gpgClient +} + +// GuessGPGVersion guesses the version of gpg. Defaults to gpg2 if exists, if +// not defaults to regular gpg. +func GuessGPGVersion() GPGVersion { + if err := exec.Command("gpg2", "--version").Run(); err == nil { + return GPGv2 + } else if err := exec.Command("gpg", "--version").Run(); err == nil { + return GPGv1 + } + return GPGVersionUndetermined +} + +// NewGPGClient creates a new GPGClient object representing the given version +// and using the given home directory +func NewGPGClient(gpgVersion, gpgHomeDir string) (GPGClient, error) { + v := new(GPGVersion) + switch gpgVersion { + case "v1": + *v = GPGv1 + case "v2": + *v = GPGv2 + default: + v = nil + } + return newGPGClient(v, gpgHomeDir) +} + +func newGPGClient(version *GPGVersion, homedir string) (GPGClient, error) { + var gpgVersion GPGVersion + if version != nil { + gpgVersion = *version + } else { + gpgVersion = GuessGPGVersion() + } + + switch gpgVersion { + case GPGv1: + return &gpgv1Client{ + gpgClient: gpgClient{gpgHomeDir: homedir}, + }, nil + case GPGv2: + return &gpgv2Client{ + gpgClient: gpgClient{gpgHomeDir: homedir}, + }, nil + case GPGVersionUndetermined: + return nil, fmt.Errorf("unable to determine GPG version") + default: + return nil, fmt.Errorf("unhandled case: NewGPGClient") + } +} + +// GetGPGPrivateKey gets the bytes of a specified keyid, supplying a passphrase +func (gc *gpgv2Client) GetGPGPrivateKey(keyid uint64, passphrase string) ([]byte, error) { + var args []string + + if gc.gpgHomeDir != "" { + args = append(args, []string{"--homedir", gc.gpgHomeDir}...) + } + + rfile, wfile, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("could not create pipe: %w", err) + } + defer func() { + rfile.Close() + wfile.Close() + }() + // fill pipe in background + go func(passphrase string) { + _, _ = wfile.Write([]byte(passphrase)) + wfile.Close() + }(passphrase) + + args = append(args, []string{"--pinentry-mode", "loopback", "--batch", "--passphrase-fd", fmt.Sprintf("%d", 3), "--export-secret-key", fmt.Sprintf("0x%x", keyid)}...) + + cmd := exec.Command("gpg2", args...) + cmd.ExtraFiles = []*os.File{rfile} + + return runGPGGetOutput(cmd) +} + +// ReadGPGPubRingFile reads the GPG public key ring file +func (gc *gpgv2Client) ReadGPGPubRingFile() ([]byte, error) { + var args []string + + if gc.gpgHomeDir != "" { + args = append(args, []string{"--homedir", gc.gpgHomeDir}...) + } + args = append(args, []string{"--batch", "--export"}...) + + cmd := exec.Command("gpg2", args...) + + return runGPGGetOutput(cmd) +} + +func (gc *gpgv2Client) getKeyDetails(option string, keyid uint64) ([]byte, bool, error) { + var args []string + + if gc.gpgHomeDir != "" { + args = []string{"--homedir", gc.gpgHomeDir} + } + args = append(args, option, fmt.Sprintf("0x%x", keyid)) + + cmd := exec.Command("gpg2", args...) + + keydata, err := runGPGGetOutput(cmd) + return keydata, err == nil, err +} + +// GetSecretKeyDetails retrieves the secret key details of key with keyid. +// returns a byte array of the details and a bool if the key exists +func (gc *gpgv2Client) GetSecretKeyDetails(keyid uint64) ([]byte, bool, error) { + return gc.getKeyDetails("-K", keyid) +} + +// GetKeyDetails retrieves the public key details of key with keyid. +// returns a byte array of the details and a bool if the key exists +func (gc *gpgv2Client) GetKeyDetails(keyid uint64) ([]byte, bool, error) { + return gc.getKeyDetails("-k", keyid) +} + +// ResolveRecipients converts PGP keyids to email addresses, if possible +func (gc *gpgv2Client) ResolveRecipients(recipients []string) []string { + return resolveRecipients(gc, recipients) +} + +// GetGPGPrivateKey gets the bytes of a specified keyid, supplying a passphrase +func (gc *gpgv1Client) GetGPGPrivateKey(keyid uint64, _ string) ([]byte, error) { + var args []string + + if gc.gpgHomeDir != "" { + args = append(args, []string{"--homedir", gc.gpgHomeDir}...) + } + args = append(args, []string{"--batch", "--export-secret-key", fmt.Sprintf("0x%x", keyid)}...) + + cmd := exec.Command("gpg", args...) + + return runGPGGetOutput(cmd) +} + +// ReadGPGPubRingFile reads the GPG public key ring file +func (gc *gpgv1Client) ReadGPGPubRingFile() ([]byte, error) { + var args []string + + if gc.gpgHomeDir != "" { + args = append(args, []string{"--homedir", gc.gpgHomeDir}...) + } + args = append(args, []string{"--batch", "--export"}...) + + cmd := exec.Command("gpg", args...) + + return runGPGGetOutput(cmd) +} + +func (gc *gpgv1Client) getKeyDetails(option string, keyid uint64) ([]byte, bool, error) { + var args []string + + if gc.gpgHomeDir != "" { + args = []string{"--homedir", gc.gpgHomeDir} + } + args = append(args, option, fmt.Sprintf("0x%x", keyid)) + + cmd := exec.Command("gpg", args...) + + keydata, err := runGPGGetOutput(cmd) + + return keydata, err == nil, err +} + +// GetSecretKeyDetails retrieves the secret key details of key with keyid. +// returns a byte array of the details and a bool if the key exists +func (gc *gpgv1Client) GetSecretKeyDetails(keyid uint64) ([]byte, bool, error) { + return gc.getKeyDetails("-K", keyid) +} + +// GetKeyDetails retrieves the public key details of key with keyid. +// returns a byte array of the details and a bool if the key exists +func (gc *gpgv1Client) GetKeyDetails(keyid uint64) ([]byte, bool, error) { + return gc.getKeyDetails("-k", keyid) +} + +// ResolveRecipients converts PGP keyids to email addresses, if possible +func (gc *gpgv1Client) ResolveRecipients(recipients []string) []string { + return resolveRecipients(gc, recipients) +} + +// runGPGGetOutput runs the GPG commandline and returns stdout as byte array +// and any stderr in the error +func runGPGGetOutput(cmd *exec.Cmd) ([]byte, error) { + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + stderr, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + + stdoutstr, err2 := io.ReadAll(stdout) + stderrstr, _ := io.ReadAll(stderr) + + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("error from %s: %s", cmd.Path, string(stderrstr)) + } + + return stdoutstr, err2 +} + +// resolveRecipients walks the list of recipients and attempts to convert +// all keyIds to email addresses; if something goes wrong during the +// conversion of a recipient, the original string is returned for that +// recpient +func resolveRecipients(gc GPGClient, recipients []string) []string { + var result []string + + for _, recipient := range recipients { + keyID, err := strconv.ParseUint(recipient, 0, 64) + if err != nil { + result = append(result, recipient) + } else { + details, found, _ := gc.GetKeyDetails(keyID) + if !found { + result = append(result, recipient) + } else { + email := extractEmailFromDetails(details) + if email == "" { + result = append(result, recipient) + } else { + result = append(result, email) + } + } + } + } + return result +} + +var ( + onceRegexp sync.Once + emailPattern *regexp.Regexp +) + +func extractEmailFromDetails(details []byte) string { + onceRegexp.Do(func() { + emailPattern = regexp.MustCompile(`uid\s+\[.*\]\s.*\s<(?P.+)>`) + }) + loc := emailPattern.FindSubmatchIndex(details) + if len(loc) == 0 { + return "" + } + return string(emailPattern.Expand(nil, []byte("$email"), details, loc)) +} + +// uint64ToStringArray converts an array of uint64's to an array of strings +// by applying a format string to each uint64 +func uint64ToStringArray(format string, in []uint64) []string { + var ret []string + + for _, v := range in { + ret = append(ret, fmt.Sprintf(format, v)) + } + return ret +} + +// GPGGetPrivateKey walks the list of layerInfos and tries to decrypt the +// wrapped symmetric keys. For this it determines whether a private key is +// in the GPGVault or on this system and prompts for the passwords for those +// that are available. If we do not find a private key on the system for +// getting to the symmetric key of a layer then an error is generated. +func GPGGetPrivateKey(descs []ocispec.Descriptor, gpgClient GPGClient, gpgVault GPGVault, mustFindKey bool) (gpgPrivKeys [][]byte, gpgPrivKeysPwds [][]byte, err error) { + // PrivateKeyData describes a private key + type PrivateKeyData struct { + KeyData []byte + KeyDataPassword []byte + } + var pkd PrivateKeyData + keyIDPasswordMap := make(map[uint64]PrivateKeyData) + + for _, desc := range descs { + for scheme, b64pgpPackets := range GetWrappedKeysMap(desc) { + if scheme != "pgp" { + continue + } + keywrapper := GetKeyWrapper(scheme) + if keywrapper == nil { + return nil, nil, fmt.Errorf("could not get KeyWrapper for %s", scheme) + } + keyIds, err := keywrapper.GetKeyIdsFromPacket(b64pgpPackets) + if err != nil { + return nil, nil, err + } + + found := false + for _, keyid := range keyIds { + // do we have this key? -- first check the vault + if gpgVault != nil { + _, keydata := gpgVault.GetGPGPrivateKey(keyid) + if len(keydata) > 0 { + pkd = PrivateKeyData{ + KeyData: keydata, + KeyDataPassword: nil, // password not supported in this case + } + keyIDPasswordMap[keyid] = pkd + found = true + break + } + } else if gpgClient != nil { + // check the local system's gpg installation + keyinfo, haveKey, _ := gpgClient.GetSecretKeyDetails(keyid) + // this may fail if the key is not here; we ignore the error + if !haveKey { + // key not on this system + continue + } + + _, found = keyIDPasswordMap[keyid] + if !found { + fmt.Printf("Passphrase required for Key id 0x%x: \n%v", keyid, string(keyinfo)) + fmt.Printf("Enter passphrase for key with Id 0x%x: ", keyid) + + password, err := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Printf("\n") + if err != nil { + return nil, nil, err + } + keydata, err := gpgClient.GetGPGPrivateKey(keyid, string(password)) + if err != nil { + return nil, nil, err + } + pkd = PrivateKeyData{ + KeyData: keydata, + KeyDataPassword: password, + } + keyIDPasswordMap[keyid] = pkd + found = true + } + break + } else { + return nil, nil, errors.New("no GPGVault or GPGClient passed") + } + } + if !found && len(b64pgpPackets) > 0 && mustFindKey { + ids := uint64ToStringArray("0x%x", keyIds) + + return nil, nil, fmt.Errorf("missing key for decryption of layer %x of %s. Need one of the following keys: %s", desc.Digest, desc.Platform, strings.Join(ids, ", ")) + } + } + } + + for _, pkd := range keyIDPasswordMap { + gpgPrivKeys = append(gpgPrivKeys, pkd.KeyData) + gpgPrivKeysPwds = append(gpgPrivKeysPwds, pkd.KeyDataPassword) + } + + return gpgPrivKeys, gpgPrivKeysPwds, nil +} diff --git a/vendor/github.com/containers/ocicrypt/gpgvault.go b/vendor/github.com/containers/ocicrypt/gpgvault.go new file mode 100644 index 00000000..f1bd0d98 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/gpgvault.go @@ -0,0 +1,100 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ocicrypt + +import ( + "bytes" + "fmt" + "os" + + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" +) + +// GPGVault defines an interface for wrapping multiple secret key rings +type GPGVault interface { + // AddSecretKeyRingData adds a secret keyring via its raw byte array + AddSecretKeyRingData(gpgSecretKeyRingData []byte) error + // AddSecretKeyRingDataArray adds secret keyring via its raw byte arrays + AddSecretKeyRingDataArray(gpgSecretKeyRingDataArray [][]byte) error + // AddSecretKeyRingFiles adds secret keyrings given their filenames + AddSecretKeyRingFiles(filenames []string) error + // GetGPGPrivateKey gets the private key bytes of a keyid given a passphrase + GetGPGPrivateKey(keyid uint64) ([]openpgp.Key, []byte) +} + +// gpgVault wraps an array of gpgSecretKeyRing +type gpgVault struct { + entityLists []openpgp.EntityList + keyDataList [][]byte // the raw data original passed in +} + +// NewGPGVault creates an empty GPGVault +func NewGPGVault() GPGVault { + return &gpgVault{} +} + +// AddSecretKeyRingData adds a secret keyring's to the gpgVault; the raw byte +// array read from the file must be passed and will be parsed by this function +func (g *gpgVault) AddSecretKeyRingData(gpgSecretKeyRingData []byte) error { + // read the private keys + r := bytes.NewReader(gpgSecretKeyRingData) + entityList, err := openpgp.ReadKeyRing(r) + if err != nil { + return fmt.Errorf("could not read keyring: %w", err) + } + g.entityLists = append(g.entityLists, entityList) + g.keyDataList = append(g.keyDataList, gpgSecretKeyRingData) + return nil +} + +// AddSecretKeyRingDataArray adds secret keyrings to the gpgVault; the raw byte +// arrays read from files must be passed +func (g *gpgVault) AddSecretKeyRingDataArray(gpgSecretKeyRingDataArray [][]byte) error { + for _, gpgSecretKeyRingData := range gpgSecretKeyRingDataArray { + if err := g.AddSecretKeyRingData(gpgSecretKeyRingData); err != nil { + return err + } + } + return nil +} + +// AddSecretKeyRingFiles adds the secret key rings given their filenames +func (g *gpgVault) AddSecretKeyRingFiles(filenames []string) error { + for _, filename := range filenames { + gpgSecretKeyRingData, err := os.ReadFile(filename) + if err != nil { + return err + } + err = g.AddSecretKeyRingData(gpgSecretKeyRingData) + if err != nil { + return err + } + } + return nil +} + +// GetGPGPrivateKey gets the bytes of a specified keyid, supplying a passphrase +func (g *gpgVault) GetGPGPrivateKey(keyid uint64) ([]openpgp.Key, []byte) { + for i, el := range g.entityLists { + decKeys := el.KeysByIdUsage(keyid, packet.KeyFlagEncryptCommunications) + if len(decKeys) > 0 { + return decKeys, g.keyDataList[i] + } + } + return nil, nil +} diff --git a/vendor/github.com/containers/ocicrypt/keywrap/jwe/keywrapper_jwe.go b/vendor/github.com/containers/ocicrypt/keywrap/jwe/keywrapper_jwe.go new file mode 100644 index 00000000..c1bdd6fb --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/keywrap/jwe/keywrapper_jwe.go @@ -0,0 +1,156 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package jwe + +import ( + "crypto/ecdsa" + "errors" + "fmt" + + "github.com/containers/ocicrypt/config" + "github.com/containers/ocicrypt/keywrap" + "github.com/containers/ocicrypt/utils" + "github.com/go-jose/go-jose/v4" +) + +type jweKeyWrapper struct { +} + +func (kw *jweKeyWrapper) GetAnnotationID() string { + return "org.opencontainers.image.enc.keys.jwe" +} + +// NewKeyWrapper returns a new key wrapping interface using jwe +func NewKeyWrapper() keywrap.KeyWrapper { + return &jweKeyWrapper{} +} + +// WrapKeys wraps the session key for recpients and encrypts the optsData, which +// describe the symmetric key used for encrypting the layer +func (kw *jweKeyWrapper) WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error) { + var joseRecipients []jose.Recipient + + err := addPubKeys(&joseRecipients, ec.Parameters["pubkeys"]) + if err != nil { + return nil, err + } + // no recipients is not an error... + if len(joseRecipients) == 0 { + return nil, nil + } + + encrypter, err := jose.NewMultiEncrypter(jose.A256GCM, joseRecipients, nil) + if err != nil { + return nil, fmt.Errorf("jose.NewMultiEncrypter failed: %w", err) + } + jwe, err := encrypter.Encrypt(optsData) + if err != nil { + return nil, fmt.Errorf("JWE Encrypt failed: %w", err) + } + return []byte(jwe.FullSerialize()), nil +} + +func (kw *jweKeyWrapper) UnwrapKey(dc *config.DecryptConfig, jweString []byte) ([]byte, error) { + // cf. list of algorithms in func addPubKeys() below + keyEncryptionAlgorithms := []jose.KeyAlgorithm{jose.RSA_OAEP, jose.RSA_OAEP_256, jose.ECDH_ES_A128KW, jose.ECDH_ES_A192KW, jose.ECDH_ES_A256KW} + // accept all algorithms defined in RFC 7518, section 5.1 + contentEncryption := []jose.ContentEncryption{jose.A128CBC_HS256, jose.A192CBC_HS384, jose.A256CBC_HS512, jose.A128GCM, jose.A192GCM, jose.A256GCM} + jwe, err := jose.ParseEncrypted(string(jweString), keyEncryptionAlgorithms, contentEncryption) + if err != nil { + return nil, errors.New("jose.ParseEncrypted failed") + } + + privKeys := kw.GetPrivateKeys(dc.Parameters) + if len(privKeys) == 0 { + return nil, errors.New("No private keys found for JWE decryption") + } + privKeysPasswords := kw.getPrivateKeysPasswords(dc.Parameters) + if len(privKeysPasswords) != len(privKeys) { + return nil, errors.New("Private key password array length must be same as that of private keys") + } + + for idx, privKey := range privKeys { + key, err := utils.ParsePrivateKey(privKey, privKeysPasswords[idx], "JWE") + if err != nil { + return nil, err + } + _, _, plain, err := jwe.DecryptMulti(key) + if err == nil { + return plain, nil + } + } + return nil, errors.New("JWE: No suitable private key found for decryption") +} + +func (kw *jweKeyWrapper) NoPossibleKeys(dcparameters map[string][][]byte) bool { + return len(kw.GetPrivateKeys(dcparameters)) == 0 +} + +func (kw *jweKeyWrapper) GetPrivateKeys(dcparameters map[string][][]byte) [][]byte { + return dcparameters["privkeys"] +} + +func (kw *jweKeyWrapper) getPrivateKeysPasswords(dcparameters map[string][][]byte) [][]byte { + return dcparameters["privkeys-passwords"] +} + +func (kw *jweKeyWrapper) GetKeyIdsFromPacket(b64jwes string) ([]uint64, error) { + return nil, nil +} + +func (kw *jweKeyWrapper) GetRecipients(b64jwes string) ([]string, error) { + return []string{"[jwe]"}, nil +} + +func addPubKeys(joseRecipients *[]jose.Recipient, pubKeys [][]byte) error { + if len(pubKeys) == 0 { + return nil + } + for _, pubKey := range pubKeys { + key, err := utils.ParsePublicKey(pubKey, "JWE") + if err != nil { + return err + } + + alg := jose.RSA_OAEP + switch key := key.(type) { + case *ecdsa.PublicKey: + alg = jose.ECDH_ES_A256KW + case *jose.JSONWebKey: + if key.Algorithm != "" { + alg = jose.KeyAlgorithm(key.Algorithm) + switch alg { + /* accepted algorithms */ + case jose.RSA_OAEP: + case jose.RSA_OAEP_256: + case jose.ECDH_ES_A128KW: + case jose.ECDH_ES_A192KW: + case jose.ECDH_ES_A256KW: + /* all others are rejected */ + default: + return fmt.Errorf("%s is an unsupported JWE key algorithm", alg) + } + } + } + + *joseRecipients = append(*joseRecipients, jose.Recipient{ + Algorithm: alg, + Key: key, + }) + } + return nil +} diff --git a/vendor/github.com/containers/ocicrypt/keywrap/keyprovider/keyprovider.go b/vendor/github.com/containers/ocicrypt/keywrap/keyprovider/keyprovider.go new file mode 100644 index 00000000..6ac0fcb9 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/keywrap/keyprovider/keyprovider.go @@ -0,0 +1,242 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package keyprovider + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/containers/ocicrypt/config" + keyproviderconfig "github.com/containers/ocicrypt/config/keyprovider-config" + "github.com/containers/ocicrypt/keywrap" + "github.com/containers/ocicrypt/utils" + keyproviderpb "github.com/containers/ocicrypt/utils/keyprovider" + log "github.com/sirupsen/logrus" + "google.golang.org/grpc" +) + +type keyProviderKeyWrapper struct { + provider string + attrs keyproviderconfig.KeyProviderAttrs +} + +func (kw *keyProviderKeyWrapper) GetAnnotationID() string { + return "org.opencontainers.image.enc.keys.provider." + kw.provider +} + +// NewKeyWrapper returns a new key wrapping interface using keyprovider +func NewKeyWrapper(p string, a keyproviderconfig.KeyProviderAttrs) keywrap.KeyWrapper { + return &keyProviderKeyWrapper{provider: p, attrs: a} +} + +type KeyProviderKeyWrapProtocolOperation string + +var ( + OpKeyWrap KeyProviderKeyWrapProtocolOperation = "keywrap" + OpKeyUnwrap KeyProviderKeyWrapProtocolOperation = "keyunwrap" +) + +// KeyProviderKeyWrapProtocolInput defines the input to the key provider binary or grpc method. +type KeyProviderKeyWrapProtocolInput struct { + // Operation is either "keywrap" or "keyunwrap" + Operation KeyProviderKeyWrapProtocolOperation `json:"op"` + // KeyWrapParams encodes the arguments to key wrap if operation is set to wrap + KeyWrapParams KeyWrapParams `json:"keywrapparams,omitempty"` + // KeyUnwrapParams encodes the arguments to key unwrap if operation is set to unwrap + KeyUnwrapParams KeyUnwrapParams `json:"keyunwrapparams,omitempty"` +} + +// KeyProviderKeyWrapProtocolOutput defines the output of the key provider binary or grpc method. +type KeyProviderKeyWrapProtocolOutput struct { + // KeyWrapResult encodes the results to key wrap if operation is to wrap + KeyWrapResults KeyWrapResults `json:"keywrapresults,omitempty"` + // KeyUnwrapResult encodes the result to key unwrap if operation is to unwrap + KeyUnwrapResults KeyUnwrapResults `json:"keyunwrapresults,omitempty"` +} + +type KeyWrapParams struct { + Ec *config.EncryptConfig `json:"ec"` + OptsData []byte `json:"optsdata"` +} + +type KeyUnwrapParams struct { + Dc *config.DecryptConfig `json:"dc"` + Annotation []byte `json:"annotation"` +} + +type KeyUnwrapResults struct { + OptsData []byte `json:"optsdata"` +} + +type KeyWrapResults struct { + Annotation []byte `json:"annotation"` +} + +var runner utils.CommandExecuter + +func init() { + runner = utils.Runner{} +} + +// WrapKeys calls appropriate binary executable/grpc server for wrapping the session key for recipients and gets encrypted optsData, which +// describe the symmetric key used for encrypting the layer +func (kw *keyProviderKeyWrapper) WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error) { + + input, err := json.Marshal(KeyProviderKeyWrapProtocolInput{ + Operation: OpKeyWrap, + KeyWrapParams: KeyWrapParams{ + Ec: ec, + OptsData: optsData, + }, + }) + + if err != nil { + return nil, err + } + + if _, ok := ec.Parameters[kw.provider]; ok { + if kw.attrs.Command != nil { + protocolOuput, err := getProviderCommandOutput(input, kw.attrs.Command) + if err != nil { + return nil, fmt.Errorf("error while retrieving keyprovider protocol command output: %w", err) + } + return protocolOuput.KeyWrapResults.Annotation, nil + } else if kw.attrs.Grpc != "" { + protocolOuput, err := getProviderGRPCOutput(input, kw.attrs.Grpc, OpKeyWrap) + if err != nil { + return nil, fmt.Errorf("error while retrieving keyprovider protocol grpc output: %w", err) + } + + return protocolOuput.KeyWrapResults.Annotation, nil + } + return nil, errors.New("Unsupported keyprovider invocation. Supported invocation methods are grpc and cmd") + } + + return nil, nil +} + +// UnwrapKey calls appropriate binary executable/grpc server for unwrapping the session key based on the protocol given in annotation for recipients and gets decrypted optsData, +// which describe the symmetric key used for decrypting the layer +func (kw *keyProviderKeyWrapper) UnwrapKey(dc *config.DecryptConfig, jsonString []byte) ([]byte, error) { + input, err := json.Marshal(KeyProviderKeyWrapProtocolInput{ + Operation: OpKeyUnwrap, + KeyUnwrapParams: KeyUnwrapParams{ + Dc: dc, + Annotation: jsonString, + }, + }) + if err != nil { + return nil, err + } + + if kw.attrs.Command != nil { + protocolOuput, err := getProviderCommandOutput(input, kw.attrs.Command) + if err != nil { + // If err is not nil, then ignore it and continue with rest of the given keyproviders + return nil, err + } + + return protocolOuput.KeyUnwrapResults.OptsData, nil + } else if kw.attrs.Grpc != "" { + protocolOuput, err := getProviderGRPCOutput(input, kw.attrs.Grpc, OpKeyUnwrap) + if err != nil { + // If err is not nil, then ignore it and continue with rest of the given keyproviders + return nil, err + } + + return protocolOuput.KeyUnwrapResults.OptsData, nil + } + return nil, errors.New("Unsupported keyprovider invocation. Supported invocation methods are grpc and cmd") +} + +func getProviderGRPCOutput(input []byte, connString string, operation KeyProviderKeyWrapProtocolOperation) (*KeyProviderKeyWrapProtocolOutput, error) { + var protocolOuput KeyProviderKeyWrapProtocolOutput + var grpcOutput *keyproviderpb.KeyProviderKeyWrapProtocolOutput + cc, err := grpc.Dial(connString, grpc.WithInsecure()) + if err != nil { + return nil, fmt.Errorf("error while dialing rpc server: %w", err) + } + defer func() { + derr := cc.Close() + if derr != nil { + log.WithError(derr).Error("Error closing grpc socket") + } + }() + + client := keyproviderpb.NewKeyProviderServiceClient(cc) + req := &keyproviderpb.KeyProviderKeyWrapProtocolInput{ + KeyProviderKeyWrapProtocolInput: input, + } + + if operation == OpKeyWrap { + grpcOutput, err = client.WrapKey(context.Background(), req) + if err != nil { + return nil, fmt.Errorf("Error from grpc method: %w", err) + } + } else if operation == OpKeyUnwrap { + grpcOutput, err = client.UnWrapKey(context.Background(), req) + if err != nil { + return nil, fmt.Errorf("Error from grpc method: %w", err) + } + } else { + return nil, errors.New("Unsupported operation") + } + + respBytes := grpcOutput.GetKeyProviderKeyWrapProtocolOutput() + err = json.Unmarshal(respBytes, &protocolOuput) + if err != nil { + return nil, fmt.Errorf("Error while unmarshalling grpc method output: %w", err) + } + + return &protocolOuput, nil +} + +func getProviderCommandOutput(input []byte, command *keyproviderconfig.Command) (*KeyProviderKeyWrapProtocolOutput, error) { + var protocolOuput KeyProviderKeyWrapProtocolOutput + // Convert interface to command structure + respBytes, err := runner.Exec(command.Path, command.Args, input) + if err != nil { + return nil, err + } + err = json.Unmarshal(respBytes, &protocolOuput) + if err != nil { + return nil, fmt.Errorf("Error while unmarshalling binary executable command output: %w", err) + } + return &protocolOuput, nil +} + +// Return false as it is not applicable to keyprovider protocol +func (kw *keyProviderKeyWrapper) NoPossibleKeys(dcparameters map[string][][]byte) bool { + return false +} + +// Return nil as it is not applicable to keyprovider protocol +func (kw *keyProviderKeyWrapper) GetPrivateKeys(dcparameters map[string][][]byte) [][]byte { + return nil +} + +// Return nil as it is not applicable to keyprovider protocol +func (kw *keyProviderKeyWrapper) GetKeyIdsFromPacket(_ string) ([]uint64, error) { + return nil, nil +} + +// Return nil as it is not applicable to keyprovider protocol +func (kw *keyProviderKeyWrapper) GetRecipients(_ string) ([]string, error) { + return nil, nil +} diff --git a/vendor/github.com/containers/ocicrypt/keywrap/keywrap.go b/vendor/github.com/containers/ocicrypt/keywrap/keywrap.go new file mode 100644 index 00000000..ed25e7da --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/keywrap/keywrap.go @@ -0,0 +1,48 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package keywrap + +import ( + "github.com/containers/ocicrypt/config" +) + +// KeyWrapper is the interface used for wrapping keys using +// a specific encryption technology (pgp, jwe) +type KeyWrapper interface { + WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error) + UnwrapKey(dc *config.DecryptConfig, annotation []byte) ([]byte, error) + GetAnnotationID() string + + // NoPossibleKeys returns true if there is no possibility of performing + // decryption for parameters provided. + NoPossibleKeys(dcparameters map[string][][]byte) bool + + // GetPrivateKeys (optional) gets the array of private keys. It is an optional implementation + // as in some key services, a private key may not be exportable (i.e. HSM) + // If not implemented, return nil + GetPrivateKeys(dcparameters map[string][][]byte) [][]byte + + // GetKeyIdsFromPacket (optional) gets a list of key IDs. This is optional as some encryption + // schemes may not have a notion of key IDs + // If not implemented, return the nil slice + GetKeyIdsFromPacket(packet string) ([]uint64, error) + + // GetRecipients (optional) gets a list of recipients. It is optional due to the validity of + // recipients in a particular encryptiong scheme + // If not implemented, return the nil slice + GetRecipients(packet string) ([]string, error) +} diff --git a/vendor/github.com/containers/ocicrypt/keywrap/pgp/keywrapper_gpg.go b/vendor/github.com/containers/ocicrypt/keywrap/pgp/keywrapper_gpg.go new file mode 100644 index 00000000..4ab9bd97 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/keywrap/pgp/keywrapper_gpg.go @@ -0,0 +1,272 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package pgp + +import ( + "bytes" + "crypto" + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "io" + "net/mail" + "strconv" + "strings" + + "github.com/containers/ocicrypt/config" + "github.com/containers/ocicrypt/keywrap" + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" +) + +type gpgKeyWrapper struct { +} + +// NewKeyWrapper returns a new key wrapping interface for pgp +func NewKeyWrapper() keywrap.KeyWrapper { + return &gpgKeyWrapper{} +} + +var ( + // GPGDefaultEncryptConfig is the default configuration for layer encryption/decryption + GPGDefaultEncryptConfig = &packet.Config{ + Rand: rand.Reader, + DefaultHash: crypto.SHA256, + DefaultCipher: packet.CipherAES256, + CompressionConfig: &packet.CompressionConfig{Level: 0}, // No compression + RSABits: 2048, + } +) + +func (kw *gpgKeyWrapper) GetAnnotationID() string { + return "org.opencontainers.image.enc.keys.pgp" +} + +// WrapKeys wraps the session key for recpients and encrypts the optsData, which +// describe the symmetric key used for encrypting the layer +func (kw *gpgKeyWrapper) WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error) { + ciphertext := new(bytes.Buffer) + el, err := kw.createEntityList(ec) + if err != nil { + return nil, fmt.Errorf("unable to create entity list: %w", err) + } + if len(el) == 0 { + // nothing to do -- not an error + return nil, nil + } + + plaintextWriter, err := openpgp.Encrypt(ciphertext, + el, /*EntityList*/ + nil, /* Sign*/ + nil, /* FileHint */ + GPGDefaultEncryptConfig) + if err != nil { + return nil, err + } + + if _, err = plaintextWriter.Write(optsData); err != nil { + return nil, err + } else if err = plaintextWriter.Close(); err != nil { + return nil, err + } + return ciphertext.Bytes(), err +} + +// UnwrapKey unwraps the symmetric key with which the layer is encrypted +// This symmetric key is encrypted in the PGP payload. +func (kw *gpgKeyWrapper) UnwrapKey(dc *config.DecryptConfig, pgpPacket []byte) ([]byte, error) { + pgpPrivateKeys, pgpPrivateKeysPwd, err := kw.getKeyParameters(dc.Parameters) + if err != nil { + return nil, err + } + + for idx, pgpPrivateKey := range pgpPrivateKeys { + r := bytes.NewBuffer(pgpPrivateKey) + entityList, err := openpgp.ReadKeyRing(r) + if err != nil { + return nil, fmt.Errorf("unable to parse private keys: %w", err) + } + + var prompt openpgp.PromptFunction + if len(pgpPrivateKeysPwd) > idx { + responded := false + prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) { + if responded { + return nil, fmt.Errorf("don't seem to have the right password") + } + responded = true + for _, key := range keys { + if key.PrivateKey != nil { + _ = key.PrivateKey.Decrypt(pgpPrivateKeysPwd[idx]) + } + } + return pgpPrivateKeysPwd[idx], nil + } + } + + r = bytes.NewBuffer(pgpPacket) + md, err := openpgp.ReadMessage(r, entityList, prompt, GPGDefaultEncryptConfig) + if err != nil { + continue + } + // we get the plain key options back + optsData, err := io.ReadAll(md.UnverifiedBody) + if err != nil { + continue + } + return optsData, nil + } + return nil, errors.New("PGP: No suitable key found to unwrap key") +} + +// GetKeyIdsFromWrappedKeys converts the base64 encoded PGPPacket to uint64 keyIds +func (kw *gpgKeyWrapper) GetKeyIdsFromPacket(b64pgpPackets string) ([]uint64, error) { + + var keyids []uint64 + for _, b64pgpPacket := range strings.Split(b64pgpPackets, ",") { + pgpPacket, err := base64.StdEncoding.DecodeString(b64pgpPacket) + if err != nil { + return nil, fmt.Errorf("could not decode base64 encoded PGP packet: %w", err) + } + newids, err := kw.getKeyIDs(pgpPacket) + if err != nil { + return nil, err + } + keyids = append(keyids, newids...) + } + return keyids, nil +} + +// getKeyIDs parses a PGPPacket and gets the list of recipients' key IDs +func (kw *gpgKeyWrapper) getKeyIDs(pgpPacket []byte) ([]uint64, error) { + var keyids []uint64 + + kbuf := bytes.NewBuffer(pgpPacket) + packets := packet.NewReader(kbuf) +ParsePackets: + for { + p, err := packets.Next() + if err == io.EOF { + break ParsePackets + } + if err != nil { + return []uint64{}, fmt.Errorf("packets.Next() failed: %w", err) + } + switch p := p.(type) { + case *packet.EncryptedKey: + keyids = append(keyids, p.KeyId) + case *packet.SymmetricallyEncrypted: + break ParsePackets + } + } + return keyids, nil +} + +// GetRecipients converts the wrappedKeys to an array of recipients +func (kw *gpgKeyWrapper) GetRecipients(b64pgpPackets string) ([]string, error) { + keyIds, err := kw.GetKeyIdsFromPacket(b64pgpPackets) + if err != nil { + return nil, err + } + var array []string + for _, keyid := range keyIds { + array = append(array, "0x"+strconv.FormatUint(keyid, 16)) + } + return array, nil +} + +func (kw *gpgKeyWrapper) NoPossibleKeys(dcparameters map[string][][]byte) bool { + return len(kw.GetPrivateKeys(dcparameters)) == 0 +} + +func (kw *gpgKeyWrapper) GetPrivateKeys(dcparameters map[string][][]byte) [][]byte { + return dcparameters["gpg-privatekeys"] +} + +func (kw *gpgKeyWrapper) getKeyParameters(dcparameters map[string][][]byte) ([][]byte, [][]byte, error) { + + privKeys := kw.GetPrivateKeys(dcparameters) + if len(privKeys) == 0 { + return nil, nil, errors.New("GPG: Missing private key parameter") + } + + return privKeys, dcparameters["gpg-privatekeys-passwords"], nil +} + +// createEntityList creates the opengpg EntityList by reading the KeyRing +// first and then filtering out recipients' keys +func (kw *gpgKeyWrapper) createEntityList(ec *config.EncryptConfig) (openpgp.EntityList, error) { + pgpPubringFile := ec.Parameters["gpg-pubkeyringfile"] + if len(pgpPubringFile) == 0 { + return nil, nil + } + r := bytes.NewReader(pgpPubringFile[0]) + + entityList, err := openpgp.ReadKeyRing(r) + if err != nil { + return nil, err + } + + gpgRecipients := ec.Parameters["gpg-recipients"] + if len(gpgRecipients) == 0 { + return nil, nil + } + + rSet := make(map[string]int) + for _, r := range gpgRecipients { + rSet[string(r)] = 0 + } + + var filteredList openpgp.EntityList + for _, entity := range entityList { + for k := range entity.Identities { + addr, err := mail.ParseAddress(k) + if err != nil { + return nil, err + } + for _, r := range gpgRecipients { + recp := string(r) + if strings.Compare(addr.Name, recp) == 0 || strings.Compare(addr.Address, recp) == 0 { + filteredList = append(filteredList, entity) + rSet[recp] = rSet[recp] + 1 + } + } + } + } + + // make sure we found keys for all the Recipients... + var buffer bytes.Buffer + notFound := false + buffer.WriteString("PGP: No key found for the following recipients: ") + + for k, v := range rSet { + if v == 0 { + if notFound { + buffer.WriteString(", ") + } + buffer.WriteString(k) + notFound = true + } + } + + if notFound { + return nil, errors.New(buffer.String()) + } + + return filteredList, nil +} diff --git a/vendor/github.com/containers/ocicrypt/keywrap/pkcs11/keywrapper_pkcs11.go b/vendor/github.com/containers/ocicrypt/keywrap/pkcs11/keywrapper_pkcs11.go new file mode 100644 index 00000000..b9a83c53 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/keywrap/pkcs11/keywrapper_pkcs11.go @@ -0,0 +1,152 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package pkcs11 + +import ( + "errors" + "fmt" + + "github.com/containers/ocicrypt/config" + "github.com/containers/ocicrypt/crypto/pkcs11" + "github.com/containers/ocicrypt/keywrap" + "github.com/containers/ocicrypt/utils" +) + +type pkcs11KeyWrapper struct { +} + +func (kw *pkcs11KeyWrapper) GetAnnotationID() string { + return "org.opencontainers.image.enc.keys.pkcs11" +} + +// NewKeyWrapper returns a new key wrapping interface using pkcs11 +func NewKeyWrapper() keywrap.KeyWrapper { + return &pkcs11KeyWrapper{} +} + +// WrapKeys wraps the session key for recpients and encrypts the optsData, which +// describe the symmetric key used for encrypting the layer +func (kw *pkcs11KeyWrapper) WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error) { + // append({}, ...) allocates a fresh backing array, and that's necessary to guarantee concurrent calls to WrapKeys (as in c/image/copy.Image) + // can't race writing to the same backing array. + pubKeys := append([][]byte{}, ec.Parameters["pkcs11-pubkeys"]...) // In Go 1.21, slices.Clone(ec.Parameters["pkcs11-pubkeys"]) + pubKeys = append(pubKeys, ec.Parameters["pkcs11-yamls"]...) + pkcs11Recipients, err := addPubKeys(&ec.DecryptConfig, pubKeys) + if err != nil { + return nil, err + } + // no recipients is not an error... + if len(pkcs11Recipients) == 0 { + return nil, nil + } + + jsonString, err := pkcs11.EncryptMultiple(pkcs11Recipients, optsData) + if err != nil { + return nil, fmt.Errorf("PKCS11 EncryptMulitple failed: %w", err) + } + return jsonString, nil +} + +func (kw *pkcs11KeyWrapper) UnwrapKey(dc *config.DecryptConfig, jsonString []byte) ([]byte, error) { + var pkcs11PrivKeys []*pkcs11.Pkcs11KeyFileObject + + privKeys := kw.GetPrivateKeys(dc.Parameters) + if len(privKeys) == 0 { + return nil, errors.New("No private keys found for PKCS11 decryption") + } + + p11conf, err := p11confFromParameters(dc.Parameters) + if err != nil { + return nil, err + } + + for _, privKey := range privKeys { + key, err := utils.ParsePrivateKey(privKey, nil, "PKCS11") + if err != nil { + return nil, err + } + switch pkcs11PrivKey := key.(type) { + case *pkcs11.Pkcs11KeyFileObject: + if p11conf != nil { + pkcs11PrivKey.Uri.SetModuleDirectories(p11conf.ModuleDirectories) + pkcs11PrivKey.Uri.SetAllowedModulePaths(p11conf.AllowedModulePaths) + } + pkcs11PrivKeys = append(pkcs11PrivKeys, pkcs11PrivKey) + default: + continue + } + } + + plaintext, err := pkcs11.Decrypt(pkcs11PrivKeys, jsonString) + if err == nil { + return plaintext, nil + } + + return nil, fmt.Errorf("PKCS11: No suitable private key found for decryption: %w", err) +} + +func (kw *pkcs11KeyWrapper) NoPossibleKeys(dcparameters map[string][][]byte) bool { + return len(kw.GetPrivateKeys(dcparameters)) == 0 +} + +func (kw *pkcs11KeyWrapper) GetPrivateKeys(dcparameters map[string][][]byte) [][]byte { + return dcparameters["pkcs11-yamls"] +} + +func (kw *pkcs11KeyWrapper) GetKeyIdsFromPacket(_ string) ([]uint64, error) { + return nil, nil +} + +func (kw *pkcs11KeyWrapper) GetRecipients(_ string) ([]string, error) { + return []string{"[pkcs11]"}, nil +} + +func addPubKeys(dc *config.DecryptConfig, pubKeys [][]byte) ([]interface{}, error) { + var pkcs11Keys []interface{} + + if len(pubKeys) == 0 { + return pkcs11Keys, nil + } + + p11conf, err := p11confFromParameters(dc.Parameters) + if err != nil { + return nil, err + } + + for _, pubKey := range pubKeys { + key, err := utils.ParsePublicKey(pubKey, "PKCS11") + if err != nil { + return nil, err + } + switch pkcs11PubKey := key.(type) { + case *pkcs11.Pkcs11KeyFileObject: + if p11conf != nil { + pkcs11PubKey.Uri.SetModuleDirectories(p11conf.ModuleDirectories) + pkcs11PubKey.Uri.SetAllowedModulePaths(p11conf.AllowedModulePaths) + } + } + pkcs11Keys = append(pkcs11Keys, key) + } + return pkcs11Keys, nil +} + +func p11confFromParameters(dcparameters map[string][][]byte) (*pkcs11.Pkcs11Config, error) { + if _, ok := dcparameters["pkcs11-config"]; ok { + return pkcs11.ParsePkcs11ConfigFile(dcparameters["pkcs11-config"][0]) + } + return nil, nil +} diff --git a/vendor/github.com/containers/ocicrypt/keywrap/pkcs7/keywrapper_pkcs7.go b/vendor/github.com/containers/ocicrypt/keywrap/pkcs7/keywrapper_pkcs7.go new file mode 100644 index 00000000..7ca32fc8 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/keywrap/pkcs7/keywrapper_pkcs7.go @@ -0,0 +1,137 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package pkcs7 + +import ( + "crypto" + "crypto/x509" + "errors" + "fmt" + + "github.com/containers/ocicrypt/config" + "github.com/containers/ocicrypt/keywrap" + "github.com/containers/ocicrypt/utils" + "github.com/smallstep/pkcs7" +) + +type pkcs7KeyWrapper struct { +} + +// NewKeyWrapper returns a new key wrapping interface using jwe +func NewKeyWrapper() keywrap.KeyWrapper { + return &pkcs7KeyWrapper{} +} + +func (kw *pkcs7KeyWrapper) GetAnnotationID() string { + return "org.opencontainers.image.enc.keys.pkcs7" +} + +// WrapKeys wraps the session key for recpients and encrypts the optsData, which +// describe the symmetric key used for encrypting the layer +func (kw *pkcs7KeyWrapper) WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error) { + x509Certs, err := collectX509s(ec.Parameters["x509s"]) + if err != nil { + return nil, err + } + // no recipients is not an error... + if len(x509Certs) == 0 { + return nil, nil + } + + pkcs7.ContentEncryptionAlgorithm = pkcs7.EncryptionAlgorithmAES128GCM + return pkcs7.Encrypt(optsData, x509Certs) +} + +func collectX509s(x509s [][]byte) ([]*x509.Certificate, error) { + if len(x509s) == 0 { + return nil, nil + } + var x509Certs []*x509.Certificate + for _, x509 := range x509s { + x509Cert, err := utils.ParseCertificate(x509, "PKCS7") + if err != nil { + return nil, err + } + x509Certs = append(x509Certs, x509Cert) + } + return x509Certs, nil +} + +func (kw *pkcs7KeyWrapper) NoPossibleKeys(dcparameters map[string][][]byte) bool { + return len(kw.GetPrivateKeys(dcparameters)) == 0 +} + +func (kw *pkcs7KeyWrapper) GetPrivateKeys(dcparameters map[string][][]byte) [][]byte { + return dcparameters["privkeys"] +} + +func (kw *pkcs7KeyWrapper) getPrivateKeysPasswords(dcparameters map[string][][]byte) [][]byte { + return dcparameters["privkeys-passwords"] +} + +// UnwrapKey unwraps the symmetric key with which the layer is encrypted +// This symmetric key is encrypted in the PKCS7 payload. +func (kw *pkcs7KeyWrapper) UnwrapKey(dc *config.DecryptConfig, pkcs7Packet []byte) ([]byte, error) { + privKeys := kw.GetPrivateKeys(dc.Parameters) + if len(privKeys) == 0 { + return nil, errors.New("no private keys found for PKCS7 decryption") + } + privKeysPasswords := kw.getPrivateKeysPasswords(dc.Parameters) + if len(privKeysPasswords) != len(privKeys) { + return nil, errors.New("private key password array length must be same as that of private keys") + } + + x509Certs, err := collectX509s(dc.Parameters["x509s"]) + if err != nil { + return nil, err + } + if len(x509Certs) == 0 { + return nil, errors.New("no x509 certificates found needed for PKCS7 decryption") + } + + p7, err := pkcs7.Parse(pkcs7Packet) + if err != nil { + return nil, fmt.Errorf("could not parse PKCS7 packet: %w", err) + } + + for idx, privKey := range privKeys { + key, err := utils.ParsePrivateKey(privKey, privKeysPasswords[idx], "PKCS7") + if err != nil { + return nil, err + } + for _, x509Cert := range x509Certs { + optsData, err := p7.Decrypt(x509Cert, crypto.PrivateKey(key)) + if err != nil { + continue + } + return optsData, nil + } + } + return nil, errors.New("PKCS7: No suitable private key found for decryption") +} + +// GetKeyIdsFromWrappedKeys converts the base64 encoded Packet to uint64 keyIds; +// We cannot do this with pkcs7 +func (kw *pkcs7KeyWrapper) GetKeyIdsFromPacket(b64pkcs7Packets string) ([]uint64, error) { + return nil, nil +} + +// GetRecipients converts the wrappedKeys to an array of recipients +// We cannot do this with pkcs7 +func (kw *pkcs7KeyWrapper) GetRecipients(b64pkcs7Packets string) ([]string, error) { + return []string{"[pkcs7]"}, nil +} diff --git a/vendor/github.com/containers/ocicrypt/reader.go b/vendor/github.com/containers/ocicrypt/reader.go new file mode 100644 index 00000000..a93eec8e --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/reader.go @@ -0,0 +1,40 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ocicrypt + +import ( + "io" +) + +type readerAtReader struct { + r io.ReaderAt + off int64 +} + +// ReaderFromReaderAt takes an io.ReaderAt and returns an io.Reader +func ReaderFromReaderAt(r io.ReaderAt) io.Reader { + return &readerAtReader{ + r: r, + off: 0, + } +} + +func (rar *readerAtReader) Read(p []byte) (n int, err error) { + n, err = rar.r.ReadAt(p, rar.off) + rar.off += int64(n) + return n, err +} diff --git a/vendor/github.com/containers/ocicrypt/spec/spec.go b/vendor/github.com/containers/ocicrypt/spec/spec.go new file mode 100644 index 00000000..c0c17182 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/spec/spec.go @@ -0,0 +1,20 @@ +package spec + +const ( + // MediaTypeLayerEnc is MIME type used for encrypted layers. + MediaTypeLayerEnc = "application/vnd.oci.image.layer.v1.tar+encrypted" + // MediaTypeLayerGzipEnc is MIME type used for encrypted gzip-compressed layers. + MediaTypeLayerGzipEnc = "application/vnd.oci.image.layer.v1.tar+gzip+encrypted" + // MediaTypeLayerZstdEnc is MIME type used for encrypted zstd-compressed layers. + MediaTypeLayerZstdEnc = "application/vnd.oci.image.layer.v1.tar+zstd+encrypted" + // MediaTypeLayerNonDistributableEnc is MIME type used for non distributable encrypted layers. + MediaTypeLayerNonDistributableEnc = "application/vnd.oci.image.layer.nondistributable.v1.tar+encrypted" + // MediaTypeLayerNonDistributableGzipEnc is MIME type used for non distributable encrypted gzip-compressed layers. + MediaTypeLayerNonDistributableGzipEnc = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip+encrypted" + // MediaTypeLayerNonDistributableZstdEnc is MIME type used for non distributable encrypted zstd-compressed layers. + MediaTypeLayerNonDistributableZstdEnc = "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd+encrypted" + // MediaTypeLayerNonDistributableZsdtEnc is MIME type used for non distributable encrypted zstd-compressed layers. + // + // Deprecated: Use [MediaTypeLayerNonDistributableZstdEnc]. + MediaTypeLayerNonDistributableZsdtEnc = MediaTypeLayerNonDistributableZstdEnc +) diff --git a/vendor/github.com/containers/ocicrypt/utils/delayedreader.go b/vendor/github.com/containers/ocicrypt/utils/delayedreader.go new file mode 100644 index 00000000..3b939bde --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/utils/delayedreader.go @@ -0,0 +1,109 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package utils + +import ( + "io" +) + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +// DelayedReader wraps a io.Reader and allows a client to use the Reader +// interface. The DelayedReader holds back some buffer to the client +// so that it can report any error that occurred on the Reader it wraps +// early to the client while it may still have held some data back. +type DelayedReader struct { + reader io.Reader // Reader to Read() bytes from and delay them + err error // error that occurred on the reader + buffer []byte // delay buffer + bufbytes int // number of bytes in the delay buffer to give to Read(); on '0' we return 'EOF' to caller + bufoff int // offset in the delay buffer to give to Read() +} + +// NewDelayedReader wraps a io.Reader and allocates a delay buffer of bufsize bytes +func NewDelayedReader(reader io.Reader, bufsize uint) io.Reader { + return &DelayedReader{ + reader: reader, + buffer: make([]byte, bufsize), + } +} + +// Read implements the io.Reader interface +func (dr *DelayedReader) Read(p []byte) (int, error) { + if dr.err != nil && dr.err != io.EOF { + return 0, dr.err + } + + // if we are completely drained, return io.EOF + if dr.err == io.EOF && dr.bufbytes == 0 { + return 0, io.EOF + } + + // only at the beginning we fill our delay buffer in an extra step + if dr.bufbytes < len(dr.buffer) && dr.err == nil { + dr.bufbytes, dr.err = FillBuffer(dr.reader, dr.buffer) + if dr.err != nil && dr.err != io.EOF { + return 0, dr.err + } + } + // dr.err != nil means we have EOF and can drain the delay buffer + // otherwise we need to still read from the reader + + var tmpbuf []byte + tmpbufbytes := 0 + if dr.err == nil { + tmpbuf = make([]byte, len(p)) + tmpbufbytes, dr.err = FillBuffer(dr.reader, tmpbuf) + if dr.err != nil && dr.err != io.EOF { + return 0, dr.err + } + } + + // copy out of the delay buffer into 'p' + tocopy1 := min(len(p), dr.bufbytes) + c1 := copy(p[:tocopy1], dr.buffer[dr.bufoff:]) + dr.bufoff += c1 + dr.bufbytes -= c1 + + c2 := 0 + // can p still hold more data? + if c1 < len(p) { + // copy out of the tmpbuf into 'p' + c2 = copy(p[tocopy1:], tmpbuf[:tmpbufbytes]) + } + + // if tmpbuf holds data we need to hold onto, copy them + // into the delay buffer + if tmpbufbytes-c2 > 0 { + // left-shift the delay buffer and append the tmpbuf's remaining data + dr.buffer = dr.buffer[dr.bufoff : dr.bufoff+dr.bufbytes] + dr.buffer = append(dr.buffer, tmpbuf[c2:tmpbufbytes]...) + dr.bufoff = 0 + dr.bufbytes = len(dr.buffer) + } + + var err error + if dr.bufbytes == 0 { + err = io.EOF + } + return c1 + c2, err +} diff --git a/vendor/github.com/containers/ocicrypt/utils/ioutils.go b/vendor/github.com/containers/ocicrypt/utils/ioutils.go new file mode 100644 index 00000000..c6265168 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/utils/ioutils.go @@ -0,0 +1,58 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package utils + +import ( + "bytes" + "fmt" + "io" + "os/exec" +) + +// FillBuffer fills the given buffer with as many bytes from the reader as possible. It returns +// EOF if an EOF was encountered or any other error. +func FillBuffer(reader io.Reader, buffer []byte) (int, error) { + n, err := io.ReadFull(reader, buffer) + if err == io.ErrUnexpectedEOF { + return n, io.EOF + } + return n, err +} + +// first argument is the command, like cat or echo, +// the second is the list of args to pass to it +type CommandExecuter interface { + Exec(string, []string, []byte) ([]byte, error) +} + +type Runner struct{} + +// ExecuteCommand is used to execute a linux command line command and return the output of the command with an error if it exists. +func (r Runner) Exec(cmdName string, args []string, input []byte) ([]byte, error) { + var out bytes.Buffer + var stderr bytes.Buffer + stdInputBuffer := bytes.NewBuffer(input) + cmd := exec.Command(cmdName, args...) + cmd.Stdin = stdInputBuffer + cmd.Stdout = &out + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + return nil, fmt.Errorf("Error while running command: %s. stderr: %s: %w", cmdName, stderr.String(), err) + } + return out.Bytes(), nil +} diff --git a/vendor/github.com/containers/ocicrypt/utils/keyprovider/keyprovider.pb.go b/vendor/github.com/containers/ocicrypt/utils/keyprovider/keyprovider.pb.go new file mode 100644 index 00000000..dc477d3c --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/utils/keyprovider/keyprovider.pb.go @@ -0,0 +1,243 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: keyprovider.proto + +package keyprovider + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type KeyProviderKeyWrapProtocolInput struct { + KeyProviderKeyWrapProtocolInput []byte `protobuf:"bytes,1,opt,name=KeyProviderKeyWrapProtocolInput,proto3" json:"KeyProviderKeyWrapProtocolInput,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *KeyProviderKeyWrapProtocolInput) Reset() { *m = KeyProviderKeyWrapProtocolInput{} } +func (m *KeyProviderKeyWrapProtocolInput) String() string { return proto.CompactTextString(m) } +func (*KeyProviderKeyWrapProtocolInput) ProtoMessage() {} +func (*KeyProviderKeyWrapProtocolInput) Descriptor() ([]byte, []int) { + return fileDescriptor_da74c8e785ad390c, []int{0} +} + +func (m *KeyProviderKeyWrapProtocolInput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_KeyProviderKeyWrapProtocolInput.Unmarshal(m, b) +} +func (m *KeyProviderKeyWrapProtocolInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_KeyProviderKeyWrapProtocolInput.Marshal(b, m, deterministic) +} +func (m *KeyProviderKeyWrapProtocolInput) XXX_Merge(src proto.Message) { + xxx_messageInfo_KeyProviderKeyWrapProtocolInput.Merge(m, src) +} +func (m *KeyProviderKeyWrapProtocolInput) XXX_Size() int { + return xxx_messageInfo_KeyProviderKeyWrapProtocolInput.Size(m) +} +func (m *KeyProviderKeyWrapProtocolInput) XXX_DiscardUnknown() { + xxx_messageInfo_KeyProviderKeyWrapProtocolInput.DiscardUnknown(m) +} + +var xxx_messageInfo_KeyProviderKeyWrapProtocolInput proto.InternalMessageInfo + +func (m *KeyProviderKeyWrapProtocolInput) GetKeyProviderKeyWrapProtocolInput() []byte { + if m != nil { + return m.KeyProviderKeyWrapProtocolInput + } + return nil +} + +type KeyProviderKeyWrapProtocolOutput struct { + KeyProviderKeyWrapProtocolOutput []byte `protobuf:"bytes,1,opt,name=KeyProviderKeyWrapProtocolOutput,proto3" json:"KeyProviderKeyWrapProtocolOutput,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *KeyProviderKeyWrapProtocolOutput) Reset() { *m = KeyProviderKeyWrapProtocolOutput{} } +func (m *KeyProviderKeyWrapProtocolOutput) String() string { return proto.CompactTextString(m) } +func (*KeyProviderKeyWrapProtocolOutput) ProtoMessage() {} +func (*KeyProviderKeyWrapProtocolOutput) Descriptor() ([]byte, []int) { + return fileDescriptor_da74c8e785ad390c, []int{1} +} + +func (m *KeyProviderKeyWrapProtocolOutput) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_KeyProviderKeyWrapProtocolOutput.Unmarshal(m, b) +} +func (m *KeyProviderKeyWrapProtocolOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_KeyProviderKeyWrapProtocolOutput.Marshal(b, m, deterministic) +} +func (m *KeyProviderKeyWrapProtocolOutput) XXX_Merge(src proto.Message) { + xxx_messageInfo_KeyProviderKeyWrapProtocolOutput.Merge(m, src) +} +func (m *KeyProviderKeyWrapProtocolOutput) XXX_Size() int { + return xxx_messageInfo_KeyProviderKeyWrapProtocolOutput.Size(m) +} +func (m *KeyProviderKeyWrapProtocolOutput) XXX_DiscardUnknown() { + xxx_messageInfo_KeyProviderKeyWrapProtocolOutput.DiscardUnknown(m) +} + +var xxx_messageInfo_KeyProviderKeyWrapProtocolOutput proto.InternalMessageInfo + +func (m *KeyProviderKeyWrapProtocolOutput) GetKeyProviderKeyWrapProtocolOutput() []byte { + if m != nil { + return m.KeyProviderKeyWrapProtocolOutput + } + return nil +} + +func init() { + proto.RegisterType((*KeyProviderKeyWrapProtocolInput)(nil), "keyprovider.keyProviderKeyWrapProtocolInput") + proto.RegisterType((*KeyProviderKeyWrapProtocolOutput)(nil), "keyprovider.keyProviderKeyWrapProtocolOutput") +} + +func init() { + proto.RegisterFile("keyprovider.proto", fileDescriptor_da74c8e785ad390c) +} + +var fileDescriptor_da74c8e785ad390c = []byte{ + // 169 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcc, 0x4e, 0xad, 0x2c, + 0x28, 0xca, 0x2f, 0xcb, 0x4c, 0x49, 0x2d, 0xd2, 0x03, 0x32, 0x4a, 0xf2, 0x85, 0xb8, 0x91, 0x84, + 0x94, 0xb2, 0xb9, 0xe4, 0x81, 0xdc, 0x00, 0x28, 0xd7, 0x3b, 0xb5, 0x32, 0xbc, 0x28, 0xb1, 0x20, + 0x00, 0xa4, 0x2e, 0x39, 0x3f, 0xc7, 0x33, 0xaf, 0xa0, 0xb4, 0x44, 0xc8, 0x83, 0x4b, 0xde, 0x1b, + 0xbf, 0x12, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x9e, 0x20, 0x42, 0xca, 0x94, 0xf2, 0xb8, 0x14, 0x70, + 0x5b, 0xe6, 0x5f, 0x5a, 0x02, 0xb2, 0xcd, 0x8b, 0x4b, 0xc1, 0x9b, 0x80, 0x1a, 0xa8, 0x75, 0x04, + 0xd5, 0x19, 0xbd, 0x62, 0xe4, 0x12, 0x42, 0x52, 0x14, 0x9c, 0x5a, 0x54, 0x96, 0x99, 0x9c, 0x2a, + 0x94, 0xc1, 0xc5, 0x0e, 0x52, 0x0c, 0x94, 0x11, 0xd2, 0xd1, 0x43, 0x0e, 0x1f, 0x02, 0x21, 0x21, + 0xa5, 0x4b, 0xa4, 0x6a, 0x88, 0xf5, 0x4a, 0x0c, 0x42, 0x59, 0x5c, 0x9c, 0xa1, 0x79, 0xf4, 0xb1, + 0xcb, 0x89, 0x37, 0x0a, 0x39, 0x62, 0x93, 0xd8, 0xc0, 0x91, 0x6d, 0x0c, 0x08, 0x00, 0x00, 0xff, + 0xff, 0x9a, 0x10, 0xcb, 0xf9, 0x01, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// KeyProviderServiceClient is the client API for KeyProviderService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type KeyProviderServiceClient interface { + WrapKey(ctx context.Context, in *KeyProviderKeyWrapProtocolInput, opts ...grpc.CallOption) (*KeyProviderKeyWrapProtocolOutput, error) + UnWrapKey(ctx context.Context, in *KeyProviderKeyWrapProtocolInput, opts ...grpc.CallOption) (*KeyProviderKeyWrapProtocolOutput, error) +} + +type keyProviderServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewKeyProviderServiceClient(cc grpc.ClientConnInterface) KeyProviderServiceClient { + return &keyProviderServiceClient{cc} +} + +func (c *keyProviderServiceClient) WrapKey(ctx context.Context, in *KeyProviderKeyWrapProtocolInput, opts ...grpc.CallOption) (*KeyProviderKeyWrapProtocolOutput, error) { + out := new(KeyProviderKeyWrapProtocolOutput) + err := c.cc.Invoke(ctx, "/keyprovider.KeyProviderService/WrapKey", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *keyProviderServiceClient) UnWrapKey(ctx context.Context, in *KeyProviderKeyWrapProtocolInput, opts ...grpc.CallOption) (*KeyProviderKeyWrapProtocolOutput, error) { + out := new(KeyProviderKeyWrapProtocolOutput) + err := c.cc.Invoke(ctx, "/keyprovider.KeyProviderService/UnWrapKey", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// KeyProviderServiceServer is the server API for KeyProviderService service. +type KeyProviderServiceServer interface { + WrapKey(context.Context, *KeyProviderKeyWrapProtocolInput) (*KeyProviderKeyWrapProtocolOutput, error) + UnWrapKey(context.Context, *KeyProviderKeyWrapProtocolInput) (*KeyProviderKeyWrapProtocolOutput, error) +} + +// UnimplementedKeyProviderServiceServer can be embedded to have forward compatible implementations. +type UnimplementedKeyProviderServiceServer struct { +} + +func (*UnimplementedKeyProviderServiceServer) WrapKey(ctx context.Context, req *KeyProviderKeyWrapProtocolInput) (*KeyProviderKeyWrapProtocolOutput, error) { + return nil, status.Errorf(codes.Unimplemented, "method WrapKey not implemented") +} +func (*UnimplementedKeyProviderServiceServer) UnWrapKey(ctx context.Context, req *KeyProviderKeyWrapProtocolInput) (*KeyProviderKeyWrapProtocolOutput, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnWrapKey not implemented") +} + +func RegisterKeyProviderServiceServer(s *grpc.Server, srv KeyProviderServiceServer) { + s.RegisterService(&_KeyProviderService_serviceDesc, srv) +} + +func _KeyProviderService_WrapKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(KeyProviderKeyWrapProtocolInput) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeyProviderServiceServer).WrapKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/keyprovider.KeyProviderService/WrapKey", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeyProviderServiceServer).WrapKey(ctx, req.(*KeyProviderKeyWrapProtocolInput)) + } + return interceptor(ctx, in, info, handler) +} + +func _KeyProviderService_UnWrapKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(KeyProviderKeyWrapProtocolInput) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeyProviderServiceServer).UnWrapKey(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/keyprovider.KeyProviderService/UnWrapKey", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeyProviderServiceServer).UnWrapKey(ctx, req.(*KeyProviderKeyWrapProtocolInput)) + } + return interceptor(ctx, in, info, handler) +} + +var _KeyProviderService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "keyprovider.KeyProviderService", + HandlerType: (*KeyProviderServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "WrapKey", + Handler: _KeyProviderService_WrapKey_Handler, + }, + { + MethodName: "UnWrapKey", + Handler: _KeyProviderService_UnWrapKey_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "keyprovider.proto", +} diff --git a/vendor/github.com/containers/ocicrypt/utils/keyprovider/keyprovider.proto b/vendor/github.com/containers/ocicrypt/utils/keyprovider/keyprovider.proto new file mode 100644 index 00000000..a71f0a59 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/utils/keyprovider/keyprovider.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package keyprovider; +option go_package = "keyprovider"; + +message keyProviderKeyWrapProtocolInput { + bytes KeyProviderKeyWrapProtocolInput = 1; +} + +message keyProviderKeyWrapProtocolOutput { + bytes KeyProviderKeyWrapProtocolOutput = 1; +} + +service KeyProviderService { + rpc WrapKey(keyProviderKeyWrapProtocolInput) returns (keyProviderKeyWrapProtocolOutput) {}; + rpc UnWrapKey(keyProviderKeyWrapProtocolInput) returns (keyProviderKeyWrapProtocolOutput) {}; +} \ No newline at end of file diff --git a/vendor/github.com/containers/ocicrypt/utils/testing.go b/vendor/github.com/containers/ocicrypt/utils/testing.go new file mode 100644 index 00000000..050aa885 --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/utils/testing.go @@ -0,0 +1,174 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package utils + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "time" +) + +// CreateRSAKey creates an RSA key +func CreateRSAKey(bits int) (*rsa.PrivateKey, error) { + key, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, fmt.Errorf("rsa.GenerateKey failed: %w", err) + } + return key, nil +} + +// CreateECDSAKey creates an elliptic curve key for the given curve +func CreateECDSAKey(curve elliptic.Curve) (*ecdsa.PrivateKey, error) { + key, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + return nil, fmt.Errorf("ecdsa.GenerateKey failed: %w", err) + } + return key, nil +} + +// CreateRSATestKey creates an RSA key of the given size and returns +// the public and private key in PEM or DER format +func CreateRSATestKey(bits int, password []byte, pemencode bool) ([]byte, []byte, error) { + key, err := CreateRSAKey(bits) + if err != nil { + return nil, nil, err + } + + pubData, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + if err != nil { + return nil, nil, fmt.Errorf("x509.MarshalPKIXPublicKey failed: %w", err) + } + privData := x509.MarshalPKCS1PrivateKey(key) + + // no more encoding needed for DER + if !pemencode { + return pubData, privData, nil + } + + publicKey := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubData, + }) + + var block *pem.Block + + typ := "RSA PRIVATE KEY" + if len(password) > 0 { + block, err = x509.EncryptPEMBlock(rand.Reader, typ, privData, password, x509.PEMCipherAES256) //nolint:staticcheck // ignore SA1019, which is kept for backward compatibility + if err != nil { + return nil, nil, fmt.Errorf("x509.EncryptPEMBlock failed: %w", err) + } + } else { + block = &pem.Block{ + Type: typ, + Bytes: privData, + } + } + + privateKey := pem.EncodeToMemory(block) + + return publicKey, privateKey, nil +} + +// CreateECDSATestKey creates and elliptic curve key for the given curve and returns +// the public and private key in DER format +func CreateECDSATestKey(curve elliptic.Curve) ([]byte, []byte, error) { + key, err := CreateECDSAKey(curve) + if err != nil { + return nil, nil, err + } + + pubData, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + if err != nil { + return nil, nil, fmt.Errorf("x509.MarshalPKIXPublicKey failed: %w", err) + } + + privData, err := x509.MarshalECPrivateKey(key) + if err != nil { + return nil, nil, fmt.Errorf("x509.MarshalECPrivateKey failed: %w", err) + } + + return pubData, privData, nil +} + +// CreateTestCA creates a root CA for testing +func CreateTestCA() (*rsa.PrivateKey, *x509.Certificate, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, fmt.Errorf("rsa.GenerateKey failed: %w", err) + } + + ca := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "test-ca", + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), + IsCA: true, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + caCert, err := certifyKey(&key.PublicKey, ca, key, ca) + + return key, caCert, err +} + +// CertifyKey certifies a public key using the given CA's private key and cert; +// The certificate template for the public key is optional +func CertifyKey(pubbytes []byte, template *x509.Certificate, caKey *rsa.PrivateKey, caCert *x509.Certificate) (*x509.Certificate, error) { + pubKey, err := ParsePublicKey(pubbytes, "CertifyKey") + if err != nil { + return nil, err + } + return certifyKey(pubKey, template, caKey, caCert) +} + +func certifyKey(pub interface{}, template *x509.Certificate, caKey *rsa.PrivateKey, caCert *x509.Certificate) (*x509.Certificate, error) { + if template == nil { + template = &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "testkey", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + IsCA: false, + KeyUsage: x509.KeyUsageDigitalSignature, + BasicConstraintsValid: true, + } + } + + certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, pub, caKey) + if err != nil { + return nil, fmt.Errorf("x509.CreateCertificate failed: %w", err) + } + + cert, err := x509.ParseCertificate(certDER) + if err != nil { + return nil, fmt.Errorf("x509.ParseCertificate failed: %w", err) + } + + return cert, nil +} diff --git a/vendor/github.com/containers/ocicrypt/utils/utils.go b/vendor/github.com/containers/ocicrypt/utils/utils.go new file mode 100644 index 00000000..f653f2ef --- /dev/null +++ b/vendor/github.com/containers/ocicrypt/utils/utils.go @@ -0,0 +1,249 @@ +/* + Copyright The ocicrypt Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package utils + +import ( + "bytes" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "strings" + + "github.com/containers/ocicrypt/crypto/pkcs11" + "github.com/go-jose/go-jose/v4" + "golang.org/x/crypto/openpgp" +) + +// parseJWKPrivateKey parses the input byte array as a JWK and makes sure it's a private key +func parseJWKPrivateKey(privKey []byte, prefix string) (interface{}, error) { + jwk := jose.JSONWebKey{} + err := jwk.UnmarshalJSON(privKey) + if err != nil { + return nil, fmt.Errorf("%s: Could not parse input as JWK: %w", prefix, err) + } + if jwk.IsPublic() { + return nil, fmt.Errorf("%s: JWK is not a private key", prefix) + } + return &jwk, nil +} + +// parseJWKPublicKey parses the input byte array as a JWK +func parseJWKPublicKey(privKey []byte, prefix string) (interface{}, error) { + jwk := jose.JSONWebKey{} + err := jwk.UnmarshalJSON(privKey) + if err != nil { + return nil, fmt.Errorf("%s: Could not parse input as JWK: %w", prefix, err) + } + if !jwk.IsPublic() { + return nil, fmt.Errorf("%s: JWK is not a public key", prefix) + } + return &jwk, nil +} + +// parsePkcs11PrivateKeyYaml parses the input byte array as pkcs11 key file yaml format) +func parsePkcs11PrivateKeyYaml(yaml []byte, prefix string) (*pkcs11.Pkcs11KeyFileObject, error) { + // if the URI does not have enough attributes, we will throw an error when decrypting + return pkcs11.ParsePkcs11KeyFile(yaml) +} + +// parsePkcs11URIPublicKey parses the input byte array as a pkcs11 key file yaml +func parsePkcs11PublicKeyYaml(yaml []byte, prefix string) (*pkcs11.Pkcs11KeyFileObject, error) { + // if the URI does not have enough attributes, we will throw an error when decrypting + return pkcs11.ParsePkcs11KeyFile(yaml) +} + +// IsPasswordError checks whether an error is related to a missing or wrong +// password +func IsPasswordError(err error) bool { + if err == nil { + return false + } + msg := strings.ToLower(err.Error()) + + return strings.Contains(msg, "password") && + (strings.Contains(msg, "missing") || strings.Contains(msg, "wrong")) +} + +// ParsePrivateKey tries to parse a private key in DER format first and +// PEM format after, returning an error if the parsing failed +func ParsePrivateKey(privKey, privKeyPassword []byte, prefix string) (interface{}, error) { + key, err := x509.ParsePKCS8PrivateKey(privKey) + if err != nil { + key, err = x509.ParsePKCS1PrivateKey(privKey) + if err != nil { + key, err = x509.ParseECPrivateKey(privKey) + } + } + if err != nil { + block, _ := pem.Decode(privKey) + if block != nil { + var der []byte + if x509.IsEncryptedPEMBlock(block) { //nolint:staticcheck // ignore SA1019, which is kept for backward compatibility + if privKeyPassword == nil { + return nil, fmt.Errorf("%s: Missing password for encrypted private key", prefix) + } + der, err = x509.DecryptPEMBlock(block, privKeyPassword) //nolint:staticcheck // ignore SA1019, which is kept for backward compatibility + if err != nil { + return nil, fmt.Errorf("%s: Wrong password: could not decrypt private key", prefix) + } + } else { + der = block.Bytes + } + + key, err = x509.ParsePKCS8PrivateKey(der) + if err != nil { + key, err = x509.ParsePKCS1PrivateKey(der) + if err != nil { + return nil, fmt.Errorf("%s: Could not parse private key: %w", prefix, err) + } + } + } else { + key, err = parseJWKPrivateKey(privKey, prefix) + if err != nil { + key, err = parsePkcs11PrivateKeyYaml(privKey, prefix) + } + } + } + return key, err +} + +// IsPrivateKey returns true in case the given byte array represents a private key +// It returns an error if for example the password is wrong +func IsPrivateKey(data []byte, password []byte) (bool, error) { + _, err := ParsePrivateKey(data, password, "") + return err == nil, err +} + +// IsPkcs11PrivateKey returns true in case the given byte array represents a pkcs11 private key +func IsPkcs11PrivateKey(data []byte) bool { + return pkcs11.IsPkcs11PrivateKey(data) +} + +// ParsePublicKey tries to parse a public key in DER format first and +// PEM format after, returning an error if the parsing failed +func ParsePublicKey(pubKey []byte, prefix string) (interface{}, error) { + key, err := x509.ParsePKIXPublicKey(pubKey) + if err != nil { + block, _ := pem.Decode(pubKey) + if block != nil { + key, err = x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("%s: Could not parse public key: %w", prefix, err) + } + } else { + key, err = parseJWKPublicKey(pubKey, prefix) + if err != nil { + key, err = parsePkcs11PublicKeyYaml(pubKey, prefix) + } + } + } + return key, err +} + +// IsPublicKey returns true in case the given byte array represents a public key +func IsPublicKey(data []byte) bool { + _, err := ParsePublicKey(data, "") + return err == nil +} + +// IsPkcs11PublicKey returns true in case the given byte array represents a pkcs11 public key +func IsPkcs11PublicKey(data []byte) bool { + return pkcs11.IsPkcs11PublicKey(data) +} + +// ParseCertificate tries to parse a public key in DER format first and +// PEM format after, returning an error if the parsing failed +func ParseCertificate(certBytes []byte, prefix string) (*x509.Certificate, error) { + x509Cert, err := x509.ParseCertificate(certBytes) + if err != nil { + block, _ := pem.Decode(certBytes) + if block == nil { + return nil, fmt.Errorf("%s: Could not PEM decode x509 certificate", prefix) + } + x509Cert, err = x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("%s: Could not parse x509 certificate: %w", prefix, err) + } + } + return x509Cert, err +} + +// IsCertificate returns true in case the given byte array represents an x.509 certificate +func IsCertificate(data []byte) bool { + _, err := ParseCertificate(data, "") + return err == nil +} + +// IsGPGPrivateKeyRing returns true in case the given byte array represents a GPG private key ring file +func IsGPGPrivateKeyRing(data []byte) bool { + r := bytes.NewBuffer(data) + _, err := openpgp.ReadKeyRing(r) + return err == nil +} + +// SortDecryptionKeys parses a list of comma separated base64 entries and sorts the data into +// a map. Each entry in the list may be either a GPG private key ring, private key, or x.509 +// certificate +func SortDecryptionKeys(b64ItemList string) (map[string][][]byte, error) { + dcparameters := make(map[string][][]byte) + + for _, b64Item := range strings.Split(b64ItemList, ",") { + var password []byte + b64Data := strings.Split(b64Item, ":") + keyData, err := base64.StdEncoding.DecodeString(b64Data[0]) + if err != nil { + return nil, errors.New("Could not base64 decode a passed decryption key") + } + if len(b64Data) == 2 { + password, err = base64.StdEncoding.DecodeString(b64Data[1]) + if err != nil { + return nil, errors.New("Could not base64 decode a passed decryption key password") + } + } + var key string + isPrivKey, err := IsPrivateKey(keyData, password) + if IsPasswordError(err) { + return nil, err + } + if isPrivKey { + key = "privkeys" + if _, ok := dcparameters["privkeys-passwords"]; !ok { + dcparameters["privkeys-passwords"] = [][]byte{password} + } else { + dcparameters["privkeys-passwords"] = append(dcparameters["privkeys-passwords"], password) + } + } else if IsCertificate(keyData) { + key = "x509s" + } else if IsGPGPrivateKeyRing(keyData) { + key = "gpg-privatekeys" + } + if key != "" { + values := dcparameters[key] + if values == nil { + dcparameters[key] = [][]byte{keyData} + } else { + dcparameters[key] = append(dcparameters[key], keyData) + } + } else { + return nil, errors.New("Unknown decryption key type") + } + } + + return dcparameters, nil +} diff --git a/vendor/github.com/containers/podman/v5/LICENSE b/vendor/github.com/containers/podman/v5/LICENSE new file mode 100644 index 00000000..9b259bdf --- /dev/null +++ b/vendor/github.com/containers/podman/v5/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/podman/v5/cmd/podman/parse/filters.go b/vendor/github.com/containers/podman/v5/cmd/podman/parse/filters.go new file mode 100644 index 00000000..af9157cd --- /dev/null +++ b/vendor/github.com/containers/podman/v5/cmd/podman/parse/filters.go @@ -0,0 +1,19 @@ +package parse + +import ( + "fmt" + "net/url" + "strings" +) + +func FilterArgumentsIntoFilters(filters []string) (url.Values, error) { + parsedFilters := make(url.Values) + for _, f := range filters { + fname, filter, hasFilter := strings.Cut(f, "=") + if !hasFilter { + return parsedFilters, fmt.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + parsedFilters.Add(fname, filter) + } + return parsedFilters, nil +} diff --git a/vendor/github.com/containers/podman/v5/cmd/podman/parse/net.go b/vendor/github.com/containers/podman/v5/cmd/podman/parse/net.go new file mode 100644 index 00000000..3f6806e1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/cmd/podman/parse/net.go @@ -0,0 +1,176 @@ +// most of these validate and parse functions have been taken from projectatomic/docker +// and modified for cri-o +package parse + +import ( + "bufio" + "fmt" + "net" + "net/url" + "os" + "strings" + + "go.podman.io/common/libnetwork/etchosts" + "go.podman.io/storage/pkg/regexp" +) + +const ( + LabelType string = "label" + ENVType string = "env" +) + +// Note: for flags that are in the form , use the RAMInBytes function +// from the units package in docker/go-units/size.go + +var ( + whiteSpaces = " \t" + alphaRegexp = regexp.Delayed(`[a-zA-Z]`) + domainRegexp = regexp.Delayed(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) +) + +// ValidateExtraHost validates that the specified string is a valid extrahost and returns it. +// ExtraHost is in the form of name1;name2;name3:ip where the ip has to be a valid ip (ipv4 or ipv6) or the special string HostGateway. +// for add-host flag +func ValidateExtraHost(val string) (string, error) { + // allow for IPv6 addresses in extra hosts by only splitting on first ":" + names, ip, hasIP := strings.Cut(val, ":") + if !hasIP || len(names) == 0 { + return "", fmt.Errorf("bad format for add-host: %q", val) + } + + // Split the hostnames by semicolon and validate each one + for name := range strings.SplitSeq(names, ";") { + if len(name) == 0 { + return "", fmt.Errorf("hostname in add-host %q is empty", val) + } + } + + if ip == etchosts.HostGateway { + return val, nil + } + if _, err := validateIPAddress(ip); err != nil { + return "", fmt.Errorf("invalid IP address in add-host: %q", ip) + } + return val, nil +} + +// validateIPAddress validates an Ip address. +// for dns, ip, and ip6 flags also +func validateIPAddress(val string) (string, error) { + var ip = net.ParseIP(strings.TrimSpace(val)) + if ip != nil { + return ip.String(), nil + } + return "", fmt.Errorf("%s is not an ip address", val) +} + +func ValidateDomain(val string) (string, error) { + if alphaRegexp.FindString(val) == "" { + return "", fmt.Errorf("%s is not a valid domain", val) + } + ns := domainRegexp.FindSubmatch([]byte(val)) + if len(ns) > 0 && len(ns[1]) < 255 { + return string(ns[1]), nil + } + return "", fmt.Errorf("%s is not a valid domain", val) +} + +// GetAllLabels retrieves all labels given a potential label file and a number +// of labels provided from the command line. +func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { + labels := make(map[string]string) + for _, file := range labelFile { + // Use of parseEnvFile still seems safe, as it's missing the + // extra parsing logic of parseEnv. + // There's an argument that we SHOULD be doing that parsing for + // all environment variables, even those sourced from files, but + // that would require a substantial rework. + if err := parseEnvOrLabelFile(labels, file, LabelType); err != nil { + return nil, err + } + } + for _, label := range inputLabels { + key, value, _ := strings.Cut(label, "=") + if key == "" { + return nil, fmt.Errorf("invalid label format: %q", label) + } + labels[key] = value + } + return labels, nil +} + +func parseEnvOrLabel(env map[string]string, line, configType string) error { + key, val, hasVal := strings.Cut(line, "=") + + // catch invalid variables such as "=" or "=A" + if key == "" { + return fmt.Errorf("invalid environment variable: %q", line) + } + + // trim the front of a variable, but nothing else + name := strings.TrimLeft(key, whiteSpaces) + if strings.ContainsAny(name, whiteSpaces) { + return fmt.Errorf("name %q has white spaces, poorly formatted name", name) + } + + if hasVal { + env[name] = val + } else { + if name, hasStar := strings.CutSuffix(name, "*"); hasStar { + for _, e := range os.Environ() { + envKey, envVal, hasEq := strings.Cut(e, "=") + if hasEq && strings.HasPrefix(envKey, name) { + env[envKey] = envVal + } + } + } else if configType == ENVType { + // if only a pass-through variable is given, clean it up. + if val, ok := os.LookupEnv(name); ok { + env[name] = val + } + } + } + return nil +} + +// parseEnvOrLabelFile reads a file with environment variables enumerated by lines +// configType should be set to either "label" or "env" based on what type is being parsed +func parseEnvOrLabelFile(envOrLabel map[string]string, filename, configType string) error { + fh, err := os.Open(filename) + if err != nil { + return err + } + defer fh.Close() + + scanner := bufio.NewScanner(fh) + for scanner.Scan() { + // trim the line from all leading whitespace first + line := strings.TrimLeft(scanner.Text(), whiteSpaces) + // line is not empty, and not starting with '#' + if len(line) > 0 && !strings.HasPrefix(line, "#") { + if err := parseEnvOrLabel(envOrLabel, line, configType); err != nil { + return err + } + } + } + return scanner.Err() +} + +// ValidWebURL checks a string urlStr is a url or not +func ValidWebURL(urlStr string) error { + parsedURL, err := url.ParseRequestURI(urlStr) + if err != nil { + return fmt.Errorf("invalid URL %q: %w", urlStr, err) + } + + // to be a valid web url, scheme must be either http or https + if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { + return fmt.Errorf("invalid URL %q: unsupported scheme %q", urlStr, parsedURL.Scheme) + } + + // ensure url contain a host + if parsedURL.Host == "" { + return fmt.Errorf("invalid URL %q: missing host", urlStr) + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/cmd/podman/parse/parse.go b/vendor/github.com/containers/podman/v5/cmd/podman/parse/parse.go new file mode 100644 index 00000000..b018dad2 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/cmd/podman/parse/parse.go @@ -0,0 +1,17 @@ +//go:build !windows + +package parse + +import ( + "fmt" + "strings" +) + +// ValidateFileName returns an error if filename contains ":" +// as it is currently not supported +func ValidateFileName(filename string) error { + if strings.Contains(filename, ":") { + return fmt.Errorf("invalid filename (should not contain ':') %q", filename) + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/cmd/podman/parse/parse_windows.go b/vendor/github.com/containers/podman/v5/cmd/podman/parse/parse_windows.go new file mode 100644 index 00000000..12c8c420 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/cmd/podman/parse/parse_windows.go @@ -0,0 +1,5 @@ +package parse + +func ValidateFileName(_ string) error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/internal/remote_build_helpers/utils.go b/vendor/github.com/containers/podman/v5/internal/remote_build_helpers/utils.go new file mode 100644 index 00000000..f0c12273 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/internal/remote_build_helpers/utils.go @@ -0,0 +1,82 @@ +package remote_build_helpers + +import ( + "errors" + "fmt" + "io" + "os" + + "github.com/sirupsen/logrus" +) + +// TempFileManager manages temporary files created during image build. +// It maintains a list of created temporary files and provides cleanup functionality +// to ensure proper resource management. +type TempFileManager struct { + files []string +} + +func NewTempFileManager() *TempFileManager { + return &TempFileManager{} +} + +func (t *TempFileManager) AddFile(filename string) { + t.files = append(t.files, filename) +} + +func (t *TempFileManager) Cleanup() { + for _, file := range t.files { + if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) { + logrus.Errorf("Failed to remove temp file %s: %v", file, err) + } + } + t.files = t.files[:0] // Reset slice +} + +// CreateTempFileFromReader creates a temporary file in the specified destination directory +// with the given pattern, and copies content from the provided reader into the file. +// The created temporary file is automatically added to the manager's cleanup list. +// +// Parameters: +// - dest: The directory where the temporary file should be created +// - pattern: The pattern for naming the temporary file +// - reader: The io.Reader from which to read content to write into the temporary file +// +// Returns: +// - string: The path to the created temporary file +// - error: Any error encountered during the operation +func (t *TempFileManager) CreateTempFileFromReader(dest string, pattern string, reader io.Reader) (string, error) { + tmpFile, err := os.CreateTemp(dest, pattern) + if err != nil { + return "", fmt.Errorf("creating temp file: %w", err) + } + defer tmpFile.Close() + + t.AddFile(tmpFile.Name()) + + if _, err := io.Copy(tmpFile, reader); err != nil { + return "", fmt.Errorf("copying stdin content: %w", err) + } + return tmpFile.Name(), nil +} + +// CreateTempSecret creates a temporary copy of a secret file in the specified +// context directory. The original secret file is copied to a new temporary file +// which is automatically added to the manager's cleanup list. +// +// Parameters: +// - secretPath: The path to the source secret file to copy +// - contextDir: The directory where the temporary secret file should be created +// +// Returns: +// - string: The path to the created temporary secret file +// - error: Any error encountered during the operation +func (t *TempFileManager) CreateTempSecret(secretPath, contextDir string) (string, error) { + secretFile, err := os.Open(secretPath) + if err != nil { + return "", fmt.Errorf("opening secret file %s: %w", secretPath, err) + } + defer secretFile.Close() + + return t.CreateTempFileFromReader(contextDir, "podman-build-secret-*", secretFile) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/boltdb_state.go b/vendor/github.com/containers/podman/v5/libpod/boltdb_state.go new file mode 100644 index 00000000..db2f7084 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/boltdb_state.go @@ -0,0 +1,3550 @@ +//go:build !remote + +package libpod + +import ( + "bytes" + "errors" + "fmt" + "io/fs" + "net" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/sirupsen/logrus" + bolt "go.etcd.io/bbolt" + "go.podman.io/common/libnetwork/types" + "go.podman.io/storage/pkg/fileutils" +) + +// BoltState is a state implementation backed by a Bolt DB +type BoltState struct { + valid bool + dbPath string + dbLock sync.Mutex + runtime *Runtime +} + +// A brief description of the format of the BoltDB state: +// At the top level, the following buckets are created: +// - idRegistryBkt: Maps ID to Name for containers and pods. +// Used to ensure container and pod IDs are globally unique. +// - nameRegistryBkt: Maps Name to ID for containers and pods. +// Used to ensure container and pod names are globally unique. +// - ctrBkt: Contains a sub-bucket for each container in the state. +// Each sub-bucket has config and state keys holding the container's JSON +// encoded configuration and state (respectively), an optional netNS key +// containing the path to the container's network namespace, a dependencies +// bucket containing the container's dependencies, and an optional pod key +// containing the ID of the pod the container is joined to. +// After updates to include exec sessions, may also include an exec bucket +// with the IDs of exec sessions currently in use by the container. +// - allCtrsBkt: Map of ID to name containing only containers. Used for +// container lookup operations. +// - podBkt: Contains a sub-bucket for each pod in the state. +// Each sub-bucket has config and state keys holding the pod's JSON encoded +// configuration and state, plus a containers sub bucket holding the IDs of +// containers in the pod. +// - allPodsBkt: Map of ID to name containing only pods. Used for pod lookup +// operations. +// - execBkt: Map of exec session ID to container ID - used for resolving +// exec session IDs to the containers that hold the exec session. +// - networksBkt: Contains all network names as key with their options json +// encoded as value. +// - aliasesBkt - Deprecated, use the networksBkt. Used to contain a bucket +// for each CNI network which contain a map of network alias (an extra name +// for containers in DNS) to the ID of the container holding the alias. +// Aliases must be unique per-network, and cannot conflict with names +// registered in nameRegistryBkt. +// - runtimeConfigBkt: Contains configuration of the libpod instance that +// initially created the database. This must match for any further instances +// that access the database, to ensure that state mismatches with +// containers/storage do not occur. +// - exitCodeBucket/exitCodeTimeStampBucket: (#14559) exit codes must be part +// of the database to resolve a previous race condition when one process waits +// for the exit file to be written and another process removes it along with +// the container during auto-removal. The same race would happen trying to +// read the exit code from the containers bucket. Hence, exit codes go into +// their own bucket. To avoid the rather expensive JSON (un)marshalling, we +// have two buckets: one for the exit codes, the other for the timestamps. + +// NewBoltState creates a new bolt-backed state database +func NewBoltState(path string, runtime *Runtime) (State, error) { + logrus.Info("Using boltdb as database backend") + state := new(BoltState) + state.dbPath = path + state.runtime = runtime + + logrus.Debugf("Initializing boltdb state at %s", path) + + ciDesiredDB := os.Getenv("CI_DESIRED_DATABASE") + + // BoltDB is deprecated and, as of Podman 5.0, we no longer allow the + // creation of new Bolt states. + // If the DB does not already exist, error out. + // To continue testing in CI, allow creation iff an undocumented env + // var is set. + if ciDesiredDB != "boltdb" { + if err := fileutils.Exists(path); err != nil && errors.Is(err, fs.ErrNotExist) { + return nil, fmt.Errorf("the BoltDB backend has been deprecated, no new BoltDB databases can be created: %w", define.ErrInvalidArg) + } + } else { + logrus.Debugf("Allowing deprecated database backend due to CI_DESIRED_DATABASE.") + } + + // TODO: Up this to ERROR level in 5.8 + if os.Getenv("SUPPRESS_BOLTDB_WARNING") == "" && ciDesiredDB != "boltdb" { + logrus.Warnf("The deprecated BoltDB database driver is in use. This driver will be removed in the upcoming Podman 6.0 release in mid 2026. It is advised that you migrate to SQLite to avoid issues when this occurs. Set SUPPRESS_BOLTDB_WARNING environment variable to remove this message.") + } + + db, err := bolt.Open(path, 0o600, nil) + if err != nil { + return nil, fmt.Errorf("opening database %s: %w", path, err) + } + // Everywhere else, we use s.deferredCloseDBCon(db) to ensure the state's DB + // mutex is also unlocked. + // However, here, the mutex has not been locked, since we just created + // the DB connection, and it hasn't left this function yet - no risk of + // concurrent access. + // As such, just a db.Close() is fine here. + defer db.Close() + + createBuckets := [][]byte{ + idRegistryBkt, + nameRegistryBkt, + ctrBkt, + allCtrsBkt, + podBkt, + allPodsBkt, + volBkt, + allVolsBkt, + execBkt, + runtimeConfigBkt, + exitCodeBkt, + exitCodeTimeStampBkt, + volCtrsBkt, + } + + // Does the DB need an update? + needsUpdate := false + err = db.View(func(tx *bolt.Tx) error { + for _, bkt := range createBuckets { + if test := tx.Bucket(bkt); test == nil { + needsUpdate = true + break + } + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("checking DB schema: %w", err) + } + + if !needsUpdate { + state.valid = true + return state, nil + } + + // Ensure schema is properly created in DB + err = db.Update(func(tx *bolt.Tx) error { + for _, bkt := range createBuckets { + if _, err := tx.CreateBucketIfNotExists(bkt); err != nil { + return fmt.Errorf("creating bucket %s: %w", string(bkt), err) + } + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("creating buckets for DB: %w", err) + } + + state.valid = true + + return state, nil +} + +// Close closes the state and prevents further use +func (s *BoltState) Close() error { + s.valid = false + return nil +} + +// Refresh clears container and pod states after a reboot +func (s *BoltState) Refresh() error { + if !s.valid { + return define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + idBucket, err := getIDBucket(tx) + if err != nil { + return err + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + ctrsBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + podsBucket, err := getPodBucket(tx) + if err != nil { + return err + } + + allVolsBucket, err := getAllVolsBucket(tx) + if err != nil { + return err + } + + volBucket, err := getVolBucket(tx) + if err != nil { + return err + } + + execBucket, err := getExecBucket(tx) + if err != nil { + return err + } + + exitCodeBucket, err := getExitCodeBucket(tx) + if err != nil { + return err + } + + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + // Clear all exec exit codes + toRemoveExitCodes := []string{} + err = exitCodeBucket.ForEach(func(id, _ []byte) error { + toRemoveExitCodes = append(toRemoveExitCodes, string(id)) + return nil + }) + if err != nil { + return fmt.Errorf("reading exit codes bucket: %w", err) + } + for _, id := range toRemoveExitCodes { + if err := exitCodeBucket.Delete([]byte(id)); err != nil { + return fmt.Errorf("removing exit code for ID %s: %w", id, err) + } + } + + toRemoveTimeStamps := []string{} + err = timeStampBucket.ForEach(func(id, _ []byte) error { + toRemoveTimeStamps = append(toRemoveTimeStamps, string(id)) + return nil + }) + if err != nil { + return fmt.Errorf("reading timestamps bucket: %w", err) + } + for _, id := range toRemoveTimeStamps { + if err := timeStampBucket.Delete([]byte(id)); err != nil { + return fmt.Errorf("removing timestamp for ID %s: %w", id, err) + } + } + + // Iterate through all IDs. Check if they are containers. + // If they are, unmarshal their state, and then clear + // PID, mountpoint, and state for all of them + // Then save the modified state + // Also clear all network namespaces + toRemoveIDs := []string{} + err = idBucket.ForEach(func(id, _ []byte) error { + ctrBkt := ctrsBucket.Bucket(id) + if ctrBkt == nil { + // It's a pod + podBkt := podsBucket.Bucket(id) + if podBkt == nil { + // This is neither a pod nor a container + // Something is seriously wrong, but + // continue on and try to clean up the + // state and become consistent. + // Just note what needs to be removed + // for now - ForEach says you shouldn't + // remove things from the table during + // it. + logrus.Errorf("Database issue: dangling ID %s found (not a pod or container) - removing", string(id)) + toRemoveIDs = append(toRemoveIDs, string(id)) + return nil + } + + // Get the state + stateBytes := podBkt.Get(stateKey) + if stateBytes == nil { + return fmt.Errorf("pod %s missing state key: %w", string(id), define.ErrInternal) + } + + state := new(podState) + + if err := json.Unmarshal(stateBytes, state); err != nil { + return fmt.Errorf("unmarshalling state for pod %s: %w", string(id), err) + } + + // Refresh the state + resetPodState(state) + + newStateBytes, err := json.Marshal(state) + if err != nil { + return fmt.Errorf("marshalling modified state for pod %s: %w", string(id), err) + } + + if err := podBkt.Put(stateKey, newStateBytes); err != nil { + return fmt.Errorf("updating state for pod %s in DB: %w", string(id), err) + } + + // It's not a container, nothing to do + return nil + } + + // First, delete the network namespace + if err := ctrBkt.Delete(netNSKey); err != nil { + return fmt.Errorf("removing network namespace for container %s: %w", string(id), err) + } + + stateBytes := ctrBkt.Get(stateKey) + if stateBytes == nil { + // Badly formatted container bucket + return fmt.Errorf("container %s missing state in DB: %w", string(id), define.ErrInternal) + } + + state := new(ContainerState) + + if err := json.Unmarshal(stateBytes, state); err != nil { + return fmt.Errorf("unmarshalling state for container %s: %w", string(id), err) + } + + resetContainerState(state) + + newStateBytes, err := json.Marshal(state) + if err != nil { + return fmt.Errorf("marshalling modified state for container %s: %w", string(id), err) + } + + if err := ctrBkt.Put(stateKey, newStateBytes); err != nil { + return fmt.Errorf("updating state for container %s in DB: %w", string(id), err) + } + + // Delete all exec sessions, if there are any + ctrExecBkt := ctrBkt.Bucket(execBkt) + if ctrExecBkt != nil { + // Can't delete in a ForEach, so build a list of + // what to remove then remove. + toRemove := []string{} + err = ctrExecBkt.ForEach(func(id, _ []byte) error { + toRemove = append(toRemove, string(id)) + return nil + }) + if err != nil { + return err + } + for _, execID := range toRemove { + if err := ctrExecBkt.Delete([]byte(execID)); err != nil { + return fmt.Errorf("removing exec session %s from container %s: %w", execID, string(id), err) + } + } + } + + return nil + }) + if err != nil { + return err + } + + // Remove dangling IDs. + for _, id := range toRemoveIDs { + // Look up the ID to see if we also have a dangling name + // in the DB. + name := idBucket.Get([]byte(id)) + if name != nil { + if testID := namesBucket.Get(name); testID != nil { + logrus.Infof("Found dangling name %s (ID %s) in database", string(name), id) + if err := namesBucket.Delete(name); err != nil { + return fmt.Errorf("removing dangling name %s (ID %s) from database: %w", string(name), id, err) + } + } + } + if err := idBucket.Delete([]byte(id)); err != nil { + return fmt.Errorf("removing dangling ID %s from database: %w", id, err) + } + } + + // Now refresh volumes + err = allVolsBucket.ForEach(func(id, _ []byte) error { + dbVol := volBucket.Bucket(id) + if dbVol == nil { + return fmt.Errorf("inconsistency in state - volume %s is in all volumes bucket but volume not found: %w", string(id), define.ErrInternal) + } + + // Get the state + volStateBytes := dbVol.Get(stateKey) + if volStateBytes == nil { + // If the volume doesn't have a state, nothing to do + return nil + } + + oldState := new(VolumeState) + + if err := json.Unmarshal(volStateBytes, oldState); err != nil { + return fmt.Errorf("unmarshalling state for volume %s: %w", string(id), err) + } + + resetVolumeState(oldState) + + newState, err := json.Marshal(oldState) + if err != nil { + return fmt.Errorf("marshalling state for volume %s: %w", string(id), err) + } + + if err := dbVol.Put(stateKey, newState); err != nil { + return fmt.Errorf("storing new state for volume %s: %w", string(id), err) + } + + return nil + }) + if err != nil { + return err + } + + // Now refresh exec sessions + // We want to remove them all, but for-each can't modify buckets + // So we have to make a list of what to operate on, then do the + // work. + toRemoveExec := []string{} + err = execBucket.ForEach(func(id, _ []byte) error { + toRemoveExec = append(toRemoveExec, string(id)) + return nil + }) + if err != nil { + return err + } + + for _, execSession := range toRemoveExec { + if err := execBucket.Delete([]byte(execSession)); err != nil { + return fmt.Errorf("deleting exec session %s registry from database: %w", execSession, err) + } + } + + return nil + }) + return err +} + +// GetDBConfig retrieves runtime configuration fields that were created when +// the database was first initialized +func (s *BoltState) GetDBConfig() (*DBConfig, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + cfg := new(DBConfig) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + configBucket, err := getRuntimeConfigBucket(tx) + if err != nil { + return err + } + + // Some of these may be nil + // When we convert to string, Go will coerce them to "" + // That's probably fine - we could raise an error if the key is + // missing, but just not including it is also OK. + libpodRoot := configBucket.Get(staticDirKey) + libpodTmp := configBucket.Get(tmpDirKey) + storageRoot := configBucket.Get(graphRootKey) + storageTmp := configBucket.Get(runRootKey) + graphDriver := configBucket.Get(graphDriverKey) + volumePath := configBucket.Get(volPathKey) + + cfg.LibpodRoot = string(libpodRoot) + cfg.LibpodTmp = string(libpodTmp) + cfg.StorageRoot = string(storageRoot) + cfg.StorageTmp = string(storageTmp) + cfg.GraphDriver = string(graphDriver) + cfg.VolumePath = string(volumePath) + + return nil + }) + if err != nil { + return nil, err + } + + return cfg, nil +} + +// ValidateDBConfig validates paths in the given runtime against the database +func (s *BoltState) ValidateDBConfig(runtime *Runtime) error { + if !s.valid { + return define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + // Check runtime configuration + if err := checkRuntimeConfig(db, runtime); err != nil { + return err + } + + return nil +} + +// GetContainerName returns the name associated with a given ID. +// Returns ErrNoSuchCtr if the ID does not exist. +func (s *BoltState) GetContainerName(id string) (string, error) { + if id == "" { + return "", define.ErrEmptyID + } + + if !s.valid { + return "", define.ErrDBClosed + } + + idBytes := []byte(id) + + db, err := s.getDBCon() + if err != nil { + return "", err + } + defer s.deferredCloseDBCon(db) + + name := "" + + err = db.View(func(tx *bolt.Tx) error { + idBkt, err := getIDBucket(tx) + if err != nil { + return err + } + + ctrsBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + + nameBytes := idBkt.Get(idBytes) + if nameBytes == nil { + return define.ErrNoSuchCtr + } + + ctrExists := ctrsBkt.Bucket(idBytes) + if ctrExists == nil { + return define.ErrNoSuchCtr + } + + name = string(nameBytes) + return nil + }) + if err != nil { + return "", err + } + + return name, nil +} + +// GetPodName returns the name associated with a given ID. +// Returns ErrNoSuchPod if the ID does not exist. +func (s *BoltState) GetPodName(id string) (string, error) { + if id == "" { + return "", define.ErrEmptyID + } + + if !s.valid { + return "", define.ErrDBClosed + } + + idBytes := []byte(id) + + db, err := s.getDBCon() + if err != nil { + return "", err + } + defer s.deferredCloseDBCon(db) + + name := "" + + err = db.View(func(tx *bolt.Tx) error { + idBkt, err := getIDBucket(tx) + if err != nil { + return err + } + + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + nameBytes := idBkt.Get(idBytes) + if nameBytes == nil { + return define.ErrNoSuchPod + } + + podExists := podBkt.Bucket(idBytes) + if podExists == nil { + return define.ErrNoSuchPod + } + + name = string(nameBytes) + return nil + }) + if err != nil { + return "", err + } + + return name, nil +} + +// Container retrieves a single container from the state by its full ID +func (s *BoltState) Container(id string) (*Container, error) { + if id == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + ctrID := []byte(id) + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(ContainerState) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + return s.getContainerFromDB(ctrID, ctr, ctrBucket, false) + }) + if err != nil { + return nil, err + } + + return ctr, nil +} + +// LookupContainerID retrieves a container ID from the state by full or unique +// partial ID or name +func (s *BoltState) LookupContainerID(idOrName string) (string, error) { + if idOrName == "" { + return "", define.ErrEmptyID + } + + if !s.valid { + return "", define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return "", err + } + defer s.deferredCloseDBCon(db) + + var id []byte + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + fullID, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket) + id = fullID + return err + }) + + if err != nil { + return "", err + } + + retID := string(id) + return retID, nil +} + +// LookupContainer retrieves a container from the state by full or unique +// partial ID or name +func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { + if idOrName == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(ContainerState) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + id, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket) + if err != nil { + return err + } + + return s.getContainerFromDB(id, ctr, ctrBucket, false) + }) + if err != nil { + return nil, err + } + + return ctr, nil +} + +// HasContainer checks if a container is present in the state +func (s *BoltState) HasContainer(id string) (bool, error) { + if id == "" { + return false, define.ErrEmptyID + } + + if !s.valid { + return false, define.ErrDBClosed + } + + ctrID := []byte(id) + + db, err := s.getDBCon() + if err != nil { + return false, err + } + defer s.deferredCloseDBCon(db) + + exists := false + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + ctrDB := ctrBucket.Bucket(ctrID) + if ctrDB != nil { + exists = true + } + + return nil + }) + if err != nil { + return false, err + } + + return exists, nil +} + +// AddContainer adds a container to the state +// The container being added cannot belong to a pod +func (s *BoltState) AddContainer(ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if ctr.config.Pod != "" { + return fmt.Errorf("cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod: %w", define.ErrInvalidArg) + } + + return s.addContainer(ctr, nil) +} + +// RemoveContainer removes a container from the state +// Only removes containers not in pods - for containers that are a member of a +// pod, use RemoveContainerFromPod +func (s *BoltState) RemoveContainer(ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if ctr.config.Pod != "" { + return fmt.Errorf("container %s is part of a pod, use RemoveContainerFromPod instead: %w", ctr.ID(), define.ErrPodExists) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + return s.removeContainer(ctr, nil, tx) + }) + return err +} + +// UpdateContainer updates a container's state from the database +func (s *BoltState) UpdateContainer(ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + return db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + return s.getContainerStateDB(ctrID, ctr, ctrBucket) + }) +} + +// SaveContainer saves a container's current state in the database +func (s *BoltState) SaveContainer(ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + stateJSON, err := json.Marshal(ctr.state) + if err != nil { + return fmt.Errorf("marshalling container %s state to JSON: %w", ctr.ID(), err) + } + netNSPath := ctr.state.NetNS + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + ctrToSave := ctrBucket.Bucket(ctrID) + if ctrToSave == nil { + ctr.valid = false + return fmt.Errorf("container %s does not exist in DB: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + // Update the state + if err := ctrToSave.Put(stateKey, stateJSON); err != nil { + return fmt.Errorf("updating container %s state in DB: %w", ctr.ID(), err) + } + + if netNSPath == "" { + // Delete the existing network namespace + if err := ctrToSave.Delete(netNSKey); err != nil { + return fmt.Errorf("removing network namespace path for container %s in DB: %w", ctr.ID(), err) + } + } + + return nil + }) + return err +} + +// ContainerInUse checks if other containers depend on the given container +// It returns a slice of the IDs of the containers depending on the given +// container. If the slice is empty, no containers depend on the given container +func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + depCtrs := []string{} + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + ctrDB := ctrBucket.Bucket([]byte(ctr.ID())) + if ctrDB == nil { + ctr.valid = false + return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + dependsBkt := ctrDB.Bucket(dependenciesBkt) + if dependsBkt == nil { + return fmt.Errorf("container %s has no dependencies bucket: %w", ctr.ID(), define.ErrInternal) + } + + // Iterate through and add dependencies + err = dependsBkt.ForEach(func(id, _ []byte) error { + depCtrs = append(depCtrs, string(id)) + + return nil + }) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return depCtrs, nil +} + +// AllContainers retrieves all the containers in the database +// If `loadState` is set, the containers' state will be loaded as well. +func (s *BoltState) AllContainers(loadState bool) ([]*Container, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + ctrs := []*Container{} + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + allCtrsBucket, err := getAllCtrsBucket(tx) + if err != nil { + return err + } + + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + return allCtrsBucket.ForEach(func(id, _ []byte) error { + // If performance becomes an issue, this check can be + // removed. But the error messages that come back will + // be much less helpful. + ctrExists := ctrBucket.Bucket(id) + if ctrExists == nil { + return fmt.Errorf("state is inconsistent - container ID %s in all containers, but container not found: %w", string(id), define.ErrInternal) + } + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(ContainerState) + + if err := s.getContainerFromDB(id, ctr, ctrBucket, loadState); err != nil { + logrus.Errorf("Error retrieving container from database: %v", err) + } else { + ctrs = append(ctrs, ctr) + } + + return nil + }) + }) + if err != nil { + return nil, err + } + + return ctrs, nil +} + +// GetNetworks returns the networks this container is a part of. +func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + // if the network mode is not bridge return no networks + if !ctr.config.NetMode.IsBridge() { + return nil, nil + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + networks := make(map[string]types.PerNetworkOptions) + + var convertDB bool + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + ctrNetworkBkt := dbCtr.Bucket(networksBkt) + if ctrNetworkBkt == nil { + // convert if needed + convertDB = true + return nil + } + + return ctrNetworkBkt.ForEach(func(network, v []byte) error { + opts := types.PerNetworkOptions{} + if err := json.Unmarshal(v, &opts); err != nil { + // special case for backwards compat + // earlier version used the container id as value so we set a + // special error to indicate the we have to migrate the db + if !bytes.Equal(v, ctrID) { + return err + } + convertDB = true + } + networks[string(network)] = opts + return nil + }) + }) + if err != nil { + return nil, err + } + if convertDB { + err = db.Update(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + var networkList []string + + ctrNetworkBkt := dbCtr.Bucket(networksBkt) + if ctrNetworkBkt == nil { + ctrNetworkBkt, err = dbCtr.CreateBucket(networksBkt) + if err != nil { + return fmt.Errorf("creating networks bucket for container %s: %w", ctr.ID(), err) + } + // the container has no networks in the db lookup config and write to the db + networkList = ctr.config.NetworksDeprecated + // if there are no networks we have to add the default + if len(networkList) == 0 { + networkList = []string{ctr.runtime.config.Network.DefaultNetwork} + } + } else { + err = ctrNetworkBkt.ForEach(func(network, _ []byte) error { + networkList = append(networkList, string(network)) + return nil + }) + if err != nil { + return err + } + } + + // the container has no networks in the db lookup config and write to the db + for i, network := range networkList { + var intName string + if ctr.state.NetInterfaceDescriptions != nil { + eth, exists := ctr.state.NetInterfaceDescriptions.getInterfaceByName(network) + if !exists { + return fmt.Errorf("no network interface name for container %s on network %s", ctr.config.ID, network) + } + intName = eth + } else { + intName = fmt.Sprintf("eth%d", i) + } + getAliases := func(network string) []string { + var aliases []string + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + return nil + } + netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) + if netAliasesBkt == nil { + // No aliases for this specific network. + return nil + } + + // let's ignore the error here there is nothing we can do + _ = netAliasesBkt.ForEach(func(alias, _ []byte) error { + aliases = append(aliases, string(alias)) + return nil + }) + // also add the short container id as alias + return aliases + } + + netOpts := &types.PerNetworkOptions{ + InterfaceName: intName, + // we have to add the short id as alias for docker compat + Aliases: append(getAliases(network), ctr.config.ID[:12]), + } + // only set the static ip/mac on the first network + if i == 0 { + if ctr.config.StaticIP != nil { + netOpts.StaticIPs = []net.IP{ctr.config.StaticIP} + } + netOpts.StaticMAC = ctr.config.StaticMAC + } + + optsBytes, err := json.Marshal(netOpts) + if err != nil { + return err + } + // insert into network map because we need to return this + networks[network] = *netOpts + + err = ctrNetworkBkt.Put([]byte(network), optsBytes) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return nil, err + } + } + + return networks, nil +} + +// NetworkConnect adds the given container to the given network. If aliases are +// specified, those will be added to the given network. +func (s *BoltState) NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error { + return s.networkModify(ctr, network, opts, true) +} + +// NetworkModify will allow you to set new options on an existing connected network +func (s *BoltState) NetworkModify(ctr *Container, network string, opts types.PerNetworkOptions) error { + return s.networkModify(ctr, network, opts, false) +} + +// networkModify allows you to modify or add a new network, to add a new network use the new bool +func (s *BoltState) networkModify(ctr *Container, network string, opts types.PerNetworkOptions, new bool) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if network == "" { + return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg) + } + + optBytes, err := json.Marshal(opts) + if err != nil { + return fmt.Errorf("marshalling network options JSON for container %s: %w", ctr.ID(), err) + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + return db.Update(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + ctrNetworksBkt := dbCtr.Bucket(networksBkt) + if ctrNetworksBkt == nil { + return fmt.Errorf("container %s does not have a network bucket: %w", ctr.ID(), define.ErrNoSuchNetwork) + } + netConnected := ctrNetworksBkt.Get([]byte(network)) + + if new && netConnected != nil { + return fmt.Errorf("container %s is already connected to network %q: %w", ctr.ID(), network, define.ErrNetworkConnected) + } else if !new && netConnected == nil { + return fmt.Errorf("container %s is not connected to network %q: %w", ctr.ID(), network, define.ErrNoSuchNetwork) + } + + // Modify/Add the network + if err := ctrNetworksBkt.Put([]byte(network), optBytes); err != nil { + return fmt.Errorf("adding container %s to network %s in DB: %w", ctr.ID(), network, err) + } + + return nil + }) +} + +// NetworkDisconnect disconnects the container from the given network, also +// removing any aliases in the network. +func (s *BoltState) NetworkDisconnect(ctr *Container, network string) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if network == "" { + return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg) + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + return db.Update(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + ctrNetworksBkt := dbCtr.Bucket(networksBkt) + if ctrNetworksBkt == nil { + return fmt.Errorf("container %s is not connected to any networks, so cannot disconnect: %w", ctr.ID(), define.ErrNoSuchNetwork) + } + netConnected := ctrNetworksBkt.Get([]byte(network)) + if netConnected == nil { + return fmt.Errorf("container %s is not connected to network %q: %w", ctr.ID(), network, define.ErrNoSuchNetwork) + } + + if err := ctrNetworksBkt.Delete([]byte(network)); err != nil { + return fmt.Errorf("removing container %s from network %s: %w", ctr.ID(), network, err) + } + + if ctrAliasesBkt != nil { + bktExists := ctrAliasesBkt.Bucket([]byte(network)) + if bktExists == nil { + return nil + } + + if err := ctrAliasesBkt.DeleteBucket([]byte(network)); err != nil { + return fmt.Errorf("removing container %s network aliases for network %s: %w", ctr.ID(), network, err) + } + } + + return nil + }) +} + +// GetContainerConfig returns a container config from the database by full ID +func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) { + if len(id) == 0 { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + config := new(ContainerConfig) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + return s.getContainerConfigFromDB([]byte(id), config, ctrBucket) + }) + if err != nil { + return nil, err + } + + return config, nil +} + +// AddContainerExitCode adds the exit code for the specified container to the database. +func (s *BoltState) AddContainerExitCode(id string, exitCode int32) error { + if len(id) == 0 { + return define.ErrEmptyID + } + + if !s.valid { + return define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + rawID := []byte(id) + rawExitCode := []byte(strconv.Itoa(int(exitCode))) + rawTimeStamp, err := time.Now().MarshalText() + if err != nil { + return fmt.Errorf("marshalling exit-code time stamp: %w", err) + } + + return db.Update(func(tx *bolt.Tx) error { + exitCodeBucket, err := getExitCodeBucket(tx) + if err != nil { + return err + } + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + if err := exitCodeBucket.Put(rawID, rawExitCode); err != nil { + return fmt.Errorf("adding exit code of container %s to DB: %w", id, err) + } + if err := timeStampBucket.Put(rawID, rawTimeStamp); err != nil { + if rmErr := exitCodeBucket.Delete(rawID); rmErr != nil { + logrus.Errorf("Removing exit code of container %s from DB: %v", id, rmErr) + } + return fmt.Errorf("adding exit-code time stamp of container %s to DB: %w", id, err) + } + + return nil + }) +} + +// GetContainerExitCode returns the exit code for the specified container. +func (s *BoltState) GetContainerExitCode(id string) (int32, error) { + if len(id) == 0 { + return -1, define.ErrEmptyID + } + + if !s.valid { + return -1, define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return -1, err + } + defer s.deferredCloseDBCon(db) + + rawID := []byte(id) + result := int32(-1) + return result, db.View(func(tx *bolt.Tx) error { + exitCodeBucket, err := getExitCodeBucket(tx) + if err != nil { + return err + } + + rawExitCode := exitCodeBucket.Get(rawID) + if rawExitCode == nil { + return fmt.Errorf("getting exit code of container %s from DB: %w", id, define.ErrNoSuchExitCode) + } + + exitCode, err := strconv.Atoi(string(rawExitCode)) + if err != nil { + return fmt.Errorf("converting raw exit code %v of container %s: %w", rawExitCode, id, err) + } + + result = int32(exitCode) + return nil + }) +} + +// GetContainerExitCodeTimeStamp returns the time stamp when the exit code of +// the specified container was added to the database. +func (s *BoltState) GetContainerExitCodeTimeStamp(id string) (*time.Time, error) { + if len(id) == 0 { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + rawID := []byte(id) + var result time.Time + return &result, db.View(func(tx *bolt.Tx) error { + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + rawTimeStamp := timeStampBucket.Get(rawID) + if rawTimeStamp == nil { + return fmt.Errorf("getting exit-code time stamp of container %s from DB: %w", id, define.ErrNoSuchExitCode) + } + + if err := result.UnmarshalText(rawTimeStamp); err != nil { + return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, id, err) + } + + return nil + }) +} + +// PruneContainerExitCodes removes exit codes older than 5 minutes unless the associated +// container still exists. +func (s *BoltState) PruneContainerExitCodes() error { + if !s.valid { + return define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + toRemoveIDs := []string{} + + threshold := time.Minute * 5 + err = db.View(func(tx *bolt.Tx) error { + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + ctrsBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + return timeStampBucket.ForEach(func(rawID, rawTimeStamp []byte) error { + if ctrsBucket.Bucket(rawID) != nil { + // If the container still exists, don't prune + // its exit code since we may still need it. + return nil + } + var timeStamp time.Time + if err := timeStamp.UnmarshalText(rawTimeStamp); err != nil { + return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, string(rawID), err) + } + if time.Since(timeStamp) > threshold { + toRemoveIDs = append(toRemoveIDs, string(rawID)) + } + return nil + }) + }) + if err != nil { + return fmt.Errorf("reading exit codes to prune: %w", err) + } + + if len(toRemoveIDs) > 0 { + err = db.Update(func(tx *bolt.Tx) error { + exitCodeBucket, err := getExitCodeBucket(tx) + if err != nil { + return err + } + timeStampBucket, err := getExitCodeTimeStampBucket(tx) + if err != nil { + return err + } + + var finalErr error + for _, id := range toRemoveIDs { + rawID := []byte(id) + if err := exitCodeBucket.Delete(rawID); err != nil { + if finalErr != nil { + logrus.Error(finalErr) + } + finalErr = fmt.Errorf("removing exit code of container %s from DB: %w", id, err) + } + if err := timeStampBucket.Delete(rawID); err != nil { + if finalErr != nil { + logrus.Error(finalErr) + } + finalErr = fmt.Errorf("removing exit code timestamp of container %s from DB: %w", id, err) + } + } + + return finalErr + }) + if err != nil { + return fmt.Errorf("pruning exit codes: %w", err) + } + } + + return nil +} + +// AddExecSession adds an exec session to the state. +func (s *BoltState) AddExecSession(ctr *Container, session *ExecSession) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + ctrID := []byte(ctr.ID()) + sessionID := []byte(session.ID()) + + err = db.Update(func(tx *bolt.Tx) error { + execBucket, err := getExecBucket(tx) + if err != nil { + return err + } + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return fmt.Errorf("container %s is not present in the database: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + ctrExecSessionBucket, err := dbCtr.CreateBucketIfNotExists(execBkt) + if err != nil { + return fmt.Errorf("creating exec sessions bucket for container %s: %w", ctr.ID(), err) + } + + execExists := execBucket.Get(sessionID) + if execExists != nil { + return fmt.Errorf("an exec session with ID %s already exists: %w", session.ID(), define.ErrExecSessionExists) + } + + if err := execBucket.Put(sessionID, ctrID); err != nil { + return fmt.Errorf("adding exec session %s to DB: %w", session.ID(), err) + } + + if err := ctrExecSessionBucket.Put(sessionID, ctrID); err != nil { + return fmt.Errorf("adding exec session %s to container %s in DB: %w", session.ID(), ctr.ID(), err) + } + + return nil + }) + return err +} + +// GetExecSession returns the ID of the container an exec session is associated +// with. +func (s *BoltState) GetExecSession(id string) (string, error) { + if !s.valid { + return "", define.ErrDBClosed + } + + if id == "" { + return "", define.ErrEmptyID + } + + db, err := s.getDBCon() + if err != nil { + return "", err + } + defer s.deferredCloseDBCon(db) + + ctrID := "" + err = db.View(func(tx *bolt.Tx) error { + execBucket, err := getExecBucket(tx) + if err != nil { + return err + } + + ctr := execBucket.Get([]byte(id)) + if ctr == nil { + return fmt.Errorf("no exec session with ID %s found: %w", id, define.ErrNoSuchExecSession) + } + ctrID = string(ctr) + return nil + }) + return ctrID, err +} + +// RemoveExecSession removes references to the given exec session in the +// database. +func (s *BoltState) RemoveExecSession(session *ExecSession) error { + if !s.valid { + return define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + sessionID := []byte(session.ID()) + containerID := []byte(session.ContainerID()) + err = db.Update(func(tx *bolt.Tx) error { + execBucket, err := getExecBucket(tx) + if err != nil { + return err + } + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + sessionExists := execBucket.Get(sessionID) + if sessionExists == nil { + return define.ErrNoSuchExecSession + } + // Check that container ID matches + if string(sessionExists) != session.ContainerID() { + return fmt.Errorf("database inconsistency: exec session %s points to container %s in state but %s in database: %w", session.ID(), session.ContainerID(), string(sessionExists), define.ErrInternal) + } + + if err := execBucket.Delete(sessionID); err != nil { + return fmt.Errorf("removing exec session %s from database: %w", session.ID(), err) + } + + dbCtr := ctrBucket.Bucket(containerID) + if dbCtr == nil { + // State is inconsistent. We refer to a container that + // is no longer in the state. + // Return without error, to attempt to recover. + return nil + } + + ctrExecBucket := dbCtr.Bucket(execBkt) + if ctrExecBucket == nil { + // Again, state is inconsistent. We should have an exec + // bucket, and it should have this session. + // Again, nothing we can do, so proceed and try to + // recover. + return nil + } + + ctrSessionExists := ctrExecBucket.Get(sessionID) + if ctrSessionExists != nil { + if err := ctrExecBucket.Delete(sessionID); err != nil { + return fmt.Errorf("removing exec session %s from container %s in database: %w", session.ID(), session.ContainerID(), err) + } + } + + return nil + }) + return err +} + +// GetContainerExecSessions retrieves the IDs of all exec sessions running in a +// container that the database is aware of (IE, were added via AddExecSession). +func (s *BoltState) GetContainerExecSessions(ctr *Container) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + ctrID := []byte(ctr.ID()) + sessions := []string{} + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return define.ErrNoSuchCtr + } + + ctrExecSessions := dbCtr.Bucket(execBkt) + if ctrExecSessions == nil { + return nil + } + + return ctrExecSessions.ForEach(func(id, _ []byte) error { + sessions = append(sessions, string(id)) + return nil + }) + }) + if err != nil { + return nil, err + } + + return sessions, nil +} + +// RemoveContainerExecSessions removes all exec sessions attached to a given +// container. +func (s *BoltState) RemoveContainerExecSessions(ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + ctrID := []byte(ctr.ID()) + sessions := []string{} + + err = db.Update(func(tx *bolt.Tx) error { + execBucket, err := getExecBucket(tx) + if err != nil { + return err + } + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return define.ErrNoSuchCtr + } + + ctrExecSessions := dbCtr.Bucket(execBkt) + if ctrExecSessions == nil { + return nil + } + + err = ctrExecSessions.ForEach(func(id, _ []byte) error { + sessions = append(sessions, string(id)) + return nil + }) + if err != nil { + return err + } + + for _, session := range sessions { + if err := ctrExecSessions.Delete([]byte(session)); err != nil { + return fmt.Errorf("removing container %s exec session %s from database: %w", ctr.ID(), session, err) + } + // Check if the session exists in the global table + // before removing. It should, but in cases where the DB + // has become inconsistent, we should try and proceed + // so we can recover. + sessionExists := execBucket.Get([]byte(session)) + if sessionExists == nil { + continue + } + if string(sessionExists) != ctr.ID() { + return fmt.Errorf("database mismatch: exec session %s is associated with containers %s and %s: %w", session, ctr.ID(), string(sessionExists), define.ErrInternal) + } + if err := execBucket.Delete([]byte(session)); err != nil { + return fmt.Errorf("removing container %s exec session %s from exec sessions: %w", ctr.ID(), session, err) + } + } + + return nil + }) + return err +} + +// RewriteContainerConfig rewrites a container's configuration. +// WARNING: This function is DANGEROUS. Do not use without reading the full +// comment on this function in state.go. +func (s *BoltState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + newCfgJSON, err := json.Marshal(newCfg) + if err != nil { + return fmt.Errorf("marshalling new configuration JSON for container %s: %w", ctr.ID(), err) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + ctrBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + + ctrDB := ctrBkt.Bucket([]byte(ctr.ID())) + if ctrDB == nil { + ctr.valid = false + return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + if err := ctrDB.Put(configKey, newCfgJSON); err != nil { + return fmt.Errorf("updating container %s config JSON: %w", ctr.ID(), err) + } + + return nil + }) + return err +} + +// SafeRewriteContainerConfig rewrites a container's configuration in a more +// limited fashion than RewriteContainerConfig. It is marked as safe to use +// under most circumstances, unlike RewriteContainerConfig. +// DO NOT USE TO: Change container dependencies, change pod membership, change +// locks, change container ID. +func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if newName != "" && newCfg.Name != newName { + return fmt.Errorf("new name %s for container %s must match name in given container config: %w", newName, ctr.ID(), define.ErrInvalidArg) + } + if newName != "" && oldName == "" { + return fmt.Errorf("must provide old name for container if a new name is given: %w", define.ErrInvalidArg) + } + + newCfgJSON, err := json.Marshal(newCfg) + if err != nil { + return fmt.Errorf("marshalling new configuration JSON for container %s: %w", ctr.ID(), err) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + if newName != "" { + idBkt, err := getIDBucket(tx) + if err != nil { + return err + } + namesBkt, err := getNamesBucket(tx) + if err != nil { + return err + } + allCtrsBkt, err := getAllCtrsBucket(tx) + if err != nil { + return err + } + + needsRename := true + if exists := namesBkt.Get([]byte(newName)); exists != nil { + if string(exists) == ctr.ID() { + // Name already associated with the ID + // of this container. No need for a + // rename. + needsRename = false + } else { + return fmt.Errorf("name %s already in use, cannot rename container %s: %w", newName, ctr.ID(), define.ErrCtrExists) + } + } + + if needsRename { + // We do have to remove the old name. The other + // buckets are ID-indexed so we just need to + // overwrite the values there. + if err := namesBkt.Delete([]byte(oldName)); err != nil { + return fmt.Errorf("deleting container %s old name from DB for rename: %w", ctr.ID(), err) + } + if err := idBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil { + return fmt.Errorf("renaming container %s in ID bucket in DB: %w", ctr.ID(), err) + } + if err := namesBkt.Put([]byte(newName), []byte(ctr.ID())); err != nil { + return fmt.Errorf("adding new name %s for container %s in DB: %w", newName, ctr.ID(), err) + } + if err := allCtrsBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil { + return fmt.Errorf("renaming container %s in all containers bucket in DB: %w", ctr.ID(), err) + } + if ctr.config.Pod != "" { + podsBkt, err := getPodBucket(tx) + if err != nil { + return err + } + podBkt := podsBkt.Bucket([]byte(ctr.config.Pod)) + if podBkt == nil { + return fmt.Errorf("bucket for pod %s does not exist: %w", ctr.config.Pod, define.ErrInternal) + } + podCtrBkt := podBkt.Bucket(containersBkt) + if podCtrBkt == nil { + return fmt.Errorf("pod %s does not have a containers bucket: %w", ctr.config.Pod, define.ErrInternal) + } + if err := podCtrBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil { + return fmt.Errorf("renaming container %s in pod %s members bucket: %w", ctr.ID(), ctr.config.Pod, err) + } + } + } + } + + ctrBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + + ctrDB := ctrBkt.Bucket([]byte(ctr.ID())) + if ctrDB == nil { + ctr.valid = false + return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + if err := ctrDB.Put(configKey, newCfgJSON); err != nil { + return fmt.Errorf("updating container %s config JSON: %w", ctr.ID(), err) + } + + return nil + }) + return err +} + +// RewritePodConfig rewrites a pod's configuration. +// WARNING: This function is DANGEROUS. Do not use without reading the full +// comment on this function in state.go. +func (s *BoltState) RewritePodConfig(pod *Pod, newCfg *PodConfig) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + newCfgJSON, err := json.Marshal(newCfg) + if err != nil { + return fmt.Errorf("marshalling new configuration JSON for pod %s: %w", pod.ID(), err) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + podDB := podBkt.Bucket([]byte(pod.ID())) + if podDB == nil { + pod.valid = false + return fmt.Errorf("no pod with ID %s found in DB: %w", pod.ID(), define.ErrNoSuchPod) + } + + if err := podDB.Put(configKey, newCfgJSON); err != nil { + return fmt.Errorf("updating pod %s config JSON: %w", pod.ID(), err) + } + + return nil + }) + return err +} + +// RewriteVolumeConfig rewrites a volume's configuration. +// WARNING: This function is DANGEROUS. Do not use without reading the full +// comment on this function in state.go. +func (s *BoltState) RewriteVolumeConfig(volume *Volume, newCfg *VolumeConfig) error { + if !s.valid { + return define.ErrDBClosed + } + + if !volume.valid { + return define.ErrVolumeRemoved + } + + newCfgJSON, err := json.Marshal(newCfg) + if err != nil { + return fmt.Errorf("marshalling new configuration JSON for volume %q: %w", volume.Name(), err) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + volBkt, err := getVolBucket(tx) + if err != nil { + return err + } + + volDB := volBkt.Bucket([]byte(volume.Name())) + if volDB == nil { + volume.valid = false + return fmt.Errorf("no volume with name %q found in DB: %w", volume.Name(), define.ErrNoSuchVolume) + } + + if err := volDB.Put(configKey, newCfgJSON); err != nil { + return fmt.Errorf("updating volume %q config JSON: %w", volume.Name(), err) + } + + return nil + }) + return err +} + +// Pod retrieves a pod given its full ID +func (s *BoltState) Pod(id string) (*Pod, error) { + if id == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + podID := []byte(id) + + pod := new(Pod) + pod.config = new(PodConfig) + pod.state = new(podState) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + return s.getPodFromDB(podID, pod, podBkt) + }) + if err != nil { + return nil, err + } + + return pod, nil +} + +// LookupPod retrieves a pod from full or unique partial ID or name +func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { + if idOrName == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + pod := new(Pod) + pod.config = new(PodConfig) + pod.state = new(podState) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + namesBkt, err := getNamesBucket(tx) + if err != nil { + return err + } + + // First, check if the ID given was the actual pod ID + var id []byte + podExists := podBkt.Bucket([]byte(idOrName)) + if podExists != nil { + // A full pod ID was given. + id = []byte(idOrName) + return s.getPodFromDB(id, pod, podBkt) + } + + // Next, check if the full name was given + isCtr := false + fullID := namesBkt.Get([]byte(idOrName)) + if fullID != nil { + // The name exists and maps to an ID. + // However, we aren't yet sure if the ID is a pod. + podExists = podBkt.Bucket(fullID) + if podExists != nil { + // A pod bucket matching the full ID was found. + return s.getPodFromDB(fullID, pod, podBkt) + } + // Don't error if we have a name match but it's not a + // pod - there's a chance we have a pod with an ID + // starting with those characters. + // However, so we can return a good error, note whether + // this is a container. + isCtr = true + } + // They did not give us a full pod name or ID. + // Search for partial ID matches. + exists := false + err = podBkt.ForEach(func(checkID, _ []byte) error { + if strings.HasPrefix(string(checkID), idOrName) { + if exists { + return fmt.Errorf("more than one result for ID or name %s: %w", idOrName, define.ErrPodExists) + } + id = checkID + exists = true + } + + return nil + }) + if err != nil { + return err + } else if !exists { + if isCtr { + return fmt.Errorf("%s is a container, not a pod: %w", idOrName, define.ErrNoSuchPod) + } + return fmt.Errorf("no pod with name or ID %s found: %w", idOrName, define.ErrNoSuchPod) + } + + // We might have found a container ID, but it's OK + // We'll just fail in getPodFromDB with ErrNoSuchPod + return s.getPodFromDB(id, pod, podBkt) + }) + if err != nil { + return nil, err + } + + return pod, nil +} + +// HasPod checks if a pod with the given ID exists in the state +func (s *BoltState) HasPod(id string) (bool, error) { + if id == "" { + return false, define.ErrEmptyID + } + + if !s.valid { + return false, define.ErrDBClosed + } + + podID := []byte(id) + + exists := false + + db, err := s.getDBCon() + if err != nil { + return false, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + podDB := podBkt.Bucket(podID) + if podDB != nil { + exists = true + } + + return nil + }) + if err != nil { + return false, err + } + + return exists, nil +} + +// PodHasContainer checks if the given pod has a container with the given ID +func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) { + if id == "" { + return false, define.ErrEmptyID + } + + if !s.valid { + return false, define.ErrDBClosed + } + + if !pod.valid { + return false, define.ErrPodRemoved + } + + ctrID := []byte(id) + podID := []byte(pod.ID()) + + exists := false + + db, err := s.getDBCon() + if err != nil { + return false, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + // Get pod itself + podDB := podBkt.Bucket(podID) + if podDB == nil { + pod.valid = false + return fmt.Errorf("pod %s not found in database: %w", pod.ID(), define.ErrNoSuchPod) + } + + // Get pod containers bucket + podCtrs := podDB.Bucket(containersBkt) + if podCtrs == nil { + return fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal) + } + + ctr := podCtrs.Get(ctrID) + if ctr != nil { + exists = true + } + + return nil + }) + if err != nil { + return false, err + } + + return exists, nil +} + +// PodContainersByID returns the IDs of all containers present in the given pod +func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !pod.valid { + return nil, define.ErrPodRemoved + } + + podID := []byte(pod.ID()) + + ctrs := []string{} + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + // Get pod itself + podDB := podBkt.Bucket(podID) + if podDB == nil { + pod.valid = false + return fmt.Errorf("pod %s not found in database: %w", pod.ID(), define.ErrNoSuchPod) + } + + // Get pod containers bucket + podCtrs := podDB.Bucket(containersBkt) + if podCtrs == nil { + return fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal) + } + + // Iterate through all containers in the pod + err = podCtrs.ForEach(func(id, _ []byte) error { + ctrs = append(ctrs, string(id)) + + return nil + }) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return ctrs, nil +} + +// PodContainers returns all the containers present in the given pod +func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !pod.valid { + return nil, define.ErrPodRemoved + } + + podID := []byte(pod.ID()) + + ctrs := []*Container{} + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + ctrBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + + // Get pod itself + podDB := podBkt.Bucket(podID) + if podDB == nil { + pod.valid = false + return fmt.Errorf("pod %s not found in database: %w", pod.ID(), define.ErrNoSuchPod) + } + + // Get pod containers bucket + podCtrs := podDB.Bucket(containersBkt) + if podCtrs == nil { + return fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal) + } + + // Iterate through all containers in the pod + err = podCtrs.ForEach(func(id, _ []byte) error { + newCtr := new(Container) + newCtr.config = new(ContainerConfig) + newCtr.state = new(ContainerState) + ctrs = append(ctrs, newCtr) + + return s.getContainerFromDB(id, newCtr, ctrBkt, false) + }) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return ctrs, nil +} + +// AddVolume adds the given volume to the state. It also adds ctrDepID to +// the sub bucket holding the container dependencies that this volume has +func (s *BoltState) AddVolume(volume *Volume) error { + if !s.valid { + return define.ErrDBClosed + } + + if !volume.valid { + return define.ErrVolumeRemoved + } + + volName := []byte(volume.Name()) + + volConfigJSON, err := json.Marshal(volume.config) + if err != nil { + return fmt.Errorf("marshalling volume %s config to JSON: %w", volume.Name(), err) + } + + // Volume state is allowed to not exist + var volStateJSON []byte + if volume.state != nil { + volStateJSON, err = json.Marshal(volume.state) + if err != nil { + return fmt.Errorf("marshalling volume %s state to JSON: %w", volume.Name(), err) + } + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + volBkt, err := getVolBucket(tx) + if err != nil { + return err + } + + allVolsBkt, err := getAllVolsBucket(tx) + if err != nil { + return err + } + + volCtrsBkt, err := getVolumeContainersBucket(tx) + if err != nil { + return err + } + + // Check if we already have a volume with the given name + volExists := allVolsBkt.Get(volName) + if volExists != nil { + return fmt.Errorf("name %s is in use: %w", volume.Name(), define.ErrVolumeExists) + } + + // We are good to add the volume + // Make a bucket for it + newVol, err := volBkt.CreateBucket(volName) + if err != nil { + return fmt.Errorf("creating bucket for volume %s: %w", volume.Name(), err) + } + + // Make a subbucket for the containers using the volume. Dependent container IDs will be addedremoved to + // this bucket in addcontainer/removeContainer + if _, err := newVol.CreateBucket(volDependenciesBkt); err != nil { + return fmt.Errorf("creating bucket for containers using volume %s: %w", volume.Name(), err) + } + + if err := newVol.Put(configKey, volConfigJSON); err != nil { + return fmt.Errorf("storing volume %s configuration in DB: %w", volume.Name(), err) + } + + if volStateJSON != nil { + if err := newVol.Put(stateKey, volStateJSON); err != nil { + return fmt.Errorf("storing volume %s state in DB: %w", volume.Name(), err) + } + } + + if volume.config.StorageID != "" { + if err := volCtrsBkt.Put([]byte(volume.config.StorageID), volName); err != nil { + return fmt.Errorf("storing volume %s container ID in DB: %w", volume.Name(), err) + } + } + + if err := allVolsBkt.Put(volName, volName); err != nil { + return fmt.Errorf("storing volume %s in all volumes bucket in DB: %w", volume.Name(), err) + } + + return nil + }) + return err +} + +// RemoveVolume removes the given volume from the state +func (s *BoltState) RemoveVolume(volume *Volume) error { + if !s.valid { + return define.ErrDBClosed + } + + volName := []byte(volume.Name()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + volBkt, err := getVolBucket(tx) + if err != nil { + return err + } + + allVolsBkt, err := getAllVolsBucket(tx) + if err != nil { + return err + } + + ctrBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + + volCtrIDBkt, err := getVolumeContainersBucket(tx) + if err != nil { + return err + } + + // Check if the volume exists + volDB := volBkt.Bucket(volName) + if volDB == nil { + volume.valid = false + return fmt.Errorf("volume %s does not exist in DB: %w", volume.Name(), define.ErrNoSuchVolume) + } + + // Check if volume is not being used by any container + // This should never be nil + // But if it is, we can assume that no containers are using + // the volume. + volCtrsBkt := volDB.Bucket(volDependenciesBkt) + if volCtrsBkt != nil { + var deps []string + err = volCtrsBkt.ForEach(func(id, _ []byte) error { + // Alright, this is ugly. + // But we need it to work around the change in + // volume dependency handling, to make sure that + // older Podman versions don't cause DB + // corruption. + // Look up all dependencies and see that they + // still exist before appending. + ctrExists := ctrBkt.Bucket(id) + if ctrExists == nil { + return nil + } + + deps = append(deps, string(id)) + return nil + }) + if err != nil { + return fmt.Errorf("getting list of dependencies from dependencies bucket for volumes %q: %w", volume.Name(), err) + } + if len(deps) > 0 { + return fmt.Errorf("volume %s is being used by container(s) %s: %w", volume.Name(), strings.Join(deps, ","), define.ErrVolumeBeingUsed) + } + } + + // volume is ready for removal + // Let's kick it out + if err := allVolsBkt.Delete(volName); err != nil { + return fmt.Errorf("removing volume %s from all volumes bucket in DB: %w", volume.Name(), err) + } + if err := volBkt.DeleteBucket(volName); err != nil { + return fmt.Errorf("removing volume %s from DB: %w", volume.Name(), err) + } + if volume.config.StorageID != "" { + if err := volCtrIDBkt.Delete([]byte(volume.config.StorageID)); err != nil { + return fmt.Errorf("removing volume %s container ID from DB: %w", volume.Name(), err) + } + } + + return nil + }) + return err +} + +// UpdateVolume updates the volume's state from the database. +func (s *BoltState) UpdateVolume(volume *Volume) error { + if !s.valid { + return define.ErrDBClosed + } + + if !volume.valid { + return define.ErrVolumeRemoved + } + + newState := new(VolumeState) + volumeName := []byte(volume.Name()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + volBucket, err := getVolBucket(tx) + if err != nil { + return err + } + + volToUpdate := volBucket.Bucket(volumeName) + if volToUpdate == nil { + volume.valid = false + return fmt.Errorf("no volume with name %s found in database: %w", volume.Name(), define.ErrNoSuchVolume) + } + + stateBytes := volToUpdate.Get(stateKey) + if stateBytes == nil { + // Having no state is valid. + // Return nil, use the empty state. + return nil + } + + if err := json.Unmarshal(stateBytes, newState); err != nil { + return fmt.Errorf("unmarshalling volume %s state: %w", volume.Name(), err) + } + + return nil + }) + if err != nil { + return err + } + + volume.state = newState + + return nil +} + +// SaveVolume saves the volume's state to the database. +func (s *BoltState) SaveVolume(volume *Volume) error { + if !s.valid { + return define.ErrDBClosed + } + + if !volume.valid { + return define.ErrVolumeRemoved + } + + volumeName := []byte(volume.Name()) + + var newStateJSON []byte + if volume.state != nil { + stateJSON, err := json.Marshal(volume.state) + if err != nil { + return fmt.Errorf("marshalling volume %s state to JSON: %w", volume.Name(), err) + } + newStateJSON = stateJSON + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + volBucket, err := getVolBucket(tx) + if err != nil { + return err + } + + volToUpdate := volBucket.Bucket(volumeName) + if volToUpdate == nil { + volume.valid = false + return fmt.Errorf("no volume with name %s found in database: %w", volume.Name(), define.ErrNoSuchVolume) + } + + return volToUpdate.Put(stateKey, newStateJSON) + }) + return err +} + +// AllVolumes returns all volumes present in the state +func (s *BoltState) AllVolumes() ([]*Volume, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + volumes := []*Volume{} + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + allVolsBucket, err := getAllVolsBucket(tx) + if err != nil { + return err + } + + volBucket, err := getVolBucket(tx) + if err != nil { + return err + } + err = allVolsBucket.ForEach(func(id, _ []byte) error { + volExists := volBucket.Bucket(id) + // This check can be removed if performance becomes an + // issue, but much less helpful errors will be produced + if volExists == nil { + return fmt.Errorf("inconsistency in state - volume %s is in all volumes bucket but volume not found: %w", string(id), define.ErrInternal) + } + + volume := new(Volume) + volume.config = new(VolumeConfig) + volume.state = new(VolumeState) + + if err := s.getVolumeFromDB(id, volume, volBucket); err != nil { + if !errors.Is(err, define.ErrNSMismatch) { + logrus.Errorf("Retrieving volume %s from the database: %v", string(id), err) + } + } else { + volumes = append(volumes, volume) + } + + return nil + }) + return err + }) + if err != nil { + return nil, err + } + + return volumes, nil +} + +// Volume retrieves a volume from full name +func (s *BoltState) Volume(name string) (*Volume, error) { + if name == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + volName := []byte(name) + + volume := new(Volume) + volume.config = new(VolumeConfig) + volume.state = new(VolumeState) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + volBkt, err := getVolBucket(tx) + if err != nil { + return err + } + + return s.getVolumeFromDB(volName, volume, volBkt) + }) + if err != nil { + return nil, err + } + + return volume, nil +} + +// LookupVolume locates a volume from a partial name. +func (s *BoltState) LookupVolume(name string) (*Volume, error) { + if name == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + volName := []byte(name) + + volume := new(Volume) + volume.config = new(VolumeConfig) + volume.state = new(VolumeState) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + volBkt, err := getVolBucket(tx) + if err != nil { + return err + } + + allVolsBkt, err := getAllVolsBucket(tx) + if err != nil { + return err + } + + // Check for exact match on name + volDB := volBkt.Bucket(volName) + if volDB != nil { + return s.getVolumeFromDB(volName, volume, volBkt) + } + + // No exact match. Search all names. + foundMatch := false + err = allVolsBkt.ForEach(func(checkName, _ []byte) error { + if strings.HasPrefix(string(checkName), name) { + if foundMatch { + return fmt.Errorf("more than one result for volume name %q: %w", name, define.ErrVolumeExists) + } + foundMatch = true + volName = checkName + } + return nil + }) + if err != nil { + return err + } + + if !foundMatch { + return fmt.Errorf("no volume with name %q found: %w", name, define.ErrNoSuchVolume) + } + + return s.getVolumeFromDB(volName, volume, volBkt) + }) + if err != nil { + return nil, err + } + + return volume, nil +} + +// HasVolume returns true if the given volume exists in the state, otherwise it returns false +func (s *BoltState) HasVolume(name string) (bool, error) { + if name == "" { + return false, define.ErrEmptyID + } + + if !s.valid { + return false, define.ErrDBClosed + } + + volName := []byte(name) + + exists := false + + db, err := s.getDBCon() + if err != nil { + return false, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + volBkt, err := getVolBucket(tx) + if err != nil { + return err + } + + volDB := volBkt.Bucket(volName) + if volDB != nil { + exists = true + } + + return nil + }) + if err != nil { + return false, err + } + + return exists, nil +} + +// VolumeInUse checks if any container is using the volume +// It returns a slice of the IDs of the containers using the given +// volume. If the slice is empty, no containers use the given volume +func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !volume.valid { + return nil, define.ErrVolumeRemoved + } + + depCtrs := []string{} + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + volBucket, err := getVolBucket(tx) + if err != nil { + return err + } + + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + volDB := volBucket.Bucket([]byte(volume.Name())) + if volDB == nil { + volume.valid = false + return fmt.Errorf("no volume with name %s found in DB: %w", volume.Name(), define.ErrNoSuchVolume) + } + + dependsBkt := volDB.Bucket(volDependenciesBkt) + if dependsBkt == nil { + return fmt.Errorf("volume %s has no dependencies bucket: %w", volume.Name(), define.ErrInternal) + } + + // Iterate through and add dependencies + err = dependsBkt.ForEach(func(id, _ []byte) error { + // Look up all dependencies and see that they + // still exist before appending. + ctrExists := ctrBucket.Bucket(id) + if ctrExists == nil { + return nil + } + + depCtrs = append(depCtrs, string(id)) + + return nil + }) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return depCtrs, nil +} + +// AddPod adds the given pod to the state. +func (s *BoltState) AddPod(pod *Pod) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + podID := []byte(pod.ID()) + podName := []byte(pod.Name()) + + podConfigJSON, err := json.Marshal(pod.config) + if err != nil { + return fmt.Errorf("marshalling pod %s config to JSON: %w", pod.ID(), err) + } + + podStateJSON, err := json.Marshal(pod.state) + if err != nil { + return fmt.Errorf("marshalling pod %s state to JSON: %w", pod.ID(), err) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + allPodsBkt, err := getAllPodsBucket(tx) + if err != nil { + return err + } + + idsBkt, err := getIDBucket(tx) + if err != nil { + return err + } + + namesBkt, err := getNamesBucket(tx) + if err != nil { + return err + } + + // Check if we already have something with the given ID and name + idExist := idsBkt.Get(podID) + if idExist != nil { + err = define.ErrPodExists + if allPodsBkt.Get(idExist) == nil { + err = define.ErrCtrExists + } + return fmt.Errorf("ID \"%s\" is in use: %w", pod.ID(), err) + } + nameExist := namesBkt.Get(podName) + if nameExist != nil { + err = define.ErrPodExists + if allPodsBkt.Get(nameExist) == nil { + err = define.ErrCtrExists + } + return fmt.Errorf("name \"%s\" is in use: %w", pod.Name(), err) + } + + // We are good to add the pod + // Make a bucket for it + newPod, err := podBkt.CreateBucket(podID) + if err != nil { + return fmt.Errorf("creating bucket for pod %s: %w", pod.ID(), err) + } + + // Make a subbucket for pod containers + if _, err := newPod.CreateBucket(containersBkt); err != nil { + return fmt.Errorf("creating bucket for pod %s containers: %w", pod.ID(), err) + } + + if err := newPod.Put(configKey, podConfigJSON); err != nil { + return fmt.Errorf("storing pod %s configuration in DB: %w", pod.ID(), err) + } + + if err := newPod.Put(stateKey, podStateJSON); err != nil { + return fmt.Errorf("storing pod %s state JSON in DB: %w", pod.ID(), err) + } + + // Add us to the ID and names buckets + if err := idsBkt.Put(podID, podName); err != nil { + return fmt.Errorf("storing pod %s ID in DB: %w", pod.ID(), err) + } + if err := namesBkt.Put(podName, podID); err != nil { + return fmt.Errorf("storing pod %s name in DB: %w", pod.Name(), err) + } + if err := allPodsBkt.Put(podID, podName); err != nil { + return fmt.Errorf("storing pod %s in all pods bucket in DB: %w", pod.ID(), err) + } + + return nil + }) + if err != nil { + return err + } + + return nil +} + +// RemovePod removes the given pod from the state +// Only empty pods can be removed +func (s *BoltState) RemovePod(pod *Pod) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + podID := []byte(pod.ID()) + podName := []byte(pod.Name()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + allPodsBkt, err := getAllPodsBucket(tx) + if err != nil { + return err + } + + idsBkt, err := getIDBucket(tx) + if err != nil { + return err + } + + namesBkt, err := getNamesBucket(tx) + if err != nil { + return err + } + + // Check if the pod exists + podDB := podBkt.Bucket(podID) + if podDB == nil { + pod.valid = false + return fmt.Errorf("pod %s does not exist in DB: %w", pod.ID(), define.ErrNoSuchPod) + } + + // Check if pod is empty + // This should never be nil + // But if it is, we can assume there are no containers in the + // pod. + // So let's eject the malformed pod without error. + podCtrsBkt := podDB.Bucket(containersBkt) + if podCtrsBkt != nil { + cursor := podCtrsBkt.Cursor() + if id, _ := cursor.First(); id != nil { + return fmt.Errorf("pod %s is not empty: %w", pod.ID(), define.ErrCtrExists) + } + } + + // Pod is empty, and ready for removal + // Let's kick it out + if err := idsBkt.Delete(podID); err != nil { + return fmt.Errorf("removing pod %s ID from DB: %w", pod.ID(), err) + } + if err := namesBkt.Delete(podName); err != nil { + return fmt.Errorf("removing pod %s name (%s) from DB: %w", pod.ID(), pod.Name(), err) + } + if err := allPodsBkt.Delete(podID); err != nil { + return fmt.Errorf("removing pod %s ID from all pods bucket in DB: %w", pod.ID(), err) + } + if err := podBkt.DeleteBucket(podID); err != nil { + return fmt.Errorf("removing pod %s from DB: %w", pod.ID(), err) + } + + return nil + }) + if err != nil { + return err + } + + return nil +} + +// RemovePodContainers removes all containers in a pod +func (s *BoltState) RemovePodContainers(pod *Pod) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + podID := []byte(pod.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + ctrBkt, err := getCtrBucket(tx) + if err != nil { + return err + } + + allCtrsBkt, err := getAllCtrsBucket(tx) + if err != nil { + return err + } + + idsBkt, err := getIDBucket(tx) + if err != nil { + return err + } + + namesBkt, err := getNamesBucket(tx) + if err != nil { + return err + } + + // Check if the pod exists + podDB := podBkt.Bucket(podID) + if podDB == nil { + pod.valid = false + return fmt.Errorf("pod %s does not exist in DB: %w", pod.ID(), define.ErrNoSuchPod) + } + + podCtrsBkt := podDB.Bucket(containersBkt) + if podCtrsBkt == nil { + return fmt.Errorf("pod %s does not have a containers bucket: %w", pod.ID(), define.ErrInternal) + } + + // Traverse all containers in the pod with a cursor + // for-each has issues with data mutation + err = podCtrsBkt.ForEach(func(id, name []byte) error { + // Get the container so we can check dependencies + ctr := ctrBkt.Bucket(id) + if ctr == nil { + // This should never happen + // State is inconsistent + return fmt.Errorf("pod %s referenced nonexistent container %s: %w", pod.ID(), string(id), define.ErrNoSuchCtr) + } + ctrDeps := ctr.Bucket(dependenciesBkt) + // This should never be nil, but if it is, we're + // removing it anyways, so continue if it is + if ctrDeps != nil { + err = ctrDeps.ForEach(func(depID, _ []byte) error { + exists := podCtrsBkt.Get(depID) + if exists == nil { + return fmt.Errorf("container %s has dependency %s outside of pod %s: %w", string(id), string(depID), pod.ID(), define.ErrCtrExists) + } + return nil + }) + if err != nil { + return err + } + } + + // Dependencies are set, we're clear to remove + + if err := ctrBkt.DeleteBucket(id); err != nil { + return fmt.Errorf("deleting container %s from DB: %w", string(id), define.ErrInternal) + } + + if err := idsBkt.Delete(id); err != nil { + return fmt.Errorf("deleting container %s ID in DB: %w", string(id), err) + } + + if err := namesBkt.Delete(name); err != nil { + return fmt.Errorf("deleting container %s name in DB: %w", string(id), err) + } + + if err := allCtrsBkt.Delete(id); err != nil { + return fmt.Errorf("deleting container %s ID from all containers bucket in DB: %w", string(id), err) + } + + return nil + }) + if err != nil { + return err + } + + // Delete and recreate the bucket to empty it + if err := podDB.DeleteBucket(containersBkt); err != nil { + return fmt.Errorf("removing pod %s containers bucket: %w", pod.ID(), err) + } + if _, err := podDB.CreateBucket(containersBkt); err != nil { + return fmt.Errorf("recreating pod %s containers bucket: %w", pod.ID(), err) + } + + return nil + }) + if err != nil { + return err + } + + return nil +} + +// AddContainerToPod adds the given container to an existing pod +// The container will be added to the state and the pod +func (s *BoltState) AddContainerToPod(pod *Pod, ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if ctr.config.Pod != pod.ID() { + return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr) + } + + return s.addContainer(ctr, pod) +} + +// RemoveContainerFromPod removes a container from an existing pod +// The container will also be removed from the state +func (s *BoltState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + if ctr.config.Pod == "" { + return fmt.Errorf("container %s is not part of a pod, use RemoveContainer instead: %w", ctr.ID(), define.ErrNoSuchPod) + } + + if ctr.config.Pod != pod.ID() { + return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrInvalidArg) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + return s.removeContainer(ctr, pod, tx) + }) + return err +} + +// UpdatePod updates a pod's state from the database +func (s *BoltState) UpdatePod(pod *Pod) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + newState := new(podState) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + podID := []byte(pod.ID()) + + err = db.View(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + podDB := podBkt.Bucket(podID) + if podDB == nil { + pod.valid = false + return fmt.Errorf("no pod with ID %s found in database: %w", pod.ID(), define.ErrNoSuchPod) + } + + // Get the pod state JSON + podStateBytes := podDB.Get(stateKey) + if podStateBytes == nil { + return fmt.Errorf("pod %s is missing state key in DB: %w", pod.ID(), define.ErrInternal) + } + + if err := json.Unmarshal(podStateBytes, newState); err != nil { + return fmt.Errorf("unmarshalling pod %s state JSON: %w", pod.ID(), err) + } + + return nil + }) + if err != nil { + return err + } + + pod.state = newState + + return nil +} + +// SavePod saves a pod's state to the database +func (s *BoltState) SavePod(pod *Pod) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + stateJSON, err := json.Marshal(pod.state) + if err != nil { + return fmt.Errorf("marshalling pod %s state to JSON: %w", pod.ID(), err) + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + podID := []byte(pod.ID()) + + err = db.Update(func(tx *bolt.Tx) error { + podBkt, err := getPodBucket(tx) + if err != nil { + return err + } + + podDB := podBkt.Bucket(podID) + if podDB == nil { + pod.valid = false + return fmt.Errorf("no pod with ID %s found in database: %w", pod.ID(), define.ErrNoSuchPod) + } + + // Set the pod state JSON + if err := podDB.Put(stateKey, stateJSON); err != nil { + return fmt.Errorf("updating pod %s state in database: %w", pod.ID(), err) + } + + return nil + }) + if err != nil { + return err + } + + return nil +} + +// AllPods returns all pods present in the state +func (s *BoltState) AllPods() ([]*Pod, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + pods := []*Pod{} + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + allPodsBucket, err := getAllPodsBucket(tx) + if err != nil { + return err + } + + podBucket, err := getPodBucket(tx) + if err != nil { + return err + } + + err = allPodsBucket.ForEach(func(id, _ []byte) error { + podExists := podBucket.Bucket(id) + // This check can be removed if performance becomes an + // issue, but much less helpful errors will be produced + if podExists == nil { + return fmt.Errorf("inconsistency in state - pod %s is in all pods bucket but pod not found: %w", string(id), define.ErrInternal) + } + + pod := new(Pod) + pod.config = new(PodConfig) + pod.state = new(podState) + + if err := s.getPodFromDB(id, pod, podBucket); err != nil { + if !errors.Is(err, define.ErrNSMismatch) { + logrus.Errorf("Retrieving pod %s from the database: %v", string(id), err) + } + } else { + pods = append(pods, pod) + } + + return nil + }) + return err + }) + if err != nil { + return nil, err + } + + return pods, nil +} + +// ContainerIDIsVolume checks if the given c/storage container ID is used as +// backing storage for a volume. +func (s *BoltState) ContainerIDIsVolume(id string) (bool, error) { + if !s.valid { + return false, define.ErrDBClosed + } + + isVol := false + + db, err := s.getDBCon() + if err != nil { + return false, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + volCtrsBkt, err := getVolumeContainersBucket(tx) + if err != nil { + return err + } + + volName := volCtrsBkt.Get([]byte(id)) + if volName != nil { + isVol = true + } + + return nil + }) + return isVol, err +} diff --git a/vendor/github.com/containers/podman/v5/libpod/boltdb_state_internal.go b/vendor/github.com/containers/podman/v5/libpod/boltdb_state_internal.go new file mode 100644 index 00000000..ceaf8e39 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/boltdb_state_internal.go @@ -0,0 +1,1058 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/sirupsen/logrus" + bolt "go.etcd.io/bbolt" + "go.podman.io/storage" +) + +const ( + idRegistryName = "id-registry" + nameRegistryName = "name-registry" + ctrName = "ctr" + allCtrsName = "all-ctrs" + podName = "pod" + allPodsName = "allPods" + volName = "vol" + allVolsName = "allVolumes" + execName = "exec" + aliasesName = "aliases" + runtimeConfigName = "runtime-config" + volumeCtrsName = "volume-ctrs" + + exitCodeName = "exit-code" + exitCodeTimeStampName = "exit-code-time-stamp" + + configName = "config" + stateName = "state" + dependenciesName = "dependencies" + volCtrDependencies = "vol-dependencies" + netNSName = "netns" + containersName = "containers" + podIDName = "pod-id" + networksName = "networks" + + staticDirName = "static-dir" + tmpDirName = "tmp-dir" + runRootName = "run-root" + graphRootName = "graph-root" + graphDriverName = "graph-driver-name" + osName = "os" + volPathName = "volume-path" +) + +var ( + idRegistryBkt = []byte(idRegistryName) + nameRegistryBkt = []byte(nameRegistryName) + ctrBkt = []byte(ctrName) + allCtrsBkt = []byte(allCtrsName) + podBkt = []byte(podName) + allPodsBkt = []byte(allPodsName) + volBkt = []byte(volName) + allVolsBkt = []byte(allVolsName) + execBkt = []byte(execName) + aliasesBkt = []byte(aliasesName) + runtimeConfigBkt = []byte(runtimeConfigName) + dependenciesBkt = []byte(dependenciesName) + volDependenciesBkt = []byte(volCtrDependencies) + networksBkt = []byte(networksName) + volCtrsBkt = []byte(volumeCtrsName) + + exitCodeBkt = []byte(exitCodeName) + exitCodeTimeStampBkt = []byte(exitCodeTimeStampName) + + configKey = []byte(configName) + stateKey = []byte(stateName) + netNSKey = []byte(netNSName) + containersBkt = []byte(containersName) + podIDKey = []byte(podIDName) + + staticDirKey = []byte(staticDirName) + tmpDirKey = []byte(tmpDirName) + runRootKey = []byte(runRootName) + graphRootKey = []byte(graphRootName) + graphDriverKey = []byte(graphDriverName) + osKey = []byte(osName) + volPathKey = []byte(volPathName) +) + +// This represents a field in the runtime configuration that will be validated +// against the DB to ensure no configuration mismatches occur. +type dbConfigValidation struct { + name string // Only used for error messages + runtimeValue string + key []byte + defaultValue string + isPath bool +} + +// Check if the configuration of the database is compatible with the +// configuration of the runtime opening it +// If there is no runtime configuration loaded, load our own +func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error { + storeOpts, err := storage.DefaultStoreOptions() + if err != nil { + return err + } + + // We need to validate the following things + checks := []dbConfigValidation{ + { + "OS", + runtime.GOOS, + osKey, + runtime.GOOS, + false, + }, + { + "libpod root directory (staticdir)", + filepath.Clean(rt.config.Engine.StaticDir), + staticDirKey, + "", + true, + }, + { + "libpod temporary files directory (tmpdir)", + filepath.Clean(rt.config.Engine.TmpDir), + tmpDirKey, + "", + true, + }, + { + "storage temporary directory (runroot)", + filepath.Clean(rt.StorageConfig().RunRoot), + runRootKey, + storeOpts.RunRoot, + true, + }, + { + "storage graph root directory (graphroot)", + filepath.Clean(rt.StorageConfig().GraphRoot), + graphRootKey, + storeOpts.GraphRoot, + true, + }, + { + "storage graph driver", + rt.StorageConfig().GraphDriverName, + graphDriverKey, + storeOpts.GraphDriverName, + false, + }, + { + "volume path", + rt.config.Engine.VolumePath, + volPathKey, + "", + true, + }, + } + + // These fields were missing and will have to be recreated. + missingFields := []dbConfigValidation{} + + // Let's try and validate read-only first + err = db.View(func(tx *bolt.Tx) error { + configBkt, err := getRuntimeConfigBucket(tx) + if err != nil { + return err + } + + for _, check := range checks { + exists, err := readOnlyValidateConfig(configBkt, check) + if err != nil { + return err + } + if !exists { + missingFields = append(missingFields, check) + } + } + + return nil + }) + if err != nil { + return err + } + + if len(missingFields) == 0 { + return nil + } + + // Populate missing fields + return db.Update(func(tx *bolt.Tx) error { + configBkt, err := getRuntimeConfigBucket(tx) + if err != nil { + return err + } + + for _, missing := range missingFields { + dbValue := []byte(missing.runtimeValue) + if missing.runtimeValue == "" && missing.defaultValue != "" { + dbValue = []byte(missing.defaultValue) + } + + if err := configBkt.Put(missing.key, dbValue); err != nil { + return fmt.Errorf("updating %s in DB runtime config: %w", missing.name, err) + } + } + + return nil + }) +} + +// Attempt a read-only validation of a configuration entry in the DB against an +// element of the current runtime configuration. +// If the configuration key in question does not exist, (false, nil) will be +// returned. +// If the configuration key does exist, and matches the runtime configuration +// successfully, (true, nil) is returned. +// An error is only returned when validation fails. +// if the given runtimeValue or value retrieved from the database are empty, +// and defaultValue is not, defaultValue will be checked instead. This ensures +// that we will not fail on configuration changes in c/storage (where we may +// pass the empty string to use defaults). +func readOnlyValidateConfig(bucket *bolt.Bucket, toCheck dbConfigValidation) (bool, error) { + keyBytes := bucket.Get(toCheck.key) + if keyBytes == nil { + // False return indicates missing key + return false, nil + } + + dbValue := string(keyBytes) + ourValue := toCheck.runtimeValue + + // Tolerate symlinks when possible - most relevant for OStree systems + // and rootless containers, where we want to put containers in /home, + // which is symlinked to /var/home. + if toCheck.isPath { + if dbValue != "" { + checkedVal, err := evalSymlinksIfExists(dbValue) + if err != nil { + return false, fmt.Errorf("evaluating symlinks on DB %s path %q: %w", toCheck.name, dbValue, err) + } + dbValue = checkedVal + } + if ourValue != "" { + checkedVal, err := evalSymlinksIfExists(ourValue) + if err != nil { + return false, fmt.Errorf("evaluating symlinks on configured %s path %q: %w", toCheck.name, ourValue, err) + } + ourValue = checkedVal + } + } + + if ourValue != dbValue { + // If the runtime value is the empty string and default is not, + // check against default. + if ourValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue { + return true, nil + } + + // If the DB value is the empty string, check that the runtime + // value is the default. + if dbValue == "" && toCheck.defaultValue != "" && ourValue == toCheck.defaultValue { + return true, nil + } + + return true, fmt.Errorf("database %s %q does not match our %s %q: %w", + toCheck.name, dbValue, toCheck.name, ourValue, define.ErrDBBadConfig) + } + + return true, nil +} + +// Open a connection to the database. +// Must be paired with a `defer closeDBCon()` on the returned database, to +// ensure the state is properly unlocked +func (s *BoltState) getDBCon() (*bolt.DB, error) { + // We need an in-memory lock to avoid issues around POSIX file advisory + // locks as described in the link below: + // https://www.sqlite.org/src/artifact/c230a7a24?ln=994-1081 + s.dbLock.Lock() + + db, err := bolt.Open(s.dbPath, 0o600, nil) + if err != nil { + return nil, fmt.Errorf("opening database %s: %w", s.dbPath, err) + } + + return db, nil +} + +// deferredCloseDBCon closes the bolt db but instead of returning an +// error it logs the error. it is meant to be used within the confines +// of a defer statement only +func (s *BoltState) deferredCloseDBCon(db *bolt.DB) { + if err := s.closeDBCon(db); err != nil { + logrus.Errorf("Failed to close libpod db: %q", err) + } +} + +// Close a connection to the database. +// MUST be used in place of `db.Close()` to ensure proper unlocking of the +// state. +func (s *BoltState) closeDBCon(db *bolt.DB) error { + err := db.Close() + + s.dbLock.Unlock() + + return err +} + +func getIDBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(idRegistryBkt) + if bkt == nil { + return nil, fmt.Errorf("id registry bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getNamesBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(nameRegistryBkt) + if bkt == nil { + return nil, fmt.Errorf("name registry bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getCtrBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(ctrBkt) + if bkt == nil { + return nil, fmt.Errorf("containers bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getAllCtrsBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(allCtrsBkt) + if bkt == nil { + return nil, fmt.Errorf("all containers bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getPodBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(podBkt) + if bkt == nil { + return nil, fmt.Errorf("pods bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getAllPodsBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(allPodsBkt) + if bkt == nil { + return nil, fmt.Errorf("all pods bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getVolBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(volBkt) + if bkt == nil { + return nil, fmt.Errorf("volumes bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getAllVolsBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(allVolsBkt) + if bkt == nil { + return nil, fmt.Errorf("all volumes bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getExecBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(execBkt) + if bkt == nil { + return nil, fmt.Errorf("exec bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(runtimeConfigBkt) + if bkt == nil { + return nil, fmt.Errorf("runtime configuration bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getExitCodeBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(exitCodeBkt) + if bkt == nil { + return nil, fmt.Errorf("exit-code container bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getExitCodeTimeStampBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(exitCodeTimeStampBkt) + if bkt == nil { + return nil, fmt.Errorf("exit-code time stamp bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func getVolumeContainersBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(volCtrsBkt) + if bkt == nil { + return nil, fmt.Errorf("volume containers bucket not found in DB: %w", define.ErrDBBadConfig) + } + return bkt, nil +} + +func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error { + ctrBkt := ctrsBkt.Bucket(id) + if ctrBkt == nil { + return fmt.Errorf("container %s not found in DB: %w", string(id), define.ErrNoSuchCtr) + } + + configBytes := ctrBkt.Get(configKey) + if configBytes == nil { + return fmt.Errorf("container %s missing config key in DB: %w", string(id), define.ErrInternal) + } + + if err := json.Unmarshal(configBytes, config); err != nil { + return fmt.Errorf("unmarshalling container %s config: %w", string(id), err) + } + + // convert ports to the new format if needed + if len(config.ContainerNetworkConfig.OldPortMappings) > 0 && len(config.ContainerNetworkConfig.PortMappings) == 0 { + config.ContainerNetworkConfig.PortMappings = ocicniPortsToNetTypesPorts(config.ContainerNetworkConfig.OldPortMappings) + // keep the OldPortMappings in case an user has to downgrade podman + + // indicate that the config was modified and should be written back to the db when possible + config.rewrite = true + } + + return nil +} + +func (s *BoltState) getContainerStateDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error { + newState := new(ContainerState) + ctrToUpdate := ctrsBkt.Bucket(id) + if ctrToUpdate == nil { + ctr.valid = false + return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + newStateBytes := ctrToUpdate.Get(stateKey) + if newStateBytes == nil { + return fmt.Errorf("container %s does not have a state key in DB: %w", ctr.ID(), define.ErrInternal) + } + + if err := json.Unmarshal(newStateBytes, newState); err != nil { + return fmt.Errorf("unmarshalling container %s state: %w", ctr.ID(), err) + } + + // backwards compat, previously we used an extra bucket for the netns so try to get it from there + netNSBytes := ctrToUpdate.Get(netNSKey) + if netNSBytes != nil && newState.NetNS == "" { + newState.NetNS = string(netNSBytes) + } + + // New state compiled successfully, swap it into the current state + ctr.state = newState + return nil +} + +func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket, loadState bool) error { + if err := s.getContainerConfigFromDB(id, ctr.config, ctrsBkt); err != nil { + return err + } + + if loadState { + if err := s.getContainerStateDB(id, ctr, ctrsBkt); err != nil { + return err + } + } + + // Get the lock + lock, err := s.runtime.lockManager.RetrieveLock(ctr.config.LockID) + if err != nil { + return fmt.Errorf("retrieving lock for container %s: %w", string(id), err) + } + ctr.lock = lock + + if ctr.config.OCIRuntime == "" { + ctr.ociRuntime = s.runtime.defaultOCIRuntime + } else { + // Handle legacy containers which might use a literal path for + // their OCI runtime name. + runtimeName := ctr.config.OCIRuntime + ociRuntime, ok := s.runtime.ociRuntimes[runtimeName] + if !ok { + runtimeSet := false + + // If the path starts with a / and exists, make a new + // OCI runtime for it using the full path. + if strings.HasPrefix(runtimeName, "/") { + if stat, err := os.Stat(runtimeName); err == nil && !stat.IsDir() { + newOCIRuntime, err := newConmonOCIRuntime(runtimeName, []string{runtimeName}, s.runtime.conmonPath, s.runtime.runtimeFlags, s.runtime.config) + if err == nil { + // The runtime lock should + // protect against concurrent + // modification of the map. + ociRuntime = newOCIRuntime + s.runtime.ociRuntimes[runtimeName] = ociRuntime + runtimeSet = true + } + } + } + + if !runtimeSet { + // Use a MissingRuntime implementation + ociRuntime = getMissingRuntime(runtimeName, s.runtime) + } + } + ctr.ociRuntime = ociRuntime + } + + ctr.runtime = s.runtime + ctr.valid = true + + return nil +} + +func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error { + podDB := podBkt.Bucket(id) + if podDB == nil { + return fmt.Errorf("pod with ID %s not found: %w", string(id), define.ErrNoSuchPod) + } + + podConfigBytes := podDB.Get(configKey) + if podConfigBytes == nil { + return fmt.Errorf("pod %s is missing configuration key in DB: %w", string(id), define.ErrInternal) + } + + if err := json.Unmarshal(podConfigBytes, pod.config); err != nil { + return fmt.Errorf("unmarshalling pod %s config from DB: %w", string(id), err) + } + + // Get the lock + lock, err := s.runtime.lockManager.RetrieveLock(pod.config.LockID) + if err != nil { + return fmt.Errorf("retrieving lock for pod %s: %w", string(id), err) + } + pod.lock = lock + + pod.runtime = s.runtime + pod.valid = true + + return nil +} + +func (s *BoltState) getVolumeFromDB(name []byte, volume *Volume, volBkt *bolt.Bucket) error { + volDB := volBkt.Bucket(name) + if volDB == nil { + return fmt.Errorf("volume with name %s not found: %w", string(name), define.ErrNoSuchVolume) + } + + volConfigBytes := volDB.Get(configKey) + if volConfigBytes == nil { + return fmt.Errorf("volume %s is missing configuration key in DB: %w", string(name), define.ErrInternal) + } + + if err := json.Unmarshal(volConfigBytes, volume.config); err != nil { + return fmt.Errorf("unmarshalling volume %s config from DB: %w", string(name), err) + } + + // Volume state is allowed to be nil for legacy compatibility + volStateBytes := volDB.Get(stateKey) + if volStateBytes != nil { + if err := json.Unmarshal(volStateBytes, volume.state); err != nil { + return fmt.Errorf("unmarshalling volume %s state from DB: %w", string(name), err) + } + } + + // Need this for UsesVolumeDriver() so set it now. + volume.runtime = s.runtime + + // Retrieve volume driver + if volume.UsesVolumeDriver() { + plugin, err := s.runtime.getVolumePlugin(volume.config) + if err != nil { + // We want to fail gracefully here, to ensure that we + // can still remove volumes even if their plugin is + // missing. Otherwise, we end up with volumes that + // cannot even be retrieved from the database and will + // cause things like `volume ls` to fail. + logrus.Errorf("Volume %s uses volume plugin %s, but it cannot be accessed - some functionality may not be available: %v", volume.Name(), volume.config.Driver, err) + } else { + volume.plugin = plugin + } + } + + // Get the lock + lock, err := s.runtime.lockManager.RetrieveLock(volume.config.LockID) + if err != nil { + return fmt.Errorf("retrieving lock for volume %q: %w", string(name), err) + } + volume.lock = lock + + volume.valid = true + + return nil +} + +// Add a container to the DB +// If pod is not nil, the container is added to the pod as well +func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { + // Set the original networks to nil. We can save some space by not storing it in the config + // since we store it in a different mutable bucket anyway. + configNetworks := ctr.config.Networks + ctr.config.Networks = nil + + // JSON container structs to insert into DB + configJSON, err := json.Marshal(ctr.config) + if err != nil { + return fmt.Errorf("marshalling container %s config to JSON: %w", ctr.ID(), err) + } + stateJSON, err := json.Marshal(ctr.state) + if err != nil { + return fmt.Errorf("marshalling container %s state to JSON: %w", ctr.ID(), err) + } + dependsCtrs := ctr.Dependencies() + + ctrID := []byte(ctr.ID()) + ctrName := []byte(ctr.Name()) + + // make sure to marshal the network options before we get the db lock + networks := make(map[string][]byte, len(configNetworks)) + for net, opts := range configNetworks { + // Check that we don't have any empty network names + if net == "" { + return fmt.Errorf("network names cannot be an empty string: %w", define.ErrInvalidArg) + } + if opts.InterfaceName == "" { + return fmt.Errorf("network interface name cannot be an empty string: %w", define.ErrInvalidArg) + } + optBytes, err := json.Marshal(opts) + if err != nil { + return fmt.Errorf("marshalling network options JSON for container %s: %w", ctr.ID(), err) + } + networks[net] = optBytes + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + err = db.Update(func(tx *bolt.Tx) error { + idsBucket, err := getIDBucket(tx) + if err != nil { + return err + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + allCtrsBucket, err := getAllCtrsBucket(tx) + if err != nil { + return err + } + + volBkt, err := getVolBucket(tx) + if err != nil { + return err + } + + // If a pod was given, check if it exists + var podDB *bolt.Bucket + var podCtrs *bolt.Bucket + if pod != nil { + podBucket, err := getPodBucket(tx) + if err != nil { + return err + } + + podID := []byte(pod.ID()) + + podDB = podBucket.Bucket(podID) + if podDB == nil { + pod.valid = false + return fmt.Errorf("pod %s does not exist in database: %w", pod.ID(), define.ErrNoSuchPod) + } + podCtrs = podDB.Bucket(containersBkt) + if podCtrs == nil { + return fmt.Errorf("pod %s does not have a containers bucket: %w", pod.ID(), define.ErrInternal) + } + } + + // Check if we already have a container with the given ID and name + idExist := idsBucket.Get(ctrID) + if idExist != nil { + err = define.ErrCtrExists + if allCtrsBucket.Get(idExist) == nil { + err = define.ErrPodExists + } + return fmt.Errorf("ID \"%s\" is in use: %w", ctr.ID(), err) + } + nameExist := namesBucket.Get(ctrName) + if nameExist != nil { + err = define.ErrCtrExists + if allCtrsBucket.Get(nameExist) == nil { + err = define.ErrPodExists + } + return fmt.Errorf("name \"%s\" is in use: %w", ctr.Name(), err) + } + + // No overlapping containers + // Add the new container to the DB + if err := idsBucket.Put(ctrID, ctrName); err != nil { + return fmt.Errorf("adding container %s ID to DB: %w", ctr.ID(), err) + } + if err := namesBucket.Put(ctrName, ctrID); err != nil { + return fmt.Errorf("adding container %s name (%s) to DB: %w", ctr.ID(), ctr.Name(), err) + } + if err := allCtrsBucket.Put(ctrID, ctrName); err != nil { + return fmt.Errorf("adding container %s to all containers bucket in DB: %w", ctr.ID(), err) + } + + newCtrBkt, err := ctrBucket.CreateBucket(ctrID) + if err != nil { + return fmt.Errorf("adding container %s bucket to DB: %w", ctr.ID(), err) + } + + if err := newCtrBkt.Put(configKey, configJSON); err != nil { + return fmt.Errorf("adding container %s config to DB: %w", ctr.ID(), err) + } + if err := newCtrBkt.Put(stateKey, stateJSON); err != nil { + return fmt.Errorf("adding container %s state to DB: %w", ctr.ID(), err) + } + if pod != nil { + if err := newCtrBkt.Put(podIDKey, []byte(pod.ID())); err != nil { + return fmt.Errorf("adding container %s pod to DB: %w", ctr.ID(), err) + } + } + if len(networks) > 0 { + ctrNetworksBkt, err := newCtrBkt.CreateBucket(networksBkt) + if err != nil { + return fmt.Errorf("creating networks bucket for container %s: %w", ctr.ID(), err) + } + for network, opts := range networks { + if err := ctrNetworksBkt.Put([]byte(network), opts); err != nil { + return fmt.Errorf("adding network %q to networks bucket for container %s: %w", network, ctr.ID(), err) + } + } + } + + if _, err := newCtrBkt.CreateBucket(dependenciesBkt); err != nil { + return fmt.Errorf("creating dependencies bucket for container %s: %w", ctr.ID(), err) + } + + // Add dependencies for the container + for _, dependsCtr := range dependsCtrs { + depCtrID := []byte(dependsCtr) + + depCtrBkt := ctrBucket.Bucket(depCtrID) + if depCtrBkt == nil { + return fmt.Errorf("container %s depends on container %s, but it does not exist in the DB: %w", ctr.ID(), dependsCtr, define.ErrNoSuchCtr) + } + + depCtrPod := depCtrBkt.Get(podIDKey) + if pod != nil { + // If we're part of a pod, make sure the dependency is part of the same pod + if depCtrPod == nil { + return fmt.Errorf("container %s depends on container %s which is not in pod %s: %w", ctr.ID(), dependsCtr, pod.ID(), define.ErrInvalidArg) + } + + if string(depCtrPod) != pod.ID() { + return fmt.Errorf("container %s depends on container %s which is in a different pod (%s): %w", ctr.ID(), dependsCtr, string(depCtrPod), define.ErrInvalidArg) + } + } else if depCtrPod != nil { + // If we're not part of a pod, we cannot depend on containers in a pod + return fmt.Errorf("container %s depends on container %s which is in a pod - containers not in pods cannot depend on containers in pods: %w", ctr.ID(), dependsCtr, define.ErrInvalidArg) + } + + depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt) + if depCtrDependsBkt == nil { + return fmt.Errorf("container %s does not have a dependencies bucket: %w", dependsCtr, define.ErrInternal) + } + if err := depCtrDependsBkt.Put(ctrID, ctrName); err != nil { + return fmt.Errorf("adding ctr %s as dependency of container %s: %w", ctr.ID(), dependsCtr, err) + } + } + + // Add ctr to pod + if pod != nil && podCtrs != nil { + if err := podCtrs.Put(ctrID, ctrName); err != nil { + return fmt.Errorf("adding container %s to pod %s: %w", ctr.ID(), pod.ID(), err) + } + } + + // Add container to named volume dependencies buckets + for _, vol := range ctr.config.NamedVolumes { + volDB := volBkt.Bucket([]byte(vol.Name)) + if volDB == nil { + return fmt.Errorf("no volume with name %s found in database when adding container %s: %w", vol.Name, ctr.ID(), define.ErrNoSuchVolume) + } + + ctrDepsBkt, err := volDB.CreateBucketIfNotExists(volDependenciesBkt) + if err != nil { + return fmt.Errorf("creating volume %s dependencies bucket to add container %s: %w", vol.Name, ctr.ID(), err) + } + if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { + if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil { + return fmt.Errorf("adding container %s to volume %s dependencies: %w", ctr.ID(), vol.Name, err) + } + } + } + + return nil + }) + return err +} + +// Remove a container from the DB +// If pod is not nil, the container is treated as belonging to a pod, and +// will be removed from the pod as well +func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error { + ctrID := []byte(ctr.ID()) + ctrName := []byte(ctr.Name()) + + idsBucket, err := getIDBucket(tx) + if err != nil { + return err + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + allCtrsBucket, err := getAllCtrsBucket(tx) + if err != nil { + return err + } + + volBkt, err := getVolBucket(tx) + if err != nil { + return err + } + + // Does the pod exist? + var podDB *bolt.Bucket + if pod != nil { + podBucket, err := getPodBucket(tx) + if err != nil { + return err + } + + podID := []byte(pod.ID()) + + podDB = podBucket.Bucket(podID) + if podDB == nil { + pod.valid = false + return fmt.Errorf("no pod with ID %s found in DB: %w", pod.ID(), define.ErrNoSuchPod) + } + } + + // Does the container exist? + ctrExists := ctrBucket.Bucket(ctrID) + if ctrExists == nil { + ctr.valid = false + return fmt.Errorf("no container with ID %s found in DB: %w", ctr.ID(), define.ErrNoSuchCtr) + } + + if podDB != nil && pod != nil { + // Check if the container is in the pod, remove it if it is + podCtrs := podDB.Bucket(containersBkt) + if podCtrs == nil { + // Malformed pod + logrus.Errorf("Pod %s malformed in database, missing containers bucket!", pod.ID()) + } else { + ctrInPod := podCtrs.Get(ctrID) + if ctrInPod == nil { + return fmt.Errorf("container %s is not in pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr) + } + if err := podCtrs.Delete(ctrID); err != nil { + return fmt.Errorf("removing container %s from pod %s: %w", ctr.ID(), pod.ID(), err) + } + } + } + + // Does the container have exec sessions? + ctrExecSessionsBkt := ctrExists.Bucket(execBkt) + if ctrExecSessionsBkt != nil { + sessions := []string{} + err = ctrExecSessionsBkt.ForEach(func(id, _ []byte) error { + sessions = append(sessions, string(id)) + + return nil + }) + if err != nil { + return err + } + if len(sessions) > 0 { + return fmt.Errorf("container %s has active exec sessions: %s: %w", ctr.ID(), strings.Join(sessions, ", "), define.ErrExecSessionExists) + } + } + + // Does the container have dependencies? + ctrDepsBkt := ctrExists.Bucket(dependenciesBkt) + if ctrDepsBkt == nil { + return fmt.Errorf("container %s does not have a dependencies bucket: %w", ctr.ID(), define.ErrInternal) + } + deps := []string{} + err = ctrDepsBkt.ForEach(func(id, _ []byte) error { + deps = append(deps, string(id)) + + return nil + }) + if err != nil { + return err + } + if len(deps) != 0 { + return fmt.Errorf("container %s is a dependency of the following containers: %s: %w", ctr.ID(), strings.Join(deps, ", "), define.ErrDepExists) + } + + if err := ctrBucket.DeleteBucket(ctrID); err != nil { + return fmt.Errorf("deleting container %s from DB: %w", ctr.ID(), define.ErrInternal) + } + + if err := idsBucket.Delete(ctrID); err != nil { + return fmt.Errorf("deleting container %s ID in DB: %w", ctr.ID(), err) + } + + if err := namesBucket.Delete(ctrName); err != nil { + return fmt.Errorf("deleting container %s name in DB: %w", ctr.ID(), err) + } + if err := allCtrsBucket.Delete(ctrID); err != nil { + return fmt.Errorf("deleting container %s from all containers bucket in DB: %w", ctr.ID(), err) + } + + depCtrs := ctr.Dependencies() + + // Remove us from other container's dependencies + for _, depCtr := range depCtrs { + depCtrID := []byte(depCtr) + + depCtrBkt := ctrBucket.Bucket(depCtrID) + if depCtrBkt == nil { + // The dependent container has been removed + // This should not be possible, and means the + // state is inconsistent, but don't error + // The container with inconsistent state is the + // one being removed + continue + } + + depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt) + if depCtrDependsBkt == nil { + // This is more serious - another container in + // the state is inconsistent + // Log it, continue removing + logrus.Errorf("Container %s is missing dependencies bucket in DB", ctr.ID()) + continue + } + + if err := depCtrDependsBkt.Delete(ctrID); err != nil { + return fmt.Errorf("removing container %s as a dependency of container %s: %w", ctr.ID(), depCtr, err) + } + } + + // Remove container from named volume dependencies buckets + for _, vol := range ctr.config.NamedVolumes { + volDB := volBkt.Bucket([]byte(vol.Name)) + if volDB == nil { + // Let's assume the volume was already deleted and + // continue to remove the container + continue + } + + ctrDepsBkt := volDB.Bucket(volDependenciesBkt) + if ctrDepsBkt == nil { + return fmt.Errorf("volume %s is missing container dependencies bucket, cannot remove container %s from dependencies: %w", vol.Name, ctr.ID(), define.ErrInternal) + } + if depExists := ctrDepsBkt.Get(ctrID); depExists == nil { + if err := ctrDepsBkt.Delete(ctrID); err != nil { + return fmt.Errorf("deleting container %s dependency on volume %s: %w", ctr.ID(), vol.Name, err) + } + } + } + + return nil +} + +// lookupContainerID retrieves a container ID from the state by full or unique +// partial ID or name. +func (s *BoltState) lookupContainerID(idOrName string, ctrBucket, namesBucket *bolt.Bucket) ([]byte, error) { + // First, check if the ID given was the actual container ID + ctrExists := ctrBucket.Bucket([]byte(idOrName)) + if ctrExists != nil { + // A full container ID was given. + return []byte(idOrName), nil + } + + // Next, check if the full name was given + isPod := false + fullID := namesBucket.Get([]byte(idOrName)) + if fullID != nil { + // The name exists and maps to an ID. + // However, we are not yet certain the ID is a + // container. + ctrExists = ctrBucket.Bucket(fullID) + if ctrExists != nil { + // A container bucket matching the full ID was + // found. + return fullID, nil + } + // Don't error if we have a name match but it's not a + // container - there's a chance we have a container with + // an ID starting with those characters. + // However, so we can return a good error, note whether + // this is a pod. + isPod = true + } + + var id []byte + // We were not given a full container ID or name. + // Search for partial ID matches. + exists := false + err := ctrBucket.ForEach(func(checkID, _ []byte) error { + if strings.HasPrefix(string(checkID), idOrName) { + if exists { + return fmt.Errorf("more than one result for container ID %s: %w", idOrName, define.ErrCtrExists) + } + id = checkID + exists = true + } + + return nil + }) + + if err != nil { + return nil, err + } else if !exists { + if isPod { + return nil, fmt.Errorf("%q is a pod, not a container: %w", idOrName, define.ErrNoSuchCtr) + } + return nil, fmt.Errorf("no container with name or ID %q found: %w", idOrName, define.ErrNoSuchCtr) + } + return id, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container.go b/vendor/github.com/containers/podman/v5/libpod/container.go new file mode 100644 index 00000000..18e153e4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container.go @@ -0,0 +1,1539 @@ +//go:build !remote + +package libpod + +import ( + "bytes" + "errors" + "fmt" + "io" + "maps" + "net" + "os" + "strings" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/lock" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/pasta" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/secrets" + "go.podman.io/image/v5/manifest" + "go.podman.io/storage" + "golang.org/x/sys/unix" +) + +// CgroupfsDefaultCgroupParent is the cgroup parent for CgroupFS in libpod +const CgroupfsDefaultCgroupParent = "/libpod_parent" + +// SystemdDefaultCgroupParent is the cgroup parent for the systemd cgroup +// manager in libpod +const SystemdDefaultCgroupParent = "machine.slice" + +// SystemdDefaultRootlessCgroupParent is the cgroup parent for the systemd cgroup +// manager in libpod when running as rootless +const SystemdDefaultRootlessCgroupParent = "user.slice" + +// DefaultWaitInterval is the default interval between container status checks +// while waiting. +const DefaultWaitInterval = 250 * time.Millisecond + +// LinuxNS represents a Linux namespace +type LinuxNS int + +const ( + // InvalidNS is an invalid namespace + InvalidNS LinuxNS = iota + // IPCNS is the IPC namespace + IPCNS + // MountNS is the mount namespace + MountNS + // NetNS is the network namespace + NetNS + // PIDNS is the PID namespace + PIDNS + // UserNS is the user namespace + UserNS + // UTSNS is the UTS namespace + UTSNS + // CgroupNS is the Cgroup namespace + CgroupNS +) + +// String returns a string representation of a Linux namespace +// It is guaranteed to be the name of the namespace in /proc for valid ns types +func (ns LinuxNS) String() string { + s := [...]string{ + InvalidNS: "invalid", + IPCNS: "ipc", + MountNS: "mnt", + NetNS: "net", + PIDNS: "pid", + UserNS: "user", + UTSNS: "uts", + CgroupNS: "cgroup", + } + if ns >= 0 && int(ns) < len(s) { + return s[ns] + } + return "unknown" +} + +// Container is a single OCI container. +// All operations on a Container that access state must begin with a call to +// syncContainer(). +// There is no guarantee that state exists in a readable state before +// syncContainer() is run, and even if it does, its contents will be out of date +// and must be refreshed from the database. +// Generally, this requirement applies only to top-level functions; helpers can +// assume that their callers handled this requirement. Generally speaking, if a +// function takes the container lock and accesses any part of state, it should +// syncContainer() immediately after locking. +type Container struct { + config *ContainerConfig + + state *ContainerState + + // Batched indicates that a container has been locked as part of a + // Batch() operation + // Functions called on a batched container will not lock or sync + batched bool + + valid bool + lock lock.Locker + runtime *Runtime + ociRuntime OCIRuntime + + rootlessSlirpSyncR *os.File + rootlessSlirpSyncW *os.File + + rootlessPortSyncR *os.File + rootlessPortSyncW *os.File + + // reservedPorts contains the fds for the bound ports when using the + // bridge network mode as root. + reservedPorts []*os.File + + // perNetworkOpts should be set when you want to use special network + // options when calling network setup/teardown. This should be used for + // container restore or network reload for example. Leave this nil if + // the settings from the container config should be used. + perNetworkOpts map[string]types.PerNetworkOptions + + // This is true if a container is restored from a checkpoint. + restoreFromCheckpoint bool + + slirp4netnsSubnet *net.IPNet + pastaResult *pasta.SetupResult +} + +// ContainerState contains the current state of the container +// It is stored on disk in a tmpfs and recreated on reboot +type ContainerState struct { + // The current state of the running container + State define.ContainerStatus `json:"state"` + // The path to the JSON OCI runtime spec for this container + ConfigPath string `json:"configPath,omitempty"` + // RunDir is a per-boot directory for container content + RunDir string `json:"runDir,omitempty"` + // Mounted indicates whether the container's storage has been mounted + // for use + Mounted bool `json:"mounted,omitempty"` + // Mountpoint contains the path to the container's mounted storage as given + // by containers/storage. + Mountpoint string `json:"mountPoint,omitempty"` + // StartedTime is the time the container was started + StartedTime time.Time `json:"startedTime"` + // FinishedTime is the time the container finished executing + FinishedTime time.Time `json:"finishedTime"` + // ExitCode is the exit code returned when the container stopped + ExitCode int32 `json:"exitCode,omitempty"` + // Exited is whether the container has exited + Exited bool `json:"exited,omitempty"` + // Error holds the last known error message during start, stop, or remove + Error string `json:"error,omitempty"` + // OOMKilled indicates that the container was killed as it ran out of + // memory + OOMKilled bool `json:"oomKilled,omitempty"` + // Checkpointed indicates that the container was stopped by a checkpoint + // operation. + Checkpointed bool `json:"checkpointed,omitempty"` + // PID is the PID of a running container + PID int `json:"pid,omitempty"` + // ConmonPID is the PID of the container's conmon + ConmonPID int `json:"conmonPid,omitempty"` + // ExecSessions contains all exec sessions that are associated with this + // container. + ExecSessions map[string]*ExecSession `json:"newExecSessions,omitempty"` + // LegacyExecSessions are legacy exec sessions from older versions of + // Podman. + // These are DEPRECATED and will be removed in a future release. + LegacyExecSessions map[string]*legacyExecSession `json:"execSessions,omitempty"` + // NetNS is the path or name of the NetNS + NetNS string `json:"netns,omitempty"` + // NetworkStatus contains the network Status for all networks + // the container is attached to. Only populated if we created a network + // namespace for the container, and the network namespace is currently + // active. + // To read this field use container.getNetworkStatus() instead, this will + // take care of migrating the old DEPRECATED network status to the new format. + NetworkStatus map[string]types.StatusBlock `json:"networkStatus,omitempty"` + // BindMounts contains files that will be bind-mounted into the + // container when it is mounted. + // These include /etc/hosts and /etc/resolv.conf + // This maps the path the file will be mounted to in the container to + // the path of the file on disk outside the container + BindMounts map[string]string `json:"bindMounts,omitempty"` + // StoppedByUser indicates whether the container was stopped by an + // explicit call to the Stop() API. + StoppedByUser bool `json:"stoppedByUser,omitempty"` + // RestartPolicyMatch indicates whether the conditions for restart + // policy have been met. + RestartPolicyMatch bool `json:"restartPolicyMatch,omitempty"` + // RestartCount is how many times the container was restarted by its + // restart policy. This is NOT incremented by normal container restarts + // (only by restart policy). + RestartCount uint `json:"restartCount,omitempty"` + // StartupHCPassed indicates that the startup healthcheck has + // succeeded and the main healthcheck can begin. + StartupHCPassed bool `json:"startupHCPassed,omitempty"` + // StartupHCSuccessCount indicates the number of successes of the + // startup healthcheck. A startup HC can require more than one success + // to be marked as passed. + StartupHCSuccessCount int `json:"startupHCSuccessCount,omitempty"` + // StartupHCFailureCount indicates the number of failures of the startup + // healthcheck. The container will be restarted if this exceed a set + // number in the startup HC config. + StartupHCFailureCount int `json:"startupHCFailureCount,omitempty"` + // HCUnitName records the name of the healthcheck unit. + // Automatically generated when the healthcheck is started. + HCUnitName string `json:"hcUnitName,omitempty"` + + // ExtensionStageHooks holds hooks which will be executed by libpod + // and not delegated to the OCI runtime. + ExtensionStageHooks map[string][]spec.Hook `json:"extensionStageHooks,omitempty"` + + // NetInterfaceDescriptions describe the relationship between a CNI + // network and an interface names + NetInterfaceDescriptions ContainerNetworkDescriptions `json:"networkDescriptions,omitempty"` + + // Service indicates that container is the service container of a + // service. A service consists of one or more pods. The service + // container is started before all pods and is stopped when the last + // pod stops. The service container allows for tracking and managing + // the entire life cycle of service which may be started via + // `podman-play-kube`. + Service Service + + // Following checkpoint/restore related information is displayed + // if the container has been checkpointed or restored. + CheckpointedTime time.Time `json:"checkpointedTime"` + RestoredTime time.Time `json:"restoredTime"` + CheckpointLog string `json:"checkpointLog,omitempty"` + CheckpointPath string `json:"checkpointPath,omitempty"` + RestoreLog string `json:"restoreLog,omitempty"` + Restored bool `json:"restored,omitempty"` +} + +// ContainerNamedVolume is a named volume that will be mounted into the +// container. Each named volume is a libpod Volume present in the state. +type ContainerNamedVolume struct { + // Name is the name of the volume to mount in. + // Must resolve to a valid volume present in this Podman. + Name string `json:"volumeName"` + // Dest is the mount's destination + Dest string `json:"dest"` + // Options are fstab style mount options + Options []string `json:"options,omitempty"` + // IsAnonymous sets the named volume as anonymous even if it has a name + // This is used for emptyDir volumes from a kube yaml + IsAnonymous bool `json:"setAnonymous,omitempty"` + // SubPath determines which part of the Source will be mounted in the container + SubPath string `json:",omitempty"` +} + +// ContainerOverlayVolume is an overlay volume that will be mounted into the +// container. Each volume is a libpod Volume present in the state. +type ContainerOverlayVolume struct { + // Destination is the absolute path where the mount will be placed in the container. + Dest string `json:"dest"` + // Source specifies the source path of the mount. + Source string `json:"source,omitempty"` + // Options holds overlay volume options. + Options []string `json:"options,omitempty"` +} + +// ContainerImageVolume is a volume based on a container image. The container +// image is first mounted on the host and is then bind-mounted into the +// container. +type ContainerImageVolume struct { + // Source is the source of the image volume. The image can be referred + // to by name and by ID. + Source string `json:"source"` + // Dest is the absolute path of the mount in the container. + Dest string `json:"dest"` + // ReadWrite sets the volume writable. + ReadWrite bool `json:"rw"` + // SubPath determines which part of the image will be mounted into the container. + SubPath string `json:"subPath,omitempty"` +} + +// ContainerArtifactVolume is a volume based on a artifact. The artifact blobs will +// be bind mounted directly as files and must always be read only. +type ContainerArtifactVolume struct { + // Source is the name or digest of the artifact that should be mounted + Source string `json:"source"` + // Dest is the absolute path of the mount in the container. + // If path is a file in the container, then the artifact must consist of a single blob. + // Otherwise if it is a directory or does not exists all artifact blobs will be mounted + // into this path as files. As name the "org.opencontainers.image.title" will be used if + // available otherwise the digest is used as name. + Dest string `json:"dest"` + // Title can be used for multi blob artifacts to only mount the one specific blob that + // matches the "org.opencontainers.image.title" annotation. + // Optional. Conflicts with Digest. + Title string `json:"title"` + // Digest can be used to filter a single blob from a multi blob artifact by the given digest. + // When this option is set the file name in the container defaults to the digest even when + // the title annotation exist. + // Optional. Conflicts with Title. + Digest string `json:"digest"` + // Name is the name that should be used for the path inside the container. When a single blob + // is mounted the name is used as is. If multiple blobs are mounted then mount them as + // "-x" where x is a 0 indexed integer based on the layer order. + // Optional. + Name string `json:"name,omitempty"` +} + +// ContainerSecret is a secret that is mounted in a container +type ContainerSecret struct { + // Secret is the secret + *secrets.Secret + // UID is the UID of the secret file + UID uint32 + // GID is the GID of the secret file + GID uint32 + // Mode is the mode of the secret file + Mode uint32 + // Secret target inside container + Target string +} + +// ContainerNetworkDescriptions describes the relationship between the CNI +// network and the ethN where N is an integer +type ContainerNetworkDescriptions map[string]int + +// Config accessors +// Unlocked + +// Config returns the configuration used to create the container. +// Note that the returned config does not include the actual networks. +// Use ConfigWithNetworks() if you need them. +func (c *Container) Config() *ContainerConfig { + returnConfig := new(ContainerConfig) + if err := JSONDeepCopy(c.config, returnConfig); err != nil { + return nil + } + return returnConfig +} + +// Config returns the configuration used to create the container. +func (c *Container) ConfigWithNetworks() *ContainerConfig { + returnConfig := c.Config() + if returnConfig == nil { + return nil + } + + networks, err := c.networks() + if err != nil { + return nil + } + returnConfig.Networks = networks + + return returnConfig +} + +// ConfigNoCopy returns the configuration used by the container. +// Note that the returned value is not a copy and must hence +// only be used in a reading fashion. +func (c *Container) ConfigNoCopy() *ContainerConfig { + return c.config +} + +// DeviceHostSrc returns the user supplied device to be passed down in the pod +func (c *Container) DeviceHostSrc() []spec.LinuxDevice { + return c.config.DeviceHostSrc +} + +// Runtime returns the container's Runtime. +func (c *Container) Runtime() *Runtime { + return c.runtime +} + +// Spec returns the container's OCI runtime spec +// The spec returned is the one used to create the container. The running +// spec may differ slightly as mounts are added based on the image +func (c *Container) Spec() *spec.Spec { + returnSpec := new(spec.Spec) + if err := JSONDeepCopy(c.config.Spec, returnSpec); err != nil { + return nil + } + + return returnSpec +} + +// specFromState returns the unmarshalled json config of the container. If the +// config does not exist (e.g., because the container was never started) return +// the spec from the config. +func (c *Container) specFromState() (*spec.Spec, error) { + returnSpec := c.config.Spec + + if f, err := os.Open(c.state.ConfigPath); err == nil { + returnSpec = new(spec.Spec) + content, err := io.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("reading container config: %w", err) + } + if err := json.Unmarshal(content, &returnSpec); err != nil { + // Malformed spec, just use c.config.Spec instead + logrus.Warnf("Error unmarshalling container %s config: %v", c.ID(), err) + return c.config.Spec, nil + } + } else if !os.IsNotExist(err) { + // ignore when the file does not exist + return nil, fmt.Errorf("opening container config: %w", err) + } + + return returnSpec, nil +} + +// ID returns the container's ID +func (c *Container) ID() string { + return c.config.ID +} + +// Name returns the container's name +func (c *Container) Name() string { + return c.config.Name +} + +// PodID returns the full ID of the pod the container belongs to, or "" if it +// does not belong to a pod +func (c *Container) PodID() string { + return c.config.Pod +} + +// Namespace returns the libpod namespace the container is in. +// Namespaces are used to logically separate containers and pods in the state. +func (c *Container) Namespace() string { + return c.config.Namespace +} + +// Image returns the ID and name of the image used as the container's rootfs. +func (c *Container) Image() (string, string) { + return c.config.RootfsImageID, c.config.RootfsImageName +} + +// RawImageName returns the unprocessed and not-normalized user-specified image +// name. +func (c *Container) RawImageName() string { + return c.config.RawImageName +} + +// ShmDir returns the sources path to be mounted on /dev/shm in container +func (c *Container) ShmDir() string { + return c.config.ShmDir +} + +// ShmSize returns the size of SHM device to be mounted into the container +func (c *Container) ShmSize() int64 { + return c.config.ShmSize +} + +// StaticDir returns the directory used to store persistent container files +func (c *Container) StaticDir() string { + return c.config.StaticDir +} + +// NamedVolumes returns the container's named volumes. +// The name of each is guaranteed to point to a valid libpod Volume present in +// the state. +func (c *Container) NamedVolumes() []*ContainerNamedVolume { + volumes := []*ContainerNamedVolume{} + for _, vol := range c.config.NamedVolumes { + newVol := new(ContainerNamedVolume) + newVol.Name = vol.Name + newVol.Dest = vol.Dest + newVol.Options = vol.Options + newVol.SubPath = vol.SubPath + volumes = append(volumes, newVol) + } + + return volumes +} + +// Privileged returns whether the container is privileged +func (c *Container) Privileged() bool { + return c.config.Privileged +} + +// ProcessLabel returns the selinux ProcessLabel of the container +func (c *Container) ProcessLabel() string { + return c.config.ProcessLabel +} + +// MountLabel returns the SELinux mount label of the container +func (c *Container) MountLabel() string { + return c.config.MountLabel +} + +// Systemd returns whether the container will be running in systemd mode +func (c *Container) Systemd() bool { + if c.config.Systemd != nil { + return *c.config.Systemd + } + return false +} + +// User returns the user who the container is run as +func (c *Container) User() string { + return c.config.User +} + +// Dependencies gets the containers this container depends upon +func (c *Container) Dependencies() []string { + // Collect in a map first to remove dupes + dependsCtrs := map[string]bool{} + + // First add all namespace containers + if c.config.IPCNsCtr != "" { + dependsCtrs[c.config.IPCNsCtr] = true + } + if c.config.MountNsCtr != "" { + dependsCtrs[c.config.MountNsCtr] = true + } + if c.config.NetNsCtr != "" { + dependsCtrs[c.config.NetNsCtr] = true + } + if c.config.PIDNsCtr != "" { + dependsCtrs[c.config.PIDNsCtr] = true + } + if c.config.UserNsCtr != "" { + dependsCtrs[c.config.UserNsCtr] = true + } + if c.config.UTSNsCtr != "" { + dependsCtrs[c.config.UTSNsCtr] = true + } + if c.config.CgroupNsCtr != "" { + dependsCtrs[c.config.CgroupNsCtr] = true + } + + // Add all generic dependencies + for _, id := range c.config.Dependencies { + dependsCtrs[id] = true + } + + if len(dependsCtrs) == 0 { + return []string{} + } + + depends := make([]string, 0, len(dependsCtrs)) + for ctr := range dependsCtrs { + depends = append(depends, ctr) + } + + return depends +} + +// NewNetNS returns whether the container will create a new network namespace +func (c *Container) NewNetNS() bool { + return c.config.CreateNetNS +} + +// PortMappings returns the ports that will be mapped into a container if +// a new network namespace is created +// If NewNetNS() is false, this value is unused +func (c *Container) PortMappings() ([]types.PortMapping, error) { + // First check if the container belongs to a network namespace (like a pod) + if len(c.config.NetNsCtr) > 0 { + netNsCtr, err := c.runtime.GetContainer(c.config.NetNsCtr) + if err != nil { + return nil, fmt.Errorf("unable to look up network namespace for container %s: %w", c.ID(), err) + } + return netNsCtr.PortMappings() + } + return c.config.PortMappings, nil +} + +// DNSServers returns DNS servers that will be used in the container's +// resolv.conf +// If empty, DNS server from the host's resolv.conf will be used instead +func (c *Container) DNSServers() []net.IP { + return c.config.DNSServer +} + +// DNSSearch returns the DNS search domains that will be used in the container's +// resolv.conf +// If empty, DNS Search domains from the host's resolv.conf will be used instead +func (c *Container) DNSSearch() []string { + return c.config.DNSSearch +} + +// DNSOption returns the DNS options that will be used in the container's +// resolv.conf +// If empty, options from the host's resolv.conf will be used instead +func (c *Container) DNSOption() []string { + return c.config.DNSOption +} + +// HostsAdd returns hosts that will be added to the container's hosts file +// The host system's hosts file is used as a base, and these are appended to it +func (c *Container) HostsAdd() []string { + return c.config.HostAdd +} + +// UserVolumes returns user-added volume mounts in the container. +// These are not added to the spec, but are used during image commit and to +// trigger some OCI hooks. +func (c *Container) UserVolumes() []string { + volumes := make([]string, 0, len(c.config.UserVolumes)) + volumes = append(volumes, c.config.UserVolumes...) + return volumes +} + +// Entrypoint is the container's entrypoint. +// This is not added to the spec, but is instead used during image commit. +func (c *Container) Entrypoint() []string { + entrypoint := make([]string, 0, len(c.config.Entrypoint)) + entrypoint = append(entrypoint, c.config.Entrypoint...) + return entrypoint +} + +// Command is the container's command +// This is not added to the spec, but is instead used during image commit +func (c *Container) Command() []string { + command := make([]string, 0, len(c.config.Command)) + command = append(command, c.config.Command...) + return command +} + +// Stdin returns whether STDIN on the container will be kept open +func (c *Container) Stdin() bool { + return c.config.Stdin +} + +// Labels returns the container's labels +func (c *Container) Labels() map[string]string { + labels := make(map[string]string) + maps.Copy(labels, c.config.Labels) + return labels +} + +// StopSignal is the signal that will be used to stop the container +// If it fails to stop the container, SIGKILL will be used after a timeout +// If StopSignal is 0, the default signal of SIGTERM will be used +func (c *Container) StopSignal() uint { + return c.config.StopSignal +} + +// StopTimeout returns the container's stop timeout +// If the container's default stop signal fails to kill the container, SIGKILL +// will be used after this timeout +func (c *Container) StopTimeout() uint { + return c.config.StopTimeout +} + +// CreatedTime gets the time when the container was created +func (c *Container) CreatedTime() time.Time { + return c.config.CreatedTime +} + +// CgroupParent gets the container's Cgroup parent +func (c *Container) CgroupParent() string { + return c.config.CgroupParent +} + +// LogPath returns the path to the container's log file +// This file will only be present after Init() is called to create the container +// in the runtime +func (c *Container) LogPath() string { + return c.config.LogPath +} + +// LogTag returns the tag to the container's log file +func (c *Container) LogTag() string { + return c.config.LogTag +} + +// LogSizeMax returns the maximum size of the container's log file. +func (c *Container) LogSizeMax() int64 { + if c.config.LogSize > 0 { + return c.config.LogSize + } + return c.runtime.config.Containers.LogSizeMax +} + +// RestartPolicy returns the container's restart policy. +func (c *Container) RestartPolicy() string { + return c.config.RestartPolicy +} + +// RestartRetries returns the number of retries that will be attempted when +// using the "on-failure" restart policy +func (c *Container) RestartRetries() uint { + return c.config.RestartRetries +} + +// LogDriver returns the log driver for this container +func (c *Container) LogDriver() string { + return c.config.LogDriver +} + +// RuntimeName returns the name of the runtime +func (c *Container) RuntimeName() string { + return c.config.OCIRuntime +} + +// Runtime spec accessors +// Unlocked + +// hostname determines the container's hostname. +// If 'network' is true and the container isn't running in a +// private UTS namespoace, an empty string will be returned +// instead of the host's hostname because we never want to +// send the host's hostname to a DHCP or DNS server. +func (c *Container) hostname(network bool) string { + if c.config.UTSNsCtr != "" { + utsNsCtr, err := c.runtime.GetContainer(c.config.UTSNsCtr) + if err != nil { + // should we return an error here? + logrus.Errorf("unable to look up uts namespace for container %s: %v", c.ID(), err) + return "" + } + return utsNsCtr.Hostname() + } + + if c.config.Spec.Hostname != "" { + return c.config.Spec.Hostname + } + + // If the container is not running in a private UTS namespace, + // return the host's hostname unless 'network' is true in which + // case we return an empty string. + privateUTS := c.hasPrivateUTS() + if !privateUTS { + hostname, err := os.Hostname() + if err == nil { + if network { + return "" + } + return hostname + } + logrus.Errorf("unable to get host's hostname for container %s: %v", c.ID(), err) + return "" + } + + // If container_name_as_hostname is set in the CONTAINERS table in + // containers.conf, use a sanitized version of the container's name + // as the hostname. Since the container name must already match + // the set '[a-zA-Z0-9][a-zA-Z0-9_.-]*', we can just remove any + // underscores and limit it to 64 characters to make it a valid + // hostname. + if c.runtime.config.Containers.ContainerNameAsHostName { + sanitizedHostname := strings.ReplaceAll(c.Name(), "_", "") + if len(sanitizedHostname) <= 64 { + return sanitizedHostname + } + return sanitizedHostname[:64] + } + + // Otherwise use the container's short ID as the hostname. + if len(c.ID()) < 11 { + return c.ID() + } + return c.ID()[:12] +} + +// Hostname gets the container's hostname +func (c *Container) Hostname() string { + return c.hostname(false) +} + +// If the container isn't running in a private UTS namespace, Hostname() +// will return the host's hostname as the container's hostname. If netavark +// were to try and obtain a DHCP lease with the host's hostname in an environment +// where DDNS was active, bad things could happen. NetworkHostname() on the +// other hand, will return an empty string if the container isn't running +// in a private UTS namespace. +// +// This function should only be used to populate the ContainerHostname member +// of the common.libnetwork.types.NetworkOptions struct. +func (c *Container) NetworkHostname() string { + return c.hostname(true) +} + +// WorkingDir returns the containers working dir +func (c *Container) WorkingDir() string { + if c.config.Spec.Process != nil { + return c.config.Spec.Process.Cwd + } + return "/" +} + +// Terminal returns true if the container has a terminal +func (c *Container) Terminal() bool { + if c.config.Spec != nil && c.config.Spec.Process != nil { + return c.config.Spec.Process.Terminal + } + return false +} + +// LinuxResources return the containers Linux Resources (if any) +func (c *Container) LinuxResources() *spec.LinuxResources { + if c.config.Spec != nil && c.config.Spec.Linux != nil { + return c.config.Spec.Linux.Resources + } + return nil +} + +// Env returns the default environment variables defined for the container +func (c *Container) Env() []string { + if c.config.Spec != nil && c.config.Spec.Process != nil { + return c.config.Spec.Process.Env + } + return nil +} + +// State Accessors +// Require locking + +// State returns the current state of the container +func (c *Container) State() (define.ContainerStatus, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return define.ContainerStateUnknown, err + } + } + return c.state.State, nil +} + +func (c *Container) RestartCount() (uint, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return 0, err + } + } + return c.state.RestartCount, nil +} + +// Mounted returns whether the container is mounted and the path it is mounted +// at (if it is mounted). +// If the container is not mounted, no error is returned, and the mountpoint +// will be set to "". +func (c *Container) Mounted() (bool, string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return false, "", fmt.Errorf("updating container %s state: %w", c.ID(), err) + } + } + // We cannot directly return c.state.Mountpoint as it is not guaranteed + // to be set if the container is mounted, only if the container has been + // prepared with c.prepare(). + // Instead, let's call into c/storage + mountedTimes, err := c.runtime.storageService.MountedContainerImage(c.ID()) + if err != nil { + return false, "", err + } + + if mountedTimes > 0 { + mountPoint, err := c.runtime.storageService.GetMountpoint(c.ID()) + if err != nil { + return false, "", err + } + + return true, mountPoint, nil + } + + return false, "", nil +} + +// StartedTime is the time the container was started +func (c *Container) StartedTime() (time.Time, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return time.Time{}, fmt.Errorf("updating container %s state: %w", c.ID(), err) + } + } + return c.state.StartedTime, nil +} + +// FinishedTime is the time the container was stopped +func (c *Container) FinishedTime() (time.Time, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return time.Time{}, fmt.Errorf("updating container %s state: %w", c.ID(), err) + } + } + return c.state.FinishedTime, nil +} + +// ExitCode returns the exit code of the container as +// an int32, and whether the container has exited. +// If the container has not exited, exit code will always be 0. +// If the container restarts, the exit code is reset to 0. +func (c *Container) ExitCode() (int32, bool, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return 0, false, fmt.Errorf("updating container %s state: %w", c.ID(), err) + } + } + return c.state.ExitCode, c.state.Exited, nil +} + +// OOMKilled returns whether the container was killed by an OOM condition +func (c *Container) OOMKilled() (bool, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return false, fmt.Errorf("updating container %s state: %w", c.ID(), err) + } + } + return c.state.OOMKilled, nil +} + +// PID returns the PID of the container. +// If the container is not running, a pid of 0 will be returned. No error will +// occur. +func (c *Container) PID() (int, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return -1, err + } + } + + return c.state.PID, nil +} + +// ConmonPID Returns the PID of the container's conmon process. +// If the container is not running, a PID of 0 will be returned. No error will +// occur. +func (c *Container) ConmonPID() (int, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return -1, err + } + } + + return c.state.ConmonPID, nil +} + +// ExecSessions retrieves active exec sessions running in the container +func (c *Container) ExecSessions() ([]string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + ids := make([]string, 0, len(c.state.ExecSessions)) + for id := range c.state.ExecSessions { + ids = append(ids, id) + } + + return ids, nil +} + +// execSessionNoCopy returns the associated exec session to id. +// Note that the session is not a deep copy. +func (c *Container) execSessionNoCopy(id string) (*ExecSession, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + session, ok := c.state.ExecSessions[id] + if !ok { + return nil, fmt.Errorf("no exec session with ID %s found in container %s: %w", id, c.ID(), define.ErrNoSuchExecSession) + } + + // make sure to update the exec session if needed #18424 + alive, err := c.ociRuntime.ExecUpdateStatus(c, id) + if err != nil { + return nil, err + } + if !alive { + if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil { + return nil, err + } + } + + return session, nil +} + +// ExecSession retrieves detailed information on a single active exec session in +// a container +func (c *Container) ExecSession(id string) (*ExecSession, error) { + session, err := c.execSessionNoCopy(id) + if err != nil { + return nil, err + } + + returnSession := new(ExecSession) + if err := JSONDeepCopy(session, returnSession); err != nil { + return nil, fmt.Errorf("copying contents of container %s exec session %s: %w", c.ID(), session.ID(), err) + } + + return returnSession, nil +} + +// BindMounts retrieves bind mounts that were created by libpod and will be +// added to the container +// All these mounts except /dev/shm are ignored if a mount in the given spec has +// the same destination +// These mounts include /etc/resolv.conf, /etc/hosts, and /etc/hostname +// The return is formatted as a map from destination (mountpoint in the +// container) to source (path of the file that will be mounted into the +// container) +// If the container has not been started yet, an empty map will be returned, as +// the files in question are only created when the container is started. +func (c *Container) BindMounts() (map[string]string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + newMap := make(map[string]string, len(c.state.BindMounts)) + + maps.Copy(newMap, c.state.BindMounts) + + return newMap, nil +} + +// StoppedByUser returns whether the container was last stopped by an explicit +// call to the Stop() API, or whether it exited naturally. +func (c *Container) StoppedByUser() (bool, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return false, err + } + } + + return c.state.StoppedByUser, nil +} + +// StartupHCPassed returns whether the container's startup healthcheck passed. +func (c *Container) StartupHCPassed() (bool, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return false, err + } + } + + return c.state.StartupHCPassed, nil +} + +// Misc Accessors +// Most will require locking + +// NamespacePath returns the path of one of the container's namespaces +// If the container is not running, an error will be returned +func (c *Container) NamespacePath(linuxNS LinuxNS) (string, error) { //nolint:interfacer + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return "", fmt.Errorf("updating container %s state: %w", c.ID(), err) + } + } + + return c.namespacePath(linuxNS) +} + +// namespacePath returns the path of one of the container's namespaces +// If the container is not running, an error will be returned +func (c *Container) namespacePath(linuxNS LinuxNS) (string, error) { //nolint:interfacer + if c.state.State != define.ContainerStateRunning && c.state.State != define.ContainerStatePaused { + return "", fmt.Errorf("cannot get namespace path unless container %s is running: %w", c.ID(), define.ErrCtrStopped) + } + + if linuxNS == InvalidNS { + return "", fmt.Errorf("invalid namespace requested from container %s: %w", c.ID(), define.ErrInvalidArg) + } + + return fmt.Sprintf("/proc/%d/ns/%s", c.state.PID, linuxNS.String()), nil +} + +// CgroupManager returns the cgroup manager used by the given container. +func (c *Container) CgroupManager() string { + cgroupManager := c.config.CgroupManager + if cgroupManager == "" { + cgroupManager = c.runtime.config.Engine.CgroupManager + } + return cgroupManager +} + +// CgroupPath returns a cgroups "path" for the given container. +// Note that the container must be running. Otherwise, an error +// is returned. +func (c *Container) CgroupPath() (string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return "", fmt.Errorf("updating container %s state: %w", c.ID(), err) + } + } + return c.cGroupPath() +} + +// cGroupPath returns a cgroups "path" for the given container. +// Note that the container must be running. Otherwise, an error +// is returned. +// NOTE: only call this when owning the container's lock. +func (c *Container) cGroupPath() (string, error) { + if c.config.NoCgroups || c.config.CgroupsMode == "disabled" { + return "", fmt.Errorf("this container is not creating cgroups: %w", define.ErrNoCgroups) + } + if c.state.State != define.ContainerStateRunning && c.state.State != define.ContainerStatePaused { + return "", fmt.Errorf("cannot get cgroup path unless container %s is running: %w", c.ID(), define.ErrCtrStopped) + } + + // Read /proc/{PID}/cgroup and find the *longest* cgroup entry. That's + // needed to account for hacks in cgroups v1, where each line in the + // file could potentially point to a cgroup. The longest one, however, + // is the libpod-specific one we're looking for. + // + // See #8397 on the need for the longest-path look up. + // + // And another workaround for containers running systemd as the payload. + // containers running systemd moves themselves into a child subgroup of + // the named systemd cgroup hierarchy. Ignore any named cgroups during + // the lookup. + // See #10602 for more details. + procPath := fmt.Sprintf("/proc/%d/cgroup", c.state.PID) + lines, err := os.ReadFile(procPath) + if err != nil { + // If the file doesn't exist, it means the container could have been terminated + // so report it. Also check for ESRCH, which means the container could have been + // terminated after the file under /proc was opened but before it was read. + if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.ESRCH) { + return "", fmt.Errorf("cannot get cgroup path unless container %s is running: %w", c.ID(), define.ErrCtrStopped) + } + return "", err + } + + var cgroupPath string + for line := range bytes.SplitSeq(lines, []byte("\n")) { + // skip last empty line + if len(line) == 0 { + continue + } + // cgroups(7) nails it down to three fields with the 3rd + // pointing to the cgroup's path which works both on v1 and v2. + fields := bytes.Split(line, []byte(":")) + if len(fields) != 3 { + logrus.Debugf("Error parsing cgroup: expected 3 fields but got %d: %s", len(fields), procPath) + continue + } + // Ignore named cgroups like name=systemd. + if bytes.Contains(fields[1], []byte("=")) { + continue + } + path := string(fields[2]) + if len(path) > len(cgroupPath) { + cgroupPath = path + } + } + + if len(cgroupPath) == 0 { + return "", fmt.Errorf("could not find any cgroup in %q", procPath) + } + + cgroupManager := c.CgroupManager() + switch { + case c.config.CgroupsMode == cgroupSplit: + name := fmt.Sprintf("/libpod-payload-%s/", c.ID()) + if index := strings.LastIndex(cgroupPath, name); index >= 0 { + return cgroupPath[:index+len(name)-1], nil + } + case cgroupManager == config.CgroupfsCgroupsManager: + name := fmt.Sprintf("/libpod-%s/", c.ID()) + if index := strings.LastIndex(cgroupPath, name); index >= 0 { + return cgroupPath[:index+len(name)-1], nil + } + case cgroupManager == config.SystemdCgroupsManager: + // When running under systemd, try to detect the scope that was requested + // to be created. It improves the heuristic since we report the first + // cgroup that was created instead of the cgroup where PID 1 might have + // moved to. + name := fmt.Sprintf("/libpod-%s.scope/", c.ID()) + if index := strings.LastIndex(cgroupPath, name); index >= 0 { + return cgroupPath[:index+len(name)-1], nil + } + } + + return cgroupPath, nil +} + +// RootFsSize returns the root FS size of the container +func (c *Container) RootFsSize() (int64, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return -1, fmt.Errorf("updating container %s state: %w", c.ID(), err) + } + } + return c.rootFsSize() +} + +// RWSize returns the rw size of the container +func (c *Container) RWSize() (int64, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return -1, fmt.Errorf("updating container %s state: %w", c.ID(), err) + } + } + return c.rwSize() +} + +// IDMappings returns the UID/GID mapping used for the container +func (c *Container) IDMappings() storage.IDMappingOptions { + return c.config.IDMappings +} + +// RootUID returns the root user mapping from container +func (c *Container) RootUID() int { + if len(c.config.IDMappings.UIDMap) == 1 && c.config.IDMappings.UIDMap[0].Size == 1 { + return c.config.IDMappings.UIDMap[0].HostID + } + for _, uidmap := range c.config.IDMappings.UIDMap { + if uidmap.ContainerID == 0 { + return uidmap.HostID + } + } + return 0 +} + +// RootGID returns the root user mapping from container +func (c *Container) RootGID() int { + if len(c.config.IDMappings.GIDMap) == 1 && c.config.IDMappings.GIDMap[0].Size == 1 { + return c.config.IDMappings.GIDMap[0].HostID + } + for _, gidmap := range c.config.IDMappings.GIDMap { + if gidmap.ContainerID == 0 { + return gidmap.HostID + } + } + return 0 +} + +// IsInfra returns whether the container is an infra container +func (c *Container) IsInfra() bool { + return c.config.IsInfra +} + +// IsDefaultInfra returns whether the container is a default infra container generated directly by podman +func (c *Container) IsDefaultInfra() bool { + return c.config.IsDefaultInfra +} + +// IsInitCtr returns whether the container is an init container +func (c *Container) IsInitCtr() bool { + return len(c.config.InitContainerType) > 0 +} + +// IsReadOnly returns whether the container is running in read-only mode +func (c *Container) IsReadOnly() bool { + return c.config.Spec.Root.Readonly +} + +// NetworkDisabled returns whether the container is running with a disabled network +func (c *Container) NetworkDisabled() (bool, error) { + if c.config.NetNsCtr != "" { + container, err := c.runtime.state.Container(c.config.NetNsCtr) + if err != nil { + return false, err + } + return container.NetworkDisabled() + } + return networkDisabled(c) +} + +func (c *Container) HostNetwork() bool { + if c.config.CreateNetNS || c.config.NetNsCtr != "" { + return false + } + if c.config.Spec.Linux != nil { + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + return false + } + } + } + return true +} + +// HasHealthCheck returns bool as to whether there is a health check +// defined for the container +func (c *Container) HasHealthCheck() bool { + return c.config.HealthCheckConfig != nil +} + +// HealthCheckConfig returns the command and timing attributes of the health check +func (c *Container) HealthCheckConfig() *manifest.Schema2HealthConfig { + return c.config.HealthCheckConfig +} + +func (c *Container) HealthCheckLogDestination() string { + if c.config.HealthLogDestination == nil { + return define.DefaultHealthCheckLocalDestination + } + return *c.config.HealthLogDestination +} + +func (c *Container) HealthCheckMaxLogCount() uint { + if c.config.HealthMaxLogCount == nil { + return define.DefaultHealthMaxLogCount + } + return *c.config.HealthMaxLogCount +} + +func (c *Container) HealthCheckMaxLogSize() uint { + if c.config.HealthMaxLogSize == nil { + return define.DefaultHealthMaxLogSize + } + return *c.config.HealthMaxLogSize +} + +// AutoRemove indicates whether the container will be removed after it is executed +func (c *Container) AutoRemove() bool { + spec := c.config.Spec + if spec.Annotations == nil { + return false + } + return spec.Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue +} + +// AutoRemoveImage indicates that the container will automatically remove the +// image it is using after it exits and is removed. +// Only allowed if AutoRemove is true. +func (c *Container) AutoRemoveImage() bool { + spec := c.config.Spec + if spec.Annotations == nil { + return false + } + return spec.Annotations[define.InspectAnnotationAutoremoveImage] == define.InspectResponseTrue +} + +// Timezone returns the timezone configured inside the container. +// Local means it has the same timezone as the host machine +func (c *Container) Timezone() string { + return c.config.Timezone +} + +// Umask returns the Umask bits configured inside the container. +func (c *Container) Umask() string { + return c.config.Umask +} + +// Secrets return the secrets in the container +func (c *Container) Secrets() []*ContainerSecret { + return c.config.Secrets +} + +// Networks gets all the networks this container is connected to. +// Please do NOT use ctr.config.Networks, as this can be changed from those +// values at runtime via network connect and disconnect. +// Returned array of network names or error. +func (c *Container) Networks() ([]string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + networks, err := c.networks() + if err != nil { + return nil, err + } + + names := make([]string, 0, len(networks)) + + for name := range networks { + names = append(names, name) + } + + return names, nil +} + +// NetworkMode gets the configured network mode for the container. +// Get actual value from the database +func (c *Container) NetworkMode() string { + networkMode := "" + ctrSpec := c.config.Spec + + switch { + case c.config.CreateNetNS: + // We actually store the network + // mode for Slirp and Bridge, so + // we can just use that + networkMode = string(c.config.NetMode) + case c.config.NetNsCtr != "": + networkMode = fmt.Sprintf("container:%s", c.config.NetNsCtr) + default: + // Find the spec's network namespace. + // If there is none, it's host networking. + // If there is one and it has a path, it's "ns:". + foundNetNS := false + for _, ns := range ctrSpec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + foundNetNS = true + if ns.Path != "" { + networkMode = fmt.Sprintf("ns:%s", ns.Path) + } else { + // We're making a network ns, but not + // configuring with Slirp or CNI. That + // means it's --net=none + networkMode = "none" + } + break + } + } + if !foundNetNS { + networkMode = "host" + } + } + return networkMode +} + +// Unlocked accessor for networks +func (c *Container) networks() (map[string]types.PerNetworkOptions, error) { + return c.runtime.state.GetNetworks(c) +} + +// getInterfaceByName returns a formatted interface name for a given +// network along with a bool as to whether the network existed +func (d ContainerNetworkDescriptions) getInterfaceByName(networkName string) (string, bool) { + val, exists := d[networkName] + if !exists { + return "", exists + } + return fmt.Sprintf("eth%d", val), exists +} + +// GetNetworkStatus returns the current network status for this container. +// This returns a map without deep copying which means this should only ever +// be used as read only access, do not modify this status. +func (c *Container) GetNetworkStatus() (map[string]types.StatusBlock, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + return c.getNetworkStatus(), nil +} + +// getNetworkStatus get the current network status from the state. This function +// should be used instead of reading c.state.NetworkStatus directly. +func (c *Container) getNetworkStatus() map[string]types.StatusBlock { + return c.state.NetworkStatus +} + +func (c *Container) NamespaceMode(ns spec.LinuxNamespaceType, ctrSpec *spec.Spec) string { + switch ns { + case spec.UTSNamespace: + if c.config.UTSNsCtr != "" { + return fmt.Sprintf("container:%s", c.config.UTSNsCtr) + } + case spec.CgroupNamespace: + if c.config.CgroupNsCtr != "" { + return fmt.Sprintf("container:%s", c.config.CgroupNsCtr) + } + case spec.IPCNamespace: + if c.config.IPCNsCtr != "" { + return fmt.Sprintf("container:%s", c.config.IPCNsCtr) + } + case spec.PIDNamespace: + if c.config.PIDNsCtr != "" { + return fmt.Sprintf("container:%s", c.config.PIDNsCtr) + } + case spec.UserNamespace: + if c.config.UserNsCtr != "" { + return fmt.Sprintf("container:%s", c.config.UserNsCtr) + } + case spec.NetworkNamespace: + if c.config.NetNsCtr != "" { + return fmt.Sprintf("container:%s", c.config.NetNsCtr) + } + case spec.MountNamespace: + if c.config.MountNsCtr != "" { + return fmt.Sprintf("container:%s", c.config.MountNsCtr) + } + } + + if ctrSpec.Linux != nil { + // Locate the spec's given namespace. + // If there is none, it's namespace=host. + // If there is one and it has a path, it's "ns:". + // If there is no path, it's default - the empty string. + for _, availableNS := range ctrSpec.Linux.Namespaces { + if availableNS.Type == ns { + if availableNS.Path != "" { + return fmt.Sprintf("ns:%s", availableNS.Path) + } + return "private" + } + } + } + return "host" +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_api.go b/vendor/github.com/containers/podman/v5/libpod/container_api.go new file mode 100644 index 00000000..f98b6480 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_api.go @@ -0,0 +1,1160 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/resize" + "go.podman.io/storage/pkg/archive" + "golang.org/x/sys/unix" +) + +// Init creates a container in the OCI runtime, moving a container from +// ContainerStateConfigured, ContainerStateStopped, or ContainerStateExited to +// ContainerStateCreated. Once in Created state, Conmon will be running, which +// allows the container to be attached to. The container can subsequently +// transition to ContainerStateRunning via Start(), or be transitioned back to +// ContainerStateConfigured by Cleanup() (which will stop conmon and unmount the +// container). +// Init requires that all dependency containers be started (e.g. pod infra +// containers). The `recursive` parameter will, if set to true, start these +// dependency containers before initializing this container. +func (c *Container) Init(ctx context.Context, recursive bool) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + return c.initUnlocked(ctx, recursive) +} + +func (c *Container) initUnlocked(ctx context.Context, recursive bool) error { + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateStopped, define.ContainerStateExited) { + return fmt.Errorf("container %s has already been created in runtime: %w", c.ID(), define.ErrCtrStateInvalid) + } + + if !recursive { + if err := c.checkDependenciesAndHandleError(); err != nil { + return err + } + } else { + if err := c.startDependencies(ctx); err != nil { + return err + } + } + + if err := c.prepare(); err != nil { + if err2 := c.cleanup(ctx); err2 != nil { + logrus.Errorf("Cleaning up container %s: %v", c.ID(), err2) + } + return err + } + + if c.state.State == define.ContainerStateStopped { + // Reinitialize the container + return c.reinit(ctx, false) + } + + // Initialize the container for the first time + return c.init(ctx, false) +} + +// Start starts the given container. +// Start will accept container in ContainerStateConfigured, +// ContainerStateCreated, ContainerStateStopped, and ContainerStateExited, and +// transition them to ContainerStateRunning (all containers not in +// ContainerStateCreated will make an intermediate stop there via the Init API). +// Once in ContainerStateRunning, the container can be transitioned to +// ContainerStatePaused via Pause(), or to ContainerStateStopped by the process +// stopping (either due to exit, or being forced to stop by the Kill or Stop API +// calls). +// Start requires that all dependency containers (e.g. pod infra containers) are +// running before starting the container. The recursive parameter, if set, will start all +// dependencies before starting this container. +func (c *Container) Start(ctx context.Context, recursive bool) error { + // Have to lock the pod the container is a part of. + // This prevents running `podman start` at the same time a + // `podman pod stop` is running, which could lead to weird races. + // Pod locks come before container locks, so do this first. + if c.config.Pod != "" { + // If we get an error, the pod was probably removed. + // So we get an expected ErrCtrRemoved instead of ErrPodRemoved, + // just ignore this and move on to syncing the container. + pod, _ := c.runtime.state.Pod(c.config.Pod) + if pod != nil { + pod.lock.Lock() + defer pod.lock.Unlock() + } + } + + return c.startNoPodLock(ctx, recursive) +} + +// Update updates the given container. +// Either resource limits, restart policies, or HealthCheck configuration can be updated. +// Either resources, restartPolicy or changedHealthCheckConfiguration must not be nil in the updateOptions. +// If restartRetries is not nil, restartPolicy must be set and must be "on-failure". +// Nil values of changedHealthCheckConfiguration are not updated. +func (c *Container) Update(updateOptions *entities.ContainerUpdateOptions) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.ensureState(define.ContainerStateRemoving) { + return fmt.Errorf("container %s is being removed, cannot update: %w", c.ID(), define.ErrCtrStateInvalid) + } + + healthCheckConfig, changedHealthCheck, err := GetNewHealthCheckConfig(&HealthCheckConfig{Schema2HealthConfig: c.HealthCheckConfig()}, *updateOptions.ChangedHealthCheckConfiguration) + if err != nil { + return err + } + if changedHealthCheck { + if err := c.updateHealthCheck( + healthCheckConfig, + &HealthCheckConfig{Schema2HealthConfig: c.config.HealthCheckConfig}, + ); err != nil { + return err + } + } + + startupHealthCheckConfig, changedStartupHealthCheck, err := GetNewHealthCheckConfig(&StartupHealthCheckConfig{StartupHealthCheck: c.Config().StartupHealthCheckConfig}, *updateOptions.ChangedHealthCheckConfiguration) + if err != nil { + return err + } + if changedStartupHealthCheck { + if err := c.updateHealthCheck( + startupHealthCheckConfig, + &StartupHealthCheckConfig{StartupHealthCheck: c.config.StartupHealthCheckConfig}, + ); err != nil { + return err + } + } + + globalHealthCheckOptions, err := updateOptions.ChangedHealthCheckConfiguration.GetNewGlobalHealthCheck() + if err != nil { + return err + } + if err := c.updateGlobalHealthCheckConfiguration(globalHealthCheckOptions); err != nil { + return err + } + + defer c.newContainerEvent(events.Update) + return c.update(updateOptions) +} + +// Attach to a container. +// The last parameter "start" can be used to also start the container. +// This will then Start and Attach APIs, ensuring proper +// ordering of the two such that no output from the container is lost (e.g. the +// Attach call occurs before Start). +func (c *Container) Attach(ctx context.Context, streams *define.AttachStreams, keys string, resize <-chan resize.TerminalSize, start bool) (retChan <-chan error, finalErr error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + // defer's are executed LIFO so we are locked here + // as long as we call this after the defer unlock() + defer func() { + if finalErr != nil { + if err := saveContainerError(c, finalErr); err != nil { + logrus.Debug(err) + } + } + }() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + if c.state.State != define.ContainerStateRunning { + if !start { + return nil, errors.New("you can only attach to running containers") + } + + if err := c.prepareToStart(ctx, true); err != nil { + return nil, err + } + } + + if !start { + // A bit awkward, technically passthrough never supports attach. We only pretend + // it does as we leak the stdio fds down into the container but that of course only + // works if we are the process that started conmon with the right fds. + if c.LogDriver() == define.PassthroughLogging { + return nil, fmt.Errorf("this container is using the 'passthrough' log driver, cannot attach: %w", define.ErrNoLogs) + } else if c.LogDriver() == define.PassthroughTTYLogging { + return nil, fmt.Errorf("this container is using the 'passthrough-tty' log driver, cannot attach: %w", define.ErrNoLogs) + } + } + + attachChan := make(chan error) + + // We need to ensure that we don't return until start() fired in attach. + // Use a channel to sync + startedChan := make(chan bool) + + // Attach to the container before starting it + go func() { + // Start resizing + if c.LogDriver() != define.PassthroughLogging && c.LogDriver() != define.PassthroughTTYLogging { + registerResizeFunc(resize, c.bundlePath()) + } + + opts := new(AttachOptions) + opts.Streams = streams + opts.DetachKeys = &keys + opts.Start = start + opts.Started = startedChan + + // attach and start the container on a different thread. waitForHealthy must + // be done later, as it requires to run on the same thread that holds the lock + // for the container. + if err := c.ociRuntime.Attach(c, opts); err != nil { + attachChan <- err + } + close(attachChan) + }() + + select { + case err := <-attachChan: + return nil, err + case <-startedChan: + c.newContainerEvent(events.Attach) + } + + if start { + if err := c.waitForHealthy(ctx); err != nil { + return nil, err + } + } + + return attachChan, nil +} + +// RestartWithTimeout restarts a running container and takes a given timeout in uint +func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if err := c.checkDependenciesAndHandleError(); err != nil { + return err + } + + return c.restartWithTimeout(ctx, timeout) +} + +// Stop uses the container's stop signal (or SIGTERM if no signal was specified) +// to stop the container, and if it has not stopped after container's stop +// timeout, SIGKILL is used to attempt to forcibly stop the container +// Default stop timeout is 10 seconds, but can be overridden when the container +// is created +func (c *Container) Stop() error { + // Stop with the container's given timeout + return c.StopWithTimeout(c.config.StopTimeout) +} + +// StopWithTimeout is a version of Stop that allows a timeout to be specified +// manually. If timeout is 0, SIGKILL will be used immediately to kill the +// container. +func (c *Container) StopWithTimeout(timeout uint) (finalErr error) { + // Have to lock the pod the container is a part of. + // This prevents running `podman stop` at the same time a + // `podman pod start` is running, which could lead to weird races. + // Pod locks come before container locks, so do this first. + if c.config.Pod != "" { + // If we get an error, the pod was probably removed. + // So we get an expected ErrCtrRemoved instead of ErrPodRemoved, + // just ignore this and move on to syncing the container. + pod, _ := c.runtime.state.Pod(c.config.Pod) + if pod != nil { + pod.lock.Lock() + defer pod.lock.Unlock() + } + } + + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + // defer's are executed LIFO so we are locked here + // as long as we call this after the defer unlock() + defer func() { + // The podman stop command is idempotent while the internal function here is not. + // As such we do not want to log these errors in the state because they are not + // actually user visible errors. + if finalErr != nil && !errors.Is(finalErr, define.ErrCtrStopped) && + !errors.Is(finalErr, define.ErrCtrStateInvalid) { + if err := saveContainerError(c, finalErr); err != nil { + logrus.Debug(err) + } + } + }() + + if err := c.syncContainer(); err != nil { + return err + } + } + + return c.stop(timeout) +} + +// Kill sends a signal to a container +func (c *Container) Kill(signal uint) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + switch c.state.State { + case define.ContainerStateRunning, define.ContainerStateStopping, define.ContainerStatePaused: + // Note that killing containers in "stopping" state is okay. + // In that state, the Podman is waiting for the runtime to + // stop the container and if that is taking too long, a user + // may have decided to kill the container after all. + default: + return fmt.Errorf("can only kill running containers. %s is in state %s: %w", c.ID(), c.state.State.String(), define.ErrCtrStateInvalid) + } + + // Hardcode all = false, we only use all when removing. + if err := c.ociRuntime.KillContainer(c, signal, false); err != nil { + return err + } + + c.state.StoppedByUser = true + + c.newContainerEvent(events.Kill) + + // Make sure to wait for the container to exit in case of SIGKILL. + if signal == uint(unix.SIGKILL) { + return c.waitForConmonToExitAndSave() + } + + return c.save() +} + +// HTTPAttach forwards an attach session over a hijacked HTTP session. +// HTTPAttach will consume and close the included httpCon, which is expected to +// be sourced from a hijacked HTTP connection. +// The cancel channel is optional, and can be used to asynchronously cancel the +// attach session. +// The streams variable is only supported if the container was not a terminal, +// and allows specifying which of the container's standard streams will be +// forwarded to the client. +// This function returns when the attach finishes. It does not hold the lock for +// the duration of its runtime, only using it at the beginning to verify state. +// The streamLogs parameter indicates that all the container's logs until present +// will be streamed at the beginning of the attach. +// The streamAttach parameter indicates that the attach itself will be streamed +// over the socket; if this is not set, but streamLogs is, only the logs will be +// sent. +// At least one of streamAttach and streamLogs must be set. +func (c *Container) HTTPAttach(r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, streamAttach, streamLogs bool, hijackDone chan<- bool) error { + // Ensure we don't leak a goroutine if we exit before hijack completes. + defer func() { + close(hijackDone) + }() + + locked := false + if !c.batched { + locked = true + c.lock.Lock() + defer func() { + if locked { + c.lock.Unlock() + } + }() + if err := c.syncContainer(); err != nil { + return err + } + } + // For Docker compatibility, we need to re-initialize containers in these states. + if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited, define.ContainerStateStopped) { + if err := c.initUnlocked(r.Context(), c.config.Pod != ""); err != nil { + return fmt.Errorf("preparing container %s for attach: %w", c.ID(), err) + } + } else if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { + return fmt.Errorf("can only attach to created or running containers - currently in state %s: %w", c.state.State.String(), define.ErrCtrStateInvalid) + } + + if !streamAttach && !streamLogs { + return fmt.Errorf("must specify at least one of stream or logs: %w", define.ErrInvalidArg) + } + + // We are NOT holding the lock for the duration of the function. + locked = false + c.lock.Unlock() + + logrus.Infof("Performing HTTP Hijack attach to container %s", c.ID()) + + c.newContainerEvent(events.Attach) + return c.ociRuntime.HTTPAttach(c, r, w, streams, detachKeys, cancel, hijackDone, streamAttach, streamLogs) +} + +// AttachResize resizes the container's terminal, which is displayed by Attach +// and HTTPAttach. +func (c *Container) AttachResize(newSize resize.TerminalSize) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { + return fmt.Errorf("can only resize created or running containers: %w", define.ErrCtrStateInvalid) + } + + logrus.Infof("Resizing TTY of container %s", c.ID()) + + return c.ociRuntime.AttachResize(c, newSize) +} + +// Mount mounts a container's filesystem on the host +// The path where the container has been mounted is returned +func (c *Container) Mount() (string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return "", err + } + } + + defer c.newContainerEvent(events.Mount) + return c.mount() +} + +// Unmount unmounts a container's filesystem on the host +func (c *Container) Unmount(force bool) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + if c.state.Mounted { + mounted, err := c.runtime.storageService.MountedContainerImage(c.ID()) + if err != nil { + return fmt.Errorf("can't determine how many times %s is mounted, refusing to unmount: %w", c.ID(), err) + } + if mounted == 1 { + if c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { + return fmt.Errorf("cannot unmount storage for container %s as it is running or paused: %w", c.ID(), define.ErrCtrStateInvalid) + } + execSessions, err := c.getActiveExecSessions() + if err != nil { + return err + } + if len(execSessions) != 0 { + return fmt.Errorf("container %s has active exec sessions, refusing to unmount: %w", c.ID(), define.ErrCtrStateInvalid) + } + return fmt.Errorf("can't unmount %s last mount, it is still in use: %w", c.ID(), define.ErrInternal) + } + } + defer c.newContainerEvent(events.Unmount) + return c.unmount(force) +} + +// Pause pauses a container +func (c *Container) Pause() error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.state.State == define.ContainerStatePaused { + return fmt.Errorf("%q is already paused: %w", c.ID(), define.ErrCtrStateInvalid) + } + if c.state.State != define.ContainerStateRunning { + return fmt.Errorf("%q is not running, can't pause: %w", c.state.State, define.ErrCtrStateInvalid) + } + defer c.newContainerEvent(events.Pause) + return c.pause() +} + +// Unpause unpauses a container +func (c *Container) Unpause() error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.state.State != define.ContainerStatePaused { + return fmt.Errorf("%q is not paused, can't unpause: %w", c.ID(), define.ErrCtrStateInvalid) + } + defer c.newContainerEvent(events.Unpause) + return c.unpause() +} + +// Export exports a container's root filesystem as a tar archive +// The archive will be saved as a file at the given path +func (c *Container) Export(out io.Writer) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.state.State == define.ContainerStateRemoving { + return fmt.Errorf("cannot mount container %s as it is being removed: %w", c.ID(), define.ErrCtrStateInvalid) + } + + defer c.newContainerEvent(events.Mount) + return c.export(out) +} + +// AddArtifact creates and writes to an artifact file for the container +func (c *Container) AddArtifact(name string, data []byte) error { + if !c.valid { + return define.ErrCtrRemoved + } + + return os.WriteFile(c.getArtifactPath(name), data, 0o740) +} + +// GetArtifact reads the specified artifact file from the container +func (c *Container) GetArtifact(name string) ([]byte, error) { + if !c.valid { + return nil, define.ErrCtrRemoved + } + + return os.ReadFile(c.getArtifactPath(name)) +} + +// RemoveArtifact deletes the specified artifacts file +func (c *Container) RemoveArtifact(name string) error { + if !c.valid { + return define.ErrCtrRemoved + } + + return os.Remove(c.getArtifactPath(name)) +} + +// Wait blocks until the container exits and returns its exit code. +func (c *Container) Wait(ctx context.Context) (int32, error) { + return c.WaitForExit(ctx, DefaultWaitInterval) +} + +// WaitForExit blocks until the container exits and returns its exit code. The +// argument is the interval at which checks the container's status. +// If the argument is less than or equal to 0 Nanoseconds a default interval is +// used. +func (c *Container) WaitForExit(ctx context.Context, pollInterval time.Duration) (int32, error) { + id := c.ID() + if !c.valid { + // if the container is not valid at this point as it was deleted, + // check if the exit code was recorded in the db. + exitCode, err := c.runtime.state.GetContainerExitCode(id) + if err == nil { + return exitCode, nil + } + return -1, define.ErrCtrRemoved + } + + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + // if the container is not valid at this point as it was deleted, + // check if the exit code was recorded in the db. + exitCode, err := c.runtime.state.GetContainerExitCode(id) + if err == nil { + return exitCode, nil + } + } + return -1, err + } + } + + conmonPID := c.state.ConmonPID + // conmonPID == 0 means container is not running + if conmonPID == 0 { + exitCode, err := c.runtime.state.GetContainerExitCode(id) + if err != nil { + if errors.Is(err, define.ErrNoSuchExitCode) { + // If the container is configured or created, we must assume it never ran. + if c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated) { + return 0, nil + } + } + return -1, err + } + return exitCode, nil + } + + conmonPidFd := c.getConmonPidFd() + if conmonPidFd > -1 { + defer unix.Close(conmonPidFd) + } + + if pollInterval <= 0 { + pollInterval = DefaultWaitInterval + } + + // we cannot wait locked as we would hold the lock forever, so we unlock and then lock again + c.lock.Unlock() + err := waitForConmonExit(ctx, conmonPID, conmonPidFd, pollInterval) + c.lock.Lock() + if err != nil { + return -1, fmt.Errorf("failed to wait for conmon to exit: %w", err) + } + + // we locked again so we must sync the state + if err := c.syncContainer(); err != nil { + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + // if the container is not valid at this point as it was deleted, + // check if the exit code was recorded in the db. + exitCode, err := c.runtime.state.GetContainerExitCode(id) + if err == nil { + return exitCode, nil + } + } + return -1, err + } + + // syncContainer already did the exit file read so nothing extra for us to do here + return c.runtime.state.GetContainerExitCode(id) +} + +func waitForConmonExit(ctx context.Context, conmonPID, conmonPidFd int, pollInterval time.Duration) error { + if conmonPidFd > -1 { + for { + fds := []unix.PollFd{{Fd: int32(conmonPidFd), Events: unix.POLLIN}} + if n, err := unix.Poll(fds, int(pollInterval.Milliseconds())); err != nil { + if err == unix.EINTR { + continue + } + return err + } else if n == 0 { + // n == 0 means timeout + select { + case <-ctx.Done(): + return define.ErrCanceled + default: + // context not done, wait again + continue + } + } + return nil + } + } + // no pidfd support, we must poll the pid + for { + if err := unix.Kill(conmonPID, 0); err != nil { + if err == unix.ESRCH { + break + } + return err + } + select { + case <-ctx.Done(): + return define.ErrCanceled + case <-time.After(pollInterval): + } + } + return nil +} + +type waitResult struct { + code int32 + err error +} + +func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeout time.Duration, conditions ...string) (int32, error) { + if !c.valid { + return -1, define.ErrCtrRemoved + } + + if len(conditions) == 0 { + panic("at least one condition should be passed") + } + + ctx, cancelFn := context.WithCancel(ctx) + defer cancelFn() + + resultChan := make(chan waitResult) + waitForExit := false + wantedStates := make(map[define.ContainerStatus]bool, len(conditions)) + wantedHealthStates := make(map[string]bool) + + for _, rawCondition := range conditions { + switch rawCondition { + case define.HealthCheckHealthy, define.HealthCheckUnhealthy: + if !c.HasHealthCheck() { + return -1, fmt.Errorf("cannot use condition %q: container %s has no healthcheck", rawCondition, c.ID()) + } + wantedHealthStates[rawCondition] = true + default: + condition, err := define.StringToContainerStatus(rawCondition) + if err != nil { + return -1, err + } + switch condition { + case define.ContainerStateExited, define.ContainerStateStopped: + waitForExit = true + default: + wantedStates[condition] = true + } + } + } + + trySend := func(code int32, err error) { + select { + case resultChan <- waitResult{code, err}: + case <-ctx.Done(): + } + } + + if waitForExit { + go func() { + code, err := c.WaitForExit(ctx, waitTimeout) + trySend(code, err) + }() + } + + if len(wantedStates) > 0 || len(wantedHealthStates) > 0 { + go func() { + stoppedCount := 0 + for { + if len(wantedStates) > 0 { + state, err := c.State() + if err != nil { + // If the we wait for removing and the container is removed do not return this as error. + // This allows callers to actually wait for the ctr to be removed. + if wantedStates[define.ContainerStateRemoving] && + (errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved)) { + // check if the exit code was recorded in the db to return it + exitCode, err := c.runtime.state.GetContainerExitCode(c.ID()) + if err == nil { + trySend(exitCode, nil) + return + } + + trySend(-1, nil) + return + } + trySend(-1, err) + return + } + if _, found := wantedStates[state]; found { + trySend(-1, nil) + return + } + } + if len(wantedHealthStates) > 0 { + // even if we are interested only in the health check + // check that the container is still running to avoid + // waiting until the timeout expires. + if stoppedCount > 0 { + stoppedCount++ + } else { + state, err := c.State() + if err != nil { + trySend(-1, err) + return + } + if state != define.ContainerStateCreated && state != define.ContainerStateRunning && state != define.ContainerStatePaused { + stoppedCount++ + } + } + status, err := c.HealthCheckStatus() + if err != nil { + trySend(-1, err) + return + } + if _, found := wantedHealthStates[status]; found { + trySend(-1, nil) + return + } + // wait for another waitTimeout interval to give the health check process some time + // to record the healthy status. + if stoppedCount > 1 { + trySend(-1, define.ErrCtrStopped) + return + } + } + select { + case <-ctx.Done(): + return + case <-time.After(waitTimeout): + continue + } + } + }() + } + + var result waitResult + select { + case result = <-resultChan: + cancelFn() + case <-ctx.Done(): + result = waitResult{-1, define.ErrCanceled} + } + return result.code, result.err +} + +// Cleanup unmounts all mount points in container and cleans up container storage +// It also cleans up the network stack. +// onlyStopped is set by the podman container cleanup to ensure we only cleanup a stopped container, +// all other states mean another process already called cleanup before us which is fine in such cases. +func (c *Container) Cleanup(ctx context.Context, onlyStopped bool) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + // When the container has already been removed, the OCI runtime directory remains. + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + if err := c.cleanupRuntime(ctx); err != nil { + return fmt.Errorf("cleaning up container %s from OCI runtime: %w", c.ID(), err) + } + return nil + } + logrus.Errorf("Syncing container %s status: %v", c.ID(), err) + return err + } + } + + return c.fullCleanup(ctx, onlyStopped) +} + +// Batch starts a batch operation on the given container +// All commands in the passed function will execute under the same lock and +// without synchronizing state after each operation +// This will result in substantial performance benefits when running numerous +// commands on the same container +// Note that the container passed into the Batch function cannot be removed +// during batched operations. runtime.RemoveContainer can only be called outside +// of Batch +// Any error returned by the given batch function will be returned unmodified by +// Batch +// As Batch normally disables updating the current state of the container, the +// Sync() function is provided to enable container state to be updated and +// checked within Batch. +func (c *Container) Batch(batchFunc func(*Container) error) error { + c.lock.Lock() + defer c.lock.Unlock() + + newCtr := new(Container) + newCtr.config = c.config + newCtr.state = c.state + newCtr.runtime = c.runtime + newCtr.ociRuntime = c.ociRuntime + newCtr.lock = c.lock + newCtr.valid = true + + newCtr.batched = true + err := batchFunc(newCtr) + newCtr.batched = false + + return err +} + +// Sync updates the status of a container by querying the OCI runtime. +// If the container has not been created inside the OCI runtime, nothing will be +// done. +// Most of the time, Podman does not explicitly query the OCI runtime for +// container status, and instead relies upon exit files created by conmon. +// This can cause a disconnect between running state and what Podman sees in +// cases where Conmon was killed unexpectedly, or runc was upgraded. +// Running a manual Sync() ensures that container state will be correct in +// such situations. +func (c *Container) Sync() error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + } + + if err := c.syncContainer(); err != nil { + return err + } + + defer c.newContainerEvent(events.Sync) + return nil +} + +// ReloadNetwork reconfigures the container's network. +// Technically speaking, it will tear down and then reconfigure the container's +// network namespace, which will result in all firewall rules being recreated. +// It is mostly intended to be used in cases where the system firewall has been +// reloaded, and existing rules have been wiped out. It is expected that some +// downtime will result, as the rules are destroyed as part of this process. +// At present, this only works on root containers; it may be expanded to restart +// slirp4netns in the future to work with rootless containers as well. +// Requires that the container must be running or created. +func (c *Container) ReloadNetwork() error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { + return fmt.Errorf("cannot reload network unless container network has been configured: %w", define.ErrCtrStateInvalid) + } + + return c.reloadNetwork() +} + +// Refresh is DEPRECATED and REMOVED. +func (c *Container) Refresh(_ context.Context) error { + // This has been deprecated for a long while, and is in the process of + // being removed. + return define.ErrNotImplemented +} + +// ContainerCheckpointOptions is a struct used to pass the parameters +// for checkpointing (and restoring) to the corresponding functions +type ContainerCheckpointOptions struct { + // Keep tells the API to not delete checkpoint artifacts + Keep bool + // KeepRunning tells the API to keep the container running + // after writing the checkpoint to disk + KeepRunning bool + // TCPEstablished tells the API to checkpoint a container + // even if it contains established TCP connections + TCPEstablished bool + // TCPClose tells the API to close all TCP connections during restore + TCPClose bool + // TargetFile tells the API to read (or write) the checkpoint image + // from (or to) the filename set in TargetFile + TargetFile string + // CheckpointImageID tells the API to restore the container from + // checkpoint image with ID set in CheckpointImageID + CheckpointImageID string + // Name tells the API that during restore from an exported + // checkpoint archive a new name should be used for the + // restored container + Name string + // IgnoreRootfs tells the API to not export changes to + // the container's root file-system (or to not import) + IgnoreRootfs bool + // IgnoreStaticIP tells the API to ignore the IP set + // during 'podman run' with '--ip'. This is especially + // important to be able to restore a container multiple + // times with '--import --name'. + IgnoreStaticIP bool + // IgnoreStaticMAC tells the API to ignore the MAC set + // during 'podman run' with '--mac-address'. This is especially + // important to be able to restore a container multiple + // times with '--import --name'. + IgnoreStaticMAC bool + // IgnoreVolumes tells the API to not export or not to import + // the content of volumes associated with the container + IgnoreVolumes bool + // Pre Checkpoint container and leave container running + PreCheckPoint bool + // Dump container with Pre Checkpoint images + WithPrevious bool + // ImportPrevious tells the API to restore container with two + // images. One is TargetFile, the other is ImportPrevious. + ImportPrevious string + // CreateImage tells Podman to create an OCI image from container + // checkpoint in the local image store. + CreateImage string + // Compression tells the API which compression to use for + // the exported checkpoint archive. + Compression archive.Compression + // If Pod is set the container should be restored into the + // given Pod. If Pod is empty it is a restore without a Pod. + // Restoring a non Pod container into a Pod or a Pod container + // without a Pod is theoretically possible, but will + // probably not work if a PID namespace is shared. + // A shared PID namespace means that a Pod container has PID 1 + // in the infrastructure container, but without the infrastructure + // container no PID 1 will be in the namespace and that is not + // possible. + Pod string + // PrintStats tells the API to fill out the statistics about + // how much time each component in the stack requires to + // checkpoint a container. + PrintStats bool + // FileLocks tells the API to checkpoint/restore a container + // with file-locks + FileLocks bool +} + +// Checkpoint checkpoints a container +// The return values *define.CRIUCheckpointRestoreStatistics and int64 (time +// the runtime needs to checkpoint the container) are only set if +// options.PrintStats is set to true. Not setting options.PrintStats to true +// will return nil and 0. +func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) (*define.CRIUCheckpointRestoreStatistics, int64, error) { + logrus.Debugf("Trying to checkpoint container %s", c.ID()) + + if options.TargetFile != "" { + if err := c.prepareCheckpointExport(); err != nil { + return nil, 0, err + } + } + + if options.WithPrevious { + if err := c.canWithPrevious(); err != nil { + return nil, 0, err + } + } + + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, 0, err + } + } + return c.checkpoint(ctx, options) +} + +// Restore restores a container +// The return values *define.CRIUCheckpointRestoreStatistics and int64 (time +// the runtime needs to restore the container) are only set if +// options.PrintStats is set to true. Not setting options.PrintStats to true +// will return nil and 0. +func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) (*define.CRIUCheckpointRestoreStatistics, int64, error) { + if options.Pod == "" { + logrus.Debugf("Trying to restore container %s", c.ID()) + } else { + logrus.Debugf("Trying to restore container %s into pod %s", c.ID(), options.Pod) + } + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, 0, err + } + } + defer c.newContainerEvent(events.Restore) + return c.restore(ctx, options) +} + +// Indicate whether or not the container should restart +func (c *Container) ShouldRestart(_ context.Context) bool { + logrus.Debugf("Checking if container %s should restart", c.ID()) + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return false + } + } + return c.shouldRestart() +} + +// CopyFromArchive copies the contents from the specified tarStream to path +// *inside* the container. +func (c *Container) CopyFromArchive(_ context.Context, containerPath string, chown, noOverwriteDirNonDir bool, rename map[string]string, tarStream io.Reader) (func() error, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + return c.copyFromArchive(containerPath, chown, noOverwriteDirNonDir, rename, tarStream) +} + +// CopyToArchive copies the contents from the specified path *inside* the +// container to the tarStream. +func (c *Container) CopyToArchive(_ context.Context, containerPath string, tarStream io.Writer) (func() error, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + return c.copyToArchive(containerPath, tarStream) +} + +// Stat the specified path *inside* the container and return a file info. +func (c *Container) Stat(_ context.Context, containerPath string) (*define.FileInfo, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + var mountPoint string + var err error + if c.state.Mounted { + mountPoint = c.state.Mountpoint + } else { + mountPoint, err = c.mount() + if err != nil { + return nil, err + } + defer func() { + if err := c.unmount(false); err != nil { + logrus.Errorf("Unmounting container %s: %v", c.ID(), err) + } + }() + } + + info, _, _, err := c.stat(mountPoint, containerPath) + return info, err +} + +func saveContainerError(c *Container, err error) error { + c.state.Error = err.Error() + return c.save() +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_commit.go b/vendor/github.com/containers/podman/v5/libpod/container_commit.go new file mode 100644 index 00000000..cef7bf40 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_commit.go @@ -0,0 +1,172 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + "slices" + "strings" + + "github.com/containers/buildah" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + is "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/types" +) + +// ContainerCommitOptions is a struct used to commit a container to an image +// It uses buildah's CommitOptions as a base. Long-term we might wish to +// decouple these because it includes duplicates of fields that are in, or +// could later be added, to buildah's CommitOptions, which gets confusing +type ContainerCommitOptions struct { + buildah.CommitOptions + Pause bool + IncludeVolumes bool + Author string + Message string + Changes []string // gets merged with CommitOptions.OverrideChanges + Squash bool // always used instead of CommitOptions.Squash +} + +// Commit commits the changes between a container and its image, creating a new +// image +func (c *Container) Commit(ctx context.Context, destImage string, options ContainerCommitOptions) (*libimage.Image, error) { + if c.config.Rootfs != "" { + return nil, errors.New("cannot commit a container that uses an exploded rootfs") + } + + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + if c.state.State == define.ContainerStateRunning && options.Pause { + if err := c.pause(); err != nil { + return nil, fmt.Errorf("pausing container %q to commit: %w", c.ID(), err) + } + defer func() { + if err := c.unpause(); err != nil { + logrus.Errorf("Unpausing container %q: %v", c.ID(), err) + } + }() + } + + builderOptions := buildah.ImportOptions{ + Container: c.ID(), + SignaturePolicyPath: options.SignaturePolicyPath, + } + commitOptions := buildah.CommitOptions{ + SignaturePolicyPath: options.SignaturePolicyPath, + ReportWriter: options.ReportWriter, + Squash: options.Squash, + SystemContext: c.runtime.imageContext, + PreferredManifestType: options.PreferredManifestType, + OverrideChanges: append(append([]string{}, options.Changes...), options.CommitOptions.OverrideChanges...), + OverrideConfig: options.CommitOptions.OverrideConfig, + } + importBuilder, err := buildah.ImportBuilder(ctx, c.runtime.store, builderOptions) + if err != nil { + return nil, err + } + importBuilder.Format = options.PreferredManifestType + if options.Author != "" { + importBuilder.SetMaintainer(options.Author) + } + if options.Message != "" { + importBuilder.SetComment(options.Message) + } + + // We need to take meta we find in the current container and + // add it to the resulting image. + + // Entrypoint - always set this first or cmd will get wiped out + importBuilder.SetEntrypoint(c.config.Entrypoint) + + // Cmd + importBuilder.SetCmd(c.config.Command) + + // Env + // TODO - this includes all the default environment vars as well + // Should we store the ENV we actually want in the spec separately? + if c.config.Spec.Process != nil { + for _, e := range c.config.Spec.Process.Env { + key, val, _ := strings.Cut(e, "=") + importBuilder.SetEnv(key, val) + } + } + // Expose ports + for _, p := range c.config.PortMappings { + importBuilder.SetPort(fmt.Sprintf("%d/%s", p.ContainerPort, p.Protocol)) + } + for port, protocols := range c.config.ExposedPorts { + for _, protocol := range protocols { + importBuilder.SetPort(fmt.Sprintf("%d/%s", port, protocol)) + } + } + // Labels + for k, v := range c.Labels() { + importBuilder.SetLabel(k, v) + } + // No stop signal + // User + if c.config.User != "" { + importBuilder.SetUser(c.config.User) + } + // Volumes + if options.IncludeVolumes { + for _, v := range c.config.UserVolumes { + if v != "" { + importBuilder.AddVolume(v) + } + } + } else { + // Only include anonymous named volumes added by the user by + // default. + for _, v := range c.config.NamedVolumes { + if slices.Contains(c.config.UserVolumes, v.Dest) { + vol, err := c.runtime.GetVolume(v.Name) + if err != nil { + return nil, fmt.Errorf("volume %s used in container %s has been removed: %w", v.Name, c.ID(), err) + } + if vol.Anonymous() { + importBuilder.AddVolume(v.Dest) + } + } + } + } + // Workdir + importBuilder.SetWorkDir(c.config.Spec.Process.Cwd) + + var commitRef types.ImageReference + if destImage != "" { + // Now resolve the name. + resolvedImageName, err := c.runtime.LibimageRuntime().ResolveName(destImage) + if err != nil { + return nil, err + } + + imageRef, err := is.Transport.ParseStoreReference(c.runtime.store, resolvedImageName) + if err != nil { + return nil, fmt.Errorf("parsing target image name %q: %w", destImage, err) + } + commitRef = imageRef + } + id, _, _, err := importBuilder.Commit(ctx, commitRef, commitOptions) + if err != nil { + return nil, err + } + defer c.newContainerEvent(events.Commit) + img, _, err := c.runtime.libimageRuntime.LookupImage(id, nil) + if err != nil { + return nil, err + } + return img, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_config.go b/vendor/github.com/containers/podman/v5/libpod/container_config.go new file mode 100644 index 00000000..fe046176 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_config.go @@ -0,0 +1,493 @@ +//go:build !remote + +package libpod + +import ( + "net" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/namespaces" + "github.com/containers/podman/v5/pkg/specgen" + spec "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/secrets" + "go.podman.io/image/v5/manifest" + "go.podman.io/storage" +) + +// ContainerConfig contains all information that was used to create the +// container. It may not be changed once created. +// It is stored, read-only, on disk in Libpod's State. +// Any changes will not be written back to the database, and will cause +// inconsistencies with other Libpod instances. +type ContainerConfig struct { + // Spec is OCI runtime spec used to create the container. This is passed + // in when the container is created, but it is not the final spec used + // to run the container - it will be modified by Libpod to add things we + // manage (e.g. bind mounts for /etc/resolv.conf, named volumes, a + // network namespace prepared by the network backend) in the + // generateSpec() function. + Spec *spec.Spec `json:"spec"` + + // ID is a hex-encoded 256-bit pseudorandom integer used as a unique + // identifier for the container. IDs are globally unique in Libpod - + // once an ID is in use, no other container or pod will be created with + // the same one until the holder of the ID has been removed. + // ID is generated by Libpod, and cannot be chosen or influenced by the + // user (except when restoring a checkpointed container). + // ID is guaranteed to be 64 characters long. + ID string `json:"id"` + + // Name is a human-readable name for the container. All containers must + // have a non-empty name. Name may be provided when the container is + // created; if no name is chosen, a name will be auto-generated. + Name string `json:"name"` + + // Pod is the full ID of the pod the container belongs to. If the + // container does not belong to a pod, this will be empty. + // If this is not empty, a pod with this ID is guaranteed to exist in + // the state for the duration of this container's existence. + Pod string `json:"pod,omitempty"` + + // Namespace is the libpod Namespace the container is in. + // Namespaces are used to divide containers in the state. + Namespace string `json:"namespace,omitempty"` + + // LockID is the ID of this container's lock. Each container, pod, and + // volume is assigned a unique Lock (from one of several backends) by + // the libpod Runtime. This lock will belong only to this container for + // the duration of the container's lifetime. + LockID uint32 `json:"lockID"` + + // CreateCommand is the full command plus arguments that were used to + // create the container. It is shown in the output of Inspect, and may + // be used to recreate an identical container for automatic updates or + // portable systemd unit files. + CreateCommand []string `json:"CreateCommand,omitempty"` + + // RawImageName is the raw and unprocessed name of the image when creating + // the container (as specified by the user). May or may not be set. One + // use case to store this data are auto-updates where we need the _exact_ + // name and not some normalized instance of it. + RawImageName string `json:"RawImageName,omitempty"` + + // IDMappings are UID/GID mappings used by the container's user + // namespace. They are used by the OCI runtime when creating the + // container, and by c/storage to ensure that the container's files have + // the appropriate owner. + IDMappings storage.IDMappingOptions `json:"idMappingsOptions"` + + // Dependencies are the IDs of dependency containers. + // These containers must be started before this container is started. + Dependencies []string + + // rewrite is an internal bool to indicate that the config was modified after + // a read from the db, e.g. to migrate config fields after an upgrade. + // This field should never be written to the db, the json tag ensures this. + rewrite bool `json:"-"` + + // embedded sub-configs + ContainerRootFSConfig + ContainerSecurityConfig + ContainerNameSpaceConfig + ContainerNetworkConfig + ContainerImageConfig + ContainerMiscConfig +} + +// ContainerRootFSConfig is an embedded sub-config providing config info +// about the container's root fs. +type ContainerRootFSConfig struct { + // RootfsImageID is the ID of the image used to create the container. + // If the container was created from a Rootfs, this will be empty. + // If non-empty, Podman will create a root filesystem for the container + // based on an image with this ID. + // This conflicts with Rootfs. + RootfsImageID string `json:"rootfsImageID,omitempty"` + // RootfsImageName is the (normalized) name of the image used to create + // the container. If the container was created from a Rootfs, this will + // be empty. + RootfsImageName string `json:"rootfsImageName,omitempty"` + // Rootfs is a directory to use as the container's root filesystem. + // If RootfsImageID is set, this will be empty. + // If this is set, Podman will not create a root filesystem for the + // container based on an image, and will instead use the given directory + // as the container's root. + // Conflicts with RootfsImageID. + Rootfs string `json:"rootfs,omitempty"` + // RootfsOverlay tells if rootfs has to be mounted as an overlay + RootfsOverlay bool `json:"rootfs_overlay,omitempty"` + // RootfsMapping specifies if there are mappings to apply to the rootfs. + RootfsMapping *string `json:"rootfs_mapping,omitempty"` + // ShmDir is the path to be mounted on /dev/shm in container. + // If not set manually at creation time, Libpod will create a tmpfs + // with the size specified in ShmSize and populate this with the path of + // said tmpfs. + ShmDir string `json:"ShmDir,omitempty"` + // NoShmShare indicates whether /dev/shm can be shared with other containers + NoShmShare bool `json:"NOShmShare,omitempty"` + // NoShm indicates whether a tmpfs should be created and mounted on /dev/shm + NoShm bool `json:"NoShm,omitempty"` + // ShmSize is the size of the container's SHM. Only used if ShmDir was + // not set manually at time of creation. + ShmSize int64 `json:"shmSize"` + // ShmSizeSystemd is the size of systemd-specific tmpfs mounts + ShmSizeSystemd int64 `json:"shmSizeSystemd"` + // Static directory for container content that will persist across + // reboot. + // StaticDir is a persistent directory for Libpod files that will + // survive system reboot. It is not part of the container's rootfs and + // is not mounted into the container. It will be removed when the + // container is removed. + // Usually used to store container log files, files that will be bind + // mounted into the container (e.g. the resolv.conf we made for the + // container), and other per-container content. + StaticDir string `json:"staticDir"` + // Mounts contains all additional mounts into the container rootfs. + // It is presently only used for the container's SHM directory. + // These must be unmounted before the container's rootfs is unmounted. + Mounts []string `json:"mounts,omitempty"` + // NamedVolumes lists the Libpod named volumes to mount into the + // container. Each named volume is guaranteed to exist so long as this + // container exists. + NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` + // OverlayVolumes lists the overlay volumes to mount into the container. + OverlayVolumes []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"` + // ImageVolumes lists the image volumes to mount into the container. + // Please note that this is named ctrImageVolumes in JSON to + // distinguish between these and the old `imageVolumes` field in Podman + // pre-1.8, which was used in very old Podman versions to determine how + // image volumes were handled in Libpod (support for these eventually + // moved out of Libpod into pkg/specgen). + // Please DO NOT reuse the `imageVolumes` name in container JSON again. + ImageVolumes []*ContainerImageVolume `json:"ctrImageVolumes,omitempty"` + // ArtifactVolumes lists the artifact volumes to mount into the container. + ArtifactVolumes []*ContainerArtifactVolume `json:"artifactVolumes,omitempty"` + // CreateWorkingDir indicates that Libpod should create the container's + // working directory if it does not exist. Some OCI runtimes do this by + // default, but others do not. + CreateWorkingDir bool `json:"createWorkingDir,omitempty"` + // Secrets lists secrets to mount into the container + Secrets []*ContainerSecret `json:"secrets,omitempty"` + // SecretPath is the secrets location in storage + SecretsPath string `json:"secretsPath"` + // StorageOpts to be used when creating rootfs + StorageOpts map[string]string `json:"storageOpts"` + // Volatile specifies whether the container storage can be optimized + // at the cost of not syncing all the dirty files in memory. + Volatile bool `json:"volatile,omitempty"` + // Passwd allows to user to override podman's passwd/group file setup + Passwd *bool `json:"passwd,omitempty"` + // ChrootDirs is an additional set of directories that need to be + // treated as root directories. Standard bind mounts will be mounted + // into paths relative to these directories. + ChrootDirs []string `json:"chroot_directories,omitempty"` +} + +// ContainerSecurityConfig is an embedded sub-config providing security configuration +// to the container. +type ContainerSecurityConfig struct { + // Privileged is whether the container is privileged. Privileged + // containers have lessened security and increased access to the system. + // Note that this does NOT directly correspond to Podman's --privileged + // flag - most of the work of that flag is done in creating the OCI spec + // given to Libpod. This only enables a small subset of the overall + // operation, mostly around mounting the container image with reduced + // security. + Privileged bool `json:"privileged"` + // ProcessLabel is the SELinux process label for the container. + ProcessLabel string `json:"ProcessLabel,omitempty"` + // MountLabel is the SELinux mount label for the container's root + // filesystem. Only used if the container was created from an image. + // If not explicitly set, an unused random MLS label will be assigned by + // containers/storage (but only if SELinux is enabled). + MountLabel string `json:"MountLabel,omitempty"` + // LabelOpts are options passed in by the user to set up SELinux labels. + // These are used by the containers/storage library. + LabelOpts []string `json:"labelopts,omitempty"` + // User and group to use in the container. Can be specified as only user + // (in which case we will attempt to look up the user in the container + // to determine the appropriate group) or user and group separated by a + // colon. + // Can be specified by name or UID/GID. + // If unset, this will default to UID and GID 0 (root). + User string `json:"user,omitempty"` + // Groups are additional groups to add the container's user to. These + // are resolved within the container using the container's /etc/passwd. + Groups []string `json:"groups,omitempty"` + // HostUsers are a list of host user accounts to add to /etc/passwd + HostUsers []string `json:"HostUsers,omitempty"` + // AddCurrentUserPasswdEntry indicates that Libpod should ensure that + // the container's /etc/passwd contains an entry for the user running + // Libpod - mostly used in rootless containers where the user running + // Libpod wants to retain their UID inside the container. + AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"` + // LabelNested, allow labeling separation from within a container + LabelNested bool `json:"label_nested"` +} + +// ContainerNameSpaceConfig is an embedded sub-config providing +// namespace configuration to the container. +type ContainerNameSpaceConfig struct { + // IDs of container to share namespaces with + // NetNsCtr conflicts with the CreateNetNS bool + // These containers are considered dependencies of the given container + // They must be started before the given container is started + IPCNsCtr string `json:"ipcNsCtr,omitempty"` + MountNsCtr string `json:"mountNsCtr,omitempty"` + NetNsCtr string `json:"netNsCtr,omitempty"` + PIDNsCtr string `json:"pidNsCtr,omitempty"` + UserNsCtr string `json:"userNsCtr,omitempty"` + UTSNsCtr string `json:"utsNsCtr,omitempty"` + CgroupNsCtr string `json:"cgroupNsCtr,omitempty"` +} + +// ContainerNetworkConfig is an embedded sub-config providing network configuration +// to the container. +type ContainerNetworkConfig struct { + // CreateNetNS indicates that libpod should create and configure a new + // network namespace for the container. + // This cannot be set if NetNsCtr is also set. + CreateNetNS bool `json:"createNetNS"` + // StaticIP is a static IP to request for the container. + // This cannot be set unless CreateNetNS is set. + // If not set, the container will be dynamically assigned an IP by CNI. + // Deprecated: Do no use this anymore, this is only for DB backwards compat. + StaticIP net.IP `json:"staticIP,omitempty"` + // StaticMAC is a static MAC to request for the container. + // This cannot be set unless CreateNetNS is set. + // If not set, the container will be dynamically assigned a MAC by CNI. + // Deprecated: Do no use this anymore, this is only for DB backwards compat. + StaticMAC types.HardwareAddr `json:"staticMAC,omitempty"` + // PortMappings are the ports forwarded to the container's network + // namespace + // These are not used unless CreateNetNS is true + PortMappings []types.PortMapping `json:"newPortMappings,omitempty"` + // OldPortMappings are the ports forwarded to the container's network + // namespace. As of podman 4.0 this field is deprecated, use PortMappings + // instead. The db will convert the old ports to the new structure for you. + // These are not used unless CreateNetNS is true + OldPortMappings []types.OCICNIPortMapping `json:"portMappings,omitempty"` + // ExposedPorts are the ports which are exposed but not forwarded + // into the container. + // The map key is the port and the string slice contains the protocols, + // e.g. tcp and udp + // These are only set when exposed ports are given but not published. + ExposedPorts map[uint16][]string `json:"exposedPorts,omitempty"` + // UseImageResolvConf indicates that resolv.conf should not be + // bind-mounted inside the container. + // Conflicts with DNSServer, DNSSearch, DNSOption. + UseImageResolvConf bool + // DNS servers to use in container resolv.conf + // Will override servers in host resolv if set + DNSServer []net.IP `json:"dnsServer,omitempty"` + // DNS Search domains to use in container resolv.conf + // Will override search domains in host resolv if set + DNSSearch []string `json:"dnsSearch,omitempty"` + // DNS options to be set in container resolv.conf + // With override options in host resolv if set + DNSOption []string `json:"dnsOption,omitempty"` + // UseImageHostname indicates that /etc/hostname should not be + // bind-mounted inside the container. + UseImageHostname bool `json:"useImageHostname"` + // UseImageHosts indicates that /etc/hosts should not be + // bind-mounted inside the container. + // Conflicts with HostAdd. + UseImageHosts bool + // BaseHostsFile is the base file to create the `/etc/hosts` file inside the container. + // This must either be an absolute path to a file on the host system, or one of the + // special flags `image` or `none`. + // If it is empty it defaults to the base_hosts_file configuration in containers.conf. + BaseHostsFile string `json:"baseHostsFile,omitempty"` + // Hosts to add in container + // Will be appended to host's host file + HostAdd []string `json:"hostsAdd,omitempty"` + // Network names with the network specific options. + // Please note that these can be altered at runtime. The actual list is + // stored in the DB and should be retrieved from there via c.networks() + // this value is only used for container create. + // Added in podman 4.0, previously NetworksDeprecated was used. Make + // sure to not change the json tags. + Networks map[string]types.PerNetworkOptions `json:"newNetworks,omitempty"` + // Network names to add container to. Empty to use default network. + // Please note that these can be altered at runtime. The actual list is + // stored in the DB and should be retrieved from there; this is only the + // set of networks the container was *created* with. + // Deprecated: Do no use this anymore, this is only for DB backwards compat. + // Also note that we need to keep the old json tag to decode from DB correctly + NetworksDeprecated []string `json:"networks,omitempty"` + // Network mode specified for the default network. + NetMode namespaces.NetworkMode `json:"networkMode,omitempty"` + // NetworkOptions are additional options for each network + NetworkOptions map[string][]string `json:"network_options,omitempty"` +} + +// ContainerImageConfig is an embedded sub-config providing image configuration +// to the container. +type ContainerImageConfig struct { + // UserVolumes contains user-added volume mounts in the container. + // These will not be added to the container's spec, as it is assumed + // they are already present in the spec given to Libpod. Instead, it is + // used when committing containers to generate the VOLUMES field of the + // image that is created, and for triggering some OCI hooks which do not + // fire unless user-added volume mounts are present. + UserVolumes []string `json:"userVolumes,omitempty"` + // Entrypoint is the container's entrypoint. + // It is not used in spec generation, but will be used when the + // container is committed to populate the entrypoint of the new image. + Entrypoint []string `json:"entrypoint,omitempty"` + // Command is the container's command. + // It is not used in spec generation, but will be used when the + // container is committed to populate the command of the new image. + Command []string `json:"command,omitempty"` +} + +// ContainerMiscConfig is an embedded sub-config providing misc configuration +// to the container. +type ContainerMiscConfig struct { + // Whether to keep container STDIN open + Stdin bool `json:"stdin,omitempty"` + // Labels is a set of key-value pairs providing additional information + // about a container + Labels map[string]string `json:"labels,omitempty"` + // StopSignal is the signal that will be used to stop the container + StopSignal uint `json:"stopSignal,omitempty"` + // StopTimeout is maximum time a container is allowed to run after getting the stop signal + StopTimeout uint `json:"stopTimeout,omitempty"` + // Timeout is maximum time a container will run before getting the kill signal + Timeout uint `json:"timeout,omitempty"` + // Time container was created + CreatedTime time.Time `json:"createdTime"` + // CgroupManager is the cgroup manager used to create this container. + // If empty, the runtime default will be used. + CgroupManager string `json:"cgroupManager,omitempty"` + // NoCgroups indicates that the container will not create Cgroups. It is + // incompatible with CgroupParent. Deprecated in favor of CgroupsMode. + NoCgroups bool `json:"noCgroups,omitempty"` + // CgroupsMode indicates how the container will create cgroups + // (disabled, no-conmon, enabled). It supersedes NoCgroups. + CgroupsMode string `json:"cgroupsMode,omitempty"` + // Cgroup parent of the container. + CgroupParent string `json:"cgroupParent"` + // GroupEntry specifies arbitrary data to append to a file. + GroupEntry string `json:"group_entry,omitempty"` + // KubeExitCodePropagation of the service container. + KubeExitCodePropagation define.KubeExitCodePropagation `json:"kubeExitCodePropagation"` + // LogPath log location + LogPath string `json:"logPath"` + // LogTag is the tag used for logging + LogTag string `json:"logTag"` + // LogSize is the maximum size of the container's log file + LogSize int64 `json:"logSize"` + // LogDriver driver for logs + LogDriver string `json:"logDriver"` + // File containing the conmon PID + ConmonPidFile string `json:"conmonPidFile,omitempty"` + // RestartPolicy indicates what action the container will take upon + // exiting naturally. + // Allowed options are "no" (take no action), "on-failure" (restart on + // non-zero exit code, up to a maximum of RestartRetries times), + // and "always" (always restart the container on any exit code). + // The empty string is treated as the default ("no") + RestartPolicy string `json:"restart_policy,omitempty"` + // RestartRetries indicates the number of attempts that will be made to + // restart the container. Used only if RestartPolicy is set to + // "on-failure". + RestartRetries uint `json:"restart_retries,omitempty"` + // PostConfigureNetNS needed when a user namespace is created by an OCI runtime + // if the network namespace is created before the user namespace it will be + // owned by the wrong user namespace. + PostConfigureNetNS bool `json:"postConfigureNetNS"` + // OCIRuntime used to create the container + OCIRuntime string `json:"runtime,omitempty"` + // IsInfra is a bool indicating whether this container is an infra container used for + // sharing kernel namespaces in a pod + IsInfra bool `json:"pause"` + // IsDefaultInfra is a bool indicating whether this container is a default infra container + // using the default rootfs with catatonit bind-mounted into it. + IsDefaultInfra bool `json:"defaultPause"` + // IsService is a bool indicating whether this container is a service container used for + // tracking the life cycle of K8s service. + IsService bool `json:"isService"` + // SdNotifyMode tells libpod what to do with a NOTIFY_SOCKET if passed + SdNotifyMode string `json:"sdnotifyMode,omitempty"` + // SdNotifySocket stores NOTIFY_SOCKET in use by the container + SdNotifySocket string `json:"sdnotifySocket,omitempty"` + // Systemd tells libpod to set up the container in systemd mode, a value of nil denotes false + Systemd *bool `json:"systemd,omitempty"` + // HealthCheckConfig has the health check command and related timings + HealthCheckConfig *manifest.Schema2HealthConfig `json:"healthcheck"` + // HealthCheckOnFailureAction defines an action to take once the container turns unhealthy. + HealthCheckOnFailureAction define.HealthCheckOnFailureAction `json:"healthcheck_on_failure_action"` + // HealthLogDestination defines the destination where the log is stored + // Nil value means the default value (local). + HealthLogDestination *string `json:"healthLogDestination,omitempty"` + // HealthMaxLogCount is maximum number of attempts in the HealthCheck log file. + // ('0' value means an infinite number of attempts in the log file) + // Nil value means the default value (5). + HealthMaxLogCount *uint `json:"healthMaxLogCount,omitempty"` + // HealthMaxLogSize is the maximum length in characters of stored HealthCheck log + // ("0" value means an infinite log length) + // Nil value means the default value (500). + HealthMaxLogSize *uint `json:"healthMaxLogSize,omitempty"` + // StartupHealthCheckConfig is the configuration of the startup + // healthcheck for the container. This will run before the regular HC + // runs, and when it passes the regular HC will be activated. + StartupHealthCheckConfig *define.StartupHealthCheck `json:"startupHealthCheck,omitempty"` + // PreserveFDs is a number of additional file descriptors (in addition + // to 0, 1, 2) that will be passed to the executed process. The total FDs + // passed will be 3 + PreserveFDs. + PreserveFDs uint `json:"preserveFds,omitempty"` + // PreserveFD is a list of additional file descriptors (in addition + // to 0, 1, 2) that will be passed to the executed process. + PreserveFD []uint `json:"preserveFd,omitempty"` + // Timezone is the timezone inside the container. + // Local means it has the same timezone as the host machine + Timezone string `json:"timezone,omitempty"` + // Umask is the umask inside the container. + Umask string `json:"umask,omitempty"` + // PidFile is the file that saves the pid of the container process + PidFile string `json:"pid_file,omitempty"` + // CDIDevices contains devices that use the CDI + CDIDevices []string `json:"cdiDevices,omitempty"` + // DeviceHostSrc contains the original source on the host + DeviceHostSrc []spec.LinuxDevice `json:"device_host_src,omitempty"` + // EnvSecrets are secrets that are set as environment variables + EnvSecrets map[string]*secrets.Secret `json:"secret_env,omitempty"` + // InitContainerType specifies if the container is an initcontainer + // and if so, what type: always or once are possible non-nil entries + InitContainerType string `json:"init_container_type,omitempty"` + // PasswdEntry specifies arbitrary data to append to a file. + PasswdEntry string `json:"passwd_entry,omitempty"` + // MountAllDevices is an option to indicate whether a privileged container + // will mount all the host's devices + MountAllDevices bool `json:"mountAllDevices"` + // ReadWriteTmpfs indicates whether all tmpfs should be mounted readonly when in ReadOnly mode + ReadWriteTmpfs bool `json:"readWriteTmpfs"` +} + +// InfraInherit contains the compatible options inheritable from the infra container +type InfraInherit struct { + ApparmorProfile string `json:"apparmor_profile,omitempty"` + CapAdd []string `json:"cap_add,omitempty"` + CapDrop []string `json:"cap_drop,omitempty"` + HostDeviceList []spec.LinuxDevice `json:"host_device_list,omitempty"` + ImageVolumes []*specgen.ImageVolume `json:"image_volumes,omitempty"` + Mounts []spec.Mount `json:"mounts,omitempty"` + NoNewPrivileges bool `json:"no_new_privileges,omitempty"` + OverlayVolumes []*specgen.OverlayVolume `json:"overlay_volumes,omitempty"` + SeccompPolicy string `json:"seccomp_policy,omitempty"` + SeccompProfilePath string `json:"seccomp_profile_path,omitempty"` + SelinuxOpts []string `json:"selinux_opts,omitempty"` + Volumes []*specgen.NamedVolume `json:"volumes,omitempty"` + ShmSize *int64 `json:"shm_size"` + ShmSizeSystemd *int64 `json:"shm_size_systemd"` + UseImageHosts bool `json:"use_image_hosts"` + UseImageHostname bool `json:"use_image_hostname"` +} + +// IsDefaultShmSize determines if the user actually set the shm in the parent ctr or if it has been set to the default size +func (inherit *InfraInherit) IsDefaultShmSize() bool { + return inherit.ShmSize == nil || *inherit.ShmSize == 65536000 +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_copy_common.go b/vendor/github.com/containers/podman/v5/libpod/container_copy_common.go new file mode 100644 index 00000000..66b66e8c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_copy_common.go @@ -0,0 +1,344 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + buildahCopiah "github.com/containers/buildah/copier" + "github.com/containers/buildah/pkg/chrootuser" + "github.com/containers/buildah/util" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/shutdown" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/stringid" +) + +func (c *Container) copyFromArchive(path string, chown, noOverwriteDirNonDir bool, rename map[string]string, reader io.Reader) (func() error, error) { + var ( + mountPoint string + resolvedRoot string + resolvedPath string + unmount func() + cleanupFuncs []func() + err error + locked = true + ) + + // Make sure that "/" copies the *contents* of the mount point and not + // the directory. + if path == "/" { + path = "/." + } + + // Optimization: only mount if the container is not already. + if c.state.Mounted { + mountPoint = c.state.Mountpoint + unmount = func() {} + } else { + // NOTE: make sure to unmount in error paths. + mountPoint, err = c.mount() + if err != nil { + return nil, err + } + c.state.Mountpoint = mountPoint + if err := c.save(); err != nil { + return nil, err + } + + unmount = func() { + if !locked { + c.lock.Lock() + defer c.lock.Unlock() + } + + if err := c.syncContainer(); err != nil { + logrus.Errorf("Unable to sync container %s state: %v", c.ID(), err) + return + } + + // These have to be first, some of them rely on container rootfs still being mounted. + for _, cleanupFunc := range cleanupFuncs { + cleanupFunc() + } + if err := c.unmount(false); err != nil { + logrus.Errorf("Failed to unmount container: %v", err) + } + + if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { + c.state.Mountpoint = "" + if err := c.save(); err != nil { + logrus.Errorf("Writing container %s state: %v", c.ID(), err) + } + } + } + + // Before we proceed, mount all named volumes associated with the + // container. + // This solves two issues: + // Firstly, it ensures that if the volume actually requires a mount, we + // will mount it for safe use. + // (For example, image volumes, volume plugins). + // Secondly, it copies up into the volume if necessary. + // This ensures that permissions are correct for copies into volumes on + // containers that have never started. + if len(c.config.NamedVolumes) > 0 { + for _, v := range c.config.NamedVolumes { + vol, err := c.mountNamedVolume(v, mountPoint) + if err != nil { + unmount() + return nil, err + } + + volUnmountName := fmt.Sprintf("volume unmount %s %s", vol.Name(), stringid.GenerateNonCryptoID()[0:12]) + + // The unmount function can be called in two places: + // First, from unmount(), our generic cleanup function that gets + // called on success or on failure by error. + // Second, from the shutdown handler on receipt of a SIGTERM + // or similar. + volUnmountFunc := func() error { + vol.lock.Lock() + defer vol.lock.Unlock() + + if err := vol.unmount(false); err != nil { + return err + } + + return nil + } + + cleanupFuncs = append(cleanupFuncs, func() { + _ = shutdown.Unregister(volUnmountName) + + if err := volUnmountFunc(); err != nil { + logrus.Errorf("Unmounting container %s volume %s: %v", c.ID(), vol.Name(), err) + } + }) + + if err := shutdown.Register(volUnmountName, func(_ os.Signal) error { + return volUnmountFunc() + }); err != nil && !errors.Is(err, shutdown.ErrHandlerExists) { + return nil, fmt.Errorf("adding shutdown handler for volume %s unmount: %w", vol.Name(), err) + } + } + } + } + + resolvedRoot, resolvedPath, volume, err := c.resolveCopyTarget(mountPoint, path) + if err != nil { + unmount() + return nil, err + } + + if volume != nil { + // This must be the first cleanup function so it fires before volume unmounts happen. + cleanupFuncs = append([]func(){func() { + // This is a gross hack to ensure correct permissions + // on a volume that was copied into that needed, but did + // not receive, a copy-up. + // Why do we need this? + // Basically: fixVolumePermissions is needed to ensure + // the volume has the right permissions. + // However, fixVolumePermissions only fires on a volume + // that is not empty iff a copy-up occurred. + // In this case, the volume is not empty as we just + // copied into it, so in order to get + // fixVolumePermissions to actually run, we must + // convince it that a copy-up occurred - even if it did + // not. + // At the same time, clear NeedsCopyUp as we just + // populated the volume and that will block a future + // copy-up. + volume.lock.Lock() + defer volume.lock.Unlock() + + if err := volume.update(); err != nil { + logrus.Errorf("Unable to update volume %s status: %v", volume.Name(), err) + return + } + + if volume.state.NeedsCopyUp && volume.state.NeedsChown { + volume.state.NeedsCopyUp = false + volume.state.CopiedUp = true + if err := volume.save(); err != nil { + logrus.Errorf("Unable to save volume %s state: %v", volume.Name(), err) + return + } + + for _, namedVol := range c.config.NamedVolumes { + if namedVol.Name == volume.Name() { + if err := c.fixVolumePermissionsUnlocked(namedVol, volume); err != nil { + logrus.Errorf("Unable to fix volume %s permissions: %v", volume.Name(), err) + } + return + } + } + } + }}, cleanupFuncs...) + } + + var idPair *idtools.IDPair + if chown { + // Make sure we chown the files to the container's main user and group ID. + user, err := getContainerUser(c, mountPoint) + if err != nil { + unmount() + return nil, err + } + idPair = &idtools.IDPair{UID: int(user.UID), GID: int(user.GID)} + } + + decompressed, err := archive.DecompressStream(reader) + if err != nil { + unmount() + return nil, err + } + + locked = false + + logrus.Debugf("Container copy *to* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID()) + + return func() error { + defer unmount() + defer decompressed.Close() + putOptions := buildahCopiah.PutOptions{ + UIDMap: c.config.IDMappings.UIDMap, + GIDMap: c.config.IDMappings.GIDMap, + ChownDirs: idPair, + ChownFiles: idPair, + NoOverwriteDirNonDir: noOverwriteDirNonDir, + NoOverwriteNonDirDir: noOverwriteDirNonDir, + Rename: rename, + } + + return c.joinMountAndExec( + func() error { + return buildahCopiah.Put(resolvedRoot, resolvedPath, putOptions, decompressed) + }, + ) + }, nil +} + +func (c *Container) copyToArchive(path string, writer io.Writer) (func() error, error) { + var ( + mountPoint string + unmount func() + err error + ) + + // Optimization: only mount if the container is not already. + if c.state.Mounted { + mountPoint = c.state.Mountpoint + unmount = func() {} + } else { + // NOTE: make sure to unmount in error paths. + mountPoint, err = c.mount() + if err != nil { + return nil, err + } + unmount = func() { + if err := c.unmount(false); err != nil { + logrus.Errorf("Failed to unmount container: %v", err) + } + } + } + + statInfo, resolvedRoot, resolvedPath, err := c.stat(mountPoint, path) + if err != nil { + unmount() + return nil, err + } + + // We optimistically chown to the host user. In case of a hypothetical + // container-to-container copy, the reading side will chown back to the + // container user. + user, err := getContainerUser(c, mountPoint) + if err != nil { + unmount() + return nil, err + } + hostUID, hostGID, err := util.GetHostIDs( + idtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), + idtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), + user.UID, + user.GID, + ) + if err != nil { + unmount() + return nil, err + } + idPair := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} + + logrus.Debugf("Container copy *from* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID()) + + return func() error { + defer unmount() + getOptions := buildahCopiah.GetOptions{ + // Unless the specified points to ".", we want to copy the base directory. + KeepDirectoryNames: statInfo.IsDir && filepath.Base(path) != ".", + UIDMap: c.config.IDMappings.UIDMap, + GIDMap: c.config.IDMappings.GIDMap, + ChownDirs: &idPair, + ChownFiles: &idPair, + Excludes: []string{"dev", "proc", "sys"}, + // Ignore EPERMs when copying from rootless containers + // since we cannot read TTY devices. Those are owned + // by the host's root and hence "nobody" inside the + // container's user namespace. + IgnoreUnreadable: rootless.IsRootless() && c.state.State == define.ContainerStateRunning, + } + return c.joinMountAndExec( + func() error { + return buildahCopiah.Get(resolvedRoot, "", getOptions, []string{resolvedPath}, writer) + }, + ) + }, nil +} + +// getContainerUser returns the specs.User and ID mappings of the container. +func getContainerUser(container *Container, mountPoint string) (specs.User, error) { + userspec := container.config.User + + uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec) + u := specs.User{ + UID: uid, + GID: gid, + Username: userspec, + } + + if !strings.Contains(userspec, ":") { + groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID)) + if err2 != nil { + if !errors.Is(err2, chrootuser.ErrNoSuchUser) && err == nil { + err = err2 + } + } else { + u.AdditionalGids = groups + } + } + + return u, err +} + +// idtoolsToRuntimeSpec converts idtools ID mapping to the one of the runtime spec. +func idtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) { + for _, idmap := range idMaps { + tempIDMap := specs.LinuxIDMapping{ + ContainerID: uint32(idmap.ContainerID), + HostID: uint32(idmap.HostID), + Size: uint32(idmap.Size), + } + convertedIDMap = append(convertedIDMap, tempIDMap) + } + return convertedIDMap +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_copy_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/container_copy_freebsd.go new file mode 100644 index 00000000..60d15646 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_copy_freebsd.go @@ -0,0 +1,15 @@ +//go:build !remote + +package libpod + +// On FreeBSD, the container's mounts are in the global mount +// namespace so we can just execute the function directly. +func (c *Container) joinMountAndExec(f func() error) error { + return f() +} + +// Similarly, we can just use resolvePath for both running and stopped +// containers. +func (c *Container) resolveCopyTarget(mountPoint string, containerPath string) (string, string, *Volume, error) { + return c.resolvePath(mountPoint, containerPath) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_copy_linux.go b/vendor/github.com/containers/podman/v5/libpod/container_copy_linux.go new file mode 100644 index 00000000..eda28573 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_copy_linux.go @@ -0,0 +1,90 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "os" + "runtime" + + "github.com/containers/podman/v5/libpod/define" + "golang.org/x/sys/unix" +) + +// joinMountAndExec executes the specified function `f` inside the container's +// mount and PID namespace. That allows for having the exact view on the +// container's file system. +// +// Note, if the container is not running `f()` will be executed as is. +func (c *Container) joinMountAndExec(f func() error) error { + if c.state.State != define.ContainerStateRunning { + return f() + } + + // Container's running, so we need to execute `f()` inside its mount NS. + errChan := make(chan error) + go func() { + runtime.LockOSThread() + + // Join the mount and PID NS of the container. + getFD := func(ns LinuxNS) (*os.File, error) { + nsPath, err := c.namespacePath(ns) + if err != nil { + return nil, err + } + return os.Open(nsPath) + } + + mountFD, err := getFD(MountNS) + if err != nil { + errChan <- err + return + } + defer mountFD.Close() + + inHostPidNS, err := c.inHostPidNS() + if err != nil { + errChan <- fmt.Errorf("checking inHostPidNS: %w", err) + return + } + var pidFD *os.File + if !inHostPidNS { + pidFD, err = getFD(PIDNS) + if err != nil { + errChan <- err + return + } + defer pidFD.Close() + } + + if err := unix.Unshare(unix.CLONE_NEWNS); err != nil { + errChan <- err + return + } + + if pidFD != nil { + if err := unix.Setns(int(pidFD.Fd()), unix.CLONE_NEWPID); err != nil { + errChan <- err + return + } + } + if err := unix.Setns(int(mountFD.Fd()), unix.CLONE_NEWNS); err != nil { + errChan <- err + return + } + + // Last but not least, execute the workload. + errChan <- f() + }() + return <-errChan +} + +func (c *Container) resolveCopyTarget(mountPoint string, containerPath string) (string, string, *Volume, error) { + // If the container is running, we will execute the copy + // inside the container's mount namespace so we return a path + // relative to the container's root. + if c.state.State == define.ContainerStateRunning { + return "/", c.pathAbs(containerPath), nil, nil + } + return c.resolvePath(mountPoint, containerPath) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_exec.go b/vendor/github.com/containers/podman/v5/libpod/container_exec.go new file mode 100644 index 00000000..f343bbee --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_exec.go @@ -0,0 +1,1322 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "path/filepath" + "strconv" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/pkg/pidhandle" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/resize" + "go.podman.io/common/pkg/util" + "go.podman.io/storage/pkg/stringid" + "golang.org/x/sys/unix" +) + +// ExecConfig contains the configuration of an exec session +type ExecConfig struct { + // Command is the command that will be invoked in the exec session. + // Must not be empty. + Command []string `json:"command"` + // Terminal is whether the exec session will allocate a pseudoterminal. + Terminal bool `json:"terminal,omitempty"` + // AttachStdin is whether the STDIN stream will be forwarded to the exec + // session's first process when attaching. Only available if Terminal is + // false. + AttachStdin bool `json:"attachStdin,omitempty"` + // AttachStdout is whether the STDOUT stream will be forwarded to the + // exec session's first process when attaching. Only available if + // Terminal is false. + AttachStdout bool `json:"attachStdout,omitempty"` + // AttachStderr is whether the STDERR stream will be forwarded to the + // exec session's first process when attaching. Only available if + // Terminal is false. + AttachStderr bool `json:"attachStderr,omitempty"` + // DetachKeys are keys that will be used to detach from the exec + // session. Here, nil will use the default detach keys, where a pointer + // to the empty string ("") will disable detaching via detach keys. + DetachKeys *string `json:"detachKeys,omitempty"` + // Environment is a set of environment variables that will be set for + // the first process started by the exec session. + Environment map[string]string `json:"environment,omitempty"` + // Privileged is whether the exec session will be privileged - that is, + // will be granted additional capabilities. + Privileged bool `json:"privileged,omitempty"` + // User is the user the exec session will be run as. + // If set to "" the exec session will be started as the same user the + // container was started as. + User string `json:"user,omitempty"` + // WorkDir is the working directory for the first process that will be + // launched by the exec session. + // If set to "" the exec session will be started in / within the + // container. + WorkDir string `json:"workDir,omitempty"` + // PreserveFDs indicates that a number of extra FDs from the process + // running libpod will be passed into the container. These are assumed + // to begin at 3 (immediately after the standard streams). The number + // given is the number that will be passed into the exec session, + // starting at 3. + PreserveFDs uint `json:"preserveFds,omitempty"` + // PreserveFD is a list of additional file descriptors (in addition + // to 0, 1, 2) that will be passed to the executed process. + PreserveFD []uint `json:"preserveFd,omitempty"` + // ExitCommand is the exec session's exit command. + // This command will be executed when the exec session exits. + // If unset, no command will be executed. + // Two arguments will be appended to the exit command by Libpod: + // The ID of the exec session, and the ID of the container the exec + // session is a part of (in that order). + ExitCommand []string `json:"exitCommand,omitempty"` + // ExitCommandDelay is a delay (in seconds) between the container + // exiting, and the exit command being executed. If set to 0, there is + // no delay. If set, ExitCommand must also be set. + ExitCommandDelay uint `json:"exitCommandDelay,omitempty"` +} + +// ExecSession contains information on a single exec session attached to a given +// container. +type ExecSession struct { + // Id is the ID of the exec session. + // Named somewhat strangely to not conflict with ID(). + Id string `json:"id"` + // ContainerId is the ID of the container this exec session belongs to. + // Named somewhat strangely to not conflict with ContainerID(). + ContainerId string `json:"containerId"` + + // State is the state of the exec session. + State define.ContainerExecStatus `json:"state"` + // PID is the PID of the process created by the exec session. + PID int `json:"pid,omitempty"` + // ExitCode is the exit code of the exec session, if it has exited. + ExitCode int `json:"exitCode,omitempty"` + + // Config is the configuration of this exec session. + // Cannot be empty. + Config *ExecConfig `json:"config"` + + // PIDData is a string uniquely identifying the PID. The value is platform + // specific. It is generated by pidhandle package and used by isSessionAlive. + PIDData string `json:"pidData,omitempty"` +} + +// ID returns the ID of an exec session. +func (e *ExecSession) ID() string { + return e.Id +} + +// ContainerID returns the ID of the container this exec session was started in. +func (e *ExecSession) ContainerID() string { + return e.ContainerId +} + +// Inspect inspects the given exec session and produces detailed output on its +// configuration and current state. +func (e *ExecSession) Inspect() (*define.InspectExecSession, error) { + if e.Config == nil { + return nil, fmt.Errorf("given exec session does not have a configuration block: %w", define.ErrInternal) + } + + output := new(define.InspectExecSession) + output.CanRemove = e.State == define.ExecStateStopped + output.ContainerID = e.ContainerId + if e.Config.DetachKeys != nil { + output.DetachKeys = *e.Config.DetachKeys + } + output.ExitCode = e.ExitCode + output.ID = e.Id + output.OpenStderr = e.Config.AttachStderr + output.OpenStdin = e.Config.AttachStdin + output.OpenStdout = e.Config.AttachStdout + output.Running = e.State == define.ExecStateRunning + output.Pid = e.PID + output.ProcessConfig = new(define.InspectExecProcess) + if len(e.Config.Command) > 0 { + output.ProcessConfig.Entrypoint = e.Config.Command[0] + if len(e.Config.Command) > 1 { + output.ProcessConfig.Arguments = make([]string, 0, len(e.Config.Command)-1) + output.ProcessConfig.Arguments = append(output.ProcessConfig.Arguments, e.Config.Command[1:]...) + } + } + output.ProcessConfig.Privileged = e.Config.Privileged + output.ProcessConfig.Tty = e.Config.Terminal + output.ProcessConfig.User = e.Config.User + + return output, nil +} + +// legacyExecSession contains information on an active exec session. It is a +// holdover from a previous Podman version and is DEPRECATED. +type legacyExecSession struct { + ID string `json:"id"` + Command []string `json:"command"` + PID int `json:"pid"` +} + +func (c *Container) verifyExecConfig(config *ExecConfig) error { + if config == nil { + return fmt.Errorf("must provide a configuration to ExecCreate: %w", define.ErrInvalidArg) + } + if len(config.Command) == 0 { + return fmt.Errorf("must provide a non-empty command to start an exec session: %w", define.ErrInvalidArg) + } + if config.ExitCommandDelay > 0 && len(config.ExitCommand) == 0 { + return fmt.Errorf("must provide a non-empty exit command if giving an exit command delay: %w", define.ErrInvalidArg) + } + return nil +} + +func (c *Container) getUniqueExecSessionID() string { + // Generate an ID for our new exec session + sessionID := stringid.GenerateRandomID() + found := true + // This really ought to be a do-while, but Go doesn't have those... + for found { + found = false + for id := range c.state.ExecSessions { + if id == sessionID { + found = true + break + } + } + if found { + sessionID = stringid.GenerateRandomID() + } + } + return sessionID +} + +func (c *Container) createExecSession(config *ExecConfig) (*ExecSession, error) { + session := new(ExecSession) + session.Id = c.getUniqueExecSessionID() + session.ContainerId = c.ID() + session.State = define.ExecStateCreated + session.Config = new(ExecConfig) + if err := JSONDeepCopy(config, session.Config); err != nil { + return nil, fmt.Errorf("copying exec configuration into exec session: %w", err) + } + + if len(session.Config.ExitCommand) > 0 { + session.Config.ExitCommand = append(session.Config.ExitCommand, []string{session.ID(), c.ID()}...) + } + return session, nil +} + +// ExecCreate creates a new exec session for the container. +// The session is not started. The ID of the new exec session will be returned. +func (c *Container) ExecCreate(config *ExecConfig) (string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return "", err + } + } + + // Verify our config + if err := c.verifyExecConfig(config); err != nil { + return "", err + } + + // Verify that we are in a good state to continue + if !c.ensureState(define.ContainerStateRunning) { + return "", fmt.Errorf("can only create exec sessions on running containers: %w", define.ErrCtrStateInvalid) + } + + // Make our new exec session + session, err := c.createExecSession(config) + if err != nil { + return "", err + } + + if c.state.ExecSessions == nil { + c.state.ExecSessions = make(map[string]*ExecSession) + } + + // Need to add to container state and exec session registry + c.state.ExecSessions[session.ID()] = session + if err := c.save(); err != nil { + return "", err + } + if err := c.runtime.state.AddExecSession(c, session); err != nil { + return "", err + } + + logrus.Infof("Created exec session %s in container %s", session.ID(), c.ID()) + + return session.Id, nil +} + +// Returns a serialized representation of the pid using the PIDHandle package. +// This string can be passed to NewPIDHandleFromString to recreate a PIDHandle +// that reliably refers to the same process as the original. +// +// NOTE that there is a still race in generating the PIDData on start but this +// is as good as we can do currently. Long term we could change conmon to +// send pidfd back directly. +func getPidData(pid int) string { + pidHandle, err := pidhandle.NewPIDHandle(pid) + if err != nil { + logrus.Debugf("getting the PID handle for pid %d: %v", pid, err) + return "" + } + defer pidHandle.Close() + pidData, err := pidHandle.String() + if err != nil { + logrus.Debugf("generating PIDData (%d) failed: %v", pid, err) + } + return pidData +} + +// ExecStart starts an exec session in the container, but does not attach to it. +// Returns immediately upon starting the exec session, unlike other ExecStart +// functions, which will only return when the exec session exits. +func (c *Container) ExecStart(sessionID string) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + // Verify that we are in a good state to continue + if !c.ensureState(define.ContainerStateRunning) { + return fmt.Errorf("can only start exec sessions when their container is running: %w", define.ErrCtrStateInvalid) + } + + session, ok := c.state.ExecSessions[sessionID] + if !ok { + return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession) + } + + if session.State != define.ExecStateCreated { + return fmt.Errorf("can only start created exec sessions, while container %s session %s state is %q: %w", c.ID(), session.ID(), session.State.String(), define.ErrExecSessionStateInvalid) + } + + logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID()) + + opts, err := prepareForExec(c, session) + if err != nil { + return err + } + + pid, err := c.ociRuntime.ExecContainerDetached(c, session.ID(), opts, session.Config.AttachStdin) + if err != nil { + return err + } + + c.newContainerEvent(events.Exec) + logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID()) + + // Update and save session to reflect PID/running + session.PID = pid + session.State = define.ExecStateRunning + session.PIDData = getPidData(pid) + + return c.save() +} + +// execStartAndAttach starts and attaches to an exec session in a container. +// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty +func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *resize.TerminalSize, isHealthcheck bool) error { + unlock := true + if !c.batched { + c.lock.Lock() + defer func() { + if unlock { + c.lock.Unlock() + } + }() + + if err := c.syncContainer(); err != nil { + return err + } + } + + // Verify that we are in a good state to continue + if !c.ensureState(define.ContainerStateRunning) { + return fmt.Errorf("can only start exec sessions when their container is running: %w", define.ErrCtrStateInvalid) + } + + session, ok := c.state.ExecSessions[sessionID] + if !ok { + return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession) + } + + if session.State != define.ExecStateCreated { + return fmt.Errorf("can only start created exec sessions, while container %s session %s state is %q: %w", c.ID(), session.ID(), session.State.String(), define.ErrExecSessionStateInvalid) + } + + logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID()) + + opts, err := prepareForExec(c, session) + if err != nil { + return err + } + + pid, attachChan, err := c.ociRuntime.ExecContainer(c, session.ID(), opts, streams, newSize) + if err != nil { + return err + } + + if !isHealthcheck { + c.newContainerEvent(events.Exec) + } + + logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID()) + + var lastErr error + + // Update and save session to reflect PID/running + session.PID = pid + session.State = define.ExecStateRunning + session.PIDData = getPidData(pid) + + if err := c.save(); err != nil { + lastErr = err + } + + // Unlock so other processes can use the container + if !c.batched { + c.lock.Unlock() + } + + tmpErr := <-attachChan + // user detached + if errors.Is(tmpErr, define.ErrDetach) { + // ensure we the defer does not unlock as we are not locked here + unlock = false + return tmpErr + } + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + lastErr = tmpErr + + exitCode, exitCodeErr := c.readExecExitCode(session.ID()) + + // Lock again. + // Important: we must lock and sync *before* the above error is handled. + // We need info from the database to handle the error. + if !c.batched { + c.lock.Lock() + } + // We can't reuse the old exec session (things may have changed from + // other use, the container was unlocked). + // So re-sync and get a fresh copy. + // If we can't do this, no point in continuing, any attempt to save + // would write garbage to the DB. + if err := c.syncContainer(); err != nil { + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + // We can't save status, but since the container has + // been entirely removed, we don't have to; exit cleanly + return lastErr + } + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + return fmt.Errorf("syncing container %s state to update exec session %s: %w", c.ID(), sessionID, err) + } + + // Now handle the error from readExecExitCode above. + if exitCodeErr != nil { + newSess, ok := c.state.ExecSessions[sessionID] + if !ok { + // The exec session was removed entirely, probably by + // the cleanup process. When it did so, it should have + // written an event with the exit code. + // Given that, there's nothing more we can do. + logrus.Infof("Container %s exec session %s already removed", c.ID(), session.ID()) + return lastErr + } + + if newSess.State == define.ExecStateStopped { + // Exec session already cleaned up. + // Exit code should be recorded, so it's OK if we were + // not able to read it. + logrus.Infof("Container %s exec session %s already cleaned up", c.ID(), session.ID()) + return lastErr + } + + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + lastErr = exitCodeErr + } + + logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode) + + if err := justWriteExecExitCode(c, session.ID(), exitCode, !isHealthcheck); err != nil { + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + lastErr = err + } + + // Clean up after ourselves + if err := c.cleanupExecBundle(session.ID()); err != nil { + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + lastErr = err + } + + return lastErr +} + +// ExecHTTPStartAndAttach starts and performs an HTTP attach to an exec session. +// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty +func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w http.ResponseWriter, + streams *HTTPAttachStreams, _ *string, cancel <-chan bool, hijackDone chan<- bool, newSize *resize.TerminalSize) error { + // TODO: How do we combine streams with the default streams set in the exec session? + + // Ensure that we don't leak a goroutine here + defer func() { + close(hijackDone) + }() + + unlock := true + if !c.batched { + c.lock.Lock() + defer func() { + if unlock { + c.lock.Unlock() + } + }() + + if err := c.syncContainer(); err != nil { + return err + } + } + + session, ok := c.state.ExecSessions[sessionID] + if !ok { + return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession) + } + + // Verify that we are in a good state to continue + if !c.ensureState(define.ContainerStateRunning) { + return fmt.Errorf("can only start exec sessions when their container is running: %w", define.ErrCtrStateInvalid) + } + + if session.State != define.ExecStateCreated { + return fmt.Errorf("can only start created exec sessions, while container %s session %s state is %q: %w", c.ID(), session.ID(), session.State.String(), define.ErrExecSessionStateInvalid) + } + + logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID()) + + execOpts, err := prepareForExec(c, session) + if err != nil { + session.State = define.ExecStateStopped + session.ExitCode = define.ExecErrorCodeGeneric + + if err := c.save(); err != nil { + logrus.Errorf("Saving container %s exec session %s after failure to prepare: %v", err, c.ID(), session.ID()) + } + + return err + } + + if streams == nil { + streams = new(HTTPAttachStreams) + streams.Stdin = session.Config.AttachStdin + streams.Stdout = session.Config.AttachStdout + streams.Stderr = session.Config.AttachStderr + } + + holdConnOpen := make(chan bool) + + defer func() { + close(holdConnOpen) + }() + + pid, attachChan, err := c.ociRuntime.ExecContainerHTTP(c, session.ID(), execOpts, r, w, streams, cancel, hijackDone, holdConnOpen, newSize) + if err != nil { + session.State = define.ExecStateStopped + session.ExitCode = define.TranslateExecErrorToExitCode(define.ExecErrorCodeGeneric, err) + + if err := c.save(); err != nil { + logrus.Errorf("Saving container %s exec session %s after failure to start: %v", err, c.ID(), session.ID()) + } + + return err + } + + // TODO: Investigate whether more of this can be made common with + // ExecStartAndAttach + + c.newContainerEvent(events.Exec) + logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID()) + + var lastErr error + + session.PID = pid + session.State = define.ExecStateRunning + session.PIDData = getPidData(pid) + + if err := c.save(); err != nil { + lastErr = err + } + + // Unlock so other processes can use the container + if !c.batched { + c.lock.Unlock() + } + + tmpErr := <-attachChan + // user detached + if errors.Is(tmpErr, define.ErrDetach) { + // ensure we the defer does not unlock as we are not locked here + unlock = false + return tmpErr + } + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + lastErr = tmpErr + + exitCode, err := c.readExecExitCode(session.ID()) + if err != nil { + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + lastErr = err + } + + logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode) + + // Lock again + if !c.batched { + c.lock.Lock() + } + + if err := writeExecExitCode(c, session.ID(), exitCode); err != nil { + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + lastErr = err + } + + // Clean up after ourselves + if err := c.cleanupExecBundle(session.ID()); err != nil { + if lastErr != nil { + logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr) + } + lastErr = err + } + + return lastErr +} + +// ExecStop stops an exec session in the container. +// If a timeout is provided, it will be used; otherwise, the timeout will +// default to the stop timeout of the container. +// Cleanup will be invoked automatically once the session is stopped. +func (c *Container) ExecStop(sessionID string, timeout *uint) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + session, ok := c.state.ExecSessions[sessionID] + if !ok { + return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession) + } + + if session.State != define.ExecStateRunning { + return fmt.Errorf("container %s exec session %s is %q, can only stop running sessions: %w", c.ID(), session.ID(), session.State.String(), define.ErrExecSessionStateInvalid) + } + + logrus.Infof("Stopping container %s exec session %s", c.ID(), session.ID()) + + finalTimeout := c.StopTimeout() + if timeout != nil { + finalTimeout = *timeout + } + + // Stop the session + if err := c.ociRuntime.ExecStopContainer(c, session.ID(), finalTimeout); err != nil { + return err + } + + var cleanupErr error + + // Retrieve exit code and update status + if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil { + cleanupErr = err + } + + if err := c.cleanupExecBundle(session.ID()); err != nil { + if cleanupErr != nil { + logrus.Errorf("Stopping container %s exec session %s: %v", c.ID(), session.ID(), cleanupErr) + } + cleanupErr = err + } + + return cleanupErr +} + +// ExecCleanup cleans up an exec session in the container, removing temporary +// files associated with it. +func (c *Container) ExecCleanup(sessionID string) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + session, ok := c.state.ExecSessions[sessionID] + if !ok { + return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession) + } + + if session.State == define.ExecStateRunning { + // Check if the exec session is still running. + alive, err := c.ociRuntime.ExecUpdateStatus(c, session.ID()) + if err != nil { + return err + } + + if alive { + return fmt.Errorf("cannot clean up container %s exec session %s as it is running: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid) + } + + if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil { + return err + } + } + + logrus.Infof("Cleaning up container %s exec session %s", c.ID(), session.ID()) + + return c.cleanupExecBundle(session.ID()) +} + +// ExecRemove removes an exec session in the container. +// If force is given, the session will be stopped first if it is running. +func (c *Container) ExecRemove(sessionID string, force bool) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + session, ok := c.state.ExecSessions[sessionID] + if !ok { + return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession) + } + + logrus.Infof("Removing container %s exec session %s", c.ID(), session.ID()) + + // Update status of exec session if running, so we can check if it + // stopped in the meantime. + if session.State == define.ExecStateRunning { + running, err := c.ociRuntime.ExecUpdateStatus(c, session.ID()) + if err != nil { + return err + } + if !running { + if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil { + return err + } + } + } + + if session.State == define.ExecStateRunning { + if !force { + return fmt.Errorf("container %s exec session %s is still running, cannot remove: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid) + } + + // Stop the session + if err := c.ociRuntime.ExecStopContainer(c, session.ID(), c.StopTimeout()); err != nil { + return err + } + + if err := retrieveAndWriteExecExitCode(c, session.ID()); err != nil { + return err + } + + if err := c.cleanupExecBundle(session.ID()); err != nil { + return err + } + } + + // First remove exec session from DB. + if err := c.runtime.state.RemoveExecSession(session); err != nil { + return err + } + // Next, remove it from the container and save state + delete(c.state.ExecSessions, sessionID) + if err := c.save(); err != nil { + return err + } + + logrus.Debugf("Successfully removed container %s exec session %s", c.ID(), session.ID()) + + return nil +} + +// ExecResize resizes the TTY of the given exec session. Only available if the +// exec session created a TTY. +func (c *Container) ExecResize(sessionID string, newSize resize.TerminalSize) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + session, ok := c.state.ExecSessions[sessionID] + if !ok { + return fmt.Errorf("container %s has no exec session with ID %s: %w", c.ID(), sessionID, define.ErrNoSuchExecSession) + } + + logrus.Infof("Resizing container %s exec session %s to %+v", c.ID(), session.ID(), newSize) + + if session.State != define.ExecStateRunning { + return fmt.Errorf("cannot resize container %s exec session %s as it is not running: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid) + } + + // The exec session may have exited since we last updated. + // Needed to prevent race conditions around short-running exec sessions. + running, err := c.ociRuntime.ExecUpdateStatus(c, session.ID()) + if err != nil { + return err + } + if !running { + session.State = define.ExecStateStopped + + if err := c.save(); err != nil { + logrus.Errorf("Saving state of container %s: %v", c.ID(), err) + } + + return fmt.Errorf("cannot resize container %s exec session %s as it has stopped: %w", c.ID(), session.ID(), define.ErrExecSessionStateInvalid) + } + + // Make sure the exec session is still running. + + return c.ociRuntime.ExecAttachResize(c, sessionID, newSize) +} + +func (c *Container) healthCheckExec(config *ExecConfig, timeout time.Duration, streams *define.AttachStreams) (int, error) { + unlock := true + if !c.batched { + c.lock.Lock() + defer func() { + if unlock { + c.lock.Unlock() + } + }() + + if err := c.syncContainer(); err != nil { + return -1, err + } + } + + if err := c.verifyExecConfig(config); err != nil { + return -1, err + } + + if !c.ensureState(define.ContainerStateRunning) { + return -1, fmt.Errorf("can only create exec sessions on running containers: %w", define.ErrCtrStateInvalid) + } + + session, err := c.createExecSession(config) + if err != nil { + return -1, err + } + + if c.state.ExecSessions == nil { + c.state.ExecSessions = make(map[string]*ExecSession) + } + c.state.ExecSessions[session.ID()] = session + defer delete(c.state.ExecSessions, session.ID()) + + opts, err := prepareForExec(c, session) + if err != nil { + return -1, err + } + defer func() { + // cleanupExecBundle MUST be called with the parent container locked. + if !unlock && !c.batched { + c.lock.Lock() + unlock = true + + if err := c.syncContainer(); err != nil { + logrus.Errorf("Error syncing container %s state: %v", c.ID(), err) + // Normally we'd want to continue here, get rid of the exec directory. + // But the risk of proceeding into a function that can mutate state with a bad state is high. + // Lesser of two evils is to bail and leak a directory. + return + } + } + if err := c.cleanupExecBundle(session.ID()); err != nil { + logrus.Errorf("Container %s light exec session cleanup error: %v", c.ID(), err) + } + }() + + pid, attachErrChan, err := c.ociRuntime.ExecContainer(c, session.ID(), opts, streams, nil) + if err != nil { + return -1, err + } + session.PID = pid + session.PIDData = getPidData(pid) + + if !c.batched { + c.lock.Unlock() + unlock = false + } + + select { + case err = <-attachErrChan: + if err != nil { + return -1, fmt.Errorf("container %s light exec session with pid: %d error: %v", c.ID(), pid, err) + } + case <-time.After(timeout): + if err := c.ociRuntime.ExecStopContainer(c, session.ID(), 0); err != nil { + return -1, err + } + return -1, fmt.Errorf("%v of %s", define.ErrHealthCheckTimeout, c.HealthCheckConfig().Timeout.String()) + } + + return c.readExecExitCode(session.ID()) +} + +func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan resize.TerminalSize) (int, error) { + return c.exec(config, streams, resize, false) +} + +// Exec emulates the old Libpod exec API, providing a single call to create, +// run, and remove an exec session. Returns exit code and error. Exit code is +// not guaranteed to be set sanely if error is not nil. +func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resizeChan <-chan resize.TerminalSize, isHealthcheck bool) (exitCode int, retErr error) { + sessionID, err := c.ExecCreate(config) + if err != nil { + return -1, err + } + cleanup := true + defer func() { + if cleanup { + if err := c.ExecRemove(sessionID, false); err != nil { + if retErr == nil && !errors.Is(err, define.ErrNoSuchExecSession) { + exitCode = -1 + retErr = err + } + } + } + }() + + // Start resizing if we have a resize channel. + // This goroutine may likely leak, given that we cannot close it here. + // Not a big deal, since it should run for as long as the Podman process + // does. Could be a big deal for `podman service` but we don't need this + // API there. + // TODO: Refactor so this is closed here, before we remove the exec + // session. + var size *resize.TerminalSize + if resizeChan != nil { + s := <-resizeChan + size = &s + go func() { + logrus.Debugf("Sending resize events to exec session %s", sessionID) + for resizeRequest := range resizeChan { + if err := c.ExecResize(sessionID, resizeRequest); err != nil { + if errors.Is(err, define.ErrExecSessionStateInvalid) { + // The exec session stopped + // before we could resize. + logrus.Infof("Missed resize on exec session %s, already stopped", sessionID) + } else { + logrus.Warnf("Error resizing exec session %s: %v", sessionID, err) + } + return + } + } + }() + } + + if err := c.execStartAndAttach(sessionID, streams, size, isHealthcheck); err != nil { + // user detached, there will be no exit just exit without reporting an error + if errors.Is(err, define.ErrDetach) { + cleanup = false + return 0, nil + } + return -1, err + } + + session, err := c.execSessionNoCopy(sessionID) + if err != nil { + if errors.Is(err, define.ErrNoSuchExecSession) { + // TODO: If a proper Context is ever plumbed in here, we + // should use it. + // As things stand, though, it's not worth it - this + // should always terminate quickly since it's not + // streaming. + diedEvent, err := c.runtime.GetExecDiedEvent(context.Background(), c.ID(), sessionID) + if err != nil { + return -1, fmt.Errorf("retrieving exec session %s exit code: %w", sessionID, err) + } + return *diedEvent.ContainerExitCode, nil + } + return -1, err + } + return session.ExitCode, nil +} + +// cleanupExecBundle cleans up an exec session after completion. +// MUST BE CALLED with container `c` locked. +// Please be careful when using this function since it might temporarily unlock +// the container when os.RemoveAll($bundlePath) fails with ENOTEMPTY or EBUSY +// errors. +func (c *Container) cleanupExecBundle(sessionID string) (err error) { + path := c.execBundlePath(sessionID) + for range 50 { + err = os.RemoveAll(path) + if err == nil || os.IsNotExist(err) { + return nil + } + if pathErr, ok := err.(*os.PathError); ok { + err = pathErr.Err + if errors.Is(err, unix.ENOTEMPTY) || errors.Is(err, unix.EBUSY) { + // give other processes a chance to use the container + if !c.batched { + if err := c.save(); err != nil { + return err + } + c.lock.Unlock() + } + time.Sleep(time.Millisecond * 100) + if !c.batched { + c.lock.Lock() + if err := c.syncContainer(); err != nil { + return err + } + } + continue + } + } + return + } + return +} + +// the path to a container's exec session bundle +func (c *Container) execBundlePath(sessionID string) string { + return filepath.Join(c.bundlePath(), sessionID) +} + +// Get PID file path for a container's exec session +func (c *Container) execPidPath(sessionID string) string { + return filepath.Join(c.execBundlePath(sessionID), "exec_pid") +} + +// the log path for an exec session +func (c *Container) execLogPath(sessionID string) string { + return filepath.Join(c.execBundlePath(sessionID), "exec_log") +} + +// the socket conmon creates for an exec session +func (c *Container) execAttachSocketPath(sessionID string) (string, error) { + return c.ociRuntime.ExecAttachSocketPath(c, sessionID) +} + +// execExitFileDir gets the path to the container's exit file +func (c *Container) execExitFileDir(sessionID string) string { + return filepath.Join(c.execBundlePath(sessionID), "exit") +} + +// execPersistDir gets the path to the container's persist directory +// The persist directory container the exit file and oom file (if oomkilled) +// of a container +func (c *Container) execPersistDir(sessionID string) string { + return filepath.Join(c.execBundlePath(sessionID), "persist", c.ID()) +} + +// execOCILog returns the file path for the exec sessions oci log +func (c *Container) execOCILog(sessionID string) string { + if !c.ociRuntime.SupportsJSONErrors() { + return "" + } + return filepath.Join(c.execBundlePath(sessionID), "oci-log") +} + +// create a bundle path and associated files for an exec session +func (c *Container) createExecBundle(sessionID string) (retErr error) { + bundlePath := c.execBundlePath(sessionID) + if err := os.MkdirAll(bundlePath, execDirPermission); err != nil { + return err + } + defer func() { + if retErr != nil { + if err := os.RemoveAll(bundlePath); err != nil { + logrus.Warnf("Error removing exec bundle after creation caused another error: %v", err) + } + } + }() + if err := os.MkdirAll(c.execExitFileDir(sessionID), execDirPermission); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + return fmt.Errorf("creating OCI runtime exit file path %s: %w", c.execExitFileDir(sessionID), err) + } + } + if err := os.MkdirAll(c.execPersistDir(sessionID), execDirPermission); err != nil { + return fmt.Errorf("creating OCI runtime persist directory path %s: %w", c.execPersistDir(sessionID), err) + } + return nil +} + +// readExecExitCode reads the exit file for an exec session and returns +// the exit code +func (c *Container) readExecExitCode(sessionID string) (int, error) { + exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID()) + chWait := make(chan error) + defer close(chWait) + + _, err := util.WaitForFile(exitFile, chWait, time.Second*5) + if err != nil { + return -1, err + } + ec, err := os.ReadFile(exitFile) + if err != nil { + return -1, err + } + ecInt, err := strconv.Atoi(string(ec)) + if err != nil { + return -1, err + } + return ecInt, nil +} + +// getExecSessionPID gets the PID of an active exec session +func (c *Container) getExecSessionPID(sessionID string) (int, string, error) { + session, ok := c.state.ExecSessions[sessionID] + if ok { + return session.PID, session.PIDData, nil + } + oldSession, ok := c.state.LegacyExecSessions[sessionID] + if ok { + return oldSession.PID, "", nil + } + + return -1, "", fmt.Errorf("no exec session with ID %s found in container %s: %w", sessionID, c.ID(), define.ErrNoSuchExecSession) +} + +// getKnownExecSessions gets a list of all exec sessions we think are running, +// but does not verify their current state. +// Please use getActiveExecSessions() outside of container_exec.go, as this +// function performs further checks to return an accurate list. +func (c *Container) getKnownExecSessions() []string { + knownSessions := []string{} + // First check legacy sessions. + // TODO: This is DEPRECATED and will be removed in a future major + // release. + for sessionID := range c.state.LegacyExecSessions { + knownSessions = append(knownSessions, sessionID) + } + // Next check new exec sessions, but only if in running state + for sessionID, session := range c.state.ExecSessions { + if session.State == define.ExecStateRunning { + knownSessions = append(knownSessions, sessionID) + } + } + + return knownSessions +} + +// getActiveExecSessions checks if there are any active exec sessions in the +// current container. Returns an array of active exec sessions. +// Will continue through errors where possible. +// Currently handles both new and legacy, deprecated exec sessions. +func (c *Container) getActiveExecSessions() ([]string, error) { + activeSessions := []string{} + knownSessions := c.getKnownExecSessions() + + // Instead of saving once per iteration, do it once at the end. + var lastErr error + needSave := false + for _, id := range knownSessions { + alive, err := c.ociRuntime.ExecUpdateStatus(c, id) + if err != nil { + if lastErr != nil { + logrus.Errorf("Checking container %s exec sessions: %v", c.ID(), lastErr) + } + lastErr = err + continue + } + if !alive { + _, isLegacy := c.state.LegacyExecSessions[id] + if isLegacy { + delete(c.state.LegacyExecSessions, id) + needSave = true + } else { + session := c.state.ExecSessions[id] + exitCode, err := c.readExecExitCode(session.ID()) + if err != nil { + if lastErr != nil { + logrus.Errorf("Checking container %s exec sessions: %v", c.ID(), lastErr) + } + lastErr = err + } + session.ExitCode = exitCode + session.PID = 0 + session.PIDData = "" + session.State = define.ExecStateStopped + + c.newExecDiedEvent(session.ID(), exitCode) + + needSave = true + } + if err := c.cleanupExecBundle(id); err != nil { + if lastErr != nil { + logrus.Errorf("Checking container %s exec sessions: %v", c.ID(), lastErr) + } + lastErr = err + } + } else { + activeSessions = append(activeSessions, id) + } + } + if needSave { + if err := c.save(); err != nil { + if lastErr != nil { + logrus.Errorf("Reaping exec sessions for container %s: %v", c.ID(), lastErr) + } + lastErr = err + } + } + + return activeSessions, lastErr +} + +// removeAllExecSessions stops and removes all the container's exec sessions +func (c *Container) removeAllExecSessions() error { + knownSessions := c.getKnownExecSessions() + + logrus.Debugf("Removing all exec sessions for container %s", c.ID()) + + var lastErr error + for _, id := range knownSessions { + if err := c.ociRuntime.ExecStopContainer(c, id, c.StopTimeout()); err != nil { + if lastErr != nil { + logrus.Errorf("Stopping container %s exec sessions: %v", c.ID(), lastErr) + } + lastErr = err + continue + } + + if err := c.cleanupExecBundle(id); err != nil { + if lastErr != nil { + logrus.Errorf("Stopping container %s exec sessions: %v", c.ID(), lastErr) + } + lastErr = err + } + } + // Delete all exec sessions + if err := c.runtime.state.RemoveContainerExecSessions(c); err != nil { + if !errors.Is(err, define.ErrCtrRemoved) { + if lastErr != nil { + logrus.Errorf("Stopping container %s exec sessions: %v", c.ID(), lastErr) + } + lastErr = err + } + } + c.state.ExecSessions = nil + c.state.LegacyExecSessions = nil + + return lastErr +} + +// Make an ExecOptions struct to start the OCI runtime and prepare its exec +// bundle. +func prepareForExec(c *Container, session *ExecSession) (*ExecOptions, error) { + if err := c.createExecBundle(session.ID()); err != nil { + return nil, err + } + + opts := new(ExecOptions) + opts.Cmd = session.Config.Command + opts.Env = session.Config.Environment + opts.Terminal = session.Config.Terminal + opts.Cwd = session.Config.WorkDir + opts.User = session.Config.User + opts.PreserveFDs = session.Config.PreserveFDs + opts.PreserveFD = session.Config.PreserveFD + opts.DetachKeys = session.Config.DetachKeys + opts.ExitCommand = session.Config.ExitCommand + opts.ExitCommandDelay = session.Config.ExitCommandDelay + opts.Privileged = session.Config.Privileged + + return opts, nil +} + +// Write an exec session's exit code to the database +func writeExecExitCode(c *Container, sessionID string, exitCode int) error { + // We can't reuse the old exec session (things may have changed from + // under use, the container was unlocked). + // So re-sync and get a fresh copy. + // If we can't do this, no point in continuing, any attempt to save + // would write garbage to the DB. + if err := c.syncContainer(); err != nil { + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + // Container's entirely removed. We can't save status, + // but the container's entirely removed, so we don't + // need to. Exit without error. + return nil + } + return fmt.Errorf("syncing container %s state to remove exec session %s: %w", c.ID(), sessionID, err) + } + + return justWriteExecExitCode(c, sessionID, exitCode, true) +} + +func retrieveAndWriteExecExitCode(c *Container, sessionID string) error { + exitCode, err := c.readExecExitCode(sessionID) + if err != nil { + return err + } + + return justWriteExecExitCode(c, sessionID, exitCode, true) +} + +func justWriteExecExitCode(c *Container, sessionID string, exitCode int, emitEvent bool) error { + // Write an event first + if emitEvent { + c.newExecDiedEvent(sessionID, exitCode) + } + + session, ok := c.state.ExecSessions[sessionID] + if !ok { + // Exec session already removed. + logrus.Infof("Container %s exec session %s already removed from database", c.ID(), sessionID) + return nil + } + + session.State = define.ExecStateStopped + session.ExitCode = exitCode + session.PID = 0 + session.PIDData = "" + + // Finally, save our changes. + return c.save() +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/container_freebsd.go new file mode 100644 index 00000000..51d8e9bf --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_freebsd.go @@ -0,0 +1,13 @@ +//go:build !remote + +package libpod + +func networkDisabled(c *Container) (bool, error) { + if c.config.CreateNetNS { + return false, nil + } + if !c.config.PostConfigureNetNS { + return c.state.NetNS != "", nil + } + return false, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_graph.go b/vendor/github.com/containers/podman/v5/libpod/container_graph.go new file mode 100644 index 00000000..b2c3afe0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_graph.go @@ -0,0 +1,516 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/parallel" + "github.com/containers/podman/v5/pkg/syncmap" + "github.com/sirupsen/logrus" +) + +type containerNode struct { + lock sync.Mutex + id string + container *Container + dependsOn []*containerNode + dependedOn []*containerNode +} + +// ContainerGraph is a dependency graph based on a set of containers. +type ContainerGraph struct { + nodes map[string]*containerNode + noDepNodes []*containerNode + notDependedOnNodes map[string]*containerNode +} + +// DependencyMap returns the dependency graph as map with the key being a +// container and the value being the containers the key depends on. +func (cg *ContainerGraph) DependencyMap() (dependencies map[*Container][]*Container) { + dependencies = make(map[*Container][]*Container) + for _, node := range cg.nodes { + dependsOn := make([]*Container, len(node.dependsOn)) + for i, d := range node.dependsOn { + dependsOn[i] = d.container + } + dependencies[node.container] = dependsOn + } + return dependencies +} + +// BuildContainerGraph builds a dependency graph based on the container slice. +func BuildContainerGraph(ctrs []*Container) (*ContainerGraph, error) { + graph := new(ContainerGraph) + graph.nodes = make(map[string]*containerNode) + graph.notDependedOnNodes = make(map[string]*containerNode) + + // Start by building all nodes, with no edges + for _, ctr := range ctrs { + ctrNode := new(containerNode) + ctrNode.id = ctr.ID() + ctrNode.container = ctr + + graph.nodes[ctr.ID()] = ctrNode + graph.notDependedOnNodes[ctr.ID()] = ctrNode + } + + // Now add edges based on dependencies + for _, node := range graph.nodes { + deps := node.container.Dependencies() + for _, dep := range deps { + // Get the dep's node + depNode, ok := graph.nodes[dep] + if !ok { + return nil, fmt.Errorf("container %s depends on container %s not found in input list: %w", node.id, dep, define.ErrNoSuchCtr) + } + + // Add the dependent node to the node's dependencies + // And add the node to the dependent node's dependedOn + node.dependsOn = append(node.dependsOn, depNode) + depNode.dependedOn = append(depNode.dependedOn, node) + + // The dependency now has something depending on it + delete(graph.notDependedOnNodes, dep) + } + + // Maintain a list of nodes with no dependencies + // (no edges coming from them) + if len(deps) == 0 { + graph.noDepNodes = append(graph.noDepNodes, node) + } + } + + // Need to do cycle detection + // We cannot start or stop if there are cyclic dependencies + cycle, err := detectCycles(graph) + if err != nil { + return nil, err + } else if cycle { + return nil, fmt.Errorf("cycle found in container dependency graph: %w", define.ErrInternal) + } + + return graph, nil +} + +// Detect cycles in a container graph using Tarjan's strongly connected +// components algorithm +// Return true if a cycle is found, false otherwise +func detectCycles(graph *ContainerGraph) (bool, error) { + type nodeInfo struct { + index int + lowLink int + onStack bool + } + + index := 0 + + nodes := make(map[string]*nodeInfo) + stack := make([]*containerNode, 0, len(graph.nodes)) + + var strongConnect func(*containerNode) (bool, error) + strongConnect = func(node *containerNode) (bool, error) { + logrus.Debugf("Strongconnecting node %s", node.id) + + info := new(nodeInfo) + info.index = index + info.lowLink = index + index++ + + nodes[node.id] = info + + stack = append(stack, node) + + info.onStack = true + + logrus.Debugf("Pushed %s onto stack", node.id) + + // Work through all nodes we point to + for _, successor := range node.dependsOn { + if _, ok := nodes[successor.id]; !ok { + logrus.Debugf("Recursing to successor node %s", successor.id) + + cycle, err := strongConnect(successor) + if err != nil { + return false, err + } else if cycle { + return true, nil + } + + successorInfo := nodes[successor.id] + if successorInfo.lowLink < info.lowLink { + info.lowLink = successorInfo.lowLink + } + } else { + successorInfo := nodes[successor.id] + if successorInfo.index < info.lowLink && successorInfo.onStack { + info.lowLink = successorInfo.index + } + } + } + + if info.lowLink == info.index { + l := len(stack) + if l == 0 { + return false, fmt.Errorf("empty stack in detectCycles: %w", define.ErrInternal) + } + + // Pop off the stack + topOfStack := stack[l-1] + stack = stack[:l-1] + + // Popped item is no longer on the stack, mark as such + topInfo, ok := nodes[topOfStack.id] + if !ok { + return false, fmt.Errorf("finding node info for %s: %w", topOfStack.id, define.ErrInternal) + } + topInfo.onStack = false + + logrus.Debugf("Finishing node %s. Popped %s off stack", node.id, topOfStack.id) + + // If the top of the stack is not us, we have found a + // cycle + if topOfStack.id != node.id { + return true, nil + } + } + + return false, nil + } + + for id, node := range graph.nodes { + if _, ok := nodes[id]; !ok { + cycle, err := strongConnect(node) + if err != nil { + return false, err + } else if cycle { + return true, nil + } + } + } + + return false, nil +} + +// Visit a node on a container graph and start the container, or set an error if +// a dependency failed to start. if restart is true, startNode will restart the node instead of starting it. +func startNode(ctx context.Context, node *containerNode, setError bool, ctrErrors map[string]error, ctrsVisited map[string]bool, restart bool) { + // First, check if we have already visited the node + if ctrsVisited[node.id] { + return + } + + // If setError is true, a dependency of us failed + // Mark us as failed and recurse + if setError { + // Mark us as visited, and set an error + ctrsVisited[node.id] = true + ctrErrors[node.id] = fmt.Errorf("a dependency of container %s failed to start: %w", node.id, define.ErrCtrStateInvalid) + + // Hit anyone who depends on us, and set errors on them too + for _, successor := range node.dependedOn { + startNode(ctx, successor, true, ctrErrors, ctrsVisited, restart) + } + + return + } + + // Have all our dependencies started? + // If not, don't visit the node yet + depsVisited := true + for _, dep := range node.dependsOn { + depsVisited = depsVisited && ctrsVisited[dep.id] + } + if !depsVisited { + // Don't visit us yet, all dependencies are not up + // We'll hit the dependencies eventually, and when we do it will + // recurse here + return + } + + // Going to try to start the container, mark us as visited + ctrsVisited[node.id] = true + + ctrErrored := false + + // Check if dependencies are running + // Graph traversal means we should have started them + // But they could have died before we got here + // Does not require that the container be locked, we only need to lock + // the dependencies + depsStopped, err := node.container.checkDependenciesRunning() + if err != nil { + ctrErrors[node.id] = err + ctrErrored = true + } else if len(depsStopped) > 0 { + // Our dependencies are not running + depsList := strings.Join(depsStopped, ",") + ctrErrors[node.id] = fmt.Errorf("the following dependencies of container %s are not running: %s: %w", node.id, depsList, define.ErrCtrStateInvalid) + ctrErrored = true + } + + // Lock before we start + node.container.lock.Lock() + + // Sync the container to pick up current state + if !ctrErrored { + if err := node.container.syncContainer(); err != nil { + ctrErrored = true + ctrErrors[node.id] = err + } + } + + // Start the container (only if it is not running) + if !ctrErrored && len(node.container.config.InitContainerType) < 1 { + if !restart && node.container.state.State != define.ContainerStateRunning { + if err := node.container.initAndStart(ctx); err != nil { + ctrErrored = true + ctrErrors[node.id] = err + } + } + if restart && node.container.state.State != define.ContainerStatePaused && node.container.state.State != define.ContainerStateUnknown { + if err := node.container.restartWithTimeout(ctx, node.container.config.StopTimeout); err != nil { + ctrErrored = true + ctrErrors[node.id] = err + } + } + } + + node.container.lock.Unlock() + + // Recurse to anyone who depends on us and start them + for _, successor := range node.dependedOn { + startNode(ctx, successor, ctrErrored, ctrErrors, ctrsVisited, restart) + } +} + +// Contains all details required for traversing the container graph. +type nodeTraversal struct { + // Optional. but *MUST* be locked. + // Should NOT be changed once a traversal is started. + pod *Pod + // Function to execute on the individual container being acted on. + // Should NOT be changed once a traversal is started. + actionFunc func(ctr *Container, pod *Pod) error + // Shared set of errors for all containers currently acted on. + ctrErrors *syncmap.Map[string, error] + // Shared set of what containers have been visited. + ctrsVisited *syncmap.Map[string, bool] +} + +// Perform a traversal of the graph in an inwards direction - meaning from nodes +// with no dependencies, recursing inwards to the nodes they depend on. +// Safe to run in parallel on multiple nodes. +func traverseNodeInwards(node *containerNode, nodeDetails *nodeTraversal, setError bool) { + node.lock.Lock() + + // If we already visited this node, we're done. + visited := nodeDetails.ctrsVisited.Exists(node.id) + if visited { + node.lock.Unlock() + return + } + + // Someone who depends on us failed. + // Mark us as failed and recurse. + if setError { + nodeDetails.ctrsVisited.Put(node.id, true) + nodeDetails.ctrErrors.Put(node.id, fmt.Errorf("a container that depends on container %s could not be stopped: %w", node.id, define.ErrCtrStateInvalid)) + + node.lock.Unlock() + + // Hit anyone who depends on us, set errors there as well. + for _, successor := range node.dependsOn { + traverseNodeInwards(successor, nodeDetails, true) + } + + return + } + + // Does anyone still depend on us? + // Cannot stop if true. Once all our dependencies have been stopped, + // we will be stopped. + for _, dep := range node.dependedOn { + // The container that depends on us hasn't been removed yet. + // OK to continue on + ok := nodeDetails.ctrsVisited.Exists(dep.id) + if !ok { + node.lock.Unlock() + return + } + } + + ctrErrored := false + if err := nodeDetails.actionFunc(node.container, nodeDetails.pod); err != nil { + ctrErrored = true + nodeDetails.ctrErrors.Put(node.id, err) + } + + // Mark as visited *only after* finished with operation. + // This ensures that the operation has completed, one way or the other. + // If an error was set, only do this after the viral ctrErrored + // propagates in traverseNodeInwards below. + // Same with the node lock - we don't want to release it until we are + // marked as visited. + if !ctrErrored { + nodeDetails.ctrsVisited.Put(node.id, true) + + node.lock.Unlock() + } + + // Recurse to anyone who we depend on and work on them + for _, successor := range node.dependsOn { + traverseNodeInwards(successor, nodeDetails, ctrErrored) + } + + // If we propagated an error, finally mark us as visited here, after + // all nodes we traverse to have already been marked failed. + // If we don't do this, there is a race condition where a node could try + // and perform its operation before it was marked failed by the + // traverseNodeInwards triggered by this process. + if ctrErrored { + nodeDetails.ctrsVisited.Put(node.id, true) + + node.lock.Unlock() + } +} + +// Stop all containers in the given graph, assumed to be a graph of pod. +// Pod is mandatory and should be locked. +func stopContainerGraph(ctx context.Context, graph *ContainerGraph, pod *Pod, timeout *uint, cleanup bool) (map[string]error, error) { + // Are there actually any containers in the graph? + // If not, return immediately. + if len(graph.nodes) == 0 { + return map[string]error{}, nil + } + + nodeDetails := new(nodeTraversal) + nodeDetails.pod = pod + nodeDetails.ctrErrors = syncmap.New[string, error]() + nodeDetails.ctrsVisited = syncmap.New[string, bool]() + + traversalFunc := func(ctr *Container, _ *Pod) error { + ctr.lock.Lock() + defer ctr.lock.Unlock() + + if err := ctr.syncContainer(); err != nil { + return err + } + + realTimeout := ctr.config.StopTimeout + if timeout != nil { + realTimeout = *timeout + } + + if err := ctr.stop(realTimeout); err != nil && !errors.Is(err, define.ErrCtrStateInvalid) && !errors.Is(err, define.ErrCtrStopped) { + return err + } + + if cleanup { + return ctr.fullCleanup(ctx, false) + } + + return nil + } + nodeDetails.actionFunc = traversalFunc + + doneChans := make([]<-chan error, 0, len(graph.notDependedOnNodes)) + + // Parallel enqueue jobs for all our starting nodes. + if len(graph.notDependedOnNodes) == 0 { + return nil, fmt.Errorf("no containers in pod %s are not dependencies of other containers, unable to stop", pod.ID()) + } + for _, node := range graph.notDependedOnNodes { + doneChan := parallel.Enqueue(ctx, func() error { + traverseNodeInwards(node, nodeDetails, false) + return nil + }) + doneChans = append(doneChans, doneChan) + } + + // We don't care about the returns values, these functions always return nil + // But we do need all of the parallel jobs to terminate. + for _, doneChan := range doneChans { + <-doneChan + } + + return nodeDetails.ctrErrors.Underlying(), nil +} + +// Remove all containers in the given graph +// Pod is optional, and must be locked if given. +func removeContainerGraph(ctx context.Context, graph *ContainerGraph, pod *Pod, timeout *uint, force bool) (map[string]*ContainerNamedVolume, map[string]bool, map[string]error, error) { + // Are there actually any containers in the graph? + // If not, return immediately. + if len(graph.nodes) == 0 { + return nil, nil, nil, nil + } + + nodeDetails := new(nodeTraversal) + nodeDetails.pod = pod + nodeDetails.ctrErrors = syncmap.New[string, error]() + nodeDetails.ctrsVisited = syncmap.New[string, bool]() + + ctrNamedVolumes := syncmap.New[string, *ContainerNamedVolume]() + + traversalFunc := func(ctr *Container, pod *Pod) error { + ctr.lock.Lock() + defer ctr.lock.Unlock() + + if err := ctr.syncContainer(); err != nil { + return err + } + + for _, vol := range ctr.config.NamedVolumes { + ctrNamedVolumes.Put(vol.Name, vol) + } + + if pod != nil && pod.state.InfraContainerID == ctr.ID() { + pod.state.InfraContainerID = "" + if err := pod.save(); err != nil { + return fmt.Errorf("error removing infra container %s from pod %s: %w", ctr.ID(), pod.ID(), err) + } + } + + opts := ctrRmOpts{ + Force: force, + RemovePod: true, + Timeout: timeout, + } + + if _, _, err := ctr.runtime.removeContainer(ctx, ctr, opts); err != nil { + return err + } + + return nil + } + nodeDetails.actionFunc = traversalFunc + + doneChans := make([]<-chan error, 0, len(graph.notDependedOnNodes)) + + // Parallel enqueue jobs for all our starting nodes. + if len(graph.notDependedOnNodes) == 0 { + return nil, nil, nil, fmt.Errorf("no containers in graph are not dependencies of other containers, unable to stop") + } + for _, node := range graph.notDependedOnNodes { + doneChan := parallel.Enqueue(ctx, func() error { + traverseNodeInwards(node, nodeDetails, false) + return nil + }) + doneChans = append(doneChans, doneChan) + } + + // We don't care about the returns values, these functions always return nil + // But we do need all of the parallel jobs to terminate. + for _, doneChan := range doneChans { + <-doneChan + } + + // Safe to use Underlying as the SyncMap passes out of scope as we return + return ctrNamedVolumes.Underlying(), nodeDetails.ctrsVisited.Underlying(), nodeDetails.ctrErrors.Underlying(), nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_inspect.go b/vendor/github.com/containers/podman/v5/libpod/container_inspect.go new file mode 100644 index 00000000..ba7ca3d6 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_inspect.go @@ -0,0 +1,693 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "fmt" + "maps" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/driver" + "github.com/containers/podman/v5/pkg/signal" + "github.com/containers/podman/v5/pkg/util" + "github.com/docker/go-units" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +// inspectLocked inspects a container for low-level information. +// The caller must held c.lock. +func (c *Container) inspectLocked(size bool) (*define.InspectContainerData, error) { + storeCtr, err := c.runtime.store.Container(c.ID()) + if err != nil { + return nil, fmt.Errorf("getting container from store %q: %w", c.ID(), err) + } + layer, err := c.runtime.store.Layer(storeCtr.LayerID) + if err != nil { + return nil, fmt.Errorf("reading information about layer %q: %w", storeCtr.LayerID, err) + } + driverData, err := driver.GetDriverData(c.runtime.store, layer.ID) + if err != nil { + return nil, fmt.Errorf("getting graph driver info %q: %w", c.ID(), err) + } + return c.getContainerInspectData(size, driverData) +} + +// Inspect a container for low-level information +func (c *Container) Inspect(size bool) (*define.InspectContainerData, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + return c.inspectLocked(size) +} + +func (c *Container) volumesFrom() ([]string, error) { + ctrSpec, err := c.specFromState() + if err != nil { + return nil, err + } + if ctrs, ok := ctrSpec.Annotations[define.VolumesFromAnnotation]; ok { + return strings.Split(ctrs, ";"), nil + } + return nil, nil +} + +func (c *Container) getContainerInspectData(size bool, driverData *define.DriverData) (*define.InspectContainerData, error) { + config := c.config + runtimeInfo := c.state + ctrSpec, err := c.specFromState() + if err != nil { + return nil, err + } + + // Process is allowed to be nil in the stateSpec + args := []string{} + if config.Spec.Process != nil { + args = config.Spec.Process.Args + } + var path string + if len(args) > 0 { + path = args[0] + } + if len(args) > 1 { + args = args[1:] + } + + execIDs := []string{} + for id := range c.state.ExecSessions { + execIDs = append(execIDs, id) + } + + resolvPath := "" + hostsPath := "" + hostnamePath := "" + if c.state.BindMounts != nil { + if getPath, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { + resolvPath = getPath + } + if getPath, ok := c.state.BindMounts["/etc/hosts"]; ok { + hostsPath = getPath + } + if getPath, ok := c.state.BindMounts["/etc/hostname"]; ok { + hostnamePath = getPath + } + } + + namedVolumes, mounts := c.SortUserVolumes(ctrSpec) + inspectMounts, err := c.GetMounts(namedVolumes, c.config.ImageVolumes, mounts) + if err != nil { + return nil, err + } + + cgroupPath, err := c.cGroupPath() + if err != nil { + // Handle the case where the container is not running or has no cgroup. + if errors.Is(err, define.ErrNoCgroups) || errors.Is(err, define.ErrCtrStopped) { + cgroupPath = "" + } else { + return nil, err + } + } + + data := &define.InspectContainerData{ + ID: config.ID, + Created: config.CreatedTime, + Path: path, + Args: args, + State: &define.InspectContainerState{ + OciVersion: ctrSpec.Version, + Status: runtimeInfo.State.String(), + Running: runtimeInfo.State == define.ContainerStateRunning, + Paused: runtimeInfo.State == define.ContainerStatePaused, + OOMKilled: runtimeInfo.OOMKilled, + Dead: runtimeInfo.State.String() == "bad state", + Pid: runtimeInfo.PID, + ConmonPid: runtimeInfo.ConmonPID, + ExitCode: runtimeInfo.ExitCode, + Error: runtimeInfo.Error, + StartedAt: runtimeInfo.StartedTime, + FinishedAt: runtimeInfo.FinishedTime, + Checkpointed: runtimeInfo.Checkpointed, + CgroupPath: cgroupPath, + RestoredAt: runtimeInfo.RestoredTime, + CheckpointedAt: runtimeInfo.CheckpointedTime, + Restored: runtimeInfo.Restored, + CheckpointPath: runtimeInfo.CheckpointPath, + CheckpointLog: runtimeInfo.CheckpointLog, + RestoreLog: runtimeInfo.RestoreLog, + StoppedByUser: c.state.StoppedByUser, + }, + Image: config.RootfsImageID, + ImageName: config.RootfsImageName, + Namespace: config.Namespace, + Rootfs: config.Rootfs, + Pod: config.Pod, + ResolvConfPath: resolvPath, + HostnamePath: hostnamePath, + HostsPath: hostsPath, + StaticDir: config.StaticDir, + OCIRuntime: config.OCIRuntime, + ConmonPidFile: config.ConmonPidFile, + PidFile: config.PidFile, + Name: config.Name, + RestartCount: int32(runtimeInfo.RestartCount), + Driver: driverData.Name, + MountLabel: config.MountLabel, + ProcessLabel: config.ProcessLabel, + AppArmorProfile: ctrSpec.Process.ApparmorProfile, + ExecIDs: execIDs, + GraphDriver: driverData, + Mounts: inspectMounts, + Dependencies: c.Dependencies(), + IsInfra: c.IsInfra(), + IsService: c.IsService(), + KubeExitCodePropagation: config.KubeExitCodePropagation.String(), + LockNumber: c.lock.ID(), + UseImageHosts: c.config.UseImageHosts, + UseImageHostname: c.config.UseImageHostname, + } + + if config.RootfsImageID != "" { // May not be set if the container was created with --rootfs + image, _, err := c.runtime.libimageRuntime.LookupImage(config.RootfsImageID, nil) + if err != nil { + return nil, err + } + data.ImageDigest = image.Digest().String() + } + + if ctrSpec.Process.Capabilities != nil { + data.EffectiveCaps = ctrSpec.Process.Capabilities.Effective + data.BoundingCaps = ctrSpec.Process.Capabilities.Bounding + } + + if c.state.ConfigPath != "" { + data.OCIConfigPath = c.state.ConfigPath + } + + // Check if healthcheck is not nil and --no-healthcheck option is not set. + // If --no-healthcheck is set Test will be always set to `[NONE]`, so the + // inspect status should be set to nil. + if c.config.HealthCheckConfig != nil && (len(c.config.HealthCheckConfig.Test) != 1 || c.config.HealthCheckConfig.Test[0] != "NONE") { + // This container has a healthcheck defined in it; we need to add its state + healthCheckState, err := c.readHealthCheckLog() + if err != nil { + // An error here is not considered fatal; no health state will be displayed + logrus.Error(err) + } else { + data.State.Health = &healthCheckState + } + } else { + data.State.Health = nil + } + + networkConfig, err := c.getContainerNetworkInfo() + if err != nil { + return nil, err + } + data.NetworkSettings = networkConfig + // Ports in NetworkSettings includes exposed ports for network modes that are not host, + // and not container. + if c.config.NetNsCtr == "" && c.NetworkMode() != "host" { + addInspectPortsExpose(c.config.ExposedPorts, data.NetworkSettings.Ports) + } + + inspectConfig := c.generateInspectContainerConfig(ctrSpec) + data.Config = inspectConfig + + hostConfig, err := c.generateInspectContainerHostConfig(ctrSpec, namedVolumes, mounts) + if err != nil { + return nil, err + } + data.HostConfig = hostConfig + + if size { + rootFsSize, err := c.rootFsSize() + if err != nil { + logrus.Errorf("Getting rootfs size %q: %v", config.ID, err) + } + data.SizeRootFs = rootFsSize + + rwSize, err := c.rwSize() + if err != nil { + logrus.Errorf("Getting rw size %q: %v", config.ID, err) + } + data.SizeRw = &rwSize + } + return data, nil +} + +// Get inspect-formatted mounts list. +// Only includes user-specified mounts. Only includes bind mounts and named +// volumes, not tmpfs volumes. +func (c *Container) GetMounts(namedVolumes []*ContainerNamedVolume, imageVolumes []*ContainerImageVolume, mounts []spec.Mount) ([]define.InspectMount, error) { + inspectMounts := []define.InspectMount{} + + // No mounts, return early + if len(c.config.UserVolumes) == 0 { + return inspectMounts, nil + } + + for _, volume := range namedVolumes { + mountStruct := define.InspectMount{} + mountStruct.Type = "volume" + mountStruct.Destination = volume.Dest + mountStruct.Name = volume.Name + mountStruct.SubPath = volume.SubPath + + // For src and driver, we need to look up the named + // volume. + volFromDB, err := c.runtime.state.Volume(volume.Name) + if err != nil { + return nil, fmt.Errorf("looking up volume %s in container %s config: %w", volume.Name, c.ID(), err) + } + mountStruct.Driver = volFromDB.Driver() + + mountPoint, err := volFromDB.MountPoint() + if err != nil { + return nil, err + } + mountStruct.Source = mountPoint + + parseMountOptionsForInspect(volume.Options, &mountStruct) + + inspectMounts = append(inspectMounts, mountStruct) + } + + for _, volume := range imageVolumes { + mountStruct := define.InspectMount{} + mountStruct.Type = "image" + mountStruct.Destination = volume.Dest + mountStruct.Source = volume.Source + mountStruct.RW = volume.ReadWrite + mountStruct.SubPath = volume.SubPath + + inspectMounts = append(inspectMounts, mountStruct) + } + + for _, mount := range mounts { + // It's a mount. + // Is it a tmpfs? If so, discard. + if mount.Type == define.TypeTmpfs { + continue + } + + mountStruct := define.InspectMount{} + mountStruct.Type = define.TypeBind + mountStruct.Source = mount.Source + mountStruct.Destination = mount.Destination + + parseMountOptionsForInspect(mount.Options, &mountStruct) + + inspectMounts = append(inspectMounts, mountStruct) + } + + return inspectMounts, nil +} + +// GetSecurityOptions retrieves and returns the security related annotations and process information upon inspection +func (c *Container) GetSecurityOptions() []string { + ctrSpec := c.config.Spec + SecurityOpt := []string{} + if ctrSpec.Process != nil { + if ctrSpec.Process.NoNewPrivileges { + SecurityOpt = append(SecurityOpt, "no-new-privileges") + } + } + if label, ok := ctrSpec.Annotations[define.InspectAnnotationLabel]; ok { + SecurityOpt = append(SecurityOpt, fmt.Sprintf("label=%s", label)) + } + if seccomp, ok := ctrSpec.Annotations[define.InspectAnnotationSeccomp]; ok { + SecurityOpt = append(SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp)) + } + if apparmor, ok := ctrSpec.Annotations[define.InspectAnnotationApparmor]; ok { + SecurityOpt = append(SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor)) + } + if c.config.Spec != nil && c.config.Spec.Linux != nil && c.config.Spec.Linux.MaskedPaths == nil { + SecurityOpt = append(SecurityOpt, "unmask=all") + } + + return SecurityOpt +} + +// Parse mount options so we can populate them in the mount structure. +// The mount passed in will be modified. +func parseMountOptionsForInspect(options []string, mount *define.InspectMount) { + isRW := true + mountProp := "" + zZ := "" + otherOpts := []string{} + + // Some of these may be overwritten if the user passes us garbage opts + // (for example, [ro,rw]) + // We catch these on the Podman side, so not a problem there, but other + // users of libpod who do not properly validate mount options may see + // this. + // Not really worth dealing with on our end - garbage in, garbage out. + for _, opt := range options { + switch opt { + case "ro": + isRW = false + case "rw": + // Do nothing, silently discard + case "shared", "slave", "private", "rshared", "rslave", "rprivate", "unbindable", "runbindable": + mountProp = opt + case "z", "Z": + zZ = opt + default: + otherOpts = append(otherOpts, opt) + } + } + + mount.RW = isRW + mount.Propagation = mountProp + mount.Mode = zZ + mount.Options = otherOpts +} + +// Generate the InspectContainerConfig struct for the Config field of Inspect. +func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.InspectContainerConfig { + ctrConfig := new(define.InspectContainerConfig) + + ctrConfig.Hostname = c.Hostname() + ctrConfig.User = c.config.User + if spec.Process != nil { + ctrConfig.Tty = spec.Process.Terminal + ctrConfig.Env = append([]string{}, spec.Process.Env...) + + // finds all secrets mounted as env variables and hides the value + // the inspect command should not display it + envSecrets := c.config.EnvSecrets + for envIndex, envValue := range ctrConfig.Env { + // env variables come in the style `name=value` + envName := strings.Split(envValue, "=")[0] + + envSecret, ok := envSecrets[envName] + if ok { + ctrConfig.Env[envIndex] = envSecret.Name + "=*******" + } + } + + ctrConfig.WorkingDir = spec.Process.Cwd + } + + ctrConfig.StopTimeout = c.config.StopTimeout + ctrConfig.Timeout = c.config.Timeout + ctrConfig.OpenStdin = c.config.Stdin + ctrConfig.Image = c.config.RootfsImageName + ctrConfig.SystemdMode = c.Systemd() + + // Leave empty is not explicitly overwritten by user + if len(c.config.Command) != 0 { + ctrConfig.Cmd = []string{} + ctrConfig.Cmd = append(ctrConfig.Cmd, c.config.Command...) + } + + // Leave empty if not explicitly overwritten by user + if len(c.config.Entrypoint) != 0 { + ctrConfig.Entrypoint = c.config.Entrypoint + } + + if len(c.config.Labels) != 0 { + ctrConfig.Labels = maps.Clone(c.config.Labels) + } + + if len(spec.Annotations) != 0 { + ctrConfig.Annotations = maps.Clone(spec.Annotations) + } + ctrConfig.StopSignal = signal.ToDockerFormat(c.config.StopSignal) + // TODO: should JSON deep copy this to ensure internal pointers don't + // leak. + ctrConfig.StartupHealthCheck = c.config.StartupHealthCheckConfig + + ctrConfig.Healthcheck = c.config.HealthCheckConfig + + ctrConfig.HealthcheckOnFailureAction = c.config.HealthCheckOnFailureAction.String() + + ctrConfig.HealthLogDestination = c.HealthCheckLogDestination() + + ctrConfig.HealthMaxLogCount = c.HealthCheckMaxLogCount() + + ctrConfig.HealthMaxLogSize = c.HealthCheckMaxLogSize() + + ctrConfig.CreateCommand = c.config.CreateCommand + + ctrConfig.Timezone = c.config.Timezone + for _, secret := range c.config.Secrets { + newSec := define.InspectSecret{} + newSec.Name = secret.Name + newSec.ID = secret.ID + newSec.UID = secret.UID + newSec.GID = secret.GID + newSec.Mode = secret.Mode + ctrConfig.Secrets = append(ctrConfig.Secrets, &newSec) + } + + // Pad Umask to 4 characters + if len(c.config.Umask) < 4 { + pad := strings.Repeat("0", 4-len(c.config.Umask)) + ctrConfig.Umask = pad + c.config.Umask + } else { + ctrConfig.Umask = c.config.Umask + } + + ctrConfig.Passwd = c.config.Passwd + ctrConfig.ChrootDirs = append(ctrConfig.ChrootDirs, c.config.ChrootDirs...) + + ctrConfig.SdNotifyMode = c.config.SdNotifyMode + ctrConfig.SdNotifySocket = c.config.SdNotifySocket + + // Exosed ports consists of all exposed ports and all port mappings for + // this container. It does *NOT* follow to another container if we share + // the network namespace. + exposedPorts := make(map[string]struct{}) + for port, protocols := range c.config.ExposedPorts { + for _, proto := range protocols { + exposedPorts[fmt.Sprintf("%d/%s", port, proto)] = struct{}{} + } + } + for _, mapping := range c.config.PortMappings { + for i := range mapping.Range { + exposedPorts[fmt.Sprintf("%d/%s", mapping.ContainerPort+i, mapping.Protocol)] = struct{}{} + } + } + if len(exposedPorts) > 0 { + ctrConfig.ExposedPorts = exposedPorts + } + + return ctrConfig +} + +// Generate the InspectContainerHostConfig struct for the HostConfig field of +// Inspect. +func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, namedVolumes []*ContainerNamedVolume, mounts []spec.Mount) (*define.InspectContainerHostConfig, error) { + hostConfig := new(define.InspectContainerHostConfig) + + logConfig := new(define.InspectLogConfig) + logConfig.Type = c.config.LogDriver + logConfig.Path = c.config.LogPath + logConfig.Size = units.HumanSize(float64(c.LogSizeMax())) + logConfig.Tag = c.config.LogTag + + hostConfig.LogConfig = logConfig + + restartPolicy := new(define.InspectRestartPolicy) + restartPolicy.Name = c.config.RestartPolicy + if restartPolicy.Name == "" { + restartPolicy.Name = define.RestartPolicyNo + } + restartPolicy.MaximumRetryCount = c.config.RestartRetries + hostConfig.RestartPolicy = restartPolicy + if c.config.NoCgroups { + hostConfig.Cgroups = "disabled" + } else { + hostConfig.Cgroups = "default" + } + + hostConfig.Dns = make([]string, 0, len(c.config.DNSServer)) + for _, dns := range c.config.DNSServer { + hostConfig.Dns = append(hostConfig.Dns, dns.String()) + } + + hostConfig.DnsOptions = make([]string, 0, len(c.config.DNSOption)) + hostConfig.DnsOptions = append(hostConfig.DnsOptions, c.config.DNSOption...) + + hostConfig.DnsSearch = make([]string, 0, len(c.config.DNSSearch)) + hostConfig.DnsSearch = append(hostConfig.DnsSearch, c.config.DNSSearch...) + + hostConfig.ExtraHosts = make([]string, 0, len(c.config.HostAdd)) + hostConfig.ExtraHosts = append(hostConfig.ExtraHosts, c.config.HostAdd...) + + hostConfig.GroupAdd = make([]string, 0, len(c.config.Groups)) + hostConfig.GroupAdd = append(hostConfig.GroupAdd, c.config.Groups...) + + hostConfig.HostsFile = c.config.BaseHostsFile + + if ctrSpec.Process != nil { + if ctrSpec.Process.OOMScoreAdj != nil { + hostConfig.OomScoreAdj = *ctrSpec.Process.OOMScoreAdj + } + } + + hostConfig.SecurityOpt = c.GetSecurityOptions() + + hostConfig.ReadonlyRootfs = ctrSpec.Root.Readonly + hostConfig.ShmSize = c.config.ShmSize + hostConfig.Runtime = "oci" + + // Annotations + if ctrSpec.Annotations != nil { + if len(ctrSpec.Annotations) != 0 { + hostConfig.Annotations = ctrSpec.Annotations + } + + hostConfig.ContainerIDFile = ctrSpec.Annotations[define.InspectAnnotationCIDFile] + if ctrSpec.Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue { + hostConfig.AutoRemove = true + } + if ctrSpec.Annotations[define.InspectAnnotationAutoremoveImage] == define.InspectResponseTrue { + hostConfig.AutoRemoveImage = true + } + if ctrs, ok := ctrSpec.Annotations[define.VolumesFromAnnotation]; ok { + hostConfig.VolumesFrom = strings.Split(ctrs, ";") + } + if ctrSpec.Annotations[define.InspectAnnotationPrivileged] == define.InspectResponseTrue { + hostConfig.Privileged = true + } + if ctrSpec.Annotations[define.InspectAnnotationInit] == define.InspectResponseTrue { + hostConfig.Init = true + } + if ctrSpec.Annotations[define.InspectAnnotationPublishAll] == define.InspectResponseTrue { + hostConfig.PublishAllPorts = true + } + } + + if err := c.platformInspectContainerHostConfig(ctrSpec, hostConfig); err != nil { + return nil, err + } + + // NanoCPUs. + // This is only calculated if CpuPeriod == 100000. + // It is given in nanoseconds, versus the microseconds used elsewhere - + // so multiply by 10000 (not sure why, but 1000 is off by 10). + if hostConfig.CpuPeriod == 100000 { + hostConfig.NanoCpus = 10000 * hostConfig.CpuQuota + } + + // Bind mounts, formatted as src:dst. + // We'll be appending some options that aren't necessarily in the + // original command line... but no helping that from inside libpod. + binds := []string{} + tmpfs := make(map[string]string) + for _, namedVol := range namedVolumes { + if len(namedVol.Options) > 0 { + binds = append(binds, fmt.Sprintf("%s:%s:%s", namedVol.Name, namedVol.Dest, strings.Join(namedVol.Options, ","))) + } else { + binds = append(binds, fmt.Sprintf("%s:%s", namedVol.Name, namedVol.Dest)) + } + } + for _, mount := range mounts { + if mount.Type == define.TypeTmpfs { + tmpfs[mount.Destination] = strings.Join(mount.Options, ",") + } else { + // TODO - maybe we should parse for empty source/destination + // here. Would be confusing if we print just a bare colon. + if len(mount.Options) > 0 { + binds = append(binds, fmt.Sprintf("%s:%s:%s", mount.Source, mount.Destination, strings.Join(mount.Options, ","))) + } else { + binds = append(binds, fmt.Sprintf("%s:%s", mount.Source, mount.Destination)) + } + } + } + hostConfig.Binds = binds + hostConfig.Tmpfs = tmpfs + + // Network mode parsing. + networkMode := c.NetworkMode() + hostConfig.NetworkMode = networkMode + + // Port bindings. + // Only populate if we are creating the network namespace to configure the network. + if c.config.CreateNetNS { + hostConfig.PortBindings = makeInspectPortBindings(c.config.PortMappings) + } else { + hostConfig.PortBindings = make(map[string][]define.InspectHostPort) + } + + // Ulimits + hostConfig.Ulimits = []define.InspectUlimit{} + if ctrSpec.Process != nil { + for _, limit := range ctrSpec.Process.Rlimits { + newLimit := define.InspectUlimit{} + newLimit.Name = limit.Type + newLimit.Soft = int64(limit.Soft) + newLimit.Hard = int64(limit.Hard) + hostConfig.Ulimits = append(hostConfig.Ulimits, newLimit) + } + } + + // Terminal size + // We can't actually get this for now... + // So default to something sane. + // TODO: Populate this. + hostConfig.ConsoleSize = []uint{0, 0} + + return hostConfig, nil +} + +func (c *Container) GetDevices(priv bool, ctrSpec spec.Spec, deviceNodes map[string]string) ([]define.InspectDevice, error) { + devices := []define.InspectDevice{} + if ctrSpec.Linux != nil && !priv { + for _, dev := range ctrSpec.Linux.Devices { + key := fmt.Sprintf("%d:%d", dev.Major, dev.Minor) + if deviceNodes == nil { + nodes, err := util.FindDeviceNodes(false) + if err != nil { + return nil, err + } + deviceNodes = nodes + } + path, ok := deviceNodes[key] + if !ok { + logrus.Warnf("Could not locate device %s on host", key) + continue + } + newDev := define.InspectDevice{} + newDev.PathOnHost = path + newDev.PathInContainer = dev.Path + devices = append(devices, newDev) + } + } + return devices, nil +} + +func blkioDeviceThrottle(deviceNodes map[string]string, devs []spec.LinuxThrottleDevice) ([]define.InspectBlkioThrottleDevice, error) { + out := []define.InspectBlkioThrottleDevice{} + for _, dev := range devs { + key := fmt.Sprintf("%d:%d", dev.Major, dev.Minor) + if deviceNodes == nil { + nodes, err := util.FindDeviceNodes(true) + if err != nil { + return nil, err + } + deviceNodes = nodes + } + path, ok := deviceNodes[key] + if !ok { + logrus.Infof("Could not locate throttle device %s in system devices", key) + continue + } + throttleDev := define.InspectBlkioThrottleDevice{} + throttleDev.Path = path + throttleDev.Rate = dev.Rate + out = append(out, throttleDev) + } + return out, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_inspect_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/container_inspect_freebsd.go new file mode 100644 index 00000000..a35b6754 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_inspect_freebsd.go @@ -0,0 +1,28 @@ +//go:build !remote + +package libpod + +import ( + "github.com/containers/podman/v5/libpod/define" + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +func (c *Container) platformInspectContainerHostConfig(ctrSpec *spec.Spec, hostConfig *define.InspectContainerHostConfig) error { + // Not sure what to put here. FreeBSD jails use pids from the + // global pool but can only see their own pids. + hostConfig.PidMode = "host" + + // UTS namespace mode + hostConfig.UTSMode = c.NamespaceMode(spec.UTSNamespace, ctrSpec) + + // Devices + // Do not include if privileged - assumed that all devices will be + // included. + var err error + hostConfig.Devices, err = c.GetDevices(hostConfig.Privileged, *ctrSpec, map[string]string{}) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_inspect_linux.go b/vendor/github.com/containers/podman/v5/libpod/container_inspect_linux.go new file mode 100644 index 00000000..63608db7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_inspect_linux.go @@ -0,0 +1,346 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "sort" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/util" + "github.com/moby/sys/capability" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/config" + "go.podman.io/storage/types" +) + +func (c *Container) platformInspectContainerHostConfig(ctrSpec *spec.Spec, hostConfig *define.InspectContainerHostConfig) error { + // This is very expensive to initialize. + // So we don't want to initialize it unless we absolutely have to - IE, + // there are things that require a major:minor to path translation. + var deviceNodes map[string]string + + if ctrSpec.Linux != nil { + if ctrSpec.Linux.IntelRdt != nil { + if ctrSpec.Linux.IntelRdt.ClosID != "" { + // container is assigned to a ClosID + hostConfig.IntelRdtClosID = ctrSpec.Linux.IntelRdt.ClosID + } + } + // Resource limits + if ctrSpec.Linux.Resources != nil { + if ctrSpec.Linux.Resources.CPU != nil { + if ctrSpec.Linux.Resources.CPU.Shares != nil { + hostConfig.CpuShares = *ctrSpec.Linux.Resources.CPU.Shares + } + if ctrSpec.Linux.Resources.CPU.Period != nil { + hostConfig.CpuPeriod = *ctrSpec.Linux.Resources.CPU.Period + } + if ctrSpec.Linux.Resources.CPU.Quota != nil { + hostConfig.CpuQuota = *ctrSpec.Linux.Resources.CPU.Quota + } + if ctrSpec.Linux.Resources.CPU.RealtimePeriod != nil { + hostConfig.CpuRealtimePeriod = *ctrSpec.Linux.Resources.CPU.RealtimePeriod + } + if ctrSpec.Linux.Resources.CPU.RealtimeRuntime != nil { + hostConfig.CpuRealtimeRuntime = *ctrSpec.Linux.Resources.CPU.RealtimeRuntime + } + hostConfig.CpusetCpus = ctrSpec.Linux.Resources.CPU.Cpus + hostConfig.CpusetMems = ctrSpec.Linux.Resources.CPU.Mems + } + if ctrSpec.Linux.Resources.Memory != nil { + if ctrSpec.Linux.Resources.Memory.Limit != nil { + hostConfig.Memory = *ctrSpec.Linux.Resources.Memory.Limit + } + if ctrSpec.Linux.Resources.Memory.Reservation != nil { + hostConfig.MemoryReservation = *ctrSpec.Linux.Resources.Memory.Reservation + } + if ctrSpec.Linux.Resources.Memory.Swap != nil { + hostConfig.MemorySwap = *ctrSpec.Linux.Resources.Memory.Swap + } + if ctrSpec.Linux.Resources.Memory.Swappiness != nil { + hostConfig.MemorySwappiness = int64(*ctrSpec.Linux.Resources.Memory.Swappiness) + } else { + // Swappiness has a default of -1 + hostConfig.MemorySwappiness = -1 + } + if ctrSpec.Linux.Resources.Memory.DisableOOMKiller != nil { + hostConfig.OomKillDisable = *ctrSpec.Linux.Resources.Memory.DisableOOMKiller + } + } + if ctrSpec.Linux.Resources.Pids != nil { + hostConfig.PidsLimit = ctrSpec.Linux.Resources.Pids.Limit + } + hostConfig.CgroupConf = ctrSpec.Linux.Resources.Unified + if ctrSpec.Linux.Resources.BlockIO != nil { + if ctrSpec.Linux.Resources.BlockIO.Weight != nil { + hostConfig.BlkioWeight = *ctrSpec.Linux.Resources.BlockIO.Weight + } + hostConfig.BlkioWeightDevice = []define.InspectBlkioWeightDevice{} + for _, dev := range ctrSpec.Linux.Resources.BlockIO.WeightDevice { + key := fmt.Sprintf("%d:%d", dev.Major, dev.Minor) + // TODO: how do we handle LeafWeight vs + // Weight? For now, ignore anything + // without Weight set. + if dev.Weight == nil { + logrus.Infof("Ignoring weight device %s as it lacks a weight", key) + continue + } + if deviceNodes == nil { + nodes, err := util.FindDeviceNodes(true) + if err != nil { + return err + } + deviceNodes = nodes + } + path, ok := deviceNodes[key] + if !ok { + logrus.Infof("Could not locate weight device %s in system devices", key) + continue + } + weightDev := define.InspectBlkioWeightDevice{} + weightDev.Path = path + weightDev.Weight = *dev.Weight + hostConfig.BlkioWeightDevice = append(hostConfig.BlkioWeightDevice, weightDev) + } + + readBps, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleReadBpsDevice) + if err != nil { + return err + } + hostConfig.BlkioDeviceReadBps = readBps + + writeBps, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleWriteBpsDevice) + if err != nil { + return err + } + hostConfig.BlkioDeviceWriteBps = writeBps + + readIops, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleReadIOPSDevice) + if err != nil { + return err + } + hostConfig.BlkioDeviceReadIOps = readIops + + writeIops, err := blkioDeviceThrottle(deviceNodes, ctrSpec.Linux.Resources.BlockIO.ThrottleWriteIOPSDevice) + if err != nil { + return err + } + hostConfig.BlkioDeviceWriteIOps = writeIops + } + } + } + + // Cap add and cap drop. + // We need a default set of capabilities to compare against. + // The OCI generate package has one, and is commonly used, so we'll + // use it. + // Problem: there are 5 sets of capabilities. + // Use the bounding set for this computation, it's the most encompassing + // (but still not perfect). + capAdd := []string{} + capDrop := []string{} + // No point in continuing if we got a spec without a Process block... + if ctrSpec.Process != nil { + // Max an O(1) lookup table for default bounding caps. + boundingCaps := make(map[string]bool) + if !hostConfig.Privileged { + for _, cap := range c.runtime.config.Containers.DefaultCapabilities.Get() { + boundingCaps[cap] = true + } + } else { + // If we are privileged, use all caps. + for _, cap := range capability.ListKnown() { + boundingCaps[fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String()))] = true + } + } + // Iterate through default caps. + // If it's not in default bounding caps, it was added. + // If it is, delete from the default set. Whatever remains after + // we finish are the dropped caps. + for _, cap := range ctrSpec.Process.Capabilities.Bounding { + if _, ok := boundingCaps[cap]; ok { + delete(boundingCaps, cap) + } else { + capAdd = append(capAdd, cap) + } + } + for cap := range boundingCaps { + capDrop = append(capDrop, cap) + } + // Sort CapDrop so it displays in consistent order (GH #9490) + sort.Strings(capDrop) + } + hostConfig.CapAdd = capAdd + hostConfig.CapDrop = capDrop + switch { + case c.config.IPCNsCtr != "": + hostConfig.IpcMode = fmt.Sprintf("container:%s", c.config.IPCNsCtr) + case ctrSpec.Linux != nil: + // Locate the spec's IPC namespace. + // If there is none, it's ipc=host. + // If there is one and it has a path, it's "ns:". + // If no path, it's default - the empty string. + hostConfig.IpcMode = "host" + for _, ns := range ctrSpec.Linux.Namespaces { + if ns.Type == spec.IPCNamespace { + if ns.Path != "" { + hostConfig.IpcMode = fmt.Sprintf("ns:%s", ns.Path) + } else { + switch { + case c.config.NoShm: + hostConfig.IpcMode = "none" + case c.config.NoShmShare: + hostConfig.IpcMode = "private" + default: + hostConfig.IpcMode = "shareable" + } + } + break + } + } + case c.config.NoShm: + hostConfig.IpcMode = "none" + case c.config.NoShmShare: + hostConfig.IpcMode = "private" + } + + // Cgroup namespace mode + cgroupMode := "" + if c.config.CgroupNsCtr != "" { + cgroupMode = fmt.Sprintf("container:%s", c.config.CgroupNsCtr) + } else if ctrSpec.Linux != nil { + // Locate the spec's cgroup namespace + // If there is none, it's cgroup=host. + // If there is one and it has a path, it's "ns:". + // If there is no path, it's private. + for _, ns := range ctrSpec.Linux.Namespaces { + if ns.Type == spec.CgroupNamespace { + if ns.Path != "" { + cgroupMode = fmt.Sprintf("ns:%s", ns.Path) + } else { + cgroupMode = "private" + } + } + } + if cgroupMode == "" { + cgroupMode = "host" + } + } + hostConfig.CgroupMode = cgroupMode + + // Cgroup parent + // Need to check if it's the default, and not print if so. + defaultCgroupParent := "" + switch c.CgroupManager() { + case config.CgroupfsCgroupsManager: + defaultCgroupParent = CgroupfsDefaultCgroupParent + case config.SystemdCgroupsManager: + defaultCgroupParent = SystemdDefaultCgroupParent + } + if c.config.CgroupParent != defaultCgroupParent { + hostConfig.CgroupParent = c.config.CgroupParent + } + hostConfig.CgroupManager = c.CgroupManager() + + // PID namespace mode + pidMode := "" + if c.config.PIDNsCtr != "" { + pidMode = fmt.Sprintf("container:%s", c.config.PIDNsCtr) + } else if ctrSpec.Linux != nil { + // Locate the spec's PID namespace. + // If there is none, it's pid=host. + // If there is one and it has a path, it's "ns:". + // If there is no path, it's default - the empty string. + for _, ns := range ctrSpec.Linux.Namespaces { + if ns.Type == spec.PIDNamespace { + if ns.Path != "" { + pidMode = fmt.Sprintf("ns:%s", ns.Path) + } else { + pidMode = "private" + } + break + } + } + if pidMode == "" { + pidMode = "host" + } + } + hostConfig.PidMode = pidMode + + // UTS namespace mode + utsMode := c.NamespaceMode(spec.UTSNamespace, ctrSpec) + + hostConfig.UTSMode = utsMode + + // User namespace mode + usernsMode := "" + if c.config.UserNsCtr != "" { + usernsMode = fmt.Sprintf("container:%s", c.config.UserNsCtr) + } else if ctrSpec.Linux != nil { + // Locate the spec's user namespace. + // If there is none, it's default - the empty string. + // If there is one, it's "private" if no path, or "ns:" if + // there's a path. + + for _, ns := range ctrSpec.Linux.Namespaces { + if ns.Type == spec.UserNamespace { + if ns.Path != "" { + usernsMode = fmt.Sprintf("ns:%s", ns.Path) + } else { + usernsMode = "private" + } + } + } + } + hostConfig.UsernsMode = usernsMode + if c.config.IDMappings.UIDMap != nil && c.config.IDMappings.GIDMap != nil { + hostConfig.IDMappings = generateIDMappings(c.config.IDMappings) + } + // Devices + // Do not include if privileged - assumed that all devices will be + // included. + var err error + hostConfig.Devices, err = c.GetDevices(hostConfig.Privileged, *ctrSpec, deviceNodes) + if err != nil { + return err + } + + return nil +} + +func generateIDMappings(idMappings types.IDMappingOptions) *define.InspectIDMappings { + var inspectMappings define.InspectIDMappings + for _, uid := range idMappings.UIDMap { + inspectMappings.UIDMap = append(inspectMappings.UIDMap, fmt.Sprintf("%d:%d:%d", uid.ContainerID, uid.HostID, uid.Size)) + } + for _, gid := range idMappings.GIDMap { + inspectMappings.GIDMap = append(inspectMappings.GIDMap, fmt.Sprintf("%d:%d:%d", gid.ContainerID, gid.HostID, gid.Size)) + } + return &inspectMappings +} + +// Return true if the container is running in the host's PID NS. +func (c *Container) inHostPidNS() (bool, error) { + if c.config.PIDNsCtr != "" { + return false, nil + } + ctrSpec, err := c.specFromState() + if err != nil { + return false, err + } + if ctrSpec.Linux != nil { + // Locate the spec's PID namespace. + // If there is none, it's pid=host. + // If there is one and it has a path, it's "ns:". + // If there is no path, it's default - the empty string. + for _, ns := range ctrSpec.Linux.Namespaces { + if ns.Type == spec.PIDNamespace { + return false, nil + } + } + } + return true, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_internal.go b/vendor/github.com/containers/podman/v5/libpod/container_internal.go new file mode 100644 index 00000000..268663da --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_internal.go @@ -0,0 +1,3007 @@ +//go:build !remote + +package libpod + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "io/fs" + "maps" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + "sync" + "syscall" + "time" + + metadata "github.com/checkpoint-restore/checkpointctl/lib" + "github.com/containers/buildah/copier" + "github.com/containers/buildah/pkg/overlay" + butil "github.com/containers/buildah/util" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/libpod/shutdown" + "github.com/containers/podman/v5/pkg/ctime" + "github.com/containers/podman/v5/pkg/domain/entities" + envLib "github.com/containers/podman/v5/pkg/env" + "github.com/containers/podman/v5/pkg/lookup" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/selinux" + "github.com/containers/podman/v5/pkg/systemd/notifyproxy" + "github.com/containers/podman/v5/pkg/util" + "github.com/coreos/go-systemd/v22/daemon" + securejoin "github.com/cyphar/filepath-securejoin" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/etchosts" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/common/pkg/chown" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/hooks" + "go.podman.io/common/pkg/hooks/exec" + "go.podman.io/common/pkg/timezone" + cutil "go.podman.io/common/pkg/util" + "go.podman.io/storage" + "go.podman.io/storage/pkg/chrootarchive" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idmap" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/lockfile" + "go.podman.io/storage/pkg/mount" + "golang.org/x/sys/unix" +) + +const ( + // name of the directory holding the artifacts + artifactsDir = "artifacts" + execDirPermission = 0755 + preCheckpointDir = "pre-checkpoint" +) + +// rootFsSize gets the size of the container, which can be divided notionally +// into two parts. The first is the part of its size that can be directly +// attributed to its base image, if it has one. The second is the set of +// changes that the container has had made relative to that base image. Both +// parts include some ancillary data, and we count that, too. +func (c *Container) rootFsSize() (int64, error) { + if c.config.Rootfs != "" { + return 0, nil + } + if c.runtime.store == nil { + return 0, nil + } + + container, err := c.runtime.store.Container(c.ID()) + if err != nil { + return 0, err + } + + size := int64(0) + if container.ImageID != "" { + size, err = c.runtime.store.ImageSize(container.ImageID) + if err != nil { + return 0, err + } + } + + layerSize, err := c.runtime.store.ContainerSize(c.ID()) + + return size + layerSize, err +} + +// rwSize gets the combined size of the writeable layer and any ancillary data +// for a given container. +func (c *Container) rwSize() (int64, error) { + if c.config.Rootfs != "" { + size, err := util.SizeOfPath(c.config.Rootfs) + return int64(size), err + } + + layerSize, err := c.runtime.store.ContainerSize(c.ID()) + if err != nil { + return 0, err + } + + return layerSize, nil +} + +// bundlePath returns the path to the container's root filesystem - where the OCI spec will be +// placed, amongst other things +func (c *Container) bundlePath() string { + if c.runtime.storageConfig.TransientStore { + return c.state.RunDir + } + return c.config.StaticDir +} + +// ControlSocketPath returns the path to the container's control socket for things like tty +// resizing +func (c *Container) ControlSocketPath() string { + return filepath.Join(c.bundlePath(), "ctl") +} + +// CheckpointVolumesPath returns the path to the directory containing the checkpointed volumes +func (c *Container) CheckpointVolumesPath() string { + return filepath.Join(c.bundlePath(), metadata.CheckpointVolumesDirectory) +} + +// CheckpointPath returns the path to the directory containing the checkpoint +func (c *Container) CheckpointPath() string { + return filepath.Join(c.bundlePath(), metadata.CheckpointDirectory) +} + +// PreCheckPointPath returns the path to the directory containing the pre-checkpoint-images +func (c *Container) PreCheckPointPath() string { + return filepath.Join(c.bundlePath(), preCheckpointDir) +} + +// AttachSocketPath retrieves the path of the container's attach socket +func (c *Container) AttachSocketPath() (string, error) { + return c.ociRuntime.AttachSocketPath(c) +} + +// exitFilePath gets the path to the container's exit file +func (c *Container) exitFilePath() (string, error) { + return c.ociRuntime.ExitFilePath(c) +} + +func (c *Container) oomFilePath() (string, error) { + return c.ociRuntime.OOMFilePath(c) +} + +func (c *Container) persistDirPath() (string, error) { + return c.ociRuntime.PersistDirectoryPath(c) +} + +// Wait for the container's exit file to appear. +// When it does, update our state based on it. +func (c *Container) waitForExitFileAndSync() error { + exitFile, err := c.exitFilePath() + if err != nil { + return err + } + + chWait := make(chan error) + defer close(chWait) + + _, err = cutil.WaitForFile(exitFile, chWait, time.Second*5) + if err != nil { + // Exit file did not appear + // Reset our state + c.state.ExitCode = -1 + c.state.FinishedTime = time.Now() + c.state.State = define.ContainerStateStopped + + if err2 := c.save(); err2 != nil { + logrus.Errorf("Saving container %s state: %v", c.ID(), err2) + } + + return err + } + + if err := c.checkExitFile(); err != nil { + return err + } + + return c.save() +} + +// Handle the container exit file. +// The exit file is used to supply container exit time and exit code. +// This assumes the exit file already exists. +// Also check for an oom file to determine if the container was oom killed or not. +func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error { + c.state.FinishedTime = ctime.Created(fi) + statusCodeStr, err := os.ReadFile(exitFile) + if err != nil { + return fmt.Errorf("failed to read exit file for container %s: %w", c.ID(), err) + } + statusCode, err := strconv.Atoi(string(statusCodeStr)) + if err != nil { + return fmt.Errorf("converting exit status code (%q, err) for container %s to int: %w", + c.ID(), statusCodeStr, err) + } + c.state.ExitCode = int32(statusCode) + + oomFilePath, err := c.oomFilePath() + if err != nil { + return err + } + if err = fileutils.Exists(oomFilePath); err == nil { + c.state.OOMKilled = true + } + + c.state.Exited = true + + // Write an event for the container's death + c.newContainerExitedEvent(c.state.ExitCode) + + return c.runtime.state.AddContainerExitCode(c.ID(), c.state.ExitCode) +} + +func (c *Container) shouldRestart() bool { + if c.config.HealthCheckOnFailureAction == define.HealthCheckOnFailureActionRestart { + isUnhealthy, err := c.isUnhealthy() + if err != nil { + logrus.Errorf("Checking if container is unhealthy: %v", err) + } else if isUnhealthy { + return true + } + } + + // Explicitly stopped by user, do not restart again. + if c.state.StoppedByUser { + return false + } + + // If we did not get a restart policy match, return false + // Do the same if we're not a policy that restarts. + if !c.state.RestartPolicyMatch || + c.config.RestartPolicy == define.RestartPolicyNo || + c.config.RestartPolicy == define.RestartPolicyNone { + return false + } + + // If we're RestartPolicyOnFailure, we need to check retries and exit + // code. + if c.config.RestartPolicy == define.RestartPolicyOnFailure { + if c.state.ExitCode == 0 { + return false + } + + // If we don't have a max retries set, continue + if c.config.RestartRetries > 0 { + if c.state.RestartCount >= c.config.RestartRetries { + return false + } + } + } + return true +} + +// Handle container restart policy. +// This is called when a container has exited, and was not explicitly stopped by +// an API call to stop the container or pod it is in. +func (c *Container) handleRestartPolicy(ctx context.Context) (_ bool, retErr error) { + if !c.shouldRestart() { + return false, nil + } + logrus.Debugf("Restarting container %s due to restart policy %s", c.ID(), c.config.RestartPolicy) + + // Need to check if dependencies are alive. + if err := c.checkDependenciesAndHandleError(); err != nil { + return false, err + } + + if c.config.HealthCheckConfig != nil { + if err := c.removeTransientFiles(ctx, + c.config.StartupHealthCheckConfig != nil && !c.state.StartupHCPassed, + c.state.HCUnitName); err != nil { + return false, err + } + } + + // Is the container running again? + // If so, we don't have to do anything + if c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { + return false, nil + } else if c.state.State == define.ContainerStateUnknown { + return false, fmt.Errorf("invalid container state encountered in restart attempt: %w", define.ErrInternal) + } + + c.newContainerEvent(events.Restart) + + // Increment restart count + c.state.RestartCount++ + logrus.Debugf("Container %s now on retry %d", c.ID(), c.state.RestartCount) + if err := c.save(); err != nil { + return false, err + } + + defer func() { + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("Cleaning up container %s: %v", c.ID(), err) + } + } + }() + + // Always teardown the network, trying to reuse the netns has caused + // a significant amount of bugs in this code here. It also never worked + // for containers with user namespaces. So once and for all simplify this + // by never reusing the netns. Originally this was done to have a faster + // restart of containers but with netavark now we are much faster so it + // shouldn't be that noticeable in practice. It also makes more sense to + // reconfigure the netns as it is likely that the container exited due + // some broken network state in which case reusing would just cause more + // harm than good. + if err := c.cleanupNetwork(); err != nil { + return false, err + } + + if err := c.prepare(); err != nil { + return false, err + } + + if c.state.State == define.ContainerStateStopped { + // Reinitialize the container if we need to + if err := c.reinit(ctx, true); err != nil { + return false, err + } + } else if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { + // Initialize the container + if err := c.init(ctx, true); err != nil { + return false, err + } + } + if err := c.start(); err != nil { + return false, err + } + return true, c.waitForHealthy(ctx) +} + +// Ensure that the container is in a specific state or state. +// Returns true if the container is in one of the given states, +// or false otherwise. +func (c *Container) ensureState(states ...define.ContainerStatus) bool { + return slices.Contains(states, c.state.State) +} + +// Sync this container with on-disk state and runtime status +// Should only be called with container lock held +// This function should suffice to ensure a container's state is accurate and +// it is valid for use. +func (c *Container) syncContainer() error { + if err := c.runtime.state.UpdateContainer(c); err != nil { + return err + } + // If runtime knows about the container, update its status in runtime + // And then save back to disk + if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStateStopped, define.ContainerStateStopping, define.ContainerStatePaused) { + oldState := c.state.State + + if err := c.checkExitFile(); err != nil { + return err + } + + // Only save back to DB if state changed + if c.state.State != oldState { + // Check for a restart policy match + if c.config.RestartPolicy != define.RestartPolicyNone && c.config.RestartPolicy != define.RestartPolicyNo && + (oldState == define.ContainerStateRunning || oldState == define.ContainerStatePaused) && + (c.state.State == define.ContainerStateStopped || c.state.State == define.ContainerStateExited) && + !c.state.StoppedByUser { + c.state.RestartPolicyMatch = true + } + + if err := c.save(); err != nil { + return err + } + } + } + + if !c.valid { + return fmt.Errorf("container %s is not valid: %w", c.ID(), define.ErrCtrRemoved) + } + + return nil +} + +func (c *Container) setupStorageMapping(dest, from *storage.IDMappingOptions) { + *dest = *from + // If we are creating a container inside a pod, we always want to inherit the + // userns settings from the infra container. So clear the auto userns settings + // so that we don't request storage for a new uid/gid map. + if c.PodID() != "" && !c.IsInfra() { + dest.AutoUserNs = false + } + if dest.AutoUserNs { + overrides := c.getUserOverrides() + dest.AutoUserNsOpts.PasswdFile = overrides.ContainerEtcPasswdPath + dest.AutoUserNsOpts.GroupFile = overrides.ContainerEtcGroupPath + if c.config.User != "" { + initialSize := uint32(0) + parts := strings.SplitSeq(c.config.User, ":") + for p := range parts { + s, err := strconv.ParseUint(p, 10, 32) + if err == nil && uint32(s) > initialSize { + initialSize = uint32(s) + } + } + dest.AutoUserNsOpts.InitialSize = initialSize + 1 + } + } else if c.config.Spec.Linux != nil { + dest.UIDMap = nil + for _, r := range c.config.Spec.Linux.UIDMappings { + u := idtools.IDMap{ + ContainerID: int(r.ContainerID), + HostID: int(r.HostID), + Size: int(r.Size), + } + dest.UIDMap = append(dest.UIDMap, u) + } + dest.GIDMap = nil + for _, r := range c.config.Spec.Linux.GIDMappings { + g := idtools.IDMap{ + ContainerID: int(r.ContainerID), + HostID: int(r.HostID), + Size: int(r.Size), + } + dest.GIDMap = append(dest.GIDMap, g) + } + dest.HostUIDMapping = false + dest.HostGIDMapping = false + } +} + +// Create container root filesystem for use +func (c *Container) setupStorage(ctx context.Context) error { + if !c.valid { + return fmt.Errorf("container %s is not valid: %w", c.ID(), define.ErrCtrRemoved) + } + + if c.state.State != define.ContainerStateConfigured { + return fmt.Errorf("container %s must be in Configured state to have storage set up: %w", c.ID(), define.ErrCtrStateInvalid) + } + + // Need both an image ID and image name, plus a bool telling us whether to use the image configuration + if c.config.Rootfs == "" && (c.config.RootfsImageID == "" || c.config.RootfsImageName == "") { + return fmt.Errorf("must provide image ID and image name to use an image: %w", define.ErrInvalidArg) + } + options := storage.ContainerOptions{ + IDMappingOptions: storage.IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + }, + LabelOpts: c.config.LabelOpts, + } + + options.StorageOpt = c.config.StorageOpts + + if c.restoreFromCheckpoint && c.config.ProcessLabel != "" && c.config.MountLabel != "" { + // If restoring from a checkpoint, the root file-system needs + // to be mounted with the same SELinux labels as it was mounted + // previously. But only if both labels have been set. For + // privileged containers or '--ipc host' only ProcessLabel will + // be set and so we will skip it for cases like that. + if options.Flags == nil { + options.Flags = make(map[string]any) + } + options.Flags["ProcessLabel"] = c.config.ProcessLabel + options.Flags["MountLabel"] = c.config.MountLabel + } + if c.config.Privileged { + privOpt := func(opt string) bool { + return slices.Contains([]string{"nodev", "nosuid", "noexec"}, opt) + } + + defOptions, err := storage.GetMountOptions(c.runtime.store.GraphDriverName(), c.runtime.store.GraphOptions()) + if err != nil { + return fmt.Errorf("getting default mount options: %w", err) + } + var newOptions []string + for _, opt := range defOptions { + if !privOpt(opt) { + newOptions = append(newOptions, opt) + } + } + options.MountOpts = newOptions + } + + options.Volatile = c.config.Volatile + + c.setupStorageMapping(&options.IDMappingOptions, &c.config.IDMappings) + + // Unless the user has specified a name, use a randomly generated one. + // Note that name conflicts may occur (see #11735), so we need to loop. + generateName := c.config.Name == "" + var containerInfo ContainerInfo + var containerInfoErr error + for { + if generateName { + name, err := c.runtime.generateName() + if err != nil { + return err + } + c.config.Name = name + } + containerInfo, containerInfoErr = c.runtime.storageService.CreateContainerStorage(ctx, c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, options) + + if !generateName || !errors.Is(containerInfoErr, storage.ErrDuplicateName) { + break + } + } + if containerInfoErr != nil { + if errors.Is(containerInfoErr, storage.ErrDuplicateName) { + if _, err := c.runtime.LookupContainer(c.config.Name); errors.Is(err, define.ErrNoSuchCtr) { + return fmt.Errorf("creating container storage: %w by an external entity", containerInfoErr) + } + } + return fmt.Errorf("creating container storage: %w", containerInfoErr) + } + + c.config.IDMappings.UIDMap = containerInfo.UIDMap + c.config.IDMappings.GIDMap = containerInfo.GIDMap + + processLabel, err := c.processLabel(containerInfo.ProcessLabel) + if err != nil { + return err + } + c.config.ProcessLabel = processLabel + c.config.MountLabel = containerInfo.MountLabel + c.config.StaticDir = containerInfo.Dir + c.state.RunDir = containerInfo.RunDir + + // Set the default Entrypoint and Command + if containerInfo.Config != nil { + // Set CMD in the container to the default configuration only if ENTRYPOINT is not set by the user. + if c.config.Entrypoint == nil && c.config.Command == nil { + c.config.Command = containerInfo.Config.Config.Cmd + } + if c.config.Entrypoint == nil { + c.config.Entrypoint = containerInfo.Config.Config.Entrypoint + } + } + + artifacts := filepath.Join(c.config.StaticDir, artifactsDir) + if err := os.MkdirAll(artifacts, 0o755); err != nil { + return fmt.Errorf("creating artifacts directory: %w", err) + } + + return nil +} + +func (c *Container) processLabel(processLabel string) (string, error) { + if !c.Systemd() && !c.ociRuntime.SupportsKVM() { + return processLabel, nil + } + ctrSpec, err := c.specFromState() + if err != nil { + return "", err + } + label, ok := ctrSpec.Annotations[define.InspectAnnotationLabel] + if !ok || !strings.Contains(label, "type:") { + switch { + case c.ociRuntime.SupportsKVM(): + return selinux.KVMLabel(processLabel) + case c.Systemd(): + return selinux.InitLabel(processLabel) + } + } + return processLabel, nil +} + +// Tear down a container's storage prior to removal +func (c *Container) teardownStorage() error { + if c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { + return fmt.Errorf("cannot remove storage for container %s as it is running or paused: %w", c.ID(), define.ErrCtrStateInvalid) + } + + artifacts := filepath.Join(c.config.StaticDir, artifactsDir) + if err := os.RemoveAll(artifacts); err != nil { + return fmt.Errorf("removing container %s artifacts %q: %w", c.ID(), artifacts, err) + } + + if err := c.cleanupStorage(); err != nil { + return fmt.Errorf("failed to clean up container %s storage: %w", c.ID(), err) + } + + if err := c.runtime.storageService.DeleteContainer(c.ID()); err != nil { + // If the container has already been removed, warn but do not + // error - we wanted it gone, it is already gone. + // Potentially another tool using containers/storage already + // removed it? + if errors.Is(err, storage.ErrNotAContainer) || errors.Is(err, storage.ErrContainerUnknown) { + logrus.Infof("Storage for container %s already removed", c.ID()) + return nil + } + + return fmt.Errorf("removing container %s root filesystem: %w", c.ID(), err) + } + + return nil +} + +// Reset resets state fields to default values. +// It is performed before a refresh and clears the state after a reboot. +// It does not save the results - assumes the database will do that for us. +func resetContainerState(state *ContainerState) { + state.PID = 0 + state.ConmonPID = 0 + state.Mountpoint = "" + state.Mounted = false + // Reset state. + // Almost all states are reset to either Configured or Exited, + // except ContainerStateRemoving which is preserved. + switch state.State { + case define.ContainerStateStopped, define.ContainerStateExited, define.ContainerStateStopping, define.ContainerStateRunning, define.ContainerStatePaused: + // All containers that ran at any point during the last boot + // must be placed in the Exited state. + state.State = define.ContainerStateExited + case define.ContainerStateConfigured, define.ContainerStateCreated: + state.State = define.ContainerStateConfigured + case define.ContainerStateUnknown: + // Something really strange must have happened to get us here. + // Reset to configured, maybe the reboot cleared things up? + state.State = define.ContainerStateConfigured + } + state.ExecSessions = make(map[string]*ExecSession) + state.LegacyExecSessions = nil + state.BindMounts = make(map[string]string) + state.StoppedByUser = false + state.RestartPolicyMatch = false + state.RestartCount = 0 + state.Checkpointed = false + state.Restored = false + state.CheckpointedTime = time.Time{} + state.RestoredTime = time.Time{} + state.CheckpointPath = "" + state.CheckpointLog = "" + state.RestoreLog = "" + state.StartupHCPassed = false + state.StartupHCSuccessCount = 0 + state.StartupHCFailureCount = 0 + state.HCUnitName = "" + state.NetNS = "" + state.NetworkStatus = nil +} + +// Refresh refreshes the container's state after a restart. +// Refresh cannot perform any operations that would lock another container. +// We cannot guarantee any other container has a valid lock at the time it is +// running. +func (c *Container) refresh() error { + // Don't need a full sync, but we do need to update from the database to + // pick up potentially-missing container state + if err := c.runtime.state.UpdateContainer(c); err != nil { + return err + } + + if !c.valid { + return fmt.Errorf("container %s is not valid - may have been removed: %w", c.ID(), define.ErrCtrRemoved) + } + + // We need to get the container's temporary directory from c/storage + // It was lost in the reboot and must be recreated + dir, err := c.runtime.storageService.GetRunDir(c.ID()) + if err != nil { + return fmt.Errorf("retrieving temporary directory for container %s: %w", c.ID(), err) + } + c.state.RunDir = dir + + if len(c.config.IDMappings.UIDMap) != 0 || len(c.config.IDMappings.GIDMap) != 0 { + info, err := os.Stat(c.runtime.config.Engine.TmpDir) + if err != nil { + return err + } + if err := os.Chmod(c.runtime.config.Engine.TmpDir, info.Mode()|0111); err != nil { + return err + } + root := filepath.Join(c.runtime.config.Engine.TmpDir, "containers-root", c.ID()) + if err := os.MkdirAll(root, 0o755); err != nil { + return fmt.Errorf("creating userNS tmpdir for container %s: %w", c.ID(), err) + } + if err := idtools.SafeChown(root, c.RootUID(), c.RootGID()); err != nil { + return err + } + } + + // We need to pick up a new lock + lock, err := c.runtime.lockManager.AllocateAndRetrieveLock(c.config.LockID) + if err != nil { + return fmt.Errorf("acquiring lock %d for container %s: %w", c.config.LockID, c.ID(), err) + } + c.lock = lock + + c.state.NetworkStatus = nil + + // Rewrite the config if necessary. + // Podman 4.0 uses a new port format in the config. + // getContainerConfigFromDB() already converted the old ports to the new one + // but it did not write the config to the db back for performance reasons. + // If a rewrite must happen the config.rewrite field is set to true. + if c.config.rewrite { + // SafeRewriteContainerConfig must be used with care. Make sure to not change config fields by accident. + if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + return fmt.Errorf("failed to rewrite the config for container %s: %w", c.config.ID, err) + } + c.config.rewrite = false + } + + if err := c.save(); err != nil { + return fmt.Errorf("refreshing state for container %s: %w", c.ID(), err) + } + + // Remove ctl and attach files, which may persist across reboot + if err := c.removeConmonFiles(); err != nil { + return err + } + + return nil +} + +// Remove conmon attach socket and terminal resize FIFO +// This is necessary for restarting containers +func (c *Container) removeConmonFiles() error { + // Files are allowed to not exist, so ignore ENOENT + attachFile, err := c.AttachSocketPath() + if err != nil { + return fmt.Errorf("failed to get attach socket path for container %s: %w", c.ID(), err) + } + + if err := os.Remove(attachFile); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("removing container %s attach file: %w", c.ID(), err) + } + + ctlFile := filepath.Join(c.bundlePath(), "ctl") + if err := os.Remove(ctlFile); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("removing container %s ctl file: %w", c.ID(), err) + } + + winszFile := filepath.Join(c.bundlePath(), "winsz") + if err := os.Remove(winszFile); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("removing container %s winsz file: %w", c.ID(), err) + } + + // Remove the exit file so we don't leak memory in tmpfs + exitFile, err := c.exitFilePath() + if err != nil { + return err + } + if err := os.Remove(exitFile); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("removing container %s exit file: %w", c.ID(), err) + } + + // Remove the persist directory + persistDir, err := c.persistDirPath() + if err != nil { + return err + } + if persistDir != "" { + if err := os.RemoveAll(persistDir); err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("removing container %s persist directory: %w", c.ID(), err) + } + } + + return nil +} + +func (c *Container) export(out io.Writer) error { + mountPoint := c.state.Mountpoint + if !c.state.Mounted { + containerMount, err := c.runtime.store.Mount(c.ID(), c.config.MountLabel) + if err != nil { + return fmt.Errorf("mounting container %q: %w", c.ID(), err) + } + mountPoint = containerMount + defer func() { + if _, err := c.runtime.store.Unmount(c.ID(), false); err != nil { + logrus.Errorf("Unmounting container %q: %v", c.ID(), err) + } + }() + } + + input, err := chrootarchive.Tar(mountPoint, nil, mountPoint) + if err != nil { + return fmt.Errorf("reading container directory %q: %w", c.ID(), err) + } + + _, err = io.Copy(out, input) + return err +} + +// Get path of artifact with a given name for this container +func (c *Container) getArtifactPath(name string) string { + return filepath.Join(c.config.StaticDir, artifactsDir, name) +} + +// save container state to the database +func (c *Container) save() error { + if err := c.runtime.state.SaveContainer(c); err != nil { + return fmt.Errorf("saving container %s state: %w", c.ID(), err) + } + return nil +} + +// Checks the container is in the right state, then initializes the container in preparation to start the container. +// If recursive is true, each of the container's dependencies will be started. +// Otherwise, this function will return with error if there are dependencies of this container that aren't running. +func (c *Container) prepareToStart(ctx context.Context, recursive bool) (retErr error) { + // Container must be created or stopped to be started + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateExited) { + // Special case: Let the caller know that is is already running, + // the caller can then decide to ignore/handle the error the way it needs. + if c.state.State == define.ContainerStateRunning { + return fmt.Errorf("container %s: %w", c.ID(), define.ErrCtrStateRunning) + } + return fmt.Errorf("container %s must be in Created or Stopped state to be started: %w", c.ID(), define.ErrCtrStateInvalid) + } + + if !recursive { + if err := c.checkDependenciesAndHandleError(); err != nil { + return err + } + } else { + if err := c.startDependencies(ctx); err != nil { + return err + } + } + + defer func() { + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("Cleaning up container %s: %v", c.ID(), err) + } + } + }() + + if err := c.prepare(); err != nil { + return err + } + + if c.state.State == define.ContainerStateStopped { + // Reinitialize the container if we need to + if err := c.reinit(ctx, false); err != nil { + return err + } + } else if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { + // Or initialize it if necessary + if err := c.init(ctx, false); err != nil { + return err + } + } + return nil +} + +// checks dependencies are running and prints a helpful message +func (c *Container) checkDependenciesAndHandleError() error { + notRunning, err := c.checkDependenciesRunning() + if err != nil { + return fmt.Errorf("checking dependencies for container %s: %w", c.ID(), err) + } + if len(notRunning) > 0 { + depString := strings.Join(notRunning, ",") + return fmt.Errorf("some dependencies of container %s are not started: %s: %w", c.ID(), depString, define.ErrCtrStateInvalid) + } + + return nil +} + +// Recursively start all dependencies of a container so the container can be started. +func (c *Container) startDependencies(ctx context.Context) error { + depCtrIDs := c.Dependencies() + if len(depCtrIDs) == 0 { + return nil + } + + depVisitedCtrs := make(map[string]*Container) + if err := c.getAllDependencies(depVisitedCtrs); err != nil { + return fmt.Errorf("starting dependency for container %s: %w", c.ID(), err) + } + + // Because of how Go handles passing slices through functions, a slice cannot grow between function calls + // without clunky syntax. Circumnavigate this by translating the map to a slice for buildContainerGraph + depCtrs := make([]*Container, 0) + for _, ctr := range depVisitedCtrs { + depCtrs = append(depCtrs, ctr) + } + + // Build a dependency graph of containers + graph, err := BuildContainerGraph(depCtrs) + if err != nil { + return fmt.Errorf("generating dependency graph for container %s: %w", c.ID(), err) + } + + // If there are no containers without dependencies, we can't start + // Error out + if len(graph.noDepNodes) == 0 { + // we have no dependencies that need starting, go ahead and return + if len(graph.nodes) == 0 { + return nil + } + return fmt.Errorf("all dependencies have dependencies of %s: %w", c.ID(), define.ErrNoSuchCtr) + } + + ctrErrors := make(map[string]error) + ctrsVisited := make(map[string]bool) + + // Traverse the graph beginning at nodes with no dependencies + for _, node := range graph.noDepNodes { + startNode(ctx, node, false, ctrErrors, ctrsVisited, false) + } + + if len(ctrErrors) > 0 { + logrus.Errorf("Starting some container dependencies") + for _, e := range ctrErrors { + logrus.Errorf("%q", e) + } + return fmt.Errorf("starting some containers: %w", define.ErrInternal) + } + return nil +} + +// getAllDependencies is a precursor to starting dependencies. +// To start a container with all of its dependencies, we need to recursively find all dependencies +// a container has, as well as each of those containers' dependencies, and so on +// To do so, keep track of containers already visited (so there aren't redundant state lookups), +// and recursively search until we have reached the leafs of every dependency node. +// Since we need to start all dependencies for our original container to successfully start, we propagate any errors +// in looking up dependencies. +// Note: this function is currently meant as a robust solution to a narrow problem: start an infra-container when +// a container in the pod is run. It has not been tested for performance past one level, so expansion of recursive start +// must be tested first. +func (c *Container) getAllDependencies(visited map[string]*Container) error { + depIDs := c.Dependencies() + if len(depIDs) == 0 { + return nil + } + for _, depID := range depIDs { + if _, ok := visited[depID]; !ok { + dep, err := c.runtime.state.Container(depID) + if err != nil { + return err + } + visited[depID] = dep + if err := dep.getAllDependencies(visited); err != nil { + return err + } + } + } + return nil +} + +// Check if a container's dependencies are running +// Returns a []string containing the IDs of dependencies that are not running +func (c *Container) checkDependenciesRunning() ([]string, error) { + deps := c.Dependencies() + notRunning := []string{} + + // We were not passed a set of dependency containers + // Make it ourselves + depCtrs := make(map[string]*Container, len(deps)) + for _, dep := range deps { + // Get the dependency container + depCtr, err := c.runtime.state.Container(dep) + if err != nil { + return nil, fmt.Errorf("retrieving dependency %s of container %s from state: %w", dep, c.ID(), err) + } + + // Check the status + state, err := depCtr.State() + if err != nil { + return nil, fmt.Errorf("retrieving state of dependency %s of container %s: %w", dep, c.ID(), err) + } + if state != define.ContainerStateRunning && !depCtr.config.IsInfra { + notRunning = append(notRunning, dep) + } + depCtrs[dep] = depCtr + } + + return notRunning, nil +} + +func (c *Container) completeNetworkSetup() error { + netDisabled, err := c.NetworkDisabled() + if err != nil { + return err + } + if netDisabled { + // with net=none we still want to set up /etc/hosts + return c.addHosts() + } + if c.config.NetNsCtr != "" { + return nil + } + if c.config.PostConfigureNetNS { + if err := c.syncContainer(); err != nil { + return err + } + if err := c.runtime.setupNetNS(c); err != nil { + return err + } + if err := c.save(); err != nil { + return err + } + } + // add /etc/hosts entries + if err := c.addHosts(); err != nil { + return err + } + + return c.addResolvConf() +} + +// Initialize a container, creating it in the runtime +func (c *Container) init(ctx context.Context, retainRetries bool) error { + // Unconditionally remove conmon temporary files. + // We've been running into far too many issues where they block startup. + if err := c.removeConmonFiles(); err != nil { + return err + } + + // Generate the OCI newSpec + newSpec, cleanupFunc, err := c.generateSpec(ctx) + if err != nil { + return err + } + defer cleanupFunc() + + // Make sure the workdir exists while initializing container + if err := c.resolveWorkDir(); err != nil { + return err + } + + // Save the OCI newSpec to disk + if err := c.saveSpec(newSpec); err != nil { + return err + } + + for _, v := range c.config.NamedVolumes { + if err := c.fixVolumePermissions(v); err != nil { + return err + } + } + + // To ensure that we don't lose track of Conmon if hit by a SIGTERM + // in the middle of setting up the container, inhibit shutdown signals + // until after we save Conmon's PID to the state. + // TODO: This can likely be removed once conmon-rs support merges. + shutdown.Inhibit() + defer shutdown.Uninhibit() + + // If the container is part of a pod, make sure the pod cgroup is created before the container + // so the limits can be applied. + if c.PodID() != "" { + pod, err := c.runtime.LookupPod(c.PodID()) + if err != nil { + return err + } + + if _, err := c.runtime.platformMakePod(pod, &pod.config.ResourceLimits); err != nil { + return err + } + } + + // With the spec complete, do an OCI create + if _, err = c.ociRuntime.CreateContainer(c, nil); err != nil { + return err + } + + logrus.Debugf("Created container %s in OCI runtime", c.ID()) + + // Remove any exec sessions leftover from a potential prior run. + if len(c.state.ExecSessions) > 0 { + if err := c.runtime.state.RemoveContainerExecSessions(c); err != nil { + logrus.Errorf("Removing container %s exec sessions from DB: %v", c.ID(), err) + } + c.state.ExecSessions = make(map[string]*ExecSession) + } + + c.state.Checkpointed = false + c.state.Restored = false + c.state.CheckpointedTime = time.Time{} + c.state.RestoredTime = time.Time{} + c.state.CheckpointPath = "" + c.state.CheckpointLog = "" + c.state.RestoreLog = "" + c.state.ExitCode = 0 + c.state.Exited = false + // Reset any previous errors as we try to init it again, either it works and we don't + // want to keep an old error around or a new error will be written anyway. + c.state.Error = "" + c.state.State = define.ContainerStateCreated + c.state.StoppedByUser = false + c.state.RestartPolicyMatch = false + c.state.StartupHCFailureCount = 0 + c.state.StartupHCSuccessCount = 0 + c.state.StartupHCPassed = false + + if !retainRetries { + c.state.RestartCount = 0 + } + + // bugzilla.redhat.com/show_bug.cgi?id=2144754: + // In case of a restart, make sure to remove the healthcheck log to + // have a clean state. + err = c.writeHealthCheckLog(define.HealthCheckResults{Status: define.HealthCheckReset}) + if err != nil { + return err + } + + if err := c.save(); err != nil { + return err + } + + if c.config.HealthCheckConfig != nil { + timer := c.config.HealthCheckConfig.Interval.String() + if c.config.StartupHealthCheckConfig != nil { + timer = c.config.StartupHealthCheckConfig.Interval.String() + } + if err := c.createTimer(timer, c.config.StartupHealthCheckConfig != nil); err != nil { + return fmt.Errorf("create healthcheck: %w", err) + } + } + + defer c.newContainerEvent(events.Init) + return c.completeNetworkSetup() +} + +// Clean up a container in the OCI runtime. +// Deletes the container in the runtime, and resets its state to Exited. +// The container can be restarted cleanly after this. +func (c *Container) cleanupRuntime(ctx context.Context) error { + // If the container is not ContainerStateStopped or + // ContainerStateCreated, do nothing. + if !c.ensureState(define.ContainerStateStopped, define.ContainerStateCreated) { + return nil + } + + // We may be doing this redundantly for some call paths but we need to + // make sure the exit code is being read at this point. + if err := c.checkExitFile(); err != nil { + return err + } + + // If necessary, delete attach and ctl files + if err := c.removeConmonFiles(); err != nil { + return err + } + + if err := c.delete(ctx); err != nil { + return err + } + + // If we were Stopped, we are now Exited, as we've removed ourself + // from the runtime. + // If we were Created, we are now Configured. + switch c.state.State { + case define.ContainerStateStopped: + c.state.State = define.ContainerStateExited + case define.ContainerStateCreated: + c.state.State = define.ContainerStateConfigured + } + + if c.valid { + if err := c.save(); err != nil { + return err + } + } + + logrus.Debugf("Successfully cleaned up container %s", c.ID()) + + return nil +} + +// Reinitialize a container. +// Deletes and recreates a container in the runtime. +// Should only be done on ContainerStateStopped containers. +// Not necessary for ContainerStateExited - the container has already been +// removed from the runtime, so init() can proceed freely. +func (c *Container) reinit(ctx context.Context, retainRetries bool) error { + logrus.Debugf("Recreating container %s in OCI runtime", c.ID()) + + if err := c.cleanupRuntime(ctx); err != nil { + return err + } + + // Initialize the container again + return c.init(ctx, retainRetries) +} + +// Initialize (if necessary) and start a container +// Performs all necessary steps to start a container that is not running +// Does not lock or check validity, requires to run on the same thread that holds the lock for the container. +func (c *Container) initAndStart(ctx context.Context) (retErr error) { + // If we are ContainerState{Unknown,Removing}, throw an error. + switch c.state.State { + case define.ContainerStateUnknown: + return fmt.Errorf("container %s is in an unknown state: %w", c.ID(), define.ErrCtrStateInvalid) + case define.ContainerStateRemoving: + return fmt.Errorf("cannot start container %s as it is being removed: %w", c.ID(), define.ErrCtrStateInvalid) + } + + // If we are running, do nothing + if c.state.State == define.ContainerStateRunning { + return nil + } + // If we are paused, throw an error + if c.state.State == define.ContainerStatePaused { + return fmt.Errorf("cannot start paused container %s: %w", c.ID(), define.ErrCtrStateInvalid) + } + + defer func() { + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("Cleaning up container %s: %v", c.ID(), err) + } + } + }() + + if err := c.prepare(); err != nil { + return err + } + + // If we are ContainerStateStopped we need to remove from runtime + // And reset to ContainerStateConfigured + if c.state.State == define.ContainerStateStopped { + logrus.Debugf("Recreating container %s in OCI runtime", c.ID()) + + if err := c.reinit(ctx, false); err != nil { + return err + } + } else if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { + if err := c.init(ctx, false); err != nil { + return err + } + } + + // Now start the container + if err := c.start(); err != nil { + return err + } + return c.waitForHealthy(ctx) +} + +// Internal function to start a container without taking the pod lock. +// Please note that this DOES take the container lock. +// Intended to be used in pod-related functions. +func (c *Container) startNoPodLock(ctx context.Context, recursive bool) (finalErr error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + // defer's are executed LIFO so we are locked here + // as long as we call this after the defer unlock() + defer func() { + if finalErr != nil { + if err := saveContainerError(c, finalErr); err != nil { + logrus.Debug(err) + } + } + }() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if err := c.prepareToStart(ctx, recursive); err != nil { + return err + } + + // Start the container + if err := c.start(); err != nil { + return err + } + return c.waitForHealthy(ctx) +} + +// Internal, non-locking function to start a container +func (c *Container) start() error { + if c.config.Spec.Process != nil { + logrus.Debugf("Starting container %s with command %v", c.ID(), c.config.Spec.Process.Args) + } + + if err := c.ociRuntime.StartContainer(c); err != nil { + return err + } + logrus.Debugf("Started container %s", c.ID()) + + c.state.State = define.ContainerStateRunning + + // Unless being ignored, set the MAINPID to conmon. + if c.config.SdNotifyMode != define.SdNotifyModeIgnore { + payload := fmt.Sprintf("MAINPID=%d", c.state.ConmonPID) + if c.config.SdNotifyMode == define.SdNotifyModeConmon { + // Also send the READY message for the "conmon" policy. + payload += "\n" + payload += daemon.SdNotifyReady + } + if err := notifyproxy.SendMessage(c.config.SdNotifySocket, payload); err != nil { + logrus.Errorf("Notifying systemd of Conmon PID: %s", err.Error()) + } else { + logrus.Debugf("Notify sent successfully") + } + } + + // Check if healthcheck is not nil and --no-healthcheck option is not set. + // If --no-healthcheck is set Test will be always set to `[NONE]` so no need + // to update status in such case. + if c.config.HealthCheckConfig != nil && (len(c.config.HealthCheckConfig.Test) != 1 || c.config.HealthCheckConfig.Test[0] != "NONE") { + if err := c.updateHealthStatus(define.HealthCheckStarting); err != nil { + return fmt.Errorf("update healthcheck status: %w", err) + } + if err := c.startTimer(c.config.StartupHealthCheckConfig != nil); err != nil { + return fmt.Errorf("start healthcheck: %w", err) + } + } + + c.newContainerEvent(events.Start) + + return c.save() +} + +// waitForHealthy, when sdNotifyMode == SdNotifyModeHealthy, waits up to the DefaultWaitInterval +// for the container to get into the healthy state and reports the status to the notify socket. +// The function unlocks the container lock, so it must be called from the same thread that locks +// the container. +func (c *Container) waitForHealthy(ctx context.Context) error { + if c.config.SdNotifyMode != define.SdNotifyModeHealthy { + return nil + } + + // Wait for the container to turn healthy before sending the READY + // message. This implies that we need to unlock and re-lock the + // container. + if !c.batched { + c.lock.Unlock() + defer c.lock.Lock() + } + + if _, err := c.WaitForConditionWithInterval(ctx, DefaultWaitInterval, define.HealthCheckHealthy); err != nil { + if errors.Is(err, define.ErrNoSuchCtr) { + return nil + } + return err + } + + if err := notifyproxy.SendMessage(c.config.SdNotifySocket, daemon.SdNotifyReady); err != nil { + logrus.Errorf("Sending READY message after turning healthy: %s", err.Error()) + } else { + logrus.Debugf("Notify sent successfully") + } + return nil +} + +// Whether a container should use `all` when stopping +func (c *Container) stopWithAll() (bool, error) { + // If the container is running in a PID Namespace, then killing the + // primary pid is enough to kill the container. If it is not running in + // a pid namespace then the OCI Runtime needs to kill ALL processes in + // the container's cgroup in order to make sure the container is stopped. + all := !c.hasNamespace(spec.PIDNamespace) + // We can't use --all if Cgroups aren't present. + // Rootless containers with Cgroups v1 and NoCgroups are both cases + // where this can happen. + if all { + if c.config.NoCgroups { + all = false + } else if rootless.IsRootless() { + // Only do this check if we need to + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return false, err + } + if !unified { + all = false + } + } + } + + return all, nil +} + +// Internal, non-locking function to stop container +func (c *Container) stop(timeout uint) error { + logrus.Debugf("Stopping ctr %s (timeout %d)", c.ID(), timeout) + + all, err := c.stopWithAll() + if err != nil { + return err + } + + // OK, the following code looks a bit weird but we have to make sure we can stop + // containers with the restart policy always, to do this we have to set + // StoppedByUser even when there is nothing to stop right now. This is due to the + // cleanup process waiting on the container lock and then afterwards restarts it. + // shouldRestart() then checks for StoppedByUser and does not restart it. + // https://github.com/containers/podman/issues/18259 + var cannotStopErr error + if c.ensureState(define.ContainerStateStopped, define.ContainerStateExited) { + cannotStopErr = define.ErrCtrStopped + } else if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStateStopping) { + cannotStopErr = fmt.Errorf("can only stop created or running containers. %s is in state %s: %w", c.ID(), c.state.State.String(), define.ErrCtrStateInvalid) + } + + c.state.StoppedByUser = true + if cannotStopErr == nil { + // Set the container state to "stopping" and unlock the container + // before handing it over to conmon to unblock other commands. #8501 + // demonstrates nicely that a high stop timeout will block even simple + // commands such as `podman ps` from progressing if the container lock + // is held when busy-waiting for the container to be stopped. + c.state.State = define.ContainerStateStopping + } + if err := c.save(); err != nil { + rErr := fmt.Errorf("saving container %s state before stopping: %w", c.ID(), err) + if cannotStopErr == nil { + return rErr + } + // we return below with cannotStopErr + logrus.Error(rErr) + } + if cannotStopErr != nil { + return cannotStopErr + } + if !c.batched { + c.lock.Unlock() + } + + stopErr := c.ociRuntime.StopContainer(c, timeout, all) + + if !c.batched { + c.lock.Lock() + if err := c.syncContainer(); err != nil { + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + // If the container has already been removed (e.g., via + // the cleanup process), set the container state to "stopped". + c.state.State = define.ContainerStateStopped + return stopErr + } + + if stopErr != nil { + logrus.Errorf("Syncing container %s status: %v", c.ID(), err) + return stopErr + } + return err + } + } + + // We have to check stopErr *after* we lock again - otherwise, we have a + // change of panicking on a double-unlock. Ref: GH Issue 9615 + if stopErr != nil { + return stopErr + } + + // Since we're now subject to a race condition with other processes who + // may have altered the state (and other data), let's check if the + // state has changed. If so, we should return immediately and leave + // breadcrumbs for debugging if needed. + if c.state.State != define.ContainerStateStopping { + logrus.Debugf( + "Container %q state changed from %q to %q while waiting for it to be stopped: discontinuing stop procedure as another process interfered", + c.ID(), define.ContainerStateStopping, c.state.State, + ) + return nil + } + + c.newContainerEvent(events.Stop) + return c.waitForConmonToExitAndSave() +} + +func (c *Container) waitForConmonToExitAndSave() error { + conmonAlive, err := c.ociRuntime.CheckConmonRunning(c) + if err != nil { + return err + } + if !conmonAlive { + if err := c.checkExitFile(); err != nil { + return err + } + + // If we are still ContainerStateStopping, conmon exited without + // creating an exit file. Let's try and handle that here. + if c.state.State == define.ContainerStateStopping { + // Is container PID1 still alive? + if err := unix.Kill(c.state.PID, 0); err == nil { + // We have a runaway container, unmanaged by + // Conmon. Invoke OCI runtime stop. + // Use 0 timeout for immediate SIGKILL as things + // have gone seriously wrong. + // Ignore the error from stopWithAll, it's just + // a cgroup check - more important that we + // continue. + // If we wanted to be really fancy here, we + // could open a pidfd on container PID1 before + // this to get the real exit code... But I'm not + // that dedicated. + all, _ := c.stopWithAll() + if err := c.ociRuntime.StopContainer(c, 0, all); err != nil { + logrus.Errorf("Error stopping container %s after Conmon exited prematurely: %v", c.ID(), err) + } + } + + // Conmon is dead. Handle it. + c.state.State = define.ContainerStateStopped + c.state.PID = 0 + c.state.ConmonPID = 0 + c.state.FinishedTime = time.Now() + c.state.ExitCode = -1 + c.state.Exited = true + + c.state.Error = "conmon died without writing exit file, container exit code could not be retrieved" + + c.newContainerExitedEvent(c.state.ExitCode) + + if err := c.save(); err != nil { + logrus.Errorf("Error saving container %s state after Conmon exited prematurely: %v", c.ID(), err) + } + + if err := c.runtime.state.AddContainerExitCode(c.ID(), c.state.ExitCode); err != nil { + logrus.Errorf("Error saving container %s exit code after Conmon exited prematurely: %v", c.ID(), err) + } + + // No Conmon alive to trigger cleanup, and the calls in + // regular Podman are conditional on no errors. + // Need to clean up manually. + if err := c.cleanup(context.Background()); err != nil { + logrus.Errorf("Error cleaning up container %s after Conmon exited prematurely: %v", c.ID(), err) + } + + return fmt.Errorf("container %s conmon exited prematurely, exit code could not be retrieved: %w", c.ID(), define.ErrConmonDead) + } + + return c.save() + } + + if err := c.save(); err != nil { + return fmt.Errorf("saving container %s state after stopping: %w", c.ID(), err) + } + + // Wait until we have an exit file, and sync once we do + if err := c.waitForExitFileAndSync(); err != nil { + return err + } + + return nil +} + +// Internal, non-locking function to pause a container +func (c *Container) pause() error { + if c.config.NoCgroups { + return fmt.Errorf("cannot pause without using Cgroups: %w", define.ErrNoCgroups) + } + + if rootless.IsRootless() { + cgroupv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return fmt.Errorf("failed to determine cgroupversion: %w", err) + } + if !cgroupv2 { + return fmt.Errorf("can not pause containers on rootless containers with cgroup V1: %w", define.ErrNoCgroups) + } + } + + if c.state.HCUnitName != "" { + if err := c.removeTransientFiles(context.Background(), + c.config.StartupHealthCheckConfig != nil && !c.state.StartupHCPassed, + c.state.HCUnitName); err != nil { + return fmt.Errorf("failed to remove HealthCheck timer: %v", err) + } + } + + if err := c.ociRuntime.PauseContainer(c); err != nil { + // TODO when using docker-py there is some sort of race/incompatibility here + return err + } + + logrus.Debugf("Paused container %s", c.ID()) + + c.state.State = define.ContainerStatePaused + c.state.HCUnitName = "" + + return c.save() +} + +// Internal, non-locking function to unpause a container +func (c *Container) unpause() error { + if c.config.NoCgroups { + return fmt.Errorf("cannot unpause without using Cgroups: %w", define.ErrNoCgroups) + } + + if err := c.ociRuntime.UnpauseContainer(c); err != nil { + // TODO when using docker-py there is some sort of race/incompatibility here + return err + } + + isStartupHealthCheck := c.config.StartupHealthCheckConfig != nil && !c.state.StartupHCPassed + isHealthCheckEnabled := c.config.HealthCheckConfig != nil && + (len(c.config.HealthCheckConfig.Test) != 1 || c.config.HealthCheckConfig.Test[0] != "NONE") + if isHealthCheckEnabled || isStartupHealthCheck { + timer := c.config.HealthCheckConfig.Interval.String() + if isStartupHealthCheck { + timer = c.config.StartupHealthCheckConfig.Interval.String() + } + if err := c.createTimer(timer, isStartupHealthCheck); err != nil { + return fmt.Errorf("create healthcheck: %w", err) + } + } + + if isHealthCheckEnabled { + if err := c.updateHealthStatus(define.HealthCheckReset); err != nil { + return err + } + if err := c.startTimer(isStartupHealthCheck); err != nil { + return err + } + } + + logrus.Debugf("Unpaused container %s", c.ID()) + + c.state.State = define.ContainerStateRunning + + return c.save() +} + +// Internal, non-locking function to restart a container +// It requires to run on the same thread that holds the lock. +func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (retErr error) { + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStateStopped, define.ContainerStateExited) { + return fmt.Errorf("unable to restart a container in a paused or unknown state: %w", define.ErrCtrStateInvalid) + } + + c.newContainerEvent(events.Restart) + + if c.state.State == define.ContainerStateRunning { + if err := c.stop(timeout); err != nil { + return err + } + + if c.config.HealthCheckConfig != nil { + if err := c.removeTransientFiles(context.Background(), + c.config.StartupHealthCheckConfig != nil && !c.state.StartupHCPassed, + c.state.HCUnitName); err != nil { + logrus.Error(err.Error()) + } + } + // Ensure we tear down the container network so it will be + // recreated - otherwise, behavior of restart differs from stop + // and start + if err := c.cleanupNetwork(); err != nil { + return err + } + } + defer func() { + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("Cleaning up container %s: %v", c.ID(), err) + } + } + }() + if err := c.prepare(); err != nil { + return err + } + + switch c.state.State { + case define.ContainerStateStopped: + // Reinitialize the container if we need to. + if err := c.reinit(ctx, false); err != nil { + return err + } + case define.ContainerStateConfigured, define.ContainerStateExited: + // Initialize the container. + if err := c.init(ctx, false); err != nil { + return err + } + } + if err := c.start(); err != nil { + return err + } + return c.waitForHealthy(ctx) +} + +// mountStorage sets up the container's root filesystem +// It mounts the image and any other requested mounts +// TODO: Add ability to override mount label so we can use this for Mount() too +// TODO: Can we use this for export? Copying SHM into the export might not be +// good +func (c *Container) mountStorage() (_ string, deferredErr error) { + var err error + // Container already mounted, nothing to do + if c.state.Mounted { + mounted := true + if c.ensureState(define.ContainerStateExited) { + mounted, _ = mount.Mounted(c.state.Mountpoint) + } + if mounted { + return c.state.Mountpoint, nil + } + } + + if !c.config.NoShm { + mounted, err := mount.Mounted(c.config.ShmDir) + if err != nil { + return "", fmt.Errorf("unable to determine if %q is mounted: %w", c.config.ShmDir, err) + } + + if !mounted && !MountExists(c.config.Spec.Mounts, "/dev/shm") { + shmOptions := fmt.Sprintf("mode=1777,size=%d", c.config.ShmSize) + if err := c.mountSHM(shmOptions); err != nil { + return "", err + } + if err := idtools.SafeChown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil { + return "", fmt.Errorf("failed to chown %s: %w", c.config.ShmDir, err) + } + defer func() { + if deferredErr != nil { + if err := c.unmountSHM(c.config.ShmDir); err != nil { + logrus.Errorf("Unmounting SHM for container %s after mount error: %v", c.ID(), err) + } + } + }() + } + } + + // We need to mount the container before volumes - to ensure the copyup + // works properly. + mountPoint := c.config.Rootfs + + if c.config.RootfsMapping != nil { + uidMappings, gidMappings, err := parseIDMapMountOption(c.config.IDMappings, *c.config.RootfsMapping) + if err != nil { + return "", err + } + + pid, cleanupFunc, err := idmap.CreateUsernsProcess(util.RuntimeSpecToIDtools(uidMappings), util.RuntimeSpecToIDtools(gidMappings)) + if err != nil { + return "", err + } + defer cleanupFunc() + + if err := idmap.CreateIDMappedMount(c.config.Rootfs, c.config.Rootfs, pid); err != nil { + return "", fmt.Errorf("failed to create idmapped mount: %w", err) + } + defer func() { + if deferredErr != nil { + if err := unix.Unmount(c.config.Rootfs, 0); err != nil { + logrus.Errorf("Unmounting idmapped rootfs for container %s after mount error: %v", c.ID(), err) + } + } + }() + } + + // Check if overlay has to be created on top of Rootfs + if c.config.RootfsOverlay { + overlayDest := c.runtime.GraphRoot() + contentDir, err := overlay.GenerateStructure(overlayDest, c.ID(), "rootfs", c.RootUID(), c.RootGID()) + if err != nil { + return "", fmt.Errorf("rootfs-overlay: failed to create TempDir in the %s directory: %w", overlayDest, err) + } + + // Recreate the rootfs for infra container. It can be missing after system reboot if it's stored on tmpfs. + if c.IsDefaultInfra() || c.IsService() { + err := c.createInitRootfs() + if err != nil { + return "", err + } + } + + overlayMount, err := overlay.Mount(contentDir, c.config.Rootfs, overlayDest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) + if err != nil { + return "", fmt.Errorf("rootfs-overlay: creating overlay failed %q: %w", c.config.Rootfs, err) + } + + // Seems fuse-overlayfs is not present + // fallback to native overlay + if overlayMount.Type == "overlay" { + overlayMount.Options = append(overlayMount.Options, "nodev") + mountOpts := label.FormatMountLabel(strings.Join(overlayMount.Options, ","), c.MountLabel()) + err = mount.Mount("overlay", overlayMount.Source, overlayMount.Type, mountOpts) + if err != nil { + return "", fmt.Errorf("rootfs-overlay: creating overlay failed %q from native overlay: %w", c.config.Rootfs, err) + } + } + + mountPoint = overlayMount.Source + execUser, err := lookup.GetUserGroupInfo(mountPoint, c.config.User, nil) + if err != nil { + return "", err + } + hostUID, hostGID, err := butil.GetHostIDs(util.IDtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), util.IDtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), uint32(execUser.Uid), uint32(execUser.Gid)) + if err != nil { + return "", fmt.Errorf("unable to get host UID and host GID: %w", err) + } + + //note: this should not be recursive, if using external rootfs users should be responsible on configuring ownership. + if err := chown.ChangeHostPathOwnership(mountPoint, false, int(hostUID), int(hostGID)); err != nil { + return "", err + } + } + + if mountPoint == "" { + mountPoint, err = c.mount() + if err != nil { + return "", err + } + defer func() { + if deferredErr != nil { + if err := c.unmount(false); err != nil { + logrus.Errorf("Unmounting container %s after mount error: %v", c.ID(), err) + } + } + }() + } + + rootUID, rootGID := c.RootUID(), c.RootGID() + + dirfd, err := openDirectory(mountPoint) + if err != nil { + return "", fmt.Errorf("open mount point: %w", err) + } + defer unix.Close(dirfd) + + err = unix.Mkdirat(dirfd, "etc", 0o755) + if err != nil && !os.IsExist(err) { + return "", fmt.Errorf("create /etc: %w", err) + } + // If the etc directory was created, chown it to root in the container + if err == nil && (rootUID != 0 || rootGID != 0) { + err = unix.Fchownat(dirfd, "etc", rootUID, rootGID, unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + return "", fmt.Errorf("chown /etc: %w", err) + } + } + + etcInTheContainerPath, err := securejoin.SecureJoin(mountPoint, "etc") + if err != nil { + return "", fmt.Errorf("resolve /etc in the container: %w", err) + } + + etcInTheContainerFd, err := openDirectory(etcInTheContainerPath) + if err != nil { + return "", fmt.Errorf("open /etc in the container: %w", err) + } + defer unix.Close(etcInTheContainerFd) + + if err := c.makePlatformMtabLink(etcInTheContainerFd, rootUID, rootGID); err != nil { + return "", err + } + + tz := c.Timezone() + localTimePath, err := timezone.ConfigureContainerTimeZone(tz, c.state.RunDir, mountPoint, etcInTheContainerPath, c.ID()) + if err != nil { + return "", fmt.Errorf("configuring timezone for container %s: %w", c.ID(), err) + } + if localTimePath != "" { + if err := c.relabel(localTimePath, c.config.MountLabel, false); err != nil { + return "", err + } + if c.state.BindMounts == nil { + c.state.BindMounts = make(map[string]string) + } + c.state.BindMounts["/etc/localtime"] = localTimePath + } + + // Request a mount of all named volumes + for _, v := range c.config.NamedVolumes { + vol, err := c.mountNamedVolume(v, mountPoint) + if err != nil { + return "", err + } + defer func() { + if deferredErr == nil { + return + } + vol.lock.Lock() + if err := vol.unmount(false); err != nil { + logrus.Errorf("Unmounting volume %s after error mounting container %s: %v", vol.Name(), c.ID(), err) + } + vol.lock.Unlock() + }() + } + + return mountPoint, nil +} + +// Mount a single named volume into the container. +// If necessary, copy up image contents into the volume. +// Does not verify that the name volume given is actually present in container +// config. +// Returns the volume that was mounted. +func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string) (*Volume, error) { + logrus.Debugf("Going to mount named volume %s", v.Name) + vol, err := c.runtime.state.Volume(v.Name) + if err != nil { + return nil, fmt.Errorf("retrieving named volume %s for container %s: %w", v.Name, c.ID(), err) + } + + if vol.config.LockID == c.config.LockID { + return nil, fmt.Errorf("container %s and volume %s share lock ID %d: %w", c.ID(), vol.Name(), c.config.LockID, define.ErrWillDeadlock) + } + vol.lock.Lock() + defer vol.lock.Unlock() + if vol.needsMount() { + if err := vol.mount(); err != nil { + return nil, fmt.Errorf("mounting volume %s for container %s: %w", vol.Name(), c.ID(), err) + } + } + // The volume may need a copy-up. Check the state. + if err := vol.update(); err != nil { + return nil, err + } + _, hasNoCopy := vol.config.Options["nocopy"] + if vol.state.NeedsCopyUp && !slices.Contains(v.Options, "nocopy") && !hasNoCopy { + logrus.Debugf("Copying up contents from container %s to volume %s", c.ID(), vol.Name()) + + srcDir, err := securejoin.SecureJoin(mountpoint, v.Dest) + if err != nil { + return nil, fmt.Errorf("calculating destination path to copy up container %s volume %s: %w", c.ID(), vol.Name(), err) + } + // Do a manual stat on the source directory to verify existence. + // Skip the rest if it exists. + srcStat, err := os.Lstat(srcDir) + if err != nil { + if os.IsNotExist(err) { + // Source does not exist, don't bother copying + // up. + return vol, nil + } + return nil, fmt.Errorf("identifying source directory for copy up into volume %s: %w", vol.Name(), err) + } + // If it's not a directory we're mounting over it. + if !srcStat.IsDir() { + return vol, nil + } + // Read contents, do not bother continuing if it's empty. Fixes + // a bizarre issue where something copier.Get will ENOENT on + // empty directories and sometimes it will not. + // RHBZ#1928643 + srcContents, err := os.ReadDir(srcDir) + if err != nil { + return nil, fmt.Errorf("reading contents of source directory for copy up into volume %s: %w", vol.Name(), err) + } + if len(srcContents) == 0 { + return vol, nil + } + + // If the volume is not empty, we should not copy up. + volMount := vol.mountPoint() + contents, err := os.ReadDir(volMount) + if err != nil { + return nil, fmt.Errorf("listing contents of volume %s mountpoint when copying up from container %s: %w", vol.Name(), c.ID(), err) + } + if len(contents) > 0 { + // The volume is not empty. It was likely modified + // outside of Podman. For safety, let's not copy up into + // it. Fixes CVE-2020-1726. + return vol, nil + } + + // Set NeedsCopyUp to false since we are about to do first copy + // Do not copy second time. + vol.state.NeedsCopyUp = false + vol.state.CopiedUp = true + if err := vol.save(); err != nil { + return nil, err + } + + // Buildah Copier accepts a reader, so we'll need a pipe. + reader, writer := io.Pipe() + defer reader.Close() + + errChan := make(chan error, 1) + + logrus.Infof("About to copy up into volume %s", vol.Name()) + + // Copy, container side: get a tar archive of what needs to be + // streamed into the volume. + go func() { + defer writer.Close() + getOptions := copier.GetOptions{ + KeepDirectoryNames: false, + } + // If the volume is idmapped, we need to "undo" the idmapping + if slices.Contains(v.Options, "idmap") { + getOptions.UIDMap = c.config.IDMappings.UIDMap + getOptions.GIDMap = c.config.IDMappings.GIDMap + } + errChan <- copier.Get(srcDir, "", getOptions, []string{"/."}, writer) + }() + + // Copy, volume side: stream what we've written to the pipe, into + // the volume. + copyOpts := copier.PutOptions{} + if err := copier.Put(volMount, "", copyOpts, reader); err != nil { + // consume the reader otherwise the goroutine will block + _, _ = io.Copy(io.Discard, reader) + err2 := <-errChan + if err2 != nil { + logrus.Errorf("Streaming contents of container %s directory for volume copy-up: %v", c.ID(), err2) + } + return nil, fmt.Errorf("copying up to volume %s: %w", vol.Name(), err) + } + + if err := <-errChan; err != nil { + return nil, fmt.Errorf("streaming container content for copy up into volume %s: %w", vol.Name(), err) + } + } + return vol, nil +} + +// cleanupStorage unmounts and cleans up the container's root filesystem +func (c *Container) cleanupStorage() error { + if !c.state.Mounted { + // Already unmounted, do nothing + logrus.Debugf("Container %s storage is already unmounted, skipping...", c.ID()) + return nil + } + + var cleanupErr error + reportErrorf := func(msg string, args ...any) { + err := fmt.Errorf(msg, args...) // Always use fmt.Errorf instead of just logrus.Errorf(…) because the format string probably contains %w + if cleanupErr == nil { + cleanupErr = err + } else { + logrus.Errorf("%s", err.Error()) + } + } + + markUnmounted := func() { + c.state.Mountpoint = "" + c.state.Mounted = false + + if c.valid { + if err := c.save(); err != nil { + reportErrorf("unmounting container %s: %w", c.ID(), err) + } + } + } + + // umount rootfs overlay if it was created + if c.config.RootfsOverlay { + overlayBasePath := filepath.Dir(c.state.Mountpoint) + if err := overlay.Unmount(overlayBasePath); err != nil { + reportErrorf("failed to clean up overlay mounts for %s: %w", c.ID(), err) + } + } + if c.config.RootfsMapping != nil { + if err := unix.Unmount(c.config.Rootfs, 0); err != nil && err != unix.EINVAL { + reportErrorf("unmounting idmapped rootfs for container %s after mount error: %w", c.ID(), err) + } + } + + for _, containerMount := range c.config.Mounts { + if err := c.unmountSHM(containerMount); err != nil { + reportErrorf("unmounting container %s: %w", c.ID(), err) + } + } + + if err := c.cleanupOverlayMounts(); err != nil { + // If the container can't remove content report the error + reportErrorf("failed to clean up overlay mounts for %s: %w", c.ID(), err) + } + + if c.config.Rootfs != "" { + markUnmounted() + return cleanupErr + } + + if err := c.unmount(false); err != nil { + // If the container has already been removed, warn but don't + // error + // We still want to be able to kick the container out of the + // state + switch { + case errors.Is(err, storage.ErrLayerNotMounted): + logrus.Infof("Storage for container %s is not mounted: %v", c.ID(), err) + case errors.Is(err, storage.ErrNotAContainer) || errors.Is(err, storage.ErrContainerUnknown): + logrus.Warnf("Storage for container %s has been removed: %v", c.ID(), err) + default: + reportErrorf("cleaning up container %s storage: %w", c.ID(), err) + } + } + + // Request an unmount of all named volumes + for _, v := range c.config.NamedVolumes { + vol, err := c.runtime.state.Volume(v.Name) + if err != nil { + reportErrorf("retrieving named volume %s for container %s: %w", v.Name, c.ID(), err) + + // We need to try and unmount every volume, so continue + // if they fail. + continue + } + + if vol.needsMount() { + vol.lock.Lock() + if err := vol.unmount(false); err != nil { + reportErrorf("unmounting volume %s for container %s: %w", vol.Name(), c.ID(), err) + } + vol.lock.Unlock() + } + } + + markUnmounted() + return cleanupErr +} + +// fullCleanup performs all cleanup tasks, including handling restart policy. +func (c *Container) fullCleanup(ctx context.Context, onlyStopped bool) error { + // Check if state is good + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateStopping, define.ContainerStateExited) { + return fmt.Errorf("container %s is running or paused, refusing to clean up: %w", c.ID(), define.ErrCtrStateInvalid) + } + if onlyStopped && !c.ensureState(define.ContainerStateStopped) { + return fmt.Errorf("container %s is not stopped and only cleanup for a stopped container was requested: %w", c.ID(), define.ErrCtrStateInvalid) + } + + // if the container was not created in the oci runtime or was already cleaned up, then do nothing + if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { + return nil + } + + // Handle restart policy. + // Returns a bool indicating whether we actually restarted. + // If we did, don't proceed to cleanup - just exit. + didRestart, err := c.handleRestartPolicy(ctx) + if err != nil { + return err + } + if didRestart { + return nil + } + + // If we didn't restart, we perform a normal cleanup + + // make sure all the container processes are terminated if we are running without a pid namespace. + hasPidNs := false + if c.config.Spec.Linux != nil { + for _, i := range c.config.Spec.Linux.Namespaces { + if i.Type == spec.PIDNamespace { + hasPidNs = true + break + } + } + } + if !hasPidNs { + // do not fail on errors + _ = c.ociRuntime.KillContainer(c, uint(unix.SIGKILL), true) + } + + // Check for running exec sessions + sessions, err := c.getActiveExecSessions() + if err != nil { + return err + } + if len(sessions) > 0 { + return fmt.Errorf("container %s has active exec sessions, refusing to clean up: %w", c.ID(), define.ErrCtrStateInvalid) + } + + defer c.newContainerEvent(events.Cleanup) + return c.cleanup(ctx) +} + +// Unmount the container and free its resources +func (c *Container) cleanup(ctx context.Context) error { + var lastError error + + logrus.Debugf("Cleaning up container %s", c.ID()) + + // Ensure we are not killed half way through cleanup + // which can leave us in a bad state. + shutdown.Inhibit() + defer shutdown.Uninhibit() + + // Remove healthcheck unit/timer file if it execs + if c.config.HealthCheckConfig != nil { + if err := c.removeTransientFiles(ctx, + c.config.StartupHealthCheckConfig != nil && !c.state.StartupHCPassed, + c.state.HCUnitName); err != nil { + logrus.Errorf("Removing timer for container %s healthcheck: %v", c.ID(), err) + } + } + + // Clean up network namespace, if present + if err := c.cleanupNetwork(); err != nil { + lastError = fmt.Errorf("removing container %s network: %w", c.ID(), err) + } + + // cleanup host entry if it is shared + if c.config.NetNsCtr != "" { + if hoststFile, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + if err := fileutils.Exists(hoststFile); err == nil { + // we cannot use the dependency container lock due ABBA deadlocks + if lock, err := lockfile.GetLockFile(hoststFile); err == nil { + lock.Lock() + // make sure to ignore ENOENT error in case the netns container was cleaned up before this one + if err := etchosts.Remove(hoststFile, getLocalhostHostEntry(c)); err != nil && !errors.Is(err, os.ErrNotExist) { + // this error is not fatal we still want to do proper cleanup + logrus.Errorf("failed to remove hosts entry from the netns containers /etc/hosts: %v", err) + } + lock.Unlock() + } + } + } + } + + // Remove the container from the runtime, if necessary. + // Do this *before* unmounting storage - some runtimes (e.g. Kata) + // apparently object to having storage removed while the container still + // exists. + if err := c.cleanupRuntime(ctx); err != nil { + if lastError != nil { + logrus.Errorf("Removing container %s from OCI runtime: %v", c.ID(), err) + } else { + lastError = err + } + } + + // Unmount storage + if err := c.cleanupStorage(); err != nil { + if lastError != nil { + logrus.Errorf("Unmounting container %s storage: %v", c.ID(), err) + } else { + lastError = fmt.Errorf("unmounting container %s storage: %w", c.ID(), err) + } + } + + // Unmount image volumes + for _, v := range c.config.ImageVolumes { + img, _, err := c.runtime.LibimageRuntime().LookupImage(v.Source, nil) + if err != nil { + if lastError == nil { + lastError = err + continue + } + logrus.Errorf("Unmounting image volume %q:%q :%v", v.Source, v.Dest, err) + } + if err := img.Unmount(false); err != nil { + if lastError == nil { + lastError = err + continue + } + logrus.Errorf("Unmounting image volume %q:%q :%v", v.Source, v.Dest, err) + } + } + + if err := c.stopPodIfNeeded(context.Background()); err != nil { + if lastError == nil { + lastError = err + } else { + logrus.Errorf("Stopping pod of container %s: %v", c.ID(), err) + } + } + + // Prune the exit codes of other container during clean up. + // Since Podman is no daemon, we have to clean them up somewhere. + // Cleanup seems like a good place as it's not performance + // critical. + if err := c.runtime.state.PruneContainerExitCodes(); err != nil { + if lastError == nil { + lastError = err + } else { + logrus.Errorf("Pruning container exit codes: %v", err) + } + } + + return lastError +} + +// If the container is part of a pod where only the infra container remains +// running, attempt to stop the pod. +func (c *Container) stopPodIfNeeded(ctx context.Context) error { + if c.config.Pod == "" { + return nil + } + + // Never try to stop the pod when a init container stopped + if c.IsInitCtr() { + return nil + } + + pod, err := c.runtime.state.Pod(c.config.Pod) + if err != nil { + return fmt.Errorf("container %s is in pod %s, but pod cannot be retrieved: %w", c.ID(), c.config.Pod, err) + } + + switch pod.config.ExitPolicy { + case config.PodExitPolicyContinue: + return nil + + case config.PodExitPolicyStop: + // Use the runtime's work queue to stop the pod. This resolves + // a number of scenarios where we'd otherwise run into + // deadlocks. For instance, during `pod stop`, the pod has + // already been locked. + // The work queue is a simple means without having to worry about + // future changes that may introduce more deadlock scenarios. + c.runtime.queueWork(func() { + if err := pod.stopIfOnlyInfraRemains(ctx, c.ID()); err != nil { + if !errors.Is(err, define.ErrNoSuchPod) { + logrus.Errorf("Checking if infra needs to be stopped: %v", err) + } + } + }) + } + return nil +} + +// delete deletes the container and runs any configured poststop +// hooks. +func (c *Container) delete(ctx context.Context) error { + if err := c.ociRuntime.DeleteContainer(c); err != nil { + return fmt.Errorf("removing container %s from runtime: %w", c.ID(), err) + } + + if err := c.postDeleteHooks(ctx); err != nil { + return fmt.Errorf("container %s poststop hooks: %w", c.ID(), err) + } + + return nil +} + +// postDeleteHooks runs the poststop hooks (if any) as specified by +// the OCI Runtime Specification (which requires them to run +// post-delete, despite the stage name). +func (c *Container) postDeleteHooks(ctx context.Context) error { + if c.state.ExtensionStageHooks != nil { + extensionHooks, ok := c.state.ExtensionStageHooks["poststop"] + if ok { + state, err := json.Marshal(spec.State{ + Version: spec.Version, + ID: c.ID(), + Status: "stopped", + Bundle: c.bundlePath(), + Annotations: c.config.Spec.Annotations, + }) + if err != nil { + return err + } + for i, hook := range extensionHooks { + logrus.Debugf("container %s: invoke poststop hook %d, path %s", c.ID(), i, hook.Path) + var stderr, stdout bytes.Buffer + hookErr, err := exec.RunWithOptions( + ctx, + exec.RunOptions{ + Hook: &hook, + Dir: c.bundlePath(), + State: state, + Stdout: &stdout, + Stderr: &stderr, + PostKillTimeout: exec.DefaultPostKillTimeout, + }, + ) + if err != nil { + logrus.Warnf("Container %s: poststop hook %d: %v", c.ID(), i, err) + if hookErr != err { + logrus.Debugf("container %s: poststop hook %d (hook error): %v", c.ID(), i, hookErr) + } + stdoutString := stdout.String() + if stdoutString != "" { + logrus.Debugf("container %s: poststop hook %d: stdout:\n%s", c.ID(), i, stdoutString) + } + stderrString := stderr.String() + if stderrString != "" { + logrus.Debugf("container %s: poststop hook %d: stderr:\n%s", c.ID(), i, stderrString) + } + } + } + } + } + + return nil +} + +// writeStringToRundir writes the given string to a file with the given name in +// the container's temporary files directory. The file will be chown'd to the +// container's root user and have an appropriate SELinux label set. +// If a file with the same name already exists, it will be deleted and recreated +// with the new contents. +// Returns the full path to the new file. +func (c *Container) writeStringToRundir(destFile, contents string) (string, error) { + destFileName := filepath.Join(c.state.RunDir, destFile) + + if err := os.Remove(destFileName); err != nil && !os.IsNotExist(err) { + return "", fmt.Errorf("removing %s for container %s: %w", destFile, c.ID(), err) + } + + if err := writeStringToPath(destFileName, contents, c.config.MountLabel, c.RootUID(), c.RootGID()); err != nil { + return "", err + } + + return destFileName, nil +} + +// writeStringToStaticDir writes the given string to a file with the given name +// in the container's permanent files directory. The file will be chown'd to the +// container's root user and have an appropriate SELinux label set. +// Unlike writeStringToRundir, will *not* delete and re-create if the file +// already exists (will instead error). +// Returns the full path to the new file. +func (c *Container) writeStringToStaticDir(filename, contents string) (string, error) { + destFileName := filepath.Join(c.config.StaticDir, filename) + + if err := writeStringToPath(destFileName, contents, c.config.MountLabel, c.RootUID(), c.RootGID()); err != nil { + return "", err + } + + return destFileName, nil +} + +// saveSpec saves the OCI spec to disk, replacing any existing specs for the container +func (c *Container) saveSpec(spec *spec.Spec) error { + // If the OCI spec already exists, we need to replace it + // Cannot guarantee some things, e.g. network namespaces, have the same + // paths + jsonPath := filepath.Join(c.bundlePath(), "config.json") + if err := fileutils.Exists(jsonPath); err != nil { + if !os.IsNotExist(err) { + return fmt.Errorf("doing stat on container %s spec: %w", c.ID(), err) + } + // The spec does not exist, we're fine + } else { + // The spec exists, need to remove it + if err := os.Remove(jsonPath); err != nil { + return fmt.Errorf("replacing runtime spec for container %s: %w", c.ID(), err) + } + } + + fileJSON, err := json.Marshal(spec) + if err != nil { + return fmt.Errorf("exporting runtime spec for container %s to JSON: %w", c.ID(), err) + } + if err := os.WriteFile(jsonPath, fileJSON, 0o644); err != nil { + return fmt.Errorf("writing runtime spec JSON for container %s to disk: %w", c.ID(), err) + } + + logrus.Debugf("Created OCI spec for container %s at %s", c.ID(), jsonPath) + + c.state.ConfigPath = jsonPath + + return nil +} + +// Warning: precreate hooks may alter 'config' in place. +func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (map[string][]spec.Hook, error) { + allHooks := make(map[string][]spec.Hook) + if len(c.runtime.config.Engine.HooksDir.Get()) == 0 { + if rootless.IsRootless() { + return nil, nil + } + for _, hDir := range []string{hooks.DefaultDir, hooks.OverrideDir} { + manager, err := hooks.New(ctx, []string{hDir}, []string{"precreate", "poststop"}) + if err != nil { + if os.IsNotExist(err) { + continue + } + return nil, err + } + ociHooks, err := manager.Hooks(config, c.config.Spec.Annotations, len(c.config.UserVolumes) > 0) + if err != nil { + return nil, err + } + if len(ociHooks) > 0 || config.Hooks != nil { + logrus.Warnf("Implicit hook directories are deprecated; set --ociHooks-dir=%q explicitly to continue to load ociHooks from this directory", hDir) + } + maps.Copy(allHooks, ociHooks) + } + } else { + manager, err := hooks.New(ctx, c.runtime.config.Engine.HooksDir.Get(), []string{"precreate", "poststop"}) + if err != nil { + return nil, err + } + + allHooks, err = manager.Hooks(config, c.config.Spec.Annotations, len(c.config.UserVolumes) > 0) + if err != nil { + return nil, err + } + } + + hookErr, err := exec.RuntimeConfigFilterWithOptions( + ctx, + exec.RuntimeConfigFilterOptions{ + Hooks: allHooks["precreate"], + Dir: c.bundlePath(), + Config: config, + PostKillTimeout: exec.DefaultPostKillTimeout, + }, + ) + if err != nil { + logrus.Warnf("Container %s: precreate hook: %v", c.ID(), err) + if hookErr != nil && hookErr != err { + logrus.Debugf("container %s: precreate hook (hook error): %v", c.ID(), hookErr) + } + return nil, err + } + + return allHooks, nil +} + +// getRootPathForOCI returns the root path to use for the OCI runtime. +// If the current user is mapped in the container user namespace, then it returns +// the container's mountpoint directly from the storage. +// Otherwise, it returns an intermediate mountpoint that is accessible to anyone. +func (c *Container) getRootPathForOCI() (string, error) { + if hasCurrentUserMapped(c) || c.config.RootfsMapping != nil { + return c.state.Mountpoint, nil + } + return c.getIntermediateMountpointUser() +} + +var ( + intermediateMountPoint string + intermediateMountPointErr error + intermediateMountPointSync sync.Mutex +) + +// getIntermediateMountpointUser returns a path that is accessible to everyone. It must be on TMPDIR since +// the runroot/tmpdir used by libpod are accessible only to the owner. +// To avoid TOCTOU issues, the path must be owned by the current user's UID and GID. +// The path can be used by different containers, so a mount must be created only in a private mount namespace. +func (c *Container) recreateIntermediateMountpointUser() (string, error) { + uid := os.Geteuid() + gid := os.Getegid() + for i := 0; ; i++ { + tmpDir := os.Getenv("TMPDIR") + if tmpDir == "" { + tmpDir = "/tmp" + } + dir := filepath.Join(tmpDir, fmt.Sprintf("intermediate-mountpoint-%d.%d", rootless.GetRootlessUID(), i)) + err := os.Mkdir(dir, 0o755) + if err != nil { + if !errors.Is(err, os.ErrExist) { + return "", err + } + st, err2 := os.Stat(dir) + if err2 != nil { + return "", err + } + sys := st.Sys().(*syscall.Stat_t) + if !st.IsDir() || sys.Uid != uint32(uid) || sys.Gid != uint32(gid) { + continue + } + } + return dir, nil + } +} + +// getIntermediateMountpointUser returns a path that is accessible to everyone. +// To avoid TOCTOU issues, the path must be owned by the current user's UID and GID. +// The path can be used by different containers, so a mount must be created only in a private mount namespace. +func (c *Container) getIntermediateMountpointUser() (string, error) { + intermediateMountPointSync.Lock() + defer intermediateMountPointSync.Unlock() + + if intermediateMountPoint == "" || fileutils.Exists(intermediateMountPoint) != nil { + return c.recreateIntermediateMountpointUser() + } + + // update the timestamp to prevent systemd-tmpfiles from removing it + now := time.Now() + if err := os.Chtimes(intermediateMountPoint, now, now); err != nil { + if !errors.Is(err, os.ErrNotExist) { + return c.recreateIntermediateMountpointUser() + } + } + return intermediateMountPoint, intermediateMountPointErr +} + +// mount mounts the container's root filesystem +func (c *Container) mount() (string, error) { + if c.state.State == define.ContainerStateRemoving { + return "", fmt.Errorf("cannot mount container %s as it is being removed: %w", c.ID(), define.ErrCtrStateInvalid) + } + + mountPoint, err := c.runtime.storageService.MountContainerImage(c.ID()) + if err != nil { + return "", fmt.Errorf("mounting storage for container %s: %w", c.ID(), err) + } + mountPoint, err = filepath.EvalSymlinks(mountPoint) + if err != nil { + return "", fmt.Errorf("resolving storage path for container %s: %w", c.ID(), err) + } + if err := idtools.SafeChown(mountPoint, c.RootUID(), c.RootGID()); err != nil { + return "", fmt.Errorf("cannot chown %s to %d:%d: %w", mountPoint, c.RootUID(), c.RootGID(), err) + } + return mountPoint, nil +} + +// unmount unmounts the container's root filesystem +func (c *Container) unmount(force bool) error { + // Also unmount storage + if _, err := c.runtime.storageService.UnmountContainerImage(c.ID(), force); err != nil { + return fmt.Errorf("unmounting container %s root filesystem: %w", c.ID(), err) + } + + return nil +} + +// checkReadyForRemoval checks whether the given container is ready to be +// removed. +// These checks are only used if force-remove is not specified. +// If it is, we'll remove the container anyways. +// Returns nil if safe to remove, or an error describing why it's unsafe if not. +func (c *Container) checkReadyForRemoval() error { + if c.state.State == define.ContainerStateUnknown { + return fmt.Errorf("container %s is in invalid state: %w", c.ID(), define.ErrCtrStateInvalid) + } + + if c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping) && !c.IsInfra() { + return fmt.Errorf("cannot remove container %s as it is %s - running or paused containers cannot be removed without force: %w", c.ID(), c.state.State.String(), define.ErrCtrStateInvalid) + } + + // Check exec sessions + sessions, err := c.getActiveExecSessions() + if err != nil { + return err + } + if len(sessions) != 0 { + return fmt.Errorf("cannot remove container %s as it has active exec sessions: %w", c.ID(), define.ErrCtrStateInvalid) + } + + return nil +} + +// canWithPrevious return the stat of the preCheckPoint dir +func (c *Container) canWithPrevious() error { + return fileutils.Exists(c.PreCheckPointPath()) +} + +// prepareCheckpointExport writes the config and spec to +// JSON files for later export +func (c *Container) prepareCheckpointExport() error { + networks, err := c.networks() + if err != nil { + return err + } + // make sure to exclude the short ID alias since the container gets a new ID on restore + for net, opts := range networks { + newAliases := make([]string, 0, len(opts.Aliases)) + for _, alias := range opts.Aliases { + if alias != c.config.ID[:12] { + newAliases = append(newAliases, alias) + } + } + opts.Aliases = newAliases + networks[net] = opts + } + + // add the networks from the db to the config so that the exported checkpoint still stores all current networks + c.config.Networks = networks + // save live config + if _, err := metadata.WriteJSONFile(c.config, c.bundlePath(), metadata.ConfigDumpFile); err != nil { + return err + } + + // save spec + jsonPath := filepath.Join(c.bundlePath(), "config.json") + g, err := generate.NewFromFile(jsonPath) + if err != nil { + logrus.Debugf("generating spec for container %q failed with %v", c.ID(), err) + return err + } + if _, err := metadata.WriteJSONFile(g.Config, c.bundlePath(), metadata.SpecDumpFile); err != nil { + return err + } + + return nil +} + +// SortUserVolumes sorts the volumes specified for a container +// between named and normal volumes +func (c *Container) SortUserVolumes(ctrSpec *spec.Spec) ([]*ContainerNamedVolume, []spec.Mount) { + namedUserVolumes := []*ContainerNamedVolume{} + userMounts := []spec.Mount{} + + // We need to parse all named volumes and mounts into maps, so we don't + // end up with repeated lookups for each user volume. + // Map destination to struct, as destination is what is stored in + // UserVolumes. + namedVolumes := make(map[string]*ContainerNamedVolume) + mounts := make(map[string]spec.Mount) + for _, namedVol := range c.config.NamedVolumes { + namedVolumes[namedVol.Dest] = namedVol + } + for _, mount := range ctrSpec.Mounts { + mounts[mount.Destination] = mount + } + + for _, vol := range c.config.UserVolumes { + if volume, ok := namedVolumes[vol]; ok { + namedUserVolumes = append(namedUserVolumes, volume) + } else if mount, ok := mounts[vol]; ok { + userMounts = append(userMounts, mount) + } else { + logrus.Warnf("Could not find mount at destination %q when parsing user volumes for container %s", vol, c.ID()) + } + } + return namedUserVolumes, userMounts +} + +// Check for an exit file, and handle one if present +func (c *Container) checkExitFile() error { + // If the container's not running, nothing to do. + if !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping) { + return nil + } + + exitFile, err := c.exitFilePath() + if err != nil { + return err + } + + // Check for the exit file + info, err := os.Stat(exitFile) + if err != nil { + if os.IsNotExist(err) { + // Container is still running, no error + return nil + } + + return fmt.Errorf("running stat on container %s exit file: %w", c.ID(), err) + } + + // Alright, it exists. Transition to Stopped state. + c.state.State = define.ContainerStateStopped + c.state.PID = 0 + c.state.ConmonPID = 0 + + // Read the exit file to get our stopped time and exit code. + return c.handleExitFile(exitFile, info) +} + +func (c *Container) hasNamespace(namespace spec.LinuxNamespaceType) bool { + if c.config.Spec == nil || c.config.Spec.Linux == nil { + return false + } + for _, n := range c.config.Spec.Linux.Namespaces { + if n.Type == namespace { + return true + } + } + return false +} + +// extractSecretToCtrStorage copies a secret's data from the secrets manager to the container's static dir +func (c *Container) extractSecretToCtrStorage(secr *ContainerSecret) error { + manager, err := c.runtime.SecretsManager() + if err != nil { + return err + } + _, data, err := manager.LookupSecretData(secr.Name) + if err != nil { + return err + } + secretFile := filepath.Join(c.config.SecretsPath, secr.Name) + + hostUID, hostGID, err := butil.GetHostIDs(util.IDtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), util.IDtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), secr.UID, secr.GID) + if err != nil { + return fmt.Errorf("unable to extract secret: %w", err) + } + err = os.WriteFile(secretFile, data, 0o644) + if err != nil { + return fmt.Errorf("unable to create %s: %w", secretFile, err) + } + if err := idtools.SafeLchown(secretFile, int(hostUID), int(hostGID)); err != nil { + return err + } + if err := os.Chmod(secretFile, os.FileMode(secr.Mode)); err != nil { + return err + } + if err := c.relabel(secretFile, c.config.MountLabel, false); err != nil { + return err + } + return nil +} + +// Update a container's resources or restart policy after creation. +// At least one of resources or restartPolicy must not be nil. +func (c *Container) update(updateOptions *entities.ContainerUpdateOptions) error { + if updateOptions.Resources == nil && updateOptions.RestartPolicy == nil { + return fmt.Errorf("must provide at least one of resources and restartPolicy to update a container: %w", define.ErrInvalidArg) + } + if updateOptions.RestartRetries != nil && updateOptions.RestartPolicy == nil { + return fmt.Errorf("must provide restart policy if updating restart retries: %w", define.ErrInvalidArg) + } + + oldResources := new(spec.LinuxResources) + if c.config.Spec.Linux.Resources != nil { + if err := JSONDeepCopy(c.config.Spec.Linux.Resources, oldResources); err != nil { + return err + } + } + oldRestart := c.config.RestartPolicy + oldRetries := c.config.RestartRetries + + if updateOptions.RestartPolicy != nil { + if err := define.ValidateRestartPolicy(*updateOptions.RestartPolicy); err != nil { + return err + } + + if updateOptions.RestartRetries != nil { + if *updateOptions.RestartPolicy != define.RestartPolicyOnFailure { + return fmt.Errorf("cannot set restart policy retries unless policy is on-failure: %w", define.ErrInvalidArg) + } + } + + c.config.RestartPolicy = *updateOptions.RestartPolicy + if updateOptions.RestartRetries != nil { + c.config.RestartRetries = *updateOptions.RestartRetries + } else { + c.config.RestartRetries = 0 + } + } + + if updateOptions.Resources != nil { + if c.config.Spec.Linux == nil { + c.config.Spec.Linux = new(spec.Linux) + } + + resourcesToUpdate, err := json.Marshal(updateOptions.Resources) + if err != nil { + return err + } + if err := json.Unmarshal(resourcesToUpdate, c.config.Spec.Linux.Resources); err != nil { + return err + } + updateOptions.Resources = c.config.Spec.Linux.Resources + } + + if len(updateOptions.Env) != 0 { + c.config.Spec.Process.Env = envLib.Slice(envLib.Join(envLib.Map(c.config.Spec.Process.Env), envLib.Map(updateOptions.Env))) + } + + if len(updateOptions.UnsetEnv) != 0 { + envMap := envLib.Map(c.config.Spec.Process.Env) + for _, e := range updateOptions.UnsetEnv { + delete(envMap, e) + } + c.config.Spec.Process.Env = envLib.Slice(envMap) + } + + if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + // Assume DB write failed, revert to old resources block + c.config.Spec.Linux.Resources = oldResources + c.config.RestartPolicy = oldRestart + c.config.RestartRetries = oldRetries + return err + } + + if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused) && + (updateOptions.Resources != nil || updateOptions.Env != nil || updateOptions.UnsetEnv != nil) { + // So `podman inspect` on running containers sources its OCI spec from disk. + // To keep inspect accurate we need to update the on-disk OCI spec. + onDiskSpec, err := c.specFromState() + if err != nil { + return fmt.Errorf("retrieving on-disk OCI spec to update: %w", err) + } + if updateOptions.Resources != nil { + if onDiskSpec.Linux == nil { + onDiskSpec.Linux = new(spec.Linux) + } + onDiskSpec.Linux.Resources = updateOptions.Resources + } + if len(updateOptions.Env) != 0 || len(updateOptions.UnsetEnv) != 0 { + onDiskSpec.Process.Env = c.config.Spec.Process.Env + } + if err := c.saveSpec(onDiskSpec); err != nil { + logrus.Errorf("Unable to update container %s OCI spec - `podman inspect` may not be accurate until container is restarted: %v", c.ID(), err) + } + + if err := c.ociRuntime.UpdateContainer(c, updateOptions.Resources); err != nil { + return err + } + } + + logrus.Debugf("updated container %s", c.ID()) + return nil +} + +func (c *Container) resetHealthCheckTimers(noHealthCheck bool, changedTimer bool, wasEnabledHealthCheck bool, isStartup bool) error { + if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused) { + return nil + } + if noHealthCheck { + if err := c.removeTransientFiles(context.Background(), + c.config.StartupHealthCheckConfig != nil && !c.state.StartupHCPassed, + c.state.HCUnitName); err != nil { + return err + } + return nil + } + + if !changedTimer { + return nil + } + + if !isStartup { + if c.state.StartupHCPassed || c.config.StartupHealthCheckConfig == nil { + if err := c.recreateHealthCheckTimer(context.Background(), false, false); err != nil { + return err + } + } + return nil + } + + if !c.state.StartupHCPassed { + c.state.StartupHCPassed = !wasEnabledHealthCheck + c.state.StartupHCSuccessCount = 0 + c.state.StartupHCFailureCount = 0 + if err := c.save(); err != nil { + return err + } + if wasEnabledHealthCheck { + if err := c.recreateHealthCheckTimer(context.Background(), true, true); err != nil { + return err + } + } + return nil + } + return nil +} + +func (c *Container) updateHealthCheck(newHealthCheckConfig IHealthCheckConfig, currentHealthCheckConfig IHealthCheckConfig) error { + oldHealthCheckConfig := currentHealthCheckConfig + if !oldHealthCheckConfig.IsNil() { + if err := JSONDeepCopy(currentHealthCheckConfig, oldHealthCheckConfig); err != nil { + return err + } + } + + newHealthCheckConfig.SetTo(c.config) + + if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + // Assume DB write failed, revert to old resources block + oldHealthCheckConfig.SetTo(c.config) + return err + } + + oldInterval := time.Duration(0) + if !oldHealthCheckConfig.IsNil() { + oldInterval = oldHealthCheckConfig.GetInterval() + } + + changedTimer := false + if !newHealthCheckConfig.IsNil() { + changedTimer = newHealthCheckConfig.IsTimeChanged(oldInterval) + } + + noHealthCheck := c.config.HealthCheckConfig != nil && slices.Contains(c.config.HealthCheckConfig.Test, "NONE") + + if err := c.resetHealthCheckTimers(noHealthCheck, changedTimer, !oldHealthCheckConfig.IsNil(), newHealthCheckConfig.IsStartup()); err != nil { + return err + } + + checkType := "HealthCheck" + if newHealthCheckConfig.IsStartup() { + checkType = "Startup HealthCheck" + } + logrus.Debugf("%s configuration updated for container %s", checkType, c.ID()) + return nil +} + +func (c *Container) updateGlobalHealthCheckConfiguration(globalOptions define.GlobalHealthCheckOptions) error { + oldHealthCheckOnFailureAction := c.config.HealthCheckOnFailureAction + oldHealthLogDestination := c.config.HealthLogDestination + oldHealthMaxLogCount := c.config.HealthMaxLogCount + oldHealthMaxLogSize := c.config.HealthMaxLogSize + + if globalOptions.HealthCheckOnFailureAction != nil { + c.config.HealthCheckOnFailureAction = *globalOptions.HealthCheckOnFailureAction + } + + if globalOptions.HealthMaxLogCount != nil { + c.config.HealthMaxLogCount = globalOptions.HealthMaxLogCount + } + + if globalOptions.HealthMaxLogSize != nil { + c.config.HealthMaxLogSize = globalOptions.HealthMaxLogSize + } + + if globalOptions.HealthLogDestination != nil { + dest, err := define.GetValidHealthCheckDestination(*globalOptions.HealthLogDestination) + if err != nil { + return err + } + c.config.HealthLogDestination = &dest + } + + if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + // Assume DB write failed, revert to old resources block + c.config.HealthCheckOnFailureAction = oldHealthCheckOnFailureAction + c.config.HealthLogDestination = oldHealthLogDestination + c.config.HealthMaxLogCount = oldHealthMaxLogCount + c.config.HealthMaxLogSize = oldHealthMaxLogSize + return err + } + + logrus.Debugf("Global HealthCheck configuration updated for container %s", c.ID()) + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_internal_common.go b/vendor/github.com/containers/podman/v5/libpod/container_internal_common.go new file mode 100644 index 00000000..b7b888ab --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_internal_common.go @@ -0,0 +1,3164 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "context" + "errors" + "fmt" + "io" + "io/fs" + "math" + "net" + "os" + "os/user" + "path" + "path/filepath" + "runtime" + "slices" + "strconv" + "strings" + "syscall" + "time" + + metadata "github.com/checkpoint-restore/checkpointctl/lib" + "github.com/checkpoint-restore/go-criu/v7/stats" + "github.com/containers/buildah" + "github.com/containers/buildah/pkg/chrootuser" + "github.com/containers/buildah/pkg/overlay" + butil "github.com/containers/buildah/util" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/pkg/annotations" + "github.com/containers/podman/v5/pkg/checkpoint/crutils" + "github.com/containers/podman/v5/pkg/criu" + "github.com/containers/podman/v5/pkg/lookup" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/util" + "github.com/containers/podman/v5/version" + securejoin "github.com/cyphar/filepath-securejoin" + runcuser "github.com/moby/sys/user" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/selinux/go-selinux" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/etchosts" + "go.podman.io/common/libnetwork/resolvconf" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/apparmor" + "go.podman.io/common/pkg/chown" + "go.podman.io/common/pkg/config" + libartTypes "go.podman.io/common/pkg/libartifact/types" + "go.podman.io/common/pkg/subscriptions" + "go.podman.io/common/pkg/umask" + is "go.podman.io/image/v5/storage" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/lockfile" + "go.podman.io/storage/pkg/unshare" + stypes "go.podman.io/storage/types" + "golang.org/x/sys/unix" + cdi "tags.cncf.io/container-device-interface/pkg/cdi" +) + +func parseOptionIDs(ctrMappings []idtools.IDMap, option string) ([]idtools.IDMap, error) { + ranges := strings.Split(option, "#") + ret := make([]idtools.IDMap, len(ranges)) + for i, m := range ranges { + var v idtools.IDMap + + if m == "" { + return nil, fmt.Errorf("invalid empty range for %q", option) + } + + relative := false + if m[0] == '@' { + relative = true + m = m[1:] + } + _, err := fmt.Sscanf(m, "%d-%d-%d", &v.ContainerID, &v.HostID, &v.Size) + if err != nil { + return nil, err + } + if v.ContainerID < 0 || v.HostID < 0 || v.Size < 1 { + return nil, fmt.Errorf("invalid value for %q", option) + } + + if relative { + found := false + for _, m := range ctrMappings { + if v.HostID >= m.ContainerID && v.HostID < m.ContainerID+m.Size { + v.HostID += m.HostID - m.ContainerID + found = true + break + } + } + if !found { + return nil, fmt.Errorf("could not find a user namespace mapping for the relative mapping %q", option) + } + } + ret[i] = v + } + return ret, nil +} + +func parseIDMapMountOption(idMappings stypes.IDMappingOptions, option string) ([]spec.LinuxIDMapping, []spec.LinuxIDMapping, error) { + uidMap := idMappings.UIDMap + gidMap := idMappings.GIDMap + if strings.HasPrefix(option, "idmap=") { + var err error + options := strings.SplitSeq(strings.SplitN(option, "=", 2)[1], ";") + for i := range options { + switch { + case strings.HasPrefix(i, "uids="): + uidMap, err = parseOptionIDs(idMappings.UIDMap, strings.Replace(i, "uids=", "", 1)) + if err != nil { + return nil, nil, err + } + case strings.HasPrefix(i, "gids="): + gidMap, err = parseOptionIDs(idMappings.GIDMap, strings.Replace(i, "gids=", "", 1)) + if err != nil { + return nil, nil, err + } + default: + return nil, nil, fmt.Errorf("unknown option %q", i) + } + } + } + + uidMappings := make([]spec.LinuxIDMapping, len(uidMap)) + gidMappings := make([]spec.LinuxIDMapping, len(gidMap)) + for i, uidmap := range uidMap { + uidMappings[i] = spec.LinuxIDMapping{ + HostID: uint32(uidmap.HostID), + ContainerID: uint32(uidmap.ContainerID), + Size: uint32(uidmap.Size), + } + } + for i, gidmap := range gidMap { + gidMappings[i] = spec.LinuxIDMapping{ + HostID: uint32(gidmap.HostID), + ContainerID: uint32(gidmap.ContainerID), + Size: uint32(gidmap.Size), + } + } + return uidMappings, gidMappings, nil +} + +// Internal only function which returns upper and work dir from +// overlay options. +func getOverlayUpperAndWorkDir(options []string) (string, string, error) { + upperDir := "" + workDir := "" + for _, o := range options { + if strings.HasPrefix(o, "upperdir") { + splitOpt := strings.SplitN(o, "=", 2) + if len(splitOpt) > 1 { + upperDir = splitOpt[1] + if upperDir == "" { + return "", "", errors.New("cannot accept empty value for upperdir") + } + } + } + if strings.HasPrefix(o, "workdir") { + splitOpt := strings.SplitN(o, "=", 2) + if len(splitOpt) > 1 { + workDir = splitOpt[1] + if workDir == "" { + return "", "", errors.New("cannot accept empty value for workdir") + } + } + } + } + if (upperDir != "" && workDir == "") || (upperDir == "" && workDir != "") { + return "", "", errors.New("must specify both upperdir and workdir") + } + return upperDir, workDir, nil +} + +// Internal only function which creates the Rootfs for default internal +// pause image and configures the Rootfs in the Container. +func (c *Container) createInitRootfs() error { + tmpDir, err := c.runtime.TmpDir() + if err != nil { + return fmt.Errorf("getting runtime temporary directory: %w", err) + } + tmpDir = filepath.Join(tmpDir, "infra-container") + err = os.MkdirAll(tmpDir, 0o755) + if err != nil { + return fmt.Errorf("creating infra container temporary directory: %w", err) + } + + c.config.Rootfs = tmpDir + c.config.RootfsOverlay = true + return nil +} + +// Internal only function which returns the mount-point for the /catatonit. +// This mount-point should be added to the Container spec. +func (c *Container) prepareCatatonitMount() (spec.Mount, error) { + newMount := spec.Mount{ + Type: define.TypeBind, + Source: "", + Destination: "", + Options: append(bindOptions, "ro", "nosuid", "nodev"), + } + + // Also look into the path as some distributions install catatonit in + // /usr/bin. + catatonitPath, err := c.runtime.config.FindInitBinary() + if err != nil { + return newMount, fmt.Errorf("finding catatonit binary: %w", err) + } + catatonitPath, err = filepath.EvalSymlinks(catatonitPath) + if err != nil { + return newMount, fmt.Errorf("follow symlink to catatonit binary: %w", err) + } + + newMount.Source = catatonitPath + newMount.Destination = "/" + filepath.Base(catatonitPath) + + if len(c.config.Entrypoint) == 0 { + c.config.Entrypoint = []string{"/" + filepath.Base(catatonitPath), "-P"} + c.config.Spec.Process.Args = c.config.Entrypoint + } + + return newMount, nil +} + +// Generate spec for a container +// Accepts a map of the container's dependencies +func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFuncRet func(), err error) { + var safeMounts []*safeMountInfo + // lock the thread so that the current thread will be kept alive until the mounts are used + runtime.LockOSThread() + cleanupFunc := func() { + runtime.UnlockOSThread() + for _, s := range safeMounts { + s.Close() + } + } + defer func() { + if err != nil { + cleanupFunc() + } + }() + + if err := c.makeBindMounts(); err != nil { + return nil, nil, err + } + + overrides := c.getUserOverrides() + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides) + if err != nil { + return nil, nil, err + } + + // NewFromSpec() is deprecated according to its comment + // however the recommended replace just causes a nil map panic + g := generate.NewFromSpec(c.config.Spec) + + // If the flag to mount all devices is set for a privileged container, add + // all the devices from the host's machine into the container + if c.config.MountAllDevices { + systemdMode := false + if c.config.Systemd != nil { + systemdMode = *c.config.Systemd + } + if err := util.AddPrivilegedDevices(&g, systemdMode); err != nil { + return nil, nil, err + } + } + + // If network namespace was requested, add it now + if err := c.addNetworkNamespace(&g); err != nil { + return nil, nil, err + } + + // Apply AppArmor checks and load the default profile if needed. + if len(c.config.Spec.Process.ApparmorProfile) > 0 { + updatedProfile, err := apparmor.CheckProfileAndLoadDefault(c.config.Spec.Process.ApparmorProfile) + if err != nil { + return nil, nil, err + } + g.SetProcessApparmorProfile(updatedProfile) + } + + if err := c.mountNotifySocket(g); err != nil { + return nil, nil, err + } + + // Get host UID and GID based on the container process UID and GID. + hostUID, hostGID, err := butil.GetHostIDs(util.IDtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), util.IDtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), uint32(execUser.Uid), uint32(execUser.Gid)) + if err != nil { + return nil, nil, err + } + + // Add named volumes + for _, namedVol := range c.config.NamedVolumes { + volume, err := c.runtime.GetVolume(namedVol.Name) + if err != nil { + return nil, nil, fmt.Errorf("retrieving volume %s to add to container %s: %w", namedVol.Name, c.ID(), err) + } + mountPoint, err := volume.MountPoint() + if err != nil { + return nil, nil, err + } + + if len(namedVol.SubPath) > 0 { + safeMount, err := c.safeMountSubPath(mountPoint, namedVol.SubPath) + if err != nil { + return nil, nil, err + } + safeMounts = append(safeMounts, safeMount) + + mountPoint = safeMount.mountPoint + } + + overlayFlag := false + upperDir := "" + workDir := "" + for _, o := range namedVol.Options { + if o == "O" { + overlayFlag = true + upperDir, workDir, err = getOverlayUpperAndWorkDir(namedVol.Options) + if err != nil { + return nil, nil, err + } + } + } + + if overlayFlag { + var overlayMount spec.Mount + var overlayOpts *overlay.Options + contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID()) + if err != nil { + return nil, nil, err + } + + overlayOpts = &overlay.Options{RootUID: c.RootUID(), + RootGID: c.RootGID(), + UpperDirOptionFragment: upperDir, + WorkDirOptionFragment: workDir, + GraphOpts: c.runtime.store.GraphOptions(), + } + + overlayMount, err = overlay.MountWithOptions(contentDir, mountPoint, namedVol.Dest, overlayOpts) + if err != nil { + return nil, nil, fmt.Errorf("mounting overlay failed %q: %w", mountPoint, err) + } + + for _, o := range namedVol.Options { + if o == "U" { + if err := c.ChangeHostPathOwnership(mountPoint, true, int(hostUID), int(hostGID)); err != nil { + return nil, nil, err + } + + if err := c.ChangeHostPathOwnership(contentDir, true, int(hostUID), int(hostGID)); err != nil { + return nil, nil, err + } + } + } + g.AddMount(overlayMount) + } else { + volMount := spec.Mount{ + Type: define.TypeBind, + Source: mountPoint, + Destination: namedVol.Dest, + Options: namedVol.Options, + } + g.AddMount(volMount) + } + } + + // Check if the spec file mounts contain the options z, Z, U or idmap. + // If they have z or Z, relabel the source directory and then remove the option. + // If they have U, chown the source directory and then remove the option. + // If they have idmap, then calculate the mappings to use in the OCI config file. + for i := range g.Config.Mounts { + m := &g.Config.Mounts[i] + var options []string + for _, o := range m.Options { + if strings.HasPrefix(o, "subpath=") { + subpath := strings.Split(o, "=")[1] + safeMount, err := c.safeMountSubPath(m.Source, subpath) + if err != nil { + return nil, nil, err + } + safeMounts = append(safeMounts, safeMount) + m.Source = safeMount.mountPoint + continue + } + if o == "idmap" || strings.HasPrefix(o, "idmap=") { + var err error + m.UIDMappings, m.GIDMappings, err = parseIDMapMountOption(c.config.IDMappings, o) + if err != nil { + return nil, nil, err + } + continue + } + switch o { + case "U": + if m.Type == define.TypeTmpfs { + options = append(options, []string{fmt.Sprintf("uid=%d", execUser.Uid), fmt.Sprintf("gid=%d", execUser.Gid)}...) + } else { + // only chown on initial creation of container + if err := c.ChangeHostPathOwnership(m.Source, true, int(hostUID), int(hostGID)); err != nil { + return nil, nil, err + } + } + case "z": + fallthrough + case "Z": + if err := c.relabel(m.Source, c.MountLabel(), label.IsShared(o)); err != nil { + return nil, nil, err + } + case "no-dereference": + // crun calls the option `copy-symlink`. + // Podman decided for --no-dereference as many + // bin-utils tools (e..g, touch, chown, cp) do. + options = append(options, "copy-symlink") + case "copy", "nocopy": + // no real OCI runtime bind mount options, these should already be handled by the named volume mount above + default: + options = append(options, o) + } + } + m.Options = options + } + + c.setProcessLabel(&g) + c.setMountLabel(&g) + + if c.IsDefaultInfra() || c.IsService() { + newMount, err := c.prepareCatatonitMount() + if err != nil { + return nil, nil, err + } + g.AddMount(newMount) + } + + // Add bind mounts to container + for dstPath, srcPath := range c.state.BindMounts { + newMount := spec.Mount{ + Type: define.TypeBind, + Source: srcPath, + Destination: dstPath, + Options: bindOptions, + } + if c.IsReadOnly() && (dstPath != "/dev/shm" || !c.config.ReadWriteTmpfs) { + newMount.Options = append(newMount.Options, "ro", "nosuid", "noexec", "nodev") + } + if dstPath == "/dev/shm" && c.state.BindMounts["/dev/shm"] == c.config.ShmDir { + newMount.Options = append(newMount.Options, "nosuid", "noexec", "nodev") + } + if !MountExists(g.Mounts(), dstPath) { + g.AddMount(newMount) + } else { + logrus.Infof("User mount overriding libpod mount at %q", dstPath) + } + } + + // Add overlay volumes + for _, overlayVol := range c.config.OverlayVolumes { + upperDir, workDir, err := getOverlayUpperAndWorkDir(overlayVol.Options) + if err != nil { + return nil, nil, err + } + contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID()) + if err != nil { + return nil, nil, err + } + overlayOpts := &overlay.Options{RootUID: c.RootUID(), + RootGID: c.RootGID(), + UpperDirOptionFragment: upperDir, + WorkDirOptionFragment: workDir, + GraphOpts: c.runtime.store.GraphOptions(), + } + + overlayMount, err := overlay.MountWithOptions(contentDir, overlayVol.Source, overlayVol.Dest, overlayOpts) + if err != nil { + return nil, nil, fmt.Errorf("mounting overlay failed %q: %w", overlayVol.Source, err) + } + + // Check overlay volume options + for _, o := range overlayVol.Options { + if o == "U" { + if err := c.ChangeHostPathOwnership(overlayVol.Source, true, int(hostUID), int(hostGID)); err != nil { + return nil, nil, err + } + + if err := c.ChangeHostPathOwnership(contentDir, true, int(hostUID), int(hostGID)); err != nil { + return nil, nil, err + } + } + } + + g.AddMount(overlayMount) + } + + // Add image volumes as overlay mounts + for _, volume := range c.config.ImageVolumes { + // Mount the specified image. + img, _, err := c.runtime.LibimageRuntime().LookupImage(volume.Source, nil) + if err != nil { + return nil, nil, fmt.Errorf("creating image volume %q:%q: %w", volume.Source, volume.Dest, err) + } + mountPoint, err := img.Mount(ctx, nil, "") + if err != nil { + return nil, nil, fmt.Errorf("mounting image volume %q:%q: %w", volume.Source, volume.Dest, err) + } + + contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID()) + if err != nil { + return nil, nil, fmt.Errorf("failed to create TempDir in the %s directory: %w", c.config.StaticDir, err) + } + + imagePath := mountPoint + if volume.SubPath != "" { + safeMount, err := c.safeMountSubPath(mountPoint, volume.SubPath) + if err != nil { + return nil, nil, err + } + + safeMounts = append(safeMounts, safeMount) + + imagePath = safeMount.mountPoint + } + + var overlayMount spec.Mount + if volume.ReadWrite { + overlayMount, err = overlay.Mount(contentDir, imagePath, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) + } else { + overlayMount, err = overlay.MountReadOnly(contentDir, imagePath, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions()) + } + if err != nil { + return nil, nil, fmt.Errorf("creating overlay mount for image %q failed: %w", volume.Source, err) + } + g.AddMount(overlayMount) + } + + if len(c.config.ArtifactVolumes) > 0 { + artStore, err := c.runtime.ArtifactStore() + if err != nil { + return nil, nil, err + } + for _, artifactMount := range c.config.ArtifactVolumes { + paths, err := artStore.BlobMountPaths(ctx, artifactMount.Source, &libartTypes.BlobMountPathOptions{ + FilterBlobOptions: libartTypes.FilterBlobOptions{ + Title: artifactMount.Title, + Digest: artifactMount.Digest, + }, + }) + if err != nil { + return nil, nil, err + } + + destIsFile, err := containerPathIsFile(c.state.Mountpoint, artifactMount.Dest) + // When the file does not exists and the artifact has only a single blob to mount + // assume it is a file so we use the dest path as direct mount. + if err != nil && len(paths) == 1 && errors.Is(err, fs.ErrNotExist) { + destIsFile = true + } + if destIsFile && len(paths) > 1 { + return nil, nil, fmt.Errorf("artifact %q contains more than one blob and container path %q is a file", artifactMount.Source, artifactMount.Dest) + } + + for i, path := range paths { + var dest string + if destIsFile { + dest = artifactMount.Dest + } else { + var filename string + if artifactMount.Name != "" { + filename = artifactMount.Name + if len(paths) > 1 { + filename += "-" + strconv.Itoa(i) + } + } else { + filename = path.Name + } + dest = filepath.Join(artifactMount.Dest, filename) + } + + logrus.Debugf("Mounting artifact %q in container %s, mount blob %q to %q", artifactMount.Source, c.ID(), path.SourcePath, dest) + + g.AddMount(spec.Mount{ + Destination: dest, + Source: path.SourcePath, + Type: define.TypeBind, + // Important: This must always be mounted read only here, we are using + // the source in the artifact store directly and because that is digest + // based a write will break the layout. + Options: []string{define.TypeBind, "ro"}, + }) + } + } + } + + err = c.setHomeEnvIfNeeded() + if err != nil { + return nil, nil, err + } + + if c.config.User != "" { + // User and Group must go together + g.SetProcessUID(uint32(execUser.Uid)) + g.SetProcessGID(uint32(execUser.Gid)) + g.AddProcessAdditionalGid(uint32(execUser.Gid)) + } + + if c.config.Umask != "" { + umask, err := c.umask() + if err != nil { + return nil, nil, err + } + g.Config.Process.User.Umask = &umask + } + + // Add addition groups if c.config.GroupAdd is not empty + if len(c.config.Groups) > 0 { + gids, err := lookup.GetContainerGroups(c.config.Groups, c.state.Mountpoint, overrides) + if err != nil { + return nil, nil, fmt.Errorf("looking up supplemental groups for container %s: %w", c.ID(), err) + } + for _, gid := range gids { + g.AddProcessAdditionalGid(gid) + } + } + + if err := c.addSystemdMounts(&g); err != nil { + return nil, nil, err + } + + // Look up and add groups the user belongs to, if a group wasn't directly specified + if !strings.Contains(c.config.User, ":") { + // the gidMappings that are present inside the container user namespace + var gidMappings []idtools.IDMap + + switch { + case len(c.config.IDMappings.GIDMap) > 0: + gidMappings = c.config.IDMappings.GIDMap + case rootless.IsRootless(): + // Check whether the current user namespace has enough gids available. + availableGids, err := rootless.GetAvailableGids() + if err != nil { + return nil, nil, fmt.Errorf("cannot read number of available GIDs: %w", err) + } + gidMappings = []idtools.IDMap{{ + ContainerID: 0, + HostID: 0, + Size: int(availableGids), + }} + default: + gidMappings = []idtools.IDMap{{ + ContainerID: 0, + HostID: 0, + Size: math.MaxInt32, + }} + } + for _, gid := range execUser.Sgids { + isGIDAvailable := false + for _, m := range gidMappings { + if gid >= m.ContainerID && gid < m.ContainerID+m.Size { + isGIDAvailable = true + break + } + } + if isGIDAvailable { + g.AddProcessAdditionalGid(uint32(gid)) + } else { + logrus.Warnf("Additional gid=%d is not present in the user namespace, skip setting it", gid) + } + } + } + + // Add shared namespaces from other containers + if err := c.addSharedNamespaces(&g); err != nil { + return nil, nil, err + } + + rootPath, err := c.getRootPathForOCI() + if err != nil { + return nil, nil, err + } + g.SetRootPath(rootPath) + g.AddAnnotation("org.opencontainers.image.stopSignal", strconv.FormatUint(uint64(c.config.StopSignal), 10)) + + if c.config.StopSignal != 0 { + g.AddAnnotation("org.systemd.property.KillSignal", strconv.FormatUint(uint64(c.config.StopSignal), 10)) + } + + if c.config.StopTimeout != 0 { + annotation := fmt.Sprintf("uint64 %d", c.config.StopTimeout*1000000) // sec to usec + g.AddAnnotation("org.systemd.property.TimeoutStopUSec", annotation) + } + + if _, exists := g.Config.Annotations[annotations.ContainerManager]; !exists { + g.AddAnnotation(annotations.ContainerManager, annotations.ContainerManagerLibpod) + } + + if err := c.setCgroupsPath(&g); err != nil { + return nil, nil, err + } + + // Warning: CDI may alter g.Config in place. + if len(c.config.CDIDevices) > 0 { + registry, err := cdi.NewCache( + cdi.WithSpecDirs(c.runtime.config.Engine.CdiSpecDirs.Get()...), + cdi.WithAutoRefresh(false), + ) + if err != nil { + return nil, nil, fmt.Errorf("creating CDI registry: %w", err) + } + if err := registry.Refresh(); err != nil { + logrus.Debugf("The following error was triggered when refreshing the CDI registry: %v", err) + } + if _, err := registry.InjectDevices(g.Config, c.config.CDIDevices...); err != nil { + return nil, nil, fmt.Errorf("setting up CDI devices: %w", err) + } + } + + // Mounts need to be sorted so paths will not cover other paths + mounts := sortMounts(g.Mounts()) + g.ClearMounts() + + for _, m := range mounts { + // We need to remove all symlinks from tmpfs mounts. + // Runc and other runtimes may choke on them. + // Easy solution: use securejoin to do a scoped evaluation of + // the links, then trim off the mount prefix. + if m.Type == define.TypeTmpfs { + finalPath, err := securejoin.SecureJoin(c.state.Mountpoint, m.Destination) + if err != nil { + return nil, nil, fmt.Errorf("resolving symlinks for mount destination %s: %w", m.Destination, err) + } + trimmedPath := strings.TrimPrefix(finalPath, strings.TrimSuffix(c.state.Mountpoint, "/")) + m.Destination = trimmedPath + } + g.AddMount(m) + } + + if err := c.addRootPropagation(&g, mounts); err != nil { + return nil, nil, err + } + + // Warning: precreate hooks may alter g.Config in place. + if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil { + return nil, nil, fmt.Errorf("setting up OCI Hooks: %w", err) + } + if len(c.config.EnvSecrets) > 0 { + manager, err := c.runtime.SecretsManager() + if err != nil { + return nil, nil, err + } + for name, secr := range c.config.EnvSecrets { + _, data, err := manager.LookupSecretData(secr.Name) + if err != nil { + return nil, nil, err + } + g.AddProcessEnv(name, string(data)) + } + } + + // Pass down the LISTEN_* environment (see #10443). + for _, key := range []string{"LISTEN_PID", "LISTEN_FDS", "LISTEN_FDNAMES"} { + if val, ok := os.LookupEnv(key); ok { + // Force the PID to `1` since we cannot rely on (all + // versions of) all runtimes to do it for us. + if key == "LISTEN_PID" { + val = "1" + } + g.AddProcessEnv(key, val) + } + } + + // setup rlimits + nofileSet := false + nprocSet := false + isRunningInUserNs := unshare.IsRootless() + if isRunningInUserNs && g.Config.Process != nil && g.Config.Process.OOMScoreAdj != nil { + var err error + *g.Config.Process.OOMScoreAdj, err = maybeClampOOMScoreAdj(*g.Config.Process.OOMScoreAdj) + if err != nil { + return nil, nil, err + } + } + for _, rlimit := range c.config.Spec.Process.Rlimits { + if rlimit.Type == "RLIMIT_NOFILE" { + nofileSet = true + } + if rlimit.Type == "RLIMIT_NPROC" { + nprocSet = true + } + } + needsClamping := false + if !nofileSet || !nprocSet { + needsClamping = isRunningInUserNs + if !needsClamping { + has, err := hasCapSysResource() + if err != nil { + return nil, nil, err + } + needsClamping = !has + } + } + if !nofileSet { + max := rlimT(define.RLimitDefaultValue) + current := rlimT(define.RLimitDefaultValue) + if needsClamping { + var rlimit unix.Rlimit + if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { + logrus.Warnf("Failed to return RLIMIT_NOFILE ulimit %q", err) + } + if rlimT(rlimit.Cur) < current { + current = rlimT(rlimit.Cur) + } + if rlimT(rlimit.Max) < max { + max = rlimT(rlimit.Max) + } + } + g.AddProcessRlimits("RLIMIT_NOFILE", uint64(max), uint64(current)) + } + if !nprocSet { + max := rlimT(define.RLimitDefaultValue) + current := rlimT(define.RLimitDefaultValue) + if needsClamping { + var rlimit unix.Rlimit + if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil { + logrus.Warnf("Failed to return RLIMIT_NPROC ulimit %q", err) + } + if rlimT(rlimit.Cur) < current { + current = rlimT(rlimit.Cur) + } + if rlimT(rlimit.Max) < max { + max = rlimT(rlimit.Max) + } + } + g.AddProcessRlimits("RLIMIT_NPROC", uint64(max), uint64(current)) + } + + c.addMaskedPaths(&g) + + return g.Config, cleanupFunc, nil +} + +// resolveWorkDir resolves the container's workdir and, depending on the +// configuration, will create it, or error out if it does not exist. +// Note that the container must be mounted before. +func (c *Container) resolveWorkDir() error { + workdir := c.WorkingDir() + + // If the specified workdir is a subdir of a volume or mount, + // we don't need to do anything. The runtime is taking care of + // that. + if isPathOnVolume(c, workdir) || isPathOnMount(c, workdir) { + logrus.Debugf("Workdir %q resolved to a volume or mount", workdir) + return nil + } + + resolvedWorkdir, err := securejoin.SecureJoin(c.state.Mountpoint, workdir) + if err != nil { + return err + } + logrus.Debugf("Workdir %q resolved to host path %q", workdir, resolvedWorkdir) + + st, err := os.Stat(resolvedWorkdir) + if err == nil { + if !st.IsDir() { + return fmt.Errorf("workdir %q exists on container %s, but is not a directory", workdir, c.ID()) + } + return nil + } + if !c.config.CreateWorkingDir { + // No need to create it (e.g., `--workdir=/foo`), so let's make sure + // the path exists on the container. + if errors.Is(err, os.ErrNotExist) { + // Check if path is a symlink, securejoin resolves and follows the links + // so the path will be different from the normal join if it is one. + if resolvedWorkdir != filepath.Join(c.state.Mountpoint, workdir) { + // Path must be a symlink to non existing directory. + // It could point to mounts that are only created later so that make + // an assumption here and let's just continue and let the oci runtime + // do its job. + return nil + } + // If they are the same we know there is no symlink/relative path involved. + // We can return a nicer error message without having to go through the OCI runtime. + return fmt.Errorf("workdir %q does not exist on container %s", workdir, c.ID()) + } + // This might be a serious error (e.g., permission), so + // we need to return the full error. + return fmt.Errorf("detecting workdir %q on container %s: %w", workdir, c.ID(), err) + } + if err := os.MkdirAll(resolvedWorkdir, 0o755); err != nil { + if errors.Is(err, fs.ErrExist) { + return nil + } + return fmt.Errorf("creating container %s workdir: %w", c.ID(), err) + } + + // Ensure container entrypoint is created (if required). + uid, gid, _, err := chrootuser.GetUser(c.state.Mountpoint, c.User()) + if err != nil { + return fmt.Errorf("looking up %s inside of the container %s: %w", c.User(), c.ID(), err) + } + if err := idtools.SafeChown(resolvedWorkdir, int(uid), int(gid)); err != nil { + return fmt.Errorf("chowning container %s workdir to container root: %w", c.ID(), err) + } + + return nil +} + +func (c *Container) getUserOverrides() *lookup.Overrides { + var hasPasswdFile, hasGroupFile bool + overrides := lookup.Overrides{} + for _, m := range c.config.Spec.Mounts { + if m.Destination == "/etc/passwd" { + overrides.ContainerEtcPasswdPath = m.Source + hasPasswdFile = true + } + if m.Destination == "/etc/group" { + overrides.ContainerEtcGroupPath = m.Source + hasGroupFile = true + } + if m.Destination == "/etc" { + if !hasPasswdFile { + overrides.ContainerEtcPasswdPath = filepath.Join(m.Source, "passwd") + } + if !hasGroupFile { + overrides.ContainerEtcGroupPath = filepath.Join(m.Source, "group") + } + } + } + if path, ok := c.state.BindMounts["/etc/passwd"]; ok { + overrides.ContainerEtcPasswdPath = path + } + return &overrides +} + +func lookupHostUser(name string) (*runcuser.ExecUser, error) { + var execUser runcuser.ExecUser + // Look up User on host + u, err := util.LookupUser(name) + if err != nil { + return &execUser, err + } + uid, err := strconv.ParseUint(u.Uid, 10, 32) + if err != nil { + return &execUser, err + } + + gid, err := strconv.ParseUint(u.Gid, 10, 32) + if err != nil { + return &execUser, err + } + execUser.Uid = int(uid) + execUser.Gid = int(gid) + execUser.Home = u.HomeDir + return &execUser, nil +} + +// mountNotifySocket mounts the NOTIFY_SOCKET into the container if it's set +// and if the sdnotify mode is set to container. It also sets c.notifySocket +// to avoid redundantly looking up the env variable. +func (c *Container) mountNotifySocket(g generate.Generator) error { + if c.config.SdNotifySocket == "" { + return nil + } + if c.config.SdNotifyMode != define.SdNotifyModeContainer { + return nil + } + + notifyDir := filepath.Join(c.bundlePath(), "notify") + logrus.Debugf("Checking notify %q dir", notifyDir) + if err := os.MkdirAll(notifyDir, 0o755); err != nil { + if !os.IsExist(err) { + return fmt.Errorf("unable to create notify %q dir: %w", notifyDir, err) + } + } + if err := c.relabel(notifyDir, c.MountLabel(), true); err != nil { + return fmt.Errorf("relabel failed %q: %w", notifyDir, err) + } + logrus.Debugf("Add bindmount notify %q dir", notifyDir) + if _, ok := c.state.BindMounts["/run/notify"]; !ok { + c.state.BindMounts["/run/notify"] = notifyDir + } + + // Set the container's notify socket to the proxy socket created by conmon + g.AddProcessEnv("NOTIFY_SOCKET", "/run/notify/notify.sock") + + return nil +} + +func (c *Container) addCheckpointImageMetadata(importBuilder *buildah.Builder) error { + // Get information about host environment + hostInfo, err := c.Runtime().hostInfo() + if err != nil { + return fmt.Errorf("getting host info: %v", err) + } + + criuVersion, err := criu.GetCriuVersion() + if err != nil { + return fmt.Errorf("getting criu version: %v", err) + } + + rootfsImageID, rootfsImageName := c.Image() + + // Add image annotations with information about the container and the host. + // This information is useful to check compatibility before restoring the checkpoint + + checkpointImageAnnotations := map[string]string{ + define.CheckpointAnnotationName: c.config.Name, + define.CheckpointAnnotationRawImageName: c.config.RawImageName, + define.CheckpointAnnotationRootfsImageID: rootfsImageID, + define.CheckpointAnnotationRootfsImageName: rootfsImageName, + define.CheckpointAnnotationPodmanVersion: version.Version.String(), + define.CheckpointAnnotationCriuVersion: strconv.Itoa(criuVersion), + define.CheckpointAnnotationRuntimeName: hostInfo.OCIRuntime.Name, + define.CheckpointAnnotationRuntimeVersion: hostInfo.OCIRuntime.Version, + define.CheckpointAnnotationConmonVersion: hostInfo.Conmon.Version, + define.CheckpointAnnotationHostArch: hostInfo.Arch, + define.CheckpointAnnotationHostKernel: hostInfo.Kernel, + define.CheckpointAnnotationCgroupVersion: hostInfo.CgroupsVersion, + define.CheckpointAnnotationDistributionVersion: hostInfo.Distribution.Version, + define.CheckpointAnnotationDistributionName: hostInfo.Distribution.Distribution, + } + + for key, value := range checkpointImageAnnotations { + importBuilder.SetAnnotation(key, value) + } + + return nil +} + +func (c *Container) resolveCheckpointImageName(options *ContainerCheckpointOptions) error { + if options.CreateImage == "" { + return nil + } + + // Resolve image name + resolvedImageName, err := c.runtime.LibimageRuntime().ResolveName(options.CreateImage) + if err != nil { + return err + } + + options.CreateImage = resolvedImageName + return nil +} + +func (c *Container) createCheckpointImage(ctx context.Context, options ContainerCheckpointOptions) error { + if options.CreateImage == "" { + return nil + } + logrus.Debugf("Create checkpoint image %s", options.CreateImage) + + // Create storage reference + imageRef, err := is.Transport.ParseStoreReference(c.runtime.store, options.CreateImage) + if err != nil { + return errors.New("failed to parse image name") + } + + // Build an image scratch + builderOptions := buildah.BuilderOptions{ + FromImage: "scratch", + } + importBuilder, err := buildah.NewBuilder(ctx, c.runtime.store, builderOptions) + if err != nil { + return err + } + // Clean up buildah working container + defer func() { + if err := importBuilder.Delete(); err != nil { + logrus.Errorf("Image builder delete failed: %v", err) + } + }() + + if err := c.prepareCheckpointExport(); err != nil { + return err + } + + // Export checkpoint into temporary tar file + tmpDir, err := os.MkdirTemp("", "checkpoint_image_") + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + + options.TargetFile = path.Join(tmpDir, "checkpoint.tar") + + if err := c.exportCheckpoint(options); err != nil { + return err + } + + // Copy checkpoint from temporary tar file in the image + addAndCopyOptions := buildah.AddAndCopyOptions{} + if err := importBuilder.Add("", true, addAndCopyOptions, options.TargetFile); err != nil { + return err + } + + if err := c.addCheckpointImageMetadata(importBuilder); err != nil { + return err + } + + commitOptions := buildah.CommitOptions{ + Squash: true, + SystemContext: c.runtime.imageContext, + } + + // Create checkpoint image + id, _, _, err := importBuilder.Commit(ctx, imageRef, commitOptions) + if err != nil { + return err + } + logrus.Debugf("Created checkpoint image: %s", id) + return nil +} + +func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error { + if len(c.Dependencies()) == 1 { + // Check if the dependency is an infra container. If it is we can checkpoint + // the container out of the Pod. + if c.config.Pod == "" { + return errors.New("cannot export checkpoints of containers with dependencies") + } + + pod, err := c.runtime.state.Pod(c.config.Pod) + if err != nil { + return fmt.Errorf("container %s is in pod %s, but pod cannot be retrieved: %w", c.ID(), c.config.Pod, err) + } + infraID, err := pod.InfraContainerID() + if err != nil { + return fmt.Errorf("cannot retrieve infra container ID for pod %s: %w", c.config.Pod, err) + } + if c.Dependencies()[0] != infraID { + return errors.New("cannot export checkpoints of containers with dependencies") + } + } + if len(c.Dependencies()) > 1 { + return errors.New("cannot export checkpoints of containers with dependencies") + } + logrus.Debugf("Exporting checkpoint image of container %q to %q", c.ID(), options.TargetFile) + + includeFiles := []string{ + "artifacts", + metadata.DevShmCheckpointTar, + metadata.ConfigDumpFile, + metadata.SpecDumpFile, + metadata.NetworkStatusFile, + stats.StatsDump, + } + + if c.LogDriver() == define.KubernetesLogging || + c.LogDriver() == define.JSONLogging { + includeFiles = append(includeFiles, "ctr.log") + } + if options.PreCheckPoint { + includeFiles = append(includeFiles, preCheckpointDir) + } else { + includeFiles = append(includeFiles, metadata.CheckpointDirectory) + } + // Get root file-system changes included in the checkpoint archive + var addToTarFiles []string + if !options.IgnoreRootfs { + // To correctly track deleted files, let's go through the output of 'podman diff' + rootFsChanges, err := c.runtime.GetDiff("", c.ID(), define.DiffContainer) + if err != nil { + return fmt.Errorf("exporting root file-system diff for %q: %w", c.ID(), err) + } + + addToTarFiles, err = crutils.CRCreateRootFsDiffTar(&rootFsChanges, c.state.Mountpoint, c.bundlePath()) + if err != nil { + return err + } + + includeFiles = append(includeFiles, addToTarFiles...) + } + + // Folder containing archived volumes that will be included in the export + expVolDir := filepath.Join(c.bundlePath(), metadata.CheckpointVolumesDirectory) + + // Create an archive for each volume associated with the container + if !options.IgnoreVolumes { + if err := os.MkdirAll(expVolDir, 0o700); err != nil { + return fmt.Errorf("creating volumes export directory %q: %w", expVolDir, err) + } + + for _, v := range c.config.NamedVolumes { + volumeTarFilePath := filepath.Join(metadata.CheckpointVolumesDirectory, v.Name+".tar") + volumeTarFileFullPath := filepath.Join(c.bundlePath(), volumeTarFilePath) + + volumeTarFile, err := os.Create(volumeTarFileFullPath) + if err != nil { + return fmt.Errorf("creating %q: %w", volumeTarFileFullPath, err) + } + + volume, err := c.runtime.GetVolume(v.Name) + if err != nil { + return err + } + + mp, err := volume.MountPoint() + if err != nil { + return err + } + if mp == "" { + return fmt.Errorf("volume %s is not mounted, cannot export: %w", volume.Name(), define.ErrInternal) + } + + input, err := archive.TarWithOptions(mp, &archive.TarOptions{ + Compression: archive.Uncompressed, + IncludeSourceDir: true, + }) + if err != nil { + return fmt.Errorf("reading volume directory %q: %w", v.Dest, err) + } + + _, err = io.Copy(volumeTarFile, input) + if err != nil { + return err + } + volumeTarFile.Close() + + includeFiles = append(includeFiles, volumeTarFilePath) + } + } + + input, err := archive.TarWithOptions(c.bundlePath(), &archive.TarOptions{ + Compression: options.Compression, + IncludeSourceDir: true, + IncludeFiles: includeFiles, + }) + + if err != nil { + return fmt.Errorf("reading checkpoint directory %q: %w", c.ID(), err) + } + + outFile, err := os.Create(options.TargetFile) + if err != nil { + return fmt.Errorf("creating checkpoint export file %q: %w", options.TargetFile, err) + } + defer outFile.Close() + + if err := os.Chmod(options.TargetFile, 0o600); err != nil { + return err + } + + _, err = io.Copy(outFile, input) + if err != nil { + return err + } + + for _, file := range addToTarFiles { + os.Remove(filepath.Join(c.bundlePath(), file)) + } + + if !options.IgnoreVolumes { + os.RemoveAll(expVolDir) + } + + return nil +} + +func (c *Container) checkpointRestoreSupported(version int) error { + if err := criu.CheckForCriu(version); err != nil { + return err + } + if !c.ociRuntime.SupportsCheckpoint() { + return errors.New("configured runtime does not support checkpoint/restore") + } + return nil +} + +func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) (*define.CRIUCheckpointRestoreStatistics, int64, error) { + if err := c.checkpointRestoreSupported(criu.MinCriuVersion); err != nil { + return nil, 0, err + } + + if c.state.State != define.ContainerStateRunning { + return nil, 0, fmt.Errorf("%q is not running, cannot checkpoint: %w", c.state.State, define.ErrCtrStateInvalid) + } + + if c.AutoRemove() && options.TargetFile == "" { + return nil, 0, errors.New("cannot checkpoint containers that have been started with '--rm' unless '--export' is used") + } + + if err := c.resolveCheckpointImageName(&options); err != nil { + return nil, 0, err + } + + if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "dump.log", c.MountLabel()); err != nil { + return nil, 0, err + } + + // Setting CheckpointLog early in case there is a failure. + c.state.CheckpointLog = path.Join(c.bundlePath(), "dump.log") + c.state.CheckpointPath = c.CheckpointPath() + + runtimeCheckpointDuration, err := c.ociRuntime.CheckpointContainer(c, options) + if err != nil { + return nil, 0, err + } + + // Keep the content of /dev/shm directory + if c.config.ShmDir != "" && c.state.BindMounts["/dev/shm"] == c.config.ShmDir { + shmDirTarFileFullPath := filepath.Join(c.bundlePath(), metadata.DevShmCheckpointTar) + + shmDirTarFile, err := os.Create(shmDirTarFileFullPath) + if err != nil { + return nil, 0, err + } + defer shmDirTarFile.Close() + + input, err := archive.TarWithOptions(c.config.ShmDir, &archive.TarOptions{ + Compression: archive.Uncompressed, + IncludeSourceDir: true, + }) + if err != nil { + return nil, 0, err + } + + if _, err = io.Copy(shmDirTarFile, input); err != nil { + return nil, 0, err + } + } + + // Save network.status. This is needed to restore the container with + // the same IP. Currently limited to one IP address in a container + // with one interface. + // FIXME: will this break something? + if _, err := metadata.WriteJSONFile(c.getNetworkStatus(), c.bundlePath(), metadata.NetworkStatusFile); err != nil { + return nil, 0, err + } + + defer c.newContainerEvent(events.Checkpoint) + + // There is a bug from criu: https://github.com/checkpoint-restore/criu/issues/116 + // We have to change the symbolic link from absolute path to relative path + if options.WithPrevious { + os.Remove(path.Join(c.CheckpointPath(), "parent")) + if err := os.Symlink("../pre-checkpoint", path.Join(c.CheckpointPath(), "parent")); err != nil { + return nil, 0, err + } + } + + if options.TargetFile != "" { + if err := c.exportCheckpoint(options); err != nil { + return nil, 0, err + } + } else { + if err := c.createCheckpointImage(ctx, options); err != nil { + return nil, 0, err + } + } + + logrus.Debugf("Checkpointed container %s", c.ID()) + + if !options.KeepRunning && !options.PreCheckPoint { + c.state.State = define.ContainerStateStopped + c.state.Checkpointed = true + c.state.CheckpointedTime = time.Now() + c.state.Restored = false + c.state.RestoredTime = time.Time{} + + // Clean up Storage and Network + if err := c.cleanup(ctx); err != nil { + return nil, 0, err + } + } + + criuStatistics, err := func() (*define.CRIUCheckpointRestoreStatistics, error) { + if !options.PrintStats { + return nil, nil + } + statsDirectory, err := os.Open(c.bundlePath()) + if err != nil { + return nil, fmt.Errorf("not able to open %q: %w", c.bundlePath(), err) + } + + dumpStatistics, err := stats.CriuGetDumpStats(statsDirectory) + if err != nil { + return nil, fmt.Errorf("displaying checkpointing statistics not possible: %w", err) + } + + return &define.CRIUCheckpointRestoreStatistics{ + FreezingTime: dumpStatistics.GetFreezingTime(), + FrozenTime: dumpStatistics.GetFrozenTime(), + MemdumpTime: dumpStatistics.GetMemdumpTime(), + MemwriteTime: dumpStatistics.GetMemwriteTime(), + PagesScanned: dumpStatistics.GetPagesScanned(), + PagesWritten: dumpStatistics.GetPagesWritten(), + }, nil + }() + if err != nil { + return nil, 0, err + } + + if !options.Keep && !options.PreCheckPoint { + cleanup := []string{ + "dump.log", + stats.StatsDump, + metadata.ConfigDumpFile, + metadata.SpecDumpFile, + } + for _, del := range cleanup { + file := filepath.Join(c.bundlePath(), del) + if err := os.Remove(file); err != nil { + logrus.Debugf("Unable to remove file %s", file) + } + } + // The file has been deleted. Do not mention it. + c.state.CheckpointLog = "" + } + + c.state.FinishedTime = time.Now() + return criuStatistics, runtimeCheckpointDuration, c.save() +} + +func (c *Container) generateContainerSpec() error { + // Make sure the newly created config.json exists on disk + + // NewFromSpec() is deprecated according to its comment + // however the recommended replace just causes a nil map panic + g := generate.NewFromSpec(c.config.Spec) + + if err := c.saveSpec(g.Config); err != nil { + return fmt.Errorf("saving imported container specification for restore failed: %w", err) + } + + return nil +} + +func (c *Container) importCheckpointImage(ctx context.Context, imageID string) error { + img, _, err := c.Runtime().LibimageRuntime().LookupImage(imageID, nil) + if err != nil { + return err + } + + mountPoint, err := img.Mount(ctx, nil, "") + defer func() { + if err := c.unmount(true); err != nil { + logrus.Errorf("Failed to unmount container: %v", err) + } + }() + if err != nil { + return err + } + + // Import all checkpoint files except ConfigDumpFile and SpecDumpFile. We + // generate new container config files to enable to specifying a new + // container name. + checkpoint := []string{ + "artifacts", + metadata.CheckpointDirectory, + metadata.CheckpointVolumesDirectory, + metadata.DevShmCheckpointTar, + metadata.RootFsDiffTar, + metadata.DeletedFilesFile, + metadata.PodOptionsFile, + metadata.PodDumpFile, + } + + for _, name := range checkpoint { + src := filepath.Join(mountPoint, name) + dst := filepath.Join(c.bundlePath(), name) + if err := archive.NewDefaultArchiver().CopyWithTar(src, dst); err != nil { + logrus.Debugf("Can't import '%s' from checkpoint image", name) + } + } + + return c.generateContainerSpec() +} + +func (c *Container) importCheckpointTar(input string) error { + if err := crutils.CRImportCheckpointWithoutConfig(c.bundlePath(), input); err != nil { + return err + } + + return c.generateContainerSpec() +} + +func (c *Container) importPreCheckpoint(input string) error { + archiveFile, err := os.Open(input) + if err != nil { + return fmt.Errorf("failed to open pre-checkpoint archive for import: %w", err) + } + + defer archiveFile.Close() + + err = archive.Untar(archiveFile, c.bundlePath(), nil) + if err != nil { + return fmt.Errorf("unpacking of pre-checkpoint archive %s failed: %w", input, err) + } + return nil +} + +func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (criuStatistics *define.CRIUCheckpointRestoreStatistics, runtimeRestoreDuration int64, retErr error) { + minCriuVersion := func() int { + if options.Pod == "" { + return criu.MinCriuVersion + } + return criu.PodCriuVersion + }() + if err := c.checkpointRestoreSupported(minCriuVersion); err != nil { + return nil, 0, err + } + + if options.Pod != "" && !crutils.CRRuntimeSupportsPodCheckpointRestore(c.ociRuntime.Path()) { + return nil, 0, fmt.Errorf("runtime %s does not support pod restore", c.ociRuntime.Path()) + } + + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { + return nil, 0, fmt.Errorf("container %s is running or paused, cannot restore: %w", c.ID(), define.ErrCtrStateInvalid) + } + + if options.ImportPrevious != "" { + if err := c.importPreCheckpoint(options.ImportPrevious); err != nil { + return nil, 0, err + } + } + + if options.TargetFile != "" { + if err := c.importCheckpointTar(options.TargetFile); err != nil { + return nil, 0, err + } + } else if options.CheckpointImageID != "" { + if err := c.importCheckpointImage(ctx, options.CheckpointImageID); err != nil { + return nil, 0, err + } + } + + // Let's try to stat() CRIU's inventory file. If it does not exist, it makes + // no sense to try a restore. This is a minimal check if a checkpoint exists. + if err := fileutils.Exists(filepath.Join(c.CheckpointPath(), "inventory.img")); errors.Is(err, fs.ErrNotExist) { + return nil, 0, fmt.Errorf("a complete checkpoint for this container cannot be found, cannot restore: %w", err) + } + + if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "restore.log", c.MountLabel()); err != nil { + return nil, 0, err + } + + // Setting RestoreLog early in case there is a failure. + c.state.RestoreLog = path.Join(c.bundlePath(), "restore.log") + c.state.CheckpointPath = c.CheckpointPath() + + if options.IgnoreStaticIP || options.IgnoreStaticMAC { + networks, err := c.networks() + if err != nil { + return nil, 0, err + } + + for net, opts := range networks { + if options.IgnoreStaticIP { + opts.StaticIPs = nil + } + if options.IgnoreStaticMAC { + opts.StaticMAC = nil + } + if err := c.runtime.state.NetworkModify(c, net, opts); err != nil { + return nil, 0, fmt.Errorf("failed to rewrite network config: %w", err) + } + } + } + + // Read network configuration from checkpoint + var netStatus map[string]types.StatusBlock + _, err := metadata.ReadJSONFile(&netStatus, c.bundlePath(), metadata.NetworkStatusFile) + if err != nil { + logrus.Infof("Failed to unmarshal network status, cannot restore the same ip/mac: %v", err) + } + // If the restored container should get a new name, the IP address of + // the container will not be restored. This assumes that if a new name is + // specified, the container is restored multiple times. + // TODO: This implicit restoring with or without IP depending on an + // unrelated restore parameter (--name) does not seem like the + // best solution. + if err == nil && options.Name == "" && (!options.IgnoreStaticIP || !options.IgnoreStaticMAC) { + // The file with the network.status does exist. Let's restore the + // container with the same networks settings as during checkpointing. + networkOpts, err := c.networks() + if err != nil { + return nil, 0, err + } + + netOpts := make(map[string]types.PerNetworkOptions, len(netStatus)) + for network, perNetOpts := range networkOpts { + // unset mac and ips before we start adding the ones from the status + perNetOpts.StaticMAC = nil + perNetOpts.StaticIPs = nil + for name, netInt := range netStatus[network].Interfaces { + perNetOpts.InterfaceName = name + if !options.IgnoreStaticMAC { + perNetOpts.StaticMAC = netInt.MacAddress + } + if !options.IgnoreStaticIP { + for _, netAddress := range netInt.Subnets { + perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.IPNet.IP) + } + } + // Normally interfaces have a length of 1, only for some special cni configs we could get more. + // For now just use the first interface to get the ips this should be good enough for most cases. + break + } + netOpts[network] = perNetOpts + } + c.perNetworkOpts = netOpts + } + + defer func() { + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("Cleaning up container %s: %v", c.ID(), err) + } + } + }() + + if err := c.prepare(); err != nil { + return nil, 0, err + } + + // Read config + jsonPath := filepath.Join(c.bundlePath(), "config.json") + logrus.Debugf("generate.NewFromFile at %v", jsonPath) + g, err := generate.NewFromFile(jsonPath) + if err != nil { + logrus.Debugf("generate.NewFromFile failed with %v", err) + return nil, 0, err + } + + // Restoring from an import means that we are doing migration + if options.TargetFile != "" || options.CheckpointImageID != "" { + g.SetRootPath(c.state.Mountpoint) + } + + // We want to have the same network namespace as before. + if err := c.addNetworkNamespace(&g); err != nil { + return nil, 0, err + } + + if options.Pod != "" { + // Running in a Pod means that we have to change all namespace settings to + // the ones from the infrastructure container. + pod, err := c.runtime.LookupPod(options.Pod) + if err != nil { + return nil, 0, fmt.Errorf("pod %q cannot be retrieved: %w", options.Pod, err) + } + + infraContainer, err := pod.InfraContainer() + if err != nil { + return nil, 0, fmt.Errorf("cannot retrieved infra container from pod %q: %w", options.Pod, err) + } + + infraContainer.lock.Lock() + if err := infraContainer.syncContainer(); err != nil { + infraContainer.lock.Unlock() + return nil, 0, fmt.Errorf("syncing infrastructure container %s status: %w", infraContainer.ID(), err) + } + if infraContainer.state.State != define.ContainerStateRunning { + if err := infraContainer.initAndStart(ctx); err != nil { + infraContainer.lock.Unlock() + return nil, 0, fmt.Errorf("starting infrastructure container %s status: %w", infraContainer.ID(), err) + } + } + infraContainer.lock.Unlock() + + if c.config.IPCNsCtr != "" { + nsPath, err := infraContainer.namespacePath(IPCNS) + if err != nil { + return nil, 0, fmt.Errorf("cannot retrieve IPC namespace path for Pod %q: %w", options.Pod, err) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), nsPath); err != nil { + return nil, 0, err + } + } + + if c.config.NetNsCtr != "" { + nsPath, err := infraContainer.namespacePath(NetNS) + if err != nil { + return nil, 0, fmt.Errorf("cannot retrieve network namespace path for Pod %q: %w", options.Pod, err) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), nsPath); err != nil { + return nil, 0, err + } + } + + if c.config.PIDNsCtr != "" { + nsPath, err := infraContainer.namespacePath(PIDNS) + if err != nil { + return nil, 0, fmt.Errorf("cannot retrieve PID namespace path for Pod %q: %w", options.Pod, err) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), nsPath); err != nil { + return nil, 0, err + } + } + + if c.config.UTSNsCtr != "" { + nsPath, err := infraContainer.namespacePath(UTSNS) + if err != nil { + return nil, 0, fmt.Errorf("cannot retrieve UTS namespace path for Pod %q: %w", options.Pod, err) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), nsPath); err != nil { + return nil, 0, err + } + } + + if c.config.CgroupNsCtr != "" { + nsPath, err := infraContainer.namespacePath(CgroupNS) + if err != nil { + return nil, 0, fmt.Errorf("cannot retrieve Cgroup namespace path for Pod %q: %w", options.Pod, err) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), nsPath); err != nil { + return nil, 0, err + } + } + } + + if err := c.makeBindMounts(); err != nil { + return nil, 0, err + } + + if options.TargetFile != "" || options.CheckpointImageID != "" { + for dstPath, srcPath := range c.state.BindMounts { + newMount := spec.Mount{ + Type: define.TypeBind, + Source: srcPath, + Destination: dstPath, + Options: []string{define.TypeBind, "private"}, + } + if c.IsReadOnly() && (dstPath != "/dev/shm" || !c.config.ReadWriteTmpfs) { + newMount.Options = append(newMount.Options, "ro", "nosuid", "noexec", "nodev") + } + if dstPath == "/dev/shm" && c.state.BindMounts["/dev/shm"] == c.config.ShmDir { + newMount.Options = append(newMount.Options, "nosuid", "noexec", "nodev") + } + if !MountExists(g.Mounts(), dstPath) { + g.AddMount(newMount) + } + } + } + + // Restore /dev/shm content + if c.config.ShmDir != "" && c.state.BindMounts["/dev/shm"] == c.config.ShmDir { + shmDirTarFileFullPath := filepath.Join(c.bundlePath(), metadata.DevShmCheckpointTar) + if err := fileutils.Exists(shmDirTarFileFullPath); err != nil { + logrus.Debug("Container checkpoint doesn't contain dev/shm: ", err.Error()) + } else { + shmDirTarFile, err := os.Open(shmDirTarFileFullPath) + if err != nil { + return nil, 0, err + } + defer shmDirTarFile.Close() + + if err := archive.UntarUncompressed(shmDirTarFile, c.config.ShmDir, nil); err != nil { + return nil, 0, err + } + } + } + + // Cleanup for a working restore. + if err := c.removeConmonFiles(); err != nil { + return nil, 0, err + } + + // Save the OCI spec to disk + if err := c.saveSpec(g.Config); err != nil { + return nil, 0, err + } + + // When restoring from an imported archive, allow restoring the content of volumes. + // Volumes are created in setupContainer() + if !options.IgnoreVolumes && (options.TargetFile != "" || options.CheckpointImageID != "") { + for _, v := range c.config.NamedVolumes { + volumeFilePath := filepath.Join(c.bundlePath(), metadata.CheckpointVolumesDirectory, v.Name+".tar") + + volumeFile, err := os.Open(volumeFilePath) + if err != nil { + return nil, 0, fmt.Errorf("failed to open volume file %s: %w", volumeFilePath, err) + } + defer volumeFile.Close() + + volume, err := c.runtime.GetVolume(v.Name) + if err != nil { + return nil, 0, fmt.Errorf("failed to retrieve volume %s: %w", v.Name, err) + } + + mountPoint, err := volume.MountPoint() + if err != nil { + return nil, 0, err + } + if mountPoint == "" { + return nil, 0, fmt.Errorf("unable to import volume %s as it is not mounted: %w", volume.Name(), err) + } + if err := archive.UntarUncompressed(volumeFile, mountPoint, nil); err != nil { + return nil, 0, fmt.Errorf("failed to extract volume %s to %s: %w", volumeFilePath, mountPoint, err) + } + } + } + + // Before actually restarting the container, apply the root file-system changes + if !options.IgnoreRootfs { + if err := crutils.CRApplyRootFsDiffTar(c.bundlePath(), c.state.Mountpoint); err != nil { + return nil, 0, err + } + + if err := crutils.CRRemoveDeletedFiles(c.ID(), c.bundlePath(), c.state.Mountpoint); err != nil { + return nil, 0, err + } + } + + // setup hosts/resolv.conf files + // Note this should normally be called after the container is created in the runtime but before it is started. + // However restore starts the container right away. This means that if we do the call afterwards there is a + // short interval where the file is still empty. Thus I decided to call it before which makes it not working + // with PostConfigureNetNS (userns) but as this does not work anyway today so I don't see it as problem. + if err := c.completeNetworkSetup(); err != nil { + return nil, 0, fmt.Errorf("complete network setup: %w", err) + } + + runtimeRestoreDuration, err = c.ociRuntime.CreateContainer(c, &options) + if err != nil { + return nil, 0, err + } + + criuStatistics, err = func() (*define.CRIUCheckpointRestoreStatistics, error) { + if !options.PrintStats { + return nil, nil + } + statsDirectory, err := os.Open(c.bundlePath()) + if err != nil { + return nil, fmt.Errorf("not able to open %q: %w", c.bundlePath(), err) + } + + restoreStatistics, err := stats.CriuGetRestoreStats(statsDirectory) + if err != nil { + return nil, fmt.Errorf("displaying restore statistics not possible: %w", err) + } + + return &define.CRIUCheckpointRestoreStatistics{ + PagesCompared: restoreStatistics.GetPagesCompared(), + PagesSkippedCow: restoreStatistics.GetPagesSkippedCow(), + ForkingTime: restoreStatistics.GetForkingTime(), + RestoreTime: restoreStatistics.GetRestoreTime(), + PagesRestored: restoreStatistics.GetPagesRestored(), + }, nil + }() + if err != nil { + return nil, 0, err + } + + logrus.Debugf("Restored container %s", c.ID()) + + c.state.State = define.ContainerStateRunning + c.state.Checkpointed = false + c.state.Restored = true + c.state.CheckpointedTime = time.Time{} + c.state.RestoredTime = time.Now() + + if !options.Keep { + // Delete all checkpoint related files. At this point, in theory, all files + // should exist. Still ignoring errors for now as the container should be + // restored and running. Not erroring out just because some cleanup operation + // failed. Starting with the checkpoint directory + err = os.RemoveAll(c.CheckpointPath()) + if err != nil { + logrus.Debugf("Non-fatal: removal of checkpoint directory (%s) failed: %v", c.CheckpointPath(), err) + } + c.state.CheckpointPath = "" + err = os.RemoveAll(c.PreCheckPointPath()) + if err != nil { + logrus.Debugf("Non-fatal: removal of pre-checkpoint directory (%s) failed: %v", c.PreCheckPointPath(), err) + } + err = os.RemoveAll(c.CheckpointVolumesPath()) + if err != nil { + logrus.Debugf("Non-fatal: removal of checkpoint volumes directory (%s) failed: %v", c.CheckpointVolumesPath(), err) + } + cleanup := [...]string{ + "restore.log", + "dump.log", + stats.StatsDump, + stats.StatsRestore, + metadata.DevShmCheckpointTar, + metadata.NetworkStatusFile, + metadata.RootFsDiffTar, + metadata.DeletedFilesFile, + } + for _, del := range cleanup { + file := filepath.Join(c.bundlePath(), del) + err = os.Remove(file) + if err != nil { + logrus.Debugf("Non-fatal: removal of checkpoint file (%s) failed: %v", file, err) + } + } + c.state.CheckpointLog = "" + c.state.RestoreLog = "" + } + + return criuStatistics, runtimeRestoreDuration, c.save() +} + +// Retrieves a container's "root" net namespace container dependency. +func (c *Container) getRootNetNsDepCtr() (depCtr *Container, err error) { + containersVisited := map[string]int{c.config.ID: 1} + nextCtr := c.config.NetNsCtr + for nextCtr != "" { + // Make sure we aren't in a loop + if _, visited := containersVisited[nextCtr]; visited { + return nil, errors.New("loop encountered while determining net namespace container") + } + containersVisited[nextCtr] = 1 + + depCtr, err = c.runtime.state.Container(nextCtr) + if err != nil { + return nil, fmt.Errorf("fetching dependency %s of container %s: %w", c.config.NetNsCtr, c.ID(), err) + } + // This should never happen without an error + if depCtr == nil { + break + } + nextCtr = depCtr.config.NetNsCtr + } + + if depCtr == nil { + return nil, errors.New("unexpected error depCtr is nil without reported error from runtime state") + } + return depCtr, nil +} + +// Ensure standard bind mounts are mounted into all root directories (including chroot directories) +func (c *Container) mountIntoRootDirs(mountName string, mountPath string) error { + c.state.BindMounts[mountName] = mountPath + + for _, chrootDir := range c.config.ChrootDirs { + c.state.BindMounts[filepath.Join(chrootDir, mountName)] = mountPath + } + + return nil +} + +// Make standard bind mounts to include in the container +func (c *Container) makeBindMounts() error { + if c.state.BindMounts == nil { + c.state.BindMounts = make(map[string]string) + } + netDisabled, err := c.NetworkDisabled() + if err != nil { + return err + } + + if !netDisabled { + // If /etc/resolv.conf and /etc/hosts exist, delete them so we + // will recreate. Only do this if we aren't sharing them with + // another container. + if c.config.NetNsCtr == "" { + if resolvePath, ok := c.state.BindMounts[resolvconf.DefaultResolvConf]; ok { + if err := os.Remove(resolvePath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("container %s: %w", c.ID(), err) + } + delete(c.state.BindMounts, resolvconf.DefaultResolvConf) + } + if hostsPath, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + if err := os.Remove(hostsPath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("container %s: %w", c.ID(), err) + } + delete(c.state.BindMounts, config.DefaultHostsFile) + } + } + + if c.config.NetNsCtr != "" && (!c.config.UseImageResolvConf || !c.config.UseImageHosts) { + // We share a net namespace. + // We want /etc/resolv.conf and /etc/hosts from the + // other container. Unless we're not creating both of + // them. + depCtr, err := c.getRootNetNsDepCtr() + if err != nil { + return fmt.Errorf("fetching network namespace dependency container for container %s: %w", c.ID(), err) + } + + // We need that container's bind mounts + bindMounts, err := depCtr.BindMounts() + if err != nil { + return fmt.Errorf("fetching bind mounts from dependency %s of container %s: %w", depCtr.ID(), c.ID(), err) + } + + // The other container may not have a resolv.conf or /etc/hosts + // If it doesn't, don't copy them + resolvPath, exists := bindMounts[resolvconf.DefaultResolvConf] + if !c.config.UseImageResolvConf && exists { + err := c.mountIntoRootDirs(resolvconf.DefaultResolvConf, resolvPath) + + if err != nil { + return fmt.Errorf("assigning mounts to container %s: %w", c.ID(), err) + } + } + + // check if dependency container has an /etc/hosts file. + // It may not have one, so only use it if it does. + hostsPath, exists := bindMounts[config.DefaultHostsFile] + if !c.config.UseImageHosts && exists { + // we cannot use the dependency container lock due ABBA deadlocks in cleanup() + lock, err := lockfile.GetLockFile(hostsPath) + if err != nil { + return fmt.Errorf("failed to lock hosts file: %w", err) + } + lock.Lock() + + // add the newly added container to the hosts file + // we always use 127.0.0.1 as ip since they have the same netns + err = etchosts.Add(hostsPath, getLocalhostHostEntry(c)) + lock.Unlock() + if err != nil { + return fmt.Errorf("creating hosts file for container %s which depends on container %s: %w", c.ID(), depCtr.ID(), err) + } + + // finally, save it in the new container + err = c.mountIntoRootDirs(config.DefaultHostsFile, hostsPath) + if err != nil { + return fmt.Errorf("assigning mounts to container %s: %w", c.ID(), err) + } + } + } else { + if !c.config.UseImageResolvConf { + if err := c.createResolvConf(); err != nil { + return fmt.Errorf("creating resolv.conf for container %s: %w", c.ID(), err) + } + } + + if !c.config.UseImageHosts { + if err := c.createHostsFile(); err != nil { + return fmt.Errorf("creating hosts file for container %s: %w", c.ID(), err) + } + } + } + + if c.state.BindMounts[config.DefaultHostsFile] != "" { + if err := c.relabel(c.state.BindMounts[config.DefaultHostsFile], c.config.MountLabel, true); err != nil { + return err + } + } + + if c.state.BindMounts[resolvconf.DefaultResolvConf] != "" { + if err := c.relabel(c.state.BindMounts[resolvconf.DefaultResolvConf], c.config.MountLabel, true); err != nil { + return err + } + } + } else if !c.config.UseImageHosts && c.state.BindMounts[config.DefaultHostsFile] == "" { + if err := c.createHostsFile(); err != nil { + return fmt.Errorf("creating hosts file for container %s: %w", c.ID(), err) + } + } + + if c.config.ShmDir != "" { + // If ShmDir has a value SHM is always added when we mount the container + c.state.BindMounts["/dev/shm"] = c.config.ShmDir + } + + if c.config.Passwd == nil || *c.config.Passwd { + newPasswd, newGroup, err := c.generatePasswdAndGroup() + if err != nil { + return fmt.Errorf("creating temporary passwd file for container %s: %w", c.ID(), err) + } + if newPasswd != "" { + // Make /etc/passwd + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/passwd") + c.state.BindMounts["/etc/passwd"] = newPasswd + } + if newGroup != "" { + // Make /etc/group + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/group") + c.state.BindMounts["/etc/group"] = newGroup + } + } + + runPath, err := c.getPlatformRunPath() + if err != nil { + return fmt.Errorf("cannot determine run directory for container: %w", err) + } + containerenvPath := filepath.Join(runPath, ".containerenv") + + _, hasRunContainerenv := c.state.BindMounts[containerenvPath] + if !hasRunContainerenv { + Loop: + // check in the spec mounts + for _, m := range c.config.Spec.Mounts { + switch { + case m.Destination == containerenvPath: + hasRunContainerenv = true + break Loop + case m.Destination == runPath && m.Type != define.TypeTmpfs: + hasRunContainerenv = true + break Loop + } + } + } + + // Make .containerenv if it does not exist + if !hasRunContainerenv { + containerenv := c.runtime.graphRootMountedFlag(c.config.Spec.Mounts) + isRootless := 0 + if rootless.IsRootless() { + isRootless = 1 + } + imageID, imageName := c.Image() + + if c.Privileged() { + // Populate the .containerenv with container information + containerenv = fmt.Sprintf(`engine="podman-%s" +name=%q +id=%q +image=%q +imageid=%q +rootless=%d +%s`, version.Version.String(), c.Name(), c.ID(), imageName, imageID, isRootless, containerenv) + } + containerenvHostPath, err := c.writeStringToRundir(".containerenv", containerenv) + if err != nil { + return fmt.Errorf("creating containerenv file for container %s: %w", c.ID(), err) + } + c.state.BindMounts[containerenvPath] = containerenvHostPath + } + + // Add Subscription Mounts + subscriptionMounts := subscriptions.MountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.Containers.DefaultMountsFile, c.state.Mountpoint, c.RootUID(), c.RootGID(), rootless.IsRootless(), false) + for _, mount := range subscriptionMounts { + if _, ok := c.state.BindMounts[mount.Destination]; !ok { + c.state.BindMounts[mount.Destination] = mount.Source + } + } + + // Secrets are mounted by getting the secret data from the secrets manager, + // copying the data into the container's static dir, + // then mounting the copied dir into /run/secrets. + // The secrets mounting must come after subscription mounts, since subscription mounts + // creates the /run/secrets dir in the container where we mount as well. + if len(c.Secrets()) > 0 { + // create /run/secrets if subscriptions did not create + if err := c.createSecretMountDir(runPath); err != nil { + return fmt.Errorf("creating secrets mount: %w", err) + } + for _, secret := range c.Secrets() { + secretFileName := secret.Name + base := filepath.Join(runPath, "secrets") + if secret.Target != "" { + secretFileName = secret.Target + // If absolute path for target given remove base. + if filepath.IsAbs(secretFileName) { + base = "" + } + } + src := filepath.Join(c.config.SecretsPath, secret.Name) + dest := filepath.Join(base, secretFileName) + c.state.BindMounts[dest] = src + } + } + + return c.makeHostnameBindMount() +} + +// createResolvConf create the resolv.conf file and bind mount it +func (c *Container) createResolvConf() error { + destPath := filepath.Join(c.state.RunDir, "resolv.conf") + f, err := os.Create(destPath) + if err != nil { + return err + } + f.Close() + return c.bindMountRootFile(destPath, resolvconf.DefaultResolvConf) +} + +// addResolvConf add resolv.conf entries +func (c *Container) addResolvConf() error { + destPath, ok := c.state.BindMounts[resolvconf.DefaultResolvConf] + if !ok { + // no resolv.conf mount, do nothing + return nil + } + + var ( + networkNameServers []string + networkSearchDomains []string + ) + + netStatus := c.getNetworkStatus() + for _, status := range netStatus { + if status.DNSServerIPs != nil { + for _, nsIP := range status.DNSServerIPs { + networkNameServers = append(networkNameServers, nsIP.String()) + } + logrus.Debugf("Adding nameserver(s) from network status of '%q'", status.DNSServerIPs) + } + if status.DNSSearchDomains != nil { + networkSearchDomains = append(networkSearchDomains, status.DNSSearchDomains...) + logrus.Debugf("Adding search domain(s) from network status of '%q'", status.DNSSearchDomains) + } + } + + ipv6 := c.checkForIPv6(netStatus) + + networkBackend := c.runtime.config.Network.NetworkBackend + nameservers := make([]string, 0, len(c.runtime.config.Containers.DNSServers.Get())+len(c.config.DNSServer)) + + // If NetworkBackend is `netavark` do not populate `/etc/resolv.conf` + // with custom dns server since after https://github.com/containers/netavark/pull/452 + // netavark will always set required `nameservers` in StatusBlock and libpod + // will correctly populate `networkNameServers`. Also see https://github.com/containers/podman/issues/16172 + + // Exception: Populate `/etc/resolv.conf` if container is not connected to any network + // with dns enabled then we do not get any nameservers back. + if networkBackend != string(types.Netavark) || len(networkNameServers) == 0 { + nameservers = append(nameservers, c.runtime.config.Containers.DNSServers.Get()...) + for _, ip := range c.config.DNSServer { + nameservers = append(nameservers, ip.String()) + } + } + // If the user provided dns, it trumps all; then dns masq; then resolv.conf + keepHostServers := false + if len(nameservers) == 0 { + // when no network name servers or not netavark use host servers + // for aardvark dns we only want our single server in there + if len(networkNameServers) == 0 || networkBackend != string(types.Netavark) { + keepHostServers = true + } + if len(networkNameServers) > 0 { + // add the nameservers from the networks status + nameservers = networkNameServers + } else { + // pasta and slirp4netns have a built in DNS forwarder. + nameservers = c.addSpecialDNS(nameservers) + } + } + + // Set DNS search domains + var search []string + keepHostSearches := false + if len(c.config.DNSSearch) > 0 || len(c.runtime.config.Containers.DNSSearches.Get()) > 0 { + customSearch := make([]string, 0, len(c.config.DNSSearch)+len(c.runtime.config.Containers.DNSSearches.Get())) + customSearch = append(customSearch, c.runtime.config.Containers.DNSSearches.Get()...) + customSearch = append(customSearch, c.config.DNSSearch...) + search = customSearch + } else { + search = networkSearchDomains + keepHostSearches = true + } + + options := make([]string, 0, len(c.config.DNSOption)+len(c.runtime.config.Containers.DNSOptions.Get())) + options = append(options, c.runtime.config.Containers.DNSOptions.Get()...) + options = append(options, c.config.DNSOption...) + + var namespaces []spec.LinuxNamespace + if c.config.Spec.Linux != nil { + namespaces = c.config.Spec.Linux.Namespaces + } + + if err := resolvconf.New(&resolvconf.Params{ + IPv6Enabled: ipv6, + KeepHostServers: keepHostServers, + KeepHostSearches: keepHostSearches, + Nameservers: nameservers, + Namespaces: namespaces, + Options: options, + Path: destPath, + Searches: search, + }); err != nil { + return fmt.Errorf("building resolv.conf for container %s: %w", c.ID(), err) + } + + return nil +} + +// Check if a container uses IPv6. +func (c *Container) checkForIPv6(netStatus map[string]types.StatusBlock) bool { + for _, status := range netStatus { + for _, netInt := range status.Interfaces { + for _, netAddress := range netInt.Subnets { + // Note: only using To16() does not work since it also returns a valid ip for ipv4 + if netAddress.IPNet.IP.To4() == nil && netAddress.IPNet.IP.To16() != nil { + return true + } + } + } + } + + if c.pastaResult != nil { + return c.pastaResult.IPv6 + } + + return c.isSlirp4netnsIPv6() +} + +// Add a new nameserver to the container's resolv.conf, ensuring that it is the +// first nameserver present. +// Usable only with running containers. +func (c *Container) addNameserver(ips []string) error { + // Take no action if container is not running. + if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { + return nil + } + + // Do we have a resolv.conf at all? + path, ok := c.state.BindMounts[resolvconf.DefaultResolvConf] + if !ok { + return nil + } + + if err := resolvconf.Add(path, ips); err != nil { + return fmt.Errorf("adding new nameserver to container %s resolv.conf: %w", c.ID(), err) + } + + return nil +} + +// Remove an entry from the existing resolv.conf of the container. +// Usable only with running containers. +func (c *Container) removeNameserver(ips []string) error { + // Take no action if container is not running. + if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { + return nil + } + + // Do we have a resolv.conf at all? + path, ok := c.state.BindMounts[resolvconf.DefaultResolvConf] + if !ok { + return nil + } + + if err := resolvconf.Remove(path, ips); err != nil { + return fmt.Errorf("removing nameservers from container %s resolv.conf: %w", c.ID(), err) + } + + return nil +} + +func getLocalhostHostEntry(c *Container) etchosts.HostEntries { + return etchosts.HostEntries{{IP: "127.0.0.1", Names: []string{c.Hostname(), c.config.Name}}} +} + +// getHostsEntries returns the container ip host entries for the correct netmode +func (c *Container) getHostsEntries() (etchosts.HostEntries, error) { + var entries etchosts.HostEntries + names := []string{c.Hostname(), c.config.Name} + switch { + case c.config.NetMode.IsBridge(): + entries = etchosts.GetNetworkHostEntries(c.state.NetworkStatus, names...) + case c.config.NetMode.IsPasta(): + // this should never be the case but check just to be sure and not panic + if len(c.pastaResult.IPAddresses) > 0 { + entries = etchosts.HostEntries{{IP: c.pastaResult.IPAddresses[0].String(), Names: names}} + } + case c.config.NetMode.IsSlirp4netns(): + ip, err := getSlirp4netnsIP(c.slirp4netnsSubnet) + if err != nil { + return nil, err + } + entries = etchosts.HostEntries{{IP: ip.String(), Names: names}} + default: + if c.hasNetNone() { + entries = etchosts.HostEntries{{IP: "127.0.0.1", Names: names}} + } + } + return entries, nil +} + +func (c *Container) createHostsFile() error { + targetFile := filepath.Join(c.state.RunDir, "hosts") + f, err := os.Create(targetFile) + if err != nil { + return err + } + f.Close() + return c.bindMountRootFile(targetFile, config.DefaultHostsFile) +} + +func (c *Container) addHosts() error { + targetFile, ok := c.state.BindMounts[config.DefaultHostsFile] + if !ok { + // no host file nothing to do + return nil + } + containerIPsEntries, err := c.getHostsEntries() + if err != nil { + return fmt.Errorf("failed to get container ip host entries: %w", err) + } + + // Consider container level BaseHostsFile configuration first. + // If it is empty, fallback to containers.conf level configuration. + baseHostsFileConf := c.config.BaseHostsFile + if baseHostsFileConf == "" { + baseHostsFileConf = c.runtime.config.Containers.BaseHostsFile + } + baseHostFile, err := etchosts.GetBaseHostFile(baseHostsFileConf, c.state.Mountpoint) + if err != nil { + return err + } + + var exclude []net.IP + var preferIP string + if c.pastaResult != nil { + exclude = c.pastaResult.IPAddresses + if len(c.pastaResult.MapGuestAddrIPs) > 0 { + // we used --map-guest-addr to setup pasta so prefer this address + preferIP = c.pastaResult.MapGuestAddrIPs[0] + } + } else if c.config.NetMode.IsBridge() { + // When running rootless we have to check the rootless netns ip addresses + // to not assign a ip that is already used in the rootless netns as it would + // not be routed to the host. + // https://github.com/containers/podman/issues/22653 + info, err := c.runtime.network.RootlessNetnsInfo() + if err == nil && info != nil { + exclude = info.IPAddresses + if len(info.MapGuestIps) > 0 { + // we used --map-guest-addr to setup pasta so prefer this address + preferIP = info.MapGuestIps[0] + } + } + } + + hostContainersInternalIP := etchosts.GetHostContainersInternalIP(etchosts.HostContainersInternalOptions{ + Conf: c.runtime.config, + NetStatus: c.state.NetworkStatus, + NetworkInterface: c.runtime.network, + Exclude: exclude, + PreferIP: preferIP, + }) + + return etchosts.New(&etchosts.Params{ + BaseFile: baseHostFile, + ExtraHosts: c.config.HostAdd, + ContainerIPs: containerIPsEntries, + HostContainersInternalIP: hostContainersInternalIP, + TargetFile: targetFile, + }) +} + +// bindMountRootFile will chown and relabel the source file to make it usable in the container. +// It will also add the path to the container bind mount map. +// source is the path on the host, dest is the path in the container. +func (c *Container) bindMountRootFile(source, dest string) error { + if err := idtools.SafeChown(source, c.RootUID(), c.RootGID()); err != nil { + return err + } + if err := c.relabel(source, c.MountLabel(), false); err != nil { + return err + } + + return c.mountIntoRootDirs(dest, source) +} + +// generateGroupEntry generates an entry or entries into /etc/group as +// required by container configuration. +// Generally speaking, we will make an entry under two circumstances: +// 1. The container is started as a specific user:group, and that group is both +// numeric, and does not already exist in /etc/group. +// 2. It is requested that Libpod add the group that launched Podman to +// /etc/group via AddCurrentUserPasswdEntry (though this does not trigger if +// the group in question already exists in /etc/passwd). +// +// Returns group entry (as a string that can be appended to /etc/group) and any +// error that occurred. +func (c *Container) generateGroupEntry() (string, error) { + groupString := "" + + // Things we *can't* handle: adding the user we added in + // generatePasswdEntry to any *existing* groups. + addedGID := -1 + if c.config.AddCurrentUserPasswdEntry { + entry, gid, err := c.generateCurrentUserGroupEntry() + if err != nil { + return "", err + } + groupString += entry + addedGID = gid + } + if c.config.User != "" || c.config.GroupEntry != "" { + entry, err := c.generateUserGroupEntry(addedGID) + if err != nil { + return "", err + } + groupString += entry + } + + return groupString, nil +} + +// Make an entry in /etc/group for the group of the user running podman iff we +// are rootless. +func (c *Container) generateCurrentUserGroupEntry() (string, int, error) { + gid := rootless.GetRootlessGID() + if gid == 0 { + return "", 0, nil + } + + g, err := user.LookupGroupId(strconv.Itoa(gid)) + if err != nil { + return "", 0, fmt.Errorf("failed to get current group: %w", err) + } + + // Look up group name to see if it exists in the image. + _, err = lookup.GetGroup(c.state.Mountpoint, g.Name) + if err != runcuser.ErrNoGroupEntries { + return "", 0, err + } + + // Look up GID to see if it exists in the image. + _, err = lookup.GetGroup(c.state.Mountpoint, g.Gid) + if err != runcuser.ErrNoGroupEntries { + return "", 0, err + } + + // We need to get the username of the rootless user so we can add it to + // the group. + username := "" + uid := rootless.GetRootlessUID() + if uid != 0 { + u, err := user.LookupId(strconv.Itoa(uid)) + if err != nil { + return "", 0, fmt.Errorf("failed to get current user to make group entry: %w", err) + } + username = u.Username + } + + // Make the entry. + return fmt.Sprintf("%s:x:%s:%s\n", g.Name, g.Gid, username), gid, nil +} + +// Make an entry in /etc/group for the group the container was specified to run +// as. +func (c *Container) generateUserGroupEntry(addedGID int) (string, error) { + if c.config.User == "" && c.config.GroupEntry == "" { + return "", nil + } + + splitUser := strings.SplitN(c.config.User, ":", 2) + group := "0" + if len(splitUser) > 1 { + group = splitUser[1] + } + + gid, err := strconv.ParseUint(group, 10, 32) + if err != nil { + return "", nil //nolint: nilerr + } + + if addedGID != -1 && addedGID == int(gid) { + return "", nil + } + + // Check if the group already exists + g, err := lookup.GetGroup(c.state.Mountpoint, group) + if err != runcuser.ErrNoGroupEntries { + return "", err + } + + if c.config.GroupEntry != "" { + return c.groupEntry(g.Name, strconv.Itoa(g.Gid), g.List), nil + } + + return fmt.Sprintf("%d:x:%d:%s\n", gid, gid, splitUser[0]), nil +} + +func (c *Container) groupEntry(groupname, gid string, list []string) string { + s := c.config.GroupEntry + s = strings.ReplaceAll(s, "$GROUPNAME", groupname) + s = strings.ReplaceAll(s, "$GID", gid) + s = strings.ReplaceAll(s, "$USERLIST", strings.Join(list, ",")) + return s + "\n" +} + +// generatePasswdEntry generates an entry or entries into /etc/passwd as +// required by container configuration. +// Generally speaking, we will make an entry under two circumstances: +// 1. The container is started as a specific user who is not in /etc/passwd. +// This only triggers if the user is given as a *numeric* ID. +// 2. It is requested that Libpod add the user that launched Podman to +// /etc/passwd via AddCurrentUserPasswdEntry (though this does not trigger if +// the user in question already exists in /etc/passwd) or the UID to be added +// is 0). +// 3. The user specified additional host user accounts to add to the /etc/passwd file +// +// Returns password entry (as a string that can be appended to /etc/passwd) and +// any error that occurred. +func (c *Container) generatePasswdEntry() (string, error) { + passwdString := "" + + addedUID := 0 + for _, userid := range c.config.HostUsers { + // Look up User on host + u, err := util.LookupUser(userid) + if err != nil { + return "", err + } + entry, err := c.userPasswdEntry(u) + if err != nil { + return "", err + } + passwdString += entry + } + if c.config.AddCurrentUserPasswdEntry { + entry, uid, _, err := c.generateCurrentUserPasswdEntry() + if err != nil { + return "", err + } + passwdString += entry + addedUID = uid + } + if c.config.User != "" { + entry, err := c.generateUserPasswdEntry(addedUID) + if err != nil { + return "", err + } + passwdString += entry + } + + return passwdString, nil +} + +// generateCurrentUserPasswdEntry generates an /etc/passwd entry for the user +// running the container engine. +// Returns a passwd entry for the user, and the UID and GID of the added entry. +func (c *Container) generateCurrentUserPasswdEntry() (string, int, int, error) { + uid := rootless.GetRootlessUID() + if uid == 0 { + return "", 0, 0, nil + } + + u, err := user.LookupId(strconv.Itoa(uid)) + if err != nil { + return "", 0, 0, fmt.Errorf("failed to get current user: %w", err) + } + pwd, err := c.userPasswdEntry(u) + if err != nil { + return "", 0, 0, err + } + + return pwd, uid, rootless.GetRootlessGID(), nil +} + +// Sets the HOME env. variable with precedence: existing home env. variable, execUser home +func (c *Container) setHomeEnvIfNeeded() error { + getExecUserHome := func() (string, error) { + overrides := c.getUserOverrides() + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides) + if err != nil { + if slices.Contains(c.config.HostUsers, c.config.User) { + execUser, err = lookupHostUser(c.config.User) + } + + if err != nil { + return "", err + } + } + + return execUser.Home, nil + } + + // Ensure HOME is not already set in Env + for _, s := range c.config.Spec.Process.Env { + if strings.HasPrefix(s, "HOME=") { + return nil + } + } + + home, err := getExecUserHome() + if err != nil { + return err + } + + c.config.Spec.Process.Env = append(c.config.Spec.Process.Env, fmt.Sprintf("HOME=%s", home)) + return nil +} + +func (c *Container) userPasswdEntry(u *user.User) (string, error) { + // Look up the user to see if it exists in the container image. + _, err := lookup.GetUser(c.state.Mountpoint, u.Username) + if err != runcuser.ErrNoPasswdEntries { + return "", err + } + + // Look up the UID to see if it exists in the container image. + _, err = lookup.GetUser(c.state.Mountpoint, u.Uid) + if err != runcuser.ErrNoPasswdEntries { + return "", err + } + + // If the user's actual home directory exists, or was mounted in - use + // that. + homeDir := c.WorkingDir() + hDir := u.HomeDir + for hDir != "/" { + if MountExists(c.config.Spec.Mounts, hDir) { + homeDir = u.HomeDir + break + } + hDir = filepath.Dir(hDir) + } + if homeDir != u.HomeDir { + if slices.Contains(c.UserVolumes(), u.HomeDir) { + homeDir = u.HomeDir + } + } + + if c.config.PasswdEntry != "" { + return c.passwdEntry(u.Username, u.Uid, u.Gid, u.Name, homeDir), nil + } + + return fmt.Sprintf("%s:*:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Name, homeDir), nil +} + +// generateUserPasswdEntry generates an /etc/passwd entry for the container user +// to run in the container. +// The UID and GID of the added entry will also be returned. +// Accepts one argument, that being any UID that has already been added to the +// passwd file by other functions; if it matches the UID we were given, we don't +// need to do anything. +func (c *Container) generateUserPasswdEntry(addedUID int) (string, error) { + var ( + groupspec string + gid int + ) + if c.config.User == "" { + return "", nil + } + splitSpec := strings.SplitN(c.config.User, ":", 2) + userspec := splitSpec[0] + if len(splitSpec) > 1 { + groupspec = splitSpec[1] + } + // If a non numeric User, then don't generate passwd + uid, err := strconv.ParseUint(userspec, 10, 32) + if err != nil { + return "", nil //nolint: nilerr + } + + if addedUID != 0 && int(uid) == addedUID { + return "", nil + } + + // Look up the user to see if it exists in the container image + _, err = lookup.GetUser(c.state.Mountpoint, userspec) + if err != runcuser.ErrNoPasswdEntries { + return "", err + } + + if groupspec != "" { + ugid, err := strconv.ParseUint(groupspec, 10, 32) + if err == nil { + gid = int(ugid) + } else { + group, err := lookup.GetGroup(c.state.Mountpoint, groupspec) + if err != nil { + return "", fmt.Errorf("unable to get gid %s from group file: %w", groupspec, err) + } + gid = group.Gid + } + } + + if c.config.PasswdEntry != "" { + entry := c.passwdEntry(strconv.FormatUint(uid, 10), strconv.FormatUint(uid, 10), strconv.FormatInt(int64(gid), 10), "container user", c.WorkingDir()) + return entry, nil + } + + u, err := user.LookupId(strconv.FormatUint(uid, 10)) + if err == nil { + return fmt.Sprintf("%s:*:%d:%d:%s:%s:/bin/sh\n", u.Username, uid, gid, u.Name, c.WorkingDir()), nil + } + return fmt.Sprintf("%d:*:%d:%d:container user:%s:/bin/sh\n", uid, uid, gid, c.WorkingDir()), nil +} + +func (c *Container) passwdEntry(username, uid, gid, name, homeDir string) string { + s := c.config.PasswdEntry + s = strings.ReplaceAll(s, "$USERNAME", username) + s = strings.ReplaceAll(s, "$UID", uid) + s = strings.ReplaceAll(s, "$GID", gid) + s = strings.ReplaceAll(s, "$NAME", name) + s = strings.ReplaceAll(s, "$HOME", homeDir) + return s + "\n" +} + +// generatePasswdAndGroup generates container-specific passwd and group files +// iff g.config.User is a number or we are configured to make a passwd entry for +// the current user or the user specified HostsUsers +// Returns path to file to mount at /etc/passwd, path to file to mount at +// /etc/group, and any error that occurred. If no passwd/group file were +// required, the empty string will be returned for those path (this may occur +// even if no error happened). +// This may modify the mounted container's /etc/passwd and /etc/group instead of +// making copies to bind-mount in, so we don't break useradd (it wants to make a +// copy of /etc/passwd and rename the copy to /etc/passwd, which is impossible +// with a bind mount). This is done in cases where the container is *not* +// read-only. In this case, the function will return nothing ("", "", nil). +func (c *Container) generatePasswdAndGroup() (string, string, error) { + if !c.config.AddCurrentUserPasswdEntry && c.config.User == "" && + len(c.config.HostUsers) == 0 && c.config.GroupEntry == "" { + return "", "", nil + } + + needPasswd := true + needGroup := true + + // First, check if there's a mount at /etc/passwd or group, we don't + // want to interfere with user mounts. + if MountExists(c.config.Spec.Mounts, "/etc/passwd") { + needPasswd = false + } + if MountExists(c.config.Spec.Mounts, "/etc/group") { + needGroup = false + } + + // Next, check if we already made the files. If we didn't, don't need to + // do anything more. + if needPasswd { + passwdPath := filepath.Join(c.config.StaticDir, "passwd") + if err := fileutils.Exists(passwdPath); err == nil { + needPasswd = false + } + } + if needGroup { + groupPath := filepath.Join(c.config.StaticDir, "group") + if err := fileutils.Exists(groupPath); err == nil { + needGroup = false + } + } + + // If we don't need a /etc/passwd or /etc/group at this point we can + // just return. + if !needPasswd && !needGroup { + return "", "", nil + } + + passwdPath := "" + groupPath := "" + + ro := c.IsReadOnly() + + if needPasswd { + passwdEntry, err := c.generatePasswdEntry() + if err != nil { + return "", "", err + } + + needsWrite := passwdEntry != "" + switch { + case ro && needsWrite: + logrus.Debugf("Making /etc/passwd for container %s", c.ID()) + originPasswdFile, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd") + if err != nil { + return "", "", fmt.Errorf("creating path to container %s /etc/passwd: %w", c.ID(), err) + } + orig, err := os.ReadFile(originPasswdFile) + if err != nil && !os.IsNotExist(err) { + return "", "", err + } + passwdFile, err := c.writeStringToStaticDir("passwd", string(orig)+passwdEntry) + if err != nil { + return "", "", fmt.Errorf("failed to create temporary passwd file: %w", err) + } + if err := os.Chmod(passwdFile, 0o644); err != nil { + return "", "", err + } + passwdPath = passwdFile + case !ro && needsWrite: + logrus.Debugf("Modifying container %s /etc/passwd", c.ID()) + containerPasswd, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd") + if err != nil { + return "", "", fmt.Errorf("looking up location of container %s /etc/passwd: %w", c.ID(), err) + } + + f, err := os.OpenFile(containerPasswd, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) + if err != nil { + return "", "", fmt.Errorf("container %s: %w", c.ID(), err) + } + defer f.Close() + + if _, err := f.WriteString(passwdEntry); err != nil { + return "", "", fmt.Errorf("unable to append to container %s /etc/passwd: %w", c.ID(), err) + } + default: + logrus.Debugf("Not modifying container %s /etc/passwd", c.ID()) + } + } + if needGroup { + groupEntry, err := c.generateGroupEntry() + if err != nil { + return "", "", err + } + + needsWrite := groupEntry != "" + switch { + case ro && needsWrite: + logrus.Debugf("Making /etc/group for container %s", c.ID()) + originGroupFile, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/group") + if err != nil { + return "", "", fmt.Errorf("creating path to container %s /etc/group: %w", c.ID(), err) + } + orig, err := os.ReadFile(originGroupFile) + if err != nil && !os.IsNotExist(err) { + return "", "", err + } + groupFile, err := c.writeStringToStaticDir("group", string(orig)+groupEntry) + if err != nil { + return "", "", fmt.Errorf("failed to create temporary group file: %w", err) + } + if err := os.Chmod(groupFile, 0o644); err != nil { + return "", "", err + } + groupPath = groupFile + case !ro && needsWrite: + logrus.Debugf("Modifying container %s /etc/group", c.ID()) + containerGroup, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/group") + if err != nil { + return "", "", fmt.Errorf("looking up location of container %s /etc/group: %w", c.ID(), err) + } + + f, err := os.OpenFile(containerGroup, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) + if err != nil { + return "", "", fmt.Errorf("container %s: %w", c.ID(), err) + } + defer f.Close() + + if _, err := f.WriteString(groupEntry); err != nil { + return "", "", fmt.Errorf("unable to append to container %s /etc/group: %w", c.ID(), err) + } + default: + logrus.Debugf("Not modifying container %s /etc/group", c.ID()) + } + } + + return passwdPath, groupPath, nil +} + +func (c *Container) cleanupOverlayMounts() error { + return overlay.CleanupContent(c.config.StaticDir) +} + +// Creates and mounts an empty dir to mount secrets into, if it does not already exist +func (c *Container) createSecretMountDir(runPath string) error { + src := filepath.Join(c.state.RunDir, "/run/secrets") + err := fileutils.Exists(src) + if os.IsNotExist(err) { + if err := umask.MkdirAllIgnoreUmask(src, os.FileMode(0o755)); err != nil { + return err + } + if err := c.relabel(src, c.config.MountLabel, false); err != nil { + return err + } + if err := idtools.SafeChown(src, c.RootUID(), c.RootGID()); err != nil { + return err + } + c.state.BindMounts[filepath.Join(runPath, "secrets")] = src + return nil + } + + return err +} + +func hasIdmapOption(options []string) bool { + for _, o := range options { + if o == "idmap" || strings.HasPrefix(o, "idmap=") { + return true + } + } + return false +} + +// Fix ownership and permissions of the specified volume if necessary. +func (c *Container) fixVolumePermissions(v *ContainerNamedVolume) error { + vol, err := c.runtime.state.Volume(v.Name) + if err != nil { + return fmt.Errorf("retrieving named volume %s for container %s: %w", v.Name, c.ID(), err) + } + + vol.lock.Lock() + defer vol.lock.Unlock() + + return c.fixVolumePermissionsUnlocked(v, vol) +} + +func (c *Container) fixVolumePermissionsUnlocked(v *ContainerNamedVolume, vol *Volume) error { + // The volume may need a copy-up. Check the state. + if err := vol.update(); err != nil { + return err + } + + // If the volume is not empty, and it is not the first copy-up event - + // we should not do a chown. + if vol.state.NeedsChown && !vol.state.CopiedUp { + contents, err := os.ReadDir(vol.mountPoint()) + if err != nil { + return fmt.Errorf("reading contents of volume %q: %w", vol.Name(), err) + } + // Not empty, do nothing and unset NeedsChown. + if len(contents) > 0 { + vol.state.NeedsChown = false + if err := vol.save(); err != nil { + return fmt.Errorf("saving volume %q state: %w", vol.Name(), err) + } + return nil + } + } + + // Volumes owned by a volume driver are not chowned - we don't want to + // mess with a mount not managed by us. + if vol.state.NeedsChown && (!vol.UsesVolumeDriver() && vol.config.Driver != "image") { + uid := int(c.config.Spec.Process.User.UID) + gid := int(c.config.Spec.Process.User.GID) + + idmapped := hasIdmapOption(v.Options) + + // if the volume is mounted with "idmap", leave the IDs in from the current environment. + if c.config.IDMappings.UIDMap != nil && !idmapped { + p := idtools.IDPair{ + UID: uid, + GID: gid, + } + mappings := idtools.NewIDMappingsFromMaps(c.config.IDMappings.UIDMap, c.config.IDMappings.GIDMap) + newPair, err := mappings.ToHost(p) + if err != nil { + return fmt.Errorf("mapping user %d:%d: %w", uid, gid, err) + } + uid = newPair.UID + gid = newPair.GID + } + + if vol.state.CopiedUp { + vol.state.NeedsChown = false + } + vol.state.CopiedUp = false + vol.state.UIDChowned = uid + vol.state.GIDChowned = gid + + if err := vol.save(); err != nil { + return err + } + + mountPoint, err := vol.MountPoint() + if err != nil { + return err + } + + if err := idtools.SafeLchown(mountPoint, uid, gid); err != nil { + return err + } + + // Make sure the new volume matches the permissions of the target directory unless 'U' is + // provided (since the volume was already chowned in this case). + // https://github.com/containers/podman/issues/10188 + if slices.Contains(v.Options, "U") { + return nil + } + + finalPath, err := securejoin.SecureJoin(c.state.Mountpoint, v.Dest) + if err != nil { + return err + } + st, err := os.Lstat(finalPath) + if err == nil { + if stat, ok := st.Sys().(*syscall.Stat_t); ok { + uid, gid := int(stat.Uid), int(stat.Gid) + + // If the volume is idmapped then undo the conversion to obtain the desired UID/GID in the container + if c.config.IDMappings.UIDMap != nil && idmapped { + p := idtools.IDPair{ + UID: uid, + GID: gid, + } + mappings := idtools.NewIDMappingsFromMaps(c.config.IDMappings.UIDMap, c.config.IDMappings.GIDMap) + newUID, newGID, err := mappings.ToContainer(p) + if err != nil { + return fmt.Errorf("mapping user %d:%d: %w", uid, gid, err) + } + uid, gid = newUID, newGID + } + + if err := idtools.SafeLchown(mountPoint, uid, gid); err != nil { + return err + } + + // UID/GID 0 are sticky - if we chown to root, + // we stop chowning thereafter. + if uid == 0 && gid == 0 && vol.state.NeedsChown { + vol.state.NeedsChown = false + + if err := vol.save(); err != nil { + return fmt.Errorf("saving volume %q state to database: %w", vol.Name(), err) + } + } + } + if err := os.Chmod(mountPoint, st.Mode()); err != nil { + return err + } + if err := setVolumeAtime(mountPoint, st); err != nil { + return err + } + } else if !os.IsNotExist(err) { + return err + } + } + return nil +} + +func (c *Container) relabel(src, mountLabel string, shared bool) error { + if !selinux.GetEnabled() || mountLabel == "" { + return nil + } + // only relabel on initial creation of container + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateUnknown) { + label, err := selinux.FileLabel(src) + if err != nil { + return err + } + // If labels are different, might be on a tmpfs + if label == mountLabel { + return nil + } + } + err := label.Relabel(src, mountLabel, shared) + if errors.Is(err, unix.ENOTSUP) { + logrus.Debugf("Labeling not supported on %q", src) + return nil + } + return err +} + +func (c *Container) ChangeHostPathOwnership(src string, recurse bool, uid, gid int) error { + // only chown on initial creation of container + if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateUnknown) { + st, err := os.Stat(src) + if err != nil { + return err + } + + // If labels are different, might be on a tmpfs + if int(st.Sys().(*syscall.Stat_t).Uid) == uid && int(st.Sys().(*syscall.Stat_t).Gid) == gid { + return nil + } + } + return chown.ChangeHostPathOwnership(src, recurse, uid, gid) +} + +func (c *Container) umask() (uint32, error) { + decVal, err := strconv.ParseUint(c.config.Umask, 8, 32) + if err != nil { + return 0, fmt.Errorf("invalid Umask Value: %w", err) + } + return uint32(decVal), nil +} + +func maybeClampOOMScoreAdj(oomScoreValue int) (int, error) { + v, err := os.ReadFile("/proc/self/oom_score_adj") + if err != nil { + return oomScoreValue, err + } + currentValue, err := strconv.Atoi(strings.TrimRight(string(v), "\n")) + if err != nil { + return oomScoreValue, err + } + if currentValue > oomScoreValue { + logrus.Warnf("Requested oom_score_adj=%d is lower than the current one, changing to %d", oomScoreValue, currentValue) + return currentValue, nil + } + return oomScoreValue, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_internal_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/container_internal_freebsd.go new file mode 100644 index 00000000..9edf3361 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_internal_freebsd.go @@ -0,0 +1,437 @@ +//go:build !remote + +package libpod + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "syscall" + "time" + + "github.com/containers/podman/v5/pkg/rootless" + securejoin "github.com/cyphar/filepath-securejoin" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" + "golang.org/x/sys/unix" +) + +var ( + bindOptions = []string{} +) + +func (c *Container) mountSHM(_ string) error { + return nil +} + +func (c *Container) unmountSHM(_ string) error { + return nil +} + +// prepare mounts the container and sets up other required resources like net +// namespaces +func (c *Container) prepare() error { + var ( + wg sync.WaitGroup + ctrNS string + networkStatus map[string]types.StatusBlock + createNetNSErr, mountStorageErr error + mountPoint string + tmpStateLock sync.Mutex + ) + + wg.Add(2) + + go func() { + defer wg.Done() + // Set up network namespace if not already set up + noNetNS := c.state.NetNS == "" + if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS { + c.reservedPorts, createNetNSErr = c.bindPorts() + if createNetNSErr != nil { + return + } + ctrNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c) + if createNetNSErr != nil { + return + } + + tmpStateLock.Lock() + defer tmpStateLock.Unlock() + + // Assign NetNS attributes to container + c.state.NetNS = ctrNS + c.state.NetworkStatus = networkStatus + } + }() + // Mount storage if not mounted + go func() { + defer wg.Done() + mountPoint, mountStorageErr = c.mountStorage() + + if mountStorageErr != nil { + return + } + + tmpStateLock.Lock() + defer tmpStateLock.Unlock() + + // Finish up mountStorage + c.state.Mounted = true + c.state.Mountpoint = mountPoint + + logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint) + }() + + wg.Wait() + + var createErr error + if createNetNSErr != nil { + createErr = createNetNSErr + } + if mountStorageErr != nil { + if createErr != nil { + logrus.Errorf("Preparing container %s: %v", c.ID(), createErr) + } + createErr = mountStorageErr + } + + // Only trigger storage cleanup if mountStorage was successful. + // Otherwise, we may mess up mount counters. + if createErr != nil { + if mountStorageErr == nil { + if err := c.cleanupStorage(); err != nil { + // createErr is guaranteed non-nil, so print + // unconditionally + logrus.Errorf("Preparing container %s: %v", c.ID(), createErr) + createErr = fmt.Errorf("unmounting storage for container %s after network create failure: %w", c.ID(), err) + } + } + // It's OK to unconditionally trigger network cleanup. If the network + // isn't ready it will do nothing. + if err := c.cleanupNetwork(); err != nil { + logrus.Errorf("Preparing container %s: %v", c.ID(), createErr) + createErr = fmt.Errorf("cleaning up container %s network after setup failure: %w", c.ID(), err) + } + for _, f := range c.reservedPorts { + // make sure to close all ports again on errors + f.Close() + } + c.reservedPorts = nil + return createErr + } + + // Save changes to container state + if err := c.save(); err != nil { + return err + } + + return nil +} + +// cleanupNetwork unmounts and cleans up the container's network +func (c *Container) cleanupNetwork() error { + if c.config.NetNsCtr != "" { + return nil + } + netDisabled, err := c.NetworkDisabled() + if err != nil { + return err + } + if netDisabled { + return nil + } + + // Stop the container's network namespace (if it has one) + neterr := c.runtime.teardownNetNS(c) + + // always save even when there was an error + err = c.save() + if err != nil { + if neterr != nil { + logrus.Errorf("Unable to clean up network for container %s: %q", c.ID(), neterr) + } + return err + } + + return neterr +} + +// reloadNetwork reloads the network for the given container, recreating +// firewall rules. +func (c *Container) reloadNetwork() error { + result, err := c.runtime.reloadContainerNetwork(c) + if err != nil { + return err + } + + c.state.NetworkStatus = result + + return c.save() +} + +// Add an existing container's network jail +func (c *Container) addNetworkContainer(g *generate.Generator, ctr string) error { + nsCtr, err := c.runtime.state.Container(ctr) + if err != nil { + return fmt.Errorf("retrieving dependency %s of container %s from state: %w", ctr, c.ID(), err) + } + if err := c.runtime.state.UpdateContainer(nsCtr); err != nil { + return err + } + if nsCtr.state.NetNS != "" { + g.AddAnnotation("org.freebsd.parentJail", nsCtr.state.NetNS) + } + return nil +} + +func isRootlessCgroupSet(_ string) bool { + return false +} + +func (c *Container) expectPodCgroup() (bool, error) { + return false, nil +} + +func (c *Container) getOCICgroupPath() (string, error) { + return "", nil +} + +func openDirectory(path string) (fd int, err error) { + const O_PATH = 0x00400000 + return unix.Open(path, unix.O_RDONLY|O_PATH|unix.O_CLOEXEC, 0) +} + +func (c *Container) addNetworkNamespace(g *generate.Generator) error { + if c.config.CreateNetNS { + // If PostConfigureNetNS is set (which is true on FreeBSD 13.3 + // and later), we can manage a container's network settings + // without an extra parent jail to own the vnew. + // + // In this case, the OCI runtime creates a new vnet for the + // container jail, otherwise it creates the container jail as a + // child of the jail owning the vnet. + if c.config.PostConfigureNetNS { + g.AddAnnotation("org.freebsd.jail.vnet", "new") + } else { + g.AddAnnotation("org.freebsd.parentJail", c.state.NetNS) + } + } + return nil +} + +func (c *Container) addSystemdMounts(_ *generate.Generator) error { + return nil +} + +func (c *Container) addSharedNamespaces(g *generate.Generator) error { + if c.config.NetNsCtr != "" { + if err := c.addNetworkContainer(g, c.config.NetNsCtr); err != nil { + return err + } + } + + availableUIDs, availableGIDs, err := rootless.GetAvailableIDMaps() + if err != nil { + if os.IsNotExist(err) { + // The kernel-provided files only exist if user namespaces are supported + logrus.Debugf("User or group ID mappings not available: %s", err) + } else { + return err + } + } else { + g.Config.Linux.UIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.UIDMappings, availableUIDs) + g.Config.Linux.GIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.GIDMappings, availableGIDs) + } + + // Hostname handling: + // If we have a UTS namespace, set Hostname in the OCI spec. + // Set the HOSTNAME environment variable unless explicitly overridden by + // the user (already present in OCI spec). If we don't have a UTS ns, + // set it to the host's hostname instead. + hostname := c.Hostname() + + // TODO: make this optional, needs progress on adding FreeBSD section to the spec + foundUTS := true + g.SetHostname(hostname) + + if !foundUTS { + tmpHostname, err := os.Hostname() + if err != nil { + return err + } + hostname = tmpHostname + } + needEnv := true + for _, checkEnv := range g.Config.Process.Env { + if strings.HasPrefix(checkEnv, "HOSTNAME=") { + needEnv = false + break + } + } + if needEnv { + g.AddProcessEnv("HOSTNAME", hostname) + } + return nil +} + +func (c *Container) addRootPropagation(_ *generate.Generator, _ []spec.Mount) error { + return nil +} + +func (c *Container) setProcessLabel(_ *generate.Generator) { +} + +func (c *Container) setMountLabel(_ *generate.Generator) { +} + +func (c *Container) setCgroupsPath(_ *generate.Generator) error { + return nil +} + +func (c *Container) addSpecialDNS(nameservers []string) []string { + return nameservers +} + +func (c *Container) isSlirp4netnsIPv6() bool { + return false +} + +// check for net=none +func (c *Container) hasNetNone() bool { + return c.state.NetNS == "" +} + +func setVolumeAtime(mountPoint string, st os.FileInfo) error { + stat := st.Sys().(*syscall.Stat_t) + atime := time.Unix(int64(stat.Atimespec.Sec), int64(stat.Atimespec.Nsec)) //nolint: unconvert + if err := os.Chtimes(mountPoint, atime, st.ModTime()); err != nil { + return err + } + return nil +} + +func (c *Container) makeHostnameBindMount() error { + return nil +} + +func (c *Container) getConmonPidFd() int { + // Note: kqueue(2) could be used here but that would require + // factoring out the call to unix.PollFd from WaitForExit so + // keeping things simple for now. + return -1 +} + +func (c *Container) jailName() (string, error) { + // If this container is in a pod, get the vnet name from the + // corresponding infra container + var ic *Container + if c.config.Pod != "" && c.config.Pod != c.ID() { + // Get the pod from state + pod, err := c.runtime.state.Pod(c.config.Pod) + if err != nil { + return "", fmt.Errorf("cannot find infra container for pod %s: %w", c.config.Pod, err) + } + ic, err = pod.InfraContainer() + if err != nil { + return "", fmt.Errorf("getting infra container for pod %s: %w", pod.ID(), err) + } + if ic.ID() != c.ID() { + ic.lock.Lock() + defer ic.lock.Unlock() + if err := ic.syncContainer(); err != nil { + return "", err + } + } + } else { + ic = c + } + + if ic.state.NetNS != "" && ic != c { + return ic.state.NetNS + "." + c.ID(), nil + } else { + return c.ID(), nil + } +} + +type safeMountInfo struct { + // mountPoint is the mount point. + mountPoint string +} + +// Close releases the resources allocated with the safe mount info. +func (s *safeMountInfo) Close() { +} + +// safeMountSubPath securely mounts a subpath inside a volume to a new temporary location. +// The function checks that the subpath is a valid subpath within the volume and that it +// does not escape the boundaries of the mount point (volume). +// +// The caller is responsible for closing the file descriptor and unmounting the subpath +// when it's no longer needed. +func (c *Container) safeMountSubPath(mountPoint, subpath string) (s *safeMountInfo, err error) { + return &safeMountInfo{mountPoint: filepath.Join(mountPoint, subpath)}, nil +} + +func (c *Container) makePlatformMtabLink(_, _, _ int) error { + // /etc/mtab does not exist on FreeBSD + return nil +} + +func (c *Container) getPlatformRunPath() (string, error) { + // If we have a linux image, use "/run", otherwise use "/var/run" for + // consistency with FreeBSD path conventions. + runPath := "/var/run" + if c.config.RootfsImageID != "" { + image, _, err := c.runtime.libimageRuntime.LookupImage(c.config.RootfsImageID, nil) + if err != nil { + return "", err + } + inspectData, err := image.Inspect(context.TODO(), nil) + if err != nil { + return "", err + } + if inspectData.Os == "linux" { + runPath = "/run" + } + } + return runPath, nil +} + +func (c *Container) addMaskedPaths(_ *generate.Generator) { + // There are currently no FreeBSD-specific masked paths +} + +func (c *Container) hasPrivateUTS() bool { + // Currently we always use a private UTS namespace on FreeBSD. This + // should be optional but needs a FreeBSD section in the OCI runtime + // specification. + return true +} + +// hasCapSysResource returns whether the current process has CAP_SYS_RESOURCE. +func hasCapSysResource() (bool, error) { + return true, nil +} + +// containerPathIsFile returns true if the given containerPath is a file +func containerPathIsFile(unsafeRoot string, containerPath string) (bool, error) { + // Note freebsd does not have support for OpenInRoot() so us the less safe way + // with the old SecureJoin(), but given this is only called before the container + // is started it is not subject to race conditions with the container process. + path, err := securejoin.SecureJoin(unsafeRoot, containerPath) + if err != nil { + return false, err + } + + st, err := os.Lstat(path) + if err == nil && !st.IsDir() { + return true, nil + } + return false, err +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_internal_linux.go b/vendor/github.com/containers/podman/v5/libpod/container_internal_linux.go new file mode 100644 index 00000000..35b9063e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_internal_linux.go @@ -0,0 +1,848 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path" + "path/filepath" + "strings" + "sync" + "syscall" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/shutdown" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/cyphar/filepath-securejoin/pathrs-lite" + "github.com/moby/sys/capability" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/slirp4netns" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/common/pkg/config" + "golang.org/x/sys/unix" +) + +var ( + bindOptions = []string{define.TypeBind, "rprivate"} +) + +func (c *Container) mountSHM(shmOptions string) error { + contextType := "context" + if c.config.LabelNested { + contextType = "rootcontext" + } + + if err := unix.Mount("shm", c.config.ShmDir, define.TypeTmpfs, unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, + label.FormatMountLabelByType(shmOptions, c.config.MountLabel, contextType)); err != nil { + return fmt.Errorf("failed to mount shm tmpfs %q: %w", c.config.ShmDir, err) + } + return nil +} + +func (c *Container) unmountSHM(mount string) error { + if err := unix.Unmount(mount, 0); err != nil { + if err != syscall.EINVAL && err != syscall.ENOENT { + return fmt.Errorf("unmounting container %s SHM mount %s: %w", c.ID(), mount, err) + } + // If it's just an EINVAL or ENOENT, debug logs only + logrus.Debugf("Container %s failed to unmount %s : %v", c.ID(), mount, err) + } + return nil +} + +// prepare mounts the container and sets up other required resources like net +// namespaces +func (c *Container) prepare() error { + var ( + wg sync.WaitGroup + netNS string + networkStatus map[string]types.StatusBlock + createNetNSErr, mountStorageErr error + mountPoint string + tmpStateLock sync.Mutex + ) + + shutdown.Inhibit() + defer shutdown.Uninhibit() + + wg.Add(2) + + go func() { + defer wg.Done() + if c.state.State == define.ContainerStateStopped { + // networking should not be reused after a stop + if err := c.cleanupNetwork(); err != nil { + createNetNSErr = err + return + } + } + + // Set up network namespace if not already set up + noNetNS := c.state.NetNS == "" + if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS { + c.reservedPorts, createNetNSErr = c.bindPorts() + if createNetNSErr != nil { + return + } + + netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c) + if createNetNSErr != nil { + return + } + + tmpStateLock.Lock() + defer tmpStateLock.Unlock() + + // Assign NetNS attributes to container + c.state.NetNS = netNS + c.state.NetworkStatus = networkStatus + } + }() + // Mount storage if not mounted + go func() { + defer wg.Done() + mountPoint, mountStorageErr = c.mountStorage() + + if mountStorageErr != nil { + return + } + + tmpStateLock.Lock() + defer tmpStateLock.Unlock() + + // Finish up mountStorage + c.state.Mounted = true + c.state.Mountpoint = mountPoint + + logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint) + }() + + wg.Wait() + + var createErr error + if createNetNSErr != nil { + createErr = createNetNSErr + } + if mountStorageErr != nil { + if createErr != nil { + logrus.Errorf("Preparing container %s: %v", c.ID(), createErr) + } + createErr = mountStorageErr + } + + // Only trigger storage cleanup if mountStorage was successful. + // Otherwise, we may mess up mount counters. + if createNetNSErr != nil && mountStorageErr == nil { + if err := c.cleanupStorage(); err != nil { + // createErr is guaranteed non-nil, so print + // unconditionally + logrus.Errorf("Preparing container %s: %v", c.ID(), createErr) + createErr = fmt.Errorf("unmounting storage for container %s after network create failure: %w", c.ID(), err) + } + } + + // It's OK to unconditionally trigger network cleanup. If the network + // isn't ready it will do nothing. + if createErr != nil { + if err := c.cleanupNetwork(); err != nil { + logrus.Errorf("Preparing container %s: %v", c.ID(), createErr) + createErr = fmt.Errorf("cleaning up container %s network after setup failure: %w", c.ID(), err) + } + } + + if createErr != nil { + for _, f := range c.reservedPorts { + // make sure to close all ports again on errors + f.Close() + } + c.reservedPorts = nil + return createErr + } + + // Save changes to container state + if err := c.save(); err != nil { + return err + } + + return nil +} + +// cleanupNetwork unmounts and cleans up the container's network +func (c *Container) cleanupNetwork() error { + if c.config.NetNsCtr != "" { + return nil + } + netDisabled, err := c.NetworkDisabled() + if err != nil { + return err + } + if netDisabled { + return nil + } + if c.state.NetNS == "" { + logrus.Debugf("Network is already cleaned up, skipping...") + return nil + } + + // Stop the container's network namespace (if it has one) + neterr := c.runtime.teardownNetNS(c) + c.state.NetNS = "" + c.state.NetworkStatus = nil + + // always save even when there was an error + err = c.save() + if err != nil { + if neterr != nil { + logrus.Errorf("Unable to clean up network for container %s: %q", c.ID(), neterr) + } + return err + } + + return neterr +} + +// reloadNetwork reloads the network for the given container, recreating +// firewall rules. +func (c *Container) reloadNetwork() error { + result, err := c.runtime.reloadContainerNetwork(c) + if err != nil { + return err + } + + c.state.NetworkStatus = result + + return c.save() +} + +// systemd expects to have /run, /run/lock and /tmp on tmpfs +// It also expects to be able to write to /sys/fs/cgroup/systemd and /var/log/journal +func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) error { + var containerUUIDSet bool + for _, s := range c.config.Spec.Process.Env { + if strings.HasPrefix(s, "container_uuid=") { + containerUUIDSet = true + break + } + } + if !containerUUIDSet { + g.AddProcessEnv("container_uuid", c.ID()[:32]) + } + // limit systemd-specific tmpfs mounts if specified + // while creating a pod or ctr, if not, default back to 50% + var shmSizeSystemdMntOpt string + if c.config.ShmSizeSystemd != 0 { + shmSizeSystemdMntOpt = fmt.Sprintf("size=%d", c.config.ShmSizeSystemd) + } + options := []string{"rw", "rprivate", "nosuid", "nodev"} + for _, dest := range []string{"/run", "/run/lock"} { + if MountExists(mounts, dest) { + continue + } + tmpfsMnt := spec.Mount{ + Destination: dest, + Type: define.TypeTmpfs, + Source: define.TypeTmpfs, + Options: append(options, "tmpcopyup", shmSizeSystemdMntOpt), + } + g.AddMount(tmpfsMnt) + } + for _, dest := range []string{"/tmp", "/var/log/journal"} { + if MountExists(mounts, dest) { + continue + } + tmpfsMnt := spec.Mount{ + Destination: dest, + Type: define.TypeTmpfs, + Source: define.TypeTmpfs, + Options: append(options, "tmpcopyup", shmSizeSystemdMntOpt), + } + g.AddMount(tmpfsMnt) + } + + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return err + } + + hasCgroupNs := false + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.CgroupNamespace { + hasCgroupNs = true + break + } + } + + if unified { + g.RemoveMount("/sys/fs/cgroup") + + var systemdMnt spec.Mount + if hasCgroupNs { + systemdMnt = spec.Mount{ + Destination: "/sys/fs/cgroup", + Type: "cgroup", + Source: "cgroup", + Options: []string{"private", "rw"}, + } + } else { + systemdMnt = spec.Mount{ + Destination: "/sys/fs/cgroup", + Type: define.TypeBind, + Source: "/sys/fs/cgroup", + Options: []string{define.TypeBind, "private", "rw"}, + } + } + g.AddMount(systemdMnt) + } else { + hasSystemdMount := MountExists(mounts, "/sys/fs/cgroup/systemd") + if hasCgroupNs && !hasSystemdMount { + return errors.New("cgroup namespace is not supported with cgroup v1 and systemd mode") + } + mountOptions := []string{define.TypeBind, "rprivate"} + + if !hasSystemdMount { + skipMount := hasSystemdMount + var statfs unix.Statfs_t + if err := unix.Statfs("/sys/fs/cgroup/systemd", &statfs); err != nil { + if errors.Is(err, os.ErrNotExist) { + // If the mount is missing on the host, we cannot bind mount it so + // just skip it. + skipMount = true + } + mountOptions = append(mountOptions, "nodev", "noexec", "nosuid") + } else { + if statfs.Flags&unix.MS_NODEV == unix.MS_NODEV { + mountOptions = append(mountOptions, "nodev") + } + if statfs.Flags&unix.MS_NOEXEC == unix.MS_NOEXEC { + mountOptions = append(mountOptions, "noexec") + } + if statfs.Flags&unix.MS_NOSUID == unix.MS_NOSUID { + mountOptions = append(mountOptions, "nosuid") + } + if statfs.Flags&unix.MS_RDONLY == unix.MS_RDONLY { + mountOptions = append(mountOptions, "ro") + } + } + if !skipMount { + systemdMnt := spec.Mount{ + Destination: "/sys/fs/cgroup/systemd", + Type: define.TypeBind, + Source: "/sys/fs/cgroup/systemd", + Options: mountOptions, + } + g.AddMount(systemdMnt) + g.AddLinuxMaskedPaths("/sys/fs/cgroup/systemd/release_agent") + } + } + } + + return nil +} + +// Add an existing container's namespace to the spec +func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr string, specNS spec.LinuxNamespaceType) error { + nsCtr, err := c.runtime.state.Container(ctr) + if err != nil { + return fmt.Errorf("retrieving dependency %s of container %s from state: %w", ctr, c.ID(), err) + } + + if specNS == spec.UTSNamespace { + hostname := nsCtr.Hostname() + // Joining an existing namespace, cannot set the hostname + g.SetHostname("") + g.AddProcessEnv("HOSTNAME", hostname) + } + + nsPath, err := nsCtr.NamespacePath(ns) + if err != nil { + return err + } + + if err := g.AddOrReplaceLinuxNamespace(string(specNS), nsPath); err != nil { + return err + } + + return nil +} + +func isRootlessCgroupSet(cgroup string) bool { + // old versions of podman were setting the CgroupParent to CgroupfsDefaultCgroupParent + // by default. Avoid breaking these versions and check whether the cgroup parent is + // set to the default and in this case enable the old behavior. It should not be a real + // problem because the default CgroupParent is usually owned by root so rootless users + // cannot access it. + // This check might be lifted in a future version of Podman. + // Check both that the cgroup or its parent is set to the default value (used by pods). + return cgroup != CgroupfsDefaultCgroupParent && filepath.Dir(cgroup) != CgroupfsDefaultCgroupParent +} + +func (c *Container) expectPodCgroup() (bool, error) { + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return false, err + } + cgroupManager := c.CgroupManager() + switch { + case c.config.NoCgroups: + return false, nil + case cgroupManager == config.SystemdCgroupsManager: + return !rootless.IsRootless() || unified, nil + case cgroupManager == config.CgroupfsCgroupsManager: + return !rootless.IsRootless(), nil + default: + return false, fmt.Errorf("invalid cgroup mode %s requested for pods: %w", cgroupManager, define.ErrInvalidArg) + } +} + +// Get cgroup path in a format suitable for the OCI spec +func (c *Container) getOCICgroupPath() (string, error) { + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return "", err + } + cgroupManager := c.CgroupManager() + switch { + case c.config.NoCgroups: + return "", nil + case c.config.CgroupsMode == cgroupSplit: + selfCgroup, err := cgroups.GetOwnCgroupDisallowRoot() + if err != nil { + return "", err + } + return filepath.Join(selfCgroup, fmt.Sprintf("libpod-payload-%s", c.ID())), nil + case cgroupManager == config.SystemdCgroupsManager: + // When the OCI runtime is set to use Systemd as a cgroup manager, it + // expects cgroups to be passed as follows: + // slice:prefix:name + systemdCgroups := fmt.Sprintf("%s:libpod:%s", path.Base(c.config.CgroupParent), c.ID()) + logrus.Debugf("Setting Cgroups for container %s to %s", c.ID(), systemdCgroups) + return systemdCgroups, nil + case (rootless.IsRootless() && (cgroupManager == config.CgroupfsCgroupsManager || !unified)): + if c.config.CgroupParent == "" || !isRootlessCgroupSet(c.config.CgroupParent) { + return "", nil + } + fallthrough + case cgroupManager == config.CgroupfsCgroupsManager: + cgroupPath := filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-%s", c.ID())) + logrus.Debugf("Setting Cgroup path for container %s to %s", c.ID(), cgroupPath) + return cgroupPath, nil + default: + return "", fmt.Errorf("invalid cgroup manager %s requested: %w", cgroupManager, define.ErrInvalidArg) + } +} + +func openDirectory(path string) (fd int, err error) { + return unix.Open(path, unix.O_RDONLY|unix.O_PATH|unix.O_CLOEXEC, 0) +} + +func (c *Container) addNetworkNamespace(g *generate.Generator) error { + if c.config.CreateNetNS { + if c.config.PostConfigureNetNS { + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), ""); err != nil { + return err + } + } else { + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), c.state.NetNS); err != nil { + return err + } + } + } + return nil +} + +func (c *Container) addSystemdMounts(g *generate.Generator) error { + if c.Systemd() { + if err := c.setupSystemd(g.Mounts(), *g); err != nil { + return err + } + } + return nil +} + +func (c *Container) addSharedNamespaces(g *generate.Generator) error { + if c.config.IPCNsCtr != "" { + if err := c.addNamespaceContainer(g, IPCNS, c.config.IPCNsCtr, spec.IPCNamespace); err != nil { + return err + } + } + if c.config.MountNsCtr != "" { + if err := c.addNamespaceContainer(g, MountNS, c.config.MountNsCtr, spec.MountNamespace); err != nil { + return err + } + } + if c.config.NetNsCtr != "" { + if err := c.addNamespaceContainer(g, NetNS, c.config.NetNsCtr, spec.NetworkNamespace); err != nil { + return err + } + } + if c.config.PIDNsCtr != "" { + if err := c.addNamespaceContainer(g, PIDNS, c.config.PIDNsCtr, spec.PIDNamespace); err != nil { + return err + } + } + if c.config.UserNsCtr != "" { + if err := c.addNamespaceContainer(g, UserNS, c.config.UserNsCtr, spec.UserNamespace); err != nil { + return err + } + if len(g.Config.Linux.UIDMappings) == 0 { + // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping + g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) + g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) + } + } + + availableUIDs, availableGIDs, err := rootless.GetAvailableIDMaps() + if err != nil { + if os.IsNotExist(err) { + // The kernel-provided files only exist if user namespaces are supported + logrus.Debugf("User or group ID mappings not available: %s", err) + } else { + return err + } + } else { + g.Config.Linux.UIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.UIDMappings, availableUIDs) + g.Config.Linux.GIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.GIDMappings, availableGIDs) + } + + // Hostname handling: + // If we have a UTS namespace, set Hostname in the OCI spec. + // Set the HOSTNAME environment variable unless explicitly overridden by + // the user (already present in OCI spec). If we don't have a UTS ns, + // set it to the host's hostname instead. + hostname := c.Hostname() + foundUTS := false + + for _, i := range c.config.Spec.Linux.Namespaces { + if i.Type == spec.UTSNamespace && i.Path == "" { + foundUTS = true + g.SetHostname(hostname) + break + } + } + if !foundUTS { + tmpHostname, err := os.Hostname() + if err != nil { + return err + } + hostname = tmpHostname + } + needEnv := true + for _, checkEnv := range g.Config.Process.Env { + if strings.SplitN(checkEnv, "=", 2)[0] == "HOSTNAME" { + needEnv = false + break + } + } + if needEnv { + g.AddProcessEnv("HOSTNAME", hostname) + } + + if c.config.UTSNsCtr != "" { + if err := c.addNamespaceContainer(g, UTSNS, c.config.UTSNsCtr, spec.UTSNamespace); err != nil { + return err + } + } + if c.config.CgroupNsCtr != "" { + if err := c.addNamespaceContainer(g, CgroupNS, c.config.CgroupNsCtr, spec.CgroupNamespace); err != nil { + return err + } + } + + if c.config.UserNsCtr == "" && c.config.IDMappings.AutoUserNs { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + g.ClearLinuxUIDMappings() + for _, uidmap := range c.config.IDMappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + g.ClearLinuxGIDMappings() + for _, gidmap := range c.config.IDMappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + } + return nil +} + +func (c *Container) addRootPropagation(g *generate.Generator, mounts []spec.Mount) error { + // Determine property of RootPropagation based on volume properties. If + // a volume is shared, then keep root propagation shared. This should + // work for slave and private volumes too. + // + // For slave volumes, it can be either [r]shared/[r]slave. + // + // For private volumes any root propagation value should work. + rootPropagation := "" + for _, m := range mounts { + for _, opt := range m.Options { + switch opt { + case MountShared, MountRShared: + if rootPropagation != MountShared && rootPropagation != MountRShared { + rootPropagation = MountShared + } + case MountSlave, MountRSlave: + if rootPropagation != MountShared && rootPropagation != MountRShared && rootPropagation != MountSlave && rootPropagation != MountRSlave { + rootPropagation = MountRSlave + } + } + } + } + if rootPropagation != "" { + logrus.Debugf("Set root propagation to %q", rootPropagation) + if err := g.SetLinuxRootPropagation(rootPropagation); err != nil { + return err + } + } + return nil +} + +func (c *Container) setProcessLabel(g *generate.Generator) { + g.SetProcessSelinuxLabel(c.ProcessLabel()) +} + +func (c *Container) setMountLabel(g *generate.Generator) { + g.SetLinuxMountLabel(c.MountLabel()) +} + +func (c *Container) setCgroupsPath(g *generate.Generator) error { + cgroupPath, err := c.getOCICgroupPath() + if err != nil { + return err + } + g.SetLinuxCgroupsPath(cgroupPath) + return nil +} + +// addSpecialDNS adds special dns servers for slirp4netns and pasta +func (c *Container) addSpecialDNS(nameservers []string) []string { + switch { + case c.config.NetMode.IsBridge(): + info, err := c.runtime.network.RootlessNetnsInfo() + if err == nil && info != nil { + nameservers = append(nameservers, info.DnsForwardIps...) + } + case c.pastaResult != nil: + nameservers = append(nameservers, c.pastaResult.DNSForwardIPs...) + case c.config.NetMode.IsSlirp4netns(): + // slirp4netns has a built in DNS forwarder. + slirp4netnsDNS, err := slirp4netns.GetDNS(c.slirp4netnsSubnet) + if err != nil { + logrus.Warn("Failed to determine Slirp4netns DNS: ", err.Error()) + } else { + nameservers = append(nameservers, slirp4netnsDNS.String()) + } + } + return nameservers +} + +func (c *Container) isSlirp4netnsIPv6() bool { + if c.config.NetMode.IsSlirp4netns() { + extraOptions := c.config.NetworkOptions[slirp4netns.BinaryName] + options := make([]string, 0, len(c.runtime.config.Engine.NetworkCmdOptions.Get())+len(extraOptions)) + options = append(options, c.runtime.config.Engine.NetworkCmdOptions.Get()...) + options = append(options, extraOptions...) + + // loop backwards as the last argument wins and we can exit early + // This should be kept in sync with c/common/libnetwork/slirp4netns. + for i := len(options) - 1; i >= 0; i-- { + switch options[i] { + case "enable_ipv6=true": + return true + case "enable_ipv6=false": + return false + } + } + // default is true + return true + } + + return false +} + +// check for net=none +func (c *Container) hasNetNone() bool { + if !c.config.CreateNetNS { + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + if ns.Path == "" { + return true + } + } + } + } + return false +} + +func setVolumeAtime(mountPoint string, st os.FileInfo) error { + stat := st.Sys().(*syscall.Stat_t) + atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) //nolint: unconvert + if err := os.Chtimes(mountPoint, atime, st.ModTime()); err != nil { + return err + } + return nil +} + +func (c *Container) makeHostnameBindMount() error { + if c.config.UseImageHostname { + return nil + } + + // Make /etc/hostname + // This should never change, so no need to recreate if it exists + if _, ok := c.state.BindMounts["/etc/hostname"]; !ok { + hostnamePath, err := c.writeStringToRundir("hostname", c.Hostname()+"\n") + if err != nil { + return fmt.Errorf("creating hostname file for container %s: %w", c.ID(), err) + } + c.state.BindMounts["/etc/hostname"] = hostnamePath + } + return nil +} + +func (c *Container) getConmonPidFd() int { + // Track lifetime of conmon precisely using pidfd_open + poll. + // There are many cases for this to fail, for instance conmon is dead + // or pidfd_open is not supported (pre linux 5.3), so fall back to the + // traditional loop with poll + sleep + if fd, err := unix.PidfdOpen(c.state.ConmonPID, 0); err == nil { + return fd + } else if err != unix.ENOSYS && err != unix.ESRCH { + logrus.Debugf("PidfdOpen(%d) failed: %v", c.state.ConmonPID, err) + } + return -1 +} + +type safeMountInfo struct { + // file is the open File. + file *os.File + + // mountPoint is the mount point. + mountPoint string +} + +// Close releases the resources allocated with the safe mount info. +func (s *safeMountInfo) Close() { + _ = unix.Unmount(s.mountPoint, unix.MNT_DETACH) + _ = s.file.Close() +} + +// safeMountSubPath securely mounts a subpath inside a volume to a new temporary location. +// The function checks that the subpath is a valid subpath within the volume and that it +// does not escape the boundaries of the mount point (volume). +// +// The caller is responsible for closing the file descriptor and unmounting the subpath +// when it's no longer needed. +func (c *Container) safeMountSubPath(mountPoint, subpath string) (s *safeMountInfo, err error) { + file, err := pathrs.OpenInRoot(mountPoint, subpath) + if err != nil { + return nil, err + } + + // we need to always reference the file by its fd, that points inside the mountpoint. + fname := fmt.Sprintf("/proc/self/fd/%d", int(file.Fd())) + + fi, err := os.Stat(fname) + if err != nil { + return nil, err + } + var npath string + switch { + case fi.Mode()&fs.ModeSymlink != 0: + return nil, fmt.Errorf("file %q is a symlink", filepath.Join(mountPoint, subpath)) + case fi.IsDir(): + npath, err = os.MkdirTemp(c.state.RunDir, "subpath") + if err != nil { + return nil, err + } + default: + tmp, err := os.CreateTemp(c.state.RunDir, "subpath") + if err != nil { + return nil, err + } + tmp.Close() + npath = tmp.Name() + } + + if err := unix.Mount(fname, npath, "", unix.MS_BIND|unix.MS_REC, ""); err != nil { + return nil, err + } + return &safeMountInfo{ + file: file, + mountPoint: npath, + }, nil +} + +func (c *Container) makePlatformMtabLink(etcInTheContainerFd, rootUID, rootGID int) error { + // If /etc/mtab does not exist in container image, then we need to + // create it, so that mount command within the container will work. + err := unix.Symlinkat("/proc/mounts", etcInTheContainerFd, "mtab") + if err != nil && !os.IsExist(err) { + return fmt.Errorf("creating /etc/mtab symlink: %w", err) + } + // If the symlink was created, then also chown it to root in the container + if err == nil && (rootUID != 0 || rootGID != 0) { + err = unix.Fchownat(etcInTheContainerFd, "mtab", rootUID, rootGID, unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + return fmt.Errorf("chown /etc/mtab: %w", err) + } + } + return nil +} + +func (c *Container) getPlatformRunPath() (string, error) { + return "/run", nil +} + +func (c *Container) addMaskedPaths(g *generate.Generator) { + if !c.config.Privileged && g.Config != nil && g.Config.Linux != nil && len(g.Config.Linux.MaskedPaths) > 0 { + g.AddLinuxMaskedPaths("/sys/devices/virtual/powercap") + } +} + +func (c *Container) hasPrivateUTS() bool { + privateUTS := false + if c.config.Spec.Linux != nil { + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.UTSNamespace { + privateUTS = true + break + } + } + } + return privateUTS +} + +// hasCapSysResource returns whether the current process has CAP_SYS_RESOURCE. +var hasCapSysResource = sync.OnceValues(func() (bool, error) { + currentCaps, err := capability.NewPid2(0) + if err != nil { + return false, err + } + if err = currentCaps.Load(); err != nil { + return false, err + } + return currentCaps.Get(capability.EFFECTIVE, capability.CAP_SYS_RESOURCE), nil +}) + +// containerPathIsFile returns true if the given containerPath is a file +func containerPathIsFile(unsafeRoot string, containerPath string) (bool, error) { + f, err := pathrs.OpenInRoot(unsafeRoot, containerPath) + if err != nil { + return false, err + } + defer f.Close() + + st, err := f.Stat() + if err == nil && !st.IsDir() { + return true, nil + } + return false, err +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_linux.go b/vendor/github.com/containers/podman/v5/libpod/container_linux.go new file mode 100644 index 00000000..1101aaa8 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_linux.go @@ -0,0 +1,21 @@ +//go:build !remote + +package libpod + +import ( + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +func networkDisabled(c *Container) (bool, error) { + if c.config.CreateNetNS { + return false, nil + } + if !c.config.PostConfigureNetNS { + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + return ns.Path == "", nil + } + } + } + return false, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_log.go b/vendor/github.com/containers/podman/v5/libpod/container_log.go new file mode 100644 index 00000000..91c0a50c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_log.go @@ -0,0 +1,155 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/logs" + systemdDefine "github.com/containers/podman/v5/pkg/systemd/define" + "github.com/nxadm/tail" + "github.com/nxadm/tail/watch" + "github.com/sirupsen/logrus" +) + +// logDrivers stores the currently available log drivers, do not modify +var logDrivers []string + +func init() { + logDrivers = append(logDrivers, define.KubernetesLogging, define.NoLogging, define.PassthroughLogging) +} + +// Log is a runtime function that can read one or more container logs. +func (r *Runtime) Log(ctx context.Context, containers []*Container, options *logs.LogOptions, logChannel chan *logs.LogLine) error { + for c, ctr := range containers { + if err := ctr.ReadLog(ctx, options, logChannel, int64(c)); err != nil { + return err + } + } + return nil +} + +// ReadLog reads a container's log based on the input options and returns log lines over a channel. +func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error { + switch c.LogDriver() { + case define.PassthroughLogging: + // if running under systemd fallback to a more native journald reading + if unitName, ok := c.config.Labels[systemdDefine.EnvVariable]; ok { + return c.readFromJournal(ctx, options, logChannel, colorID, unitName) + } + return fmt.Errorf("this container is using the 'passthrough' log driver, cannot read logs: %w", define.ErrNoLogs) + case define.NoLogging: + return fmt.Errorf("this container is using the 'none' log driver, cannot read logs: %w", define.ErrNoLogs) + case define.JournaldLogging: + return c.readFromJournal(ctx, options, logChannel, colorID, "") + case define.JSONLogging: + // TODO provide a separate implementation of this when Conmon + // has support. + fallthrough + case define.KubernetesLogging, "": + return c.readFromLogFile(ctx, options, logChannel, colorID) + default: + return fmt.Errorf("unrecognized log driver %q, cannot read logs: %w", c.LogDriver(), define.ErrInternal) + } +} + +func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error { + t, tailLog, err := logs.GetLogFile(c.LogPath(), options) + if err != nil { + // If the log file does not exist, this is not fatal. + if errors.Is(err, os.ErrNotExist) { + return nil + } + return fmt.Errorf("unable to read log file %s for %s : %w", c.ID(), c.LogPath(), err) + } + options.WaitGroup.Add(1) + go func() { + if options.Until.After(time.Now()) { + time.Sleep(time.Until(options.Until)) + if err := t.Stop(); err != nil { + logrus.Errorf("Stopping logger: %v", err) + } + } + }() + + go func() { + for _, nll := range tailLog { + nll.CID = c.ID() + nll.CName = c.Name() + nll.ColorID = colorID + if nll.Since(options.Since) && nll.Until(options.Until) { + logChannel <- nll + } + } + defer options.WaitGroup.Done() + var line *tail.Line + var ok bool + for { + select { + case <-ctx.Done(): + // the consumer has cancelled + t.Kill(errors.New("hangup by client")) + return + case line, ok = <-t.Lines: + if !ok { + // channel was closed + return + } + } + nll, err := logs.NewLogLine(line.Text) + if err != nil { + logrus.Errorf("Getting new log line: %v", err) + continue + } + nll.CID = c.ID() + nll.CName = c.Name() + nll.ColorID = colorID + if nll.Since(options.Since) && nll.Until(options.Until) { + logChannel <- nll + } + } + }() + // Check if container is still running or paused + if options.Follow { + // If the container isn't running or if we encountered an error + // getting its state, instruct the logger to read the file + // until EOF. + state, err := c.State() + if err != nil || state != define.ContainerStateRunning { + if err != nil && !errors.Is(err, define.ErrNoSuchCtr) { + logrus.Errorf("Getting container state: %v", err) + } + go func() { + // Make sure to wait at least for the poll duration + // before stopping the file logger (see #10675). + time.Sleep(watch.POLL_DURATION) + tailError := t.StopAtEOF() + if tailError != nil && tailError.Error() != "tail: stop at eof" { + logrus.Errorf("Stopping logger: %v", tailError) + } + }() + return nil + } + + // The container is running, so we need to wait until the container exited + go func() { + _, err = c.Wait(ctx) + if err != nil && !errors.Is(err, define.ErrNoSuchCtr) { + logrus.Errorf("Waiting for container to exit: %v", err) + } + // Make sure to wait at least for the poll duration + // before stopping the file logger (see #10675). + time.Sleep(watch.POLL_DURATION) + tailError := t.StopAtEOF() + if tailError != nil && tailError.Error() != "tail: stop at eof" { + logrus.Errorf("Stopping logger: %v", tailError) + } + }() + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_log_linux.go b/vendor/github.com/containers/podman/v5/libpod/container_log_linux.go new file mode 100644 index 00000000..f874faa5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_log_linux.go @@ -0,0 +1,248 @@ +//go:build !remote && linux && systemd + +package libpod + +import ( + "context" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/libpod/logs" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/coreos/go-systemd/v22/sdjournal" + "github.com/sirupsen/logrus" +) + +const ( + // journaldLogOut is the journald priority signifying stdout + journaldLogOut = "6" + + // journaldLogErr is the journald priority signifying stderr + journaldLogErr = "3" +) + +func init() { + logDrivers = append(logDrivers, define.JournaldLogging) +} + +func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, + logChannel chan *logs.LogLine, colorID int64, passthroughUnit string) error { + // We need the container's events in the same journal to guarantee + // consistency, see #10323. + if options.Follow && c.runtime.config.Engine.EventsLogger != "journald" { + return fmt.Errorf("using --follow with the journald --log-driver but without the journald --events-backend (%s) is not supported", c.runtime.config.Engine.EventsLogger) + } + + journal, err := sdjournal.NewJournal() + if err != nil { + return err + } + // While logs are written to the `logChannel`, we inspect each event + // and stop once the container has died. Having logs and events in one + // stream prevents a race condition that we faced in #10323. + + // Add the filters for events. + match := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} + if err := journal.AddMatch(match.String()); err != nil { + return fmt.Errorf("adding filter to journald logger: %v: %w", match, err) + } + match = sdjournal.Match{Field: "PODMAN_ID", Value: c.ID()} + if err := journal.AddMatch(match.String()); err != nil { + return fmt.Errorf("adding filter to journald logger: %v: %w", match, err) + } + // Make sure we only read events for the current user, while it is unlikely that there + // is a container ID duplication for two users, it is better to have it just in case. + uidMatch := sdjournal.Match{Field: "_UID", Value: strconv.Itoa(rootless.GetRootlessUID())} + if err := journal.AddMatch(uidMatch.String()); err != nil { + return fmt.Errorf("adding filter to journald logger: %v: %w", uidMatch, err) + } + + // Add the filter for logs. Note the disjunction so that we match + // either the events or the logs. + if err := journal.AddDisjunction(); err != nil { + return fmt.Errorf("adding filter disjunction to journald logger: %w", err) + } + + if passthroughUnit != "" { + // Match based on systemd unit which is the container is cgroup + // so we get the exact logs for a single container even in the + // play kube case where a single unit starts more than one container. + unitTypeName := "_SYSTEMD_UNIT" + if rootless.IsRootless() { + unitTypeName = "_SYSTEMD_USER_UNIT" + } + // By default we will have our own systemd cgroup with the name libpod-.scope. + value := "libpod-" + c.ID() + ".scope" + if c.config.CgroupsMode == cgroupSplit { + // If cgroup split the container runs in the unit cgroup so we use this for logs, + // the good thing is we filter the podman events already out below. + // Thus we are left with the real container log and possibly podman output (e.g. logrus). + value = passthroughUnit + } + + match = sdjournal.Match{Field: unitTypeName, Value: value} + if err := journal.AddMatch(match.String()); err != nil { + return fmt.Errorf("adding filter to journald logger: %v: %w", match, err) + } + } else { + match = sdjournal.Match{Field: "CONTAINER_ID_FULL", Value: c.ID()} + if err := journal.AddMatch(match.String()); err != nil { + return fmt.Errorf("adding filter to journald logger: %v: %w", match, err) + } + } + + if err := journal.AddMatch(uidMatch.String()); err != nil { + return fmt.Errorf("adding filter to journald logger: %v: %w", uidMatch, err) + } + + if options.Since.IsZero() { + if err := journal.SeekHead(); err != nil { + return err + } + } else { + // seek based on time which helps to reduce unnecessary event reads + if err := journal.SeekRealtimeUsec(uint64(options.Since.UnixMicro())); err != nil { + return err + } + } + + c.lock.Lock() + if err := c.syncContainer(); err != nil { + c.lock.Unlock() + return err + } + // The initial "containerCouldBeLogging" state must be correct, we cannot rely on the start event being still in the journal. + // This can happen if the journal was rotated after the container was started or when --since is used. + // https://github.com/containers/podman/issues/16950 + containerCouldBeLogging := c.ensureState(define.ContainerStateRunning, define.ContainerStateStopping) + c.lock.Unlock() + + options.WaitGroup.Add(1) + go func() { + defer func() { + options.WaitGroup.Done() + if err := journal.Close(); err != nil { + logrus.Errorf("Unable to close journal: %v", err) + } + }() + + tailQueue := []*logs.LogLine{} // needed for options.Tail + doTail := options.Tail >= 0 + doTailFunc := func() { + // Flush *once* we hit the end of the journal. + startIndex := int64(len(tailQueue)) + outputLines := int64(0) + for startIndex > 0 && outputLines < options.Tail { + startIndex-- + for startIndex > 0 && tailQueue[startIndex].Partial() { + startIndex-- + } + outputLines++ + } + for i := startIndex; i < int64(len(tailQueue)); i++ { + logChannel <- tailQueue[i] + } + tailQueue = nil + doTail = false + } + for { + entry, err := events.GetNextEntry(ctx, journal, !doTail && options.Follow && containerCouldBeLogging, options.Until) + if err != nil { + logrus.Errorf("Failed to get journal entry: %v", err) + return + } + // entry nil == EOF in journal + if entry == nil { + if doTail { + doTailFunc() + continue + } + return + } + + entryTime := time.Unix(0, int64(entry.RealtimeTimestamp)*int64(time.Microsecond)) + if (entryTime.Before(options.Since) && !options.Since.IsZero()) || (entryTime.After(options.Until) && !options.Until.IsZero()) { + continue + } + // If we're reading an event and the container exited/died, + // then we're done and can return. + event, ok := entry.Fields["PODMAN_EVENT"] + if ok { + status, err := events.StringToStatus(event) + if err != nil { + logrus.Errorf("Failed to translate event: %v", err) + return + } + switch status { + case events.History, events.Init, events.Start, events.Restart: + containerCouldBeLogging = true + case events.Exited: + containerCouldBeLogging = false + } + continue + } + + logLine, err := journalToLogLine(entry) + if err != nil { + logrus.Errorf("Failed parse journal entry: %v", err) + return + } + id := c.ID() + if len(id) > 12 { + id = id[:12] + } + logLine.CID = id + logLine.ColorID = colorID + if options.UseName { + logLine.CName = c.Name() + } + if doTail { + tailQueue = append(tailQueue, logLine) + continue + } + logChannel <- logLine + } + }() + + return nil +} + +func journalToLogLine(entry *sdjournal.JournalEntry) (*logs.LogLine, error) { + line := &logs.LogLine{} + + usec := entry.RealtimeTimestamp + line.Time = time.Unix(0, int64(usec)*int64(time.Microsecond)) + + priority, ok := entry.Fields["PRIORITY"] + if !ok { + return nil, errors.New("no PRIORITY field present in journal entry") + } + switch priority { + case journaldLogOut: + line.Device = "stdout" + case journaldLogErr: + line.Device = "stderr" + default: + return nil, errors.New("unexpected PRIORITY field in journal entry") + } + + // if CONTAINER_PARTIAL_MESSAGE is defined, the log type is "P" + if _, ok := entry.Fields["CONTAINER_PARTIAL_MESSAGE"]; ok { + line.ParseLogType = logs.PartialLogType + } else { + line.ParseLogType = logs.FullLogType + } + + line.Msg, ok = entry.Fields["MESSAGE"] + if !ok { + return nil, errors.New("no MESSAGE field present in journal entry") + } + line.Msg = strings.TrimSuffix(line.Msg, "\n") + + return line, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_log_unsupported.go b/vendor/github.com/containers/podman/v5/libpod/container_log_unsupported.go new file mode 100644 index 00000000..ebda51ff --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_log_unsupported.go @@ -0,0 +1,15 @@ +//go:build !remote && (!linux || !systemd) + +package libpod + +import ( + "context" + "fmt" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/logs" +) + +func (c *Container) readFromJournal(_ context.Context, _ *logs.LogOptions, _ chan *logs.LogLine, _ int64, _ string) error { + return fmt.Errorf("journald logging only enabled with systemd on linux: %w", define.ErrOSNotSupported) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_path_resolution.go b/vendor/github.com/containers/podman/v5/libpod/container_path_resolution.go new file mode 100644 index 00000000..03f8e5a9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_path_resolution.go @@ -0,0 +1,187 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/containers/podman/v5/libpod/define" + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +// pathAbs returns an absolute path. If the specified path is +// relative, it will be resolved relative to the container's working dir. +func (c *Container) pathAbs(path string) string { + if !filepath.IsAbs(path) { + // If the containerPath is not absolute, it's relative to the + // container's working dir. To be extra careful, let's first + // join the working dir with "/", and the add the containerPath + // to it. + path = filepath.Join(filepath.Join("/", c.WorkingDir()), path) + } + return path +} + +// resolvePath resolves the container's mount point and the container +// path as specified by the user. Both may resolve to paths outside of the +// container's mount point when the container path hits a volume or bind mount. +// +// It returns a bool, indicating whether containerPath resolves outside of +// mountPoint (e.g., via a mount or volume), the resolved root (e.g., container +// mount, bind mount or volume) and the resolved path on the root (absolute to +// the host). If the path is on a named volume, the volume is returned. +func (c *Container) resolvePath(mountPoint string, containerPath string) (string, string, *Volume, error) { + // Let's first make sure we have a path relative to the mount point. + pathRelativeToContainerMountPoint := c.pathAbs(containerPath) + resolvedPathOnTheContainerMountPoint := filepath.Join(mountPoint, pathRelativeToContainerMountPoint) + pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint) + pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint) + + // Now we have an "absolute container Path" but not yet resolved on the + // host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to + // check if "/foo/bar/file.txt" is on a volume or bind mount. To do + // that, we need to walk *down* the paths to the root. Assuming + // volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar", + // we must select "/foo/bar". Once selected, we need to rebase the + // remainder (i.e, "/file.txt") on the volume's mount point on the + // host. Same applies to bind mounts. + + searchPath := pathRelativeToContainerMountPoint + for { + volume, err := findVolume(c, searchPath) + if err != nil { + return "", "", nil, err + } + if volume != nil { + logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath) + + mountPoint, err := volume.MountPoint() + if err != nil { + return "", "", nil, err + } + if mountPoint == "" { + return "", "", nil, fmt.Errorf("volume %s is not mounted, cannot copy into it", volume.Name()) + } + + // We found a matching volume for searchPath. We now + // need to first find the relative path of our input + // path to the searchPath, and then join it with the + // volume's mount point. + pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath) + absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(mountPoint, pathRelativeToVolume) + if err != nil { + return "", "", nil, err + } + return mountPoint, absolutePathOnTheVolumeMount, volume, nil + } + + if mount := findBindMount(c, searchPath); mount != nil { + logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath) + // We found a matching bind mount for searchPath. We + // now need to first find the relative path of our + // input path to the searchPath, and then join it with + // the source of the bind mount. + pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath) + absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount) + if err != nil { + return "", "", nil, err + } + return mount.Source, absolutePathOnTheBindMount, nil, nil + } + + if searchPath == "/" { + // Cannot go beyond "/", so we're done. + break + } + // Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar"). + searchPath = filepath.Dir(searchPath) + } + + // No volume, no bind mount but just a normal path on the container. + return mountPoint, resolvedPathOnTheContainerMountPoint, nil, nil +} + +// findVolume checks if the specified containerPath matches the destination +// path of a Volume. Returns a matching Volume or nil. +func findVolume(c *Container, containerPath string) (*Volume, error) { + runtime := c.Runtime() + cleanedContainerPath := filepath.Clean(containerPath) + for _, vol := range c.config.NamedVolumes { + if cleanedContainerPath == filepath.Clean(vol.Dest) { + return runtime.GetVolume(vol.Name) + } + } + return nil, nil +} + +// isSubDir checks whether path is a subdirectory of root. +func isSubDir(path, root string) bool { + // check if the specified container path is below a bind mount. + rel, err := filepath.Rel(root, path) + if err != nil { + return false + } + return rel != ".." && !strings.HasPrefix(rel, "../") +} + +// isPathOnVolume returns true if the specified containerPath is a subdir of any +// Volume's destination. +func isPathOnVolume(c *Container, containerPath string) bool { + cleanedContainerPath := filepath.Clean(containerPath) + for _, vol := range c.config.NamedVolumes { + cleanedDestination := filepath.Clean(vol.Dest) + if cleanedContainerPath == cleanedDestination { + return true + } + if isSubDir(cleanedContainerPath, cleanedDestination) { + return true + } + for dest := cleanedDestination; dest != "/" && dest != "."; dest = filepath.Dir(dest) { + if cleanedContainerPath == dest { + return true + } + } + } + return false +} + +// findBindMount checks if the specified containerPath matches the destination +// path of a Mount. Returns a matching Mount or nil. +func findBindMount(c *Container, containerPath string) *specs.Mount { + cleanedPath := filepath.Clean(containerPath) + for _, m := range c.config.Spec.Mounts { + if m.Type != define.TypeBind { + continue + } + if cleanedPath == filepath.Clean(m.Destination) { + mount := m + return &mount + } + } + return nil +} + +// / isPathOnMount returns true if the specified containerPath is a subdir of any +// Mount's destination. +func isPathOnMount(c *Container, containerPath string) bool { + cleanedContainerPath := filepath.Clean(containerPath) + for _, m := range c.config.Spec.Mounts { + cleanedDestination := filepath.Clean(m.Destination) + if cleanedContainerPath == cleanedDestination { + return true + } + if isSubDir(cleanedContainerPath, cleanedDestination) { + return true + } + for dest := cleanedDestination; dest != "/" && dest != "."; dest = filepath.Dir(dest) { + if cleanedContainerPath == dest { + return true + } + } + } + return false +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_stat_common.go b/vendor/github.com/containers/podman/v5/libpod/container_stat_common.go new file mode 100644 index 00000000..5109c3ee --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_stat_common.go @@ -0,0 +1,154 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/buildah/copier" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/copy" +) + +// statOnHost stats the specified path *on the host*. It returns the file info +// along with the resolved root and the resolved path. Both paths are absolute +// to the host's root. Note that the paths may resolved outside the +// container's mount point (e.g., to a volume or bind mount). +func (c *Container) statOnHost(mountPoint string, containerPath string) (*copier.StatForItem, string, string, error) { + // Now resolve the container's path. It may hit a volume, it may hit a + // bind mount, it may be relative. + resolvedRoot, resolvedPath, _, err := c.resolvePath(mountPoint, containerPath) + if err != nil { + return nil, "", "", err + } + + statInfo, err := secureStat(resolvedRoot, resolvedPath) + return statInfo, resolvedRoot, resolvedPath, err +} + +func (c *Container) stat(containerMountPoint string, containerPath string) (*define.FileInfo, string, string, error) { + var ( + resolvedRoot string + resolvedPath string + absContainerPath string + statInfo *copier.StatForItem + statErr error + ) + + // Make sure that "/" copies the *contents* of the mount point and not + // the directory. + if containerPath == "/" { + containerPath = "/." + } + + // Wildcards are not allowed. + // TODO: it's now technically possible wildcards. + // We may consider enabling support in the future. + if strings.Contains(containerPath, "*") { + return nil, "", "", copy.ErrENOENT + } + + statInfo, resolvedRoot, resolvedPath, statErr = c.statInContainer(containerMountPoint, containerPath) + if statErr != nil { + if statInfo == nil { + return nil, "", "", statErr + } + // Not all errors from secureStat map to ErrNotExist, so we + // have to look into the error string. Turning it into an + // ENOENT lets the API handlers return the correct status code + // which is crucial for the remote client. + if os.IsNotExist(statErr) || strings.Contains(statErr.Error(), "o such file or directory") { + statErr = copy.ErrENOENT + } + } + + switch { + case statInfo.IsSymlink: + // Symlinks are already evaluated and always relative to the + // container's mount point. + absContainerPath = statInfo.ImmediateTarget + case strings.HasPrefix(resolvedPath, containerMountPoint): + // If the path is on the container's mount point, strip it off. + absContainerPath = strings.TrimPrefix(resolvedPath, containerMountPoint) + absContainerPath = filepath.Join("/", absContainerPath) + default: + // No symlink and not on the container's mount point, so let's + // move it back to the original input. It must have evaluated + // to a volume or bind mount but we cannot return host paths. + absContainerPath = containerPath + } + + // Preserve the base path as specified by the user. The `filepath` + // packages likes to remove trailing slashes and dots that are crucial + // to the copy logic. + absContainerPath = copy.PreserveBasePath(containerPath, absContainerPath) + resolvedPath = copy.PreserveBasePath(containerPath, resolvedPath) + + info := &define.FileInfo{ + IsDir: statInfo.IsDir, + Name: filepath.Base(absContainerPath), + Size: statInfo.Size, + Mode: statInfo.Mode, + ModTime: statInfo.ModTime, + LinkTarget: absContainerPath, + } + + return info, resolvedRoot, resolvedPath, statErr +} + +// secureStat extracts file info for path in a chroot'ed environment in root. +func secureStat(root string, path string) (*copier.StatForItem, error) { + var glob string + var err error + + // If root and path are equal, then dir must be empty and the glob must + // be ".". + if filepath.Clean(root) == filepath.Clean(path) { + glob = "." + } else { + glob, err = filepath.Rel(root, path) + if err != nil { + return nil, err + } + } + + globStats, err := copier.Stat(root, "", copier.StatOptions{}, []string{glob}) + if err != nil { + return nil, err + } + + if len(globStats) != 1 { + return nil, fmt.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats)) + } + if len(globStats) != 1 { + return nil, fmt.Errorf("internal error: secureStat: expected 1 result but got %d", len(globStats[0].Results)) + } + + // NOTE: the key in the map differ from `glob` when hitting symlink. + // Hence, we just take the first (and only) key/value pair. + for _, stat := range globStats[0].Results { + var statErr error + if stat.Error != "" { + statErr = errors.New(stat.Error) + } + // If necessary evaluate the symlink + if stat.IsSymlink { + target, err := copier.Eval(root, path, copier.EvalOptions{}) + if err != nil { + return nil, fmt.Errorf("evaluating symlink in container: %w", err) + } + // Need to make sure the symlink is relative to the root! + target = strings.TrimPrefix(target, root) + target = filepath.Join("/", target) + stat.ImmediateTarget = target + } + return stat, statErr + } + + // Nothing found! + return nil, copy.ErrENOENT +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_stat_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/container_stat_freebsd.go new file mode 100644 index 00000000..16464689 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_stat_freebsd.go @@ -0,0 +1,15 @@ +//go:build !remote + +package libpod + +import ( + "github.com/containers/buildah/copier" +) + +// On FreeBSD, jails use the global mount namespace, filtered to only +// the mounts the jail should see. This means that we can use +// statOnHost whether the container is running or not. +// container is running +func (c *Container) statInContainer(mountPoint string, containerPath string) (*copier.StatForItem, string, string, error) { + return c.statOnHost(mountPoint, containerPath) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_stat_linux.go b/vendor/github.com/containers/podman/v5/libpod/container_stat_linux.go new file mode 100644 index 00000000..5a67c0ea --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_stat_linux.go @@ -0,0 +1,40 @@ +//go:build !remote + +package libpod + +import ( + "github.com/containers/buildah/copier" + "github.com/containers/podman/v5/libpod/define" +) + +// statInsideMount stats the specified path *inside* the container's mount and PID +// namespace. It returns the file info along with the resolved root ("/") and +// the resolved path (relative to the root). +func (c *Container) statInsideMount(containerPath string) (*copier.StatForItem, string, string, error) { + resolvedRoot := "/" + resolvedPath := c.pathAbs(containerPath) + var statInfo *copier.StatForItem + + err := c.joinMountAndExec( + func() error { + var statErr error + statInfo, statErr = secureStat(resolvedRoot, resolvedPath) + return statErr + }, + ) + + return statInfo, resolvedRoot, resolvedPath, err +} + +// Calls either statOnHost or statInsideMount depending on whether the +// container is running +func (c *Container) statInContainer(mountPoint string, containerPath string) (*copier.StatForItem, string, string, error) { + if c.state.State == define.ContainerStateRunning { + // If the container is running, we need to join it's mount namespace + // and stat there. + return c.statInsideMount(containerPath) + } + // If the container is NOT running, we need to resolve the path + // on the host. + return c.statOnHost(mountPoint, containerPath) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_top_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/container_top_freebsd.go new file mode 100644 index 00000000..531f6722 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_top_freebsd.go @@ -0,0 +1,135 @@ +//go:build !remote + +package libpod + +import ( + "bufio" + "errors" + "fmt" + "os/exec" + "strings" + "sync" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/util" + "github.com/google/shlex" + "github.com/sirupsen/logrus" +) + +var isDescriptor = map[string]bool{} + +func init() { + allDescriptors, err := util.GetContainerPidInformationDescriptors() + if err != nil { + // Should never happen + logrus.Debugf("failed call to util.GetContainerPidInformationDescriptors()") + return + } + for _, d := range allDescriptors { + isDescriptor[d] = true + } +} + +// Top gathers statistics about the running processes in a container. It returns a +// []string for output +func (c *Container) Top(descriptors []string) ([]string, error) { + conStat, err := c.State() + if err != nil { + return nil, fmt.Errorf("unable to look up state for %s: %w", c.ID(), err) + } + if conStat != define.ContainerStateRunning { + return nil, errors.New("top can only be used on running containers") + } + + // Default to 'ps -ef' compatible descriptors + if len(strings.Join(descriptors, "")) == 0 { + descriptors = []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "args"} + } + + // If everything in descriptors is a supported AIX format + // descriptor, we use 'ps -ao ', otherwise we pass + // everything straight through to ps. + supportedDescriptors := true + for _, d := range descriptors { + if _, ok := isDescriptor[d]; !ok { + supportedDescriptors = false + break + } + } + if supportedDescriptors { + descriptors = []string{"-o", strings.Join(descriptors, ",")} + } + + // Note that the descriptors to ps(1) must be shlexed (see #12452). + psDescriptors := []string{} + for _, d := range descriptors { + shSplit, err := shlex.Split(d) + if err != nil { + return nil, fmt.Errorf("parsing ps args: %w", err) + } + for _, s := range shSplit { + if s != "" { + psDescriptors = append(psDescriptors, s) + } + } + } + + jailName, err := c.jailName() + if err != nil { + return nil, fmt.Errorf("getting jail name: %w", err) + } + + args := []string{ + "-J", + jailName, + } + args = append(args, psDescriptors...) + + output, err := execPS(args) + if err != nil { + return nil, fmt.Errorf("executing ps(1): %w", err) + } + + return output, nil +} + +func execPS(args []string) ([]string, error) { + cmd := exec.Command("ps", args...) + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + stderrPipe, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + + var wg sync.WaitGroup + wg.Add(2) + stdout := []string{} + go func() { + scanner := bufio.NewScanner(stdoutPipe) + for scanner.Scan() { + stdout = append(stdout, scanner.Text()) + } + wg.Done() + }() + stderr := []string{} + go func() { + scanner := bufio.NewScanner(stderrPipe) + for scanner.Scan() { + stderr = append(stderr, scanner.Text()) + } + wg.Done() + }() + + if err := cmd.Start(); err != nil { + return nil, err + } + wg.Wait() + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("ps(1) command failed: %w, output: %s", err, strings.Join(stderr, " ")) + } + + return stdout, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_top_linux.c b/vendor/github.com/containers/podman/v5/libpod/container_top_linux.c new file mode 100644 index 00000000..0988a7a1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_top_linux.c @@ -0,0 +1,110 @@ +//go:build !remote + + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +/* keep special_exit_code in sync with container_top_linux.go */ +int special_exit_code = 255; +int join_userns = 0; +char **argv = NULL; + +void +create_argv (int len) +{ + /* allocate one extra element because we need a final NULL in c */ + argv = malloc (sizeof (char *) * (len + 1)); + if (argv == NULL) + { + fprintf (stderr, "failed to allocate ps argv"); + exit (special_exit_code); + } + /* add final NULL */ + argv[len] = NULL; +} + +void +set_argv (int pos, char *arg) +{ + argv[pos] = arg; +} + +void +set_userns () +{ + join_userns = 1; +} + +/* + We use cgo code here so we can fork then exec separately, + this is done so we can mount proc after the fork because the pid namespace is + only active after spawning children. +*/ +void +fork_exec_ps () +{ + int r, status = 0; + pid_t pid; + + if (argv == NULL) + { + fprintf (stderr, "argv not initialized"); + exit (special_exit_code); + } + + pid = fork (); + if (pid < 0) + { + fprintf (stderr, "fork: %m"); + exit (special_exit_code); + } + if (pid == 0) + { + r = mount ("proc", "/proc", "proc", 0, NULL); + if (r < 0) + { + fprintf (stderr, "mount proc: %m"); + exit (special_exit_code); + } + if (join_userns) + { + // join the userns to make sure uid mapping match + // we are already part of the pidns so so pid 1 is the main container process + r = open ("/proc/1/ns/user", O_CLOEXEC | O_RDONLY); + if (r < 0) + { + fprintf (stderr, "open /proc/1/ns/user: %m"); + exit (special_exit_code); + } + if ((status = setns (r, CLONE_NEWUSER)) < 0) + { + fprintf (stderr, "setns NEWUSER: %m"); + exit (special_exit_code); + } + } + + /* use execve to unset all env vars, we do not want to leak anything into the container */ + execve (argv[0], argv, NULL); + fprintf (stderr, "execve: %m"); + exit (special_exit_code); + } + + r = waitpid (pid, &status, 0); + if (r < 0) + { + fprintf (stderr, "waitpid: %m"); + exit (special_exit_code); + } + if (WIFEXITED (status)) + exit (WEXITSTATUS (status)); + if (WIFSIGNALED (status)) + exit (128 + WTERMSIG (status)); + exit (special_exit_code); +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_top_linux.go b/vendor/github.com/containers/podman/v5/libpod/container_top_linux.go new file mode 100644 index 00000000..a1698162 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_top_linux.go @@ -0,0 +1,421 @@ +//go:build !remote && linux && cgo + +package libpod + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "slices" + "strconv" + "strings" + "syscall" + "unsafe" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/psgo" + "github.com/google/shlex" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/reexec" + "golang.org/x/sys/unix" +) + +/* +#include +void fork_exec_ps(); +void create_argv(int len); +void set_argv(int pos, char *arg); +void set_userns(); +*/ +import "C" + +const ( + // podmanTopCommand is the reexec key to safely setup the environment for ps to be executed + podmanTopCommand = "podman-top" + + // podmanTopExitCode is a special exec code to signal that podman failed to to something in + // reexec command not ps. This is used to give a better error. + podmanTopExitCode = 255 +) + +func init() { + reexec.Register(podmanTopCommand, podmanTopMain) +} + +// podmanTopMain - main function for the reexec +func podmanTopMain() { + if err := podmanTopInner(); err != nil { + fmt.Fprint(os.Stderr, err.Error()) + os.Exit(podmanTopExitCode) + } + os.Exit(0) +} + +// podmanTopInner os.Args = {command name} {pid} {userns(1/0)} {psPath} [args...] +// We are rexxec'd in a new mountns, then we need to set some security settings in order +// to safely execute ps in the container pid namespace. Most notably make sure podman and +// ps are read only to prevent a process from overwriting it. +func podmanTopInner() error { + if len(os.Args) < 4 { + return fmt.Errorf("internal error, need at least three arguments") + } + + // We have to lock the thread as we a) switch namespace below and b) use PR_SET_PDEATHSIG + // Also do not unlock as this thread should not be reused by go we exit anyway at the end. + runtime.LockOSThread() + + if err := unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(unix.SIGKILL), 0, 0, 0); err != nil { + return fmt.Errorf("PR_SET_PDEATHSIG: %w", err) + } + if err := unix.Prctl(unix.PR_SET_DUMPABLE, 0, 0, 0, 0); err != nil { + return fmt.Errorf("PR_SET_DUMPABLE: %w", err) + } + + if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil { + return fmt.Errorf("PR_SET_NO_NEW_PRIVS: %w", err) + } + + if err := unix.Mount("none", "/", "", unix.MS_REC|unix.MS_PRIVATE, ""); err != nil { + return fmt.Errorf("make / mount private: %w", err) + } + + psPath := os.Args[3] + + // try to mount everything read only + if err := unix.MountSetattr(0, "/", unix.AT_RECURSIVE, &unix.MountAttr{ + Attr_set: unix.MOUNT_ATTR_RDONLY, + }); err != nil { + if err != unix.ENOSYS { + return fmt.Errorf("mount_setattr / readonly: %w", err) + } + // old kernel without mount_setattr, i.e. on RHEL 8.8 + // Bind mount the directories readonly for both podman and ps. + psPath, err = remountReadOnly(psPath) + if err != nil { + return err + } + _, err = remountReadOnly(reexec.Self()) + if err != nil { + return err + } + } + + // extra safety check make sure the ps path is actually read only + err := unix.Access(psPath, unix.W_OK) + if err == nil { + return fmt.Errorf("%q was not mounted read only, this can be dangerous so we will not execute it", psPath) + } + + pid := os.Args[1] + // join the pid namespace of pid + pidFD, err := os.Open(fmt.Sprintf("/proc/%s/ns/pid", pid)) + if err != nil { + return fmt.Errorf("open pidns: %w", err) + } + if err := unix.Setns(int(pidFD.Fd()), unix.CLONE_NEWPID); err != nil { + return fmt.Errorf("setns NEWPID: %w", err) + } + pidFD.Close() + + userns := os.Args[2] + if userns == "1" { + C.set_userns() + } + + args := []string{psPath} + args = append(args, os.Args[4:]...) + + C.create_argv(C.int(len(args))) + for i, arg := range args { + cArg := C.CString(arg) + C.set_argv(C.int(i), cArg) + defer C.free(unsafe.Pointer(cArg)) + } + + // Now try to close open fds except std streams + // While golang open everything O_CLOEXEC it could still leak fds from + // the parent, i.e. bash. In this case an attacker might be able to + // read/write from them. + // Do this as last step, it has to happen before to fork because the child + // will be immediately in pid namespace so we cannot close them in the child. + entries, err := os.ReadDir("/proc/self/fd") + if err != nil { + return err + } + for _, e := range entries { + i, err := strconv.Atoi(e.Name()) + // IsFdInherited checks the we got the fd from a parent process and only close them, + // when we close all that would include the ones from the go runtime which + // then can panic because of that. + if err == nil && i > unix.Stderr && rootless.IsFdInherited(i) { + _ = unix.Close(i) + } + } + + // this function will always exit for us + C.fork_exec_ps() + return nil +} + +// remountReadOnly remounts the parent directory of the given path read only +// return the resolved path or an error. The path can then be used to exec the +// binary as we know it is on a read only mount now. +func remountReadOnly(path string) (string, error) { + resolvedPath, err := filepath.EvalSymlinks(path) + if err != nil { + return "", fmt.Errorf("resolve symlink for %s: %w", path, err) + } + dir := filepath.Dir(resolvedPath) + // create mount point + if err := unix.Mount(dir, dir, "", unix.MS_BIND, ""); err != nil { + return "", fmt.Errorf("mount %s read only: %w", dir, err) + } + // remount readonly + if err := unix.Mount(dir, dir, "", unix.MS_BIND|unix.MS_REMOUNT|unix.MS_RDONLY, ""); err != nil { + return "", fmt.Errorf("mount %s read only: %w", dir, err) + } + return resolvedPath, nil +} + +// Top gathers statistics about the running processes in a container. It returns a +// []string for output +func (c *Container) Top(descriptors []string) ([]string, error) { + if c.config.NoCgroups { + return nil, fmt.Errorf("cannot run top on container %s as it did not create a cgroup: %w", c.ID(), define.ErrNoCgroups) + } + + conStat, err := c.State() + if err != nil { + return nil, fmt.Errorf("unable to look up state for %s: %w", c.ID(), err) + } + if conStat != define.ContainerStateRunning { + return nil, errors.New("top can only be used on running containers") + } + + // Also support comma-separated input. + psgoDescriptors := []string{} + for _, d := range descriptors { + for s := range strings.SplitSeq(d, ",") { + if s != "" { + psgoDescriptors = append(psgoDescriptors, s) + } + } + } + + // If we encountered an ErrUnknownDescriptor error, fallback to executing + // ps(1). This ensures backwards compatibility to users depending on ps(1) + // and makes sure we're ~compatible with docker. + output, psgoErr := c.GetContainerPidInformation(psgoDescriptors) + if psgoErr == nil { + return output, nil + } + if !errors.Is(psgoErr, psgo.ErrUnknownDescriptor) { + return nil, psgoErr + } + + psDescriptors := descriptors + if len(descriptors) == 1 { + // Note that the descriptors to ps(1) must be shlexed (see #12452). + psDescriptors = make([]string, 0, len(descriptors)) + shSplit, err := shlex.Split(descriptors[0]) + if err != nil { + return nil, fmt.Errorf("parsing ps args: %w", err) + } + for _, s := range shSplit { + if s != "" { + psDescriptors = append(psDescriptors, s) + } + } + } + + // Only use ps(1) from the host when we know the container was not started with CAP_SYS_PTRACE, + // with it the container can access /proc/$pid/ files and potentially escape the container fs. + if c.config.Spec.Process.Capabilities != nil && + !slices.Contains(c.config.Spec.Process.Capabilities.Effective, "CAP_SYS_PTRACE") { + var retry bool + output, retry, err = c.execPS(psDescriptors) + if err != nil { + if !retry { + return nil, err + } + logrus.Warnf("Falling back to container ps(1), could not execute ps(1) from the host: %v", err) + output, err = c.execPSinContainer(psDescriptors) + if err != nil { + return nil, fmt.Errorf("executing ps(1) in container: %w", err) + } + } + } else { + output, err = c.execPSinContainer(psDescriptors) + if err != nil { + return nil, fmt.Errorf("executing ps(1) in container: %w", err) + } + } + + // Trick: filter the ps command from the output instead of + // checking/requiring PIDs in the output. + filtered := []string{} + cmd := strings.Join(descriptors, " ") + for _, line := range output { + if !strings.Contains(line, cmd) { + filtered = append(filtered, line) + } + } + + return filtered, nil +} + +// GetContainerPidInformation returns process-related data of all processes in +// the container. The output data can be controlled via the `descriptors` +// argument which expects format descriptors and supports all AIXformat +// descriptors of ps (1) plus some additional ones to for instance inspect the +// set of effective capabilities. Each element in the returned string slice +// is a tab-separated string. +// +// For more details, please refer to github.com/containers/psgo. +func (c *Container) GetContainerPidInformation(descriptors []string) ([]string, error) { + pid := strconv.Itoa(c.state.PID) + // NOTE: psgo returns a [][]string to give users the ability to apply + // filters on the data. We need to change the API here + // to return a [][]string if we want to make use of + // filtering. + opts := psgo.JoinNamespaceOpts{FillMappings: rootless.IsRootless()} + + psgoOutput, err := psgo.JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, &opts) + if err != nil { + return nil, err + } + res := []string{} + for _, out := range psgoOutput { + res = append(res, strings.Join(out, "\t")) + } + return res, nil +} + +// execute ps(1) from the host within the container pid namespace +func (c *Container) execPS(psArgs []string) ([]string, bool, error) { + rPipe, wPipe, err := os.Pipe() + if err != nil { + return nil, false, err + } + defer rPipe.Close() + + outErrChan := make(chan error) + stdout := []string{} + go func() { + defer close(outErrChan) + scanner := bufio.NewScanner(rPipe) + for scanner.Scan() { + stdout = append(stdout, scanner.Text()) + } + if err := scanner.Err(); err != nil { + outErrChan <- err + } + }() + + psPath, err := exec.LookPath("ps") + if err != nil { + wPipe.Close() + return nil, true, err + } + + // see podmanTopInner() + userns := "0" + if len(c.config.IDMappings.UIDMap) > 0 { + userns = "1" + } + + args := append([]string{podmanTopCommand, strconv.Itoa(c.state.PID), userns, psPath}, psArgs...) + + cmd := reexec.Command(args...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Unshareflags: unix.CLONE_NEWNS, + } + var errBuf bytes.Buffer + cmd.Stdout = wPipe + cmd.Stderr = &errBuf + // nil means use current env so explicitly unset all, to not leak any sensitive env vars + cmd.Env = []string{fmt.Sprintf("HOME=%s", os.Getenv("HOME"))} + + retryContainerExec := true + err = cmd.Run() + wPipe.Close() + if err != nil { + exitError := &exec.ExitError{} + if errors.As(err, &exitError) { + if exitError.ExitCode() != podmanTopExitCode { + // ps command failed + err = fmt.Errorf("ps(1) failed with exit code %d: %s", exitError.ExitCode(), errBuf.String()) + // ps command itself failed: likely invalid args, no point in retrying. + retryContainerExec = false + } else { + // podman-top reexec setup fails somewhere + err = fmt.Errorf("could not execute ps(1) in the container pid namespace: %s", errBuf.String()) + } + } else { + err = fmt.Errorf("could not reexec podman-top command: %w", err) + } + } + + if err := <-outErrChan; err != nil { + return nil, retryContainerExec, fmt.Errorf("failed to read ps stdout: %w", err) + } + return stdout, retryContainerExec, err +} + +// execPS executes ps(1) with the specified args in the container via exec session. +// This should be a bit safer then execPS() but it requires ps(1) to be installed in the container. +func (c *Container) execPSinContainer(args []string) ([]string, error) { + rPipe, wPipe, err := os.Pipe() + if err != nil { + return nil, err + } + defer rPipe.Close() + + var errBuf bytes.Buffer + streams := new(define.AttachStreams) + streams.OutputStream = wPipe + streams.ErrorStream = &errBuf + streams.AttachOutput = true + streams.AttachError = true + + outErrChan := make(chan error) + stdout := []string{} + go func() { + defer close(outErrChan) + scanner := bufio.NewScanner(rPipe) + for scanner.Scan() { + stdout = append(stdout, scanner.Text()) + } + if err := scanner.Err(); err != nil { + outErrChan <- err + } + }() + + cmd := append([]string{"ps"}, args...) + config := new(ExecConfig) + config.Command = cmd + ec, err := c.Exec(config, streams, nil) + wPipe.Close() + if err != nil { + return nil, err + } else if ec != 0 { + return nil, fmt.Errorf("runtime failed with exit status: %d and output: %s", ec, errBuf.String()) + } + + if logrus.GetLevel() >= logrus.DebugLevel { + // If we're running in debug mode or higher, we might want to have a + // look at stderr which includes debug logs from conmon. + logrus.Debug(errBuf.String()) + } + + if err := <-outErrChan; err != nil { + return nil, fmt.Errorf("failed to read ps stdout: %w", err) + } + return stdout, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_top_unsupported.go b/vendor/github.com/containers/podman/v5/libpod/container_top_unsupported.go new file mode 100644 index 00000000..1c47b69d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_top_unsupported.go @@ -0,0 +1,13 @@ +//go:build !remote && !(linux && cgo) && !freebsd + +package libpod + +import ( + "errors" +) + +// Top gathers statistics about the running processes in a container. It returns a +// []string for output +func (c *Container) Top(descriptors []string) ([]string, error) { + return nil, errors.New("not implemented (*Container) Top") +} diff --git a/vendor/github.com/containers/podman/v5/libpod/container_validate.go b/vendor/github.com/containers/podman/v5/libpod/container_validate.go new file mode 100644 index 00000000..f3fa222f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/container_validate.go @@ -0,0 +1,209 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "strings" + + "github.com/containers/podman/v5/libpod/define" + spec "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/image/v5/docker" + "go.podman.io/image/v5/pkg/shortnames" + "go.podman.io/image/v5/transports/alltransports" +) + +// Validate that the configuration of a container is valid. +func (c *Container) validate() error { + imageIDSet := c.config.RootfsImageID != "" + imageNameSet := c.config.RootfsImageName != "" + rootfsSet := c.config.Rootfs != "" + + // If one of RootfsImageIDor RootfsImageName are set, both must be set. + if (imageIDSet || imageNameSet) && (!imageIDSet || !imageNameSet) { + return fmt.Errorf("both RootfsImageName and RootfsImageID must be set if either is set: %w", define.ErrInvalidArg) + } + + // Cannot set RootfsImageID and Rootfs at the same time + if imageIDSet && rootfsSet { + return fmt.Errorf("cannot set both an image ID and rootfs for a container: %w", define.ErrInvalidArg) + } + + // Must set at least one of RootfsImageID or Rootfs + if !imageIDSet && !rootfsSet { + return fmt.Errorf("must set root filesystem source to either image or rootfs: %w", define.ErrInvalidArg) + } + + // A container cannot be marked as an infra and service container at + // the same time. + if c.IsInfra() && c.IsService() { + return fmt.Errorf("cannot be infra and service container at the same time: %w", define.ErrInvalidArg) + } + + // Cannot make a network namespace if we are joining another container's + // network namespace + if c.config.CreateNetNS && c.config.NetNsCtr != "" { + return fmt.Errorf("cannot both create a network namespace and join another container's network namespace: %w", define.ErrInvalidArg) + } + + if c.config.CgroupsMode == cgroupSplit && c.config.CgroupParent != "" { + return fmt.Errorf("cannot specify --cgroup-mode=split with a cgroup-parent: %w", define.ErrInvalidArg) + } + + // Not creating cgroups has a number of requirements, mostly related to + // the PID namespace. + if c.config.NoCgroups || c.config.CgroupsMode == "disabled" { + if c.config.PIDNsCtr != "" { + return fmt.Errorf("cannot join another container's PID namespace if not creating cgroups: %w", define.ErrInvalidArg) + } + + if c.config.CgroupParent != "" { + return fmt.Errorf("cannot set cgroup parent if not creating cgroups: %w", define.ErrInvalidArg) + } + + // Ensure we have a PID namespace + if c.config.Spec.Linux == nil { + return fmt.Errorf("must provide Linux namespace configuration in OCI spec when using NoCgroups: %w", define.ErrInvalidArg) + } + foundPid := false + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.PIDNamespace { + foundPid = true + if ns.Path != "" { + return fmt.Errorf("containers not creating Cgroups must create a private PID namespace - cannot use another: %w", define.ErrInvalidArg) + } + break + } + } + if !foundPid { + return fmt.Errorf("containers not creating Cgroups must create a private PID namespace: %w", define.ErrInvalidArg) + } + } + + // Can only set static IP or MAC is creating a network namespace. + if !c.config.CreateNetNS && (c.config.StaticIP != nil || c.config.StaticMAC != nil) { + return fmt.Errorf("cannot set static IP or MAC address if not creating a network namespace: %w", define.ErrInvalidArg) + } + + // Cannot set static IP or MAC if joining >1 network. + if len(c.config.Networks) > 1 && (c.config.StaticIP != nil || c.config.StaticMAC != nil) { + return fmt.Errorf("cannot set static IP or MAC address if joining more than one network: %w", define.ErrInvalidArg) + } + + // Using image resolv.conf conflicts with various DNS settings. + if c.config.UseImageResolvConf && + (len(c.config.DNSSearch) > 0 || len(c.config.DNSServer) > 0 || + len(c.config.DNSOption) > 0) { + return fmt.Errorf("cannot configure DNS options if using image's resolv.conf: %w", define.ErrInvalidArg) + } + + if c.config.UseImageHosts && len(c.config.HostAdd) > 0 { + return fmt.Errorf("cannot add to /etc/hosts if using image's /etc/hosts: %w", define.ErrInvalidArg) + } + + // Check named volume, overlay volume and image volume destination conflist + destinations := make(map[string]bool) + for _, vol := range c.config.NamedVolumes { + // Don't check if they already exist. + // If they don't we will automatically create them. + if _, ok := destinations[vol.Dest]; ok { + return fmt.Errorf("two volumes found with destination %s: %w", vol.Dest, define.ErrInvalidArg) + } + destinations[vol.Dest] = true + } + for _, vol := range c.config.OverlayVolumes { + // Don't check if they already exist. + // If they don't we will automatically create them. + if _, ok := destinations[vol.Dest]; ok { + return fmt.Errorf("two volumes found with destination %s: %w", vol.Dest, define.ErrInvalidArg) + } + destinations[vol.Dest] = true + } + for _, vol := range c.config.ImageVolumes { + // Don't check if they already exist. + // If they don't we will automatically create them. + if _, ok := destinations[vol.Dest]; ok { + return fmt.Errorf("two volumes found with destination %s: %w", vol.Dest, define.ErrInvalidArg) + } + destinations[vol.Dest] = true + } + + // If User in the OCI spec is set, require that c.config.User is set for + // security reasons (a lot of our code relies on c.config.User). + if c.config.User == "" && (c.config.Spec.Process.User.UID != 0 || c.config.Spec.Process.User.GID != 0) { + return fmt.Errorf("please set User explicitly via WithUser() instead of in OCI spec directly: %w", define.ErrInvalidArg) + } + + // Init-ctrs must be used inside a Pod. Check if an init container type is + // passed and if no pod is passed + if len(c.config.InitContainerType) > 0 && len(c.config.Pod) < 1 { + return fmt.Errorf("init containers must be created in a pod: %w", define.ErrInvalidArg) + } + + if c.config.SdNotifyMode == define.SdNotifyModeIgnore && len(c.config.SdNotifySocket) > 0 { + return fmt.Errorf("cannot set sd-notify socket %q with sd-notify mode %q", c.config.SdNotifySocket, c.config.SdNotifyMode) + } + + if c.config.HealthCheckOnFailureAction != define.HealthCheckOnFailureActionNone && c.config.HealthCheckConfig == nil { + return fmt.Errorf("cannot set on-failure action to %s without a health check", c.config.HealthCheckOnFailureAction.String()) + } + + if value, exists := c.config.Labels[define.AutoUpdateLabel]; exists { + // TODO: we cannot reference pkg/autoupdate here due to + // circular dependencies. It's worth considering moving the + // auto-update logic into the libpod package. + if value == "registry" || value == "image" { + if err := validateAutoUpdateImageReference(c.config.RawImageName); err != nil { + return err + } + } + } + + // Autoremoving image requires autoremoving the associated container + if c.config.Spec.Annotations != nil { + if c.config.Spec.Annotations[define.InspectAnnotationAutoremoveImage] == define.InspectResponseTrue { + if c.config.Spec.Annotations[define.InspectAnnotationAutoremove] != define.InspectResponseTrue { + return fmt.Errorf("autoremoving image requires autoremoving the container: %w", define.ErrInvalidArg) + } + if c.config.Rootfs != "" { + return fmt.Errorf("autoremoving image is not possible when a rootfs is in use: %w", define.ErrInvalidArg) + } + } + } + + // Cannot set startup HC without a healthcheck + if c.config.HealthCheckConfig == nil && c.config.StartupHealthCheckConfig != nil { + return fmt.Errorf("cannot set a startup healthcheck when there is no regular healthcheck: %w", define.ErrInvalidArg) + } + + // Ensure all ports list a single protocol + for _, p := range c.config.PortMappings { + if strings.Contains(p.Protocol, ",") { + return fmt.Errorf("each port mapping must define a single protocol, got a comma-separated list for container port %d (protocols requested %q): %w", p.ContainerPort, p.Protocol, define.ErrInvalidArg) + } + } + + if c.config.IsDefaultInfra && !c.config.IsInfra { + return fmt.Errorf("default rootfs-based infra container is set for non-infra container") + } + + return nil +} + +// validateAutoUpdateImageReference checks if the specified imageName is a +// fully-qualified image reference to the docker transport. Such a reference +// includes a domain, name and tag (e.g., quay.io/podman/stable:latest). The +// reference may also be prefixed with "docker://" explicitly indicating that +// it's a reference to the docker transport. +func validateAutoUpdateImageReference(imageName string) error { + // Make sure the input image is a docker. + imageRef, err := alltransports.ParseImageName(imageName) + if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { + return fmt.Errorf("auto updates require the docker image transport but image is of transport %q", imageRef.Transport().Name()) + } else if err != nil { + if shortnames.IsShortName(imageName) { + return fmt.Errorf("short name: auto updates require fully-qualified image reference: %q", imageName) + } + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/annotations.go b/vendor/github.com/containers/podman/v5/libpod/define/annotations.go new file mode 100644 index 00000000..b743c605 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/annotations.go @@ -0,0 +1,195 @@ +package define + +const ( + // RunOCIMountContextType tells the OCI runtime which context mount + // type to use. context, rootcontext, fscontext, defcontext + RunOCIMountContextType = "run.oci.mount_context_type" + // RunOCIKeepOriginalGroups tells the OCI runtime to leak the users + // current groups into the container + RunOCIKeepOriginalGroups = "run.oci.keep_original_groups" + // InspectAnnotationCIDFile is used by Inspect to determine if a + // container ID file was created for the container. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationCIDFile = "io.podman.annotations.cid-file" + // InspectAnnotationAutoremove is used by Inspect to determine if a + // container will be automatically removed on exit. + // If an annotation with this key is found in the OCI spec and is one of + // the two supported boolean values (InspectResponseTrue and + // InspectResponseFalse) it will be used in the output of Inspect(). + InspectAnnotationAutoremove = "io.podman.annotations.autoremove" + // InspectAnnotationAutoremoveImage is used by Inspect to identify + // containers which will automatically remove the image used by the + // container. If an annotation with this key is found in the OCI spec and + // is one of the two supported boolean values (InspectResponseTrue and + // InspectResponseFalse) it will be used in the output of Inspect(). + InspectAnnotationAutoremoveImage = "io.podman.annotations.autoremove-image" + // InspectAnnotationPrivileged is used by Inspect to identify containers + // which are privileged (IE, running with elevated privileges). + // It is expected to be a boolean, populated by one of + // InspectResponseTrue or InspectResponseFalse. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationPrivileged = "io.podman.annotations.privileged" + // InspectAnnotationPublishAll is used by Inspect to identify containers + // which have all the ports from their image published. + // It is expected to be a boolean, populated by one of + // InspectResponseTrue or InspectResponseFalse. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationPublishAll = "io.podman.annotations.publish-all" + // InspectAnnotationInit is used by Inspect to identify containers that + // mount an init binary in. + // It is expected to be a boolean, populated by one of + // InspectResponseTrue or InspectResponseFalse. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationInit = "io.podman.annotations.init" + // InspectAnnotationLabel is used by Inspect to identify containers with + // special SELinux-related settings. It is used to populate the output + // of the SecurityOpt setting. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationLabel = "io.podman.annotations.label" + // InspectAnnotationSeccomp is used by Inspect to identify containers + // with special Seccomp-related settings. It is used to populate the + // output of the SecurityOpt setting in Inspect. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationSeccomp = "io.podman.annotations.seccomp" + // InspectAnnotationApparmor is used by Inspect to identify containers + // with special Apparmor-related settings. It is used to populate the + // output of the SecurityOpt setting. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationApparmor = "io.podman.annotations.apparmor" + // InspectResponseTrue is a boolean True response for an inspect + // annotation. + InspectResponseTrue = "TRUE" + // InspectResponseFalse is a boolean False response for an inspect + // annotation. + InspectResponseFalse = "FALSE" + + // CheckpointAnnotationName is used by Container Checkpoint when creating a + // checkpoint image to specify the original human-readable name for the + // container. + CheckpointAnnotationName = "io.podman.annotations.checkpoint.name" + + // CheckpointAnnotationRawImageName is used by Container Checkpoint when + // creating a checkpoint image to specify the original unprocessed name of + // the image used to create the container (as specified by the user). + CheckpointAnnotationRawImageName = "io.podman.annotations.checkpoint.rawImageName" + + // CheckpointAnnotationRootfsImageID is used by Container Checkpoint when + // creating a checkpoint image to specify the original ID of the image used + // to create the container. + CheckpointAnnotationRootfsImageID = "io.podman.annotations.checkpoint.rootfsImageID" + + // CheckpointAnnotationRootfsImageName is used by Container Checkpoint when + // creating a checkpoint image to specify the original image name used to + // create the container. + CheckpointAnnotationRootfsImageName = "io.podman.annotations.checkpoint.rootfsImageName" + + // CheckpointAnnotationPodmanVersion is used by Container Checkpoint when + // creating a checkpoint image to specify the version of Podman used on the + // host where the checkpoint was created. + CheckpointAnnotationPodmanVersion = "io.podman.annotations.checkpoint.podman.version" + + // CheckpointAnnotationCriuVersion is used by Container Checkpoint when + // creating a checkpoint image to specify the version of CRIU used on the + // host where the checkpoint was created. + CheckpointAnnotationCriuVersion = "io.podman.annotations.checkpoint.criu.version" + + // CheckpointAnnotationRuntimeName is used by Container Checkpoint when + // creating a checkpoint image to specify the runtime used on the host where + // the checkpoint was created. + CheckpointAnnotationRuntimeName = "io.podman.annotations.checkpoint.runtime.name" + + // CheckpointAnnotationRuntimeVersion is used by Container Checkpoint when + // creating a checkpoint image to specify the version of runtime used on the + // host where the checkpoint was created. + CheckpointAnnotationRuntimeVersion = "io.podman.annotations.checkpoint.runtime.version" + + // CheckpointAnnotationConmonVersion is used by Container Checkpoint when + // creating a checkpoint image to specify the version of conmon used on + // the host where the checkpoint was created. + CheckpointAnnotationConmonVersion = "io.podman.annotations.checkpoint.conmon.version" + + // CheckpointAnnotationHostArch is used by Container Checkpoint when + // creating a checkpoint image to specify the CPU architecture of the host + // on which the checkpoint was created. + CheckpointAnnotationHostArch = "io.podman.annotations.checkpoint.host.arch" + + // CheckpointAnnotationHostKernel is used by Container Checkpoint when + // creating a checkpoint image to specify the kernel version used by the + // host where the checkpoint was created. + CheckpointAnnotationHostKernel = "io.podman.annotations.checkpoint.host.kernel" + + // CheckpointAnnotationCgroupVersion is used by Container Checkpoint when + // creating a checkpoint image to specify the cgroup version used by the + // host where the checkpoint was created. + CheckpointAnnotationCgroupVersion = "io.podman.annotations.checkpoint.cgroups.version" + + // CheckpointAnnotationDistributionVersion is used by Container Checkpoint + // when creating a checkpoint image to specify the version of host + // distribution on which the checkpoint was created. + CheckpointAnnotationDistributionVersion = "io.podman.annotations.checkpoint.distribution.version" + + // CheckpointAnnotationDistributionName is used by Container Checkpoint when + // creating a checkpoint image to specify the name of host distribution on + // which the checkpoint was created. + CheckpointAnnotationDistributionName = "io.podman.annotations.checkpoint.distribution.name" + + // InitContainerType is used by play kube when playing a kube yaml to specify the type + // of the init container. + InitContainerType = "io.podman.annotations.init.container.type" + + // InfraNameAnnotation is used by generate and play kube when the infra container is set by the user during + // pod creation + InfraNameAnnotation = "io.podman.annotations.infra.name" + + // UserNsAnnotation is used by play kube when playing a kube yaml to specify userns + // of the container + UserNsAnnotation = "io.podman.annotations.userns" + + // UlimitAnnotation is used by kube play when playing a kube yaml to specify the ulimits + // of the container + UlimitAnnotation = "io.podman.annotations.ulimit" + + // VolumesFromAnnotation is used by by play kube when playing a kube + // yaml to specify volumes-from of the container + // It is expected to be a semicolon-separated list of container names and/or + // IDs optionally with colon separated mount options. + VolumesFromAnnotation = "io.podman.annotations.volumes-from" + + // KubeHealthCheckAnnotation is used by kube play to tell podman that any health checks should follow + // the k8s behavior of waiting for the intialDelaySeconds to be over before updating the status + KubeHealthCheckAnnotation = "io.podman.annotations.kube.health.check" + + // KubeImageAutomountAnnotation + KubeImageAutomountAnnotation = "io.podman.annotations.kube.image.volumes.mount" + + // PIDsLimitAnnotation is used to limit the number of PIDs + PIDsLimitAnnotation = "io.podman.annotations.pids-limit" + + // CpusetAnnotation is used to restrict execution to specific CPU cores + CpusetAnnotation = "io.podman.annotations.cpuset" + + // MemoryNodesAnnotation is used to restrict memory allocations to specific memory nodes on NUMA systems + MemoryNodesAnnotation = "io.podman.annotations.memory-nodes" + + // TotalAnnotationSizeLimitB is the max length of annotations allowed by Kubernetes. + TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB +) + +// IsReservedAnnotation returns true if the specified value corresponds to an +// already reserved annotation that Podman sets during container creation. +func IsReservedAnnotation(value string) bool { + switch value { + case InspectAnnotationCIDFile, InspectAnnotationAutoremove, InspectAnnotationPrivileged, InspectAnnotationPublishAll, InspectAnnotationInit, InspectAnnotationLabel, InspectAnnotationSeccomp, InspectAnnotationApparmor, InspectResponseTrue, InspectResponseFalse, VolumesFromAnnotation: + return true + + default: + return false + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/autoupdate.go b/vendor/github.com/containers/podman/v5/libpod/define/autoupdate.go new file mode 100644 index 00000000..7c278c3c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/autoupdate.go @@ -0,0 +1,9 @@ +package define + +// AutoUpdateLabel denotes the container/pod label key to specify auto-update +// policies in container labels. +const AutoUpdateLabel = "io.containers.autoupdate" + +// AutoUpdateAuthfileLabel denotes the container label key to specify authfile +// in container labels. +const AutoUpdateAuthfileLabel = "io.containers.autoupdate.authfile" diff --git a/vendor/github.com/containers/podman/v5/libpod/define/checkpoint_restore.go b/vendor/github.com/containers/podman/v5/libpod/define/checkpoint_restore.go new file mode 100644 index 00000000..536bdde9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/checkpoint_restore.go @@ -0,0 +1,32 @@ +package define + +// This contains values reported by CRIU during +// checkpointing or restoring. +// All names are the same as reported by CRIU. +type CRIUCheckpointRestoreStatistics struct { + // Checkpoint values + // Time required to freeze/pause/quiesce the processes + FreezingTime uint32 `json:"freezing_time,omitempty"` + // Time the processes are actually not running during checkpointing + FrozenTime uint32 `json:"frozen_time,omitempty"` + // Time required to extract memory pages from the processes + MemdumpTime uint32 `json:"memdump_time,omitempty"` + // Time required to write memory pages to disk + MemwriteTime uint32 `json:"memwrite_time,omitempty"` + // Number of memory pages CRIU analyzed + PagesScanned uint64 `json:"pages_scanned,omitempty"` + // Number of memory pages written + PagesWritten uint64 `json:"pages_written,omitempty"` + + // Restore values + // Number of pages compared during restore + PagesCompared uint64 `json:"pages_compared,omitempty"` + // Number of COW pages skipped during restore + PagesSkippedCow uint64 `json:"pages_skipped_cow,omitempty"` + // Time required to fork processes + ForkingTime uint32 `json:"forking_time,omitempty"` + // Time required to restore + RestoreTime uint32 `json:"restore_time,omitempty"` + // Number of memory pages restored + PagesRestored uint64 `json:"pages_restored,omitempty"` +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/config.go b/vendor/github.com/containers/podman/v5/libpod/define/config.go new file mode 100644 index 00000000..8f419763 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/config.go @@ -0,0 +1,92 @@ +package define + +import ( + "bufio" + "io" + + "go.podman.io/common/libnetwork/types" +) + +var ( + // DefaultSHMLockPath is the default path for SHM locks + DefaultSHMLockPath = "/libpod_lock" + // DefaultRootlessSHMLockPath is the default path for rootless SHM locks + DefaultRootlessSHMLockPath = "/libpod_rootless_lock" + + // NameRegex is a regular expression to validate container/pod names. + // This must NOT be changed from outside of Libpod. It should be a + // constant, but Go won't let us do that. + NameRegex = types.NameRegex + // RegexError is thrown in presence of an invalid container/pod name. + RegexError = types.ErrInvalidName +) + +const ( + // DefaultTransport is a prefix that we apply to an image name + // to check docker hub first for the image + DefaultTransport = "docker://" +) + +// InfoData holds the info type, i.e store, host etc and the data for each type +type InfoData struct { + Type string + Data map[string]any +} + +// VolumeDriverLocal is the "local" volume driver. It is managed by libpod +// itself. +const VolumeDriverLocal = "local" + +// VolumeDriverImage is the "image" volume driver. It is managed by Libpod and +// uses volumes backed by an image. +const VolumeDriverImage = "image" + +const ( + OCIManifestDir = "oci-dir" + OCIArchive = "oci-archive" + V2s2ManifestDir = "docker-dir" + V2s2Archive = "docker-archive" +) + +// AttachStreams contains streams that will be attached to the container +type AttachStreams struct { + // OutputStream will be attached to container's STDOUT + OutputStream io.Writer + // ErrorStream will be attached to container's STDERR + ErrorStream io.Writer + // InputStream will be attached to container's STDIN + InputStream *bufio.Reader + // AttachOutput is whether to attach to STDOUT + // If false, stdout will not be attached + AttachOutput bool + // AttachError is whether to attach to STDERR + // If false, stdout will not be attached + AttachError bool + // AttachInput is whether to attach to STDIN + // If false, stdout will not be attached + AttachInput bool +} + +// JournaldLogging is the string conmon expects to specify journald logging +const JournaldLogging = "journald" + +// KubernetesLogging is the string conmon expects when specifying to use the kubernetes logging format +const KubernetesLogging = "k8s-file" + +// JSONLogging is the string conmon expects when specifying to use the json logging format +const JSONLogging = "json-file" + +// NoLogging is the string conmon expects when specifying to use no log driver whatsoever +const NoLogging = "none" + +// PassthroughLogging is the string conmon expects when specifying to use the passthrough driver +const PassthroughLogging = "passthrough" + +// PassthroughTTYLogging is the string conmon expects when specifying to use the passthrough driver even on a tty. +const PassthroughTTYLogging = "passthrough-tty" + +// DefaultRlimitValue is the value set by default for nofile and nproc +const RLimitDefaultValue = uint64(1048576) + +// BindMountPrefix distinguishes its annotations from others +const BindMountPrefix = "bind-mount-options" diff --git a/vendor/github.com/containers/podman/v5/libpod/define/container.go b/vendor/github.com/containers/podman/v5/libpod/define/container.go new file mode 100644 index 00000000..56cd225a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/container.go @@ -0,0 +1,176 @@ +package define + +import ( + "fmt" + + "github.com/opencontainers/runtime-spec/specs-go" +) + +// Valid restart policy types. +const ( + // RestartPolicyNone indicates that no restart policy has been requested + // by a container. + RestartPolicyNone = "" + // RestartPolicyNo is identical in function to RestartPolicyNone. + RestartPolicyNo = "no" + // RestartPolicyAlways unconditionally restarts the container. + RestartPolicyAlways = "always" + // RestartPolicyOnFailure restarts the container on non-0 exit code, + // with an optional maximum number of retries. + RestartPolicyOnFailure = "on-failure" + // RestartPolicyUnlessStopped unconditionally restarts unless stopped + // by the user. It is identical to Always except with respect to + // handling of system restart, which Podman does not yet support. + RestartPolicyUnlessStopped = "unless-stopped" +) + +// RestartPolicyMap maps between restart-policy valid values to restart policy types +var RestartPolicyMap = map[string]string{ + "none": RestartPolicyNone, + RestartPolicyNo: RestartPolicyNo, + RestartPolicyAlways: RestartPolicyAlways, + RestartPolicyOnFailure: RestartPolicyOnFailure, + RestartPolicyUnlessStopped: RestartPolicyUnlessStopped, +} + +// Validate that the given string is a valid restart policy. +func ValidateRestartPolicy(policy string) error { + switch policy { + case RestartPolicyNone, RestartPolicyNo, RestartPolicyOnFailure, RestartPolicyAlways, RestartPolicyUnlessStopped: + return nil + default: + return fmt.Errorf("%q is not a valid restart policy: %w", policy, ErrInvalidArg) + } +} + +// InitContainerTypes +const ( + // AlwaysInitContainer is an init container that runs on each + // pod start (including restart) + AlwaysInitContainer = "always" + // OneShotInitContainer is a container that only runs as init once + // and is then deleted. + OneShotInitContainer = "once" + // ContainerInitPath is the default path of the mounted container init. + ContainerInitPath = "/run/podman-init" +) + +// Kubernetes Kinds +const ( + // A Pod kube yaml spec + K8sKindPod = "pod" + // A Deployment kube yaml spec + K8sKindDeployment = "deployment" + // A DaemonSet kube yaml spec + K8sKindDaemonSet = "daemonset" + // a Job kube yaml spec + K8sKindJob = "job" +) + +type WeightDevice struct { + Path string + Weight uint16 +} + +type ThrottleDevice struct { + Path string + Rate uint64 +} + +type UpdateContainerDevicesLimits struct { + // Block IO weight (relative device weight) in the form: + // ```[{"Path": "device_path", "Weight": weight}]``` + BlkIOWeightDevice []WeightDevice `json:",omitempty"` + // Limit read rate (bytes per second) from a device, in the form: + // ```[{"Path": "device_path", "Rate": rate}]``` + DeviceReadBPs []ThrottleDevice `json:",omitempty"` + // Limit write rate (bytes per second) to a device, in the form: + // ```[{"Path": "device_path", "Rate": rate}]``` + DeviceWriteBPs []ThrottleDevice `json:",omitempty"` + // Limit read rate (IO per second) from a device, in the form: + // ```[{"Path": "device_path", "Rate": rate}]``` + DeviceReadIOPs []ThrottleDevice `json:",omitempty"` + // Limit write rate (IO per second) to a device, in the form: + // ```[{"Path": "device_path", "Rate": rate}]``` + DeviceWriteIOPs []ThrottleDevice `json:",omitempty"` +} + +func (d *WeightDevice) addToLinuxWeightDevice(wd map[string]specs.LinuxWeightDevice) { + wd[d.Path] = specs.LinuxWeightDevice{ + Weight: &d.Weight, + LeafWeight: nil, + } +} + +func LinuxWeightDeviceToWeightDevice(path string, d specs.LinuxWeightDevice) WeightDevice { + return WeightDevice{Path: path, Weight: *d.Weight} +} + +func (d *ThrottleDevice) addToLinuxThrottleDevice(td map[string]specs.LinuxThrottleDevice) { + td[d.Path] = specs.LinuxThrottleDevice{Rate: d.Rate} +} + +func LinuxThrottleDevicesToThrottleDevices(path string, d specs.LinuxThrottleDevice) ThrottleDevice { + return ThrottleDevice{Path: path, Rate: d.Rate} +} + +func (u *UpdateContainerDevicesLimits) SetBlkIOWeightDevice(wd map[string]specs.LinuxWeightDevice) { + for path, dev := range wd { + u.BlkIOWeightDevice = append(u.BlkIOWeightDevice, LinuxWeightDeviceToWeightDevice(path, dev)) + } +} + +func copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(source map[string]specs.LinuxThrottleDevice, dest []ThrottleDevice) []ThrottleDevice { + for path, dev := range source { + dest = append(dest, LinuxThrottleDevicesToThrottleDevices(path, dev)) + } + return dest +} + +func (u *UpdateContainerDevicesLimits) SetDeviceReadBPs(td map[string]specs.LinuxThrottleDevice) { + u.DeviceReadBPs = copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(td, u.DeviceReadBPs) +} + +func (u *UpdateContainerDevicesLimits) SetDeviceWriteBPs(td map[string]specs.LinuxThrottleDevice) { + u.DeviceWriteBPs = copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(td, u.DeviceWriteBPs) +} + +func (u *UpdateContainerDevicesLimits) SetDeviceReadIOPs(td map[string]specs.LinuxThrottleDevice) { + u.DeviceReadIOPs = copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(td, u.DeviceReadIOPs) +} + +func (u *UpdateContainerDevicesLimits) SetDeviceWriteIOPs(td map[string]specs.LinuxThrottleDevice) { + u.DeviceWriteIOPs = copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(td, u.DeviceWriteIOPs) +} + +func (u *UpdateContainerDevicesLimits) GetMapOfLinuxWeightDevice() map[string]specs.LinuxWeightDevice { + wd := make(map[string]specs.LinuxWeightDevice) + for _, dev := range u.BlkIOWeightDevice { + dev.addToLinuxWeightDevice(wd) + } + return wd +} + +func getMapOfLinuxThrottleDevices(source []ThrottleDevice) map[string]specs.LinuxThrottleDevice { + td := make(map[string]specs.LinuxThrottleDevice) + for _, dev := range source { + dev.addToLinuxThrottleDevice(td) + } + return td +} + +func (u *UpdateContainerDevicesLimits) GetMapOfDeviceReadBPs() map[string]specs.LinuxThrottleDevice { + return getMapOfLinuxThrottleDevices(u.DeviceReadBPs) +} + +func (u *UpdateContainerDevicesLimits) GetMapOfDeviceWriteBPs() map[string]specs.LinuxThrottleDevice { + return getMapOfLinuxThrottleDevices(u.DeviceWriteBPs) +} + +func (u *UpdateContainerDevicesLimits) GetMapOfDeviceReadIOPs() map[string]specs.LinuxThrottleDevice { + return getMapOfLinuxThrottleDevices(u.DeviceReadIOPs) +} + +func (u *UpdateContainerDevicesLimits) GetMapOfDeviceWriteIOPs() map[string]specs.LinuxThrottleDevice { + return getMapOfLinuxThrottleDevices(u.DeviceWriteIOPs) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/container_inspect.go b/vendor/github.com/containers/podman/v5/libpod/define/container_inspect.go new file mode 100644 index 00000000..422ddf60 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/container_inspect.go @@ -0,0 +1,874 @@ +package define + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/containers/podman/v5/pkg/signal" + "go.podman.io/image/v5/manifest" +) + +type InspectIDMappings struct { + UIDMap []string `json:"UidMap"` + GIDMap []string `json:"GidMap"` +} + +// InspectContainerConfig holds further data about how a container was initially +// configured. +type InspectContainerConfig struct { + // Container hostname + Hostname string `json:"Hostname"` + // Container domain name - unused at present + DomainName string `json:"Domainname"` + // User the container was launched with + User string `json:"User"` + // Unused, at present + AttachStdin bool `json:"AttachStdin"` + // Unused, at present + AttachStdout bool `json:"AttachStdout"` + // Unused, at present + AttachStderr bool `json:"AttachStderr"` + // Whether the container creates a TTY + Tty bool `json:"Tty"` + // Whether the container leaves STDIN open + OpenStdin bool `json:"OpenStdin"` + // Whether STDIN is only left open once. + // Presently not supported by Podman, unused. + StdinOnce bool `json:"StdinOnce"` + // Container environment variables + Env []string `json:"Env"` + // Container command + Cmd []string `json:"Cmd"` + // Container image + Image string `json:"Image"` + // Unused, at present. I've never seen this field populated. + Volumes map[string]struct{} `json:"Volumes"` + // Container working directory + WorkingDir string `json:"WorkingDir"` + // Container entrypoint + Entrypoint []string `json:"Entrypoint"` + // On-build arguments - presently unused. More of Buildah's domain. + OnBuild *string `json:"OnBuild"` + // Container labels + Labels map[string]string `json:"Labels"` + // Container annotations + Annotations map[string]string `json:"Annotations"` + // Container stop signal + StopSignal string `json:"StopSignal"` + // Configured startup healthcheck for the container + StartupHealthCheck *StartupHealthCheck `json:"StartupHealthCheck,omitempty"` + // Configured healthcheck for the container + Healthcheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"` + // HealthcheckOnFailureAction defines an action to take once the container turns unhealthy. + HealthcheckOnFailureAction string `json:"HealthcheckOnFailureAction,omitempty"` + // HealthLogDestination defines the destination where the log is stored + HealthLogDestination string `json:"HealthLogDestination,omitempty"` + // HealthMaxLogCount is maximum number of attempts in the HealthCheck log file. + // ('0' value means an infinite number of attempts in the log file) + HealthMaxLogCount uint `json:"HealthcheckMaxLogCount,omitempty"` + // HealthMaxLogSize is the maximum length in characters of stored HealthCheck log + // ("0" value means an infinite log length) + HealthMaxLogSize uint `json:"HealthcheckMaxLogSize,omitempty"` + // CreateCommand is the full command plus arguments of the process the + // container has been created with. + CreateCommand []string `json:"CreateCommand,omitempty"` + // Timezone is the timezone inside the container. + // Local means it has the same timezone as the host machine + Timezone string `json:"Timezone,omitempty"` + // SystemdMode is whether the container is running in systemd mode. In + // systemd mode, the container configuration is customized to optimize + // running systemd in the container. + SystemdMode bool `json:"SystemdMode,omitempty"` + // Umask is the umask inside the container. + Umask string `json:"Umask,omitempty"` + // Secrets are the secrets mounted in the container + Secrets []*InspectSecret `json:"Secrets,omitempty"` + // Timeout is time before container is killed by conmon + Timeout uint `json:"Timeout"` + // StopTimeout is time before container is stopped when calling stop + StopTimeout uint `json:"StopTimeout"` + // Passwd determines whether or not podman can add entries to /etc/passwd and /etc/group + Passwd *bool `json:"Passwd,omitempty"` + // ChrootDirs is an additional set of directories that need to be + // treated as root directories. Standard bind mounts will be mounted + // into paths relative to these directories. + ChrootDirs []string `json:"ChrootDirs,omitempty"` + // SdNotifyMode is the sd-notify mode of the container. + SdNotifyMode string `json:"sdNotifyMode,omitempty"` + // SdNotifySocket is the NOTIFY_SOCKET in use by/configured for the container. + SdNotifySocket string `json:"sdNotifySocket,omitempty"` + // ExposedPorts includes ports the container has exposed. + ExposedPorts map[string]struct{} `json:"ExposedPorts,omitempty"` + + // V4PodmanCompatMarshal indicates that the json marshaller should + // use the old v4 inspect format to keep API compatibility. + V4PodmanCompatMarshal bool `json:"-"` +} + +// UnmarshalJSON allow compatibility with podman V4 API +func (insp *InspectContainerConfig) UnmarshalJSON(data []byte) error { + type Alias InspectContainerConfig + aux := &struct { + Entrypoint any `json:"Entrypoint"` + StopSignal any `json:"StopSignal"` + *Alias + }{ + Alias: (*Alias)(insp), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + switch entrypoint := aux.Entrypoint.(type) { + case string: + insp.Entrypoint = strings.Split(entrypoint, " ") + case []string: + insp.Entrypoint = entrypoint + case []any: + insp.Entrypoint = []string{} + for _, entry := range entrypoint { + if str, ok := entry.(string); ok { + insp.Entrypoint = append(insp.Entrypoint, str) + } + } + case nil: + insp.Entrypoint = []string{} + default: + return fmt.Errorf("cannot unmarshal Config.Entrypoint of type %T", entrypoint) + } + + switch stopsignal := aux.StopSignal.(type) { + case string: + insp.StopSignal = stopsignal + case float64: + insp.StopSignal = signal.ToDockerFormat(uint(stopsignal)) + case nil: + break + default: + return fmt.Errorf("cannot unmarshal Config.StopSignal of type %T", stopsignal) + } + return nil +} + +func (insp *InspectContainerConfig) MarshalJSON() ([]byte, error) { + // the alias is needed otherwise MarshalJSON will + type Alias InspectContainerConfig + conf := (*Alias)(insp) + if !insp.V4PodmanCompatMarshal { + return json.Marshal(conf) + } + + type v4InspectContainerConfig struct { + Entrypoint string `json:"Entrypoint"` + StopSignal uint `json:"StopSignal"` + *Alias + } + stopSignal, _ := signal.ParseSignal(insp.StopSignal) + newConf := &v4InspectContainerConfig{ + Entrypoint: strings.Join(insp.Entrypoint, " "), + StopSignal: uint(stopSignal), + Alias: conf, + } + return json.Marshal(newConf) +} + +// InspectRestartPolicy holds information about the container's restart policy. +type InspectRestartPolicy struct { + // Name contains the container's restart policy. + // Allowable values are "no" or "" (take no action), + // "on-failure" (restart on non-zero exit code, with an optional max + // retry count), and "always" (always restart on container stop, unless + // explicitly requested by API). + // Note that this is NOT actually a name of any sort - the poor naming + // is for Docker compatibility. + Name string `json:"Name"` + // MaximumRetryCount is the maximum number of retries allowed if the + // "on-failure" restart policy is in use. Not used if "on-failure" is + // not set. + MaximumRetryCount uint `json:"MaximumRetryCount"` +} + +// InspectLogConfig holds information about a container's configured log driver +type InspectLogConfig struct { + Type string `json:"Type"` + Config map[string]string `json:"Config"` + // Path specifies a path to the log file + Path string `json:"Path"` + // Tag specifies a custom log tag for the container + Tag string `json:"Tag"` + // Size specifies a maximum size of the container log + Size string `json:"Size"` +} + +// InspectBlkioWeightDevice holds information about the relative weight +// of an individual device node. Weights are used in the I/O scheduler to give +// relative priority to some accesses. +type InspectBlkioWeightDevice struct { + // Path is the path to the device this applies to. + Path string `json:"Path"` + // Weight is the relative weight the scheduler will use when scheduling + // I/O. + Weight uint16 `json:"Weight"` +} + +// InspectBlkioThrottleDevice holds information about a speed cap for a device +// node. This cap applies to a specific operation (read, write, etc) on the given +// node. +type InspectBlkioThrottleDevice struct { + // Path is the path to the device this applies to. + Path string `json:"Path"` + // Rate is the maximum rate. It is in either bytes per second or iops + // per second, determined by where it is used - documentation will + // indicate which is appropriate. + Rate uint64 `json:"Rate"` +} + +// InspectUlimit is a ulimit that will be applied to the container. +type InspectUlimit struct { + // Name is the name (type) of the ulimit. + Name string `json:"Name"` + // Soft is the soft limit that will be applied. + Soft int64 `json:"Soft"` + // Hard is the hard limit that will be applied. + Hard int64 `json:"Hard"` +} + +// InspectDevice is a single device that will be mounted into the container. +type InspectDevice struct { + // PathOnHost is the path of the device on the host. + PathOnHost string `json:"PathOnHost"` + // PathInContainer is the path of the device within the container. + PathInContainer string `json:"PathInContainer"` + // CgroupPermissions is the permissions of the mounted device. + // Presently not populated. + // TODO. + CgroupPermissions string `json:"CgroupPermissions"` +} + +// InspectHostPort provides information on a port on the host that a container's +// port is bound to. +type InspectHostPort struct { + // IP on the host we are bound to. "" if not specified (binding to all + // IPs). + HostIP string `json:"HostIp"` + // Port on the host we are bound to. No special formatting - just an + // integer stuffed into a string. + HostPort string `json:"HostPort"` +} + +// InspectMount provides a record of a single mount in a container. It contains +// fields for both named and normal volumes. Only user-specified volumes will be +// included, and tmpfs volumes are not included even if the user specified them. +type InspectMount struct { + // Whether the mount is a volume or bind mount. Allowed values are + // "volume" and "bind". + Type string `json:"Type"` + // The name of the volume. Empty for bind mounts. + Name string `json:"Name,omitempty"` + // The source directory for the volume. + Source string `json:"Source"` + // The destination directory for the volume. Specified as a path within + // the container, as it would be passed into the OCI runtime. + Destination string `json:"Destination"` + // The driver used for the named volume. Empty for bind mounts. + Driver string `json:"Driver"` + // Contains SELinux :z/:Z mount options. Unclear what, if anything, else + // goes in here. + Mode string `json:"Mode"` + // All remaining mount options. Additional data, not present in the + // original output. + Options []string `json:"Options"` + // Whether the volume is read-write + RW bool `json:"RW"` + // Mount propagation for the mount. Can be empty if not specified, but + // is always printed - no omitempty. + Propagation string `json:"Propagation"` + // SubPath object from the volume. Specified as a path within + // the source volume to be mounted at the Destination. + SubPath string `json:"SubPath,omitempty"` +} + +// InspectContainerState provides a detailed record of a container's current +// state. It is returned as part of InspectContainerData. +// As with InspectContainerData, many portions of this struct are matched to +// Docker, but here we see more fields that are unused (nonsensical in the +// context of Libpod). +type InspectContainerState struct { + OciVersion string `json:"OciVersion"` + Status string `json:"Status"` + Running bool `json:"Running"` + Paused bool `json:"Paused"` + Restarting bool `json:"Restarting"` // TODO + OOMKilled bool `json:"OOMKilled"` + Dead bool `json:"Dead"` + Pid int `json:"Pid"` + ConmonPid int `json:"ConmonPid,omitempty"` + ExitCode int32 `json:"ExitCode"` + Error string `json:"Error"` // TODO + StartedAt time.Time `json:"StartedAt"` + FinishedAt time.Time `json:"FinishedAt"` + Health *HealthCheckResults `json:"Health,omitempty"` + Checkpointed bool `json:"Checkpointed,omitempty"` + CgroupPath string `json:"CgroupPath,omitempty"` + CheckpointedAt time.Time `json:"CheckpointedAt"` + RestoredAt time.Time `json:"RestoredAt"` + CheckpointLog string `json:"CheckpointLog,omitempty"` + CheckpointPath string `json:"CheckpointPath,omitempty"` + RestoreLog string `json:"RestoreLog,omitempty"` + Restored bool `json:"Restored,omitempty"` + StoppedByUser bool `json:"StoppedByUser,omitempty"` +} + +// Healthcheck returns the HealthCheckResults. This is used for old podman compat +// to make the "Healthcheck" key available in the go template. +func (s *InspectContainerState) Healthcheck() *HealthCheckResults { + return s.Health +} + +// HealthCheckResults describes the results/logs from a healthcheck +type HealthCheckResults struct { + // Status starting, healthy or unhealthy + Status string `json:"Status"` + // FailingStreak is the number of consecutive failed healthchecks + FailingStreak int `json:"FailingStreak"` + // Log describes healthcheck attempts and results + Log []HealthCheckLog `json:"Log"` +} + +// HealthCheckLog describes the results of a single healthcheck +type HealthCheckLog struct { + // Start time as string + Start string `json:"Start"` + // End time as a string + End string `json:"End"` + // Exitcode is 0 or 1 + ExitCode int `json:"ExitCode"` + // Output is the stdout/stderr from the healthcheck command + Output string `json:"Output"` +} + +// InspectContainerHostConfig holds information used when the container was +// created. +// It's very much a Docker-specific struct, retained (mostly) as-is for +// compatibility. We fill individual fields as best as we can, inferring as much +// as possible from the spec and container config. +// Some things cannot be inferred. These will be populated by spec annotations +// (if available). +type InspectContainerHostConfig struct { + // Binds contains an array of user-added mounts. + // Both volume mounts and named volumes are included. + // Tmpfs mounts are NOT included. + // In 'docker inspect' this is separated into 'Binds' and 'Mounts' based + // on how a mount was added. We do not make this distinction and do not + // include a Mounts field in inspect. + // Format: :[:] + Binds []string `json:"Binds"` + // CgroupManager is the cgroup manager used by the container. + // At present, allowed values are either "cgroupfs" or "systemd". + CgroupManager string `json:"CgroupManager,omitempty"` + // CgroupMode is the configuration of the container's cgroup namespace. + // Populated as follows: + // private - a cgroup namespace has been created + // host - No cgroup namespace created + // container: - Using another container's cgroup namespace + // ns: - A path to a cgroup namespace has been specified + CgroupMode string `json:"CgroupMode"` + // ContainerIDFile is a file created during container creation to hold + // the ID of the created container. + // This is not handled within libpod and is stored in an annotation. + ContainerIDFile string `json:"ContainerIDFile"` + // LogConfig contains information on the container's logging backend + LogConfig *InspectLogConfig `json:"LogConfig"` + // NetworkMode is the configuration of the container's network + // namespace. + // Populated as follows: + // default - A network namespace is being created and configured via CNI + // none - A network namespace is being created, not configured via CNI + // host - No network namespace created + // container: - Using another container's network namespace + // ns: - A path to a network namespace has been specified + NetworkMode string `json:"NetworkMode"` + // PortBindings contains the container's port bindings. + // It is formatted as map[string][]InspectHostPort. + // The string key here is formatted as / + // and represents the container port. A single container port may be + // bound to multiple host ports (on different IPs). + PortBindings map[string][]InspectHostPort `json:"PortBindings"` + // RestartPolicy contains the container's restart policy. + RestartPolicy *InspectRestartPolicy `json:"RestartPolicy"` + // AutoRemove is whether the container will be automatically removed on + // exiting. + // It is not handled directly within libpod and is stored in an + // annotation. + AutoRemove bool `json:"AutoRemove"` + // AutoRemoveImage is whether the container's image will be + // automatically removed on exiting. + // It is not handled directly within libpod and is stored in an + // annotation. + AutoRemoveImage bool `json:"AutoRemoveImage"` + // Annotations are provided to the runtime when the container is + // started. + Annotations map[string]string `json:"Annotations"` + // VolumeDriver is presently unused and is retained for Docker + // compatibility. + VolumeDriver string `json:"VolumeDriver"` + // VolumesFrom is a list of containers which this container uses volumes + // from. This is not handled directly within libpod and is stored in an + // annotation. + // It is formatted as an array of container names and IDs. + VolumesFrom []string `json:"VolumesFrom"` + // CapAdd is a list of capabilities added to the container. + // It is not directly stored by Libpod, and instead computed from the + // capabilities listed in the container's spec, compared against a set + // of default capabilities. + CapAdd []string `json:"CapAdd"` + // CapDrop is a list of capabilities removed from the container. + // It is not directly stored by libpod, and instead computed from the + // capabilities listed in the container's spec, compared against a set + // of default capabilities. + CapDrop []string `json:"CapDrop"` + // Dns is a list of DNS nameservers that will be added to the + // container's resolv.conf + Dns []string `json:"Dns"` + // DnsOptions is a list of DNS options that will be set in the + // container's resolv.conf + DnsOptions []string `json:"DnsOptions"` + // DnsSearch is a list of DNS search domains that will be set in the + // container's resolv.conf + DnsSearch []string `json:"DnsSearch"` + // ExtraHosts contains hosts that will be added to the container's + // /etc/hosts. + ExtraHosts []string `json:"ExtraHosts"` + // HostsFile is the base file to create the `/etc/hosts` file inside the container. + HostsFile string `json:"HostsFile"` + // GroupAdd contains groups that the user inside the container will be + // added to. + GroupAdd []string `json:"GroupAdd"` + // IpcMode represents the configuration of the container's IPC + // namespace. + // Populated as follows: + // "" (empty string) - Default, an IPC namespace will be created + // host - No IPC namespace created + // container: - Using another container's IPC namespace + // ns: - A path to an IPC namespace has been specified + IpcMode string `json:"IpcMode"` + // Cgroup contains the container's cgroup. It is presently not + // populated. + // TODO. + Cgroup string `json:"Cgroup"` + // Cgroups contains the container's Cgroup mode. + // Allowed values are "default" (container is creating Cgroups) and + // "disabled" (container is not creating Cgroups). + // This is Libpod-specific and not included in `docker inspect`. + Cgroups string `json:"Cgroups"` + // Links is unused, and provided purely for Docker compatibility. + Links []string `json:"Links"` + // OOMScoreAdj is an adjustment that will be made to the container's OOM + // score. + OomScoreAdj int `json:"OomScoreAdj"` + // PidMode represents the configuration of the container's PID + // namespace. + // Populated as follows: + // "" (empty string) - Default, a PID namespace will be created + // host - No PID namespace created + // container: - Using another container's PID namespace + // ns: - A path to a PID namespace has been specified + PidMode string `json:"PidMode"` + // Privileged indicates whether the container is running with elevated + // privileges. + // This has a very specific meaning in the Docker sense, so it's very + // difficult to decode from the spec and config, and so is stored as an + // annotation. + Privileged bool `json:"Privileged"` + // PublishAllPorts indicates whether image ports are being published. + // This is not directly stored in libpod and is saved as an annotation. + PublishAllPorts bool `json:"PublishAllPorts"` + // ReadonlyRootfs is whether the container will be mounted read-only. + ReadonlyRootfs bool `json:"ReadonlyRootfs"` + // SecurityOpt is a list of security-related options that are set in the + // container. + SecurityOpt []string `json:"SecurityOpt"` + // Tmpfs is a list of tmpfs filesystems that will be mounted into the + // container. + // It is a map of destination path to options for the mount. + Tmpfs map[string]string `json:"Tmpfs"` + // UTSMode represents the configuration of the container's UID + // namespace. + // Populated as follows: + // "" (empty string) - Default, a UTS namespace will be created + // host - no UTS namespace created + // container: - Using another container's UTS namespace + // ns: - A path to a UTS namespace has been specified + UTSMode string `json:"UTSMode"` + // UsernsMode represents the configuration of the container's user + // namespace. + // When running rootless, a user namespace is created outside of libpod + // to allow some privileged operations. This will not be reflected here. + // Populated as follows: + // "" (empty string) - No user namespace will be created + // private - The container will be run in a user namespace + // container: - Using another container's user namespace + // ns: - A path to a user namespace has been specified + // TODO Rootless has an additional 'keep-id' option, presently not + // reflected here. + UsernsMode string `json:"UsernsMode"` + // IDMappings is the UIDMapping and GIDMapping used within the container + IDMappings *InspectIDMappings `json:"IDMappings,omitempty"` + // ShmSize is the size of the container's SHM device. + + ShmSize int64 `json:"ShmSize"` + // Runtime is provided purely for Docker compatibility. + // It is set unconditionally to "oci" as Podman does not presently + // support non-OCI runtimes. + Runtime string `json:"Runtime"` + // ConsoleSize is an array of 2 integers showing the size of the + // container's console. + // It is only set if the container is creating a terminal. + // TODO. + ConsoleSize []uint `json:"ConsoleSize"` + // Isolation is presently unused and provided solely for Docker + // compatibility. + Isolation string `json:"Isolation"` + // CpuShares indicates the CPU resources allocated to the container. + // It is a relative weight in the scheduler for assigning CPU time + // versus other Cgroups. + CpuShares uint64 `json:"CpuShares"` + // Memory indicates the memory resources allocated to the container. + // This is the limit (in bytes) of RAM the container may use. + Memory int64 `json:"Memory"` + // NanoCpus indicates number of CPUs allocated to the container. + // It is an integer where one full CPU is indicated by 1000000000 (one + // billion). + // Thus, 2.5 CPUs (fractional portions of CPUs are allowed) would be + // 2500000000 (2.5 billion). + // In 'docker inspect' this is set exclusively of two further options in + // the output (CpuPeriod and CpuQuota) which are both used to implement + // this functionality. + // We can't distinguish here, so if CpuQuota is set to the default of + // 100000, we will set both CpuQuota, CpuPeriod, and NanoCpus. If + // CpuQuota is not the default, we will not set NanoCpus. + NanoCpus int64 `json:"NanoCpus"` + // CgroupParent is the Cgroup parent of the container. + // Only set if not default. + CgroupParent string `json:"CgroupParent"` + // BlkioWeight indicates the I/O resources allocated to the container. + // It is a relative weight in the scheduler for assigning I/O time + // versus other Cgroups. + BlkioWeight uint16 `json:"BlkioWeight"` + // BlkioWeightDevice is an array of I/O resource priorities for + // individual device nodes. + // Unfortunately, the spec only stores the device's Major/Minor numbers + // and not the path, which is used here. + // Fortunately, the kernel provides an interface for retrieving the path + // of a given node by major:minor at /sys/dev/. However, the exact path + // in use may not be what was used in the original CLI invocation - + // though it is guaranteed that the device node will be the same, and + // using the given path will be functionally identical. + BlkioWeightDevice []InspectBlkioWeightDevice `json:"BlkioWeightDevice"` + // BlkioDeviceReadBps is an array of I/O throttle parameters for + // individual device nodes. + // This specifically sets read rate cap in bytes per second for device + // nodes. + // As with BlkioWeightDevice, we pull the path from /sys/dev, and we + // don't guarantee the path will be identical to the original (though + // the node will be). + BlkioDeviceReadBps []InspectBlkioThrottleDevice `json:"BlkioDeviceReadBps"` + // BlkioDeviceWriteBps is an array of I/O throttle parameters for + // individual device nodes. + // this specifically sets write rate cap in bytes per second for device + // nodes. + // as with BlkioWeightDevice, we pull the path from /sys/dev, and we + // don't guarantee the path will be identical to the original (though + // the node will be). + BlkioDeviceWriteBps []InspectBlkioThrottleDevice `json:"BlkioDeviceWriteBps"` + // BlkioDeviceReadIOps is an array of I/O throttle parameters for + // individual device nodes. + // This specifically sets the read rate cap in iops per second for + // device nodes. + // As with BlkioWeightDevice, we pull the path from /sys/dev, and we + // don't guarantee the path will be identical to the original (though + // the node will be). + BlkioDeviceReadIOps []InspectBlkioThrottleDevice `json:"BlkioDeviceReadIOps"` + // BlkioDeviceWriteIOps is an array of I/O throttle parameters for + // individual device nodes. + // This specifically sets the write rate cap in iops per second for + // device nodes. + // As with BlkioWeightDevice, we pull the path from /sys/dev, and we + // don't guarantee the path will be identical to the original (though + // the node will be). + BlkioDeviceWriteIOps []InspectBlkioThrottleDevice `json:"BlkioDeviceWriteIOps"` + // CpuPeriod is the length of a CPU period in microseconds. + // It relates directly to CpuQuota. + CpuPeriod uint64 `json:"CpuPeriod"` + // CpuPeriod is the amount of time (in microseconds) that a container + // can use the CPU in every CpuPeriod. + CpuQuota int64 `json:"CpuQuota"` + // CpuRealtimePeriod is the length of time (in microseconds) of the CPU + // realtime period. If set to 0, no time will be allocated to realtime + // tasks. + CpuRealtimePeriod uint64 `json:"CpuRealtimePeriod"` + // CpuRealtimeRuntime is the length of time (in microseconds) allocated + // for realtime tasks within every CpuRealtimePeriod. + CpuRealtimeRuntime int64 `json:"CpuRealtimeRuntime"` + // CpusetCpus is the set of CPUs that the container will execute on. + // Formatted as `0-3` or `0,2`. Default (if unset) is all CPUs. + CpusetCpus string `json:"CpusetCpus"` + // CpusetMems is the set of memory nodes the container will use. + // Formatted as `0-3` or `0,2`. Default (if unset) is all memory nodes. + CpusetMems string `json:"CpusetMems"` + // Devices is a list of device nodes that will be added to the + // container. + // These are stored in the OCI spec only as type, major, minor while we + // display the host path. We convert this with /sys/dev, but we cannot + // guarantee that the host path will be identical - only that the actual + // device will be. + Devices []InspectDevice `json:"Devices"` + // DiskQuota is the maximum amount of disk space the container may use + // (in bytes). + // Presently not populated. + // TODO. + DiskQuota uint64 `json:"DiskQuota"` + // KernelMemory is the maximum amount of memory the kernel will devote + // to the container. + KernelMemory int64 `json:"KernelMemory"` + // MemoryReservation is the reservation (soft limit) of memory available + // to the container. Soft limits are warnings only and can be exceeded. + MemoryReservation int64 `json:"MemoryReservation"` + // MemorySwap is the total limit for all memory available to the + // container, including swap. 0 indicates that there is no limit to the + // amount of memory available. + MemorySwap int64 `json:"MemorySwap"` + // MemorySwappiness is the willingness of the kernel to page container + // memory to swap. It is an integer from 0 to 100, with low numbers + // being more likely to be put into swap. + // -1, the default, will not set swappiness and use the system defaults. + MemorySwappiness int64 `json:"MemorySwappiness"` + // OomKillDisable indicates whether the kernel OOM killer is disabled + // for the container. + OomKillDisable bool `json:"OomKillDisable"` + // Init indicates whether the container has an init mounted into it. + Init bool `json:"Init,omitempty"` + // PidsLimit is the maximum number of PIDs that may be created within + // the container. 0, the default, indicates no limit. + PidsLimit int64 `json:"PidsLimit"` + // Ulimits is a set of ulimits that will be set within the container. + Ulimits []InspectUlimit `json:"Ulimits"` + // CpuCount is Windows-only and not presently implemented. + CpuCount uint64 `json:"CpuCount"` + // CpuPercent is Windows-only and not presently implemented. + CpuPercent uint64 `json:"CpuPercent"` + // IOMaximumIOps is Windows-only and not presently implemented. + IOMaximumIOps uint64 `json:"IOMaximumIOps"` + // IOMaximumBandwidth is Windows-only and not presently implemented. + IOMaximumBandwidth uint64 `json:"IOMaximumBandwidth"` + // CgroupConf is the configuration for cgroup v2. + CgroupConf map[string]string `json:"CgroupConf"` + // IntelRdtClosID defines the Intel RDT CAT Class Of Service (COS) that + // all processes of the container should run in. + IntelRdtClosID string `json:"IntelRdtClosID,omitempty"` +} + +// Address represents an IP address. +type Address struct { + Addr string + PrefixLength int +} + +// InspectBasicNetworkConfig holds basic configuration information (e.g. IP +// addresses, MAC address, subnet masks, etc) that are common for all networks +// (both additional and main). +type InspectBasicNetworkConfig struct { + // EndpointID is unused, maintained exclusively for compatibility. + EndpointID string `json:"EndpointID"` + // Gateway is the IP address of the gateway this network will use. + Gateway string `json:"Gateway"` + // IPAddress is the IP address for this network. + IPAddress string `json:"IPAddress"` + // IPPrefixLen is the length of the subnet mask of this network. + IPPrefixLen int `json:"IPPrefixLen"` + // SecondaryIPAddresses is a list of extra IP Addresses that the + // container has been assigned in this network. + SecondaryIPAddresses []Address `json:"SecondaryIPAddresses,omitempty"` + // IPv6Gateway is the IPv6 gateway this network will use. + IPv6Gateway string `json:"IPv6Gateway"` + // GlobalIPv6Address is the global-scope IPv6 Address for this network. + GlobalIPv6Address string `json:"GlobalIPv6Address"` + // GlobalIPv6PrefixLen is the length of the subnet mask of this network. + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen"` + // SecondaryIPv6Addresses is a list of extra IPv6 Addresses that the + // container has been assigned in this network. + SecondaryIPv6Addresses []Address `json:"SecondaryIPv6Addresses,omitempty"` + // MacAddress is the MAC address for the interface in this network. + MacAddress string `json:"MacAddress"` + // AdditionalMacAddresses is a set of additional MAC Addresses beyond + // the first. CNI may configure more than one interface for a single + // network, which can cause this. + AdditionalMacAddresses []string `json:"AdditionalMACAddresses,omitempty"` +} + +// InspectAdditionalNetwork holds information about non-default networks the +// container has been connected to. +// As with InspectNetworkSettings, many fields are unused and maintained only +// for compatibility with Docker. +type InspectAdditionalNetwork struct { + InspectBasicNetworkConfig + + // Name of the network we're connecting to. + NetworkID string `json:"NetworkID,omitempty"` + // DriverOpts is presently unused and maintained exclusively for + // compatibility. + DriverOpts map[string]string `json:"DriverOpts"` + // IPAMConfig is presently unused and maintained exclusively for + // compatibility. + IPAMConfig map[string]string `json:"IPAMConfig"` + // Links is presently unused and maintained exclusively for + // compatibility. + Links []string `json:"Links"` + // Aliases are any network aliases the container has in this network. + Aliases []string `json:"Aliases,omitempty"` +} + +// InspectNetworkSettings holds information about the network settings of the +// container. +// Many fields are maintained only for compatibility with `docker inspect` and +// are unused within Libpod. +type InspectNetworkSettings struct { + InspectBasicNetworkConfig + + Bridge string `json:"Bridge"` + SandboxID string `json:"SandboxID"` + HairpinMode bool `json:"HairpinMode"` + LinkLocalIPv6Address string `json:"LinkLocalIPv6Address"` + LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen"` + Ports map[string][]InspectHostPort `json:"Ports"` + SandboxKey string `json:"SandboxKey"` + // Networks contains information on non-default networks this + // container has joined. + // It is a map of network name to network information. + Networks map[string]*InspectAdditionalNetwork `json:"Networks,omitempty"` +} + +// InspectContainerData provides a detailed record of a container's configuration +// and state as viewed by Libpod. +// Large portions of this structure are defined such that the output is +// compatible with `docker inspect` JSON, but additional fields have been added +// as required to share information not in the original output. +type InspectContainerData struct { + ID string `json:"Id"` + Created time.Time `json:"Created"` + Path string `json:"Path"` + Args []string `json:"Args"` + State *InspectContainerState `json:"State"` + Image string `json:"Image"` + ImageDigest string `json:"ImageDigest"` + ImageName string `json:"ImageName"` + Rootfs string `json:"Rootfs"` + Pod string `json:"Pod"` + ResolvConfPath string `json:"ResolvConfPath"` + HostnamePath string `json:"HostnamePath"` + HostsPath string `json:"HostsPath"` + StaticDir string `json:"StaticDir"` + OCIConfigPath string `json:"OCIConfigPath,omitempty"` + OCIRuntime string `json:"OCIRuntime,omitempty"` + ConmonPidFile string `json:"ConmonPidFile"` + PidFile string `json:"PidFile"` + Name string `json:"Name"` + RestartCount int32 `json:"RestartCount"` + Driver string `json:"Driver"` + MountLabel string `json:"MountLabel"` + ProcessLabel string `json:"ProcessLabel"` + AppArmorProfile string `json:"AppArmorProfile"` + EffectiveCaps []string `json:"EffectiveCaps"` + BoundingCaps []string `json:"BoundingCaps"` + ExecIDs []string `json:"ExecIDs"` + GraphDriver *DriverData `json:"GraphDriver"` + SizeRw *int64 `json:"SizeRw,omitempty"` + SizeRootFs int64 `json:"SizeRootFs,omitempty"` + Mounts []InspectMount `json:"Mounts"` + Dependencies []string `json:"Dependencies"` + NetworkSettings *InspectNetworkSettings `json:"NetworkSettings"` + Namespace string `json:"Namespace"` + IsInfra bool `json:"IsInfra"` + IsService bool `json:"IsService"` + KubeExitCodePropagation string `json:"KubeExitCodePropagation"` + LockNumber uint32 `json:"lockNumber"` + Config *InspectContainerConfig `json:"Config"` + HostConfig *InspectContainerHostConfig `json:"HostConfig"` + UseImageHosts bool `json:"UseImageHosts"` + UseImageHostname bool `json:"UseImageHostname"` +} + +// InspectExecSession contains information about a given exec session. +type InspectExecSession struct { + // CanRemove is legacy and used purely for compatibility reasons. + // Will always be set to true, unless the exec session is running. + CanRemove bool `json:"CanRemove"` + // ContainerID is the ID of the container this exec session is attached + // to. + ContainerID string `json:"ContainerID"` + // DetachKeys are the detach keys used by the exec session. + // If set to "" the default keys are being used. + // Will show "" if no detach keys are set. + DetachKeys string `json:"DetachKeys"` + // ExitCode is the exit code of the exec session. Will be set to 0 if + // the exec session has not yet exited. + ExitCode int `json:"ExitCode"` + // ID is the ID of the exec session. + ID string `json:"ID"` + // OpenStderr is whether the container's STDERR stream will be attached. + // Always set to true if the exec session created a TTY. + OpenStderr bool `json:"OpenStderr"` + // OpenStdin is whether the container's STDIN stream will be attached + // to. + OpenStdin bool `json:"OpenStdin"` + // OpenStdout is whether the container's STDOUT stream will be attached. + // Always set to true if the exec session created a TTY. + OpenStdout bool `json:"OpenStdout"` + // Running is whether the exec session is running. + Running bool `json:"Running"` + // Pid is the PID of the exec session's process. + // Will be set to 0 if the exec session is not running. + Pid int `json:"Pid"` + // ProcessConfig contains information about the exec session's process. + ProcessConfig *InspectExecProcess `json:"ProcessConfig"` +} + +// InspectExecProcess contains information about the process in a given exec +// session. +type InspectExecProcess struct { + // Arguments are the arguments to the entrypoint command of the exec + // session. + Arguments []string `json:"arguments"` + // Entrypoint is the entrypoint for the exec session (the command that + // will be executed in the container). + Entrypoint string `json:"entrypoint"` + // Privileged is whether the exec session will be started with elevated + // privileges. + Privileged bool `json:"privileged"` + // Tty is whether the exec session created a terminal. + Tty bool `json:"tty"` + // User is the user the exec session was started as. + User string `json:"user"` +} + +// DriverData handles the data for a storage driver +type DriverData struct { + Name string `json:"Name"` + Data map[string]string `json:"Data"` +} + +// InspectSecret contains information on secrets mounted inside the container +type InspectSecret struct { + // Name is the name of the secret + Name string `json:"Name"` + // ID is the ID of the secret + ID string `json:"ID"` + // ID is the UID of the mounted secret file + UID uint32 `json:"UID"` + // ID is the GID of the mounted secret file + GID uint32 `json:"GID"` + // ID is the ID of the mode of the mounted secret file + Mode uint32 `json:"Mode"` +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/containerstate.go b/vendor/github.com/containers/podman/v5/libpod/define/containerstate.go new file mode 100644 index 00000000..e9679888 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/containerstate.go @@ -0,0 +1,163 @@ +package define + +import ( + "fmt" + "time" +) + +// ContainerStatus represents the current state of a container +type ContainerStatus int + +const ( + // ContainerStateUnknown indicates that the container is in an error + // state where information about it cannot be retrieved + ContainerStateUnknown ContainerStatus = iota + // ContainerStateConfigured indicates that the container has had its + // storage configured but it has not been created in the OCI runtime + ContainerStateConfigured ContainerStatus = iota + // ContainerStateCreated indicates the container has been created in + // the OCI runtime but not started + ContainerStateCreated ContainerStatus = iota + // ContainerStateRunning indicates the container is currently executing + ContainerStateRunning ContainerStatus = iota + // ContainerStateStopped indicates that the container was running but has + // exited + ContainerStateStopped ContainerStatus = iota + // ContainerStatePaused indicates that the container has been paused + ContainerStatePaused ContainerStatus = iota + // ContainerStateExited indicates the container has stopped and been + // cleaned up + ContainerStateExited ContainerStatus = iota + // ContainerStateRemoving indicates the container is in the process of + // being removed. + ContainerStateRemoving ContainerStatus = iota + // ContainerStateStopping indicates the container is in the process of + // being stopped. + ContainerStateStopping ContainerStatus = iota +) + +// ContainerStatus returns a string representation for users of a container +// state. All results should match Docker's versions (from `docker ps`) as +// closely as possible, given the different set of states we support. +func (t ContainerStatus) String() string { + switch t { + case ContainerStateUnknown: + return "unknown" + case ContainerStateConfigured: + // The naming here is confusing, but it's necessary for Docker + // compatibility - their Created state is our Configured state. + return "created" + case ContainerStateCreated: + // Docker does not have an equivalent to this state, so give it + // a clear name. Most of the time this is a purely transitory + // state between Configured and Running so we don't expect to + // see it much anyways. + return "initialized" + case ContainerStateRunning: + return "running" + case ContainerStateStopped: + return "stopped" + case ContainerStatePaused: + return "paused" + case ContainerStateExited: + return "exited" + case ContainerStateRemoving: + return "removing" + case ContainerStateStopping: + return "stopping" + } + return "bad state" +} + +// StringToContainerStatus converts a string representation of a container's +// status into an actual container status type +func StringToContainerStatus(status string) (ContainerStatus, error) { + switch status { + case ContainerStateUnknown.String(): + return ContainerStateUnknown, nil + case ContainerStateConfigured.String(): + return ContainerStateConfigured, nil + case ContainerStateCreated.String(): + return ContainerStateCreated, nil + case ContainerStateRunning.String(): + return ContainerStateRunning, nil + case ContainerStateStopped.String(): + return ContainerStateStopped, nil + case ContainerStatePaused.String(): + return ContainerStatePaused, nil + case ContainerStateExited.String(): + return ContainerStateExited, nil + case ContainerStateRemoving.String(): + return ContainerStateRemoving, nil + default: + return ContainerStateUnknown, fmt.Errorf("unknown container state: %s: %w", status, ErrInvalidArg) + } +} + +// ContainerExecStatus is the status of an exec session within a container. +type ContainerExecStatus int + +const ( + // ExecStateUnknown indicates that the state of the exec session is not + // known. + ExecStateUnknown ContainerExecStatus = iota + // ExecStateCreated indicates that the exec session has been created but + // not yet started + ExecStateCreated ContainerExecStatus = iota + // ExecStateRunning indicates that the exec session has been started but + // has not yet exited. + ExecStateRunning ContainerExecStatus = iota + // ExecStateStopped indicates that the exec session has stopped and is + // no longer running. + ExecStateStopped ContainerExecStatus = iota +) + +// String returns a string representation of a given exec state. +func (s ContainerExecStatus) String() string { + switch s { + case ExecStateUnknown: + return "unknown" + case ExecStateCreated: + return "created" + case ExecStateRunning: + return "running" + case ExecStateStopped: + return "stopped" + default: + return "bad state" + } +} + +// ContainerStats contains the statistics information for a running container +type ContainerStats struct { + AvgCPU float64 + ContainerID string + Name string + PerCPU []uint64 + CPU float64 + CPUNano uint64 + CPUSystemNano uint64 + SystemNano uint64 + MemUsage uint64 + MemLimit uint64 + MemPerc float64 + // Map of interface name to network statistics for that interface. + Network map[string]ContainerNetworkStats + BlockInput uint64 + BlockOutput uint64 + PIDs uint64 + UpTime time.Duration + Duration uint64 +} + +// Statistics for an individual container network interface +type ContainerNetworkStats struct { + RxBytes uint64 + RxDropped uint64 + RxErrors uint64 + RxPackets uint64 + TxBytes uint64 + TxDropped uint64 + TxErrors uint64 + TxPackets uint64 +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/diff.go b/vendor/github.com/containers/podman/v5/libpod/define/diff.go new file mode 100644 index 00000000..ee492eb3 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/diff.go @@ -0,0 +1,26 @@ +package define + +// extra type to use as enum +type DiffType uint8 + +const ( + // only diff containers + DiffContainer DiffType = 1 << iota + // only diff images + DiffImage + // diff both containers and images + DiffAll DiffType = 0b11111111 +) + +func (d DiffType) String() string { + switch d { + case DiffAll: + return "all" + case DiffContainer: + return "container" + case DiffImage: + return "image" + default: + return "unknown" + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/errors.go b/vendor/github.com/containers/podman/v5/libpod/define/errors.go new file mode 100644 index 00000000..ac502b8b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/errors.go @@ -0,0 +1,223 @@ +package define + +import ( + "errors" + "fmt" + + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/detach" +) + +var ( + // ErrNoSuchCtr indicates the requested container does not exist + ErrNoSuchCtr = errors.New("no such container") + + // ErrNoSuchPod indicates the requested pod does not exist + ErrNoSuchPod = errors.New("no such pod") + + // ErrNoSuchVolume indicates the requested volume does not exist + ErrNoSuchVolume = errors.New("no such volume") + + // ErrNoSuchNetwork indicates the requested network does not exist + ErrNoSuchNetwork = types.ErrNoSuchNetwork + + // ErrNoSuchExecSession indicates that the requested exec session does + // not exist. + ErrNoSuchExecSession = errors.New("no such exec session") + + // ErrNoSuchExitCode indicates that the requested container exit code + // does not exist. + ErrNoSuchExitCode = errors.New("no such exit code") + + // ErrDepExists indicates that the current object has dependencies and + // cannot be removed before them. + ErrDepExists = errors.New("dependency exists") + + // ErrNoAliases indicates that the container does not have any network + // aliases. + ErrNoAliases = errors.New("no aliases for container") + + // ErrMissingPlugin indicates that the requested operation requires a + // plugin that is not present on the system or in the configuration. + ErrMissingPlugin = errors.New("required plugin missing") + + // ErrCtrExists indicates a container with the same name or ID already + // exists + ErrCtrExists = errors.New("container already exists") + // ErrPodExists indicates a pod with the same name or ID already exists + ErrPodExists = errors.New("pod already exists") + // ErrImageExists indicates an image with the same ID already exists + ErrImageExists = errors.New("image already exists") + // ErrVolumeExists indicates a volume with the same name already exists + ErrVolumeExists = errors.New("volume already exists") + // ErrExecSessionExists indicates an exec session with the same ID + // already exists. + ErrExecSessionExists = errors.New("exec session already exists") + // ErrNetworkExists indicates that a network with the given name already + // exists. + ErrNetworkExists = types.ErrNetworkExists + + // ErrCtrStateInvalid indicates a container is in an improper state for + // the requested operation + ErrCtrStateInvalid = errors.New("container state improper") + // ErrCtrStateRunning indicates a container is running. + ErrCtrStateRunning = errors.New("container is running") + // ErrExecSessionStateInvalid indicates that an exec session is in an + // improper state for the requested operation + ErrExecSessionStateInvalid = errors.New("exec session state improper") + // ErrVolumeBeingUsed indicates that a volume is being used by at least one container + ErrVolumeBeingUsed = errors.New("volume is being used") + + // ErrRuntimeFinalized indicates that the runtime has already been + // created and cannot be modified + ErrRuntimeFinalized = errors.New("runtime has been finalized") + // ErrCtrFinalized indicates that the container has already been created + // and cannot be modified + ErrCtrFinalized = errors.New("container has been finalized") + // ErrPodFinalized indicates that the pod has already been created and + // cannot be modified + ErrPodFinalized = errors.New("pod has been finalized") + // ErrVolumeFinalized indicates that the volume has already been created and + // cannot be modified + ErrVolumeFinalized = errors.New("volume has been finalized") + + // ErrInvalidArg indicates that an invalid argument was passed + ErrInvalidArg = types.ErrInvalidArg + // ErrEmptyID indicates that an empty ID was passed + ErrEmptyID = errors.New("name or ID cannot be empty") + + // ErrInternal indicates an internal library error + ErrInternal = errors.New("internal libpod error") + + // ErrPodPartialFail indicates that a pod operation was only partially + // successful, and some containers within the pod failed. + ErrPodPartialFail = errors.New("some containers failed") + + // ErrDetach indicates that an attach session was manually detached by + // the user. + ErrDetach = detach.ErrDetach + + // ErrWillDeadlock indicates that the requested operation will cause a + // deadlock. This is usually caused by upgrade issues, and is resolved + // by renumbering the locks. + ErrWillDeadlock = errors.New("deadlock due to lock mismatch") + + // ErrNoCgroups indicates that the container does not have its own + // Cgroup. + ErrNoCgroups = errors.New("this container does not have a cgroup") + // ErrNoLogs indicates that this container is not creating a log so log + // operations cannot be performed on it + ErrNoLogs = errors.New("this container is not logging output") + + // ErrRootless indicates that the given command cannot but run without + // root. + ErrRootless = errors.New("operation requires root privileges") + + // ErrRuntimeStopped indicates that the runtime has already been shut + // down and no further operations can be performed on it + ErrRuntimeStopped = errors.New("runtime has already been stopped") + // ErrCtrStopped indicates that the requested container is not running + // and the requested operation cannot be performed until it is started + ErrCtrStopped = errors.New("container is stopped") + + // ErrCtrRemoved indicates that the container has already been removed + // and no further operations can be performed on it + ErrCtrRemoved = errors.New("container has already been removed") + // ErrPodRemoved indicates that the pod has already been removed and no + // further operations can be performed on it + ErrPodRemoved = errors.New("pod has already been removed") + // ErrVolumeRemoved indicates that the volume has already been removed and + // no further operations can be performed on it + ErrVolumeRemoved = errors.New("volume has already been removed") + // ErrExecSessionRemoved indicates that the exec session has already + // been removed and no further operations can be performed on it. + ErrExecSessionRemoved = errors.New("exec session has already been removed") + + // ErrDBClosed indicates that the connection to the state database has + // already been closed + ErrDBClosed = errors.New("database connection already closed") + // ErrDBBadConfig indicates that the database has a different schema or + // was created by a libpod with a different config + ErrDBBadConfig = errors.New("database configuration mismatch") + + // ErrNSMismatch indicates that the requested pod or container is in a + // different namespace and cannot be accessed or modified. + ErrNSMismatch = errors.New("target is in a different namespace") + + // ErrNotImplemented indicates that the requested functionality is not + // yet present + ErrNotImplemented = errors.New("not yet implemented") + + // ErrOSNotSupported indicates the function is not available on the particular + // OS. + ErrOSNotSupported = errors.New("no support for this OS yet") + + // ErrOCIRuntime indicates a generic error from the OCI runtime + ErrOCIRuntime = errors.New("OCI runtime error") + + // ErrOCIRuntimePermissionDenied indicates the OCI runtime attempted to invoke a command that returned + // a permission denied error + ErrOCIRuntimePermissionDenied = errors.New("OCI permission denied") + + // ErrOCIRuntimeNotFound indicates the OCI runtime attempted to invoke a command + // that was not found + ErrOCIRuntimeNotFound = errors.New("OCI runtime attempted to invoke a command that was not found") + + // ErrOCIRuntimeUnavailable indicates that the OCI runtime associated to a container + // could not be found in the configuration + ErrOCIRuntimeUnavailable = errors.New("OCI unavailable") + + // ErrConmonOutdated indicates the version of conmon found (whether via the configuration or $PATH) + // is out of date for the current podman version + ErrConmonOutdated = errors.New("outdated conmon version") + // ErrConmonDead indicates that the container's conmon process has been + // killed, preventing normal operation. + ErrConmonDead = errors.New("conmon process killed") + + // ErrNetworkOnPodContainer indicates the user wishes to alter network attributes on a container + // in a pod. This cannot be done as the infra container has all the network information + ErrNetworkOnPodContainer = errors.New("network cannot be configured when it is shared with a pod") + + // ErrNetworkInUse indicates the requested operation failed because the network was in use + ErrNetworkInUse = errors.New("network is being used") + + // ErrNetworkConnected indicates that the required operation failed because the container is already a network endpoint + ErrNetworkConnected = errors.New("network is already connected") + + // ErrStoreNotInitialized indicates that the container storage was never + // initialized. + ErrStoreNotInitialized = errors.New("the container storage was never initialized") + + // ErrNoNetwork indicates that a container has no net namespace, like network=none + ErrNoNetwork = errors.New("container has no network namespace") + + // ErrNetworkModeInvalid indicates that a container has the wrong network mode for an operation + ErrNetworkModeInvalid = errors.New("invalid network mode") + + // ErrSetSecurityAttribute indicates that a request to set a container's security attribute + // was not possible. + ErrSetSecurityAttribute = fmt.Errorf("%w: unable to assign security attribute", ErrOCIRuntime) + + // ErrGetSecurityAttribute indicates that a request to get a container's security attribute + // was not possible. + ErrGetSecurityAttribute = fmt.Errorf("%w: unable to get security attribute", ErrOCIRuntime) + + // ErrSecurityAttribute indicates that an error processing security attributes + // for the container + ErrSecurityAttribute = fmt.Errorf("%w: unable to process security attribute", ErrOCIRuntime) + + // ErrCanceled indicates that an operation has been cancelled by a user. + // Useful for potentially long running tasks. + ErrCanceled = errors.New("cancelled by user") + + // ErrConmonVersionFormat is used when the expected version format of conmon + // has changed. + ErrConmonVersionFormat = "conmon version changed format" + + // ErrRemovingCtrs indicates that there was an error removing all + // containers from a pod. + ErrRemovingCtrs = errors.New("removing pod containers") + + // ErrHealthCheckTimeout indicates that a HealthCheck timed out. + ErrHealthCheckTimeout = errors.New("healthcheck command exceeded timeout") +) diff --git a/vendor/github.com/containers/podman/v5/libpod/define/exec_codes.go b/vendor/github.com/containers/podman/v5/libpod/define/exec_codes.go new file mode 100644 index 00000000..c2bdb238 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/exec_codes.go @@ -0,0 +1,55 @@ +package define + +import ( + "errors" + "strings" + + "github.com/sirupsen/logrus" +) + +const ( + // ExecErrorCodeGeneric is the default error code to return from an exec session if libpod failed + // prior to calling the runtime + ExecErrorCodeGeneric = 125 + // ExecErrorCodeCannotInvoke is the error code to return when the runtime fails to invoke a command. + // An example of this can be found by trying to execute a directory: + // `podman exec -l /etc` + ExecErrorCodeCannotInvoke = 126 + // ExecErrorCodeNotFound is the error code to return when a command cannot be found + ExecErrorCodeNotFound = 127 +) + +// TranslateExecErrorToExitCode takes an error and checks whether it +// has a predefined exit code associated. If so, it returns that, otherwise it returns +// the exit code originally stated in libpod.Exec() +func TranslateExecErrorToExitCode(originalEC int, err error) int { + if errors.Is(err, ErrOCIRuntimePermissionDenied) { + return ExecErrorCodeCannotInvoke + } + if errors.Is(err, ErrOCIRuntimeNotFound) { + return ExecErrorCodeNotFound + } + if errors.Is(err, ErrInvalidArg) { + return ExecErrorCodeGeneric + } + + return originalEC +} + +// ExitCode reads the error message when failing to executing container process +// and then returns 0 if no error, ExecErrorCodeNotFound if command does not exist, or ExecErrorCodeCannotInvoke for +// all other errors +func ExitCode(err error) int { + if err == nil { + return 0 + } + e := strings.ToLower(err.Error()) + logrus.Debugf("ExitCode msg: %q", e) + if strings.Contains(e, "not found") || + strings.Contains(e, "executable path is empty") || + strings.Contains(e, "no such file") { + return ExecErrorCodeNotFound + } + + return ExecErrorCodeCannotInvoke +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/exit_code_propagation.go b/vendor/github.com/containers/podman/v5/libpod/define/exit_code_propagation.go new file mode 100644 index 00000000..eff69b1c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/exit_code_propagation.go @@ -0,0 +1,54 @@ +package define + +import "fmt" + +// KubeExitCodePropagation defines an exit policy of kube workloads. +type KubeExitCodePropagation int + +const ( + // Invalid exit policy for a proper type system. + KubeExitCodePropagationInvalid KubeExitCodePropagation = iota + // Exit 0 regardless of any failed containers. + KubeExitCodePropagationNone + // Exit non-zero if all containers failed. + KubeExitCodePropagationAll + // Exit non-zero if any container failed. + KubeExitCodePropagationAny + + // String representations. + strKubeECPInvalid = "invalid" + strKubeECPNone = "none" + strKubeECPAll = "all" + strKubeECPAny = "any" +) + +// Parse the specified kube exit-code propagation. Return an error if an +// unsupported value is specified. +func ParseKubeExitCodePropagation(value string) (KubeExitCodePropagation, error) { + switch value { + case strKubeECPNone, "": + return KubeExitCodePropagationNone, nil + case strKubeECPAll: + return KubeExitCodePropagationAll, nil + case strKubeECPAny: + return KubeExitCodePropagationAny, nil + default: + return KubeExitCodePropagationInvalid, fmt.Errorf("unsupported exit-code propagation %q", value) + } +} + +// Return the string representation of the KubeExitCodePropagation. +func (k KubeExitCodePropagation) String() string { + switch k { + case KubeExitCodePropagationNone: + return strKubeECPNone + case KubeExitCodePropagationAll: + return strKubeECPAll + case KubeExitCodePropagationAny: + return strKubeECPAny + case KubeExitCodePropagationInvalid: + return strKubeECPInvalid + default: + return "unknown value" + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/fileinfo.go b/vendor/github.com/containers/podman/v5/libpod/define/fileinfo.go new file mode 100644 index 00000000..eec99e30 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/fileinfo.go @@ -0,0 +1,16 @@ +package define + +import ( + "os" + "time" +) + +// FileInfo describes the attributes of a file or directory. +type FileInfo struct { + Name string `json:"name"` + Size int64 `json:"size"` + Mode os.FileMode `json:"mode"` + ModTime time.Time `json:"mtime"` + IsDir bool `json:"isDir"` + LinkTarget string `json:"linkTarget"` +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/healthchecks.go b/vendor/github.com/containers/podman/v5/libpod/define/healthchecks.go new file mode 100644 index 00000000..6e1560d7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/healthchecks.go @@ -0,0 +1,358 @@ +package define + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "go.podman.io/image/v5/manifest" +) + +const ( + // HealthCheckHealthy describes a healthy container + HealthCheckHealthy string = "healthy" + // HealthCheckUnhealthy describes an unhealthy container + HealthCheckUnhealthy string = "unhealthy" + // HealthCheckStarting describes the time between when the container starts + // and the start-period (time allowed for the container to start and application + // to be running) expires. + HealthCheckStarting string = "starting" + // HealthCheckReset describes reset of HealthCheck logs + HealthCheckReset string = "reset" + // HealthCheckStopped describes the time when container was stopped during HealthCheck + // and HealthCheck was terminated + HealthCheckStopped string = "stopped" +) + +// HealthCheckStatus represents the current state of a container +type HealthCheckStatus int + +const ( + // HealthCheckSuccess means the health worked + HealthCheckSuccess HealthCheckStatus = iota + // HealthCheckFailure means the health ran and failed + HealthCheckFailure HealthCheckStatus = iota + // HealthCheckContainerStopped means the health check cannot + // be run because the container is stopped + HealthCheckContainerStopped HealthCheckStatus = iota + // HealthCheckContainerNotFound means the container could + // not be found in local store + HealthCheckContainerNotFound HealthCheckStatus = iota + // HealthCheckNotDefined means the container has no health + // check defined in it + HealthCheckNotDefined HealthCheckStatus = iota + // HealthCheckInternalError means some something failed obtaining or running + // a given health check + HealthCheckInternalError HealthCheckStatus = iota + // HealthCheckDefined means the healthcheck was found on the container + HealthCheckDefined HealthCheckStatus = iota + // HealthCheckStartup means the healthcheck was unhealthy, but is still + // either within the startup HC or the startup period of the healthcheck + HealthCheckStartup HealthCheckStatus = iota +) + +func (s HealthCheckStatus) String() string { + switch s { + case HealthCheckSuccess: + return HealthCheckHealthy + case HealthCheckStartup: + return HealthCheckStarting + case HealthCheckContainerStopped: + return HealthCheckStopped + default: + return HealthCheckUnhealthy + } +} + +// Healthcheck defaults. These are used both in the cli as well in +// libpod and were moved from cmd/podman/common +const ( + // DefaultHealthCheckInterval default value + DefaultHealthCheckInterval = "30s" + // DefaultHealthCheckRetries default value + DefaultHealthCheckRetries uint = 3 + // DefaultHealthCheckStartPeriod default value + DefaultHealthCheckStartPeriod = "0s" + // DefaultHealthCheckTimeout default value + DefaultHealthCheckTimeout = "30s" + // DefaultHealthMaxLogCount default value + DefaultHealthMaxLogCount uint = 5 + // DefaultHealthMaxLogSize default value + DefaultHealthMaxLogSize uint = 500 + // DefaultHealthCheckLocalDestination default value + DefaultHealthCheckLocalDestination string = "local" +) + +const HealthCheckEventsLoggerDestination string = "events_logger" + +// HealthConfig.Test options +const ( + // HealthConfigTestNone disables healthcheck + HealthConfigTestNone = "NONE" + // HealthConfigTestCmd execs arguments directly + HealthConfigTestCmd = "CMD" + // HealthConfigTestCmdShell runs commands with the system's default shell + HealthConfigTestCmdShell = "CMD-SHELL" +) + +// HealthCheckOnFailureAction defines how Podman reacts when a container's health +// status turns unhealthy. +type HealthCheckOnFailureAction int + +// Healthcheck on-failure actions. +const ( + // HealthCheckOnFailureActionNonce instructs Podman to not react on an unhealthy status. + HealthCheckOnFailureActionNone = iota // Must be first iota for backwards compatibility + // HealthCheckOnFailureActionInvalid denotes an invalid on-failure policy. + HealthCheckOnFailureActionInvalid = iota + // HealthCheckOnFailureActionNonce instructs Podman to kill the container on an unhealthy status. + HealthCheckOnFailureActionKill = iota + // HealthCheckOnFailureActionNonce instructs Podman to restart the container on an unhealthy status. + HealthCheckOnFailureActionRestart = iota + // HealthCheckOnFailureActionNonce instructs Podman to stop the container on an unhealthy status. + HealthCheckOnFailureActionStop = iota +) + +// String representations for on-failure actions. +const ( + strHealthCheckOnFailureActionNone = "none" + strHealthCheckOnFailureActionInvalid = "invalid" + strHealthCheckOnFailureActionKill = "kill" + strHealthCheckOnFailureActionRestart = "restart" + strHealthCheckOnFailureActionStop = "stop" +) + +// SupportedHealthCheckOnFailureActions lists all supported healthcheck restart policies. +var SupportedHealthCheckOnFailureActions = []string{ + strHealthCheckOnFailureActionNone, + strHealthCheckOnFailureActionKill, + strHealthCheckOnFailureActionRestart, + strHealthCheckOnFailureActionStop, +} + +// String returns the string representation of the HealthCheckOnFailureAction. +func (h HealthCheckOnFailureAction) String() string { + switch h { + case HealthCheckOnFailureActionNone: + return strHealthCheckOnFailureActionNone + case HealthCheckOnFailureActionKill: + return strHealthCheckOnFailureActionKill + case HealthCheckOnFailureActionRestart: + return strHealthCheckOnFailureActionRestart + case HealthCheckOnFailureActionStop: + return strHealthCheckOnFailureActionStop + default: + return strHealthCheckOnFailureActionInvalid + } +} + +// ParseHealthCheckOnFailureAction parses the specified string into a HealthCheckOnFailureAction. +// An error is returned for an invalid input. +func ParseHealthCheckOnFailureAction(s string) (HealthCheckOnFailureAction, error) { + switch s { + case "", strHealthCheckOnFailureActionNone: + return HealthCheckOnFailureActionNone, nil + case strHealthCheckOnFailureActionKill: + return HealthCheckOnFailureActionKill, nil + case strHealthCheckOnFailureActionRestart: + return HealthCheckOnFailureActionRestart, nil + case strHealthCheckOnFailureActionStop: + return HealthCheckOnFailureActionStop, nil + default: + err := fmt.Errorf("invalid on-failure action %q for health check: supported actions are %s", s, strings.Join(SupportedHealthCheckOnFailureActions, ",")) + return HealthCheckOnFailureActionInvalid, err + } +} + +// StartupHealthCheck is the configuration of a startup healthcheck. +type StartupHealthCheck struct { + manifest.Schema2HealthConfig + // Successes are the number of successes required to mark the startup HC + // as passed. + // If set to 0, a single success will mark the HC as passed. + Successes int `json:",omitempty"` +} + +type UpdateHealthCheckConfig struct { + // HealthLogDestination set the destination of the HealthCheck log. + // Directory path, local or events_logger (local use container state file) + // Warning: Changing this setting may cause the loss of previous logs! + HealthLogDestination *string `json:"health_log_destination,omitempty"` + // HealthMaxLogSize set maximum length in characters of stored HealthCheck log. + // ('0' value means an infinite log length) + HealthMaxLogSize *uint `json:"health_max_log_size,omitempty"` + // HealthMaxLogCount set maximum number of attempts in the HealthCheck log file. + // ('0' value means an infinite number of attempts in the log file) + HealthMaxLogCount *uint `json:"health_max_log_count,omitempty"` + // HealthOnFailure set the action to take once the container turns unhealthy. + HealthOnFailure *string `json:"health_on_failure,omitempty"` + // Disable healthchecks on container. + NoHealthCheck *bool `json:"no_healthcheck,omitempty"` + // HealthCmd set a healthcheck command for the container. ('none' disables the existing healthcheck) + HealthCmd *string `json:"health_cmd,omitempty"` + // HealthInterval set an interval for the healthcheck. + // (a value of disable results in no automatic timer setup) Changing this setting resets timer. + HealthInterval *string `json:"health_interval,omitempty"` + // HealthRetries set the number of retries allowed before a healthcheck is considered to be unhealthy. + HealthRetries *uint `json:"health_retries,omitempty"` + // HealthTimeout set the maximum time allowed to complete the healthcheck before an interval is considered failed. + HealthTimeout *string `json:"health_timeout,omitempty"` + // HealthStartPeriod set the initialization time needed for a container to bootstrap. + HealthStartPeriod *string `json:"health_start_period,omitempty"` + // HealthStartupCmd set a startup healthcheck command for the container. + HealthStartupCmd *string `json:"health_startup_cmd,omitempty"` + // HealthStartupInterval set an interval for the startup healthcheck. + // Changing this setting resets the timer, depending on the state of the container. + HealthStartupInterval *string `json:"health_startup_interval,omitempty"` + // HealthStartupRetries set the maximum number of retries before the startup healthcheck will restart the container. + HealthStartupRetries *uint `json:"health_startup_retries,omitempty"` + // HealthStartupTimeout set the maximum amount of time that the startup healthcheck may take before it is considered failed. + HealthStartupTimeout *string `json:"health_startup_timeout,omitempty"` + // HealthStartupSuccess set the number of consecutive successes before the startup healthcheck is marked as successful + // and the normal healthcheck begins (0 indicates any success will start the regular healthcheck) + HealthStartupSuccess *uint `json:"health_startup_success,omitempty"` +} + +func (u *UpdateHealthCheckConfig) IsStartupHealthCheckCommandSet(startupHealthCheck *StartupHealthCheck) bool { + containsStartupHealthCheckCmd := u.HealthStartupCmd != nil + containsFlags := (u.HealthStartupInterval != nil || u.HealthStartupRetries != nil || + u.HealthStartupTimeout != nil || u.HealthStartupSuccess != nil) + return startupHealthCheck == nil && !containsStartupHealthCheckCmd && containsFlags +} + +func (u *UpdateHealthCheckConfig) IsHealthCheckCommandSet(healthCheck *manifest.Schema2HealthConfig) bool { + containsStartupHealthCheckCmd := u.HealthCmd != nil + containsFlags := (u.HealthInterval != nil || u.HealthRetries != nil || + u.HealthTimeout != nil || u.HealthStartPeriod != nil) + return healthCheck == nil && !containsStartupHealthCheckCmd && containsFlags +} + +func (u *UpdateHealthCheckConfig) SetNewStartupHealthCheckConfigTo(healthCheckOptions *HealthCheckOptions) bool { + changed := false + + if u.HealthStartupCmd != nil { + healthCheckOptions.Cmd = *u.HealthStartupCmd + changed = true + } + + if u.HealthStartupInterval != nil { + healthCheckOptions.Interval = *u.HealthStartupInterval + changed = true + } + + if u.HealthStartupRetries != nil { + healthCheckOptions.Retries = int(*u.HealthStartupRetries) + changed = true + } + + if u.HealthStartupTimeout != nil { + healthCheckOptions.Timeout = *u.HealthStartupTimeout + changed = true + } + + if u.HealthStartupSuccess != nil { + healthCheckOptions.Successes = int(*u.HealthStartupSuccess) + changed = true + } + + healthCheckOptions.StartPeriod = "1s" + + return changed +} + +func (u *UpdateHealthCheckConfig) SetNewHealthCheckConfigTo(healthCheckOptions *HealthCheckOptions) bool { + changed := false + + if u.HealthCmd != nil { + healthCheckOptions.Cmd = *u.HealthCmd + changed = true + } + + if u.HealthInterval != nil { + healthCheckOptions.Interval = *u.HealthInterval + changed = true + } + + if u.HealthRetries != nil { + healthCheckOptions.Retries = int(*u.HealthRetries) + changed = true + } + + if u.HealthTimeout != nil { + healthCheckOptions.Timeout = *u.HealthTimeout + changed = true + } + + if u.HealthStartPeriod != nil { + healthCheckOptions.StartPeriod = *u.HealthStartPeriod + changed = true + } + + return changed +} + +func GetValidHealthCheckDestination(destination string) (string, error) { + if destination == HealthCheckEventsLoggerDestination || destination == DefaultHealthCheckLocalDestination { + return destination, nil + } + + fileInfo, err := os.Stat(destination) + if err != nil { + return "", fmt.Errorf("HealthCheck Log '%s' destination error: %w", destination, err) + } + mode := fileInfo.Mode() + if !mode.IsDir() { + return "", fmt.Errorf("HealthCheck Log '%s' destination must be directory", destination) + } + + absPath, err := filepath.Abs(destination) + if err != nil { + return "", err + } + return absPath, nil +} + +func (u *UpdateHealthCheckConfig) GetNewGlobalHealthCheck() (GlobalHealthCheckOptions, error) { + globalOptions := GlobalHealthCheckOptions{} + + healthLogDestination := u.HealthLogDestination + if u.HealthLogDestination != nil { + dest, err := GetValidHealthCheckDestination(*u.HealthLogDestination) + if err != nil { + return GlobalHealthCheckOptions{}, err + } + healthLogDestination = &dest + } + globalOptions.HealthLogDestination = healthLogDestination + + globalOptions.HealthMaxLogSize = u.HealthMaxLogSize + + globalOptions.HealthMaxLogCount = u.HealthMaxLogCount + + if u.HealthOnFailure != nil { + val, err := ParseHealthCheckOnFailureAction(*u.HealthOnFailure) + if err != nil { + return globalOptions, err + } + globalOptions.HealthCheckOnFailureAction = &val + } + + return globalOptions, nil +} + +type HealthCheckOptions struct { + Cmd string + Interval string + Retries int + Timeout string + StartPeriod string + Successes int +} + +type GlobalHealthCheckOptions struct { + HealthLogDestination *string + HealthMaxLogCount *uint + HealthMaxLogSize *uint + HealthCheckOnFailureAction *HealthCheckOnFailureAction +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/info.go b/vendor/github.com/containers/podman/v5/libpod/define/info.go new file mode 100644 index 00000000..2162a33f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/info.go @@ -0,0 +1,170 @@ +package define + +import ( + "go.podman.io/common/libnetwork/types" + "go.podman.io/storage/pkg/idtools" +) + +// Info is the overall struct that describes the host system +// running libpod/podman +// swagger:model LibpodInfo +type Info struct { + Host *HostInfo `json:"host"` + Store *StoreInfo `json:"store"` + Registries map[string]any `json:"registries"` + Plugins Plugins `json:"plugins"` + Version Version `json:"version"` +} + +// SecurityInfo describes the libpod host +type SecurityInfo struct { + AppArmorEnabled bool `json:"apparmorEnabled"` + DefaultCapabilities string `json:"capabilities"` + Rootless bool `json:"rootless"` + SECCOMPEnabled bool `json:"seccompEnabled"` + SECCOMPProfilePath string `json:"seccompProfilePath"` + SELinuxEnabled bool `json:"selinuxEnabled"` +} + +// HostInfo describes the libpod host +type HostInfo struct { + Arch string `json:"arch"` + BuildahVersion string `json:"buildahVersion"` + CgroupManager string `json:"cgroupManager"` + CgroupsVersion string `json:"cgroupVersion"` + CgroupControllers []string `json:"cgroupControllers"` + Conmon *ConmonInfo `json:"conmon"` + CPUs int `json:"cpus"` + CPUUtilization *CPUUsage `json:"cpuUtilization"` + DatabaseBackend string `json:"databaseBackend"` + Distribution DistributionInfo `json:"distribution"` + EventLogger string `json:"eventLogger"` + FreeLocks *uint32 `json:"freeLocks,omitempty"` + Hostname string `json:"hostname"` + IDMappings IDMappings `json:"idMappings"` + Kernel string `json:"kernel"` + LogDriver string `json:"logDriver"` + MemFree int64 `json:"memFree"` + MemTotal int64 `json:"memTotal"` + NetworkBackend string `json:"networkBackend"` + NetworkBackendInfo types.NetworkInfo `json:"networkBackendInfo"` + OCIRuntime *OCIRuntimeInfo `json:"ociRuntime"` + OS string `json:"os"` + // RemoteSocket returns the UNIX domain socket the Podman service is listening on + RemoteSocket *RemoteSocket `json:"remoteSocket,omitempty"` + // RootlessNetworkCmd returns the default rootless network command (slirp4netns or pasta) + RootlessNetworkCmd string `json:"rootlessNetworkCmd"` + RuntimeInfo map[string]any `json:"runtimeInfo,omitempty"` + // ServiceIsRemote is true when the podman/libpod service is remote to the client + ServiceIsRemote bool `json:"serviceIsRemote"` + Security SecurityInfo `json:"security"` + Slirp4NetNS SlirpInfo `json:"slirp4netns"` + Pasta PastaInfo `json:"pasta"` + + SwapFree int64 `json:"swapFree"` + SwapTotal int64 `json:"swapTotal"` + Uptime string `json:"uptime"` + Variant string `json:"variant"` + Linkmode string `json:"linkmode"` + + EmulatedArchitectures []string `json:"emulatedArchitectures,omitempty"` +} + +// RemoteSocket describes information about the API socket +type RemoteSocket struct { + Path string `json:"path,omitempty"` + Exists bool `json:"exists"` +} + +// SlirpInfo describes the slirp executable that is being used +type SlirpInfo struct { + Executable string `json:"executable"` + Package string `json:"package"` + Version string `json:"version"` +} + +// PastaInfo describes the pasta executable that is being used +type PastaInfo struct { + Executable string `json:"executable"` + Package string `json:"package"` + Version string `json:"version"` +} + +// IDMappings describe the GID and UID mappings +type IDMappings struct { + GIDMap []idtools.IDMap `json:"gidmap"` + UIDMap []idtools.IDMap `json:"uidmap"` +} + +// DistributionInfo describes the host distribution for libpod +type DistributionInfo struct { + Distribution string `json:"distribution"` + Variant string `json:"variant,omitempty"` + Version string `json:"version"` + Codename string `json:"codename,omitempty"` +} + +// ConmonInfo describes the conmon executable being used +type ConmonInfo struct { + Package string `json:"package"` + Path string `json:"path"` + Version string `json:"version"` +} + +// OCIRuntimeInfo describes the runtime (crun or runc) being +// used with podman +type OCIRuntimeInfo struct { + Name string `json:"name"` + Package string `json:"package"` + Path string `json:"path"` + Version string `json:"version"` +} + +// StoreInfo describes the container storage and its +// attributes +type StoreInfo struct { + ConfigFile string `json:"configFile"` + ContainerStore ContainerStore `json:"containerStore"` + GraphDriverName string `json:"graphDriverName"` + GraphOptions map[string]any `json:"graphOptions"` + GraphRoot string `json:"graphRoot"` + // GraphRootAllocated is how much space the graphroot has in bytes + GraphRootAllocated uint64 `json:"graphRootAllocated"` + // GraphRootUsed is how much of graphroot is used in bytes + GraphRootUsed uint64 `json:"graphRootUsed"` + GraphStatus map[string]string `json:"graphStatus"` + ImageCopyTmpDir string `json:"imageCopyTmpDir"` + ImageStore ImageStore `json:"imageStore"` + RunRoot string `json:"runRoot"` + VolumePath string `json:"volumePath"` + TransientStore bool `json:"transientStore"` +} + +// ImageStore describes the image store. Right now only the number +// of images present +type ImageStore struct { + Number int `json:"number"` +} + +// ContainerStore describes the quantity of containers in the +// store by status +type ContainerStore struct { + Number int `json:"number"` + Paused int `json:"paused"` + Running int `json:"running"` + Stopped int `json:"stopped"` +} + +type Plugins struct { + Volume []string `json:"volume"` + Network []string `json:"network"` + Log []string `json:"log"` + // Authorization is provided for compatibility, will always be nil as Podman has no daemon + Authorization []string `json:"authorization"` +} + +type CPUUsage struct { + UserPercent float64 `json:"userPercent"` + SystemPercent float64 `json:"systemPercent"` + IdlePercent float64 `json:"idlePercent"` +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/mount.go b/vendor/github.com/containers/podman/v5/libpod/define/mount.go new file mode 100644 index 00000000..83e643f8 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/mount.go @@ -0,0 +1,12 @@ +package define + +const ( + // TypeDevpts is the type for creating a devpts + TypeDevpts = "devpts" + // TypeTmpfs is the type for mounting tmpfs + TypeTmpfs = "tmpfs" + // TypeRamfs is the type for mounting ramfs + TypeRamfs = "ramfs" + // TypeVolume is the type for named volumes + TypeVolume = "volume" +) diff --git a/vendor/github.com/containers/podman/v5/libpod/define/mount_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/define/mount_freebsd.go new file mode 100644 index 00000000..3150ffbe --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/mount_freebsd.go @@ -0,0 +1,13 @@ +//go:build freebsd + +package define + +const ( + // TypeBind is the type for mounting host dir + TypeBind = "nullfs" +) + +var ( + // Mount potions for bind + BindOptions = []string{} +) diff --git a/vendor/github.com/containers/podman/v5/libpod/define/mount_linux.go b/vendor/github.com/containers/podman/v5/libpod/define/mount_linux.go new file mode 100644 index 00000000..7c8e5fe7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/mount_linux.go @@ -0,0 +1,13 @@ +//go:build linux + +package define + +const ( + // TypeBind is the type for mounting host dir + TypeBind = "bind" +) + +var ( + // Mount potions for bind + BindOptions = []string{TypeBind} +) diff --git a/vendor/github.com/containers/podman/v5/libpod/define/mount_unsupported.go b/vendor/github.com/containers/podman/v5/libpod/define/mount_unsupported.go new file mode 100644 index 00000000..cb8642fe --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/mount_unsupported.go @@ -0,0 +1,8 @@ +//go:build !linux && !freebsd + +package define + +const ( + // TypeBind is the type for mounting host dir + TypeBind = "bind" +) diff --git a/vendor/github.com/containers/podman/v5/libpod/define/pod_inspect.go b/vendor/github.com/containers/podman/v5/libpod/define/pod_inspect.go new file mode 100644 index 00000000..a66cf666 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/pod_inspect.go @@ -0,0 +1,159 @@ +package define + +import ( + "net" + "time" +) + +// InspectPodData contains detailed information on a pod's configuration and +// state. It is used as the output of Inspect on pods. +type InspectPodData struct { + // ID is the ID of the pod. + ID string `json:"Id"` + // Name is the name of the pod. + Name string + // Namespace is the Libpod namespace the pod is placed in. + Namespace string `json:"Namespace,omitempty"` + // Created is the time when the pod was created. + Created time.Time + // CreateCommand is the full command plus arguments of the process the + // container has been created with. + CreateCommand []string `json:"CreateCommand,omitempty"` + // ExitPolicy of the pod. + ExitPolicy string `json:"ExitPolicy,omitempty"` + // State represents the current state of the pod. + State string `json:"State"` + // Hostname is the hostname that the pod will set. + Hostname string + // Labels is a set of key-value labels that have been applied to the + // pod. + Labels map[string]string `json:"Labels,omitempty"` + // CreateCgroup is whether this pod will create its own Cgroup to group + // containers under. + CreateCgroup bool + // CgroupParent is the parent of the pod's Cgroup. + CgroupParent string `json:"CgroupParent,omitempty"` + // CgroupPath is the path to the pod's Cgroup. + CgroupPath string `json:"CgroupPath,omitempty"` + // CreateInfra is whether this pod will create an infra container to + // share namespaces. + CreateInfra bool + // InfraContainerID is the ID of the pod's infra container, if one is + // present. + InfraContainerID string `json:"InfraContainerID,omitempty"` + // InfraConfig is the configuration of the infra container of the pod. + // Will only be set if CreateInfra is true. + InfraConfig *InspectPodInfraConfig `json:"InfraConfig,omitempty"` + // SharedNamespaces contains a list of namespaces that will be shared by + // containers within the pod. Can only be set if CreateInfra is true. + SharedNamespaces []string `json:"SharedNamespaces,omitempty"` + // NumContainers is the number of containers in the pod, including the + // infra container. + NumContainers uint + // Containers gives a brief summary of all containers in the pod and + // their current status. + Containers []InspectPodContainerInfo `json:"Containers,omitempty"` + // CPUPeriod contains the CPU period of the pod + CPUPeriod uint64 `json:"cpu_period,omitempty"` + // CPUQuota contains the CPU quota of the pod + CPUQuota int64 `json:"cpu_quota,omitempty"` + // CPUShares contains the cpu shares for the pod + CPUShares uint64 `json:"cpu_shares,omitempty"` + // CPUSetCPUs contains linux specific CPU data for the pod + CPUSetCPUs string `json:"cpuset_cpus,omitempty"` + // CPUSetMems contains linux specific CPU data for the pod + CPUSetMems string `json:"cpuset_mems,omitempty"` + // Mounts contains volume related information for the pod + Mounts []InspectMount `json:"mounts,omitempty"` + // Devices contains the specified host devices + Devices []InspectDevice `json:"devices,omitempty"` + // BlkioDeviceReadBps contains the Read/Access limit for the pod's devices + BlkioDeviceReadBps []InspectBlkioThrottleDevice `json:"device_read_bps,omitempty"` + // BlkioDeviceReadBps contains the Read/Access limit for the pod's devices + BlkioDeviceWriteBps []InspectBlkioThrottleDevice `json:"device_write_bps,omitempty"` + // VolumesFrom contains the containers that the pod inherits mounts from + VolumesFrom []string `json:"volumes_from,omitempty"` + // SecurityOpt contains the specified security labels and related SELinux information + SecurityOpts []string `json:"security_opt,omitempty"` + // MemoryLimit contains the specified cgroup memory limit for the pod + MemoryLimit uint64 `json:"memory_limit,omitempty"` + // MemorySwap contains the specified memory swap limit for the pod + MemorySwap uint64 `json:"memory_swap,omitempty"` + // BlkioWeight contains the blkio weight limit for the pod + BlkioWeight uint64 `json:"blkio_weight,omitempty"` + // BlkioWeightDevice contains the blkio weight device limits for the pod + BlkioWeightDevice []InspectBlkioWeightDevice `json:"blkio_weight_device,omitempty"` + // RestartPolicy of the pod. + RestartPolicy string `json:"RestartPolicy,omitempty"` + // Number of the pod's Libpod lock. + LockNumber uint32 +} + +// InspectPodInfraConfig contains the configuration of the pod's infra +// container. +type InspectPodInfraConfig struct { + // PortBindings are ports that will be forwarded to the infra container + // and then shared with the pod. + PortBindings map[string][]InspectHostPort + // HostNetwork is whether the infra container (and thus the whole pod) + // will use the host's network and not create a network namespace. + HostNetwork bool + // StaticIP is a static IPv4 that will be assigned to the infra + // container and then used by the pod. + // swagger:strfmt ipv4 + StaticIP net.IP + // StaticMAC is a static MAC address that will be assigned to the infra + // container and then used by the pod. + StaticMAC string + // NoManageResolvConf indicates that the pod will not manage resolv.conf + // and instead each container will handle their own. + NoManageResolvConf bool + // DNSServer is a set of DNS Servers that will be used by the infra + // container's resolv.conf and shared with the remainder of the pod. + DNSServer []string + // DNSSearch is a set of DNS search domains that will be used by the + // infra container's resolv.conf and shared with the remainder of the + // pod. + DNSSearch []string + // DNSOption is a set of DNS options that will be used by the infra + // container's resolv.conf and shared with the remainder of the pod. + DNSOption []string + // NoManageHostname indicates that the pod will not manage /etc/hostname + // and instead each container will handle their own. + NoManageHostname bool + // NoManageHosts indicates that the pod will not manage /etc/hosts and + // instead each container will handle their own. + NoManageHosts bool + // HostAdd adds a number of hosts to the infra container's resolv.conf + // which will be shared with the rest of the pod. + HostAdd []string + // HostsFile is the base file to create the `/etc/hosts` file inside the infra container + // which will be shared with the rest of the pod. + HostsFile string + // Networks is a list of networks the pod will join. + Networks []string + // NetworkOptions are additional options for each network + NetworkOptions map[string][]string + // CPUPeriod contains the CPU period of the pod + CPUPeriod uint64 `json:"cpu_period,omitempty"` + // CPUQuota contains the CPU quota of the pod + CPUQuota int64 `json:"cpu_quota,omitempty"` + // CPUSetCPUs contains linux specific CPU data for the container + CPUSetCPUs string `json:"cpuset_cpus,omitempty"` + // Pid is the PID namespace mode of the pod's infra container + PidNS string `json:"pid_ns,omitempty"` + // UserNS is the usernamespace that all the containers in the pod will join. + UserNS string `json:"userns,omitempty"` + // UtsNS is the uts namespace that all containers in the pod will join + UtsNS string `json:"uts_ns,omitempty"` +} + +// InspectPodContainerInfo contains information on a container in a pod. +type InspectPodContainerInfo struct { + // ID is the ID of the container. + ID string `json:"Id"` + // Name is the name of the container. + Name string + // State is the current status of the container. + State string +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/podstate.go b/vendor/github.com/containers/podman/v5/libpod/define/podstate.go new file mode 100644 index 00000000..e0267197 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/podstate.go @@ -0,0 +1,22 @@ +package define + +const ( + // PodStateCreated indicates the pod is created but has not been started + PodStateCreated = "Created" + // PodStateErrored indicates the pod is in an errored state where + // information about it can no longer be retrieved + PodStateErrored = "Error" + // PodStateExited indicates the pod ran but has been stopped + PodStateExited = "Exited" + // PodStatePaused indicates the pod has been paused + PodStatePaused = "Paused" + // PodStateRunning indicates that all of the containers in the pod are + // running. + PodStateRunning = "Running" + // PodStateDegraded indicates that at least one, but not all, of the + // containers in the pod are running. + PodStateDegraded = "Degraded" + // PodStateStopped indicates all of the containers belonging to the pod + // are stopped. + PodStateStopped = "Stopped" +) diff --git a/vendor/github.com/containers/podman/v5/libpod/define/runtime.go b/vendor/github.com/containers/podman/v5/libpod/define/runtime.go new file mode 100644 index 00000000..1539e19e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/runtime.go @@ -0,0 +1,28 @@ +package define + +import "time" + +// RuntimeStateStore is a constant indicating which state store implementation +// should be used by libpod +type RuntimeStateStore int + +const ( + // InvalidStateStore is an invalid state store + InvalidStateStore RuntimeStateStore = iota + // InMemoryStateStore is an in-memory state that will not persist data + // on containers and pods between libpod instances or after system + // reboot + InMemoryStateStore RuntimeStateStore = iota + // SQLiteStateStore is a state backed by a SQLite database + // It is presently disabled + SQLiteStateStore RuntimeStateStore = iota + // BoltDBStateStore is a state backed by a BoltDB database + BoltDBStateStore RuntimeStateStore = iota + // ContainerCreateTimeout is the timeout before we decide we've failed + // to create a container. + // TODO: Make this generic - all OCI runtime operations should use the + // same timeout, this one. + // TODO: Consider dropping from 240 to 60 seconds. I don't think waiting + // 4 minutes versus 1 minute makes a real difference. + ContainerCreateTimeout = 240 * time.Second +) diff --git a/vendor/github.com/containers/podman/v5/libpod/define/sdnotify.go b/vendor/github.com/containers/podman/v5/libpod/define/sdnotify.go new file mode 100644 index 00000000..f188a365 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/sdnotify.go @@ -0,0 +1,21 @@ +package define + +import "fmt" + +// Strings used for --sdnotify option to podman +const ( + SdNotifyModeConmon = "conmon" + SdNotifyModeContainer = "container" + SdNotifyModeHealthy = "healthy" + SdNotifyModeIgnore = "ignore" +) + +// ValidateSdNotifyMode validates the specified mode. +func ValidateSdNotifyMode(mode string) error { + switch mode { + case "", SdNotifyModeContainer, SdNotifyModeConmon, SdNotifyModeIgnore, SdNotifyModeHealthy: + return nil + default: + return fmt.Errorf("%w: invalid sdnotify value %q: must be %s, %s, %s or %s", ErrInvalidArg, mode, SdNotifyModeConmon, SdNotifyModeContainer, SdNotifyModeHealthy, SdNotifyModeIgnore) + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/version.go b/vendor/github.com/containers/podman/v5/libpod/define/version.go new file mode 100644 index 00000000..6832aeac --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/version.go @@ -0,0 +1,60 @@ +package define + +import ( + "runtime" + "strconv" + "time" + + "github.com/containers/podman/v5/version" +) + +// Overwritten at build time +var ( + // GitCommit is the commit that the binary is being built from. + // It will be populated by the Makefile. + gitCommit string + // BuildInfo is the time at which the binary was built + // It will be populated by the Makefile. + buildInfo string + // BuildOrigin is the packager of the binary. + // It will be populated at build-time. + buildOrigin string +) + +// Version is an output struct for API +type Version struct { + APIVersion string + Version string + GoVersion string + GitCommit string + BuiltTime string + Built int64 + BuildOrigin string `json:",omitempty" yaml:",omitempty"` + OsArch string + Os string +} + +// GetVersion returns a VersionOutput struct for API and podman +func GetVersion() (Version, error) { + var err error + var buildTime int64 + if buildInfo != "" { + // Converts unix time from string to int64 + buildTime, err = strconv.ParseInt(buildInfo, 10, 64) + + if err != nil { + return Version{}, err + } + } + return Version{ + APIVersion: version.APIVersion[version.Libpod][version.CurrentAPI].String(), + Version: version.Version.String(), + GoVersion: runtime.Version(), + GitCommit: gitCommit, + BuiltTime: time.Unix(buildTime, 0).Format(time.ANSIC), + Built: buildTime, + BuildOrigin: buildOrigin, + OsArch: runtime.GOOS + "/" + runtime.GOARCH, + Os: runtime.GOOS, + }, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/define/volume_inspect.go b/vendor/github.com/containers/podman/v5/libpod/define/volume_inspect.go new file mode 100644 index 00000000..ff880e22 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/define/volume_inspect.go @@ -0,0 +1,72 @@ +package define + +import ( + "time" +) + +// InspectVolumeData is the output of Inspect() on a volume. It is matched to +// the format of 'docker volume inspect'. +type InspectVolumeData struct { + // Name is the name of the volume. + Name string `json:"Name"` + // Driver is the driver used to create the volume. + // If set to "local" or "", the Local driver (Podman built-in code) is + // used to service the volume; otherwise, a volume plugin with the given + // name is used to mount and manage the volume. + Driver string `json:"Driver"` + // Mountpoint is the path on the host where the volume is mounted. + Mountpoint string `json:"Mountpoint"` + // CreatedAt is the date and time the volume was created at. This is not + // stored for older Libpod volumes; if so, it will be omitted. + CreatedAt time.Time `json:"CreatedAt"` + // Status is used to return information on the volume's current state, + // if the volume was created using a volume plugin (uses a Driver that + // is not the local driver). + // Status is provided to us by an external program, so no guarantees are + // made about its format or contents. Further, it is an optional field, + // so it may not be set even in cases where a volume plugin is in use. + Status map[string]any `json:"Status,omitempty"` + // Labels includes the volume's configured labels, key:value pairs that + // can be passed during volume creation to provide information for third + // party tools. + Labels map[string]string `json:"Labels"` + // Scope is unused and provided solely for Docker compatibility. It is + // unconditionally set to "local". + Scope string `json:"Scope"` + // Options is a set of options that were used when creating the volume. + // For the Local driver, these are mount options that will be used to + // determine how a local filesystem is mounted; they are handled as + // parameters to Mount in a manner described in the volume create + // manpage. + // For non-local drivers, these are passed as-is to the volume plugin. + Options map[string]string `json:"Options"` + // UID is the UID that the volume was created with. + UID int `json:"UID,omitempty"` + // GID is the GID that the volume was created with. + GID int `json:"GID,omitempty"` + // Anonymous indicates that the volume was created as an anonymous + // volume for a specific container, and will be removed when any + // container using it is removed. + Anonymous bool `json:"Anonymous,omitempty"` + // MountCount is the number of times this volume has been mounted. + MountCount uint `json:"MountCount"` + // NeedsCopyUp indicates that the next time the volume is mounted into + NeedsCopyUp bool `json:"NeedsCopyUp,omitempty"` + // NeedsChown indicates that the next time the volume is mounted into + // a container, the container will chown the volume to the container process + // UID/GID. + NeedsChown bool `json:"NeedsChown,omitempty"` + // Timeout is the specified driver timeout if given + Timeout uint `json:"Timeout,omitempty"` + // StorageID is the ID of the container backing the volume in c/storage. + // Only used with Image Volumes. + StorageID string `json:"StorageID,omitempty"` + // LockNumber is the number of the volume's Libpod lock. + LockNumber uint32 +} + +type VolumeReload struct { + Added []string + Removed []string + Errors []error +} diff --git a/vendor/github.com/containers/podman/v5/libpod/diff.go b/vendor/github.com/containers/podman/v5/libpod/diff.go new file mode 100644 index 00000000..1cff86e9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/diff.go @@ -0,0 +1,83 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/layers" + "go.podman.io/storage/pkg/archive" +) + +var initInodes = map[string]bool{ + "/dev": true, + "/etc/hostname": true, + "/etc/hosts": true, + "/etc/resolv.conf": true, + "/proc": true, + "/run": true, + "/run/notify": true, + "/run/.containerenv": true, + "/run/secrets": true, + define.ContainerInitPath: true, + "/sys": true, + "/etc/mtab": true, +} + +// GetDiff returns the differences between the two images, layers, or containers +func (r *Runtime) GetDiff(from, to string, diffType define.DiffType) ([]archive.Change, error) { + toLayer, err := r.getLayerID(to, diffType) + if err != nil { + return nil, err + } + fromLayer := "" + if from != "" { + fromLayer, err = r.getLayerID(from, diffType) + if err != nil { + return nil, err + } + } + var rchanges []archive.Change + changes, err := r.store.Changes(fromLayer, toLayer) + if err == nil { + for _, c := range changes { + if initInodes[c.Path] { + continue + } + rchanges = append(rchanges, c) + } + } + return rchanges, err +} + +// GetLayerID gets a full layer id given a full or partial id +// If the id matches a container or image, the id of the top layer is returned +// If the id matches a layer, the top layer id is returned +func (r *Runtime) getLayerID(id string, diffType define.DiffType) (string, error) { + var lastErr error + if diffType&define.DiffImage == define.DiffImage { + toImage, _, err := r.libimageRuntime.LookupImage(id, nil) + if err == nil { + return toImage.TopLayer(), nil + } + lastErr = err + } + + if diffType&define.DiffContainer == define.DiffContainer { + toCtr, err := r.store.Container(id) + if err == nil { + return toCtr.LayerID, nil + } + lastErr = err + } + + if diffType == define.DiffAll { + toLayer, err := layers.FullID(r.store, id) + if err == nil { + return toLayer, nil + } + lastErr = err + } + return "", fmt.Errorf("%s not found: %w", id, lastErr) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/doc.go b/vendor/github.com/containers/podman/v5/libpod/doc.go new file mode 100644 index 00000000..94815318 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/doc.go @@ -0,0 +1,11 @@ +// The libpod library is not stable and we do not support use cases outside of +// this repository. The API can change at any time even with patch releases. +// +// If you need a stable interface Podman provides a HTTP API which follows semver, +// please see https://docs.podman.io/en/latest/markdown/podman-system-service.1.html +// to start the api service and https://docs.podman.io/en/latest/_static/api.html +// for the API reference. +// +// We also provide stable go bindings to talk to the api service from another go +// program, see the pkg/bindings directory. +package libpod diff --git a/vendor/github.com/containers/podman/v5/libpod/driver/driver.go b/vendor/github.com/containers/podman/v5/libpod/driver/driver.go new file mode 100644 index 00000000..35ed69fe --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/driver/driver.go @@ -0,0 +1,26 @@ +package driver + +import ( + "github.com/containers/podman/v5/libpod/define" + "go.podman.io/storage" +) + +// GetDriverData returns information on a given store's running graph driver. +func GetDriverData(store storage.Store, layerID string) (*define.DriverData, error) { + driver, err := store.GraphDriver() + if err != nil { + return nil, err + } + metaData, err := driver.Metadata(layerID) + if err != nil { + return nil, err + } + if mountTimes, err := store.Mounted(layerID); mountTimes == 0 || err != nil { + delete(metaData, "MergedDir") + } + + return &define.DriverData{ + Name: driver.String(), + Data: metaData, + }, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/events.go b/vendor/github.com/containers/podman/v5/libpod/events.go new file mode 100644 index 00000000..aac29ee5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events.go @@ -0,0 +1,257 @@ +//go:build !remote + +package libpod + +import ( + "context" + "fmt" + "path/filepath" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/sirupsen/logrus" +) + +// newEventer returns an eventer that can be used to read/write events +func (r *Runtime) newEventer() (events.Eventer, error) { + if r.config.Engine.EventsLogFilePath == "" { + // default, use path under tmpdir when none was explicitly set by the user + r.config.Engine.EventsLogFilePath = filepath.Join(r.config.Engine.TmpDir, "events", "events.log") + } + options := events.EventerOptions{ + EventerType: r.config.Engine.EventsLogger, + LogFilePath: r.config.Engine.EventsLogFilePath, + LogFileMaxSize: r.config.Engine.EventsLogMaxSize(), + } + return events.NewEventer(options) +} + +// newContainerEvent creates a new event based on a container +func (c *Container) newContainerEvent(status events.Status) { + if err := c.newContainerEventWithInspectData(status, define.HealthCheckResults{}, false); err != nil { + logrus.Errorf("Unable to write container event: %v", err) + } +} + +// newContainerHealthCheckEvent creates a new healthcheck event with the given status +func (c *Container) newContainerHealthCheckEvent(healthCheckResult define.HealthCheckResults) { + if err := c.newContainerEventWithInspectData(events.HealthStatus, healthCheckResult, false); err != nil { + logrus.Errorf("Unable to write container event: %v", err) + } +} + +// newContainerEventWithInspectData creates a new event and sets the +// ContainerInspectData field if inspectData is set. +func (c *Container) newContainerEventWithInspectData(status events.Status, healthCheckResult define.HealthCheckResults, inspectData bool) error { + e := events.NewEvent(status) + e.ID = c.ID() + e.Name = c.Name() + e.Image = c.config.RootfsImageName + e.Type = events.Container + e.HealthStatus = healthCheckResult.Status + if c.HealthCheckLogDestination() == define.HealthCheckEventsLoggerDestination { + if len(healthCheckResult.Log) > 0 { + logData, err := json.Marshal(healthCheckResult.Log[len(healthCheckResult.Log)-1]) + if err != nil { + return fmt.Errorf("unable to marshall healthcheck log for writing: %w", err) + } + e.HealthLog = string(logData) + } + } + e.HealthFailingStreak = healthCheckResult.FailingStreak + + e.Details = events.Details{ + PodID: c.PodID(), + Attributes: c.Labels(), + } + + if inspectData { + err := func() error { + data, err := c.inspectLocked(true) + if err != nil { + return err + } + rawData, err := json.Marshal(data) + if err != nil { + return err + } + e.Details.ContainerInspectData = string(rawData) + return nil + }() + if err != nil { + return fmt.Errorf("adding inspect data to container-create event: %v", err) + } + } + + if status == events.Remove { + exitCode, err := c.runtime.state.GetContainerExitCode(c.ID()) + if err == nil { + intExitCode := int(exitCode) + e.ContainerExitCode = &intExitCode + } + } + + return c.runtime.eventer.Write(e) +} + +// newContainerExitedEvent creates a new event for a container's death +func (c *Container) newContainerExitedEvent(exitCode int32) { + e := events.NewEvent(events.Exited) + e.ID = c.ID() + e.Name = c.Name() + e.Image = c.config.RootfsImageName + e.Type = events.Container + e.PodID = c.PodID() + intExitCode := int(exitCode) + e.ContainerExitCode = &intExitCode + + e.Details = events.Details{ + Attributes: c.Labels(), + } + + if err := c.runtime.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write container exited event: %q", err) + } +} + +// newExecDiedEvent creates a new event for an exec session's death +func (c *Container) newExecDiedEvent(sessionID string, exitCode int) { + e := events.NewEvent(events.ExecDied) + e.ID = c.ID() + e.Name = c.Name() + e.Image = c.config.RootfsImageName + e.Type = events.Container + intExitCode := exitCode + e.ContainerExitCode = &intExitCode + e.Attributes = make(map[string]string) + e.Attributes["execID"] = sessionID + + e.Details = events.Details{ + Attributes: c.Labels(), + } + + if err := c.runtime.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write exec died event: %q", err) + } +} + +// newNetworkEvent creates a new event based on a network create/remove +func (r *Runtime) NewNetworkEvent(status events.Status, netName, netID, netDriver string) { + e := events.NewEvent(status) + e.Network = netName + e.ID = netID + e.Attributes = make(map[string]string) + e.Attributes["driver"] = netDriver + e.Type = events.Network + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write network event: %q", err) + } +} + +// newNetworkEvent creates a new event based on a network connect/disconnect +func (c *Container) newNetworkEvent(status events.Status, netName string) { + e := events.NewEvent(status) + e.ID = c.ID() + e.Name = c.Name() + e.Type = events.Network + e.Network = netName + if err := c.runtime.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write pod event: %q", err) + } +} + +// newPodEvent creates a new event for a libpod pod +func (p *Pod) newPodEvent(status events.Status) { + e := events.NewEvent(status) + e.ID = p.ID() + e.Name = p.Name() + e.Type = events.Pod + if err := p.runtime.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write pod event: %q", err) + } +} + +// NewSystemEvent creates a new event for libpod as a whole. +func (r *Runtime) NewSystemEvent(status events.Status) { + e := events.NewEvent(status) + e.Type = events.System + + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write system event: %q", err) + } +} + +// newVolumeEvent creates a new event for a libpod volume +func (v *Volume) newVolumeEvent(status events.Status) { + e := events.NewEvent(status) + e.Name = v.Name() + e.Type = events.Volume + if err := v.runtime.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write volume event: %q", err) + } +} + +// NewSecretEvent creates a new event for a libpod secret +func (r *Runtime) NewSecretEvent(status events.Status, secretID string) { + e := events.NewEvent(status) + e.ID = secretID + e.Type = events.Secret + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write secret event: %q", err) + } +} + +// Events is a wrapper function for everyone to begin tailing the events log +// with options +func (r *Runtime) Events(ctx context.Context, options events.ReadOptions) error { + return r.eventer.Read(ctx, options) +} + +// GetEvents reads the event log and returns events based on input filters +func (r *Runtime) GetEvents(ctx context.Context, filters []string) ([]*events.Event, error) { + eventChannel := make(chan events.ReadResult) + options := events.ReadOptions{ + EventChannel: eventChannel, + Filters: filters, + FromStart: true, + Stream: false, + } + + err := r.eventer.Read(ctx, options) + if err != nil { + return nil, err + } + + logEvents := make([]*events.Event, 0, len(eventChannel)) + for evt := range eventChannel { + // we ignore any error here, this is only used on the backup + // GetExecDiedEvent() died path as best effort anyway + if evt.Error == nil { + logEvents = append(logEvents, evt.Event) + } + } + + return logEvents, nil +} + +// GetExecDiedEvent takes a container name or ID, exec session ID, and returns +// that exec session's Died event (if it has already occurred). +func (r *Runtime) GetExecDiedEvent(ctx context.Context, nameOrID, execSessionID string) (*events.Event, error) { + filters := []string{ + fmt.Sprintf("container=%s", nameOrID), + "event=exec_died", + "type=container", + fmt.Sprintf("label=execID=%s", execSessionID), + } + + containerEvents, err := r.GetEvents(ctx, filters) + if err != nil { + return nil, err + } + // There *should* only be one event maximum. + // But... just in case... let's not blow up if there's more than one. + if len(containerEvents) < 1 { + return nil, fmt.Errorf("exec died event for session %s (container %s) not found: %w", execSessionID, nameOrID, events.ErrEventNotFound) + } + return containerEvents[len(containerEvents)-1], nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/events/config.go b/vendor/github.com/containers/podman/v5/libpod/events/config.go new file mode 100644 index 00000000..053e7e3c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/config.go @@ -0,0 +1,235 @@ +package events + +import ( + "context" + "errors" + "time" +) + +// EventerType describes the type of event logger +// The string values for EventerType should be entirely lowercase. +type EventerType string + +const ( + // LogFile indicates the event logger will be a logfile + LogFile EventerType = "file" + // Journald indicates journald should be used to log events + Journald EventerType = "journald" + // Null is a no-op events logger. It does not read or write events. + Null EventerType = "none" +) + +// Event describes the attributes of a libpod event +type Event struct { + // ContainerExitCode is for storing the exit code of a container which can + // be used for "internal" event notification + ContainerExitCode *int `json:",omitempty"` + // ID can be for the container, image, volume, etc + ID string `json:",omitempty"` + // Image used where applicable + Image string `json:",omitempty"` + // Name where applicable + Name string `json:",omitempty"` + // Network is the network name in a network event + Network string `json:"network,omitempty"` + // Status describes the event that occurred + Status Status + // Time the event occurred + Time time.Time + // Type of event that occurred + Type Type + // Health status of the current container + HealthStatus string `json:"health_status,omitempty"` + // Healthcheck log of the current container + HealthLog string `json:"health_log,omitempty"` + // HealthFailingStreak log of the current container + HealthFailingStreak int `json:"health_failing_streak,omitempty"` + // Error code for certain events involving errors. + Error string `json:"error,omitempty"` + + Details +} + +// Details describes specifics about certain events, specifically around +// container events +type Details struct { + // ContainerInspectData includes the payload of the container's inspect + // data. Only set when events_container_create_inspect_data is set true + // in containers.conf. + ContainerInspectData string `json:",omitempty"` + // PodID is the ID of the pod associated with the container. + PodID string `json:",omitempty"` + // Attributes can be used to describe specifics about the event + // in the case of a container event, labels for example + Attributes map[string]string +} + +// EventerOptions describe options that need to be passed to create +// an eventer +type EventerOptions struct { + // EventerType describes whether to use journald, file or memory + EventerType string + // LogFilePath is the path to where the log file should reside if using + // the file logger + LogFilePath string + // LogFileMaxSize is the default limit used for rotating the log file + LogFileMaxSize uint64 +} + +// Eventer is the interface for journald or file event logging +type Eventer interface { + // Write an event to a backend + Write(event Event) error + // Read an event from the backend + Read(ctx context.Context, options ReadOptions) error + // String returns the type of event logger + String() string +} + +type ReadResult struct { + Event *Event + Error error +} + +// ReadOptions describe the attributes needed to read event logs +type ReadOptions struct { + // EventChannel is the comm path back to user + EventChannel chan ReadResult + // Filters are key/value pairs that describe to limit output + Filters []string + // FromStart means you start reading from the start of the logs + FromStart bool + // Since reads "since" the given time + Since string + // Stream is follow + Stream bool + // Until reads "until" the given time + Until string +} + +// Type of event that occurred (container, volume, image, pod, etc) +type Type string + +// Status describes the actual event action (stop, start, create, kill) +type Status string + +// When updating this list below please also update the shell completion list in +// cmd/podman/common/completion.go and the StringToXXX function in events.go. +const ( + // Container - event is related to containers + Container Type = "container" + // Image - event is related to images + Image Type = "image" + // Network - event is related to networks + Network Type = "network" + // Pod - event is related to pods + Pod Type = "pod" + // System - event is related to Podman whole and not to any specific + // container/pod/image/volume + System Type = "system" + // Volume - event is related to volumes + Volume Type = "volume" + // Machine - event is related to machine VM's + Machine Type = "machine" + // Secret - event is related to secrets + Secret Type = "secret" + + // Attach ... + Attach Status = "attach" + // AutoUpdate ... + AutoUpdate Status = "auto-update" + // Build ... + Build Status = "build" + // Checkpoint ... + Checkpoint Status = "checkpoint" + // Cleanup ... + Cleanup Status = "cleanup" + // Commit ... + Commit Status = "commit" + // Copy ... + Copy Status = "copy" + // Create ... + Create Status = "create" + // Exec ... + Exec Status = "exec" + // ExecDied indicates that an exec session in a container died. + ExecDied Status = "exec_died" + // Exited indicates that a container's process died + Exited Status = "died" + // Export ... + Export Status = "export" + // HealthStatus ... + HealthStatus Status = "health_status" + // History ... + History Status = "history" + // Import ... + Import Status = "import" + // Init ... + Init Status = "init" + // Kill ... + Kill Status = "kill" + // LoadFromArchive ... + LoadFromArchive Status = "loadfromarchive" + // Mount ... + Mount Status = "mount" + // NetworkConnect + NetworkConnect Status = "connect" + // NetworkDisconnect + NetworkDisconnect Status = "disconnect" + // Pause ... + Pause Status = "pause" + // Prune ... + Prune Status = "prune" + // Pull ... + Pull Status = "pull" + // PullError is an error pulling an image + PullError Status = "pull-error" + // Push ... + Push Status = "push" + // Refresh indicates that the system refreshed the state after a + // reboot. + Refresh Status = "refresh" + // Remove ... + Remove Status = "remove" + // Rename indicates that a container was renamed + Rename Status = "rename" + // Renumber indicates that lock numbers were reallocated at user + // request. + Renumber Status = "renumber" + // Restart indicates that the target was restarted via an API call. + Restart Status = "restart" + // Restore ... + Restore Status = "restore" + // Rotate indicates that the log file was rotated + Rotate Status = "log-rotation" + // Save ... + Save Status = "save" + // Start ... + Start Status = "start" + // Stop ... + Stop Status = "stop" + // Sync ... + Sync Status = "sync" + // Tag ... + Tag Status = "tag" + // Unmount ... + Unmount Status = "unmount" + // Unpause ... + Unpause Status = "unpause" + // Untag ... + Untag Status = "untag" + // Update indicates that a container's configuration has been modified. + Update Status = "update" +) + +// EventFilter for filtering events +type EventFilter func(*Event) bool + +var ( + // ErrEventTypeBlank indicates the event log found something done by podman + // but it isn't likely an event + ErrEventTypeBlank = errors.New("event type blank") + + // ErrEventNotFound indicates that the event was not found in the event log + ErrEventNotFound = errors.New("unable to find event") +) diff --git a/vendor/github.com/containers/podman/v5/libpod/events/default_journal.go b/vendor/github.com/containers/podman/v5/libpod/events/default_journal.go new file mode 100644 index 00000000..e1f1542a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/default_journal.go @@ -0,0 +1,6 @@ +//go:build systemd && linux + +package events + +// DefaultEventerType is journald when systemd is available. +const DefaultEventerType = Journald diff --git a/vendor/github.com/containers/podman/v5/libpod/events/default_logfile.go b/vendor/github.com/containers/podman/v5/libpod/events/default_logfile.go new file mode 100644 index 00000000..6076deb1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/default_logfile.go @@ -0,0 +1,7 @@ +//go:build !systemd || !linux + +package events + +// DefaultEventerType is logfile when systemd is not present +// or not supported. +const DefaultEventerType = LogFile diff --git a/vendor/github.com/containers/podman/v5/libpod/events/events.go b/vendor/github.com/containers/podman/v5/libpod/events/events.go new file mode 100644 index 00000000..1638ffb6 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/events.go @@ -0,0 +1,223 @@ +package events + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + "go.podman.io/storage/pkg/stringid" +) + +// ErrNoJournaldLogging indicates that there is no journald logging +// supported (requires libsystemd) +var ErrNoJournaldLogging = errors.New("no support for journald logging") + +// String returns a string representation of EventerType +func (et EventerType) String() string { + return string(et) +} + +// IsValidEventer checks if the given string is a valid eventer type. +func IsValidEventer(eventer string) bool { + switch EventerType(eventer) { + case LogFile, Journald, Null: + return true + default: + return false + } +} + +// NewEvent creates an event struct and populates with +// the given status and time. +func NewEvent(status Status) Event { + return Event{ + Status: status, + Time: time.Now(), + } +} + +// ToJSONString returns the event as a json'ified string +func (e *Event) ToJSONString() (string, error) { + b, err := json.Marshal(e) + return string(b), err +} + +// ToHumanReadable returns human-readable event as a formatted string +func (e *Event) ToHumanReadable(truncate bool) string { + if e == nil { + return "" + } + var humanFormat string + id := e.ID + if truncate { + id = stringid.TruncateID(id) + } + switch e.Type { + case Container, Pod: + humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name) + if e.PodID != "" { + humanFormat += fmt.Sprintf(", pod_id=%s", e.PodID) + } + if e.Status == HealthStatus { + humanFormat += fmt.Sprintf(", health_status=%s", e.HealthStatus) + humanFormat += fmt.Sprintf(", health_failing_streak=%d", e.HealthFailingStreak) + humanFormat += fmt.Sprintf(", health_log=%s", e.HealthLog) + } + // check if the container has labels and add it to the output + if len(e.Attributes) > 0 { + for k, v := range e.Attributes { + humanFormat += fmt.Sprintf(", %s=%s", k, v) + } + } + humanFormat += ")" + case Network: + if e.Status == Create || e.Status == Remove { + if netdriver, exists := e.Attributes["driver"]; exists { + humanFormat = fmt.Sprintf("%s %s %s %s (name=%s, type=%s)", e.Time, e.Type, e.Status, e.ID, e.Network, netdriver) + } + } else { + humanFormat = fmt.Sprintf("%s %s %s %s (container=%s, name=%s)", e.Time, e.Type, e.Status, id, id, e.Network) + } + case Image: + humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, id, e.Name) + if e.Error != "" { + humanFormat += " " + e.Error + } + case System: + if e.Name != "" { + humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name) + } else { + humanFormat = fmt.Sprintf("%s %s %s", e.Time, e.Type, e.Status) + } + case Volume, Machine: + humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name) + case Secret: + humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, id) + } + return humanFormat +} + +// String converts a Type to a string +func (t Type) String() string { + return string(t) +} + +// String converts a status to a string +func (s Status) String() string { + return string(s) +} + +// StringToType converts string to an EventType +func StringToType(name string) (Type, error) { + switch name { + case Container.String(): + return Container, nil + case Image.String(): + return Image, nil + case Machine.String(): + return Machine, nil + case Network.String(): + return Network, nil + case Pod.String(): + return Pod, nil + case System.String(): + return System, nil + case Volume.String(): + return Volume, nil + case Secret.String(): + return Secret, nil + case "": + return "", ErrEventTypeBlank + } + return "", fmt.Errorf("unknown event type %q", name) +} + +// StringToStatus converts a string to an Event Status +func StringToStatus(name string) (Status, error) { + switch name { + case Attach.String(): + return Attach, nil + case AutoUpdate.String(): + return AutoUpdate, nil + case Build.String(): + return Build, nil + case Checkpoint.String(): + return Checkpoint, nil + case Cleanup.String(): + return Cleanup, nil + case Commit.String(): + return Commit, nil + case Create.String(): + return Create, nil + case Exec.String(): + return Exec, nil + case ExecDied.String(): + return ExecDied, nil + case Exited.String(): + return Exited, nil + case Export.String(): + return Export, nil + case HealthStatus.String(): + return HealthStatus, nil + case History.String(): + return History, nil + case Import.String(): + return Import, nil + case Init.String(): + return Init, nil + case Kill.String(): + return Kill, nil + case LoadFromArchive.String(): + return LoadFromArchive, nil + case Mount.String(): + return Mount, nil + case NetworkConnect.String(): + return NetworkConnect, nil + case NetworkDisconnect.String(): + return NetworkDisconnect, nil + case Pause.String(): + return Pause, nil + case Prune.String(): + return Prune, nil + case Pull.String(): + return Pull, nil + case PullError.String(): + return PullError, nil + case Push.String(): + return Push, nil + case Refresh.String(): + return Refresh, nil + case Remove.String(): + return Remove, nil + case Rename.String(): + return Rename, nil + case Renumber.String(): + return Renumber, nil + case Restart.String(): + return Restart, nil + case Restore.String(): + return Restore, nil + case Rotate.String(): + return Rotate, nil + case Save.String(): + return Save, nil + case Start.String(): + return Start, nil + case Stop.String(): + return Stop, nil + case Sync.String(): + return Sync, nil + case Tag.String(): + return Tag, nil + case Unmount.String(): + return Unmount, nil + case Unpause.String(): + return Unpause, nil + case Untag.String(): + return Untag, nil + case Update.String(): + return Update, nil + } + return "", fmt.Errorf("unknown event status %q", name) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/events/events_supported.go b/vendor/github.com/containers/podman/v5/libpod/events/events_supported.go new file mode 100644 index 00000000..825c80a5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/events_supported.go @@ -0,0 +1,42 @@ +//go:build linux || freebsd + +package events + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/sirupsen/logrus" +) + +// NewEventer creates an eventer based on the eventer type +func NewEventer(options EventerOptions) (Eventer, error) { + logrus.Debugf("Initializing event backend %s", options.EventerType) + switch EventerType(strings.ToLower(options.EventerType)) { + case Journald: + return newJournalDEventer(options) + case LogFile: + return newLogFileEventer(options) + case Null: + return newNullEventer(), nil + default: + return nil, fmt.Errorf("unknown event logger type: %s", strings.ToLower(options.EventerType)) + } +} + +// newEventFromJSONString takes stringified json and converts +// it to an event +func newEventFromJSONString(event string) (*Event, error) { + e := new(Event) + if err := json.Unmarshal([]byte(event), e); err != nil { + return nil, err + } + return e, nil +} + +// newNullEventer returns a new null eventer. You should only do this for +// the purposes of internal libpod testing. +func newNullEventer() Eventer { + return EventToNull{} +} diff --git a/vendor/github.com/containers/podman/v5/libpod/events/events_unsupported.go b/vendor/github.com/containers/podman/v5/libpod/events/events_unsupported.go new file mode 100644 index 00000000..a8afd9e8 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/events_unsupported.go @@ -0,0 +1,10 @@ +//go:build !linux && !freebsd + +package events + +import "errors" + +// NewEventer creates an eventer based on the eventer type +func NewEventer(_ EventerOptions) (Eventer, error) { + return nil, errors.New("this function is not available for your platform") +} diff --git a/vendor/github.com/containers/podman/v5/libpod/events/filters.go b/vendor/github.com/containers/podman/v5/libpod/events/filters.go new file mode 100644 index 00000000..3bb22114 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/filters.go @@ -0,0 +1,167 @@ +//go:build linux || freebsd + +package events + +import ( + "fmt" + "strings" + "time" + + "github.com/containers/podman/v5/pkg/util" +) + +func generateEventFilter(filter, filterValue string) (func(e *Event) bool, error) { + switch strings.ToUpper(filter) { + case "CONTAINER": + return func(e *Event) bool { + if e.Type != Container { + return false + } + if e.Name == filterValue { + return true + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "EVENT", "STATUS": + if filterValue == "die" { // Docker compat + filterValue = "died" + } + return func(e *Event) bool { + return string(e.Status) == filterValue + }, nil + case "IMAGE": + return func(e *Event) bool { + if e.Type != Image { + return false + } + if e.Name == filterValue { + return true + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "POD": + return func(e *Event) bool { + if e.Type != Pod { + return false + } + if e.Name == filterValue { + return true + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "VOLUME": + return func(e *Event) bool { + if e.Type != Volume { + return false + } + // Prefix match with name for consistency with docker + return strings.HasPrefix(e.Name, filterValue) + }, nil + case "TYPE": + return func(e *Event) bool { + return string(e.Type) == filterValue + }, nil + + case "LABEL": + return func(e *Event) bool { + var found bool + // iterate labels and see if we match a key and value + for eventKey, eventValue := range e.Attributes { + filterKey, filterVal, hasValue := strings.Cut(filterValue, "=") + // match "key=value" or "key" + if !hasValue { + // match by key only + if eventKey == filterKey { + found = true + break + } + } else { + // match by key and value + if eventKey == filterKey && eventValue == filterVal { + found = true + break + } + } + } + return found + }, nil + } + return nil, fmt.Errorf("%s is an invalid filter", filter) +} + +func generateEventSinceOption(timeSince time.Time) func(e *Event) bool { + return func(e *Event) bool { + return e.Time.After(timeSince) + } +} + +func generateEventUntilOption(timeUntil time.Time) func(e *Event) bool { + return func(e *Event) bool { + return e.Time.Before(timeUntil) + } +} + +func parseFilter(filter string) (string, string, error) { + filterSplit := strings.SplitN(filter, "=", 2) + if len(filterSplit) != 2 { + return "", "", fmt.Errorf("%s is an invalid filter", filter) + } + return filterSplit[0], filterSplit[1], nil +} + +// applyFilters applies the EventFilter slices in sequence. Filters under the +// same key are disjunctive while each key must match (conjuctive). +func applyFilters(event *Event, filterMap map[string][]EventFilter) bool { + for _, filters := range filterMap { + success := false + for _, filter := range filters { + if filter(event) { + success = true + break + } + } + if !success { + return false + } + } + return true +} + +// generateEventFilter parses the specified filters into a filter map that can +// later on be used to filter events. Keys are conjunctive, values are +// disjunctive. +func generateEventFilters(filters []string, since, until string) (map[string][]EventFilter, error) { + filterMap := make(map[string][]EventFilter) + for _, filter := range filters { + key, val, err := parseFilter(filter) + if err != nil { + return nil, err + } + filterFunc, err := generateEventFilter(key, val) + if err != nil { + return nil, err + } + filterSlice := filterMap[key] + filterSlice = append(filterSlice, filterFunc) + filterMap[key] = filterSlice + } + + if len(since) > 0 { + timeSince, err := util.ParseInputTime(since, true) + if err != nil { + return nil, fmt.Errorf("unable to convert since time of %s: %w", since, err) + } + filterFunc := generateEventSinceOption(timeSince) + filterMap["since"] = []EventFilter{filterFunc} + } + + if len(until) > 0 { + timeUntil, err := util.ParseInputTime(until, false) + if err != nil { + return nil, fmt.Errorf("unable to convert until time of %s: %w", until, err) + } + filterFunc := generateEventUntilOption(timeUntil) + filterMap["until"] = []EventFilter{filterFunc} + } + return filterMap, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/events/journal_linux.go b/vendor/github.com/containers/podman/v5/libpod/events/journal_linux.go new file mode 100644 index 00000000..0fc1d6f7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/journal_linux.go @@ -0,0 +1,333 @@ +//go:build systemd + +package events + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "time" + + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/util" + "github.com/coreos/go-systemd/v22/journal" + "github.com/coreos/go-systemd/v22/sdjournal" + "github.com/sirupsen/logrus" +) + +// EventJournalD is the journald implementation of an eventer +type EventJournalD struct { + options EventerOptions +} + +// newJournalDEventer creates a new EventJournalD Eventer +func newJournalDEventer(options EventerOptions) (Eventer, error) { + return EventJournalD{options}, nil +} + +// Write to journald +func (e EventJournalD) Write(ee Event) error { + m := make(map[string]string) + m["SYSLOG_IDENTIFIER"] = "podman" + m["PODMAN_EVENT"] = ee.Status.String() + m["PODMAN_TYPE"] = ee.Type.String() + m["PODMAN_TIME"] = ee.Time.Format(time.RFC3339Nano) + + // Add specialized information based on the podman type + switch ee.Type { + case Image: + m["PODMAN_NAME"] = ee.Name + m["PODMAN_ID"] = ee.ID + if ee.Error != "" { + m["ERROR"] = ee.Error + } + case Container, Pod: + m["PODMAN_IMAGE"] = ee.Image + m["PODMAN_NAME"] = ee.Name + m["PODMAN_ID"] = ee.ID + if ee.ContainerExitCode != nil { + m["PODMAN_EXIT_CODE"] = strconv.Itoa(*ee.ContainerExitCode) + } + if ee.PodID != "" { + m["PODMAN_POD_ID"] = ee.PodID + } + if err := addLabelsToJournal(m, ee.Details.Attributes); err != nil { + return err + } + if ee.Status == HealthStatus { + m["PODMAN_HEALTH_STATUS"] = ee.HealthStatus + if ee.HealthLog != "" { + m["PODMAN_HEALTH_LOG"] = ee.HealthLog + } + m["PODMAN_HEALTH_FAILING_STREAK"] = strconv.Itoa(ee.HealthFailingStreak) + } + if len(ee.Details.ContainerInspectData) > 0 { + m["PODMAN_CONTAINER_INSPECT_DATA"] = ee.Details.ContainerInspectData + } + case Network: + m["PODMAN_ID"] = ee.ID + m["PODMAN_NETWORK_NAME"] = ee.Network + if err := addLabelsToJournal(m, ee.Details.Attributes); err != nil { + return err + } + case Volume: + m["PODMAN_NAME"] = ee.Name + } + + // starting with commit 7e6e267329 we set LogLevel=notice for the systemd healthcheck unit + // This so it doesn't log the started/stopped unit messages al the time which spam the + // journal if a small interval is used. That however broke the healthcheck event as it no + // longer showed up in podman events when running as root as we only send the event on info + // level. To fix this we have to send the event on notice level. + // https://github.com/containers/podman/issues/20342 + prio := journal.PriInfo + if len(ee.HealthStatus) > 0 { + prio = journal.PriNotice + } + + return journal.Send(ee.ToHumanReadable(false), prio, m) +} + +func addLabelsToJournal(journalEntry, eventAttributes map[string]string) error { + // If we have container labels, we need to convert them to a string so they + // can be recorded with the event + if len(eventAttributes) > 0 { + b, err := json.Marshal(eventAttributes) + if err != nil { + return err + } + journalEntry["PODMAN_LABELS"] = string(b) + } + return nil +} + +func getLabelsFromJournal(entry *sdjournal.JournalEntry, event *Event) error { + // we need to check for the presence of labels recorded to a container event + if stringLabels, ok := entry.Fields["PODMAN_LABELS"]; ok && len(stringLabels) > 0 { + labels := make(map[string]string, 0) + if err := json.Unmarshal([]byte(stringLabels), &labels); err != nil { + return err + } + + // if we have labels, add them to the event + if len(labels) > 0 { + event.Attributes = labels + } + } + return nil +} + +// Read reads events from the journal and sends qualified events to the event channel +func (e EventJournalD) Read(ctx context.Context, options ReadOptions) (retErr error) { + filterMap, err := generateEventFilters(options.Filters, options.Since, options.Until) + if err != nil { + return fmt.Errorf("failed to parse event filters: %w", err) + } + + var untilTime time.Time + if len(options.Until) > 0 { + untilTime, err = util.ParseInputTime(options.Until, false) + if err != nil { + return err + } + } + + j, err := sdjournal.NewJournal() + if err != nil { + return err + } + defer func() { + if retErr != nil { + if err := j.Close(); err != nil { + logrus.Errorf("Unable to close journal :%v", err) + } + } + }() + err = j.SetDataThreshold(0) + if err != nil { + return fmt.Errorf("cannot set data threshold for journal: %v", err) + } + // match only podman journal entries + podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"} + if err := j.AddMatch(podmanJournal.String()); err != nil { + return fmt.Errorf("failed to add SYSLOG_IDENTIFIER journal filter for event log: %w", err) + } + + // make sure we only read events for the current user + uidMatch := sdjournal.Match{Field: "_UID", Value: strconv.Itoa(rootless.GetRootlessUID())} + if err := j.AddMatch(uidMatch.String()); err != nil { + return fmt.Errorf("failed to add _UID journal filter for event log: %w", err) + } + + if len(options.Since) == 0 && len(options.Until) == 0 && options.Stream { + if err := j.SeekTail(); err != nil { + return fmt.Errorf("failed to seek end of journal: %w", err) + } + // After SeekTail calling Next moves to a random entry. + // To prevent this we have to call Previous first. + // see: https://bugs.freedesktop.org/show_bug.cgi?id=64614 + if _, err := j.Previous(); err != nil { + return fmt.Errorf("failed to move journal cursor to previous entry: %w", err) + } + } else if len(options.Since) > 0 { + since, err := util.ParseInputTime(options.Since, true) + if err != nil { + return err + } + // seek based on time which helps to reduce unnecessary event reads + if err := j.SeekRealtimeUsec(uint64(since.UnixMicro())); err != nil { + return err + } + } + + go func() { + defer close(options.EventChannel) + defer func() { + if err := j.Close(); err != nil { + logrus.Errorf("Unable to close journal :%v", err) + } + }() + for { + entry, err := GetNextEntry(ctx, j, options.Stream, untilTime) + if err != nil { + options.EventChannel <- ReadResult{Error: err} + break + } + // no entry == we hit the end + if entry == nil { + break + } + + newEvent, err := newEventFromJournalEntry(entry) + if err != nil { + // We can't decode this event. + // Don't fail hard - that would make events unusable. + // Instead, log and continue. + if !errors.Is(err, ErrEventTypeBlank) { + options.EventChannel <- ReadResult{Error: fmt.Errorf("unable to decode event: %v", err)} + } + continue + } + if applyFilters(newEvent, filterMap) { + options.EventChannel <- ReadResult{Event: newEvent} + } + } + }() + return nil +} + +func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { + newEvent := Event{} + eventType, err := StringToType(entry.Fields["PODMAN_TYPE"]) + if err != nil { + return nil, err + } + eventTime, err := time.Parse(time.RFC3339Nano, entry.Fields["PODMAN_TIME"]) + if err != nil { + return nil, err + } + eventStatus, err := StringToStatus(entry.Fields["PODMAN_EVENT"]) + if err != nil { + return nil, err + } + newEvent.Type = eventType + newEvent.Time = eventTime + newEvent.Status = eventStatus + newEvent.Name = entry.Fields["PODMAN_NAME"] + + switch eventType { + case Container, Pod: + newEvent.ID = entry.Fields["PODMAN_ID"] + newEvent.Image = entry.Fields["PODMAN_IMAGE"] + newEvent.PodID = entry.Fields["PODMAN_POD_ID"] + if code, ok := entry.Fields["PODMAN_EXIT_CODE"]; ok { + intCode, err := strconv.Atoi(code) + if err != nil { + logrus.Errorf("Parsing event exit code %s", code) + } else { + newEvent.ContainerExitCode = &intCode + } + } + if err := getLabelsFromJournal(entry, &newEvent); err != nil { + return nil, err + } + newEvent.HealthStatus = entry.Fields["PODMAN_HEALTH_STATUS"] + if log, ok := entry.Fields["PODMAN_HEALTH_LOG"]; ok { + newEvent.HealthLog = log + } + if FailingStreak, ok := entry.Fields["PODMAN_HEALTH_FAILING_STREAK"]; ok { + FailingStreakInt, err := strconv.Atoi(FailingStreak) + if err == nil { + newEvent.HealthFailingStreak = FailingStreakInt + } + } + newEvent.Details.ContainerInspectData = entry.Fields["PODMAN_CONTAINER_INSPECT_DATA"] + case Network: + newEvent.ID = entry.Fields["PODMAN_ID"] + newEvent.Network = entry.Fields["PODMAN_NETWORK_NAME"] + if err := getLabelsFromJournal(entry, &newEvent); err != nil { + return nil, err + } + case Image: + newEvent.ID = entry.Fields["PODMAN_ID"] + if val, ok := entry.Fields["ERROR"]; ok { + newEvent.Error = val + } + } + return &newEvent, nil +} + +// String returns a string representation of the logger +func (e EventJournalD) String() string { + return Journald.String() +} + +// GetNextEntry returns the next entry in the journal. If the end of the +// journal is reached and stream is not set or the current time is after +// the until time this function returns nil,nil. +func GetNextEntry(ctx context.Context, j *sdjournal.Journal, stream bool, untilTime time.Time) (*sdjournal.JournalEntry, error) { + for { + select { + case <-ctx.Done(): + // the consumer has cancelled + return nil, nil + default: + // fallthrough + } + // the api requires a next|prev before reading the event + ret, err := j.Next() + if err != nil { + return nil, fmt.Errorf("failed to move journal cursor to next entry: %w", err) + } + // ret == 0 equals EOF, see sd_journal_next(3) + if ret == 0 { + if !stream || (!untilTime.IsZero() && time.Now().After(untilTime)) { + // we hit the end and should not keep streaming + return nil, nil + } + // keep waiting for the next entry + // j.Wait() is blocking, this would cause the goroutine to hang forever + // if no more journal entries are generated and thus if the client + // has closed the connection in the meantime to leak memory. + // Waiting only 5 seconds makes sure we can check if the client closed in the + // meantime at least every 5 seconds. + t := 5 * time.Second + if !untilTime.IsZero() { + until := time.Until(untilTime) + if until < t { + t = until + } + } + _ = j.Wait(t) + continue + } + + entry, err := j.GetEntry() + if err != nil { + return nil, fmt.Errorf("failed to read journal entry: %w", err) + } + return entry, nil + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/events/journal_unsupported.go b/vendor/github.com/containers/podman/v5/libpod/events/journal_unsupported.go new file mode 100644 index 00000000..91be97aa --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/journal_unsupported.go @@ -0,0 +1,8 @@ +//go:build (linux && !systemd) || freebsd + +package events + +// newJournalDEventer always returns an error if libsystemd not found +func newJournalDEventer(_ EventerOptions) (Eventer, error) { + return nil, ErrNoJournaldLogging +} diff --git a/vendor/github.com/containers/podman/v5/libpod/events/logfile.go b/vendor/github.com/containers/podman/v5/libpod/events/logfile.go new file mode 100644 index 00000000..0e892bea --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/logfile.go @@ -0,0 +1,369 @@ +//go:build linux || freebsd + +package events + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "time" + + "github.com/containers/podman/v5/pkg/util" + "github.com/nxadm/tail" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/lockfile" + "golang.org/x/sys/unix" +) + +// EventLogFile is the structure for event writing to a logfile. It contains the eventer +// options and the event itself. Methods for reading and writing are also defined from it. +type EventLogFile struct { + options EventerOptions +} + +// newLogFileEventer creates a new EventLogFile eventer +func newLogFileEventer(options EventerOptions) (*EventLogFile, error) { + // Create events log dir + if err := os.MkdirAll(filepath.Dir(options.LogFilePath), 0o700); err != nil { + return nil, fmt.Errorf("creating events dirs: %w", err) + } + // We have to make sure the file is created otherwise reading events will hang. + // https://github.com/containers/podman/issues/15688 + fd, err := os.OpenFile(options.LogFilePath, os.O_RDONLY|os.O_CREATE, 0o600) + if err != nil { + return nil, fmt.Errorf("failed to create event log file: %w", err) + } + return &EventLogFile{options: options}, fd.Close() +} + +// Writes to the log file +func (e EventLogFile) Write(ee Event) error { + // We need to lock events file + lock, err := lockfile.GetLockFile(e.options.LogFilePath + ".lock") + if err != nil { + return err + } + lock.Lock() + defer lock.Unlock() + + eventJSONString, err := ee.ToJSONString() + if err != nil { + return err + } + + if _, err := rotateLog(e.options.LogFilePath, eventJSONString, e.options.LogFileMaxSize); err != nil { + return err + } + + return e.writeString(eventJSONString) +} + +func (e EventLogFile) writeString(s string) error { + f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o700) + if err != nil { + return err + } + defer f.Close() + return writeToFile(s, f) +} + +func writeToFile(s string, f *os.File) error { + _, err := f.WriteString(s + "\n") + return err +} + +func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) { + seek := tail.SeekInfo{Offset: 0, Whence: io.SeekEnd} + if options.FromStart || !options.Stream { + seek.Whence = 0 + } + stream := options.Stream + return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: stream, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true}) +} + +func (e EventLogFile) readRotateEvent(event *Event) (begin bool, end bool, err error) { + if event.Status != Rotate { + return + } + if event.Details.Attributes == nil { + // may be an old event before storing attributes in the rotate event + return + } + switch event.Details.Attributes[rotateEventAttribute] { + case rotateEventBegin: + begin = true + return + case rotateEventEnd: + end = true + return + default: + err = fmt.Errorf("unknown rotate-event attribute %q", event.Details.Attributes[rotateEventAttribute]) + return + } +} + +// Reads from the log file +func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { + filterMap, err := generateEventFilters(options.Filters, options.Since, options.Until) + if err != nil { + return fmt.Errorf("failed to parse event filters: %w", err) + } + t, err := e.getTail(options) + if err != nil { + return err + } + if len(options.Until) > 0 { + untilTime, err := util.ParseInputTime(options.Until, false) + if err != nil { + return err + } + go func() { + time.Sleep(time.Until(untilTime)) + if err := t.Stop(); err != nil { + logrus.Errorf("Stopping logger: %v", err) + } + }() + } + logrus.Debugf("Reading events from file %q", e.options.LogFilePath) + + // Get the time *before* starting to read. Comparing the timestamps + // with events avoids returning events more than once after a log-file + // rotation. + readTime, err := func() (time.Time, error) { + // We need to lock events file + lock, err := lockfile.GetLockFile(e.options.LogFilePath + ".lock") + if err != nil { + return time.Time{}, err + } + lock.Lock() + defer lock.Unlock() + return time.Now(), nil + }() + if err != nil { + return err + } + + go func() { + defer close(options.EventChannel) + var line *tail.Line + var ok bool + var skipRotate bool + for { + select { + case <-ctx.Done(): + // the consumer has cancelled + t.Kill(errors.New("hangup by client")) + return + case line, ok = <-t.Lines: + if !ok { + // channel was closed + return + } + // fallthrough + } + + event, err := newEventFromJSONString(line.Text) + if err != nil { + err := fmt.Errorf("event type is not valid in %s", e.options.LogFilePath) + options.EventChannel <- ReadResult{Error: err} + continue + } + switch event.Type { + case Image, Volume, Pod, Container, Network, Secret: + // no-op + case System: + begin, end, err := e.readRotateEvent(event) + if err != nil { + options.EventChannel <- ReadResult{Error: err} + continue + } + if begin && event.Time.After(readTime) { + // If the rotation event happened _after_ we + // started reading, we need to ignore/skip + // subsequent event until the end of the + // rotation. + skipRotate = true + logrus.Debugf("Skipping already read events after log-file rotation: %v", event) + } else if end { + // This rotate event + skipRotate = false + } + default: + err := fmt.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath) + options.EventChannel <- ReadResult{Error: err} + continue + } + if skipRotate { + continue + } + if applyFilters(event, filterMap) { + options.EventChannel <- ReadResult{Event: event} + } + } + }() + return nil +} + +// String returns a string representation of the logger +func (e EventLogFile) String() string { + return LogFile.String() +} + +const ( + rotateEventAttribute = "io.podman.event.rotate" + rotateEventBegin = "begin" + rotateEventEnd = "end" +) + +func writeRotateEvent(f *os.File, logFilePath string, begin bool) error { + rEvent := NewEvent(Rotate) + rEvent.Type = System + rEvent.Name = logFilePath + rEvent.Attributes = make(map[string]string) + if begin { + rEvent.Attributes[rotateEventAttribute] = rotateEventBegin + } else { + rEvent.Attributes[rotateEventAttribute] = rotateEventEnd + } + rotateJSONString, err := rEvent.ToJSONString() + if err != nil { + return err + } + return writeToFile(rotateJSONString, f) +} + +// Rotates the log file if the log file size and content exceeds limit +func rotateLog(logfile string, content string, limit uint64) (bool, error) { + needsRotation, err := logNeedsRotation(logfile, content, limit) + if err != nil || !needsRotation { + return false, err + } + if err := truncate(logfile); err != nil { + return false, err + } + return true, nil +} + +// logNeedsRotation return true if the log file needs to be rotated. +func logNeedsRotation(logfile string, content string, limit uint64) (bool, error) { + if limit == 0 { + return false, nil + } + file, err := os.Stat(logfile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // The logfile does not exist yet. + return false, nil + } + return false, err + } + var filesize = uint64(file.Size()) + var contentsize = uint64(len([]rune(content))) + if filesize+contentsize < limit { + return false, nil + } + + return true, nil +} + +// Truncates the log file and saves 50% of content to new log file +func truncate(filePath string) error { + orig, err := os.Open(filePath) + if err != nil { + return err + } + defer orig.Close() + + origFinfo, err := orig.Stat() + if err != nil { + return err + } + + size := origFinfo.Size() + threshold := size / 2 + + tmp, err := os.CreateTemp(path.Dir(filePath), "") + if err != nil { + // Retry in /tmp in case creating a tmp file in the same + // directory has failed. + tmp, err = os.CreateTemp("", "") + if err != nil { + return err + } + } + defer tmp.Close() + + // Jump directly to the threshold, drop the first line and copy the remainder + if _, err := orig.Seek(threshold, 0); err != nil { + return err + } + reader := bufio.NewReader(orig) + if _, err := reader.ReadString('\n'); err != nil { + if !errors.Is(err, io.EOF) { + return err + } + } + + if err := writeRotateEvent(tmp, filePath, true); err != nil { + return fmt.Errorf("writing rotation event begin marker: %w", err) + } + if _, err := reader.WriteTo(tmp); err != nil { + return fmt.Errorf("writing truncated contents: %w", err) + } + if err := writeRotateEvent(tmp, filePath, false); err != nil { + return fmt.Errorf("writing rotation event end marker: %w", err) + } + + if err := renameLog(tmp.Name(), filePath); err != nil { + return fmt.Errorf("writing back %s to %s: %w", tmp.Name(), filePath, err) + } + + return nil +} + +// Renames from, to +func renameLog(from, to string) error { + err := os.Rename(from, to) + if err == nil { + return nil + } + + if !errors.Is(err, unix.EXDEV) { + return err + } + + // Files are not on the same partition, so we need to copy them the + // hard way. + fFrom, err := os.Open(from) + if err != nil { + return err + } + defer fFrom.Close() + + // Remove the old file to make sure we're not truncating current + // readers. + if err := os.Remove(to); err != nil { + return fmt.Errorf("recreating file %s: %w", to, err) + } + + fTo, err := os.Create(to) + if err != nil { + return err + } + defer fTo.Close() + + if _, err := io.Copy(fTo, fFrom); err != nil { + return fmt.Errorf("writing back from temporary file: %w", err) + } + + if err := os.Remove(from); err != nil { + return fmt.Errorf("removing temporary file: %w", err) + } + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/events/nullout.go b/vendor/github.com/containers/podman/v5/libpod/events/nullout.go new file mode 100644 index 00000000..3e2e0073 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/events/nullout.go @@ -0,0 +1,25 @@ +package events + +import ( + "context" + "errors" +) + +// EventToNull is an eventer type that does nothing. +// It is meant for unit tests only +type EventToNull struct{} + +// Write eats the event and always returns nil +func (e EventToNull) Write(_ Event) error { + return nil +} + +// Read does nothing and returns an error. +func (e EventToNull) Read(_ context.Context, _ ReadOptions) error { + return errors.New("cannot read events with the \"none\" backend") +} + +// String returns a string representation of the logger +func (e EventToNull) String() string { + return "none" +} diff --git a/vendor/github.com/containers/podman/v5/libpod/healthcheck.go b/vendor/github.com/containers/podman/v5/libpod/healthcheck.go new file mode 100644 index 00000000..360a4af1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/healthcheck.go @@ -0,0 +1,481 @@ +//go:build !remote + +package libpod + +import ( + "bufio" + "bytes" + "context" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/shutdown" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// HealthCheck verifies the state and validity of the healthcheck configuration +// on the container and then executes the healthcheck +func (r *Runtime) HealthCheck(ctx context.Context, name string) (define.HealthCheckStatus, error) { + container, err := r.LookupContainer(name) + if err != nil { + return define.HealthCheckContainerNotFound, fmt.Errorf("unable to look up %s to perform a health check: %w", name, err) + } + + hcStatus, err := checkHealthCheckCanBeRun(container) + if err != nil { + return hcStatus, err + } + + isStartupHC := false + if container.config.StartupHealthCheckConfig != nil { + passed, err := container.StartupHCPassed() + if err != nil { + return define.HealthCheckInternalError, err + } + isStartupHC = !passed + } + + hcStatus, logStatus, err := container.runHealthCheck(ctx, isStartupHC) + if !isStartupHC { + if err := container.processHealthCheckStatus(logStatus); err != nil { + return hcStatus, err + } + } + return hcStatus, err +} + +func (c *Container) runHealthCheck(ctx context.Context, isStartup bool) (define.HealthCheckStatus, string, error) { + var ( + newCommand []string + returnCode int + inStartPeriod bool + ) + + hcCommand := c.HealthCheckConfig().Test + if isStartup { + logrus.Debugf("Running startup healthcheck for container %s", c.ID()) + hcCommand = c.config.StartupHealthCheckConfig.Test + } + if len(hcCommand) < 1 { + return define.HealthCheckNotDefined, "", fmt.Errorf("container %s has no defined healthcheck", c.ID()) + } + switch hcCommand[0] { + case "", define.HealthConfigTestNone: + return define.HealthCheckNotDefined, "", fmt.Errorf("container %s has no defined healthcheck", c.ID()) + case define.HealthConfigTestCmd: + newCommand = hcCommand[1:] + case define.HealthConfigTestCmdShell: + // TODO: SHELL command from image not available in Container - use Docker default + newCommand = []string{"/bin/sh", "-c", strings.Join(hcCommand[1:], " ")} + default: + // command supplied on command line - pass as-is + newCommand = hcCommand + } + if len(newCommand) < 1 || newCommand[0] == "" { + return define.HealthCheckNotDefined, "", fmt.Errorf("container %s has no defined healthcheck", c.ID()) + } + + streams := new(define.AttachStreams) + output := &bytes.Buffer{} + + streams.InputStream = bufio.NewReader(os.Stdin) + streams.OutputStream = output + streams.ErrorStream = output + streams.AttachOutput = true + streams.AttachError = true + streams.AttachInput = true + + logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID()) + hcResult := define.HealthCheckSuccess + config := new(ExecConfig) + config.Command = newCommand + timeStart := time.Now() + exitCode, hcErr := c.healthCheckExec(config, c.HealthCheckConfig().Timeout, streams) + timeEnd := time.Now() + if hcErr != nil { + hcResult = define.HealthCheckFailure + switch { + case errors.Is(hcErr, define.ErrOCIRuntimeNotFound) || + errors.Is(hcErr, define.ErrOCIRuntimePermissionDenied) || + errors.Is(hcErr, define.ErrOCIRuntime): + returnCode = 1 + hcErr = nil + case errors.Is(hcErr, define.ErrHealthCheckTimeout): + returnCode = -1 + default: + returnCode = 125 + } + } else if exitCode != 0 { + hcResult = define.HealthCheckFailure + returnCode = 1 + } + + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return define.HealthCheckInternalError, "", err + } + } + + // Handle startup HC + if isStartup { + inStartPeriod = true + if hcErr != nil || exitCode != 0 { + hcResult = define.HealthCheckStartup + if err := c.incrementStartupHCFailureCounter(ctx); err != nil { + return define.HealthCheckInternalError, "", err + } + } else { + if err := c.incrementStartupHCSuccessCounter(ctx); err != nil { + return define.HealthCheckInternalError, "", err + } + } + } + + if exitCode != 0 && c.ensureState(define.ContainerStateStopped, define.ContainerStateStopping, define.ContainerStateExited) { + hcResult = define.HealthCheckContainerStopped + } + + if c.HealthCheckConfig().StartPeriod > 0 { + // there is a start-period we need to honor; we add startPeriod to container start time + startPeriodTime := c.state.StartedTime.Add(c.HealthCheckConfig().StartPeriod) + if timeStart.Before(startPeriodTime) { + // we are still in the start period, flip the inStartPeriod bool + inStartPeriod = true + logrus.Debugf("healthcheck for %s being run in start-period", c.ID()) + } + } + + eventLog := output.String() + if c.HealthCheckMaxLogSize() != 0 && len(eventLog) > int(c.HealthCheckMaxLogSize()) { + eventLog = eventLog[:c.HealthCheckMaxLogSize()] + } + + hcl := newHealthCheckLog(timeStart, timeEnd, returnCode, eventLog) + + healthCheckResult, err := c.updateHealthCheckLog(hcl, hcResult, inStartPeriod, isStartup) + if err != nil { + return hcResult, "", fmt.Errorf("unable to update health check log %s for %s: %w", c.getHealthCheckLogDestination(), c.ID(), err) + } + + // Write HC event with appropriate status as the last thing before we + // return. + if hcResult == define.HealthCheckNotDefined || hcResult == define.HealthCheckInternalError { + return hcResult, healthCheckResult.Status, hcErr + } + if c.runtime.config.Engine.HealthcheckEvents { + c.newContainerHealthCheckEvent(healthCheckResult) + } + + return hcResult, healthCheckResult.Status, hcErr +} + +func (c *Container) processHealthCheckStatus(status string) error { + if status != define.HealthCheckUnhealthy { + return nil + } + + switch c.config.HealthCheckOnFailureAction { + case define.HealthCheckOnFailureActionNone: // Nothing to do + + case define.HealthCheckOnFailureActionKill: + if err := c.Kill(uint(unix.SIGKILL)); err != nil { + return fmt.Errorf("killing container health-check turned unhealthy: %w", err) + } + + case define.HealthCheckOnFailureActionRestart: + // We let the cleanup process handle the restart. Otherwise + // the container would be restarted in the context of a + // transient systemd unit which may cause undesired side + // effects. + if err := c.Stop(); err != nil { + return fmt.Errorf("restarting/stopping container after health-check turned unhealthy: %w", err) + } + + case define.HealthCheckOnFailureActionStop: + if err := c.Stop(); err != nil { + return fmt.Errorf("stopping container after health-check turned unhealthy: %w", err) + } + + default: // Should not happen but better be safe than sorry + return fmt.Errorf("unsupported on-failure action %d", c.config.HealthCheckOnFailureAction) + } + + return nil +} + +func checkHealthCheckCanBeRun(c *Container) (define.HealthCheckStatus, error) { + cstate, err := c.State() + if err != nil { + return define.HealthCheckInternalError, err + } + if cstate != define.ContainerStateRunning { + return define.HealthCheckContainerStopped, fmt.Errorf("container %s is not running", c.ID()) + } + if !c.HasHealthCheck() { + return define.HealthCheckNotDefined, fmt.Errorf("container %s has no defined healthcheck", c.ID()) + } + return define.HealthCheckDefined, nil +} + +// Increment the current startup healthcheck success counter. +// Can stop the startup HC and start the regular HC if the startup HC has enough +// consecutive successes. +// NOTE: The caller must lock and sync the container. +func (c *Container) incrementStartupHCSuccessCounter(ctx context.Context) error { + // We don't have a startup HC, can't do anything + if c.config.StartupHealthCheckConfig == nil { + return nil + } + + // Race: someone else got here first + if c.state.StartupHCPassed { + return nil + } + + // Increment the success counter + c.state.StartupHCSuccessCount++ + + logrus.Debugf("Startup healthcheck for container %s succeeded, success counter now %d", c.ID(), c.state.StartupHCSuccessCount) + + // Did we exceed threshold? + recreateTimer := false + if c.config.StartupHealthCheckConfig.Successes == 0 || c.state.StartupHCSuccessCount >= c.config.StartupHealthCheckConfig.Successes { + c.state.StartupHCPassed = true + c.state.StartupHCSuccessCount = 0 + c.state.StartupHCFailureCount = 0 + + recreateTimer = true + } + + if err := c.save(); err != nil { + return err + } + + if !recreateTimer { + return nil + } + // This kills the process the healthcheck is running. + // Which happens to be us. + // So this has to be last - after this, systemd serves us a + // SIGTERM and we exit. + // Special case, via SIGTERM we exit(1) which means systemd logs a failure in the unit. + // We do not want this as the unit will be leaked on failure states unless "reset-failed" + // is called. Fundamentally this is expected so switch it to exit 0. + // NOTE: This is only safe while being called from "podman healthcheck run" which we know + // is the case here as we should not alter the exit code of another process that just + // happened to call this. + shutdown.SetExitCode(0) + return c.recreateHealthCheckTimer(ctx, false, true) +} + +func (c *Container) recreateHealthCheckTimer(ctx context.Context, isStartup bool, isStartupRemoved bool) error { + logrus.Infof("Startup healthcheck for container %s passed, recreating timer", c.ID()) + + oldUnit := c.state.HCUnitName + // Create the new, standard healthcheck timer first. + interval := c.HealthCheckConfig().Interval.String() + if isStartup { + interval = c.config.StartupHealthCheckConfig.StartInterval.String() + } + + if err := c.createTimer(interval, isStartup); err != nil { + return fmt.Errorf("recreating container %s (isStartup: %t) healthcheck: %v", c.ID(), isStartup, err) + } + if err := c.startTimer(isStartup); err != nil { + return fmt.Errorf("restarting container %s (isStartup: %t) healthcheck timer: %v", c.ID(), isStartup, err) + } + + if err := c.removeTransientFiles(ctx, isStartupRemoved, oldUnit); err != nil { + return fmt.Errorf("removing container %s healthcheck: %v", c.ID(), err) + } + return nil +} + +// Increment the current startup healthcheck failure counter. +// Can restart the container if the HC fails enough times consecutively. +// NOTE: The caller must lock and sync the container. +func (c *Container) incrementStartupHCFailureCounter(ctx context.Context) error { + // We don't have a startup HC, can't do anything + if c.config.StartupHealthCheckConfig == nil { + return nil + } + + // Race: someone else got here first + if c.state.StartupHCPassed { + return nil + } + + c.state.StartupHCFailureCount++ + + logrus.Debugf("Startup healthcheck for container %s failed, failure counter now %d", c.ID(), c.state.StartupHCFailureCount) + + if c.config.StartupHealthCheckConfig.Retries != 0 && c.state.StartupHCFailureCount >= c.config.StartupHealthCheckConfig.Retries { + logrus.Infof("Restarting container %s as startup healthcheck failed", c.ID()) + // Restart the container + if err := c.restartWithTimeout(ctx, c.config.StopTimeout); err != nil { + return fmt.Errorf("restarting container %s after healthcheck failure: %v", c.ID(), err) + } + return nil + } + + return c.save() +} + +func newHealthCheckLog(start, end time.Time, exitCode int, log string) define.HealthCheckLog { + return define.HealthCheckLog{ + Start: start.Format(time.RFC3339Nano), + End: end.Format(time.RFC3339Nano), + ExitCode: exitCode, + Output: log, + } +} + +// updateHealthStatus updates the health status of the container +// in the healthcheck log +func (c *Container) updateHealthStatus(status string) error { + healthCheck, err := c.readHealthCheckLog() + if err != nil { + return err + } + healthCheck.Status = status + return c.writeHealthCheckLog(healthCheck) +} + +// isUnhealthy returns true if the current health check status is unhealthy. +func (c *Container) isUnhealthy() (bool, error) { + if !c.HasHealthCheck() { + return false, nil + } + healthCheck, err := c.readHealthCheckLog() + if err != nil { + return false, err + } + return healthCheck.Status == define.HealthCheckUnhealthy, nil +} + +// UpdateHealthCheckLog parses the health check results and writes the log +// NOTE: The caller must lock the container. +func (c *Container) updateHealthCheckLog(hcl define.HealthCheckLog, hcResult define.HealthCheckStatus, inStartPeriod, isStartup bool) (define.HealthCheckResults, error) { + // If we are playing a kube yaml then let's honor the start period time for + // both failing and succeeding cases to match kube behavior. + // So don't update the health check log till the start period is over + if _, ok := c.config.Spec.Annotations[define.KubeHealthCheckAnnotation]; ok && inStartPeriod && !isStartup { + return define.HealthCheckResults{}, nil + } + + healthCheck, err := c.readHealthCheckLog() + if err != nil { + return define.HealthCheckResults{}, err + } + if hcl.ExitCode == 0 { + // set status to healthy, reset failing state to 0 + healthCheck.Status = define.HealthCheckHealthy + healthCheck.FailingStreak = 0 + } else { + if len(healthCheck.Status) < 1 { + healthCheck.Status = define.HealthCheckHealthy + } + if hcResult == define.HealthCheckContainerStopped { + healthCheck.Status = define.HealthCheckStopped + } else if !inStartPeriod { + // increment failing streak + healthCheck.FailingStreak++ + // if failing streak > retries, then status to unhealthy + if healthCheck.FailingStreak >= c.HealthCheckConfig().Retries { + healthCheck.Status = define.HealthCheckUnhealthy + } + } + } + healthCheck.Log = append(healthCheck.Log, hcl) + if c.HealthCheckMaxLogCount() != 0 && len(healthCheck.Log) > int(c.HealthCheckMaxLogCount()) { + healthCheck.Log = healthCheck.Log[1:] + } + return healthCheck, c.writeHealthCheckLog(healthCheck) +} + +func (c *Container) witeToFileHealthCheckResults(path string, result define.HealthCheckResults) error { + newResults, err := json.Marshal(result) + if err != nil { + return fmt.Errorf("unable to marshall healthchecks for writing: %w", err) + } + return os.WriteFile(path, newResults, 0o700) +} + +func (c *Container) getHealthCheckLogDestination() string { + var destination string + switch c.HealthCheckLogDestination() { + case define.DefaultHealthCheckLocalDestination, define.HealthCheckEventsLoggerDestination, "": + destination = filepath.Join(filepath.Dir(c.state.RunDir), "healthcheck.log") + default: + destination = filepath.Join(c.HealthCheckLogDestination(), c.ID()+"-healthcheck.log") + } + return destination +} + +func (c *Container) writeHealthCheckLog(result define.HealthCheckResults) error { + return c.witeToFileHealthCheckResults(c.getHealthCheckLogDestination(), result) +} + +// readHealthCheckLog read HealthCheck logs from the path or events_logger +// The caller should lock the container before this function is called. +func (c *Container) readHealthCheckLog() (define.HealthCheckResults, error) { + return c.readFromFileHealthCheckLog(c.getHealthCheckLogDestination()) +} + +// readFromFileHealthCheckLog returns HealthCheck results by reading the container's +// health check log file. If the health check log file does not exist, then +// an empty healthcheck struct is returned +// The caller should lock the container before this function is called. +func (c *Container) readFromFileHealthCheckLog(path string) (define.HealthCheckResults, error) { + var healthCheck define.HealthCheckResults + b, err := os.ReadFile(path) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // If the file does not exists just return empty healthcheck and no error. + return healthCheck, nil + } + return healthCheck, fmt.Errorf("failed to read health check log file: %w", err) + } + if err := json.Unmarshal(b, &healthCheck); err != nil { + return healthCheck, fmt.Errorf("failed to unmarshal existing healthcheck results in %s: %w", path, err) + } + return healthCheck, nil +} + +// HealthCheckStatus returns the current state of a container with a healthcheck. +// Returns an empty string if no health check is defined for the container. +func (c *Container) HealthCheckStatus() (string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + } + return c.healthCheckStatus() +} + +// Internal function to return the current state of a container with a healthcheck. +// This function does not lock the container. +func (c *Container) healthCheckStatus() (string, error) { + if !c.HasHealthCheck() { + return "", nil + } + + if err := c.syncContainer(); err != nil { + return "", err + } + + results, err := c.readHealthCheckLog() + if err != nil { + return "", fmt.Errorf("unable to get healthcheck log for %s: %w", c.ID(), err) + } + + return results.Status, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/healthcheck_config.go b/vendor/github.com/containers/podman/v5/libpod/healthcheck_config.go new file mode 100644 index 00000000..32947a62 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/healthcheck_config.go @@ -0,0 +1,165 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "strings" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/specgenutil" + "go.podman.io/image/v5/manifest" +) + +type HealthCheckConfig struct { + *manifest.Schema2HealthConfig +} + +type StartupHealthCheckConfig struct { + *define.StartupHealthCheck +} + +type IHealthCheckConfig interface { + SetTo(config *ContainerConfig) + IsStartup() bool + IsNil() bool + IsTimeChanged(oldInterval time.Duration) bool + GetInterval() time.Duration + SetCurrentConfigTo(healthCheckOptions *define.HealthCheckOptions) + IsHealthCheckCommandSet(updateHealthCheckConfig define.UpdateHealthCheckConfig) bool + SetNewHealthCheckOptions(updateHealthCheckConfig define.UpdateHealthCheckConfig, healthCheckOptions *define.HealthCheckOptions) bool +} + +func (h *HealthCheckConfig) SetTo(config *ContainerConfig) { + config.HealthCheckConfig = h.Schema2HealthConfig +} + +func (h *StartupHealthCheckConfig) SetTo(config *ContainerConfig) { + config.StartupHealthCheckConfig = h.StartupHealthCheck +} + +func (h *HealthCheckConfig) IsNil() bool { + return h.Schema2HealthConfig == nil +} + +func (h *StartupHealthCheckConfig) IsNil() bool { + return h.StartupHealthCheck == nil +} + +func (h *HealthCheckConfig) IsStartup() bool { + return false +} + +func (h *StartupHealthCheckConfig) IsStartup() bool { + return true +} + +func (h *HealthCheckConfig) IsTimeChanged(oldInterval time.Duration) bool { + return h.Interval != oldInterval +} + +func (h *StartupHealthCheckConfig) IsTimeChanged(oldInterval time.Duration) bool { + return h.Interval != oldInterval +} + +func (h *HealthCheckConfig) GetInterval() time.Duration { + return h.Interval +} + +func (h *StartupHealthCheckConfig) GetInterval() time.Duration { + return h.Interval +} + +func (h *HealthCheckConfig) SetCurrentConfigTo(healthCheckOptions *define.HealthCheckOptions) { + healthCheckOptions.Cmd = strings.Join(h.Test, " ") + healthCheckOptions.Interval = h.Interval.String() + healthCheckOptions.Retries = h.Retries + healthCheckOptions.Timeout = h.Timeout.String() + healthCheckOptions.StartPeriod = h.StartPeriod.String() +} + +func (h *StartupHealthCheckConfig) SetCurrentConfigTo(healthCheckOptions *define.HealthCheckOptions) { + healthCheckOptions.Cmd = strings.Join(h.Test, " ") + healthCheckOptions.Interval = h.Interval.String() + healthCheckOptions.Retries = h.Retries + healthCheckOptions.Timeout = h.Timeout.String() + healthCheckOptions.Successes = h.Successes +} + +func (h *HealthCheckConfig) IsHealthCheckCommandSet(updateHealthCheckConfig define.UpdateHealthCheckConfig) bool { + return updateHealthCheckConfig.IsHealthCheckCommandSet(h.Schema2HealthConfig) +} + +func (h *StartupHealthCheckConfig) IsHealthCheckCommandSet(updateHealthCheckConfig define.UpdateHealthCheckConfig) bool { + return updateHealthCheckConfig.IsStartupHealthCheckCommandSet(h.StartupHealthCheck) +} + +func (h *HealthCheckConfig) SetNewHealthCheckOptions(updateHealthCheckConfig define.UpdateHealthCheckConfig, healthCheckOptions *define.HealthCheckOptions) bool { + return updateHealthCheckConfig.SetNewHealthCheckConfigTo(healthCheckOptions) +} + +func (h *StartupHealthCheckConfig) SetNewHealthCheckOptions(updateHealthCheckConfig define.UpdateHealthCheckConfig, healthCheckOptions *define.HealthCheckOptions) bool { + return updateHealthCheckConfig.SetNewStartupHealthCheckConfigTo(healthCheckOptions) +} + +func GetNewHealthCheckConfig(originalHealthCheckConfig IHealthCheckConfig, updateHealthCheckConfig define.UpdateHealthCheckConfig) (IHealthCheckConfig, bool, error) { + if originalHealthCheckConfig.IsHealthCheckCommandSet(updateHealthCheckConfig) { + return nil, false, errors.New("startup healthcheck command is not set") + } + + healthCheckOptions := define.HealthCheckOptions{ + Cmd: "", + Interval: define.DefaultHealthCheckInterval, + Retries: int(define.DefaultHealthCheckRetries), + Timeout: define.DefaultHealthCheckTimeout, + StartPeriod: define.DefaultHealthCheckStartPeriod, + Successes: 0, + } + + if originalHealthCheckConfig.IsStartup() { + healthCheckOptions.Retries = 0 + } + + if !originalHealthCheckConfig.IsNil() { + originalHealthCheckConfig.SetCurrentConfigTo(&healthCheckOptions) + } + + noHealthCheck := false + if updateHealthCheckConfig.NoHealthCheck != nil { + noHealthCheck = *updateHealthCheckConfig.NoHealthCheck + } + + changed := originalHealthCheckConfig.SetNewHealthCheckOptions(updateHealthCheckConfig, &healthCheckOptions) + + if noHealthCheck && changed { + return nil, false, errors.New("cannot specify both --no-healthcheck and other HealthCheck flags") + } + + if noHealthCheck { + if originalHealthCheckConfig.IsStartup() { + return &StartupHealthCheckConfig{StartupHealthCheck: nil}, true, nil + } + return &HealthCheckConfig{Schema2HealthConfig: &manifest.Schema2HealthConfig{Test: []string{"NONE"}}}, true, nil + } + + newHealthCheckConfig, err := specgenutil.MakeHealthCheckFromCli( + healthCheckOptions.Cmd, + healthCheckOptions.Interval, + uint(healthCheckOptions.Retries), + healthCheckOptions.Timeout, + healthCheckOptions.StartPeriod, + true, + ) + if err != nil { + return nil, false, err + } + + if originalHealthCheckConfig.IsStartup() { + newStartupHealthCheckConfig := new(define.StartupHealthCheck) + newStartupHealthCheckConfig.Schema2HealthConfig = *newHealthCheckConfig + newStartupHealthCheckConfig.Successes = healthCheckOptions.Successes + return &StartupHealthCheckConfig{StartupHealthCheck: newStartupHealthCheckConfig}, changed, nil + } + return &HealthCheckConfig{Schema2HealthConfig: newHealthCheckConfig}, changed, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/healthcheck_linux.go b/vendor/github.com/containers/podman/v5/libpod/healthcheck_linux.go new file mode 100644 index 00000000..2723c1eb --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/healthcheck_linux.go @@ -0,0 +1,194 @@ +//go:build !remote && systemd + +package libpod + +import ( + "context" + "errors" + "fmt" + "math/rand" + "os" + "os/exec" + "strings" + + "github.com/containers/podman/v5/pkg/errorhandling" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/systemd" + "github.com/sirupsen/logrus" + systemdCommon "go.podman.io/common/pkg/systemd" +) + +// createTimer systemd timers for healthchecks of a container +func (c *Container) createTimer(interval string, isStartup bool) error { + if c.disableHealthCheckSystemd(isStartup) { + return nil + } + + hcUnitName := c.hcUnitName(isStartup, false) + + podman, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get path for podman for a health check timer: %w", err) + } + + var cmd = []string{"--property", "LogLevelMax=notice"} + if rootless.IsRootless() { + cmd = append(cmd, "--user") + } + path := os.Getenv("PATH") + if path != "" { + cmd = append(cmd, "--setenv=PATH="+path) + } + + cmd = append(cmd, "--unit", hcUnitName, fmt.Sprintf("--on-unit-inactive=%s", interval), "--timer-property=AccuracySec=1s", podman) + + if logrus.IsLevelEnabled(logrus.DebugLevel) { + cmd = append(cmd, "--log-level=debug", "--syslog") + } + + cmd = append(cmd, "healthcheck", "run", c.ID()) + + conn, err := systemd.ConnectToDBUS() + if err != nil { + return fmt.Errorf("unable to get systemd connection to add healthchecks: %w", err) + } + conn.Close() + logrus.Debugf("creating systemd-transient files: %s %s", "systemd-run", cmd) + systemdRun := exec.Command("systemd-run", cmd...) + if output, err := systemdRun.CombinedOutput(); err != nil { + exitError := &exec.ExitError{} + if errors.As(err, &exitError) { + return fmt.Errorf("systemd-run failed: %w: output: %s", err, strings.TrimSpace(string(output))) + } + return fmt.Errorf("failed to execute systemd-run: %w", err) + } + + c.state.HCUnitName = hcUnitName + if err := c.save(); err != nil { + return fmt.Errorf("saving container %s healthcheck unit name: %w", c.ID(), err) + } + + return nil +} + +// Wait for a message on the channel. Throw an error if the message is not "done". +func systemdOpSuccessful(c chan string) error { + msg := <-c + switch msg { + case "done": + return nil + default: + return fmt.Errorf("expected %q but received %q", "done", msg) + } +} + +// startTimer starts a systemd timer for the healthchecks +func (c *Container) startTimer(isStartup bool) error { + if c.disableHealthCheckSystemd(isStartup) { + return nil + } + + hcUnitName := c.state.HCUnitName + if hcUnitName == "" { + hcUnitName = c.hcUnitName(isStartup, true) + } + + conn, err := systemd.ConnectToDBUS() + if err != nil { + return fmt.Errorf("unable to get systemd connection to start healthchecks: %w", err) + } + defer conn.Close() + + startFile := fmt.Sprintf("%s.service", hcUnitName) + startChan := make(chan string) + if _, err := conn.RestartUnitContext(context.Background(), startFile, "fail", startChan); err != nil { + return err + } + if err := systemdOpSuccessful(startChan); err != nil { + return fmt.Errorf("starting systemd health-check timer %q: %w", startFile, err) + } + + return nil +} + +// removeTransientFiles removes the systemd timer and unit files +// for the container +func (c *Container) removeTransientFiles(ctx context.Context, isStartup bool, unitName string) error { + if c.disableHealthCheckSystemd(isStartup) { + return nil + } + conn, err := systemd.ConnectToDBUS() + if err != nil { + return fmt.Errorf("unable to get systemd connection to remove healthchecks: %w", err) + } + defer conn.Close() + + // Errors are returned at the very end. Let's make sure to stop and + // clean up as much as possible. + stopErrors := []error{} + + if unitName == "" { + unitName = c.hcUnitName(isStartup, true) + } + // Stop the timer before the service to make sure the timer does not + // fire after the service is stopped. + timerChan := make(chan string) + timerFile := fmt.Sprintf("%s.timer", unitName) + if _, err := conn.StopUnitContext(ctx, timerFile, "ignore-dependencies", timerChan); err != nil { + if !strings.HasSuffix(err.Error(), ".timer not loaded.") { + stopErrors = append(stopErrors, fmt.Errorf("removing health-check timer %q: %w", timerFile, err)) + } + } else if err := systemdOpSuccessful(timerChan); err != nil { + stopErrors = append(stopErrors, fmt.Errorf("stopping systemd health-check timer %q: %w", timerFile, err)) + } + + serviceChan := make(chan string) + serviceFile := fmt.Sprintf("%s.service", unitName) + if _, err := conn.StopUnitContext(ctx, serviceFile, "ignore-dependencies", serviceChan); err != nil { + if !strings.HasSuffix(err.Error(), ".service not loaded.") { + stopErrors = append(stopErrors, fmt.Errorf("removing health-check service %q: %w", serviceFile, err)) + } + } else if err := systemdOpSuccessful(serviceChan); err != nil { + stopErrors = append(stopErrors, fmt.Errorf("stopping systemd health-check service %q: %w", serviceFile, err)) + } + // Reset the service after stopping it to make sure it's being removed, systemd keep failed transient services + // around in its state. We do not care about the error and we need to ensure to reset the state so we do not + // leak resources forever. + if err := conn.ResetFailedUnitContext(ctx, serviceFile); err != nil { + logrus.Debugf("Failed to reset unit file: %q", err) + } + + return errorhandling.JoinErrors(stopErrors) +} + +func (c *Container) disableHealthCheckSystemd(isStartup bool) bool { + if !systemdCommon.RunsOnSystemd() || os.Getenv("DISABLE_HC_SYSTEMD") == "true" { + return true + } + if isStartup { + if c.config.StartupHealthCheckConfig.Interval == 0 { + return true + } + } + if c.config.HealthCheckConfig.Interval == 0 { + return true + } + return false +} + +// Systemd unit name for the healthcheck systemd unit. +// Bare indicates that a random suffix should not be applied to the name. This +// was default behavior previously, and is used for backwards compatibility. +func (c *Container) hcUnitName(isStartup, bare bool) string { + unitName := c.ID() + if isStartup { + unitName += "-startup" + } + if !bare { + // Ensure that unit names are unique from run to run by appending + // a random suffix. + // Ref: RH Jira RHEL-26105 + unitName += fmt.Sprintf("-%x", rand.Int()) + } + return unitName +} diff --git a/vendor/github.com/containers/podman/v5/libpod/healthcheck_nosystemd_linux.go b/vendor/github.com/containers/podman/v5/libpod/healthcheck_nosystemd_linux.go new file mode 100644 index 00000000..4e087ac7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/healthcheck_nosystemd_linux.go @@ -0,0 +1,23 @@ +//go:build !remote && !systemd + +package libpod + +import ( + "context" +) + +// createTimer systemd timers for healthchecks of a container +func (c *Container) createTimer(_ string, _ bool) error { + return nil +} + +// startTimer starts a systemd timer for the healthchecks +func (c *Container) startTimer(_ bool) error { + return nil +} + +// removeTransientFiles removes the systemd timer and unit files +// for the container +func (c *Container) removeTransientFiles(_ context.Context, _ bool, _ string) error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/healthcheck_unsupported.go b/vendor/github.com/containers/podman/v5/libpod/healthcheck_unsupported.go new file mode 100644 index 00000000..8cb7959c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/healthcheck_unsupported.go @@ -0,0 +1,23 @@ +//go:build !remote && !linux + +package libpod + +import ( + "context" +) + +// createTimer systemd timers for healthchecks of a container +func (c *Container) createTimer(_ string, _ bool) error { + return nil +} + +// startTimer starts a systemd timer for the healthchecks +func (c *Container) startTimer(_ bool) error { + return nil +} + +// removeTransientFiles removes the systemd timer and unit files +// for the container +func (c *Container) removeTransientFiles(_ context.Context, _ bool, _ string) error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/info.go b/vendor/github.com/containers/podman/v5/libpod/info.go new file mode 100644 index 00000000..921049b4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/info.go @@ -0,0 +1,323 @@ +//go:build !remote + +package libpod + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "math" + "os" + "runtime" + "strings" + "syscall" + "time" + + "github.com/containers/buildah" + "github.com/containers/buildah/pkg/parse" + "github.com/containers/buildah/pkg/util" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/linkmode" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/version" + "go.podman.io/image/v5/pkg/sysregistriesv2" + "go.podman.io/storage" + "go.podman.io/storage/pkg/system" +) + +// Info returns the store and host information +func (r *Runtime) info() (*define.Info, error) { + info := define.Info{} + versionInfo, err := define.GetVersion() + if err != nil { + return nil, fmt.Errorf("getting version info: %w", err) + } + info.Version = versionInfo + // get host information + hostInfo, err := r.hostInfo() + if err != nil { + return nil, fmt.Errorf("getting host info: %w", err) + } + info.Host = hostInfo + + // get store information + storeInfo, err := r.storeInfo() + if err != nil { + return nil, fmt.Errorf("getting store info: %w", err) + } + info.Store = storeInfo + registries := make(map[string]any) + + sys := r.SystemContext() + data, err := sysregistriesv2.GetRegistries(sys) + if err != nil { + return nil, fmt.Errorf("getting registries: %w", err) + } + for _, reg := range data { + registries[reg.Prefix] = reg + } + regs, err := sysregistriesv2.UnqualifiedSearchRegistries(sys) + if err != nil { + return nil, fmt.Errorf("getting registries: %w", err) + } + if len(regs) > 0 { + registries["search"] = regs + } + volumePlugins := make([]string, 0, len(r.config.Engine.VolumePlugins)+1) + // the local driver always exists + volumePlugins = append(volumePlugins, "local") + for plugin := range r.config.Engine.VolumePlugins { + volumePlugins = append(volumePlugins, plugin) + } + info.Plugins.Volume = volumePlugins + info.Plugins.Network = r.network.Drivers() + info.Plugins.Log = logDrivers + + info.Registries = registries + return &info, nil +} + +// top-level "host" info +func (r *Runtime) hostInfo() (*define.HostInfo, error) { + // let's say OS, arch, number of cpus, amount of memory, maybe os distribution/version, hostname, kernel version, uptime + mi, err := system.ReadMemInfo() + if err != nil { + return nil, fmt.Errorf("reading memory info: %w", err) + } + + hostDistributionInfo := r.GetHostDistributionInfo() + + kv, err := util.ReadKernelVersion() + if err != nil { + return nil, fmt.Errorf("reading kernel version: %w", err) + } + + host, err := os.Hostname() + if err != nil { + return nil, fmt.Errorf("getting hostname: %w", err) + } + + cpuUtil, err := getCPUUtilization() + if err != nil { + return nil, err + } + + locksFree, err := r.lockManager.AvailableLocks() + if err != nil { + return nil, fmt.Errorf("getting free locks: %w", err) + } + + info := define.HostInfo{ + Arch: runtime.GOARCH, + BuildahVersion: buildah.Version, + DatabaseBackend: r.config.Engine.DBBackend, + Linkmode: linkmode.Linkmode(), + CPUs: runtime.NumCPU(), + CPUUtilization: cpuUtil, + Distribution: hostDistributionInfo, + LogDriver: r.config.Containers.LogDriver, + EventLogger: r.eventer.String(), + FreeLocks: locksFree, + Hostname: host, + Kernel: kv, + MemFree: mi.MemFree, + MemTotal: mi.MemTotal, + NetworkBackend: r.config.Network.NetworkBackend, + NetworkBackendInfo: r.network.NetworkInfo(), + OS: runtime.GOOS, + RootlessNetworkCmd: r.config.Network.DefaultRootlessNetworkCmd, + SwapFree: mi.SwapFree, + SwapTotal: mi.SwapTotal, + } + platform := parse.DefaultPlatform() + pArr := strings.Split(platform, "/") + if len(pArr) == 3 { + info.Variant = pArr[2] + } + if err := r.setPlatformHostInfo(&info); err != nil { + return nil, err + } + + conmonInfo, ociruntimeInfo, err := r.defaultOCIRuntime.RuntimeInfo() + if err != nil { + logrus.Errorf("Getting info on OCI runtime %s: %v", r.defaultOCIRuntime.Name(), err) + } else { + info.Conmon = conmonInfo + info.OCIRuntime = ociruntimeInfo + } + + duration, err := util.ReadUptime() + if err != nil { + return nil, fmt.Errorf("reading up time: %w", err) + } + + uptime := struct { + hours float64 + minutes float64 + seconds float64 + }{ + hours: duration.Truncate(time.Hour).Hours(), + minutes: duration.Truncate(time.Minute).Minutes(), + seconds: duration.Truncate(time.Second).Seconds(), + } + + // Could not find a humanize-formatter for time.Duration + var buffer bytes.Buffer + buffer.WriteString(fmt.Sprintf("%.0fh %.0fm %.2fs", + uptime.hours, + math.Mod(uptime.minutes, 60), + math.Mod(uptime.seconds, 60), + )) + if int64(uptime.hours) > 0 { + buffer.WriteString(fmt.Sprintf(" (Approximately %.2f days)", uptime.hours/24)) + } + info.Uptime = buffer.String() + + return &info, nil +} + +func (r *Runtime) getContainerStoreInfo() (define.ContainerStore, error) { + var paused, running, stopped int + cs := define.ContainerStore{} + cons, err := r.GetAllContainers() + if err != nil { + return cs, err + } + cs.Number = len(cons) + for _, con := range cons { + state, err := con.State() + if err != nil { + if errors.Is(err, define.ErrNoSuchCtr) { + // container was probably removed + cs.Number-- + continue + } + return cs, err + } + switch state { + case define.ContainerStateRunning: + running++ + case define.ContainerStatePaused: + paused++ + default: + stopped++ + } + } + cs.Paused = paused + cs.Stopped = stopped + cs.Running = running + return cs, nil +} + +// top-level "store" info +func (r *Runtime) storeInfo() (*define.StoreInfo, error) { + // let's say storage driver in use, number of images, number of containers + configFile, err := storage.DefaultConfigFile() + if err != nil { + return nil, err + } + images, err := r.store.Images() + if err != nil { + return nil, fmt.Errorf("getting number of images: %w", err) + } + conInfo, err := r.getContainerStoreInfo() + if err != nil { + return nil, err + } + imageInfo := define.ImageStore{Number: len(images)} + + var grStats syscall.Statfs_t + if err := syscall.Statfs(r.store.GraphRoot(), &grStats); err != nil { + return nil, fmt.Errorf("unable to collect graph root usage for %q: %w", r.store.GraphRoot(), err) + } + bsize := uint64(grStats.Bsize) //nolint:unconvert,nolintlint // Bsize is not always uint64 on Linux. + allocated := bsize * grStats.Blocks + info := define.StoreInfo{ + ImageStore: imageInfo, + ImageCopyTmpDir: os.Getenv("TMPDIR"), + ContainerStore: conInfo, + GraphRoot: r.store.GraphRoot(), + GraphRootAllocated: allocated, + GraphRootUsed: allocated - (bsize * grStats.Bfree), + RunRoot: r.store.RunRoot(), + GraphDriverName: r.store.GraphDriverName(), + GraphOptions: nil, + VolumePath: r.config.Engine.VolumePath, + ConfigFile: configFile, + TransientStore: r.store.TransientStore(), + } + + graphOptions := map[string]any{} + for _, o := range r.store.GraphOptions() { + split := strings.SplitN(o, "=", 2) + switch { + case strings.HasSuffix(split[0], "mount_program"): + ver, err := version.Program(split[1]) + if err != nil { + logrus.Warnf("Failed to retrieve program version for %s: %v", split[1], err) + } + program := map[string]any{} + program["Executable"] = split[1] + program["Version"] = ver + program["Package"] = version.Package(split[1]) + graphOptions[split[0]] = program + case strings.HasSuffix(split[0], "imagestore"): + key := strings.ReplaceAll(split[0], "imagestore", "additionalImageStores") + if graphOptions[key] == nil { + graphOptions[key] = []string{split[1]} + } else { + graphOptions[key] = append(graphOptions[key].([]string), split[1]) + } + // Fallthrough to include the `imagestore` key to avoid breaking + // Podman v5 API. Should be removed in Podman v6.0.0. + fallthrough + default: + graphOptions[split[0]] = split[1] + } + } + info.GraphOptions = graphOptions + + statusPairs, err := r.store.Status() + if err != nil { + return nil, err + } + status := map[string]string{} + for _, pair := range statusPairs { + status[pair[0]] = pair[1] + } + info.GraphStatus = status + return &info, nil +} + +// GetHostDistributionInfo returns a map containing the host's distribution and version +func (r *Runtime) GetHostDistributionInfo() define.DistributionInfo { + // Populate values in case we cannot find the values + // or the file + dist := define.DistributionInfo{ + Distribution: "unknown", + Version: "unknown", + } + f, err := os.Open("/etc/os-release") + if err != nil { + return dist + } + defer f.Close() + + l := bufio.NewScanner(f) + for l.Scan() { + if after, ok := strings.CutPrefix(l.Text(), "ID="); ok { + dist.Distribution = strings.Trim(after, "\"") + } + if after, ok := strings.CutPrefix(l.Text(), "VARIANT_ID="); ok { + dist.Variant = strings.Trim(after, "\"") + } + if after, ok := strings.CutPrefix(l.Text(), "VERSION_ID="); ok { + dist.Version = strings.Trim(after, "\"") + } + if after, ok := strings.CutPrefix(l.Text(), "VERSION_CODENAME="); ok { + dist.Codename = strings.Trim(after, "\"") + } + } + return dist +} diff --git a/vendor/github.com/containers/podman/v5/libpod/info_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/info_freebsd.go new file mode 100644 index 00000000..83dc06cc --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/info_freebsd.go @@ -0,0 +1,42 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "unsafe" + + "github.com/containers/podman/v5/libpod/define" + "golang.org/x/sys/unix" +) + +func (r *Runtime) setPlatformHostInfo(_ *define.HostInfo) error { + return nil +} + +func timeToPercent(time uint64, total uint64) float64 { + return 100.0 * float64(time) / float64(total) +} + +// getCPUUtilization Returns a CPUUsage object that summarizes CPU +// usage for userspace, system, and idle time. +func getCPUUtilization() (*define.CPUUsage, error) { + buf, err := unix.SysctlRaw("kern.cp_time") + if err != nil { + return nil, fmt.Errorf("reading sysctl kern.cp_time: %w", err) + } + + var total uint64 = 0 + var times [unix.CPUSTATES]uint64 + + for i := 0; i < unix.CPUSTATES; i++ { + val := *(*uint64)(unsafe.Pointer(&buf[8*i])) + times[i] = val + total += val + } + return &define.CPUUsage{ + UserPercent: timeToPercent(times[unix.CP_USER], total), + SystemPercent: timeToPercent(times[unix.CP_SYS], total), + IdlePercent: timeToPercent(times[unix.CP_IDLE], total), + }, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/info_linux.go b/vendor/github.com/containers/podman/v5/libpod/info_linux.go new file mode 100644 index 00000000..2ab53407 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/info_linux.go @@ -0,0 +1,148 @@ +//go:build !remote + +package libpod + +import ( + "bufio" + "fmt" + "math" + "os" + "strconv" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/util" + "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/pasta" + "go.podman.io/common/libnetwork/slirp4netns" + "go.podman.io/common/pkg/apparmor" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/common/pkg/seccomp" + "go.podman.io/common/pkg/version" + "go.podman.io/storage/pkg/unshare" +) + +func (r *Runtime) setPlatformHostInfo(info *define.HostInfo) error { + seccompProfilePath, err := DefaultSeccompPath() + if err != nil { + return fmt.Errorf("getting Seccomp profile path: %w", err) + } + + // Cgroups version + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return fmt.Errorf("reading cgroups mode: %w", err) + } + + // Get Map of all available controllers + availableControllers, err := cgroups.AvailableControllers(nil, unified) + if err != nil { + return fmt.Errorf("getting available cgroup controllers: %w", err) + } + + info.CgroupManager = r.config.Engine.CgroupManager + info.CgroupControllers = availableControllers + info.IDMappings = define.IDMappings{} + info.Security = define.SecurityInfo{ + AppArmorEnabled: apparmor.IsEnabled(), + DefaultCapabilities: strings.Join(r.config.Containers.DefaultCapabilities.Get(), ","), + Rootless: rootless.IsRootless(), + SECCOMPEnabled: seccomp.IsEnabled(), + SECCOMPProfilePath: seccompProfilePath, + SELinuxEnabled: selinux.GetEnabled(), + } + info.Slirp4NetNS = define.SlirpInfo{} + + cgroupVersion := "v1" + if unified { + cgroupVersion = "v2" + } + info.CgroupsVersion = cgroupVersion + + slirp4netnsPath := r.config.Engine.NetworkCmdPath + if slirp4netnsPath == "" { + slirp4netnsPath, _ = r.config.FindHelperBinary(slirp4netns.BinaryName, true) + } + if slirp4netnsPath != "" { + ver, err := version.Program(slirp4netnsPath) + if err != nil { + logrus.Warnf("Failed to retrieve program version for %s: %v", slirp4netnsPath, err) + } + program := define.SlirpInfo{ + Executable: slirp4netnsPath, + Package: version.Package(slirp4netnsPath), + Version: ver, + } + info.Slirp4NetNS = program + } + + pastaPath, _ := r.config.FindHelperBinary(pasta.BinaryName, true) + if pastaPath != "" { + ver, err := version.Program(pastaPath) + if err != nil { + logrus.Warnf("Failed to retrieve program version for %s: %v", pastaPath, err) + } + program := define.PastaInfo{ + Executable: pastaPath, + Package: version.Package(pastaPath), + Version: ver, + } + info.Pasta = program + } + + if rootless.IsRootless() { + uidmappings, gidmappings, err := unshare.GetHostIDMappings("") + if err != nil { + return fmt.Errorf("reading id mappings: %w", err) + } + idmappings := define.IDMappings{ + GIDMap: util.RuntimeSpecToIDtools(gidmappings), + UIDMap: util.RuntimeSpecToIDtools(uidmappings), + } + info.IDMappings = idmappings + } + + return nil +} + +func statToPercent(stats []string) (*define.CPUUsage, error) { + userTotal, err := strconv.ParseFloat(stats[1], 64) + if err != nil { + return nil, fmt.Errorf("unable to parse user value %q: %w", stats[1], err) + } + systemTotal, err := strconv.ParseFloat(stats[3], 64) + if err != nil { + return nil, fmt.Errorf("unable to parse system value %q: %w", stats[3], err) + } + idleTotal, err := strconv.ParseFloat(stats[4], 64) + if err != nil { + return nil, fmt.Errorf("unable to parse idle value %q: %w", stats[4], err) + } + total := userTotal + systemTotal + idleTotal + s := define.CPUUsage{ + UserPercent: math.Round((userTotal/total*100)*100) / 100, + SystemPercent: math.Round((systemTotal/total*100)*100) / 100, + IdlePercent: math.Round((idleTotal/total*100)*100) / 100, + } + return &s, nil +} + +// getCPUUtilization Returns a CPUUsage object that summarizes CPU +// usage for userspace, system, and idle time. +func getCPUUtilization() (*define.CPUUsage, error) { + f, err := os.Open("/proc/stat") + if err != nil { + return nil, err + } + defer f.Close() + scanner := bufio.NewScanner(f) + // Read first line of /proc/stat that has entries for system ("cpu" line) + for scanner.Scan() { + break + } + // column 1 is user, column 3 is system, column 4 is idle + stats := strings.Fields(scanner.Text()) + return statToPercent(stats) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/kube.go b/vendor/github.com/containers/podman/v5/libpod/kube.go new file mode 100644 index 00000000..dd5d527a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/kube.go @@ -0,0 +1,1473 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + "maps" + "math/rand" + "os" + "reflect" + "slices" + "sort" + "strconv" + "strings" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/containers/podman/v5/pkg/env" + v1 "github.com/containers/podman/v5/pkg/k8s.io/api/core/v1" + "github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource" + v12 "github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/util/intstr" + "github.com/containers/podman/v5/pkg/lookup" + "github.com/containers/podman/v5/pkg/namespaces" + "github.com/containers/podman/v5/pkg/specgen" + "github.com/containers/podman/v5/pkg/util" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" +) + +// GenerateForKube takes a slice of libpod containers and generates +// one v1.Pod description that includes just a single container. +func GenerateForKube(ctx context.Context, ctrs []*Container, getService, podmanOnly bool) (*v1.Pod, error) { + // Generate the v1.Pod yaml description + return simplePodWithV1Containers(ctx, ctrs, getService, podmanOnly) +} + +// GenerateForKube takes a slice of libpod containers and generates +// one v1.Pod description +func (p *Pod) GenerateForKube(ctx context.Context, getService, podmanOnly bool) (*v1.Pod, []v1.ServicePort, error) { + // Generate the v1.Pod yaml description + var ( + ports []v1.ContainerPort + servicePorts []v1.ServicePort + ) + + allContainers, err := p.allContainers() + if err != nil { + return nil, servicePorts, err + } + // If the pod has no containers, no sense to generate YAML + if len(allContainers) == 0 { + return nil, servicePorts, fmt.Errorf("pod %s has no containers", p.ID()) + } + // If only an infra container is present, makes no sense to generate YAML + if len(allContainers) == 1 && p.HasInfraContainer() { + return nil, servicePorts, fmt.Errorf("pod %s only has an infra container", p.ID()) + } + + extraHost := make([]v1.HostAlias, 0) + hostNetwork := false + hostUsers := true + infraName := "" + if p.HasInfraContainer() { + infraContainer, err := p.getInfraContainer() + if err != nil { + return nil, servicePorts, err + } + for _, host := range infraContainer.config.ContainerNetworkConfig.HostAdd { + hostname, ip, hasIP := strings.Cut(host, ":") + if !hasIP { + return nil, servicePorts, errors.New("invalid hostAdd") + } + extraHost = append(extraHost, v1.HostAlias{ + IP: ip, + Hostnames: []string{hostname}, + }) + } + ports, err = portMappingToContainerPort(infraContainer.config.PortMappings, getService) + if err != nil { + return nil, servicePorts, err + } + spState := newServicePortState() + servicePorts, err = spState.containerPortsToServicePorts(ports) + if err != nil { + return nil, servicePorts, err + } + hostNetwork = infraContainer.NetworkMode() == string(namespaces.NetworkMode(specgen.Host)) + hostUsers = infraContainer.IDMappings().HostUIDMapping && infraContainer.IDMappings().HostGIDMapping + infraName = infraContainer.config.Name + } + pod, err := p.podWithContainers(ctx, allContainers, ports, hostNetwork, hostUsers, getService, podmanOnly, infraName) + if err != nil { + return nil, servicePorts, err + } + pod.Spec.HostAliases = extraHost + + // Set the pod's restart policy + pod.Spec.RestartPolicy = getPodRestartPolicy(p.config.RestartPolicy) + + if p.SharesPID() { + // unfortunately, go doesn't have a nice way to specify a pointer to a bool + b := true + pod.Spec.ShareProcessNamespace = &b + } + + return pod, servicePorts, nil +} + +func (p *Pod) getInfraContainer() (*Container, error) { + infraID, err := p.InfraContainerID() + if err != nil { + return nil, err + } + return p.runtime.GetContainer(infraID) +} + +func GenerateForKubeDaemonSet(_ context.Context, pod *YAMLPod, options entities.GenerateKubeOptions) (*YAMLDaemonSet, error) { + // Restart policy for DaemonSets can only be set to Always + if pod.Spec.RestartPolicy != "" && pod.Spec.RestartPolicy != v1.RestartPolicyAlways { + return nil, fmt.Errorf("k8s DaemonSets can only have restartPolicy set to Always") + } + + // Error out if the user tries to set replica count + if options.Replicas > 1 { + return nil, fmt.Errorf("k8s DaemonSets don't allow setting replicas") + } + + // Create label map that will be added to podSpec and DaemonSet metadata + // The matching label lets the daemonset know which pod to manage + appKey := "app" + matchLabels := map[string]string{appKey: pod.Name} + // Add the key:value (app:pod-name) to the podSpec labels + if pod.Labels == nil { + pod.Labels = matchLabels + } else { + pod.Labels[appKey] = pod.Name + } + + depSpec := YAMLDaemonSetSpec{ + DaemonSetSpec: v1.DaemonSetSpec{ + Selector: &v12.LabelSelector{ + MatchLabels: matchLabels, + }, + }, + Template: &YAMLPodTemplateSpec{ + PodTemplateSpec: v1.PodTemplateSpec{ + ObjectMeta: pod.ObjectMeta, + }, + Spec: pod.Spec, + }, + } + + // Create the DaemonSet object + dep := YAMLDaemonSet{ + DaemonSet: v1.DaemonSet{ + ObjectMeta: v12.ObjectMeta{ + Name: pod.Name + "-daemonset", + CreationTimestamp: pod.CreationTimestamp, + Labels: pod.Labels, + }, + TypeMeta: v12.TypeMeta{ + Kind: "DaemonSet", + APIVersion: "apps/v1", + }, + }, + Spec: &depSpec, + } + + return &dep, nil +} + +// GenerateForKubeDeployment returns a YAMLDeployment from a YAMLPod that is then used to create a kubernetes Deployment +// kind YAML. +func GenerateForKubeDeployment(_ context.Context, pod *YAMLPod, options entities.GenerateKubeOptions) (*YAMLDeployment, error) { + // Restart policy for Deployments can only be set to Always + if options.Type == define.K8sKindDeployment && (pod.Spec.RestartPolicy != "" && pod.Spec.RestartPolicy != v1.RestartPolicyAlways) { + return nil, fmt.Errorf("k8s Deployments can only have restartPolicy set to Always") + } + + // Create label map that will be added to podSpec and Deployment metadata + // The matching label lets the deployment know which pods to manage + appKey := "app" + matchLabels := map[string]string{appKey: pod.Name} + // Add the key:value (app:pod-name) to the podSpec labels + if pod.Labels == nil { + pod.Labels = matchLabels + } else { + pod.Labels[appKey] = pod.Name + } + + depSpec := YAMLDeploymentSpec{ + DeploymentSpec: v1.DeploymentSpec{ + Selector: &v12.LabelSelector{ + MatchLabels: matchLabels, + }, + }, + Template: &YAMLPodTemplateSpec{ + PodTemplateSpec: v1.PodTemplateSpec{ + ObjectMeta: pod.ObjectMeta, + }, + Spec: pod.Spec, + }, + } + + // Add replicas count if user adds replica number with --replicas flag and is greater than 1 + // If replicas is set to 1, no need to add it to the generated yaml as k8s automatically defaults + // to that. Podman as sets replicas to 1 by default. + if options.Replicas > 1 { + depSpec.Replicas = &options.Replicas + } + + // Create the Deployment object + dep := YAMLDeployment{ + Deployment: v1.Deployment{ + ObjectMeta: v12.ObjectMeta{ + Name: pod.Name + "-deployment", + CreationTimestamp: pod.CreationTimestamp, + Labels: pod.Labels, + }, + TypeMeta: v12.TypeMeta{ + Kind: "Deployment", + APIVersion: "apps/v1", + }, + }, + Spec: &depSpec, + } + + return &dep, nil +} + +// GenerateForKubeJob returns a YAMLDeployment from a YAMLPod that is then used to create a kubernetes Job +// kind YAML. +func GenerateForKubeJob(_ context.Context, pod *YAMLPod, options entities.GenerateKubeOptions) (*YAMLJob, error) { + // Restart policy for Job cannot be set to Always + if options.Type == define.K8sKindJob && pod.Spec.RestartPolicy == v1.RestartPolicyAlways { + return nil, fmt.Errorf("k8s Jobs can not have restartPolicy set to Always; only Never and OnFailure policies allowed") + } + + // Create label map that will be added to podSpec and Job metadata + // The matching label lets the job know which pods to manage + appKey := "app" + matchLabels := map[string]string{appKey: pod.Name} + // Add the key:value (app:pod-name) to the podSpec labels + if pod.Labels == nil { + pod.Labels = matchLabels + } else { + pod.Labels[appKey] = pod.Name + } + + jobSpec := YAMLJobSpec{ + Template: &YAMLPodTemplateSpec{ + PodTemplateSpec: v1.PodTemplateSpec{ + ObjectMeta: pod.ObjectMeta, + }, + Spec: pod.Spec, + }, + } + + // Set the completions and parallelism to 1 by default for the Job + completions, parallelism := int32(1), int32(1) + jobSpec.Completions = &completions + jobSpec.Parallelism = ¶llelism + // Set the restart policy to never as k8s requires a job to have a restart policy + // of onFailure or never set in the kube yaml + jobSpec.Template.Spec.RestartPolicy = v1.RestartPolicyNever + + // Create the Deployment object + job := YAMLJob{ + Job: v1.Job{ + ObjectMeta: v12.ObjectMeta{ + Name: pod.Name + "-job", + CreationTimestamp: pod.CreationTimestamp, + Labels: pod.Labels, + }, + TypeMeta: v12.TypeMeta{ + Kind: "Job", + APIVersion: "batch/v1", + }, + }, + Spec: &jobSpec, + } + + return &job, nil +} + +// GenerateForKube generates a v1.PersistentVolumeClaim from a libpod volume. +func (v *Volume) GenerateForKube() *v1.PersistentVolumeClaim { + annotations := make(map[string]string) + annotations[util.VolumeDriverAnnotation] = v.Driver() + + for k, v := range v.Options() { + switch k { + case "o": + annotations[util.VolumeMountOptsAnnotation] = v + case "device": + annotations[util.VolumeDeviceAnnotation] = v + case "type": + annotations[util.VolumeTypeAnnotation] = v + case "UID": + annotations[util.VolumeUIDAnnotation] = v + case "GID": + annotations[util.VolumeGIDAnnotation] = v + } + } + + return &v1.PersistentVolumeClaim{ + TypeMeta: v12.TypeMeta{ + Kind: "PersistentVolumeClaim", + APIVersion: "v1", + }, + ObjectMeta: v12.ObjectMeta{ + Name: v.Name(), + Labels: v.Labels(), + Annotations: annotations, + CreationTimestamp: v12.Now(), + }, + Spec: v1.PersistentVolumeClaimSpec{ + Resources: v1.ResourceRequirements{ + Requests: map[v1.ResourceName]resource.Quantity{ + v1.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + }, + } +} + +// YAMLPodSpec represents the same k8s API core PodSpec struct with a small +// change and that is having Containers as a pointer to YAMLContainer. +// Because Go doesn't omit empty struct and we want to omit Status in YAML +// if it's empty. Fixes: GH-11998 +type YAMLPodSpec struct { + v1.PodSpec + Containers []*YAMLContainer `json:"containers"` +} + +// YAMLPod represents the same k8s API core Pod struct with a small +// change and that is having Spec as a pointer to YAMLPodSpec and +// Status as a pointer to k8s API core PodStatus. +// Because Go doesn't omit empty struct and we want to omit Status in YAML +// if it's empty. Fixes: GH-11998 +type YAMLPod struct { + v1.Pod + Spec *YAMLPodSpec `json:"spec,omitempty"` + Status *v1.PodStatus `json:"status,omitempty"` +} + +// YAMLPodTemplateSpec represents the same k8s API core PodTemplateStruct with a +// small change and that is having Spec as a pointer to YAMLPodSpec. +// Because Go doesn't omit empty struct and we want to omit any empty structs in the +// Pod yaml. This is used when generating a Deployment kind. +type YAMLPodTemplateSpec struct { + v1.PodTemplateSpec + Spec *YAMLPodSpec `json:"spec,omitempty"` +} + +// YAMLDeploymentSpec represents the same k8s API core DeploymentSpec with a small +// change and that is having Template as a pointer to YAMLPodTemplateSpec and Strategy +// as a pointer to k8s API core DeploymentStrategy. +// Because Go doesn't omit empty struct and we want to omit Strategy and any fields in the Pod YAML +// if it's empty. +type YAMLDeploymentSpec struct { + v1.DeploymentSpec + Template *YAMLPodTemplateSpec `json:"template,omitempty"` + Strategy *v1.DeploymentStrategy `json:"strategy,omitempty"` +} + +// YAMLDaemonSetSpec represents the same k8s API core DeploymentSpec with a small +// change and that is having Template as a pointer to YAMLPodTemplateSpec and Strategy +// as a pointer to k8s API core DaemonSetStrategy. +// Because Go doesn't omit empty struct and we want to omit Strategy and any fields in the Pod YAML +// if it's empty. +type YAMLDaemonSetSpec struct { + v1.DaemonSetSpec + Template *YAMLPodTemplateSpec `json:"template,omitempty"` + Strategy *v1.DaemonSetUpdateStrategy `json:"strategy,omitempty"` +} + +// YAMLJobSpec represents the same k8s API core JobSpec with a small +// change and that is having Template as a pointer to YAMLPodTemplateSpec +// because Go doesn't omit empty struct and we want to omit Strategy and any fields in the Pod YAML +// if it's empty. +type YAMLJobSpec struct { + v1.JobSpec + Template *YAMLPodTemplateSpec `json:"template,omitempty"` +} + +// YAMLDaemonSet represents the same k8s API core DaemonSet with a small change +// and that is having Spec as a pointer to YAMLDaemonSetSpec and Status as a pointer to +// k8s API core DaemonSetStatus. +// Because Go doesn't omit empty struct and we want to omit Status and any fields in the DaemonSetSpec +// if it's empty. +type YAMLDaemonSet struct { + v1.DaemonSet + Spec *YAMLDaemonSetSpec `json:"spec,omitempty"` + Status *v1.DaemonSetStatus `json:"status,omitempty"` +} + +// YAMLDeployment represents the same k8s API core Deployment with a small change +// and that is having Spec as a pointer to YAMLDeploymentSpec and Status as a pointer to +// k8s API core DeploymentStatus. +// Because Go doesn't omit empty struct and we want to omit Status and any fields in the DeploymentSpec +// if it's empty. +type YAMLDeployment struct { + v1.Deployment + Spec *YAMLDeploymentSpec `json:"spec,omitempty"` + Status *v1.DeploymentStatus `json:"status,omitempty"` +} + +type YAMLJob struct { + v1.Job + Spec *YAMLJobSpec `json:"spec,omitempty"` + Status *v1.JobStatus `json:"status,omitempty"` +} + +// YAMLService represents the same k8s API core Service struct with a small +// change and that is having Status as a pointer to k8s API core ServiceStatus. +// Because Go doesn't omit empty struct and we want to omit Status in YAML +// if it's empty. Fixes: GH-11998 +type YAMLService struct { + v1.Service + Status *v1.ServiceStatus `json:"status,omitempty"` +} + +// YAMLContainer represents the same k8s API core Container struct with a small +// change and that is having Resources as a pointer to k8s API core ResourceRequirements. +// Because Go doesn't omit empty struct and we want to omit Status in YAML +// if it's empty. Fixes: GH-11998 +type YAMLContainer struct { + v1.Container + Resources *v1.ResourceRequirements `json:"resources,omitempty"` +} + +// ConvertV1PodToYAMLPod takes k8s API core Pod and returns a pointer to YAMLPod +func ConvertV1PodToYAMLPod(pod *v1.Pod) *YAMLPod { + cs := []*YAMLContainer{} + for _, cc := range pod.Spec.Containers { + var res *v1.ResourceRequirements + if len(cc.Resources.Limits) > 0 || len(cc.Resources.Requests) > 0 { + res = &cc.Resources + } + cs = append(cs, &YAMLContainer{Container: cc, Resources: res}) + } + mpo := &YAMLPod{Pod: *pod} + mpo.Spec = &YAMLPodSpec{PodSpec: pod.Spec, Containers: cs} + for _, ctr := range pod.Spec.Containers { + if ctr.SecurityContext == nil || ctr.SecurityContext.SELinuxOptions == nil { + continue + } + selinuxOpts := ctr.SecurityContext.SELinuxOptions + if selinuxOpts.User == "" && selinuxOpts.Role == "" && selinuxOpts.Type == "" && selinuxOpts.Level == "" && selinuxOpts.FileType == "" { + ctr.SecurityContext.SELinuxOptions = nil + } + } + dnsCfg := pod.Spec.DNSConfig + if dnsCfg != nil && (len(dnsCfg.Nameservers)+len(dnsCfg.Searches)+len(dnsCfg.Options) > 0) { + mpo.Spec.DNSConfig = dnsCfg + } + status := pod.Status + if status.Phase != "" || len(status.Conditions) > 0 || + status.Message != "" || status.Reason != "" || + status.NominatedNodeName != "" || status.HostIP != "" || + status.PodIP != "" || status.StartTime != nil || + len(status.InitContainerStatuses) > 0 || len(status.ContainerStatuses) > 0 || status.QOSClass != "" || len(status.EphemeralContainerStatuses) > 0 { + mpo.Status = &status + } + return mpo +} + +// GenerateKubeServiceFromV1Pod creates a v1 service object from a v1 pod object +func GenerateKubeServiceFromV1Pod(pod *v1.Pod, servicePorts []v1.ServicePort) (YAMLService, error) { + service := YAMLService{} + selector := make(map[string]string) + selector["app"] = pod.Labels["app"] + ports := servicePorts + if len(ports) == 0 { + p, err := containersToServicePorts(pod.Spec.Containers) + if err != nil { + return service, err + } + ports = p + } + serviceSpec := v1.ServiceSpec{ + Ports: ports, + Selector: selector, + Type: v1.ServiceTypeNodePort, + } + service.Spec = serviceSpec + service.ObjectMeta = pod.ObjectMeta + // Reset the annotations for the service as the pod annotations are not needed for the service + service.ObjectMeta.Annotations = nil + tm := v12.TypeMeta{ + Kind: "Service", + APIVersion: pod.TypeMeta.APIVersion, + } + service.TypeMeta = tm + return service, nil +} + +// servicePortState allows calling containerPortsToServicePorts for a single service +type servicePortState struct { + // A program using the shared math/rand state with the default seed will produce the same sequence of pseudo-random numbers + // for each execution. Use a private RNG state not to interfere with other users. + rng *rand.Rand + usedPorts map[int]struct{} +} + +func newServicePortState() servicePortState { + return servicePortState{ + rng: rand.New(rand.NewSource(time.Now().UnixNano())), + usedPorts: map[int]struct{}{}, + } +} + +// containerPortsToServicePorts takes a slice of containerports and generates a +// slice of service ports +func (state *servicePortState) containerPortsToServicePorts(containerPorts []v1.ContainerPort) ([]v1.ServicePort, error) { + sps := make([]v1.ServicePort, 0, len(containerPorts)) + for _, cp := range containerPorts { + var nodePort int + attempt := 0 + for { + // Legal nodeport range is 30000-32767 + nodePort = 30000 + state.rng.Intn(32767-30000+1) + if _, found := state.usedPorts[nodePort]; !found { + state.usedPorts[nodePort] = struct{}{} + break + } + attempt++ + if attempt >= 100 { + return nil, fmt.Errorf("too many attempts trying to generate a unique NodePort number") + } + } + servicePort := v1.ServicePort{ + Protocol: cp.Protocol, + Port: cp.ContainerPort, + NodePort: int32(nodePort), + Name: strconv.Itoa(int(cp.ContainerPort)), + TargetPort: intstr.Parse(strconv.Itoa(int(cp.ContainerPort))), + } + sps = append(sps, servicePort) + } + return sps, nil +} + +// containersToServicePorts takes a slice of v1.Containers and generates an +// inclusive list of serviceports to expose +func containersToServicePorts(containers []v1.Container) ([]v1.ServicePort, error) { + state := newServicePortState() + sps := make([]v1.ServicePort, 0, len(containers)) + for _, ctr := range containers { + ports, err := state.containerPortsToServicePorts(ctr.Ports) + if err != nil { + return nil, err + } + sps = append(sps, ports...) + } + return sps, nil +} + +func (p *Pod) podWithContainers(ctx context.Context, containers []*Container, ports []v1.ContainerPort, hostNetwork, hostUsers, getService, podmanOnly bool, infraName string) (*v1.Pod, error) { + deDupPodVolumes := make(map[string]*v1.Volume) + first := true + podContainers := make([]v1.Container, 0, len(containers)) + podInitCtrs := []v1.Container{} + podAnnotations := make(map[string]string) + dnsInfo := v1.PodDNSConfig{} + var ( + hostname string + stopTimeout *uint + ) + + cfg, err := config.Default() + if err != nil { + return nil, err + } + + // Let's sort the containers in order of created time + // This will ensure that the init containers are defined in the correct order in the kube yaml + sort.Slice(containers, func(i, j int) bool { return containers[i].CreatedTime().Before(containers[j].CreatedTime()) }) + + for _, ctr := range containers { + if ctr.IsInfra() { + // If there is an user namespace for the infra container, then register it for the entire pod. + if v, found := ctr.config.Spec.Annotations[define.UserNsAnnotation]; found { + podAnnotations[define.UserNsAnnotation] = v + } + _, _, infraDNS, _, err := containerToV1Container(ctx, ctr, getService) + if err != nil { + return nil, err + } + if infraDNS != nil { + if servers := infraDNS.Nameservers; len(servers) > 0 { + dnsInfo.Nameservers = servers + } + if searches := infraDNS.Searches; len(searches) > 0 { + dnsInfo.Searches = searches + } + if options := infraDNS.Options; len(options) > 0 { + dnsInfo.Options = options + } + } + // If the infraName is not the podID-infra, that means the user set another infra name using + // --infra-name during pod creation + if infraName != "" && infraName != p.ID()[:12]+"-infra" { + podAnnotations[define.InfraNameAnnotation] = infraName + } + } else { + for k, v := range ctr.config.Spec.Annotations { + if !podmanOnly && (define.IsReservedAnnotation(k)) { + continue + } + podAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = v + } + // Convert auto-update labels into kube annotations + maps.Copy(podAnnotations, getAutoUpdateAnnotations(ctr.Name(), ctr.Labels())) + isInit := ctr.IsInitCtr() + // Since hostname is only set at pod level, set the hostname to the hostname of the first container we encounter + if hostname == "" { + // Only set the hostname if it is not set to the truncated container ID, which we do by default if no + // hostname is specified for the container and if it is not set to the pod name. + if !strings.Contains(ctr.ID(), ctr.Hostname()) && ctr.Hostname() != p.Name() { + hostname = ctr.Hostname() + } + } + + // Pick the first container that has a stop-timeout set and use that value + // Ignore podman's default + if ctr.config.StopTimeout != cfg.Engine.StopTimeout && stopTimeout == nil { + stopTimeout = &ctr.config.StopTimeout + } + + ctr, volumes, _, annotations, err := containerToV1Container(ctx, ctr, getService) + if err != nil { + return nil, err + } + for k, v := range annotations { + podAnnotations[define.BindMountPrefix] = k + ":" + v + } + // Since port bindings for the pod are handled by the + // infra container, wipe them here only if we are sharing the net namespace + // If the network namespace is not being shared in the pod, then containers + // can have their own network configurations + if p.SharesNet() { + ctr.Ports = nil + + // We add the original port declarations from the libpod infra container + // to the first kubernetes container description because otherwise we lose + // the original container/port bindings. + // Add the port configuration to the first regular container or the first + // init container if only init containers have been created in the pod. + if first && len(ports) > 0 && (!isInit || len(containers) == 2) { + ctr.Ports = ports + first = false + } + } + if isInit { + podInitCtrs = append(podInitCtrs, ctr) + continue + } + podContainers = append(podContainers, ctr) + // Deduplicate volumes, so if containers in the pod share a volume, it's only + // listed in the volumes section once + for _, vol := range volumes { + deDupPodVolumes[vol.Name] = &vol + } + } + } + podVolumes := []v1.Volume{} + for _, vol := range deDupPodVolumes { + podVolumes = append(podVolumes, *vol) + } + podName := removeUnderscores(p.Name()) + + return newPodObject( + podName, + podAnnotations, + podInitCtrs, + podContainers, + podVolumes, + &dnsInfo, + hostNetwork, + hostUsers, + hostname, + stopTimeout), nil +} + +func newPodObject(podName string, annotations map[string]string, initCtrs, containers []v1.Container, volumes []v1.Volume, dnsOptions *v1.PodDNSConfig, hostNetwork, hostUsers bool, hostname string, stopTimeout *uint) *v1.Pod { + tm := v12.TypeMeta{ + Kind: "Pod", + APIVersion: "v1", + } + + // Add a label called "app" with the containers name as a value + labels := make(map[string]string) + labels["app"] = removeUnderscores(podName) + om := v12.ObjectMeta{ + // The name of the pod is container_name-libpod + Name: podName, + Labels: labels, + // CreationTimestamp seems to be required, so adding it; in doing so, the timestamp + // will reflect time this is run (not container create time) because the conversion + // of the container create time to v1 Time is probably not warranted nor worthwhile. + CreationTimestamp: v12.Now(), + Annotations: annotations, + } + ps := v1.PodSpec{ + Containers: containers, + Hostname: hostname, + HostNetwork: hostNetwork, + InitContainers: initCtrs, + Volumes: volumes, + } + if !hostUsers { + ps.HostUsers = &hostUsers + } + if dnsOptions != nil && (len(dnsOptions.Nameservers)+len(dnsOptions.Searches)+len(dnsOptions.Options) > 0) { + ps.DNSConfig = dnsOptions + } + if stopTimeout != nil { + terminationGracePeriod := int64(*stopTimeout) + ps.TerminationGracePeriodSeconds = &terminationGracePeriod + } + p := v1.Pod{ + TypeMeta: tm, + ObjectMeta: om, + Spec: ps, + } + return &p +} + +// simplePodWithV1Containers is a function used by inspect when kube yaml needs to be generated +// for a single container. we "insert" that container description in a pod. +func simplePodWithV1Containers(ctx context.Context, ctrs []*Container, getService, podmanOnly bool) (*v1.Pod, error) { + cfg, err := config.Default() + if err != nil { + return nil, err + } + + kubeCtrs := make([]v1.Container, 0, len(ctrs)) + kubeInitCtrs := []v1.Container{} + kubeVolumes := make([]v1.Volume, 0) + hostUsers := true + hostNetwork := true + podDNS := v1.PodDNSConfig{} + kubeAnnotations := make(map[string]string) + ctrNames := make([]string, 0, len(ctrs)) + var ( + hostname string + restartPolicy *string + stopTimeout *uint + ) + for _, ctr := range ctrs { + ctrNames = append(ctrNames, removeUnderscores(ctr.Name())) + for k, v := range ctr.config.Spec.Annotations { + if !podmanOnly && define.IsReservedAnnotation(k) { + continue + } + kubeAnnotations[fmt.Sprintf("%s/%s", k, removeUnderscores(ctr.Name()))] = v + } + + // Convert auto-update labels into kube annotations + maps.Copy(kubeAnnotations, getAutoUpdateAnnotations(ctr.Name(), ctr.Labels())) + + isInit := ctr.IsInitCtr() + // Since hostname is only set at pod level, set the hostname to the hostname of the first container we encounter + if hostname == "" { + // Only set the hostname if it is not set to the truncated container ID, which we do by default if no + // hostname is specified for the container + if !strings.Contains(ctr.ID(), ctr.Hostname()) { + hostname = ctr.Hostname() + } + } + + // Pick the first container that has a stop-timeout set and use that value + // Ignore podman's default + if ctr.config.StopTimeout != cfg.Engine.StopTimeout && stopTimeout == nil { + stopTimeout = &ctr.config.StopTimeout + } + + // Use the restart policy of the first non-init container + if !isInit && restartPolicy == nil { + restartPolicy = &ctr.config.RestartPolicy + } + + if ctr.config.Spec.Process != nil { + var ulimitArr []string + defaultUlimits := cfg.Ulimits() + for _, ulimit := range ctr.config.Spec.Process.Rlimits { + finalUlimit := strings.ToLower(strings.ReplaceAll(ulimit.Type, "RLIMIT_", "")) + "=" + strconv.Itoa(int(ulimit.Soft)) + ":" + strconv.Itoa(int(ulimit.Hard)) + // compare ulimit with default list so we don't add it twice + if slices.Contains(defaultUlimits, finalUlimit) { + continue + } + + ulimitArr = append(ulimitArr, finalUlimit) + } + + if len(ulimitArr) > 0 { + kubeAnnotations[define.UlimitAnnotation] = strings.Join(ulimitArr, ",") + } + } + + if !ctr.HostNetwork() { + hostNetwork = false + } + if !ctr.IDMappings().HostUIDMapping || !ctr.IDMappings().HostGIDMapping { + hostUsers = false + } + kubeCtr, kubeVols, ctrDNS, annotations, err := containerToV1Container(ctx, ctr, getService) + if err != nil { + return nil, err + } + for k, v := range annotations { + kubeAnnotations[define.BindMountPrefix] = k + ":" + v + } + if isInit { + kubeInitCtrs = append(kubeInitCtrs, kubeCtr) + } else { + kubeCtrs = append(kubeCtrs, kubeCtr) + } + kubeVolumes = append(kubeVolumes, kubeVols...) + // Combine DNS information in sum'd structure + if ctrDNS != nil { + // nameservers + if servers := ctrDNS.Nameservers; servers != nil { + if podDNS.Nameservers == nil { + podDNS.Nameservers = make([]string, 0) + } + for _, s := range servers { + if !slices.Contains(podDNS.Nameservers, s) { // only append if it does not exist + podDNS.Nameservers = append(podDNS.Nameservers, s) + } + } + } + // search domains + if domains := ctrDNS.Searches; domains != nil { + if podDNS.Searches == nil { + podDNS.Searches = make([]string, 0) + } + for _, d := range domains { + if !slices.Contains(podDNS.Searches, d) { // only append if it does not exist + podDNS.Searches = append(podDNS.Searches, d) + } + } + } + // dns options + if options := ctrDNS.Options; options != nil { + if podDNS.Options == nil { + podDNS.Options = make([]v1.PodDNSConfigOption, 0) + } + podDNS.Options = append(podDNS.Options, options...) + } + } // end if ctrDNS + } + podName := removeUnderscores(ctrs[0].Name()) + // Check if the pod name and container name will end up conflicting + // Append -pod if so + if slices.Contains(ctrNames, podName) { + podName += "-pod" + } + + pod := newPodObject( + podName, + kubeAnnotations, + kubeInitCtrs, + kubeCtrs, + kubeVolumes, + &podDNS, + hostNetwork, + hostUsers, + hostname, + stopTimeout) + + // Set the pod's restart policy + policy := "" + if restartPolicy != nil { + policy = *restartPolicy + } + pod.Spec.RestartPolicy = getPodRestartPolicy(policy) + + return pod, nil +} + +// getPodRestartPolicy returns the pod restart policy to be set in the generated kube yaml +func getPodRestartPolicy(policy string) v1.RestartPolicy { + switch policy { + case define.RestartPolicyNo: + return v1.RestartPolicyNever + case define.RestartPolicyAlways: + return v1.RestartPolicyAlways + case define.RestartPolicyOnFailure: + return v1.RestartPolicyOnFailure + default: // some pod/ctr create from cmdline, such as "" - set it to "" and let k8s handle the defaults + return "" + } +} + +// containerToV1Container converts information we know about a libpod container +// to a V1.Container specification. +func containerToV1Container(ctx context.Context, c *Container, getService bool) (v1.Container, []v1.Volume, *v1.PodDNSConfig, map[string]string, error) { + kubeContainer := v1.Container{} + kubeVolumes := []v1.Volume{} + annotations := make(map[string]string) + kubeSec, hasSecData, err := generateKubeSecurityContext(c) + if err != nil { + return kubeContainer, kubeVolumes, nil, annotations, err + } + + // NOTE: a privileged container mounts all of /dev/*. + if !c.Privileged() && c.config.Spec.Linux != nil && len(c.config.Spec.Linux.Devices) > 0 { + // TODO Enable when we can support devices and their names + kubeContainer.VolumeDevices = generateKubeVolumeDeviceFromLinuxDevice(c.config.Spec.Linux.Devices) + return kubeContainer, kubeVolumes, nil, annotations, fmt.Errorf("linux devices: %w", define.ErrNotImplemented) + } + + if !c.IsInfra() && len(c.config.Rootfs) > 0 { + return kubeContainer, kubeVolumes, nil, annotations, fmt.Errorf("k8s does not support Rootfs") + } + + if len(c.config.UserVolumes) > 0 { + volumeMounts, volumes, localAnnotations, err := libpodMountsToKubeVolumeMounts(c) + if err != nil { + return kubeContainer, kubeVolumes, nil, nil, err + } + annotations = localAnnotations + kubeContainer.VolumeMounts = volumeMounts + kubeVolumes = append(kubeVolumes, volumes...) + } + + portmappings, err := c.PortMappings() + if err != nil { + return kubeContainer, kubeVolumes, nil, annotations, err + } + ports, err := portMappingToContainerPort(portmappings, getService) + if err != nil { + return kubeContainer, kubeVolumes, nil, annotations, err + } + + // Handle command and arguments. + if ep := c.Entrypoint(); len(ep) > 0 { + // If we have an entrypoint, set the container's command as + // arguments. + kubeContainer.Command = ep + kubeContainer.Args = c.Command() + } else { + kubeContainer.Command = c.Command() + } + + kubeContainer.Name = removeUnderscores(c.Name()) + _, image := c.Image() + + kubeContainer.Image = image + kubeContainer.Stdin = c.Stdin() + if len(image) > 0 { + img, _, err := c.runtime.libimageRuntime.LookupImage(image, nil) + if err != nil { + return kubeContainer, kubeVolumes, nil, annotations, fmt.Errorf("looking up image %q of container %q: %w", image, c.ID(), err) + } + imgData, err := img.Inspect(ctx, nil) + if err != nil { + return kubeContainer, kubeVolumes, nil, annotations, err + } + // If the user doesn't set a command/entrypoint when creating the container with podman and + // is using the image command or entrypoint from the image, don't add it to the generated kube yaml + if reflect.DeepEqual(imgData.Config.Cmd, kubeContainer.Command) || reflect.DeepEqual(imgData.Config.Entrypoint, kubeContainer.Command) { + kubeContainer.Command = nil + } + + if c.WorkingDir() != "/" && imgData.Config.WorkingDir != c.WorkingDir() { + kubeContainer.WorkingDir = c.WorkingDir() + } + + if imgData.User == c.User() && hasSecData { + kubeSec.RunAsGroup, kubeSec.RunAsUser = nil, nil + } + // If the image has user set as a positive integer value, then set runAsNonRoot to true + // in the kube yaml + imgUserID, err := strconv.Atoi(imgData.User) + if err == nil && imgUserID > 0 { + trueBool := true + kubeSec.RunAsNonRoot = &trueBool + } + + envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env, imgData.Config.Env) + if err != nil { + return kubeContainer, kubeVolumes, nil, annotations, err + } + kubeContainer.Env = envVariables + } + + kubeContainer.Ports = ports + // This should not be applicable + // container.EnvFromSource = + if hasSecData { + kubeContainer.SecurityContext = kubeSec + } + kubeContainer.StdinOnce = false + kubeContainer.TTY = c.Terminal() + + resources := c.LinuxResources() + if resources != nil { + if resources.Memory != nil && + resources.Memory.Limit != nil { + if kubeContainer.Resources.Limits == nil { + kubeContainer.Resources.Limits = v1.ResourceList{} + } + + qty := kubeContainer.Resources.Limits.Memory() + qty.Set(*c.config.Spec.Linux.Resources.Memory.Limit) + kubeContainer.Resources.Limits[v1.ResourceMemory] = *qty + } + + if resources.CPU != nil && + resources.CPU.Quota != nil && + resources.CPU.Period != nil { + quota := *resources.CPU.Quota + period := *resources.CPU.Period + + if quota > 0 && period > 0 { + cpuLimitMilli := int64(1000 * util.PeriodAndQuotaToCores(period, quota)) + + // Kubernetes: precision finer than 1m is not allowed + if cpuLimitMilli >= 1 { + if kubeContainer.Resources.Limits == nil { + kubeContainer.Resources.Limits = v1.ResourceList{} + } + + qty := kubeContainer.Resources.Limits.Cpu() + qty.SetMilli(cpuLimitMilli) + kubeContainer.Resources.Limits[v1.ResourceCPU] = *qty + } + } + } + } + + // Obtain the DNS entries from the container + dns := v1.PodDNSConfig{} + + // DNS servers + if servers := c.config.DNSServer; len(servers) > 0 { + dnsServers := make([]string, 0) + for _, server := range servers { + dnsServers = append(dnsServers, server.String()) + } + dns.Nameservers = dnsServers + } + + // DNS search domains + if searches := c.config.DNSSearch; len(searches) > 0 { + dns.Searches = searches + } + + // DNS options + if options := c.config.DNSOption; len(options) > 0 { + dnsOptions := make([]v1.PodDNSConfigOption, 0) + for _, option := range options { + // the option can be "k:v" or just "k", no delimiter is required + name, value, _ := strings.Cut(option, ":") + dnsOpt := v1.PodDNSConfigOption{ + Name: name, + Value: &value, + } + dnsOptions = append(dnsOptions, dnsOpt) + } + dns.Options = dnsOptions + } + return kubeContainer, kubeVolumes, &dns, annotations, nil +} + +// portMappingToContainerPort takes a portmapping and converts +// it to a v1.ContainerPort format for kube output +func portMappingToContainerPort(portMappings []types.PortMapping, getService bool) ([]v1.ContainerPort, error) { + containerPorts := make([]v1.ContainerPort, 0, len(portMappings)) + for _, p := range portMappings { + for proto := range strings.SplitSeq(p.Protocol, ",") { + var protocol v1.Protocol + switch strings.ToUpper(proto) { + case "TCP": + // do nothing as it is the default protocol in k8s, there is no need to explicitly + // add it to the generated yaml + case "UDP": + protocol = v1.ProtocolUDP + case "SCTP": + protocol = v1.ProtocolSCTP + default: + return containerPorts, fmt.Errorf("unknown network protocol %s", p.Protocol) + } + for i := uint16(0); i < p.Range; i++ { + cp := v1.ContainerPort{ + // Name will not be supported + HostIP: p.HostIP, + ContainerPort: int32(p.ContainerPort + i), + Protocol: protocol, + } + if !getService { + cp.HostPort = int32(p.HostPort + i) + } + containerPorts = append(containerPorts, cp) + } + } + } + return containerPorts, nil +} + +// libpodEnvVarsToKubeEnvVars converts a key=value string slice to []v1.EnvVar +func libpodEnvVarsToKubeEnvVars(envs []string, imageEnvs []string) ([]v1.EnvVar, error) { + defaultEnv := env.DefaultEnvVariables() + envVars := make([]v1.EnvVar, 0, len(envs)) + imageMap := make(map[string]string, len(imageEnvs)) + for _, ie := range imageEnvs { + key, val, _ := strings.Cut(ie, "=") + imageMap[key] = val + } + for _, e := range envs { + envName, envValue, hasValue := strings.Cut(e, "=") + if !hasValue { + return envVars, fmt.Errorf("environment variable %s is malformed; should be key=value", e) + } + if defaultEnv[envName] == envValue { + continue + } + if imageMap[envName] == envValue { + continue + } + ev := v1.EnvVar{ + Name: envName, + Value: envValue, + } + envVars = append(envVars, ev) + } + return envVars, nil +} + +// libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands +func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume, map[string]string, error) { + namedVolumes, mounts := c.SortUserVolumes(c.config.Spec) + vms := make([]v1.VolumeMount, 0, len(mounts)) + vos := make([]v1.Volume, 0, len(mounts)) + annotations := make(map[string]string) + + var suffix string + for index, m := range mounts { + for _, opt := range m.Options { + if opt == "Z" || opt == "z" { + annotations[m.Source] = opt + break + } + } + vm, vo, err := generateKubeVolumeMount(m) + if err != nil { + return vms, vos, annotations, err + } + // Name will be the same, so use the index as suffix + suffix = fmt.Sprintf("-%d", index) + vm.Name += suffix + vo.Name += suffix + vms = append(vms, vm) + vos = append(vos, vo) + } + for _, v := range namedVolumes { + vm, vo := generateKubePersistentVolumeClaim(v) + vms = append(vms, vm) + vos = append(vos, vo) + } + return vms, vos, annotations, nil +} + +// generateKubePersistentVolumeClaim converts a ContainerNamedVolume to a Kubernetes PersistentVolumeClaim +func generateKubePersistentVolumeClaim(v *ContainerNamedVolume) (v1.VolumeMount, v1.Volume) { + ro := slices.Contains(v.Options, "ro") + + // To avoid naming conflicts with any host path mounts, add a unique suffix to the volume's name. + vName := fixKubeVolumeName(v.Name) + name := vName + "-pvc" + + vm := v1.VolumeMount{} + vm.Name = name + vm.MountPath = v.Dest + vm.ReadOnly = ro + vm.SubPath = v.SubPath + + pvc := v1.PersistentVolumeClaimVolumeSource{ClaimName: vName, ReadOnly: ro} + vs := v1.VolumeSource{} + vs.PersistentVolumeClaim = &pvc + vo := v1.Volume{Name: name, VolumeSource: vs} + + return vm, vo +} + +// generateKubeVolumeMount takes a user specified mount and returns +// a kubernetes VolumeMount (to be added to the container) and a kubernetes Volume +// (to be added to the pod) +func generateKubeVolumeMount(m specs.Mount) (v1.VolumeMount, v1.Volume, error) { + vm := v1.VolumeMount{} + vo := v1.Volume{} + + var ( + name string + err error + ) + if m.Type == define.TypeTmpfs { + name = "tmp" + vo.EmptyDir = &v1.EmptyDirVolumeSource{ + Medium: v1.StorageMediumMemory, + } + vo.Name = name + } else { + name, err = convertVolumePathToName(m.Source) + if err != nil { + return vm, vo, err + } + // To avoid naming conflicts with any persistent volume mounts, add a unique suffix to the volume's name. + name += "-host" + vo.Name = name + vo.HostPath = &v1.HostPathVolumeSource{} + vo.HostPath.Path = m.Source + isDir, err := isHostPathDirectory(m.Source) + // neither a directory or a file lives here, default to creating a directory + // TODO should this be an error instead? + var hostPathType v1.HostPathType + switch { + case err != nil: + hostPathType = v1.HostPathDirectoryOrCreate + case isDir: + hostPathType = v1.HostPathDirectory + default: + hostPathType = v1.HostPathFile + } + vo.HostPath.Type = &hostPathType + } + vm.Name = name + vm.MountPath = m.Destination + if slices.Contains(m.Options, "ro") { + vm.ReadOnly = true + } + + return vm, vo, nil +} + +func isHostPathDirectory(hostPathSource string) (bool, error) { + info, err := os.Stat(hostPathSource) + if err != nil { + return false, err + } + return info.Mode().IsDir(), nil +} + +func fixKubeVolumeName(source string) string { + // Trim trailing slashes, + // Replace slashes with dashes. + // Replace underscores with dashes. + // Force all letters to lower case + // Thus, /mnt/data/ will become mnt-data + return strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(strings.Trim(source, "/"), "/", "-"), "_", "-")) +} + +func convertVolumePathToName(hostSourcePath string) (string, error) { + if len(hostSourcePath) == 0 { + return "", errors.New("hostSourcePath must be specified to generate volume name") + } + if len(hostSourcePath) == 1 { + if hostSourcePath != "/" { + return "", fmt.Errorf("hostSourcePath malformatted: %s", hostSourcePath) + } + // add special case name + return "root", nil + } + return fixKubeVolumeName(hostSourcePath), nil +} + +func determineCapAddDropFromCapabilities(defaultCaps, containerCaps []string) *v1.Capabilities { + var ( + drop = []v1.Capability{} + add = []v1.Capability{} + ) + dedupDrop := make(map[string]bool) + dedupAdd := make(map[string]bool) + // Find caps in the defaultCaps but not in the container's + // those indicate a dropped cap + for _, capability := range defaultCaps { + if !slices.Contains(containerCaps, capability) { + if _, ok := dedupDrop[capability]; !ok { + drop = append(drop, v1.Capability(capability)) + dedupDrop[capability] = true + } + } + } + // Find caps in the container but not in the defaults; those indicate + // an added cap + for _, capability := range containerCaps { + if !slices.Contains(defaultCaps, capability) { + if _, ok := dedupAdd[capability]; !ok { + add = append(add, v1.Capability(capability)) + dedupAdd[capability] = true + } + } + } + + if len(add) > 0 || len(drop) > 0 { + return &v1.Capabilities{ + Add: add, + Drop: drop, + } + } + return nil +} + +func (c *Container) capAddDrop(caps *specs.LinuxCapabilities) *v1.Capabilities { + // FreeBSD containers don't have caps so don't dereference if it's nil + if caps == nil { + return nil + } + + // Combine all the container's capabilities into a slice + containerCaps := make([]string, 0, len(caps.Ambient)+len(caps.Bounding)+len(caps.Effective)+len(caps.Inheritable)+len(caps.Permitted)) + containerCaps = append(containerCaps, caps.Ambient...) + containerCaps = append(containerCaps, caps.Bounding...) + containerCaps = append(containerCaps, caps.Effective...) + containerCaps = append(containerCaps, caps.Inheritable...) + containerCaps = append(containerCaps, caps.Permitted...) + + calculatedCaps := determineCapAddDropFromCapabilities(c.runtime.config.Containers.DefaultCapabilities.Get(), containerCaps) + return calculatedCaps +} + +// generateKubeSecurityContext generates a securityContext based on the existing container +func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, bool, error) { + privileged := c.Privileged() + ro := c.IsReadOnly() + allowPrivEscalation := !c.config.Spec.Process.NoNewPrivileges + + var capabilities *v1.Capabilities + if !privileged { + // Running privileged adds all caps. + capabilities = c.capAddDrop(c.config.Spec.Process.Capabilities) + } + + scHasData := false + sc := v1.SecurityContext{ + // RunAsNonRoot is an optional parameter; our first implementations should be root only; however + // I'm leaving this as a bread-crumb for later + //RunAsNonRoot: &nonRoot, + } + if capabilities != nil { + scHasData = true + sc.Capabilities = capabilities + } + var selinuxOpts v1.SELinuxOptions + selinuxHasData := false + for label := range strings.SplitSeq(c.config.Spec.Annotations[define.InspectAnnotationLabel], ",label=") { + opt, val, hasVal := strings.Cut(label, ":") + if hasVal { + switch opt { + case "filetype": + selinuxOpts.FileType = val + selinuxHasData = true + case "type": + selinuxOpts.Type = val + selinuxHasData = true + case "level": + selinuxOpts.Level = val + selinuxHasData = true + } + } else if opt == "disable" { + selinuxOpts.Type = "spc_t" + selinuxHasData = true + } + } + if selinuxHasData { + sc.SELinuxOptions = &selinuxOpts + scHasData = true + } + if !allowPrivEscalation { + scHasData = true + sc.AllowPrivilegeEscalation = &allowPrivEscalation + } + if privileged { + scHasData = true + sc.Privileged = &privileged + } + if ro { + scHasData = true + sc.ReadOnlyRootFilesystem = &ro + } + if c.config.Spec.Linux != nil && c.config.Spec.Linux.MaskedPaths == nil { + scHasData = true + unmask := v1.UnmaskedProcMount + sc.ProcMount = &unmask + } + + if c.User() != "" { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + } + if err := c.syncContainer(); err != nil { + return nil, false, fmt.Errorf("unable to sync container during YAML generation: %w", err) + } + + mountpoint := c.state.Mountpoint + if mountpoint == "" { + var err error + mountpoint, err = c.mount() + if err != nil { + return nil, false, fmt.Errorf("failed to mount %s mountpoint: %w", c.ID(), err) + } + defer func() { + if err := c.unmount(false); err != nil { + logrus.Errorf("Failed to unmount container: %v", err) + } + }() + } + logrus.Debugf("Looking in container for user: %s", c.User()) + + execUser, err := lookup.GetUserGroupInfo(mountpoint, c.User(), nil) + if err != nil { + return nil, false, err + } + uid := int64(execUser.Uid) + gid := int64(execUser.Gid) + scHasData = true + sc.RunAsUser = &uid + sc.RunAsGroup = &gid + } + + return &sc, scHasData, nil +} + +// generateKubeVolumeDeviceFromLinuxDevice takes a list of devices and makes a VolumeDevice struct for kube +func generateKubeVolumeDeviceFromLinuxDevice(devices []specs.LinuxDevice) []v1.VolumeDevice { + volumeDevices := make([]v1.VolumeDevice, 0, len(devices)) + for _, d := range devices { + vd := v1.VolumeDevice{ + // TBD How are we going to sync up these names + //Name: + DevicePath: d.Path, + } + volumeDevices = append(volumeDevices, vd) + } + return volumeDevices +} + +func removeUnderscores(s string) string { + return strings.ReplaceAll(s, "_", "") +} + +// getAutoUpdateAnnotations searches for auto-update container labels +// and returns them as kube annotations +func getAutoUpdateAnnotations(ctrName string, ctrLabels map[string]string) map[string]string { + autoUpdateLabel := "io.containers.autoupdate" + annotations := make(map[string]string) + + ctrName = removeUnderscores(ctrName) + for k, v := range ctrLabels { + if strings.Contains(k, autoUpdateLabel) { + // since labels can variate between containers within a pod, they will be + // identified with the container name when converted into kube annotations + kc := fmt.Sprintf("%s/%s", k, ctrName) + annotations[kc] = v + } + } + + return annotations +} diff --git a/vendor/github.com/containers/podman/v5/libpod/layers/layer.go b/vendor/github.com/containers/podman/v5/libpod/layers/layer.go new file mode 100644 index 00000000..d1e5bd5a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/layers/layer.go @@ -0,0 +1,12 @@ +package layers + +import cstorage "go.podman.io/storage" + +// FullID gets the full id of a layer given a partial id or name +func FullID(store cstorage.Store, id string) (string, error) { + layer, err := store.Layer(id) + if err != nil { + return "", err + } + return layer.ID, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/linkmode/linkmode_dynamic.go b/vendor/github.com/containers/podman/v5/libpod/linkmode/linkmode_dynamic.go new file mode 100644 index 00000000..81b222e5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/linkmode/linkmode_dynamic.go @@ -0,0 +1,8 @@ +//go:build !static + +package linkmode + +// Linkmode returns the linking mode (static/dynamic) for the build. +func Linkmode() string { + return "dynamic" +} diff --git a/vendor/github.com/containers/podman/v5/libpod/linkmode/linkmode_static.go b/vendor/github.com/containers/podman/v5/libpod/linkmode/linkmode_static.go new file mode 100644 index 00000000..bc2a7220 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/linkmode/linkmode_static.go @@ -0,0 +1,8 @@ +//go:build static + +package linkmode + +// Linkmode returns the linking mode (static/dynamic) for the build. +func Linkmode() string { + return "static" +} diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/file/file_lock.go b/vendor/github.com/containers/podman/v5/libpod/lock/file/file_lock.go new file mode 100644 index 00000000..6c148f05 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/file/file_lock.go @@ -0,0 +1,179 @@ +package file + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strconv" + "syscall" + + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/lockfile" +) + +// FileLocks is a struct enabling POSIX lock locking in a shared memory +// segment. +type FileLocks struct { + lockPath string + valid bool +} + +// CreateFileLock sets up a directory containing the various lock files. +func CreateFileLock(path string) (*FileLocks, error) { + err := fileutils.Exists(path) + if err == nil { + return nil, fmt.Errorf("directory %s exists: %w", path, syscall.EEXIST) + } + if err := os.MkdirAll(path, 0o711); err != nil { + return nil, err + } + + locks := new(FileLocks) + locks.lockPath = path + locks.valid = true + + return locks, nil +} + +// OpenFileLock opens an existing directory with the lock files. +func OpenFileLock(path string) (*FileLocks, error) { + err := fileutils.Exists(path) + if err != nil { + return nil, err + } + + locks := new(FileLocks) + locks.lockPath = path + locks.valid = true + + return locks, nil +} + +// Close closes an existing shared-memory segment. +// The segment will be rendered unusable after closing. +// WARNING: If you Close() while there are still locks locked, these locks may +// fail to release, causing a program freeze. +// Close() is only intended to be used while testing the locks. +func (locks *FileLocks) Close() error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + err := os.RemoveAll(locks.lockPath) + if err != nil { + return fmt.Errorf("deleting directory %s: %w", locks.lockPath, err) + } + return nil +} + +func (locks *FileLocks) getLockPath(lck uint32) string { + return filepath.Join(locks.lockPath, strconv.FormatInt(int64(lck), 10)) +} + +// AllocateLock allocates a lock and returns the index of the lock that was allocated. +func (locks *FileLocks) AllocateLock() (uint32, error) { + if !locks.valid { + return 0, fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + id := uint32(0) + for ; ; id++ { + path := locks.getLockPath(id) + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666) + if err != nil { + if os.IsExist(err) { + continue + } + return 0, fmt.Errorf("creating lock file: %w", err) + } + f.Close() + break + } + return id, nil +} + +// AllocateGivenLock allocates the given lock from the shared-memory +// segment for use by a container or pod. +// If the lock is already in use or the index is invalid an error will be +// returned. +func (locks *FileLocks) AllocateGivenLock(lck uint32) error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + f, err := os.OpenFile(locks.getLockPath(lck), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666) + if err != nil { + return fmt.Errorf("creating lock %d: %w", lck, err) + } + f.Close() + + return nil +} + +// DeallocateLock frees a lock in a shared-memory segment so it can be +// reallocated to another container or pod. +// The given lock must be already allocated, or an error will be returned. +func (locks *FileLocks) DeallocateLock(lck uint32) error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + if err := os.Remove(locks.getLockPath(lck)); err != nil { + return fmt.Errorf("deallocating lock %d: %w", lck, err) + } + return nil +} + +// DeallocateAllLocks frees all locks so they can be reallocated to +// other containers and pods. +func (locks *FileLocks) DeallocateAllLocks() error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + files, err := os.ReadDir(locks.lockPath) + if err != nil { + return fmt.Errorf("reading directory %s: %w", locks.lockPath, err) + } + var lastErr error + for _, f := range files { + p := filepath.Join(locks.lockPath, f.Name()) + err := os.Remove(p) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + continue + } + logrus.Errorf("Deallocating lock %s", p) + } + } + return lastErr +} + +// LockFileLock locks the given lock. +func (locks *FileLocks) LockFileLock(lck uint32) error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + l, err := lockfile.GetLockFile(locks.getLockPath(lck)) + if err != nil { + return fmt.Errorf("acquiring lock: %w", err) + } + + l.Lock() + return nil +} + +// UnlockFileLock unlocks the given lock. +func (locks *FileLocks) UnlockFileLock(lck uint32) error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + l, err := lockfile.GetLockFile(locks.getLockPath(lck)) + if err != nil { + return fmt.Errorf("acquiring lock: %w", err) + } + + l.Unlock() + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/file_lock_manager.go b/vendor/github.com/containers/podman/v5/libpod/lock/file_lock_manager.go new file mode 100644 index 00000000..0f6757a6 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/file_lock_manager.go @@ -0,0 +1,126 @@ +package lock + +import ( + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/lock/file" +) + +// FileLockManager manages shared memory locks. +type FileLockManager struct { + locks *file.FileLocks +} + +// NewFileLockManager makes a new FileLockManager at the specified directory. +func NewFileLockManager(lockPath string) (Manager, error) { + locks, err := file.CreateFileLock(lockPath) + if err != nil { + return nil, err + } + + manager := new(FileLockManager) + manager.locks = locks + + return manager, nil +} + +// OpenFileLockManager opens an existing FileLockManager at the specified directory. +func OpenFileLockManager(path string) (Manager, error) { + locks, err := file.OpenFileLock(path) + if err != nil { + return nil, err + } + + manager := new(FileLockManager) + manager.locks = locks + + return manager, nil +} + +// AllocateLock allocates a new lock from the manager. +func (m *FileLockManager) AllocateLock() (Locker, error) { + semIndex, err := m.locks.AllocateLock() + if err != nil { + return nil, err + } + + lock := new(FileLock) + lock.lockID = semIndex + lock.manager = m + + return lock, nil +} + +// AllocateAndRetrieveLock allocates the lock with the given ID and returns it. +// If the lock is already allocated, error. +func (m *FileLockManager) AllocateAndRetrieveLock(id uint32) (Locker, error) { + lock := new(FileLock) + lock.lockID = id + lock.manager = m + + if err := m.locks.AllocateGivenLock(id); err != nil { + return nil, err + } + + return lock, nil +} + +// RetrieveLock retrieves a lock from the manager given its ID. +func (m *FileLockManager) RetrieveLock(id uint32) (Locker, error) { + lock := new(FileLock) + lock.lockID = id + lock.manager = m + + return lock, nil +} + +// FreeAllLocks frees all locks in the manager. +// This function is DANGEROUS. Please read the full comment in locks.go before +// trying to use it. +func (m *FileLockManager) FreeAllLocks() error { + return m.locks.DeallocateAllLocks() +} + +// AvailableLocks returns the number of available locks. Since this is not +// limited in the file lock implementation, nil is returned. +func (m *FileLockManager) AvailableLocks() (*uint32, error) { + return nil, nil +} + +// LocksHeld returns any locks that are presently locked. +// It is not implemented for the file lock backend. +// It ought to be possible, but my motivation to dig into c/storage and add +// trylock semantics to the filelocker implementation for an uncommonly-used +// lock backend is lacking. +func (m *FileLockManager) LocksHeld() ([]uint32, error) { + return nil, define.ErrNotImplemented +} + +// FileLock is an individual shared memory lock. +type FileLock struct { + lockID uint32 + manager *FileLockManager +} + +// ID returns the ID of the lock. +func (l *FileLock) ID() uint32 { + return l.lockID +} + +// Lock acquires the lock. +func (l *FileLock) Lock() { + if err := l.manager.locks.LockFileLock(l.lockID); err != nil { + panic(err.Error()) + } +} + +// Unlock releases the lock. +func (l *FileLock) Unlock() { + if err := l.manager.locks.UnlockFileLock(l.lockID); err != nil { + panic(err.Error()) + } +} + +// Free releases the lock, allowing it to be reused. +func (l *FileLock) Free() error { + return l.manager.locks.DeallocateLock(l.lockID) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/in_memory_locks.go b/vendor/github.com/containers/podman/v5/libpod/lock/in_memory_locks.go new file mode 100644 index 00000000..9787d450 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/in_memory_locks.go @@ -0,0 +1,148 @@ +package lock + +import ( + "errors" + "fmt" + "sync" +) + +// Mutex holds a single mutex and whether it has been allocated. +type Mutex struct { + id uint32 + lock sync.Mutex + allocated bool +} + +// ID retrieves the ID of the mutex +func (m *Mutex) ID() uint32 { + return m.id +} + +// Lock locks the mutex +func (m *Mutex) Lock() { + m.lock.Lock() +} + +// Unlock unlocks the mutex +func (m *Mutex) Unlock() { + m.lock.Unlock() +} + +// Free deallocates the mutex to allow its reuse +func (m *Mutex) Free() error { + m.allocated = false + + return nil +} + +// InMemoryManager is a lock manager that allocates and retrieves local-only +// locks - that is, they are not multiprocess. This lock manager is intended +// purely for unit and integration testing and should not be used in production +// deployments. +type InMemoryManager struct { + locks []*Mutex + numLocks uint32 + localLock sync.Mutex +} + +// NewInMemoryManager creates a new in-memory lock manager with the given number +// of locks. +func NewInMemoryManager(numLocks uint32) (Manager, error) { + if numLocks == 0 { + return nil, errors.New("must provide a non-zero number of locks") + } + + manager := new(InMemoryManager) + manager.numLocks = numLocks + manager.locks = make([]*Mutex, numLocks) + + var i uint32 + for i = range numLocks { + lock := new(Mutex) + lock.id = i + manager.locks[i] = lock + } + + return manager, nil +} + +// AllocateLock allocates a lock from the manager. +func (m *InMemoryManager) AllocateLock() (Locker, error) { + m.localLock.Lock() + defer m.localLock.Unlock() + + for _, lock := range m.locks { + if !lock.allocated { + lock.allocated = true + return lock, nil + } + } + + return nil, errors.New("all locks have been allocated") +} + +// RetrieveLock retrieves a lock from the manager. +func (m *InMemoryManager) RetrieveLock(id uint32) (Locker, error) { + if id >= m.numLocks { + return nil, fmt.Errorf("given lock ID %d is too large - this manager only supports lock indexes up to %d", id, m.numLocks-1) + } + + return m.locks[id], nil +} + +// AllocateAndRetrieveLock allocates a lock with the given ID (if not already in +// use) and returns it. +func (m *InMemoryManager) AllocateAndRetrieveLock(id uint32) (Locker, error) { + if id >= m.numLocks { + return nil, fmt.Errorf("given lock ID %d is too large - this manager only supports lock indexes up to %d", id, m.numLocks) + } + + if m.locks[id].allocated { + return nil, fmt.Errorf("given lock ID %d is already in use, cannot reallocate", id) + } + + m.locks[id].allocated = true + + return m.locks[id], nil +} + +// FreeAllLocks frees all locks. +// This function is DANGEROUS. Please read the full comment in locks.go before +// trying to use it. +func (m *InMemoryManager) FreeAllLocks() error { + for _, lock := range m.locks { + lock.allocated = false + } + + return nil +} + +// Get number of available locks +func (m *InMemoryManager) AvailableLocks() (*uint32, error) { + var count uint32 + + for _, lock := range m.locks { + if !lock.allocated { + count++ + } + } + + return &count, nil +} + +// Get any locks that are presently being held. +// Useful for debugging deadlocks. +func (m *InMemoryManager) LocksHeld() ([]uint32, error) { + //nolint:prealloc + var locks []uint32 + + for _, lock := range m.locks { + if lock.lock.TryLock() { + lock.lock.Unlock() + continue + } + locks = append(locks, lock.ID()) + } + + return locks, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/lock.go b/vendor/github.com/containers/podman/v5/libpod/lock/lock.go new file mode 100644 index 00000000..22f3d203 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/lock.go @@ -0,0 +1,92 @@ +package lock + +// Manager provides an interface for allocating multiprocess locks. +// Locks returned by Manager MUST be multiprocess - allocating a lock in +// process A and retrieving that lock's ID in process B must return handles for +// the same lock, and locking the lock in A should exclude B from the lock until +// it is unlocked in A. +// All locks must be identified by a UUID (retrieved with Locker's ID() method). +// All locks with a given UUID must refer to the same underlying lock, and it +// must be possible to retrieve the lock given its UUID. +// Each UUID should refer to a unique underlying lock. +// Calls to AllocateLock() must return a unique, unallocated UUID. +// AllocateLock() must fail once all available locks have been allocated. +// Locks are returned to use by calls to Free(), and can subsequently be +// reallocated. +type Manager interface { + // AllocateLock returns an unallocated lock. + // It is guaranteed that the same lock will not be returned again by + // AllocateLock until the returned lock has Free() called on it. + // If all available locks are allocated, AllocateLock will return an + // error. + AllocateLock() (Locker, error) + // RetrieveLock retrieves a lock given its UUID. + // The underlying lock MUST be the same as another other lock with the + // same UUID. + RetrieveLock(id uint32) (Locker, error) + // AllocateAndRetrieveLock marks the lock with the given UUID as in use + // and retrieves it. + // RetrieveAndAllocateLock will error if the lock in question has + // already been allocated. + // This is mostly used after a system restart to repopulate the list of + // locks in use. + AllocateAndRetrieveLock(id uint32) (Locker, error) + // PLEASE READ FULL DESCRIPTION BEFORE USING. + // FreeAllLocks frees all allocated locks, in preparation for lock + // reallocation. + // As this deallocates all presently-held locks, this can be very + // dangerous - if there are other processes running that might be + // attempting to allocate new locks and free existing locks, we may + // encounter races leading to an inconsistent state. + // (This is in addition to the fact that FreeAllLocks instantly makes + // the state inconsistent simply by using it, and requires a full + // lock renumbering to restore consistency!). + // In short, this should only be used as part of unit tests, or lock + // renumbering, where reasonable guarantees about other processes can be + // made. + FreeAllLocks() error + // NumAvailableLocks gets the number of remaining locks available to be + // allocated. + // Some lock managers do not have a maximum number of locks, and can + // allocate an unlimited number. These implementations should return + // a nil uin32. + AvailableLocks() (*uint32, error) + // Get a list of locks that are currently locked. + // This may not be supported by some drivers, depending on the exact + // backend implementation in use. + LocksHeld() ([]uint32, error) +} + +// Locker is similar to sync.Locker, but provides a method for freeing the lock +// to allow its reuse. +// All Locker implementations must maintain mutex semantics - the lock only +// allows one caller in the critical section at a time. +// All locks with the same ID must refer to the same underlying lock, even +// if they are within multiple processes. +type Locker interface { + // ID retrieves the lock's ID. + // ID is guaranteed to uniquely identify the lock within the + // Manager - that is, calling RetrieveLock with this ID will return + // another instance of the same lock. + ID() uint32 + // Lock locks the lock. + // This call MUST block until it successfully acquires the lock or + // encounters a fatal error. + // All errors must be handled internally, as they are not returned. For + // the most part, panicking should be appropriate. + // Some lock implementations may require that Lock() and Unlock() occur + // within the same goroutine (SHM locking, for example). The usual Go + // Lock()/defer Unlock() pattern will still work fine in these cases. + Lock() + // Unlock unlocks the lock. + // All errors must be handled internally, as they are not returned. For + // the most part, panicking should be appropriate. + // This includes unlocking locks which are already unlocked. + Unlock() + // Free deallocates the underlying lock, allowing its reuse by other + // pods and containers. + // The lock MUST still be usable after a Free() - some libpod instances + // may still retain Container structs with the old lock. This simply + // advises the manager that the lock may be reallocated. + Free() error +} diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock.c b/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock.c new file mode 100644 index 00000000..397fb899 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock.c @@ -0,0 +1,648 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shm_lock.h" + +// Compute the size of the SHM struct +static size_t compute_shm_size(uint32_t num_bitmaps) { + return sizeof(shm_struct_t) + (num_bitmaps * sizeof(lock_group_t)); +} + +// Take the given mutex. +// Handles exceptional conditions, including a mutex locked by a process that +// died holding it. +// Returns 0 on success, or positive errno on failure. +static int take_mutex(pthread_mutex_t *mutex, bool trylock) { + int ret_code; + + if (!trylock) { + do { + ret_code = pthread_mutex_lock(mutex); + } while(ret_code == EAGAIN); + } else { + do { + ret_code = pthread_mutex_trylock(mutex); + } while(ret_code == EAGAIN); + } + + if (ret_code == EOWNERDEAD) { + // The previous owner of the mutex died while holding it + // Take it for ourselves + ret_code = pthread_mutex_consistent(mutex); + if (ret_code != 0) { + // Someone else may have gotten here first and marked the state consistent + // However, the mutex could also be invalid. + // Fail here instead of looping back to trying to lock the mutex. + return ret_code; + } + } else if (ret_code != 0) { + return ret_code; + } + + return 0; +} + +// Release the given mutex. +// Returns 0 on success, or positive errno on failure. +static int release_mutex(pthread_mutex_t *mutex) { + int ret_code; + + do { + ret_code = pthread_mutex_unlock(mutex); + } while(ret_code == EAGAIN); + + if (ret_code != 0) { + return ret_code; + } + + return 0; +} + +// Set up an SHM segment holding locks for libpod. +// num_locks must not be 0. +// Path is the path to the SHM segment. It must begin with a single / and +// container no other / characters, and be at most 255 characters including +// terminating NULL byte. +// Returns a valid pointer on success or NULL on error. +// If an error occurs, negative ERRNO values will be written to error_code. +shm_struct_t *setup_lock_shm(char *path, uint32_t num_locks, int *error_code) { + int shm_fd, i, j, ret_code; + uint32_t num_bitmaps; + size_t shm_size; + shm_struct_t *shm; + pthread_mutexattr_t attr; + + // If error_code doesn't point to anything, we can't reasonably return errors + // So fail immediately + if (error_code == NULL) { + return NULL; + } + + // We need a nonzero number of locks + if (num_locks == 0) { + *error_code = -1 * EINVAL; + return NULL; + } + + if (path == NULL) { + *error_code = -1 * EINVAL; + return NULL; + } + + // Calculate the number of bitmaps required + num_bitmaps = num_locks / BITMAP_SIZE; + if (num_locks % BITMAP_SIZE != 0) { + // The actual number given is not an even multiple of our bitmap size + // So round up + num_bitmaps += 1; + } + + // Calculate size of the shm segment + shm_size = compute_shm_size(num_bitmaps); + + // Create a new SHM segment for us + shm_fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600); + if (shm_fd < 0) { + *error_code = -1 * errno; + return NULL; + } + + // Increase its size to what we need + ret_code = ftruncate(shm_fd, shm_size); + if (ret_code < 0) { + *error_code = -1 * errno; + goto CLEANUP_UNLINK; + } + + // Map the shared memory in + shm = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + if (shm == MAP_FAILED) { + *error_code = -1 * errno; + goto CLEANUP_UNLINK; + } + + // We have successfully mapped the memory, now initialize the region + shm->magic = MAGIC; + shm->unused = 0; + shm->num_locks = num_bitmaps * BITMAP_SIZE; + shm->num_bitmaps = num_bitmaps; + + // Create an initializer for our pthread mutexes + ret_code = pthread_mutexattr_init(&attr); + if (ret_code != 0) { + *error_code = -1 * ret_code; + goto CLEANUP_UNMAP; + } + + // Ensure that recursive locking of a mutex by the same OS thread (which may + // refer to numerous goroutines) blocks. + ret_code = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + if (ret_code != 0) { + *error_code = -1 * ret_code; + goto CLEANUP_FREEATTR; + } + + // Set mutexes to pshared - multiprocess-safe + ret_code = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + if (ret_code != 0) { + *error_code = -1 * ret_code; + goto CLEANUP_FREEATTR; + } + + // Set mutexes to robust - if a process dies while holding a mutex, we'll get + // a special error code on the next attempt to lock it. + // This should prevent panicking processes from leaving the state unusable. + ret_code = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); + if (ret_code != 0) { + *error_code = -1 * ret_code; + goto CLEANUP_FREEATTR; + } + + // Initialize the mutex that protects the bitmaps using the mutex attributes + ret_code = pthread_mutex_init(&(shm->segment_lock), &attr); + if (ret_code != 0) { + *error_code = -1 * ret_code; + goto CLEANUP_FREEATTR; + } + + // Initialize all bitmaps to 0 initially + // And initialize all semaphores they use + for (i = 0; i < num_bitmaps; i++) { + shm->locks[i].bitmap = 0; + for (j = 0; j < BITMAP_SIZE; j++) { + // Initialize each mutex + ret_code = pthread_mutex_init(&(shm->locks[i].locks[j]), &attr); + if (ret_code != 0) { + *error_code = -1 * ret_code; + goto CLEANUP_FREEATTR; + } + } + } + + // Close the file descriptor, we're done with it + // Ignore errors, it's ok if we leak a single FD and this should only run once + close(shm_fd); + + // Destroy the pthread initializer attribute. + // Again, ignore errors, this will only run once and we might leak a tiny bit + // of memory at worst. + pthread_mutexattr_destroy(&attr); + + return shm; + + // Cleanup after an error + CLEANUP_FREEATTR: + pthread_mutexattr_destroy(&attr); + CLEANUP_UNMAP: + munmap(shm, shm_size); + CLEANUP_UNLINK: + close(shm_fd); + shm_unlink(path); + return NULL; +} + +// Open an existing SHM segment holding libpod locks. +// num_locks is the number of locks that will be configured in the SHM segment. +// num_locks cannot be 0. +// Path is the path to the SHM segment. It must begin with a single / and +// container no other / characters, and be at most 255 characters including +// terminating NULL byte. +// Returns a valid pointer on success or NULL on error. +// If an error occurs, negative ERRNO values will be written to error_code. +// ERANGE is returned for a mismatch between num_locks and the number of locks +// available in the SHM lock struct. +shm_struct_t *open_lock_shm(char *path, uint32_t num_locks, int *error_code) { + int shm_fd; + shm_struct_t *shm; + size_t shm_size; + uint32_t num_bitmaps; + + if (error_code == NULL) { + return NULL; + } + + // We need a nonzero number of locks + if (num_locks == 0) { + *error_code = -1 * EINVAL; + return NULL; + } + + if (path == NULL) { + *error_code = -1 * EINVAL; + return NULL; + } + + // Calculate the number of bitmaps required + num_bitmaps = num_locks / BITMAP_SIZE; + if (num_locks % BITMAP_SIZE != 0) { + num_bitmaps += 1; + } + + // Calculate size of the shm segment + shm_size = compute_shm_size(num_bitmaps); + + shm_fd = shm_open(path, O_RDWR, 0600); + if (shm_fd < 0) { + *error_code = -1 * errno; + return NULL; + } + + // Map the shared memory in + shm = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + if (shm == MAP_FAILED) { + *error_code = -1 * errno; + } + + // Ignore errors, it's ok if we leak a single FD since this only runs once + close(shm_fd); + + // Check if we successfully mmap'd + if (shm == MAP_FAILED) { + return NULL; + } + + // Need to check the SHM to see if it's actually our locks + if (shm->magic != MAGIC) { + *error_code = -1 * EBADF; + goto CLEANUP; + } + if (shm->num_locks != (num_bitmaps * BITMAP_SIZE)) { + *error_code = -1 * ERANGE; + goto CLEANUP; + } + + return shm; + + CLEANUP: + munmap(shm, shm_size); + return NULL; +} + +// Close an open SHM lock struct, unmapping the backing memory. +// The given shm_struct_t will be rendered unusable as a result. +// On success, 0 is returned. On failure, negative ERRNO values are returned. +int32_t close_lock_shm(shm_struct_t *shm) { + int ret_code; + size_t shm_size; + + // We can't unmap null... + if (shm == NULL) { + return -1 * EINVAL; + } + + shm_size = compute_shm_size(shm->num_bitmaps); + + ret_code = munmap(shm, shm_size); + + if (ret_code != 0) { + return -1 * errno; + } + + return 0; +} + +// Allocate the first available semaphore +// Returns a positive integer guaranteed to be less than UINT32_MAX on success, +// or negative errno values on failure +// On success, the returned integer is the number of the semaphore allocated +int64_t allocate_semaphore(shm_struct_t *shm) { + int ret_code, i; + bitmap_t test_map; + int64_t sem_number, num_within_bitmap; + + if (shm == NULL) { + return -1 * EINVAL; + } + + // Lock the semaphore controlling access to our shared memory + ret_code = take_mutex(&(shm->segment_lock), false); + if (ret_code != 0) { + return -1 * ret_code; + } + + // Loop through our bitmaps to search for one that is not full + for (i = 0; i < shm->num_bitmaps; i++) { + if (shm->locks[i].bitmap != 0xFFFFFFFF) { + test_map = 0x1; + num_within_bitmap = 0; + while (test_map != 0) { + if ((test_map & shm->locks[i].bitmap) == 0) { + // Compute the number of the semaphore we are allocating + sem_number = (BITMAP_SIZE * i) + num_within_bitmap; + // OR in the bitmap + shm->locks[i].bitmap = shm->locks[i].bitmap | test_map; + + // Clear the mutex + ret_code = release_mutex(&(shm->segment_lock)); + if (ret_code != 0) { + return -1 * ret_code; + } + + // Return the semaphore we've allocated + return sem_number; + } + test_map = test_map << 1; + num_within_bitmap++; + } + // We should never fall through this loop + // TODO maybe an assert() here to panic if we do? + } + } + + // Clear the mutex + ret_code = release_mutex(&(shm->segment_lock)); + if (ret_code != 0) { + return -1 * ret_code; + } + + // All bitmaps are full + // We have no available semaphores, report allocation failure + return -1 * ENOSPC; +} + +// Allocate the semaphore with the given ID. +// Returns an error if the semaphore with this ID does not exist, or has already +// been allocated. +// Returns 0 on success, or negative errno values on failure. +int32_t allocate_given_semaphore(shm_struct_t *shm, uint32_t sem_index) { + int bitmap_index, index_in_bitmap, ret_code; + bitmap_t test_map; + + if (shm == NULL) { + return -1 * EINVAL; + } + + // Check if the lock index is valid + if (sem_index >= shm->num_locks) { + return -1 * EINVAL; + } + + bitmap_index = sem_index / BITMAP_SIZE; + index_in_bitmap = sem_index % BITMAP_SIZE; + + // This should never happen if the sem_index test above succeeded, but better + // safe than sorry + if (bitmap_index >= shm->num_bitmaps) { + return -1 * EFAULT; + } + + test_map = 0x1 << index_in_bitmap; + + // Lock the mutex controlling access to our shared memory + ret_code = take_mutex(&(shm->segment_lock), false); + if (ret_code != 0) { + return -1 * ret_code; + } + + // Check if the semaphore is allocated + if ((test_map & shm->locks[bitmap_index].bitmap) != 0) { + ret_code = release_mutex(&(shm->segment_lock)); + if (ret_code != 0) { + return -1 * ret_code; + } + + return -1 * EEXIST; + } + + // The semaphore is not allocated, allocate it + shm->locks[bitmap_index].bitmap = shm->locks[bitmap_index].bitmap | test_map; + + ret_code = release_mutex(&(shm->segment_lock)); + if (ret_code != 0) { + return -1 * ret_code; + } + + return 0; +} + +// Deallocate a given semaphore +// Returns 0 on success, negative ERRNO values on failure +int32_t deallocate_semaphore(shm_struct_t *shm, uint32_t sem_index) { + bitmap_t test_map; + int bitmap_index, index_in_bitmap, ret_code; + + if (shm == NULL) { + return -1 * EINVAL; + } + + // Check if the lock index is valid + if (sem_index >= shm->num_locks) { + return -1 * EINVAL; + } + + bitmap_index = sem_index / BITMAP_SIZE; + index_in_bitmap = sem_index % BITMAP_SIZE; + + // This should never happen if the sem_index test above succeeded, but better + // safe than sorry + if (bitmap_index >= shm->num_bitmaps) { + return -1 * EFAULT; + } + + test_map = 0x1 << index_in_bitmap; + + // Lock the mutex controlling access to our shared memory + ret_code = take_mutex(&(shm->segment_lock), false); + if (ret_code != 0) { + return -1 * ret_code; + } + + // Check if the semaphore is allocated + if ((test_map & shm->locks[bitmap_index].bitmap) == 0) { + ret_code = release_mutex(&(shm->segment_lock)); + if (ret_code != 0) { + return -1 * ret_code; + } + + return -1 * ENOENT; + } + + // The semaphore is allocated, clear it + // Invert the bitmask we used to test to clear the bit + test_map = ~test_map; + shm->locks[bitmap_index].bitmap = shm->locks[bitmap_index].bitmap & test_map; + + ret_code = release_mutex(&(shm->segment_lock)); + if (ret_code != 0) { + return -1 * ret_code; + } + + return 0; +} + +// Deallocate all semaphores unconditionally. +// Returns negative ERRNO values. +int32_t deallocate_all_semaphores(shm_struct_t *shm) { + int ret_code; + uint i; + + if (shm == NULL) { + return -1 * EINVAL; + } + + // Lock the mutex controlling access to our shared memory + ret_code = take_mutex(&(shm->segment_lock), false); + if (ret_code != 0) { + return -1 * ret_code; + } + + // Iterate through all bitmaps and reset to unused + for (i = 0; i < shm->num_bitmaps; i++) { + shm->locks[i].bitmap = 0; + } + + // Unlock the allocation control mutex + ret_code = release_mutex(&(shm->segment_lock)); + if (ret_code != 0) { + return -1 * ret_code; + } + + return 0; +} + +// Lock a given semaphore +// Does not check if the semaphore is allocated - this ensures that, even for +// removed containers, we can still successfully lock to check status (and +// subsequently realize they have been removed). +// Returns 0 on success, -1 on failure +int32_t lock_semaphore(shm_struct_t *shm, uint32_t sem_index) { + int bitmap_index, index_in_bitmap; + + if (shm == NULL) { + return -1 * EINVAL; + } + + if (sem_index >= shm->num_locks) { + return -1 * EINVAL; + } + + bitmap_index = sem_index / BITMAP_SIZE; + index_in_bitmap = sem_index % BITMAP_SIZE; + + return -1 * take_mutex(&(shm->locks[bitmap_index].locks[index_in_bitmap]), false); +} + +// Unlock a given semaphore +// Does not check if the semaphore is allocated - this ensures that, even for +// removed containers, we can still successfully lock to check status (and +// subsequently realize they have been removed). +// Returns 0 on success, -1 on failure +int32_t unlock_semaphore(shm_struct_t *shm, uint32_t sem_index) { + int bitmap_index, index_in_bitmap; + + if (shm == NULL) { + return -1 * EINVAL; + } + + if (sem_index >= shm->num_locks) { + return -1 * EINVAL; + } + + bitmap_index = sem_index / BITMAP_SIZE; + index_in_bitmap = sem_index % BITMAP_SIZE; + + return -1 * release_mutex(&(shm->locks[bitmap_index].locks[index_in_bitmap])); +} + +// Get the number of free locks. +// Returns a positive integer guaranteed to be less than UINT32_MAX on success, +// or negative errno values on failure. +// On success, the returned integer is the number of free semaphores. +int64_t available_locks(shm_struct_t *shm) { + int ret_code, i, count; + bitmap_t test_map; + int64_t free_locks = 0; + + if (shm == NULL) { + return -1 * EINVAL; + } + + // Lock the semaphore controlling access to the SHM segment. + // This isn't strictly necessary as we're only reading, but it seems safer. + ret_code = take_mutex(&(shm->segment_lock), false); + if (ret_code != 0) { + return -1 * ret_code; + } + + // Loop through all bitmaps, counting number of allocated locks. + for (i = 0; i < shm->num_bitmaps; i++) { + // Short-circuit to catch fully-empty bitmaps quick. + if (shm->locks[i].bitmap == 0) { + free_locks += sizeof(bitmap_t) * 8; + continue; + } + + // Use Kernighan's Algorithm to count bits set. Subtract from number of bits + // in the integer to get free bits, and thus free lock count. + test_map = shm->locks[i].bitmap; + count = 0; + while (test_map) { + test_map = test_map & (test_map - 1); + count++; + } + + free_locks += (sizeof(bitmap_t) * 8) - count; + } + + // Clear the mutex + ret_code = release_mutex(&(shm->segment_lock)); + if (ret_code != 0) { + return -1 * ret_code; + } + + // Return free lock count. + return free_locks; +} + +// Attempt to take a given semaphore. If successfully taken, it is immediately +// released before the function returns. +// Used to check if a semaphore is in use, to detect potential deadlocks where a +// lock has not been released for an extended period of time. +// Note that this is NOT POSIX trylock as the lock is immediately released if +// taken. +// Returns negative errno on failure. +// On success, returns 1 if the lock was successfully taken, and 0 if it was +// not. +int32_t try_lock(shm_struct_t *shm, uint32_t sem_index) { + int bitmap_index, index_in_bitmap, ret_code; + pthread_mutex_t *mutex; + + if (shm == NULL) { + return -1 * EINVAL; + } + + if (sem_index >= shm->num_locks) { + return -1 * EINVAL; + } + + bitmap_index = sem_index / BITMAP_SIZE; + index_in_bitmap = sem_index % BITMAP_SIZE; + + mutex = &(shm->locks[bitmap_index].locks[index_in_bitmap]); + + ret_code = take_mutex(mutex, true); + + if (ret_code == EBUSY) { + // Did not successfully take the lock + return 0; + } else if (ret_code != 0) { + // Another, unrelated error + return -1 * ret_code; + } + + // Lock taken successfully, unlock and return. + ret_code = release_mutex(mutex); + if (ret_code != 0) { + return -1 * ret_code; + } + + return 1; +} diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock.go b/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock.go new file mode 100644 index 00000000..7aff2788 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock.go @@ -0,0 +1,315 @@ +//go:build (linux || freebsd) && cgo + +package shm + +// #cgo LDFLAGS: -lrt -lpthread +// #cgo CFLAGS: -Wall -Werror +// #include +// #include +// #include +// #include +// #include "shm_lock.h" +// const uint32_t bitmap_size_c = BITMAP_SIZE; +import "C" + +import ( + "errors" + "fmt" + "runtime" + "syscall" + "unsafe" + + "github.com/sirupsen/logrus" +) + +var ( + // BitmapSize is the size of the bitmap used when managing SHM locks. + // an SHM lock manager's max locks will be rounded up to a multiple of + // this number. + BitmapSize = uint32(C.bitmap_size_c) +) + +// SHMLocks is a struct enabling POSIX semaphore locking in a shared memory +// segment. +type SHMLocks struct { + lockStruct *C.shm_struct_t + maxLocks uint32 + valid bool +} + +// CreateSHMLock sets up a shared-memory segment holding a given number of POSIX +// semaphores, and returns a struct that can be used to operate on those locks. +// numLocks must not be 0, and may be rounded up to a multiple of the bitmap +// size used by the underlying implementation. +func CreateSHMLock(path string, numLocks uint32) (*SHMLocks, error) { + if numLocks == 0 { + return nil, fmt.Errorf("number of locks must be greater than 0: %w", syscall.EINVAL) + } + + locks := new(SHMLocks) + + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + var errCode C.int + lockStruct := C.setup_lock_shm(cPath, C.uint32_t(numLocks), &errCode) + if lockStruct == nil { + // We got a null pointer, so something errored + return nil, fmt.Errorf("failed to create %d locks in %s: %w", numLocks, path, syscall.Errno(-1*errCode)) + } + + locks.lockStruct = lockStruct + locks.maxLocks = uint32(lockStruct.num_locks) + locks.valid = true + + logrus.Debugf("Initialized SHM lock manager at path %s", path) + + return locks, nil +} + +// OpenSHMLock opens an existing shared-memory segment holding a given number of +// POSIX semaphores. numLocks must match the number of locks the shared memory +// segment was created with. +func OpenSHMLock(path string, numLocks uint32) (*SHMLocks, error) { + if numLocks == 0 { + return nil, fmt.Errorf("number of locks must be greater than 0: %w", syscall.EINVAL) + } + + locks := new(SHMLocks) + + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + var errCode C.int + lockStruct := C.open_lock_shm(cPath, C.uint32_t(numLocks), &errCode) + if lockStruct == nil { + // We got a null pointer, so something errored + return nil, fmt.Errorf("failed to open %d locks in %s: %w", numLocks, path, syscall.Errno(-1*errCode)) + } + + locks.lockStruct = lockStruct + locks.maxLocks = numLocks + locks.valid = true + + return locks, nil +} + +// GetMaxLocks returns the maximum number of locks in the SHM +func (locks *SHMLocks) GetMaxLocks() uint32 { + return locks.maxLocks +} + +// Close closes an existing shared-memory segment. +// The segment will be rendered unusable after closing. +// WARNING: If you Close() while there are still locks locked, these locks may +// fail to release, causing a program freeze. +// Close() is only intended to be used while testing the locks. +func (locks *SHMLocks) Close() error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + locks.valid = false + + retCode := C.close_lock_shm(locks.lockStruct) + if retCode < 0 { + // Negative errno returned + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// AllocateSemaphore allocates a semaphore from a shared-memory segment for use +// by a container or pod. +// Returns the index of the semaphore that was allocated. +// Allocations past the maximum number of locks given when the SHM segment was +// created will result in an error, and no semaphore will be allocated. +func (locks *SHMLocks) AllocateSemaphore() (uint32, error) { + if !locks.valid { + return 0, fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + // This returns a U64, so we have the full u32 range available for + // semaphore indexes, and can still return error codes. + retCode := C.allocate_semaphore(locks.lockStruct) + if retCode < 0 { + var err = syscall.Errno(-1 * retCode) + // Negative errno returned + if errors.Is(err, syscall.ENOSPC) { + // ENOSPC expands to "no space left on device". While it is technically true + // that there's no room in the SHM inn for this lock, this tends to send normal people + // down the path of checking disk-space which is not actually their problem. + // Give a clue that it's actually due to num_locks filling up. + var errFull = fmt.Errorf("allocation failed; exceeded num_locks (%d)", locks.maxLocks) + return uint32(retCode), errFull + } + return uint32(retCode), syscall.Errno(-1 * retCode) + } + + return uint32(retCode), nil +} + +// AllocateGivenSemaphore allocates the given semaphore from the shared-memory +// segment for use by a container or pod. +// If the semaphore is already in use or the index is invalid an error will be +// returned. +func (locks *SHMLocks) AllocateGivenSemaphore(sem uint32) error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + retCode := C.allocate_given_semaphore(locks.lockStruct, C.uint32_t(sem)) + if retCode < 0 { + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// DeallocateSemaphore frees a semaphore in a shared-memory segment so it can be +// reallocated to another container or pod. +// The given semaphore must be already allocated, or an error will be returned. +func (locks *SHMLocks) DeallocateSemaphore(sem uint32) error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + if sem > locks.maxLocks { + return fmt.Errorf("given semaphore %d is higher than maximum locks count %d: %w", sem, locks.maxLocks, syscall.EINVAL) + } + + retCode := C.deallocate_semaphore(locks.lockStruct, C.uint32_t(sem)) + if retCode < 0 { + // Negative errno returned + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// DeallocateAllSemaphores frees all semaphores so they can be reallocated to +// other containers and pods. +func (locks *SHMLocks) DeallocateAllSemaphores() error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + retCode := C.deallocate_all_semaphores(locks.lockStruct) + if retCode < 0 { + // Negative errno return from C + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// LockSemaphore locks the given semaphore. +// If the semaphore is already locked, LockSemaphore will block until the lock +// can be acquired. +// There is no requirement that the given semaphore be allocated. +// This ensures that attempts to lock a container after it has been deleted, +// but before the caller has queried the database to determine this, will +// succeed. +func (locks *SHMLocks) LockSemaphore(sem uint32) error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + if sem > locks.maxLocks { + return fmt.Errorf("given semaphore %d is higher than maximum locks count %d: %w", sem, locks.maxLocks, syscall.EINVAL) + } + + // For pthread mutexes, we have to guarantee lock and unlock happen in + // the same thread. + runtime.LockOSThread() + + retCode := C.lock_semaphore(locks.lockStruct, C.uint32_t(sem)) + if retCode < 0 { + // Negative errno returned + return syscall.Errno(-1 * retCode) + } + + return nil +} + +// UnlockSemaphore unlocks the given semaphore. +// Unlocking a semaphore that is already unlocked with return EBUSY. +// There is no requirement that the given semaphore be allocated. +// This ensures that attempts to lock a container after it has been deleted, +// but before the caller has queried the database to determine this, will +// succeed. +func (locks *SHMLocks) UnlockSemaphore(sem uint32) error { + if !locks.valid { + return fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + if sem > locks.maxLocks { + return fmt.Errorf("given semaphore %d is higher than maximum locks count %d: %w", sem, locks.maxLocks, syscall.EINVAL) + } + + retCode := C.unlock_semaphore(locks.lockStruct, C.uint32_t(sem)) + if retCode < 0 { + // Negative errno returned + return syscall.Errno(-1 * retCode) + } + + // For pthread mutexes, we have to guarantee lock and unlock happen in + // the same thread. + // OK if we take multiple locks - UnlockOSThread() won't actually unlock + // until the number of calls equals the number of calls to + // LockOSThread() + runtime.UnlockOSThread() + + return nil +} + +// GetFreeLocks gets the number of locks available to be allocated. +func (locks *SHMLocks) GetFreeLocks() (uint32, error) { + if !locks.valid { + return 0, fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + retCode := C.available_locks(locks.lockStruct) + if retCode < 0 { + // Negative errno returned + return 0, syscall.Errno(-1 * retCode) + } + + return uint32(retCode), nil +} + +// Get a list of locks that are currently taken. +func (locks *SHMLocks) GetTakenLocks() ([]uint32, error) { + if !locks.valid { + return nil, fmt.Errorf("locks have already been closed: %w", syscall.EINVAL) + } + + var usedLocks []uint32 + + // I don't think we need to lock the OS thread here, since the lock (if + // taken) is immediately released, and Go shouldn't reschedule the CGo + // to another thread before the function finished executing. + var i uint32 + for i = 0; i < locks.maxLocks; i++ { + retCode := C.try_lock(locks.lockStruct, C.uint32_t(i)) + if retCode < 0 { + return nil, syscall.Errno(-1 * retCode) + } + if retCode == 0 { + usedLocks = append(usedLocks, i) + } + } + + return usedLocks, nil +} + +func unlinkSHMLock(path string) error { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + if _, err := C.shm_unlink(cPath); err != nil { + return fmt.Errorf("failed to unlink SHM locks: %w", err) + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock.h b/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock.h new file mode 100644 index 00000000..a09cc81e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock.h @@ -0,0 +1,47 @@ +#ifndef shm_locks_h_ +#define shm_locks_h_ + +#include +#include + +// Magic number to ensure we open the right SHM segment +#define MAGIC 0x87D1 + +// Type for our bitmaps +typedef uint32_t bitmap_t; + +// bitmap size +#define BITMAP_SIZE (sizeof(bitmap_t) * 8) + +// Struct to hold a single bitmap and associated locks +typedef struct lock_group { + bitmap_t bitmap; + pthread_mutex_t locks[BITMAP_SIZE]; +} lock_group_t; + +// Struct to hold our SHM locks. +// Unused is required to be 0 in the current implementation. If we ever make +// changes to this structure in the future, this will be repurposed as a version +// field. +typedef struct shm_struct { + uint16_t magic; + uint16_t unused; + pthread_mutex_t segment_lock; + uint32_t num_bitmaps; + uint32_t num_locks; + lock_group_t locks[]; +} shm_struct_t; + +shm_struct_t *setup_lock_shm(char *path, uint32_t num_locks, int *error_code); +shm_struct_t *open_lock_shm(char *path, uint32_t num_locks, int *error_code); +int32_t close_lock_shm(shm_struct_t *shm); +int64_t allocate_semaphore(shm_struct_t *shm); +int32_t allocate_given_semaphore(shm_struct_t *shm, uint32_t sem_index); +int32_t deallocate_semaphore(shm_struct_t *shm, uint32_t sem_index); +int32_t deallocate_all_semaphores(shm_struct_t *shm); +int32_t lock_semaphore(shm_struct_t *shm, uint32_t sem_index); +int32_t unlock_semaphore(shm_struct_t *shm, uint32_t sem_index); +int64_t available_locks(shm_struct_t *shm); +int32_t try_lock(shm_struct_t *shm, uint32_t sem_index); + +#endif diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock_nocgo.go b/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock_nocgo.go new file mode 100644 index 00000000..60400793 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/shm/shm_lock_nocgo.go @@ -0,0 +1,114 @@ +//go:build linux && !cgo + +package shm + +import ( + "github.com/sirupsen/logrus" +) + +// SHMLocks is a struct enabling POSIX semaphore locking in a shared memory +// segment. +type SHMLocks struct { +} + +// CreateSHMLock sets up a shared-memory segment holding a given number of POSIX +// semaphores, and returns a struct that can be used to operate on those locks. +// numLocks must not be 0, and may be rounded up to a multiple of the bitmap +// size used by the underlying implementation. +func CreateSHMLock(path string, numLocks uint32) (*SHMLocks, error) { + logrus.Error("Locks are not supported without cgo") + return &SHMLocks{}, nil +} + +// OpenSHMLock opens an existing shared-memory segment holding a given number of +// POSIX semaphores. numLocks must match the number of locks the shared memory +// segment was created with. +func OpenSHMLock(path string, numLocks uint32) (*SHMLocks, error) { + logrus.Error("Locks are not supported without cgo") + return &SHMLocks{}, nil +} + +// GetMaxLocks returns the maximum number of locks in the SHM +func (locks *SHMLocks) GetMaxLocks() uint32 { + logrus.Error("Locks are not supported without cgo") + return 0 +} + +// Close closes an existing shared-memory segment. +// The segment will be rendered unusable after closing. +// WARNING: If you Close() while there are still locks locked, these locks may +// fail to release, causing a program freeze. +// Close() is only intended to be used while testing the locks. +func (locks *SHMLocks) Close() error { + logrus.Error("Locks are not supported without cgo") + return nil +} + +// AllocateSemaphore allocates a semaphore from a shared-memory segment for use +// by a container or pod. +// Returns the index of the semaphore that was allocated. +// Allocations past the maximum number of locks given when the SHM segment was +// created will result in an error, and no semaphore will be allocated. +func (locks *SHMLocks) AllocateSemaphore() (uint32, error) { + logrus.Error("Locks are not supported without cgo") + return 0, nil +} + +// AllocateGivenSemaphore allocates the given semaphore from the shared-memory +// segment for use by a container or pod. +// If the semaphore is already in use or the index is invalid an error will be +// returned. +func (locks *SHMLocks) AllocateGivenSemaphore(sem uint32) error { + logrus.Error("Locks are not supported without cgo") + return nil +} + +// DeallocateSemaphore frees a semaphore in a shared-memory segment so it can be +// reallocated to another container or pod. +// The given semaphore must be already allocated, or an error will be returned. +func (locks *SHMLocks) DeallocateSemaphore(sem uint32) error { + logrus.Error("Locks are not supported without cgo") + return nil +} + +// DeallocateAllSemaphores frees all semaphores so they can be reallocated to +// other containers and pods. +func (locks *SHMLocks) DeallocateAllSemaphores() error { + logrus.Error("Locks are not supported without cgo") + return nil +} + +// LockSemaphore locks the given semaphore. +// If the semaphore is already locked, LockSemaphore will block until the lock +// can be acquired. +// There is no requirement that the given semaphore be allocated. +// This ensures that attempts to lock a container after it has been deleted, +// but before the caller has queried the database to determine this, will +// succeed. +func (locks *SHMLocks) LockSemaphore(sem uint32) error { + logrus.Error("Locks are not supported without cgo") + return nil +} + +// UnlockSemaphore unlocks the given semaphore. +// Unlocking a semaphore that is already unlocked with return EBUSY. +// There is no requirement that the given semaphore be allocated. +// This ensures that attempts to lock a container after it has been deleted, +// but before the caller has queried the database to determine this, will +// succeed. +func (locks *SHMLocks) UnlockSemaphore(sem uint32) error { + logrus.Error("Locks are not supported without cgo") + return nil +} + +// GetFreeLocks gets the number of locks available to be allocated. +func (locks *SHMLocks) GetFreeLocks() (uint32, error) { + logrus.Error("Locks are not supported without cgo") + return 0, nil +} + +// Get a list of locks that are currently taken. +func (locks *SHMLocks) GetTakenLocks() ([]uint32, error) { + logrus.Error("Locks are not supported without cgo") + return nil, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/shm_lock_manager_linux.go b/vendor/github.com/containers/podman/v5/libpod/lock/shm_lock_manager_linux.go new file mode 100644 index 00000000..ba438b9c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/shm_lock_manager_linux.go @@ -0,0 +1,142 @@ +//go:build linux + +package lock + +import ( + "fmt" + "syscall" + + "github.com/containers/podman/v5/libpod/lock/shm" +) + +// SHMLockManager manages shared memory locks. +type SHMLockManager struct { + locks *shm.SHMLocks +} + +// NewSHMLockManager makes a new SHMLockManager with the given number of locks. +// Due to the underlying implementation, the exact number of locks created may +// be greater than the number given here. +func NewSHMLockManager(path string, numLocks uint32) (Manager, error) { + locks, err := shm.CreateSHMLock(path, numLocks) + if err != nil { + return nil, err + } + + manager := new(SHMLockManager) + manager.locks = locks + + return manager, nil +} + +// OpenSHMLockManager opens an existing SHMLockManager with the given number of +// locks. +func OpenSHMLockManager(path string, numLocks uint32) (Manager, error) { + locks, err := shm.OpenSHMLock(path, numLocks) + if err != nil { + return nil, err + } + + manager := new(SHMLockManager) + manager.locks = locks + + return manager, nil +} + +// AllocateLock allocates a new lock from the manager. +func (m *SHMLockManager) AllocateLock() (Locker, error) { + semIndex, err := m.locks.AllocateSemaphore() + if err != nil { + return nil, err + } + + lock := new(SHMLock) + lock.lockID = semIndex + lock.manager = m + + return lock, nil +} + +// AllocateAndRetrieveLock allocates the lock with the given ID and returns it. +// If the lock is already allocated, error. +func (m *SHMLockManager) AllocateAndRetrieveLock(id uint32) (Locker, error) { + lock := new(SHMLock) + lock.lockID = id + lock.manager = m + + if id >= m.locks.GetMaxLocks() { + return nil, fmt.Errorf("lock ID %d is too large - max lock size is %d: %w", + id, m.locks.GetMaxLocks()-1, syscall.EINVAL) + } + + if err := m.locks.AllocateGivenSemaphore(id); err != nil { + return nil, err + } + + return lock, nil +} + +// RetrieveLock retrieves a lock from the manager given its ID. +func (m *SHMLockManager) RetrieveLock(id uint32) (Locker, error) { + lock := new(SHMLock) + lock.lockID = id + lock.manager = m + + if id >= m.locks.GetMaxLocks() { + return nil, fmt.Errorf("lock ID %d is too large - max lock size is %d: %w", + id, m.locks.GetMaxLocks()-1, syscall.EINVAL) + } + + return lock, nil +} + +// FreeAllLocks frees all locks in the manager. +// This function is DANGEROUS. Please read the full comment in locks.go before +// trying to use it. +func (m *SHMLockManager) FreeAllLocks() error { + return m.locks.DeallocateAllSemaphores() +} + +// AvailableLocks returns the number of free locks in the manager. +func (m *SHMLockManager) AvailableLocks() (*uint32, error) { + avail, err := m.locks.GetFreeLocks() + if err != nil { + return nil, err + } + + return &avail, nil +} + +func (m *SHMLockManager) LocksHeld() ([]uint32, error) { + return m.locks.GetTakenLocks() +} + +// SHMLock is an individual shared memory lock. +type SHMLock struct { + lockID uint32 + manager *SHMLockManager +} + +// ID returns the ID of the lock. +func (l *SHMLock) ID() uint32 { + return l.lockID +} + +// Lock acquires the lock. +func (l *SHMLock) Lock() { + if err := l.manager.locks.LockSemaphore(l.lockID); err != nil { + panic(err.Error()) + } +} + +// Unlock releases the lock. +func (l *SHMLock) Unlock() { + if err := l.manager.locks.UnlockSemaphore(l.lockID); err != nil { + panic(err.Error()) + } +} + +// Free releases the lock, allowing it to be reused. +func (l *SHMLock) Free() error { + return l.manager.locks.DeallocateSemaphore(l.lockID) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/lock/shm_lock_manager_unsupported.go b/vendor/github.com/containers/podman/v5/libpod/lock/shm_lock_manager_unsupported.go new file mode 100644 index 00000000..defb1fae --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/lock/shm_lock_manager_unsupported.go @@ -0,0 +1,44 @@ +//go:build !linux + +package lock + +import "fmt" + +// SHMLockManager is a shared memory lock manager. +// It is not supported on non-Unix platforms. +type SHMLockManager struct{} + +// NewSHMLockManager is not supported on this platform +func NewSHMLockManager(_ string, _ uint32) (Manager, error) { + return nil, fmt.Errorf("not supported") +} + +// OpenSHMLockManager is not supported on this platform +func OpenSHMLockManager(_ string, _ uint32) (Manager, error) { + return nil, fmt.Errorf("not supported") +} + +// AllocateLock is not supported on this platform +func (m *SHMLockManager) AllocateLock() (Locker, error) { + return nil, fmt.Errorf("not supported") +} + +// RetrieveLock is not supported on this platform +func (m *SHMLockManager) RetrieveLock(_ string) (Locker, error) { + return nil, fmt.Errorf("not supported") +} + +// FreeAllLocks is not supported on this platform +func (m *SHMLockManager) FreeAllLocks() error { + return fmt.Errorf("not supported") +} + +// AvailableLocks is not supported on this platform +func (m *SHMLockManager) AvailableLocks() (*uint32, error) { + return nil, fmt.Errorf("not supported") +} + +// LocksHeld is not supported on this platform +func (m *SHMLockManager) LocksHeld() ([]uint32, error) { + return nil, fmt.Errorf("not supported") +} diff --git a/vendor/github.com/containers/podman/v5/libpod/logs/log.go b/vendor/github.com/containers/podman/v5/libpod/logs/log.go new file mode 100644 index 00000000..e6ad32f9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/logs/log.go @@ -0,0 +1,271 @@ +package logs + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + "sync" + "time" + + "github.com/containers/podman/v5/libpod/logs/reversereader" + "github.com/nxadm/tail" + "github.com/sirupsen/logrus" +) + +const ( + // LogTimeFormat is the time format used in the log. + // It is a modified version of RFC3339Nano that guarantees trailing + // zeroes are not trimmed, taken from + // https://github.com/golang/go/issues/19635 + LogTimeFormat = "2006-01-02T15:04:05.000000000Z07:00" + + // PartialLogType signifies a log line that exceeded the buffer + // length and needed to spill into a new line + PartialLogType = "P" + + // FullLogType signifies a log line is full + FullLogType = "F" + + // ANSIEscapeResetCode is a code that resets all colors and text effects + ANSIEscapeResetCode = "\033[0m" +) + +// LogOptions is the options you can use for logs +type LogOptions struct { + Details bool + Follow bool + Since time.Time + Until time.Time + Tail int64 + Timestamps bool + Colors bool + Multi bool + WaitGroup *sync.WaitGroup + UseName bool +} + +// LogLine describes the information for each line of a log +type LogLine struct { + Device string + ParseLogType string + Time time.Time + Msg string + CID string + CName string + ColorID int64 +} + +// GetLogFile returns an hp tail for a container given options +func GetLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error) { + var ( + whence int + err error + logTail []*LogLine + ) + // whence 0=origin, 2=end + if options.Tail >= 0 { + whence = 2 + } + if options.Tail > 0 { + logTail, err = getTailLog(path, int(options.Tail)) + if err != nil { + return nil, nil, err + } + } + seek := tail.SeekInfo{ + Offset: 0, + Whence: whence, + } + + t, err := tail.TailFile(path, tail.Config{MustExist: true, Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger, ReOpen: options.Follow}) + return t, logTail, err +} + +func getTailLog(path string, tail int) ([]*LogLine, error) { + var ( + nllCounter int + leftover string + tailLog []*LogLine + eof bool + ) + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + rr, err := reversereader.NewReverseReader(f) + if err != nil { + return nil, err + } + + first := true + + for { + s, err := rr.Read() + if err != nil { + if !errors.Is(err, io.EOF) { + return nil, fmt.Errorf("reverse log read: %w", err) + } + eof = true + } + + lines := strings.Split(s+leftover, "\n") + // we read a chunk of data, so make sure to read the line in inverse order + for i := len(lines) - 1; i > 0; i-- { + // ignore empty lines + if lines[i] == "" { + continue + } + nll, err := NewLogLine(lines[i]) + if err != nil { + return nil, err + } + if !nll.Partial() || first { + nllCounter++ + // Even if the last line is partial we need to count it as it will be printed as line. + // Because we read backwards the first line we read is the last line in the log. + first = false + } + // We explicitly need to check for more lines than tail because we have + // to read to next full line and must keep all partial lines + // https://github.com/containers/podman/issues/19545 + if nllCounter > tail { + // because we add lines in the inverse order we must invert the slice in the end + return reverseLog(tailLog), nil + } + // only append after the return here because we do not want to include the next full line + tailLog = append(tailLog, nll) + } + leftover = lines[0] + + // eof was reached + if eof { + // when we have still a line and do not have enough tail lines already + if leftover != "" && nllCounter < tail { + nll, err := NewLogLine(leftover) + if err != nil { + return nil, err + } + tailLog = append(tailLog, nll) + } + // because we add lines in the inverse order we must invert the slice in the end + return reverseLog(tailLog), nil + } + } +} + +// reverseLog reverse the log line slice, needed for tail as we read lines backwards but still +// need to print them in the correct order at the end so use that helper for it. +func reverseLog(s []*LogLine) []*LogLine { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } + return s +} + +// getColor returns an ANSI escape code for color based on the colorID +func getColor(colorID int64) string { + colors := map[int64]string{ + 0: "\033[37m", // Light Gray + 1: "\033[31m", // Red + 2: "\033[33m", // Yellow + 3: "\033[34m", // Blue + 4: "\033[35m", // Magenta + 5: "\033[36m", // Cyan + 6: "\033[32m", // Green + } + return colors[colorID%int64(len(colors))] +} + +func (l *LogLine) colorize(prefix string) string { + return getColor(l.ColorID) + prefix + l.Msg + ANSIEscapeResetCode +} + +// String converts a log line to a string for output given whether a detail +// bool is specified. +func (l *LogLine) String(options *LogOptions) string { + var out string + if options.Multi { + if options.UseName { + out = l.CName + " " + } else { + cid := l.CID + if len(cid) > 12 { + cid = cid[:12] + } + out = fmt.Sprintf("%s ", cid) + } + } + + if options.Timestamps { + out += fmt.Sprintf("%s ", l.Time.Format(LogTimeFormat)) + } + + if options.Colors { + out = l.colorize(out) + } else { + out += l.Msg + } + + return out +} + +// Since returns a bool as to whether a log line occurred after a given time +func (l *LogLine) Since(since time.Time) bool { + return l.Time.After(since) || since.IsZero() +} + +// Until returns a bool as to whether a log line occurred before a given time +func (l *LogLine) Until(until time.Time) bool { + return l.Time.Before(until) || until.IsZero() +} + +// NewLogLine creates a logLine struct from a container log string +func NewLogLine(line string) (*LogLine, error) { + splitLine := strings.Split(line, " ") + if len(splitLine) < 4 { + return nil, fmt.Errorf("'%s' is not a valid container log line", line) + } + logTime, err := time.Parse(LogTimeFormat, splitLine[0]) + if err != nil { + return nil, fmt.Errorf("unable to convert time %s from container log: %w", splitLine[0], err) + } + l := LogLine{ + Time: logTime, + Device: splitLine[1], + ParseLogType: splitLine[2], + Msg: strings.Join(splitLine[3:], " "), + } + return &l, nil +} + +// Partial returns a bool if the log line is a partial log type +func (l *LogLine) Partial() bool { + return l.ParseLogType == PartialLogType +} + +func (l *LogLine) Write(stdout io.Writer, stderr io.Writer, logOpts *LogOptions) { + switch l.Device { + case "stdout": + if stdout != nil { + if l.Partial() { + fmt.Fprint(stdout, l.String(logOpts)) + } else { + fmt.Fprintln(stdout, l.String(logOpts)) + } + } + case "stderr": + if stderr != nil { + if l.Partial() { + fmt.Fprint(stderr, l.String(logOpts)) + } else { + fmt.Fprintln(stderr, l.String(logOpts)) + } + } + default: + // Warn the user if the device type does not match. Most likely the file is corrupted. + logrus.Warnf("Unknown Device type '%s' in log file from Container %s", l.Device, l.CID) + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/logs/reversereader/reversereader.go b/vendor/github.com/containers/podman/v5/libpod/logs/reversereader/reversereader.go new file mode 100644 index 00000000..409be32f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/logs/reversereader/reversereader.go @@ -0,0 +1,60 @@ +package reversereader + +import ( + "errors" + "fmt" + "io" + "os" +) + +// ReverseReader structure for reading a file backwards +type ReverseReader struct { + reader *os.File + offset int64 + readSize int64 +} + +// NewReverseReader returns a reader that reads from the end of a file +// rather than the beginning. It sets the readsize to pagesize and determines +// the first offset using modulus. +func NewReverseReader(reader *os.File) (*ReverseReader, error) { + // pagesize should be safe for memory use and file reads should be on page + // boundaries as well + pageSize := int64(os.Getpagesize()) + stat, err := reader.Stat() + if err != nil { + return nil, err + } + // figure out the last page boundary + remainder := stat.Size() % pageSize + end, err := reader.Seek(0, 2) + if err != nil { + return nil, err + } + // set offset (starting position) to the last page boundary or + // zero if fits in one page + startOffset := max(end-remainder, 0) + rr := ReverseReader{ + reader: reader, + offset: startOffset, + readSize: pageSize, + } + return &rr, nil +} + +// ReverseReader reads from a given offset to the previous offset and +// then sets the newoff set one pagesize less than the previous read. +func (r *ReverseReader) Read() (string, error) { + if r.offset < 0 { + return "", fmt.Errorf("at beginning of file: %w", io.EOF) + } + // Read from given offset + b := make([]byte, r.readSize) + n, err := r.reader.ReadAt(b, r.offset) + if err != nil && !errors.Is(err, io.EOF) { + return "", err + } + // Move the offset one pagesize up + r.offset -= r.readSize + return string(b[:n]), nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/mounts_linux.go b/vendor/github.com/containers/podman/v5/libpod/mounts_linux.go new file mode 100644 index 00000000..10dbdcb1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/mounts_linux.go @@ -0,0 +1,18 @@ +//go:build !remote + +package libpod + +const ( + // MountPrivate represents the private mount option. + MountPrivate = "private" + // MountRPrivate represents the rprivate mount option. + MountRPrivate = "rprivate" + // MountShared represents the shared mount option. + MountShared = "shared" + // MountRShared represents the rshared mount option. + MountRShared = "rshared" + // MountSlave represents the slave mount option. + MountSlave = "slave" + // MountRSlave represents the rslave mount option. + MountRSlave = "rslave" +) diff --git a/vendor/github.com/containers/podman/v5/libpod/networking_common.go b/vendor/github.com/containers/podman/v5/libpod/networking_common.go new file mode 100644 index 00000000..28ee91a6 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/networking_common.go @@ -0,0 +1,765 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "errors" + "fmt" + "os" + "regexp" + "slices" + "sort" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/pkg/namespaces" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/etchosts" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/machine" + "go.podman.io/storage/pkg/lockfile" +) + +// bindPorts ports to keep them open via conmon so no other process can use them and we can check if they are in use. +// Note in all cases it is important that we bind before setting up the network to avoid issues were we add firewall +// rules before we even "own" the port. +func (c *Container) bindPorts() ([]*os.File, error) { + if !c.runtime.config.Engine.EnablePortReservation || rootless.IsRootless() || !c.config.NetMode.IsBridge() { + return nil, nil + } + return bindPorts(c.convertPortMappings()) +} + +// convertPortMappings will remove the HostIP part from the ports when running inside podman machine. +// This is needed because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports. +// For machine the HostIP must only be used by gvproxy and never in the VM. +func (c *Container) convertPortMappings() []types.PortMapping { + if !machine.IsGvProxyBased() || len(c.config.PortMappings) == 0 { + return c.config.PortMappings + } + // if we run in a machine VM we have to ignore the host IP part + newPorts := make([]types.PortMapping, 0, len(c.config.PortMappings)) + for _, port := range c.config.PortMappings { + port.HostIP = "" + newPorts = append(newPorts, port) + } + return newPorts +} + +func (c *Container) getNetworkOptions(networkOpts map[string]types.PerNetworkOptions) types.NetworkOptions { + nameservers := make([]string, 0, len(c.runtime.config.Containers.DNSServers.Get())+len(c.config.DNSServer)) + nameservers = append(nameservers, c.runtime.config.Containers.DNSServers.Get()...) + for _, ip := range c.config.DNSServer { + nameservers = append(nameservers, ip.String()) + } + opts := types.NetworkOptions{ + ContainerID: c.config.ID, + ContainerName: getNetworkPodName(c), + DNSServers: nameservers, + ContainerHostname: c.NetworkHostname(), + } + opts.PortMappings = c.convertPortMappings() + + // If the container requested special network options use this instead of the config. + // This is the case for container restore or network reload. + if c.perNetworkOpts != nil { + opts.Networks = c.perNetworkOpts + } else { + opts.Networks = networkOpts + } + return opts +} + +// setUpNetwork will set up the networks, on error it will also tear down the cni +// networks. If rootless it will join/create the rootless network namespace. +func (r *Runtime) setUpNetwork(ns string, opts types.NetworkOptions) (map[string]types.StatusBlock, error) { + return r.network.Setup(ns, types.SetupOptions{NetworkOptions: opts}) +} + +// getNetworkPodName return the pod name (hostname) used by dns backend. +// If we are in the pod network namespace use the pod name otherwise the container name +func getNetworkPodName(c *Container) string { + if c.config.NetMode.IsPod() || c.IsInfra() { + pod, err := c.runtime.state.Pod(c.PodID()) + if err == nil { + return pod.Name() + } + } + return c.Name() +} + +// Tear down a container's network configuration and joins the +// rootless net ns as rootless user +func (r *Runtime) teardownNetworkBackend(ns string, opts types.NetworkOptions) error { + return r.network.Teardown(ns, types.TeardownOptions{NetworkOptions: opts}) +} + +// Tear down a container's network backend configuration, but do not tear down the +// namespace itself. +func (r *Runtime) teardownNetwork(ctr *Container) error { + if ctr.state.NetNS == "" { + // The container has no network namespace, we're set + return nil + } + + logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS, ctr.ID()) + + networks, err := ctr.networks() + if err != nil { + return err + } + + if !ctr.config.NetMode.IsSlirp4netns() && + !ctr.config.NetMode.IsPasta() && len(networks) > 0 { + netOpts := ctr.getNetworkOptions(networks) + return r.teardownNetworkBackend(ctr.state.NetNS, netOpts) + } + return nil +} + +// isBridgeNetMode checks if the given network mode is bridge. +// It returns nil when it is set to bridge and an error otherwise. +func isBridgeNetMode(n namespaces.NetworkMode) error { + if !n.IsBridge() { + return fmt.Errorf("%q is not supported: %w", n, define.ErrNetworkModeInvalid) + } + return nil +} + +// Reload only works with containers with a configured network. +// It will tear down, and then reconfigure, the network of the container. +// This is mainly used when a reload of firewall rules wipes out existing +// firewall configuration. +// Efforts will be made to preserve MAC and IP addresses. +// Only works on containers with bridge networking at present, though in the future we could +// extend this to stop + restart slirp4netns +func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.StatusBlock, error) { + if ctr.state.NetNS == "" { + return nil, fmt.Errorf("container %s network is not configured, refusing to reload: %w", ctr.ID(), define.ErrCtrStateInvalid) + } + if err := isBridgeNetMode(ctr.config.NetMode); err != nil { + return nil, err + } + logrus.Infof("Going to reload container %s network", ctr.ID()) + + err := r.teardownNetwork(ctr) + if err != nil { + // teardownNetwork will error if the iptables rules do not exist and this is the case after + // a firewall reload. The purpose of network reload is to recreate the rules if they do + // not exists so we should not log this specific error as error. This would confuse users otherwise. + // iptables-legacy and iptables-nft will create different errors. Make sure to match both. + b, rerr := regexp.MatchString("Couldn't load target `CNI-[a-f0-9]{24}':No such file or directory|Chain 'CNI-[a-f0-9]{24}' does not exist", err.Error()) + if rerr == nil && !b { + logrus.Error(err) + } else { + logrus.Info(err) + } + } + + networkOpts, err := ctr.networks() + if err != nil { + return nil, err + } + + // Set the same network settings as before.. + netStatus := ctr.getNetworkStatus() + for network, perNetOpts := range networkOpts { + for name, netInt := range netStatus[network].Interfaces { + perNetOpts.InterfaceName = name + perNetOpts.StaticMAC = netInt.MacAddress + for _, netAddress := range netInt.Subnets { + perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.IPNet.IP) + } + // Normally interfaces have a length of 1, only for some special cni configs we could get more. + // For now just use the first interface to get the ips this should be good enough for most cases. + break + } + networkOpts[network] = perNetOpts + } + ctr.perNetworkOpts = networkOpts + + return r.configureNetNS(ctr, ctr.state.NetNS) +} + +// Produce an InspectNetworkSettings containing information on the container +// network. +func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) { + if c.config.NetNsCtr != "" { + netNsCtr, err := c.runtime.GetContainer(c.config.NetNsCtr) + if err != nil { + return nil, err + } + // see https://github.com/containers/podman/issues/10090 + // the container has to be locked for syncContainer() + netNsCtr.lock.Lock() + defer netNsCtr.lock.Unlock() + // Have to sync to ensure that state is populated + if err := netNsCtr.syncContainer(); err != nil { + return nil, err + } + logrus.Debugf("Container %s shares network namespace, retrieving network info of container %s", c.ID(), c.config.NetNsCtr) + + return netNsCtr.getContainerNetworkInfo() + } + + settings := new(define.InspectNetworkSettings) + settings.Ports = makeInspectPortBindings(c.config.PortMappings) + + networks, err := c.networks() + if err != nil { + return nil, err + } + + getNetworkID := func(nameOrID string) string { + network, err := c.runtime.network.NetworkInspect(nameOrID) + if err == nil && network.ID != "" { + return network.ID + } + return nameOrID + } + + setDefaultNetworks := func() { + settings.Networks = make(map[string]*define.InspectAdditionalNetwork, 1) + name := c.NetworkMode() + addedNet := new(define.InspectAdditionalNetwork) + addedNet.NetworkID = getNetworkID(name) + settings.Networks[name] = addedNet + } + + if c.state.NetNS == "" { + if networkNSPath, set := c.joinedNetworkNSPath(); networkNSPath != "" { + if result, err := c.inspectJoinedNetworkNS(networkNSPath); err == nil { + // fallback to dummy configuration + settings.InspectBasicNetworkConfig = resultToBasicNetworkConfig(result) + } else { + // do not propagate error inspecting a joined network ns + logrus.Errorf("Inspecting network namespace: %s of container %s: %v", networkNSPath, c.ID(), err) + } + return settings, nil + } else if set { + // network none case, if running allow user to join netns via sandbox key + // https://github.com/containers/podman/issues/16716 + if c.state.PID > 0 { + settings.SandboxKey = fmt.Sprintf("/proc/%d/ns/net", c.state.PID) + } + } + // We can't do more if the network is down. + // We still want to make dummy configurations for each network + // the container joined. + if len(networks) > 0 { + settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks)) + for net, opts := range networks { + cniNet := new(define.InspectAdditionalNetwork) + cniNet.NetworkID = getNetworkID(net) + cniNet.Aliases = opts.Aliases + settings.Networks[net] = cniNet + } + } else { + setDefaultNetworks() + } + + return settings, nil + } + + // Set network namespace path + settings.SandboxKey = c.state.NetNS + + netStatus := c.getNetworkStatus() + // If this is empty, we're probably slirp4netns + if len(netStatus) == 0 { + return settings, nil + } + + // If we have networks - handle that here + if len(networks) > 0 { + if len(networks) != len(netStatus) { + return nil, fmt.Errorf("network inspection mismatch: asked to join %d network(s) %v, but have information on %d network(s): %w", len(networks), networks, len(netStatus), define.ErrInternal) + } + + settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks)) + + for name, opts := range networks { + result := netStatus[name] + addedNet := new(define.InspectAdditionalNetwork) + addedNet.NetworkID = getNetworkID(name) + addedNet.Aliases = opts.Aliases + addedNet.InspectBasicNetworkConfig = resultToBasicNetworkConfig(result) + + settings.Networks[name] = addedNet + } + + // if not only the default network is connected we can return here + // otherwise we have to populate the InspectBasicNetworkConfig settings + _, isDefaultNet := networks[c.runtime.config.Network.DefaultNetwork] + if len(networks) != 1 || !isDefaultNet { + return settings, nil + } + } else { + setDefaultNetworks() + } + + // If not joining networks, we should have at most 1 result + if len(netStatus) > 1 { + return nil, fmt.Errorf("should have at most 1 network status result if not joining networks, instead got %d: %w", len(netStatus), define.ErrInternal) + } + + if len(netStatus) == 1 { + for _, status := range netStatus { + settings.InspectBasicNetworkConfig = resultToBasicNetworkConfig(status) + } + } + return settings, nil +} + +// resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI +// result +func resultToBasicNetworkConfig(result types.StatusBlock) define.InspectBasicNetworkConfig { + config := define.InspectBasicNetworkConfig{} + interfaceNames := make([]string, 0, len(result.Interfaces)) + for interfaceName := range result.Interfaces { + interfaceNames = append(interfaceNames, interfaceName) + } + // ensure consistent inspect results by sorting + sort.Strings(interfaceNames) + for _, interfaceName := range interfaceNames { + netInt := result.Interfaces[interfaceName] + for _, netAddress := range netInt.Subnets { + size, _ := netAddress.IPNet.Mask.Size() + if netAddress.IPNet.IP.To4() != nil { + // ipv4 + if config.IPAddress == "" { + config.IPAddress = netAddress.IPNet.IP.String() + config.IPPrefixLen = size + config.Gateway = netAddress.Gateway.String() + } else { + config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, define.Address{Addr: netAddress.IPNet.IP.String(), PrefixLength: size}) + } + } else { + // ipv6 + if config.GlobalIPv6Address == "" { + config.GlobalIPv6Address = netAddress.IPNet.IP.String() + config.GlobalIPv6PrefixLen = size + config.IPv6Gateway = netAddress.Gateway.String() + } else { + config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, define.Address{Addr: netAddress.IPNet.IP.String(), PrefixLength: size}) + } + } + } + if config.MacAddress == "" { + config.MacAddress = netInt.MacAddress.String() + } else { + config.AdditionalMacAddresses = append(config.AdditionalMacAddresses, netInt.MacAddress.String()) + } + } + return config +} + +// NetworkDisconnect removes a container from the network +func (c *Container) NetworkDisconnect(nameOrID, netName string, _ bool) error { + // only the bridge mode supports cni networks + if err := isBridgeNetMode(c.config.NetMode); err != nil { + return err + } + + c.lock.Lock() + defer c.lock.Unlock() + + networks, err := c.networks() + if err != nil { + return err + } + + // check if network exists and if the input is an ID we get the name + // CNI and netavark and the libpod db only uses names so it is important that we only use the name + netName, _, err = c.runtime.normalizeNetworkName(netName) + if err != nil { + return err + } + + netOpts, nameExists := networks[netName] + if !nameExists && len(networks) > 0 { + return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName) + } + + if err := c.syncContainer(); err != nil { + return err + } + // get network status before we disconnect + networkStatus := c.getNetworkStatus() + + if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil { + return err + } + + // Since we removed the new network from the container db we must have to add it back during partial setup errors + addContainerNetworkToDB := func() { + if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil { + logrus.Errorf("Failed to add network %s for container %s to DB after failed network disconnect", netName, nameOrID) + } + } + + c.newNetworkEvent(events.NetworkDisconnect, netName) + if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { + return nil + } + + if c.state.NetNS == "" { + addContainerNetworkToDB() + return fmt.Errorf("unable to disconnect %s from %s: %w", nameOrID, netName, define.ErrNoNetwork) + } + + opts := types.NetworkOptions{ + ContainerID: c.config.ID, + ContainerName: getNetworkPodName(c), + } + opts.PortMappings = c.convertPortMappings() + opts.Networks = map[string]types.PerNetworkOptions{ + netName: networks[netName], + } + + if err := c.runtime.teardownNetworkBackend(c.state.NetNS, opts); err != nil { + addContainerNetworkToDB() + return err + } + + // update network status if container is running + oldStatus, statusExist := networkStatus[netName] + delete(networkStatus, netName) + c.state.NetworkStatus = networkStatus + err = c.save() + if err != nil { + return err + } + + // Reload ports when there are still connected networks, maybe we removed the network interface with the child ip. + // Reloading without connected networks does not make sense, so we can skip this step. + if rootless.IsRootless() && len(networkStatus) > 0 { + if err := c.reloadRootlessRLKPortMapping(); err != nil { + return err + } + } + + // Update resolv.conf if required + if statusExist { + stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs)) + for _, ip := range oldStatus.DNSServerIPs { + stringIPs = append(stringIPs, ip.String()) + } + if len(stringIPs) > 0 { + logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs) + if err := c.removeNameserver(stringIPs); err != nil { + return err + } + } + + // update /etc/hosts file + if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + // sync the names with c.getHostsEntries() + names := []string{c.Hostname(), c.config.Name} + rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...) + if len(rm) > 0 { + // make sure to lock this file to prevent concurrent writes when + // this is used a net dependency container + lock, err := lockfile.GetLockFile(file) + if err != nil { + return fmt.Errorf("failed to lock hosts file: %w", err) + } + logrus.Debugf("Remove /etc/hosts entries %v", rm) + lock.Lock() + err = etchosts.Remove(file, rm) + lock.Unlock() + if err != nil { + return err + } + } + } + } + return nil +} + +// ConnectNetwork connects a container to a given network +func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error { + // only the bridge mode supports networks + if err := isBridgeNetMode(c.config.NetMode); err != nil { + return err + } + + c.lock.Lock() + defer c.lock.Unlock() + + networks, err := c.networks() + if err != nil { + return err + } + + // check if network exists and if the input is an ID we get the name + // CNI and netavark and the libpod db only uses names so it is important that we only use the name + var nicName string + netName, nicName, err = c.runtime.normalizeNetworkName(netName) + if err != nil { + return err + } + + if err := c.syncContainer(); err != nil { + return err + } + + // get network status before we connect + networkStatus := c.getNetworkStatus() + + netOpts.Aliases = append(netOpts.Aliases, getExtraNetworkAliases(c)...) + + // check whether interface is to be named as the network_interface + // when name left unspecified + if netOpts.InterfaceName == "" { + netOpts.InterfaceName = nicName + } + + // set default interface name + if netOpts.InterfaceName == "" { + netOpts.InterfaceName = getFreeInterfaceName(networks) + if netOpts.InterfaceName == "" { + return errors.New("could not find free network interface name") + } + } + + if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil { + // Docker compat: treat requests to attach already attached networks as a no-op, ignoring opts + if errors.Is(err, define.ErrNetworkConnected) && !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { + return nil + } + + return err + } + + // Since we added the new network to the container db we must have to remove it from that during partial setup errors + removeContainerNetworkFromDB := func() { + if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil { + logrus.Errorf("Failed to remove network %s for container %s from DB after failed network connect", netName, nameOrID) + } + } + + c.newNetworkEvent(events.NetworkConnect, netName) + if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { + return nil + } + if c.state.NetNS == "" { + removeContainerNetworkFromDB() + return fmt.Errorf("unable to connect %s to %s: %w", nameOrID, netName, define.ErrNoNetwork) + } + + opts := types.NetworkOptions{ + ContainerID: c.config.ID, + ContainerName: getNetworkPodName(c), + } + opts.PortMappings = c.convertPortMappings() + opts.Networks = map[string]types.PerNetworkOptions{ + netName: netOpts, + } + + results, err := c.runtime.setUpNetwork(c.state.NetNS, opts) + if err != nil { + removeContainerNetworkFromDB() + return err + } + if len(results) != 1 { + return errors.New("when adding aliases, results must be of length 1") + } + + // we need to get the old host entries before we add the new one to the status + // if we do not add do it here we will get the wrong existing entries which will throw of the logic + // we could also copy the map but this does not seem worth it + // sync the hostNames with c.getHostsEntries() + hostNames := []string{c.Hostname(), c.config.Name} + oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...) + + // update network status + if networkStatus == nil { + networkStatus = make(map[string]types.StatusBlock, 1) + } + networkStatus[netName] = results[netName] + c.state.NetworkStatus = networkStatus + + err = c.save() + if err != nil { + return err + } + + // The first network needs a port reload to set the correct child ip for the rootlessport process. + // Adding a second network does not require a port reload because the child ip is still valid. + if rootless.IsRootless() && len(networks) == 0 { + if err := c.reloadRootlessRLKPortMapping(); err != nil { + return err + } + } + + ipv6 := c.checkForIPv6(networkStatus) + + // Update resolv.conf if required + stringIPs := make([]string, 0, len(results[netName].DNSServerIPs)) + for _, ip := range results[netName].DNSServerIPs { + if (ip.To4() == nil) && !ipv6 { + continue + } + stringIPs = append(stringIPs, ip.String()) + } + if len(stringIPs) > 0 { + logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs) + if err := c.addNameserver(stringIPs); err != nil { + return err + } + } + + // update /etc/hosts file + if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + // make sure to lock this file to prevent concurrent writes when + // this is used a net dependency container + lock, err := lockfile.GetLockFile(file) + if err != nil { + return fmt.Errorf("failed to lock hosts file: %w", err) + } + new := etchosts.GetNetworkHostEntries(results, hostNames...) + logrus.Debugf("Add /etc/hosts entries %v", new) + // use special AddIfExists API to make sure we only add new entries if an old one exists + // see the AddIfExists() comment for more information + lock.Lock() + err = etchosts.AddIfExists(file, oldHostEntries, new) + lock.Unlock() + if err != nil { + return err + } + } + + return nil +} + +// get a free interface name for a new network +// return an empty string if no free name was found +func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string { + ifNames := make([]string, 0, len(networks)) + for _, opts := range networks { + ifNames = append(ifNames, opts.InterfaceName) + } + for i := range 100000 { + ifName := fmt.Sprintf("eth%d", i) + if !slices.Contains(ifNames, ifName) { + return ifName + } + } + return "" +} + +func getExtraNetworkAliases(c *Container) []string { + // always add the short id as alias for docker compat + alias := []string{c.config.ID[:12]} + // if an explicit hostname was set add it as well + if c.config.Spec.Hostname != "" { + alias = append(alias, c.config.Spec.Hostname) + } + return alias +} + +// DisconnectContainerFromNetwork removes a container from its network +func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error { + ctr, err := r.LookupContainer(nameOrID) + if err != nil { + return err + } + return ctr.NetworkDisconnect(nameOrID, netName, force) +} + +// ConnectContainerToNetwork connects a container to a network +func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error { + ctr, err := r.LookupContainer(nameOrID) + if err != nil { + return err + } + return ctr.NetworkConnect(nameOrID, netName, netOpts) +} + +// normalizeNetworkName takes a network name, a partial or a full network ID and +// returns: 1) the network name and 2) the network_interface name for macvlan +// and ipvlan drivers if the naming pattern is "device" defined in the +// containers.conf file. Else, "". +// If the network is not found an error is returned. +func (r *Runtime) normalizeNetworkName(nameOrID string) (string, string, error) { + net, err := r.network.NetworkInspect(nameOrID) + if err != nil { + return "", "", err + } + + netIface := "" + namingPattern := r.config.Containers.InterfaceName + if namingPattern == "device" && (net.Driver == types.MacVLANNetworkDriver || net.Driver == types.IPVLANNetworkDriver) { + netIface = net.NetworkInterface + } + + return net.Name, netIface, nil +} + +// ocicniPortsToNetTypesPorts convert the old port format to the new one +// while deduplicating ports into ranges +func ocicniPortsToNetTypesPorts(ports []types.OCICNIPortMapping) []types.PortMapping { + if len(ports) == 0 { + return nil + } + + newPorts := make([]types.PortMapping, 0, len(ports)) + + // first sort the ports + sort.Slice(ports, func(i, j int) bool { + return compareOCICNIPorts(ports[i], ports[j]) + }) + + // we already check if the slice is empty so we can use the first element + currentPort := types.PortMapping{ + HostIP: ports[0].HostIP, + HostPort: uint16(ports[0].HostPort), + ContainerPort: uint16(ports[0].ContainerPort), + Protocol: ports[0].Protocol, + Range: 1, + } + + for i := 1; i < len(ports); i++ { + if ports[i].HostIP == currentPort.HostIP && + ports[i].Protocol == currentPort.Protocol && + ports[i].HostPort-int32(currentPort.Range) == int32(currentPort.HostPort) && + ports[i].ContainerPort-int32(currentPort.Range) == int32(currentPort.ContainerPort) { + currentPort.Range++ + } else { + newPorts = append(newPorts, currentPort) + currentPort = types.PortMapping{ + HostIP: ports[i].HostIP, + HostPort: uint16(ports[i].HostPort), + ContainerPort: uint16(ports[i].ContainerPort), + Protocol: ports[i].Protocol, + Range: 1, + } + } + } + newPorts = append(newPorts, currentPort) + return newPorts +} + +// compareOCICNIPorts will sort the ocicni ports by +// 1) host ip +// 2) protocol +// 3) hostPort +// 4) container port +func compareOCICNIPorts(i, j types.OCICNIPortMapping) bool { + if i.HostIP != j.HostIP { + return i.HostIP < j.HostIP + } + + if i.Protocol != j.Protocol { + return i.Protocol < j.Protocol + } + + if i.HostPort != j.HostPort { + return i.HostPort < j.HostPort + } + + return i.ContainerPort < j.ContainerPort +} diff --git a/vendor/github.com/containers/podman/v5/libpod/networking_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/networking_freebsd.go new file mode 100644 index 00000000..76b18923 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/networking_freebsd.go @@ -0,0 +1,231 @@ +//go:build !remote + +package libpod + +import ( + "crypto/rand" + jdec "encoding/json" + "errors" + "fmt" + "net" + "os/exec" + + "github.com/containers/buildah/pkg/jail" + "github.com/containers/podman/v5/libpod/define" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" +) + +type Netstat struct { + Statistics NetstatInterface `json:"statistics"` +} + +type NetstatInterface struct { + Interface []NetstatAddress `json:"interface"` +} + +type NetstatAddress struct { + Name string `json:"name"` + Flags string `json:"flags"` + Mtu int `json:"mtu"` + Network string `json:"network"` + Address string `json:"address"` + + ReceivedPackets uint64 `json:"received-packets"` + ReceivedBytes uint64 `json:"received-bytes"` + ReceivedErrors uint64 `json:"received-errors"` + + SentPackets uint64 `json:"sent-packets"` + SentBytes uint64 `json:"sent-bytes"` + SentErrors uint64 `json:"send-errors"` + + DroppedPackets uint64 `json:"dropped-packets"` + + Collisions uint64 `json:"collisions"` +} + +func getSlirp4netnsIP(_ *net.IPNet) (*net.IP, error) { + return nil, errors.New("not implemented GetSlirp4netnsIP") +} + +// This is called after the container's jail is created but before its +// started. We can use this to initialise the container's vnet when we don't +// have a separate vnet jail (which is the case in FreeBSD 13.3 and later). +func (r *Runtime) setupNetNS(ctr *Container) error { + networkStatus, err := r.configureNetNS(ctr, ctr.ID()) + ctr.state.NetNS = ctr.ID() + ctr.state.NetworkStatus = networkStatus + return err +} + +// Create and configure a new network namespace for a container +func (r *Runtime) configureNetNS(ctr *Container, ctrNS string) (status map[string]types.StatusBlock, rerr error) { + if err := r.exposeMachinePorts(ctr.config.PortMappings); err != nil { + return nil, err + } + defer func() { + // make sure to unexpose the gvproxy ports when an error happens + if rerr != nil { + if err := r.unexposeMachinePorts(ctr.config.PortMappings); err != nil { + logrus.Errorf("failed to free gvproxy machine ports: %v", err) + } + } + }() + networks, err := ctr.networks() + if err != nil { + return nil, err + } + // All networks have been removed from the container. + // This is effectively forcing net=none. + if len(networks) == 0 { + return nil, nil + } + + netOpts := ctr.getNetworkOptions(networks) + netStatus, err := r.setUpNetwork(ctrNS, netOpts) + if err != nil { + return nil, err + } + + return netStatus, err +} + +// Create and configure a new network namespace for a container +func (r *Runtime) createNetNS(ctr *Container) (n string, q map[string]types.StatusBlock, retErr error) { + b := make([]byte, 16) + _, err := rand.Reader.Read(b) + if err != nil { + return "", nil, fmt.Errorf("failed to generate random vnet name: %v", err) + } + netns := fmt.Sprintf("vnet-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) + + jconf := jail.NewConfig() + jconf.Set("name", netns) + jconf.Set("vnet", jail.NEW) + jconf.Set("children.max", 1) + jconf.Set("persist", true) + jconf.Set("enforce_statfs", 0) + jconf.Set("devfs_ruleset", 4) + jconf.Set("allow.raw_sockets", true) + jconf.Set("allow.chflags", true) + jconf.Set("securelevel", -1) + j, err := jail.Create(jconf) + if err != nil { + return "", nil, fmt.Errorf("failed to create vnet jail %s for container %s: %w", netns, ctr.ID(), err) + } + + logrus.Debugf("Created vnet jail %s for container %s", netns, ctr.ID()) + + var networkStatus map[string]types.StatusBlock + networkStatus, err = r.configureNetNS(ctr, netns) + if err != nil { + jconf := jail.NewConfig() + jconf.Set("persist", false) + if err := j.Set(jconf); err != nil { + // Log this error and return the error from configureNetNS + logrus.Errorf("failed to destroy vnet jail %s: %v", netns, err) + } + } + return netns, networkStatus, err +} + +// Tear down a network namespace, undoing all state associated with it. +func (r *Runtime) teardownNetNS(ctr *Container) error { + if err := r.unexposeMachinePorts(ctr.config.PortMappings); err != nil { + // do not return an error otherwise we would prevent network cleanup + logrus.Errorf("failed to free gvproxy machine ports: %v", err) + } + if err := r.teardownNetwork(ctr); err != nil { + return err + } + + if ctr.state.NetNS != "" { + // If PostConfigureNetNS is false, then we are running with a + // separate vnet jail so we need to clean that up now. + if !ctr.config.PostConfigureNetNS { + // Rather than destroying the jail immediately, reset the + // persist flag so that it will live until the container is + // done. + netjail, err := jail.FindByName(ctr.state.NetNS) + if err != nil { + return fmt.Errorf("finding network jail %s: %w", ctr.state.NetNS, err) + } + jconf := jail.NewConfig() + jconf.Set("persist", false) + if err := netjail.Set(jconf); err != nil { + return fmt.Errorf("releasing network jail %s: %w", ctr.state.NetNS, err) + } + } + ctr.state.NetNS = "" + } + return nil +} + +// TODO (5.0): return the statistics per network interface +// This would allow better compat with docker. +func getContainerNetIO(ctr *Container) (map[string]define.ContainerNetworkStats, error) { + if ctr.state.NetNS == "" { + // If NetNS is nil, it was set as none, and no netNS + // was set up this is a valid state and thus return no + // error, nor any statistics + return nil, nil + } + + // First try running 'netstat -j' - this lets us retrieve stats from + // containers which don't have a separate vnet jail. + cmd := exec.Command("netstat", "-j", ctr.state.NetNS, "-bi", "--libxo", "json") + out, err := cmd.Output() + if err != nil { + // Fall back to using jexec so that this still works on 13.2 + // which does not have the -j flag. + cmd := exec.Command("jexec", ctr.state.NetNS, "netstat", "-bi", "--libxo", "json") + out, err = cmd.Output() + } + if err != nil { + return nil, fmt.Errorf("failed to read network stats: %v", err) + } + stats := Netstat{} + if err := jdec.Unmarshal(out, &stats); err != nil { + return nil, err + } + + res := make(map[string]define.ContainerNetworkStats) + + // Sum all the interface stats - in practice only Tx/TxBytes are needed + for _, ifaddr := range stats.Statistics.Interface { + // Each interface has two records, one for link-layer which has + // an MTU field and one for IP which doesn't. We only want the + // link-layer stats. + // + // It's not clear if we should include loopback stats here but + // if we move to per-interface stats in future, this can be + // reported separately. + if ifaddr.Mtu > 0 { + linkStats := define.ContainerNetworkStats{ + RxPackets: ifaddr.ReceivedPackets, + TxPackets: ifaddr.SentPackets, + RxBytes: ifaddr.ReceivedBytes, + TxBytes: ifaddr.SentBytes, + RxErrors: ifaddr.ReceivedErrors, + TxErrors: ifaddr.SentErrors, + RxDropped: ifaddr.DroppedPackets, + } + res[ifaddr.Name] = linkStats + } + } + + return res, nil +} + +func (c *Container) joinedNetworkNSPath() (string, bool) { + return c.state.NetNS, false +} + +func (c *Container) inspectJoinedNetworkNS(_ string) (q types.StatusBlock, retErr error) { + // TODO: extract interface information from the vnet jail + return types.StatusBlock{}, nil +} + +func (c *Container) reloadRootlessRLKPortMapping() error { + return errors.New("unsupported (*Container).reloadRootlessRLKPortMapping") +} diff --git a/vendor/github.com/containers/podman/v5/libpod/networking_linux.go b/vendor/github.com/containers/podman/v5/libpod/networking_linux.go new file mode 100644 index 00000000..7c58936b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/networking_linux.go @@ -0,0 +1,278 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "net" + + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/netns" +) + +// Create and configure a new network namespace for a container +func (r *Runtime) configureNetNS(ctr *Container, ctrNS string) (status map[string]types.StatusBlock, rerr error) { + if err := r.exposeMachinePorts(ctr.config.PortMappings); err != nil { + return nil, err + } + defer func() { + // make sure to unexpose the gvproxy ports when an error happens + if rerr != nil { + if err := r.unexposeMachinePorts(ctr.config.PortMappings); err != nil { + logrus.Errorf("failed to free gvproxy machine ports: %v", err) + } + } + }() + if ctr.config.NetMode.IsSlirp4netns() { + return nil, r.setupSlirp4netns(ctr, ctrNS) + } + if ctr.config.NetMode.IsPasta() { + return nil, r.setupPasta(ctr, ctrNS) + } + networks, err := ctr.networks() + if err != nil { + return nil, err + } + // All networks have been removed from the container. + // This is effectively forcing net=none. + if len(networks) == 0 { + return nil, nil + } + + netOpts := ctr.getNetworkOptions(networks) + netStatus, err := r.setUpNetwork(ctrNS, netOpts) + if err != nil { + return nil, err + } + defer func() { + // do not forget to tear down the netns when a later error happened. + if rerr != nil { + if err := r.teardownNetworkBackend(ctrNS, netOpts); err != nil { + logrus.Warnf("failed to teardown network after failed setup: %v", err) + } + } + }() + + // set up rootless port forwarder when rootless with ports and the network status is empty, + // if this is called from network reload the network status will not be empty and we should + // not set up port because they are still active + if rootless.IsRootless() && len(ctr.config.PortMappings) > 0 && ctr.getNetworkStatus() == nil { + // set up port forwarder for rootless netns + // TODO: support slirp4netns port forwarder as well + // make sure to fix this in container.handleRestartPolicy() as well + // Important we have to call this after r.setUpNetwork() so that + // we can use the proper netStatus + err = r.setupRootlessPortMappingViaRLK(ctr, ctrNS, netStatus) + } + return netStatus, err +} + +// Create and configure a new network namespace for a container +func (r *Runtime) createNetNS(ctr *Container) (n string, q map[string]types.StatusBlock, retErr error) { + ctrNS, err := netns.NewNS() + if err != nil { + return "", nil, fmt.Errorf("creating network namespace for container %s: %w", ctr.ID(), err) + } + defer func() { + if retErr != nil { + if err := netns.UnmountNS(ctrNS.Path()); err != nil { + logrus.Errorf("Unmounting partially created network namespace for container %s: %v", ctr.ID(), err) + } + if err := ctrNS.Close(); err != nil { + logrus.Errorf("Closing partially created network namespace for container %s: %v", ctr.ID(), err) + } + } + }() + + logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID()) + + var networkStatus map[string]types.StatusBlock + networkStatus, err = r.configureNetNS(ctr, ctrNS.Path()) + return ctrNS.Path(), networkStatus, err +} + +// Configure the network namespace using the container process +func (r *Runtime) setupNetNS(ctr *Container) error { + nsProcess := fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID) + nsPath, err := netns.NewNSFrom(nsProcess) + if err != nil { + return err + } + + networkStatus, err := r.configureNetNS(ctr, nsPath) + + // Assign NetNS attributes to container + ctr.state.NetNS = nsPath + ctr.state.NetworkStatus = networkStatus + return err +} + +// Tear down a network namespace, undoing all state associated with it. +func (r *Runtime) teardownNetNS(ctr *Container) error { + if err := r.unexposeMachinePorts(ctr.config.PortMappings); err != nil { + // do not return an error otherwise we would prevent network cleanup + logrus.Errorf("failed to free gvproxy machine ports: %v", err) + } + + // Do not check the error here, we want to always umount the netns + // This will ensure that the container interface will be deleted + // even when there is a CNI or netavark bug. + prevErr := r.teardownNetwork(ctr) + + // First unmount the namespace + if err := netns.UnmountNS(ctr.state.NetNS); err != nil { + if prevErr != nil { + logrus.Error(prevErr) + } + return fmt.Errorf("unmounting network namespace for container %s: %w", ctr.ID(), err) + } + + ctr.state.NetNS = "" + + return prevErr +} + +func getContainerNetNS(ctr *Container) (string, *Container, error) { + if ctr.state.NetNS != "" { + return ctr.state.NetNS, nil, nil + } + if ctr.config.NetNsCtr != "" { + c, err := ctr.runtime.GetContainer(ctr.config.NetNsCtr) + if err != nil { + return "", nil, err + } + if err = c.syncContainer(); err != nil { + return "", c, err + } + netNs, c2, err := getContainerNetNS(c) + if c2 != nil { + c = c2 + } + return netNs, c, err + } + return "", nil, nil +} + +// Returns a map of interface name to statistics for that interface. +func getContainerNetIO(ctr *Container) (map[string]define.ContainerNetworkStats, error) { + perNetworkStats := make(map[string]define.ContainerNetworkStats) + + netNSPath, _, netPathErr := getContainerNetNS(ctr) + if netPathErr != nil { + return nil, netPathErr + } + if netNSPath == "" { + // If netNSPath is empty, it was set as none, and no netNS was set up + // this is a valid state and thus return no error, nor any statistics + return nil, nil + } + + err := ns.WithNetNSPath(netNSPath, func(_ ns.NetNS) error { + links, err := netlink.LinkList() + if err != nil { + return fmt.Errorf("retrieving all network interfaces: %w", err) + } + for _, link := range links { + attributes := link.Attrs() + if attributes.Flags&net.FlagLoopback != 0 { + continue + } + + if attributes.Statistics != nil { + perNetworkStats[attributes.Name] = getNetStatsFromNetlinkStats(attributes.Statistics) + } + } + return nil + }) + return perNetworkStats, err +} + +func getNetStatsFromNetlinkStats(stats *netlink.LinkStatistics) define.ContainerNetworkStats { + return define.ContainerNetworkStats{ + RxBytes: stats.RxBytes, + RxDropped: stats.RxDropped, + RxErrors: stats.RxErrors, + RxPackets: stats.RxPackets, + TxBytes: stats.TxBytes, + TxDropped: stats.TxDropped, + TxErrors: stats.TxErrors, + TxPackets: stats.TxPackets, + } +} + +// joinedNetworkNSPath returns netns path and bool if netns was set +func (c *Container) joinedNetworkNSPath() (string, bool) { + for _, namespace := range c.config.Spec.Linux.Namespaces { + if namespace.Type == specs.NetworkNamespace { + return namespace.Path, true + } + } + return "", false +} + +func (c *Container) inspectJoinedNetworkNS(networkns string) (q types.StatusBlock, retErr error) { + var result types.StatusBlock + err := ns.WithNetNSPath(networkns, func(_ ns.NetNS) error { + ifaces, err := net.Interfaces() + if err != nil { + return err + } + routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) + if err != nil { + return err + } + var gateway net.IP + for _, route := range routes { + // add default gateway + // Dst is set to 0.0.0.0/0 or ::/0 which is the default route + if route.Dst != nil && route.Dst.IP.IsUnspecified() { + ones, _ := route.Dst.Mask.Size() + if ones == 0 { + gateway = route.Gw + } + } + } + result.Interfaces = make(map[string]types.NetInterface) + for _, iface := range ifaces { + if iface.Flags&net.FlagLoopback != 0 { + continue + } + addrs, err := iface.Addrs() + if err != nil { + continue + } + if len(addrs) == 0 { + continue + } + subnets := make([]types.NetAddress, 0, len(addrs)) + for _, address := range addrs { + if ipnet, ok := address.(*net.IPNet); ok { + if ipnet.IP.IsLinkLocalMulticast() || ipnet.IP.IsLinkLocalUnicast() { + continue + } + subnet := types.NetAddress{ + IPNet: types.IPNet{ + IPNet: *ipnet, + }, + } + if ipnet.Contains(gateway) { + subnet.Gateway = gateway + } + subnets = append(subnets, subnet) + } + } + result.Interfaces[iface.Name] = types.NetInterface{ + Subnets: subnets, + MacAddress: types.HardwareAddr(iface.HardwareAddr), + } + } + return nil + }) + return result, err +} diff --git a/vendor/github.com/containers/podman/v5/libpod/networking_machine.go b/vendor/github.com/containers/podman/v5/libpod/networking_machine.go new file mode 100644 index 00000000..0cd1a081 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/networking_machine.go @@ -0,0 +1,134 @@ +//go:build !remote + +package libpod + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "strconv" + "strings" + "time" + + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/machine" +) + +const machineGvproxyEndpoint = "gateway.containers.internal" + +// machineExpose is the struct for the gvproxy port forwarding api send via json +type machineExpose struct { + // Local is the local address on the vm host, format is ip:port + Local string `json:"local"` + // Remote is used to specify the vm ip:port + Remote string `json:"remote,omitempty"` + // Protocol to forward, tcp or udp + Protocol string `json:"protocol"` +} + +func requestMachinePorts(expose bool, ports []types.PortMapping) error { + url := "http://" + machineGvproxyEndpoint + "/services/forwarder/" + if expose { + url += "expose" + } else { + url += "unexpose" + } + ctx := context.Background() + client := &http.Client{ + Transport: &http.Transport{ + // make sure to not set a proxy here so explicitly ignore the proxy + // since we want to talk directly to gvproxy + // https://github.com/containers/podman/issues/13628 + Proxy: nil, + MaxIdleConns: 50, + IdleConnTimeout: 30 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }, + } + buf := new(bytes.Buffer) + for num, port := range ports { + protocols := strings.SplitSeq(port.Protocol, ",") + for protocol := range protocols { + for i := uint16(0); i < port.Range; i++ { + machinePort := machineExpose{ + Local: net.JoinHostPort(port.HostIP, strconv.FormatInt(int64(port.HostPort+i), 10)), + Protocol: protocol, + } + if expose { + // only set the remote port the ip will be automatically be set by gvproxy + machinePort.Remote = ":" + strconv.FormatInt(int64(port.HostPort+i), 10) + } + + // post request + if err := json.NewEncoder(buf).Encode(machinePort); err != nil { + if expose { + // in case of an error make sure to unexpose the other ports + if cerr := requestMachinePorts(false, ports[:num]); cerr != nil { + logrus.Errorf("failed to free gvproxy machine ports: %v", cerr) + } + } + return err + } + if err := makeMachineRequest(ctx, client, url, buf); err != nil { + if expose { + // in case of an error make sure to unexpose the other ports + if cerr := requestMachinePorts(false, ports[:num]); cerr != nil { + logrus.Errorf("failed to free gvproxy machine ports: %v", cerr) + } + } + return err + } + buf.Reset() + } + } + } + return nil +} + +func makeMachineRequest(ctx context.Context, client *http.Client, url string, buf io.Reader) error { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, buf) + if err != nil { + return err + } + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return annotateGvproxyResponseError(resp.Body) + } + return nil +} + +func annotateGvproxyResponseError(r io.Reader) error { + b, err := io.ReadAll(r) + if err == nil && len(b) > 0 { + return fmt.Errorf("something went wrong with the request: %q", string(b)) + } + return errors.New("something went wrong with the request, could not read response") +} + +// exposeMachinePorts exposes the ports for podman machine via gvproxy +func (r *Runtime) exposeMachinePorts(ports []types.PortMapping) error { + if !machine.IsGvProxyBased() { + return nil + } + return requestMachinePorts(true, ports) +} + +// unexposeMachinePorts closes the ports for podman machine via gvproxy +func (r *Runtime) unexposeMachinePorts(ports []types.PortMapping) error { + if !machine.IsGvProxyBased() { + return nil + } + return requestMachinePorts(false, ports) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/networking_pasta_linux.go b/vendor/github.com/containers/podman/v5/libpod/networking_pasta_linux.go new file mode 100644 index 00000000..af173e8e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/networking_pasta_linux.go @@ -0,0 +1,26 @@ +//go:build !remote + +// SPDX-License-Identifier: Apache-2.0 +// +// networking_pasta_linux.go - Start pasta(1) for user-mode connectivity +// +// Copyright (c) 2022 Red Hat GmbH +// Author: Stefano Brivio + +package libpod + +import "go.podman.io/common/libnetwork/pasta" + +func (r *Runtime) setupPasta(ctr *Container, netns string) error { + res, err := pasta.Setup(&pasta.SetupOptions{ + Config: r.config, + Netns: netns, + Ports: ctr.convertPortMappings(), + ExtraOptions: ctr.config.NetworkOptions[pasta.BinaryName], + }) + if err != nil { + return err + } + ctr.pastaResult = res + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/networking_slirp4netns.go b/vendor/github.com/containers/podman/v5/libpod/networking_slirp4netns.go new file mode 100644 index 00000000..9685f850 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/networking_slirp4netns.go @@ -0,0 +1,106 @@ +//go:build !remote && linux + +package libpod + +import ( + "fmt" + "io" + "net" + "os" + "path/filepath" + + "github.com/containers/podman/v5/pkg/errorhandling" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/slirp4netns" + "go.podman.io/common/libnetwork/types" +) + +// setupSlirp4netns can be called in rootful as well as in rootless +func (r *Runtime) setupSlirp4netns(ctr *Container, netns string) error { + ports := ctr.convertPortMappings() + + if !ctr.config.PostConfigureNetNS { + var err error + ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() + if err != nil { + return fmt.Errorf("failed to create rootless network sync pipe: %w", err) + } + if len(ports) > 0 { + ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe() + if err != nil { + return fmt.Errorf("failed to create rootless port sync pipe: %w", err) + } + } + } + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) + if ctr.rootlessPortSyncR != nil { + defer errorhandling.CloseQuiet(ctr.rootlessPortSyncR) + } + + res, err := slirp4netns.Setup(&slirp4netns.SetupOptions{ + Config: r.config, + ContainerID: ctr.ID(), + Netns: netns, + Ports: ports, + ExtraOptions: ctr.config.NetworkOptions[slirp4netns.BinaryName], + Slirp4netnsExitPipeR: ctr.rootlessSlirpSyncR, + RootlessPortExitPipeR: ctr.rootlessPortSyncR, + }) + if err != nil { + return err + } + ctr.slirp4netnsSubnet = res.Subnet + return nil +} + +func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath string, netStatus map[string]types.StatusBlock) error { + var err error + if !ctr.config.PostConfigureNetNS { + ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe() + if err != nil { + return fmt.Errorf("failed to create rootless port sync pipe: %w", err) + } + } + defer errorhandling.CloseQuiet(ctr.rootlessPortSyncR) + return slirp4netns.SetupRootlessPortMappingViaRLK(&slirp4netns.SetupOptions{ + Config: r.config, + ContainerID: ctr.ID(), + Netns: netnsPath, + Ports: ctr.convertPortMappings(), + RootlessPortExitPipeR: ctr.rootlessPortSyncR, + }, nil, netStatus) +} + +// reloadRootlessRLKPortMapping will trigger a reload for the port mappings in the rootlessport process. +// This should only be called by network connect/disconnect and only as rootless. +func (c *Container) reloadRootlessRLKPortMapping() error { + if len(c.config.PortMappings) == 0 { + return nil + } + childIP := slirp4netns.GetRootlessPortChildIP(nil, c.state.NetworkStatus) + logrus.Debugf("reloading rootless ports for container %s, childIP is %s", c.config.ID, childIP) + + conn, err := openUnixSocket(filepath.Join(c.runtime.config.Engine.TmpDir, "rp", c.config.ID)) + if err != nil { + return fmt.Errorf("could not reload rootless port mappings, port forwarding may no longer work correctly: %w", err) + } + defer conn.Close() + enc := json.NewEncoder(conn) + err = enc.Encode(childIP) + if err != nil { + return fmt.Errorf("port reloading failed: %w", err) + } + b, err := io.ReadAll(conn) + if err != nil { + return fmt.Errorf("port reloading failed: %w", err) + } + data := string(b) + if data != "OK" { + return fmt.Errorf("port reloading failed: %s", data) + } + return nil +} + +func getSlirp4netnsIP(subnet *net.IPNet) (*net.IP, error) { + return slirp4netns.GetIP(subnet) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci.go b/vendor/github.com/containers/podman/v5/libpod/oci.go new file mode 100644 index 00000000..75c1ce68 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci.go @@ -0,0 +1,242 @@ +//go:build !remote + +package libpod + +import ( + "net/http" + + "github.com/containers/podman/v5/libpod/define" + "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/common/pkg/resize" +) + +// OCIRuntime is an implementation of an OCI runtime. +// The OCI runtime implementation is expected to be a fairly thin wrapper around +// the actual runtime, and is not expected to include things like state +// management logic - e.g., we do not expect it to determine on its own that +// calling 'UnpauseContainer()' on a container that is not paused is an error. +// The code calling the OCIRuntime will manage this. +// TODO: May want to move the conmon cleanup code here - it depends on +// Conmon being in use. +type OCIRuntime interface { //nolint:interfacebloat + // Name returns the name of the runtime. + Name() string + // Path returns the path to the runtime executable. + Path() string + + // CreateContainer creates the container in the OCI runtime. + // The returned int64 contains the microseconds needed to restore + // the given container if it is a restore and if restoreOptions.PrintStats + // is true. In all other cases the returned int64 is 0. + CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) + // StartContainer starts the given container. + StartContainer(ctr *Container) error + // KillContainer sends the given signal to the given container. + // If all is set, all processes in the container will be signalled; + // otherwise, only init will be signalled. + KillContainer(ctr *Container, signal uint, all bool) error + // StopContainer stops the given container. + // The container's stop signal (or SIGTERM if unspecified) will be sent + // first. + // After the given timeout, SIGKILL will be sent. + // If the given timeout is 0, SIGKILL will be sent immediately, and the + // stop signal will be omitted. + // If all is set, we will attempt to use the --all flag will `kill` in + // the OCI runtime to kill all processes in the container, including + // exec sessions. This is only supported if the container has cgroups. + StopContainer(ctr *Container, timeout uint, all bool) error + // DeleteContainer deletes the given container from the OCI runtime. + DeleteContainer(ctr *Container) error + // PauseContainer pauses the given container. + PauseContainer(ctr *Container) error + // UnpauseContainer unpauses the given container. + UnpauseContainer(ctr *Container) error + + // Attach to a container. + Attach(ctr *Container, params *AttachOptions) error + // HTTPAttach performs an attach intended to be transported over HTTP. + // For terminal attach, the container's output will be directly streamed + // to output; otherwise, STDOUT and STDERR will be multiplexed, with + // a header prepended as follows: 1-byte STREAM (0, 1, 2 for STDIN, + // STDOUT, STDERR), 3 null (0x00) bytes, 4-byte big endian length. + // If a cancel channel is provided, it can be used to asynchronously + // terminate the attach session. Detach keys, if given, will also cause + // the attach session to be terminated if provided via the STDIN + // channel. If they are not provided, the default detach keys will be + // used instead. Detach keys of "" will disable detaching via keyboard. + // The streams parameter will determine which streams to forward to the + // client. + HTTPAttach(ctr *Container, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, hijackDone chan<- bool, streamAttach, streamLogs bool) error + // AttachResize resizes the terminal in use by the given container. + AttachResize(ctr *Container, newSize resize.TerminalSize) error + + // ExecContainer executes a command in a running container. + // Returns an int (PID of exec session), error channel (errors from + // attach), and error (errors that occurred attempting to start the exec + // session). This returns once the exec session is running - not once it + // has completed, as one might expect. The attach session will remain + // running, in a goroutine that will return via the chan error in the + // return signature. + // newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty + ExecContainer(ctr *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams, newSize *resize.TerminalSize) (int, chan error, error) + // ExecContainerHTTP executes a command in a running container and + // attaches its standard streams to a provided hijacked HTTP session. + // Maintains the same invariants as ExecContainer (returns on session + // start, with a goroutine running in the background to handle attach). + // The HTTP attach itself maintains the same invariants as HTTPAttach. + // newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty + ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, r *http.Request, w http.ResponseWriter, + streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, newSize *resize.TerminalSize) (int, chan error, error) + // ExecContainerDetached executes a command in a running container, but + // does not attach to it. Returns the PID of the exec session and an + // error (if starting the exec session failed) + ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) + // ExecAttachResize resizes the terminal of a running exec session. Only + // allowed with sessions that were created with a TTY. + ExecAttachResize(ctr *Container, sessionID string, newSize resize.TerminalSize) error + // ExecStopContainer stops a given exec session in a running container. + // SIGTERM with be sent initially, then SIGKILL after the given timeout. + // If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will + // be omitted. + ExecStopContainer(ctr *Container, sessionID string, timeout uint) error + // ExecUpdateStatus checks the status of a given exec session. + // Returns true if the session is still running, or false if it exited. + ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) + + // CheckpointContainer checkpoints the given container. + // Some OCI runtimes may not support this - if SupportsCheckpoint() + // returns false, this is not implemented, and will always return an + // error. If CheckpointOptions.PrintStats is true the first return parameter + // contains the number of microseconds the runtime needed to checkpoint + // the given container. + CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) (int64, error) + + // CheckConmonRunning verifies that the given container's Conmon + // instance is still running. Runtimes without Conmon, or systems where + // the PID of conmon is not available, should mock this as True. + // True indicates that Conmon for the instance is running, False + // indicates it is not. + CheckConmonRunning(ctr *Container) (bool, error) + + // SupportsCheckpoint returns whether this OCI runtime + // implementation supports the CheckpointContainer() operation. + SupportsCheckpoint() bool + // SupportsJSONErrors is whether the runtime can return JSON-formatted + // error messages. + SupportsJSONErrors() bool + // SupportsNoCgroups is whether the runtime supports running containers + // without cgroups. + SupportsNoCgroups() bool + // SupportsKVM os whether the OCI runtime supports running containers + // without KVM separation + SupportsKVM() bool + + // AttachSocketPath is the path to the socket to attach to a given + // container. + // TODO: If we move Attach code in here, this should be made internal. + // We don't want to force all runtimes to share the same attach + // implementation. + AttachSocketPath(ctr *Container) (string, error) + // ExecAttachSocketPath is the path to the socket to attach to a given + // exec session in the given container. + // TODO: Probably should be made internal. + ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) + // ExitFilePath is the path to a container's exit file. + // All runtime implementations must create an exit file when containers + // exit, containing the exit code of the container (as a string). + // This is the path to that file for a given container. + ExitFilePath(ctr *Container) (string, error) + + // OOMFilePath is the path to a container's oom file if it was oom killed. + // An oom file is only created when the container is oom killed. The existence + // of this file means that the container was oom killed. + // This is the path to that file for a given container. + OOMFilePath(ctr *Container) (string, error) + + // PersistDirectoryPath is the path to a container's persist directory. + // Not all OCI runtime implementations will have a persist directory. + // If they do, it may contain files such as the exit file and the OOM + // file. + // If the directory does not exist, the empty string and no error should + // be returned. + PersistDirectoryPath(ctr *Container) (string, error) + + // RuntimeInfo returns verbose information about the runtime. + RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) + + // UpdateContainer updates the given container's cgroup configuration. + UpdateContainer(ctr *Container, res *specs.LinuxResources) error +} + +// AttachOptions are options used when attached to a container or an exec +// session. +type AttachOptions struct { + // Streams are the streams to attach to. + Streams *define.AttachStreams + // DetachKeys containers the key combination that will detach from the + // attach session. Empty string is assumed as no detach keys - user + // detach is impossible. If unset, defaults from containers.conf will be + // used. + DetachKeys *string + // InitialSize is the initial size of the terminal. Set before the + // attach begins. + InitialSize *resize.TerminalSize + // AttachReady signals when the attach has successfully completed and + // streaming has begun. + AttachReady chan<- bool + // Start indicates that the container should be started if it is not + // already running. + Start bool + // Started signals when the container has been successfully started. + // Required if Start is true, unused otherwise. + Started chan<- bool +} + +// ExecOptions are options passed into ExecContainer. They control the command +// that will be executed and how the exec will proceed. +type ExecOptions struct { + // Cmd is the command to execute. + Cmd []string + // Env is a set of environment variables to add to the container. + Env map[string]string + // Terminal is whether to create a new TTY for the exec session. + Terminal bool + // Cwd is the working directory for the executed command. If unset, the + // working directory of the container will be used. + Cwd string + // User is the user the command will be executed as. If unset, the user + // the container was run as will be used. + User string + // Streams are the streams that will be attached to the container. + Streams *define.AttachStreams + // PreserveFDs is a number of additional file descriptors (in addition + // to 0, 1, 2) that will be passed to the executed process. The total FDs + // passed will be 3 + PreserveFDs. + PreserveFDs uint + // PreserveFD is a list of additional file descriptors (in addition + // to 0, 1, 2) that will be passed to the executed process. + PreserveFD []uint + // DetachKeys is a set of keys that, when pressed in sequence, will + // detach from the container. + // If not provided, the default keys will be used. + // If provided but set to "", detaching from the container will be + // disabled. + DetachKeys *string + // ExitCommand is a command that will be run after the exec session + // exits. + ExitCommand []string + // ExitCommandDelay is a delay (in seconds) between the exec session + // exiting, and the exit command being invoked. + ExitCommandDelay uint + // Privileged indicates the execed process will be launched in Privileged mode + Privileged bool +} + +// HTTPAttachStreams informs the HTTPAttach endpoint which of the container's +// standard streams should be streamed to the client. If this is passed, at +// least one of the streams must be set to true. +type HTTPAttachStreams struct { + Stdin bool + Stdout bool + Stderr bool +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon.go new file mode 100644 index 00000000..c083246e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon.go @@ -0,0 +1,9 @@ +//go:build !remote + +package libpod + +const ( + // cgroupSplit is the cgroup mode for reusing the current cgroup both + // for conmon and for the container payload. + cgroupSplit = "split" +) diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon_attach_common.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_attach_common.go new file mode 100644 index 00000000..7debcaa7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_attach_common.go @@ -0,0 +1,317 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "errors" + "fmt" + "io" + "net" + "os" + "path/filepath" + "syscall" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/errorhandling" + "github.com/moby/term" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/detach" + "go.podman.io/common/pkg/resize" + "golang.org/x/sys/unix" +) + +/* Sync with stdpipe_t in conmon.c */ +const ( + AttachPipeStdin = 1 + AttachPipeStdout = 2 + AttachPipeStderr = 3 +) + +// Attach to the given container. +// Does not check if state is appropriate. +// started is only required if startContainer is true. +// It does not wait for the container to be healthy, it is the caller responsibility to do so. +func (r *ConmonOCIRuntime) Attach(c *Container, params *AttachOptions) error { + passthrough := c.LogDriver() == define.PassthroughLogging || c.LogDriver() == define.PassthroughTTYLogging + + if params == nil || params.Streams == nil { + return fmt.Errorf("must provide parameters to Attach: %w", define.ErrInternal) + } + + if !params.Streams.AttachOutput && !params.Streams.AttachError && !params.Streams.AttachInput && !passthrough { + return fmt.Errorf("must provide at least one stream to attach to: %w", define.ErrInvalidArg) + } + if params.Start && params.Started == nil { + return fmt.Errorf("started chan not passed when startContainer set: %w", define.ErrInternal) + } + + keys := config.DefaultDetachKeys + if params.DetachKeys != nil { + keys = *params.DetachKeys + } + + detachKeys, err := processDetachKeys(keys) + if err != nil { + return err + } + + var conn *net.UnixConn + if !passthrough { + logrus.Debugf("Attaching to container %s", c.ID()) + + // If we have a resize, do it. + if params.InitialSize != nil { + if err := r.AttachResize(c, *params.InitialSize); err != nil { + return err + } + } + + attachSock, err := c.AttachSocketPath() + if err != nil { + return err + } + + conn, err = openUnixSocket(attachSock) + if err != nil { + return fmt.Errorf("failed to connect to container's attach socket: %v: %w", attachSock, err) + } + defer func() { + if err := conn.Close(); err != nil { + logrus.Errorf("unable to close socket: %q", err) + } + }() + } + + // If starting was requested, start the container and notify when that's + // done. + if params.Start { + if err := c.start(); err != nil { + return err + } + params.Started <- true + } + close(params.Started) + + if passthrough { + return nil + } + + receiveStdoutError, stdinDone := setupStdioChannels(params.Streams, conn, detachKeys) + if params.AttachReady != nil { + params.AttachReady <- true + } + return readStdio(conn, params.Streams, receiveStdoutError, stdinDone) +} + +// Attach to the given container's exec session. +// +// attachFd and startFd must be open file descriptors. attachFd must be the +// output side of the fd and is used for two things: +// +// 1. conmon will first send a nonce value across the pipe indicating it has +// set up its side of the console socket this ensures attachToExec gets all of +// the output of the called process. +// +// 2. conmon will then send the exit code of the exec process, or an error in the exec session. +// +// startFd must be the input side of the fd. +// +// newSize resizes the tty to this size before the process is started, must be +// nil if the exec session has no tty +// +// conmon will wait to start the exec session until the parent process has set up the console socket. +// +// Once attachToExec successfully attaches to the console socket, the child +// conmon process responsible for calling runtime exec will read from the +// output side of start fd, thus learning to start the child process. +// +// Thus, the order goes as follow: +// 1. conmon parent process sets up its console socket. sends on attachFd +// 2. attachToExec attaches to the console socket after reading on attachFd and resizes the tty +// 3. child waits on startFd for attachToExec to attach to said console socket +// 4. attachToExec sends on startFd, signalling it has attached to the socket and child is ready to go +// 5. child receives on startFd, runs the runtime exec command +// attachToExec is responsible for closing startFd and attachFd +func (c *Container) attachToExec(streams *define.AttachStreams, keys *string, sessionID string, startFd, attachFd *os.File, newSize *resize.TerminalSize) error { + if !streams.AttachOutput && !streams.AttachError && !streams.AttachInput { + return fmt.Errorf("must provide at least one stream to attach to: %w", define.ErrInvalidArg) + } + if startFd == nil || attachFd == nil { + return fmt.Errorf("start sync pipe and attach sync pipe must be defined for exec attach: %w", define.ErrInvalidArg) + } + + defer errorhandling.CloseQuiet(startFd) + defer errorhandling.CloseQuiet(attachFd) + + detachString := config.DefaultDetachKeys + if keys != nil { + detachString = *keys + } + detachKeys, err := processDetachKeys(detachString) + if err != nil { + return err + } + + logrus.Debugf("Attaching to container %s exec session %s", c.ID(), sessionID) + + // set up the socket path, such that it is the correct length and location for exec + sockPath, err := c.execAttachSocketPath(sessionID) + if err != nil { + return err + } + + // 2: read from attachFd that the parent process has set up the console socket + if _, err := readConmonPipeData(c.ociRuntime.Name(), attachFd, ""); err != nil { + return err + } + + // resize before we start the container process + if newSize != nil { + err = c.ociRuntime.ExecAttachResize(c, sessionID, *newSize) + if err != nil { + logrus.Warnf("Resize failed: %v", err) + } + } + + // 2: then attach + conn, err := openUnixSocket(sockPath) + if err != nil { + return fmt.Errorf("failed to connect to container's attach socket: %v: %w", sockPath, err) + } + defer func() { + if err := conn.Close(); err != nil { + logrus.Errorf("Unable to close socket: %q", err) + } + }() + + // start listening on stdio of the process + receiveStdoutError, stdinDone := setupStdioChannels(streams, conn, detachKeys) + + // 4: send start message to child + if err := writeConmonPipeData(startFd); err != nil { + return err + } + + return readStdio(conn, streams, receiveStdoutError, stdinDone) +} + +func processDetachKeys(keys string) ([]byte, error) { + // Check the validity of the provided keys first + if len(keys) == 0 { + return []byte{}, nil + } + detachKeys, err := term.ToBytes(keys) + if err != nil { + return nil, fmt.Errorf("invalid detach keys: %w", err) + } + return detachKeys, nil +} + +func registerResizeFunc(r <-chan resize.TerminalSize, bundlePath string) { + resize.HandleResizing(r, func(size resize.TerminalSize) { + controlPath := filepath.Join(bundlePath, "ctl") + controlFile, err := os.OpenFile(controlPath, unix.O_WRONLY, 0) + if err != nil { + logrus.Debugf("Could not open ctl file: %v", err) + return + } + defer controlFile.Close() + + logrus.Debugf("Received a resize event: %+v", size) + if _, err = fmt.Fprintf(controlFile, "%d %d %d\n", 1, size.Height, size.Width); err != nil { + logrus.Warnf("Failed to write to control file to resize terminal: %v", err) + } + }) +} + +func setupStdioChannels(streams *define.AttachStreams, conn *net.UnixConn, detachKeys []byte) (chan error, chan error) { + receiveStdoutError := make(chan error) + go func() { + receiveStdoutError <- redirectResponseToOutputStreams(streams.OutputStream, streams.ErrorStream, streams.AttachOutput, streams.AttachError, conn) + }() + + stdinDone := make(chan error) + go func() { + var err error + if streams.AttachInput { + _, err = detach.Copy(conn, streams.InputStream, detachKeys) + } + stdinDone <- err + }() + + return receiveStdoutError, stdinDone +} + +func redirectResponseToOutputStreams(outputStream, errorStream io.Writer, writeOutput, writeError bool, conn io.Reader) error { + var err error + buf := make([]byte, 8192+1) /* Sync with conmon STDIO_BUF_SIZE */ + for { + nr, er := conn.Read(buf) + if nr > 0 { + var dst io.Writer + var doWrite bool + switch buf[0] { + case AttachPipeStdout: + dst = outputStream + doWrite = writeOutput + case AttachPipeStderr: + dst = errorStream + doWrite = writeError + default: + logrus.Infof("Received unexpected attach type %+d", buf[0]) + } + if dst == nil { + return errors.New("output destination cannot be nil") + } + + if doWrite { + nw, ew := dst.Write(buf[1:nr]) + if ew != nil { + err = ew + break + } + if nr != nw+1 { + err = io.ErrShortWrite + break + } + } + } + if errors.Is(er, io.EOF) || errors.Is(er, syscall.ECONNRESET) { + break + } + if er != nil { + err = er + break + } + } + return err +} + +func readStdio(conn *net.UnixConn, streams *define.AttachStreams, receiveStdoutError, stdinDone chan error) error { + var err error + select { + case err = <-receiveStdoutError: + if err := socketCloseWrite(conn); err != nil { + logrus.Errorf("Failed to close stdin: %v", err) + } + return err + case err = <-stdinDone: + if err == define.ErrDetach { + if err := socketCloseWrite(conn); err != nil { + logrus.Errorf("Failed to close stdin: %v", err) + } + return err + } + if err == nil { + // copy stdin is done, close it + if connErr := socketCloseWrite(conn); connErr != nil { + logrus.Errorf("Unable to close conn: %v", connErr) + } + } + if streams.AttachOutput || streams.AttachError { + return <-receiveStdoutError + } + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon_attach_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_attach_freebsd.go new file mode 100644 index 00000000..cd5ccf8d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_attach_freebsd.go @@ -0,0 +1,23 @@ +//go:build !remote + +package libpod + +import ( + "net" + "os" + "path/filepath" +) + +func openUnixSocket(path string) (*net.UnixConn, error) { + // socket paths can be too long to fit into a sockaddr_un so we create a shorter symlink. + tmpdir, err := os.MkdirTemp("", "podman") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpdir) + tmpsockpath := filepath.Join(tmpdir, "sock") + if err := os.Symlink(path, tmpsockpath); err != nil { + return nil, err + } + return net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: tmpsockpath, Net: "unixpacket"}) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon_attach_linux.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_attach_linux.go new file mode 100644 index 00000000..10435ee5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_attach_linux.go @@ -0,0 +1,19 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "net" + + "golang.org/x/sys/unix" +) + +func openUnixSocket(path string) (*net.UnixConn, error) { + fd, err := unix.Open(path, unix.O_PATH, 0) + if err != nil { + return nil, err + } + defer unix.Close(fd) + return net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: fmt.Sprintf("/proc/self/fd/%d", fd), Net: "unixpacket"}) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon_common.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_common.go new file mode 100644 index 00000000..6bf3576c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_common.go @@ -0,0 +1,1635 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "bufio" + "bytes" + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "sync" + "syscall" + "text/template" + "time" + + conmonConfig "github.com/containers/conmon/runner/config" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/logs" + "github.com/containers/podman/v5/pkg/checkpoint/crutils" + "github.com/containers/podman/v5/pkg/errorhandling" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/specgenutil" + "github.com/containers/podman/v5/pkg/util" + "github.com/containers/podman/v5/utils" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/detach" + "go.podman.io/common/pkg/resize" + "go.podman.io/common/pkg/version" + "go.podman.io/storage/pkg/idtools" + "golang.org/x/sys/unix" +) + +const ( + // This is Conmon's STDIO_BUF_SIZE. I don't believe we have access to it + // directly from the Go code, so const it here + // Important: The conmon attach socket uses an extra byte at the beginning of each + // message to specify the STREAM so we have to increase the buffer size by one + bufferSize = conmonConfig.BufSize + 1 +) + +// ConmonOCIRuntime is an OCI runtime managed by Conmon. +// TODO: Make all calls to OCI runtime have a timeout. +type ConmonOCIRuntime struct { + name string + path string + conmonPath string + conmonEnv []string + tmpDir string + exitsDir string + logSizeMax int64 + noPivot bool + reservePorts bool + runtimeFlags []string + supportsJSON bool + supportsKVM bool + supportsNoCgroups bool + enableKeyring bool + persistDir string +} + +// Make a new Conmon-based OCI runtime with the given options. +// Conmon will wrap the given OCI runtime, which can be `runc`, `crun`, or +// any runtime with a runc-compatible CLI. +// The first path that points to a valid executable will be used. +// Deliberately private. Someone should not be able to construct this outside of +// libpod. +func newConmonOCIRuntime(name string, paths []string, conmonPath string, runtimeFlags []string, runtimeCfg *config.Config) (OCIRuntime, error) { + if name == "" { + return nil, fmt.Errorf("the OCI runtime must be provided a non-empty name: %w", define.ErrInvalidArg) + } + + // Make lookup tables for runtime support + supportsJSON := make(map[string]bool, len(runtimeCfg.Engine.RuntimeSupportsJSON.Get())) + supportsNoCgroups := make(map[string]bool, len(runtimeCfg.Engine.RuntimeSupportsNoCgroups.Get())) + supportsKVM := make(map[string]bool, len(runtimeCfg.Engine.RuntimeSupportsKVM.Get())) + for _, r := range runtimeCfg.Engine.RuntimeSupportsJSON.Get() { + supportsJSON[r] = true + } + for _, r := range runtimeCfg.Engine.RuntimeSupportsNoCgroups.Get() { + supportsNoCgroups[r] = true + } + for _, r := range runtimeCfg.Engine.RuntimeSupportsKVM.Get() { + supportsKVM[r] = true + } + + configIndex := filepath.Base(name) + + if len(runtimeFlags) == 0 { + for _, arg := range runtimeCfg.Engine.OCIRuntimesFlags[configIndex] { + runtimeFlags = append(runtimeFlags, "--"+arg) + } + } + + runtime := new(ConmonOCIRuntime) + runtime.name = name + runtime.conmonPath = conmonPath + runtime.runtimeFlags = runtimeFlags + + runtime.conmonEnv = runtimeCfg.Engine.ConmonEnvVars.Get() + runtime.tmpDir = runtimeCfg.Engine.TmpDir + runtime.logSizeMax = runtimeCfg.Containers.LogSizeMax + runtime.noPivot = runtimeCfg.Engine.NoPivotRoot + runtime.reservePorts = runtimeCfg.Engine.EnablePortReservation + runtime.enableKeyring = runtimeCfg.Containers.EnableKeyring + + // TODO: probe OCI runtime for feature and enable automatically if + // available. + + runtime.supportsJSON = supportsJSON[configIndex] + runtime.supportsNoCgroups = supportsNoCgroups[configIndex] + runtime.supportsKVM = supportsKVM[configIndex] + + foundPath := false + for _, path := range paths { + stat, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + continue + } + return nil, fmt.Errorf("cannot stat OCI runtime %s path: %w", name, err) + } + if !stat.Mode().IsRegular() { + continue + } + foundPath = true + logrus.Tracef("found runtime %q", path) + runtime.path = path + break + } + + // Search the $PATH as last fallback + if !foundPath { + if foundRuntime, err := exec.LookPath(name); err == nil { + foundPath = true + runtime.path = foundRuntime + logrus.Debugf("using runtime %q from $PATH: %q", name, foundRuntime) + } + } + + if !foundPath { + return nil, fmt.Errorf("no valid executable found for OCI runtime %s: %w", name, define.ErrInvalidArg) + } + + runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits") + // The persist-dir is where conmon writes the exit file and oom file (if oom killed), we join the container ID to this path later on + runtime.persistDir = filepath.Join(runtime.tmpDir, "persist") + + // Create the exit files and attach sockets directories + if err := os.MkdirAll(runtime.exitsDir, 0o750); err != nil { + return nil, fmt.Errorf("creating OCI runtime exit files directory: %w", err) + } + if err := os.MkdirAll(runtime.persistDir, 0o750); err != nil { + return nil, fmt.Errorf("creating OCI runtime persist directory: %w", err) + } + return runtime, nil +} + +// Name returns the name of the runtime being wrapped by Conmon. +func (r *ConmonOCIRuntime) Name() string { + return r.name +} + +// Path returns the path of the OCI runtime being wrapped by Conmon. +func (r *ConmonOCIRuntime) Path() string { + return r.path +} + +// hasCurrentUserMapped checks whether the current user is mapped inside the container user namespace +func hasCurrentUserMapped(ctr *Container) bool { + if len(ctr.config.IDMappings.UIDMap) == 0 && len(ctr.config.IDMappings.GIDMap) == 0 { + return true + } + containsID := func(id int, mappings []idtools.IDMap) bool { + for _, m := range mappings { + if id >= m.HostID && id < m.HostID+m.Size { + return true + } + } + return false + } + return containsID(os.Geteuid(), ctr.config.IDMappings.UIDMap) && containsID(os.Getegid(), ctr.config.IDMappings.GIDMap) +} + +// CreateContainer creates a container. +func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) { + if !hasCurrentUserMapped(ctr) || ctr.config.RootfsMapping != nil { + // if we are running a non privileged container, be sure to umount some kernel paths so they are not + // bind mounted inside the container at all. + hideFiles := !ctr.config.Privileged && !rootless.IsRootless() + return r.createRootlessContainer(ctr, restoreOptions, hideFiles) + } + return r.createOCIContainer(ctr, restoreOptions) +} + +// StartContainer starts the given container. +// Sets time the container was started, but does not save it. +func (r *ConmonOCIRuntime) StartContainer(ctr *Container) error { + // TODO: streams should probably *not* be our STDIN/OUT/ERR - redirect to buffers? + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + if path, ok := os.LookupEnv("PATH"); ok { + env = append(env, fmt.Sprintf("PATH=%s", path)) + } + if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, append(r.runtimeFlags, "start", ctr.ID())...); err != nil { + return err + } + + ctr.state.StartedTime = time.Now() + + return nil +} + +// UpdateContainer updates the given container's cgroup configuration +func (r *ConmonOCIRuntime) UpdateContainer(ctr *Container, resources *spec.LinuxResources) error { + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + if path, ok := os.LookupEnv("PATH"); ok { + env = append(env, fmt.Sprintf("PATH=%s", path)) + } + args := r.runtimeFlags + args = append(args, "update") + tempFile, additionalArgs, err := generateResourceFile(resources) + if err != nil { + return err + } + defer os.Remove(tempFile) + + args = append(args, additionalArgs...) + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, append(args, ctr.ID())...) +} + +func generateResourceFile(res *spec.LinuxResources) (string, []string, error) { + flags := []string{} + if res == nil { + return "", flags, nil + } + + f, err := os.CreateTemp("", "podman") + if err != nil { + return "", nil, err + } + defer f.Close() + + j, err := json.Marshal(res) + if err != nil { + return "", nil, err + } + _, err = f.Write(j) + if err != nil { + return "", nil, err + } + + flags = append(flags, "--resources="+f.Name()) + return f.Name(), flags, nil +} + +// KillContainer sends the given signal to the given container. +// If all is set, send to all PIDs in the container. +// All is only supported if the container created cgroups. +func (r *ConmonOCIRuntime) KillContainer(ctr *Container, signal uint, all bool) error { + if _, err := r.killContainer(ctr, signal, all, false); err != nil { + return err + } + + return nil +} + +// If captureStderr is requested, OCI runtime STDERR will be captured as a +// *bytes.buffer and returned; otherwise, it is set to os.Stderr. +// IMPORTANT: Thus function is called from an unlocked container state in +// the stop() code path so do not modify the state here. +func (r *ConmonOCIRuntime) killContainer(ctr *Container, signal uint, all, captureStderr bool) (*bytes.Buffer, error) { + logrus.Debugf("Sending signal %d to container %s", signal, ctr.ID()) + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return nil, err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + var args []string + args = append(args, r.runtimeFlags...) + if all { + args = append(args, "kill", "--all", ctr.ID(), strconv.FormatUint(uint64(signal), 10)) + } else { + args = append(args, "kill", ctr.ID(), strconv.FormatUint(uint64(signal), 10)) + } + var ( + stderr io.Writer = os.Stderr + stderrBuffer *bytes.Buffer + ) + if captureStderr { + stderrBuffer = new(bytes.Buffer) + stderr = stderrBuffer + } + if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, stderr, env, r.path, args...); err != nil { + rErr := err + // quick check if ctr pid is still alive + if err := unix.Kill(ctr.state.PID, 0); err == unix.ESRCH { + // pid already dead so signal sending fails logically, set error to ErrCtrStateInvalid + // This is needed for the ProxySignals() function which already ignores the error to not cause flakes + // when the ctr process just exited. + rErr = define.ErrCtrStateInvalid + } + return stderrBuffer, fmt.Errorf("sending signal to container %s: %w", ctr.ID(), rErr) + } + + return stderrBuffer, nil +} + +// StopContainer stops a container, first using its given stop signal (or +// SIGTERM if no signal was specified), then using SIGKILL. +// Timeout is given in seconds. If timeout is 0, the container will be +// immediately kill with SIGKILL. +// Does not set finished time for container, assumes you will run updateStatus +// after to pull the exit code. +// IMPORTANT: Thus function is called from an unlocked container state in +// the stop() code path so do not modify the state here. +func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, all bool) error { + logrus.Debugf("Stopping container %s (PID %d)", ctr.ID(), ctr.state.PID) + + // Ping the container to see if it's alive + // If it's not, it's already stopped, return + err := unix.Kill(ctr.state.PID, 0) + if err == unix.ESRCH { + return nil + } + + killCtr := func(signal uint) (bool, error) { + stderr, err := r.killContainer(ctr, signal, all, true) + if err != nil { + // There's an inherent race with the cleanup process (see + // #16142, #17142). If the container has already been marked as + // stopped or exited by the cleanup process, we can return + // immediately. + if errors.Is(err, define.ErrCtrStateInvalid) && ctr.ensureState(define.ContainerStateStopped, define.ContainerStateExited) { + return true, nil + } + + // If the PID is 0, then the container is already stopped. + if ctr.state.PID == 0 { + return true, nil + } + + // Is the container gone? + // If so, it probably died between the first check and + // our sending the signal + // The container is stopped, so exit cleanly + err := unix.Kill(ctr.state.PID, 0) + if err == unix.ESRCH { + return true, nil + } + + return false, err + } + + // Before handling error from KillContainer, convert STDERR to a []string + // (one string per line of output) and print it. + for line := range strings.SplitSeq(stderr.String(), "\n") { + if line != "" { + fmt.Fprintf(os.Stderr, "%s\n", line) + } + } + + return false, nil + } + + if timeout > 0 { + stopSignal := ctr.config.StopSignal + if stopSignal == 0 { + stopSignal = uint(syscall.SIGTERM) + } + + stopped, err := killCtr(stopSignal) + if err != nil { + return err + } + if stopped { + return nil + } + + if err := waitContainerStop(ctr, time.Duration(util.ConvertTimeout(int(timeout)))*time.Second); err != nil { + sigName := unix.SignalName(syscall.Signal(stopSignal)) + if sigName == "" { + sigName = fmt.Sprintf("(%d)", stopSignal) + } + logrus.Debugf("Timed out stopping container %s with %s, resorting to SIGKILL: %v", ctr.ID(), sigName, err) + logrus.Warnf("StopSignal %s failed to stop container %s in %d seconds, resorting to SIGKILL", sigName, ctr.Name(), timeout) + } else { + // No error, the container is dead + return nil + } + } + + stopped, err := killCtr(uint(unix.SIGKILL)) + if err != nil { + return fmt.Errorf("sending SIGKILL to container %s: %w", ctr.ID(), err) + } + if stopped { + return nil + } + + // Give runtime a few seconds to make it happen + if err := waitContainerStop(ctr, killContainerTimeout); err != nil { + return err + } + + return nil +} + +// DeleteContainer deletes a container from the OCI runtime. +func (r *ConmonOCIRuntime) DeleteContainer(ctr *Container) error { + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, append(r.runtimeFlags, "delete", "--force", ctr.ID())...) +} + +// PauseContainer pauses the given container. +func (r *ConmonOCIRuntime) PauseContainer(ctr *Container) error { + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, append(r.runtimeFlags, "pause", ctr.ID())...) +} + +// UnpauseContainer unpauses the given container. +func (r *ConmonOCIRuntime) UnpauseContainer(ctr *Container) error { + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, append(r.runtimeFlags, "resume", ctr.ID())...) +} + +// This filters out ENOTCONN errors which can happen on FreeBSD if the +// other side of the connection is already closed. +func socketCloseWrite(conn *net.UnixConn) error { + err := conn.CloseWrite() + if err != nil && errors.Is(err, syscall.ENOTCONN) { + return nil + } + return err +} + +// HTTPAttach performs an attach for the HTTP API. +// The caller must handle closing the HTTP connection after this returns. +// The cancel channel is not closed; it is up to the caller to do so after +// this function returns. +// If this is a container with a terminal, we will stream raw. If it is not, we +// will stream with an 8-byte header to multiplex STDOUT and STDERR. +// Returns any errors that occurred, and whether the connection was successfully +// hijacked before that error occurred. +func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, req *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, detachKeys *string, cancel <-chan bool, hijackDone chan<- bool, streamAttach, streamLogs bool) (deferredErr error) { + isTerminal := ctr.Terminal() + + if streams != nil { + if !streams.Stdin && !streams.Stdout && !streams.Stderr { + return fmt.Errorf("must specify at least one stream to attach to: %w", define.ErrInvalidArg) + } + } + + attachSock, err := r.AttachSocketPath(ctr) + if err != nil { + return err + } + + var conn *net.UnixConn + if streamAttach { + newConn, err := openUnixSocket(attachSock) + if err != nil { + return fmt.Errorf("failed to connect to container's attach socket: %v: %w", attachSock, err) + } + conn = newConn + defer func() { + if err := conn.Close(); err != nil { + logrus.Errorf("Unable to close container %s attach socket: %q", ctr.ID(), err) + } + }() + + logrus.Debugf("Successfully connected to container %s attach socket %s", ctr.ID(), attachSock) + } + + detachString := ctr.runtime.config.Engine.DetachKeys + if detachKeys != nil { + detachString = *detachKeys + } + isDetach, err := processDetachKeys(detachString) + if err != nil { + return err + } + + attachStdout := true + attachStderr := true + attachStdin := true + if streams != nil { + attachStdout = streams.Stdout + attachStderr = streams.Stderr + attachStdin = streams.Stdin + } + + logrus.Debugf("Going to hijack container %s attach connection", ctr.ID()) + + // Alright, let's hijack. + hijacker, ok := w.(http.Hijacker) + if !ok { + return fmt.Errorf("unable to hijack connection") + } + + httpCon, httpBuf, err := hijacker.Hijack() + if err != nil { + return fmt.Errorf("hijacking connection: %w", err) + } + + hijackDone <- true + + writeHijackHeader(req, httpBuf, isTerminal) + + // Force a flush after the header is written. + if err := httpBuf.Flush(); err != nil { + return fmt.Errorf("flushing HTTP hijack header: %w", err) + } + + defer func() { + hijackWriteErrorAndClose(deferredErr, ctr.ID(), isTerminal, httpCon, httpBuf) + }() + + logrus.Debugf("Hijack for container %s attach session done, ready to stream", ctr.ID()) + + // TODO: This is gross. Really, really gross. + // I want to say we should read all the logs into an array before + // calling this, in container_api.go, but that could take a lot of + // memory... + // On the whole, we need to figure out a better way of doing this, + // though. + logSize := 0 + if streamLogs { + logrus.Debugf("Will stream logs for container %s attach session", ctr.ID()) + + // Get all logs for the container + logChan := make(chan *logs.LogLine) + logOpts := new(logs.LogOptions) + logOpts.Tail = -1 + logOpts.WaitGroup = new(sync.WaitGroup) + errChan := make(chan error) + go func() { + var err error + // In non-terminal mode we need to prepend with the + // stream header. + logrus.Debugf("Writing logs for container %s to HTTP attach", ctr.ID()) + for logLine := range logChan { + if !isTerminal { + device := logLine.Device + var header []byte + headerLen := uint32(len(logLine.Msg)) + if !logLine.Partial() { + // we append an extra newline in this case so we need to increment the len as well + headerLen++ + } + logSize += len(logLine.Msg) + switch strings.ToLower(device) { + case "stdin": + header = makeHTTPAttachHeader(0, headerLen) + case "stdout": + header = makeHTTPAttachHeader(1, headerLen) + case "stderr": + header = makeHTTPAttachHeader(2, headerLen) + default: + logrus.Errorf("Unknown device for log line: %s", device) + header = makeHTTPAttachHeader(1, headerLen) + } + _, err = httpBuf.Write(header) + if err != nil { + break + } + } + _, err = httpBuf.Write([]byte(logLine.Msg)) + if err != nil { + break + } + if !logLine.Partial() { + _, err = httpBuf.Write([]byte("\n")) + if err != nil { + break + } + } + err = httpBuf.Flush() + if err != nil { + break + } + } + errChan <- err + }() + if err := ctr.ReadLog(context.Background(), logOpts, logChan, 0); err != nil { + return err + } + go func() { + logOpts.WaitGroup.Wait() + close(logChan) + }() + logrus.Debugf("Done reading logs for container %s, %d bytes", ctr.ID(), logSize) + if err := <-errChan; err != nil { + return err + } + } + if !streamAttach { + logrus.Debugf("Done streaming logs for container %s attach, exiting as attach streaming not requested", ctr.ID()) + return nil + } + + logrus.Debugf("Forwarding attach output for container %s", ctr.ID()) + + stdoutChan := make(chan error) + stdinChan := make(chan error) + + // Handle STDOUT/STDERR + go func() { + var err error + if isTerminal { + // Hack: return immediately if attachStdout not set to + // emulate Docker. + // Basically, when terminal is set, STDERR goes nowhere. + // Everything does over STDOUT. + // Therefore, if not attaching STDOUT - we'll never copy + // anything from here. + logrus.Debugf("Performing terminal HTTP attach for container %s", ctr.ID()) + if attachStdout { + err = httpAttachTerminalCopy(conn, httpBuf, ctr.ID()) + } + } else { + logrus.Debugf("Performing non-terminal HTTP attach for container %s", ctr.ID()) + err = httpAttachNonTerminalCopy(conn, httpBuf, ctr.ID(), attachStdin, attachStdout, attachStderr) + } + stdoutChan <- err + logrus.Debugf("STDOUT/ERR copy completed") + }() + // Next, STDIN. Avoid entirely if attachStdin unset. + if attachStdin { + go func() { + _, err := detach.Copy(conn, httpBuf, isDetach) + logrus.Debugf("STDIN copy completed") + stdinChan <- err + }() + } + + for { + select { + case err := <-stdoutChan: + if err != nil { + return err + } + + return nil + case err := <-stdinChan: + if err != nil { + return err + } + // copy stdin is done, close it + if connErr := socketCloseWrite(conn); connErr != nil { + logrus.Errorf("Unable to close conn: %v", connErr) + } + case <-cancel: + return nil + } + } +} + +// isRetryable returns whether the error was caused by a blocked syscall or the +// specified operation on a non blocking file descriptor wasn't ready for completion. +func isRetryable(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + return errno == syscall.EINTR || errno == syscall.EAGAIN + } + return false +} + +// openControlFile opens the terminal control file. +func openControlFile(ctr *Container, parentDir string) (*os.File, error) { + controlPath := filepath.Join(parentDir, "ctl") + for range 600 { + controlFile, err := os.OpenFile(controlPath, unix.O_WRONLY|unix.O_NONBLOCK, 0) + if err == nil { + return controlFile, nil + } + if !isRetryable(err) { + return nil, fmt.Errorf("could not open ctl file for terminal resize for container %s: %w", ctr.ID(), err) + } + time.Sleep(time.Second / 10) + } + return nil, fmt.Errorf("timeout waiting for %q", controlPath) +} + +// AttachResize resizes the terminal used by the given container. +func (r *ConmonOCIRuntime) AttachResize(ctr *Container, newSize resize.TerminalSize) error { + controlFile, err := openControlFile(ctr, ctr.bundlePath()) + if err != nil { + return err + } + defer controlFile.Close() + + logrus.Debugf("Received a resize event for container %s: %+v", ctr.ID(), newSize) + if _, err = fmt.Fprintf(controlFile, "%d %d %d\n", 1, newSize.Height, newSize.Width); err != nil { + return fmt.Errorf("failed to write to ctl file to resize terminal: %w", err) + } + + return nil +} + +// CheckpointContainer checkpoints the given container. +func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) (int64, error) { + // imagePath is used by CRIU to store the actual checkpoint files + imagePath := ctr.CheckpointPath() + if options.PreCheckPoint { + imagePath = ctr.PreCheckPointPath() + } + // workPath will be used to store dump.log and stats-dump + workPath := ctr.bundlePath() + logrus.Debugf("Writing checkpoint to %s", imagePath) + logrus.Debugf("Writing checkpoint logs to %s", workPath) + logrus.Debugf("Pre-dump the container %t", options.PreCheckPoint) + args := []string{} + args = append(args, r.runtimeFlags...) + args = append(args, "checkpoint") + args = append(args, "--image-path") + args = append(args, imagePath) + args = append(args, "--work-path") + args = append(args, workPath) + if options.KeepRunning { + args = append(args, "--leave-running") + } + if options.TCPEstablished { + args = append(args, "--tcp-established") + } + if options.FileLocks { + args = append(args, "--file-locks") + } + if !options.PreCheckPoint && options.KeepRunning { + args = append(args, "--leave-running") + } + if options.PreCheckPoint { + args = append(args, "--pre-dump") + } + if !options.PreCheckPoint && options.WithPrevious { + args = append( + args, + "--parent-path", + filepath.Join("..", preCheckpointDir), + ) + } + + args = append(args, ctr.ID()) + logrus.Debugf("the args to checkpoint: %s %s", r.path, strings.Join(args, " ")) + + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return 0, err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + if path, ok := os.LookupEnv("PATH"); ok { + env = append(env, fmt.Sprintf("PATH=%s", path)) + } + + var runtimeCheckpointStarted time.Time + err = r.withContainerSocketLabel(ctr, func() error { + runtimeCheckpointStarted = time.Now() + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, args...) + }) + + runtimeCheckpointDuration := func() int64 { + if options.PrintStats { + return time.Since(runtimeCheckpointStarted).Microseconds() + } + return 0 + }() + + return runtimeCheckpointDuration, err +} + +func (r *ConmonOCIRuntime) CheckConmonRunning(ctr *Container) (bool, error) { + if ctr.state.ConmonPID == 0 { + // If the container is running or paused, assume Conmon is + // running. We didn't record Conmon PID on some old versions, so + // that is likely what's going on... + // Unusual enough that we should print a warning message though. + if ctr.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) { + logrus.Warnf("Conmon PID is not set, but container is running!") + return true, nil + } + // Container's not running, so conmon PID being unset is + // expected. Conmon is not running. + return false, nil + } + + // We have a conmon PID. Ping it with signal 0. + if err := unix.Kill(ctr.state.ConmonPID, 0); err != nil { + if err == unix.ESRCH { + return false, nil + } + return false, fmt.Errorf("pinging container %s conmon with signal 0: %w", ctr.ID(), err) + } + return true, nil +} + +// SupportsCheckpoint checks if the OCI runtime supports checkpointing +// containers. +func (r *ConmonOCIRuntime) SupportsCheckpoint() bool { + return crutils.CRRuntimeSupportsCheckpointRestore(r.path) +} + +// SupportsJSONErrors checks if the OCI runtime supports JSON-formatted error +// messages. +func (r *ConmonOCIRuntime) SupportsJSONErrors() bool { + return r.supportsJSON +} + +// SupportsNoCgroups checks if the OCI runtime supports running containers +// without cgroups (the --cgroup-manager=disabled flag). +func (r *ConmonOCIRuntime) SupportsNoCgroups() bool { + return r.supportsNoCgroups +} + +// SupportsKVM checks if the OCI runtime supports running containers +// without KVM separation +func (r *ConmonOCIRuntime) SupportsKVM() bool { + return r.supportsKVM +} + +// AttachSocketPath is the path to a single container's attach socket. +func (r *ConmonOCIRuntime) AttachSocketPath(ctr *Container) (string, error) { + if ctr == nil { + return "", fmt.Errorf("must provide a valid container to get attach socket path: %w", define.ErrInvalidArg) + } + + return filepath.Join(ctr.bundlePath(), "attach"), nil +} + +// ExitFilePath is the path to a container's exit file. +func (r *ConmonOCIRuntime) ExitFilePath(ctr *Container) (string, error) { + if ctr == nil { + return "", fmt.Errorf("must provide a valid container to get exit file path: %w", define.ErrInvalidArg) + } + return filepath.Join(r.exitsDir, ctr.ID()), nil +} + +// OOMFilePath is the path to a container's oom file. +// The oom file will only exist if the container was oom killed. +func (r *ConmonOCIRuntime) OOMFilePath(ctr *Container) (string, error) { + return filepath.Join(r.persistDir, ctr.ID(), "oom"), nil +} + +// PersistDirectoryPath is the path to the container's persist directory. +func (r *ConmonOCIRuntime) PersistDirectoryPath(ctr *Container) (string, error) { + return filepath.Join(r.persistDir, ctr.ID()), nil +} + +// RuntimeInfo provides information on the runtime. +func (r *ConmonOCIRuntime) RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) { + runtimePackage := version.Package(r.path) + conmonPackage := version.Package(r.conmonPath) + runtimeVersion, err := r.getOCIRuntimeVersion() + if err != nil { + return nil, nil, fmt.Errorf("getting version of OCI runtime %s: %w", r.name, err) + } + conmonVersion, err := r.getConmonVersion() + if err != nil { + return nil, nil, fmt.Errorf("getting conmon version: %w", err) + } + + conmon := define.ConmonInfo{ + Package: conmonPackage, + Path: r.conmonPath, + Version: conmonVersion, + } + ocirt := define.OCIRuntimeInfo{ + Name: r.name, + Path: r.path, + Package: runtimePackage, + Version: runtimeVersion, + } + return &conmon, &ocirt, nil +} + +// Wait for a container which has been sent a signal to stop +func waitContainerStop(ctr *Container, timeout time.Duration) error { + return waitPidStop(ctr.state.PID, timeout) +} + +// Wait for a given PID to stop +func waitPidStop(pid int, timeout time.Duration) error { + timer := time.NewTimer(timeout) + defer timer.Stop() + for { + select { + case <-timer.C: + return fmt.Errorf("given PID did not die within timeout") + default: + if err := unix.Kill(pid, 0); err != nil { + if err == unix.ESRCH { + return nil + } + logrus.Errorf("Pinging PID %d with signal 0: %v", pid, err) + } + time.Sleep(10 * time.Millisecond) + } + } +} + +func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) { + logTag := ctr.LogTag() + if logTag == "" { + return "", nil + } + data, err := ctr.inspectLocked(false) + if err != nil { + // FIXME: this error should probably be returned + return "", nil //nolint: nilerr + } + tmpl, err := template.New("container").Parse(logTag) + if err != nil { + return "", fmt.Errorf("template parsing error %s: %w", logTag, err) + } + var b bytes.Buffer + err = tmpl.Execute(&b, data) + if err != nil { + return "", err + } + return b.String(), nil +} + +func getPreserveFdExtraFiles(preserveFD []uint, preserveFDs uint) (uint, []*os.File, []*os.File, error) { + var filesToClose []*os.File + var extraFiles []*os.File + + preserveFDsMap := make(map[uint]struct{}) + for _, i := range preserveFD { + if i < 3 { + return 0, nil, nil, fmt.Errorf("cannot preserve FD %d, consider using the passthrough log-driver to pass STDIO streams into the container: %w", i, define.ErrInvalidArg) + } + if i-2 > preserveFDs { + // preserveFDs is the number of FDs above 2 to keep around. + // e.g. if the user specified FD=3, then preserveFDs must be 1. + preserveFDs = i - 2 + } + preserveFDsMap[i] = struct{}{} + } + + if preserveFDs > 0 { + for fd := 3; fd < int(3+preserveFDs); fd++ { + if len(preserveFDsMap) > 0 { + if _, ok := preserveFDsMap[uint(fd)]; !ok { + extraFiles = append(extraFiles, nil) + continue + } + } + f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)) + filesToClose = append(filesToClose, f) + extraFiles = append(extraFiles, f) + } + } + return preserveFDs, filesToClose, extraFiles, nil +} + +// createOCIContainer generates this container's main conmon instance and prepares it for starting +func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) { + var stderrBuf bytes.Buffer + + parentSyncPipe, childSyncPipe, err := newPipe() + if err != nil { + return 0, fmt.Errorf("creating socket pair: %w", err) + } + defer errorhandling.CloseQuiet(parentSyncPipe) + + childStartPipe, parentStartPipe, err := newPipe() + if err != nil { + return 0, fmt.Errorf("creating socket pair for start pipe: %w", err) + } + + defer errorhandling.CloseQuiet(parentStartPipe) + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = filepath.Join(ctr.state.RunDir, "oci-log") + } + + logTag, err := r.getLogTag(ctr) + if err != nil { + return 0, err + } + + if ctr.config.CgroupsMode == cgroupSplit { + if err := moveToRuntimeCgroup(); err != nil { + return 0, err + } + } + + pidfile := ctr.config.PidFile + if pidfile == "" { + pidfile = filepath.Join(ctr.state.RunDir, "pidfile") + } + + persistDir := filepath.Join(r.persistDir, ctr.ID()) + args, err := r.sharedConmonArgs(ctr, ctr.ID(), ctr.bundlePath(), pidfile, ctr.LogPath(), r.exitsDir, persistDir, ociLog, ctr.LogDriver(), logTag) + if err != nil { + return 0, err + } + + if ctr.config.SdNotifyMode == define.SdNotifyModeContainer && ctr.config.SdNotifySocket != "" { + args = append(args, fmt.Sprintf("--sdnotify-socket=%s", ctr.config.SdNotifySocket)) + } + + if ctr.Terminal() { + args = append(args, "-t") + } else if ctr.config.Stdin { + args = append(args, "-i") + } + + if ctr.config.Timeout > 0 { + args = append(args, fmt.Sprintf("--timeout=%d", ctr.config.Timeout)) + } + + if !r.enableKeyring { + args = append(args, "--no-new-keyring") + } + if ctr.config.ConmonPidFile != "" { + args = append(args, "--conmon-pidfile", ctr.config.ConmonPidFile) + } + + if r.noPivot { + args = append(args, "--no-pivot") + } + + exitCommand, err := specgenutil.CreateExitCommandArgs(ctr.runtime.storageConfig, ctr.runtime.config, ctr.runtime.syslog || logrus.IsLevelEnabled(logrus.DebugLevel), ctr.AutoRemove(), ctr.AutoRemoveImage(), false) + if err != nil { + return 0, err + } + exitCommand = append(exitCommand, ctr.config.ID) + + args = append(args, "--exit-command", exitCommand[0]) + for _, arg := range exitCommand[1:] { + args = append(args, []string{"--exit-command-arg", arg}...) + } + + preserveFDs := ctr.config.PreserveFDs + + // Pass down the LISTEN_* environment (see #10443). + if val := os.Getenv("LISTEN_FDS"); val != "" { + if preserveFDs > 0 || len(ctr.config.PreserveFD) > 0 { + logrus.Warnf("Ignoring LISTEN_FDS to preserve custom user-specified FDs") + } else { + fds, err := strconv.Atoi(val) + if err != nil { + return 0, fmt.Errorf("converting LISTEN_FDS=%s: %w", val, err) + } + preserveFDs = uint(fds) + } + } + + preserveFDs, filesToClose, extraFiles, err := getPreserveFdExtraFiles(ctr.config.PreserveFD, preserveFDs) + if err != nil { + return 0, err + } + if preserveFDs > 0 { + args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(preserveFDs), 10))...) + } + + if restoreOptions != nil { + args = append(args, "--restore", ctr.CheckpointPath()) + if restoreOptions.TCPEstablished { + args = append(args, "--runtime-opt", "--tcp-established") + } + if restoreOptions.TCPClose { + args = append(args, "--runtime-opt", "--tcp-close") + } + if restoreOptions.FileLocks { + args = append(args, "--runtime-opt", "--file-locks") + } + if restoreOptions.Pod != "" { + mountLabel := ctr.config.MountLabel + processLabel := ctr.config.ProcessLabel + if mountLabel != "" { + args = append( + args, + "--runtime-opt", + fmt.Sprintf( + "--lsm-mount-context=%s", + mountLabel, + ), + ) + } + if processLabel != "" { + args = append( + args, + "--runtime-opt", + fmt.Sprintf( + "--lsm-profile=selinux:%s", + processLabel, + ), + ) + } + } + } + + logrus.WithFields(logrus.Fields{ + "args": args, + }).Debugf("running conmon: %s", r.conmonPath) + + cmd := exec.Command(r.conmonPath, args...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + // TODO this is probably a really bad idea for some uses + // Make this configurable + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if ctr.Terminal() { + cmd.Stderr = &stderrBuf + } + + // 0, 1 and 2 are stdin, stdout and stderr + conmonEnv, err := r.configureConmonEnv() + if err != nil { + return 0, fmt.Errorf("configuring conmon env: %w", err) + } + + cmd.ExtraFiles = extraFiles + + cmd.Env = r.conmonEnv + // we don't want to step on users fds they asked to preserve + // Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3 + cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", preserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", preserveFDs+4)) + cmd.Env = append(cmd.Env, conmonEnv...) + cmd.ExtraFiles = append(cmd.ExtraFiles, childSyncPipe, childStartPipe) + + if ctr.config.PostConfigureNetNS { + // netns was not setup yet but we have to bind ports now so we can leak the fd to conmon + ports, err := ctr.bindPorts() + if err != nil { + return 0, err + } + filesToClose = append(filesToClose, ports...) + // Leak the port we bound in the conmon process. These fd's won't be used + // by the container and conmon will keep the ports busy so that another + // process cannot use them. + cmd.ExtraFiles = append(cmd.ExtraFiles, ports...) + } else { + // ports were bound in ctr.prepare() as we must do it before the netns setup + filesToClose = append(filesToClose, ctr.reservedPorts...) + cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.reservedPorts...) + ctr.reservedPorts = nil + } + + if ctr.config.NetMode.IsSlirp4netns() || rootless.IsRootless() { + if ctr.config.PostConfigureNetNS { + havePortMapping := len(ctr.config.PortMappings) > 0 + if havePortMapping { + ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe() + if err != nil { + return 0, fmt.Errorf("failed to create rootless port sync pipe: %w", err) + } + } + ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() + if err != nil { + return 0, fmt.Errorf("failed to create rootless network sync pipe: %w", err) + } + } + + if ctr.rootlessSlirpSyncW != nil { + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) + // Leak one end in conmon, the other one will be leaked into slirp4netns + cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW) + } + + if ctr.rootlessPortSyncW != nil { + defer errorhandling.CloseQuiet(ctr.rootlessPortSyncW) + // Leak one end in conmon, the other one will be leaked into rootlessport + cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncW) + } + } + var runtimeRestoreStarted time.Time + if restoreOptions != nil { + runtimeRestoreStarted = time.Now() + } + err = cmd.Start() + + // regardless of whether we errored or not, we no longer need the children pipes + childSyncPipe.Close() + childStartPipe.Close() + if err != nil { + return 0, err + } + if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe); err != nil { + // The child likely already exited in which case the cmd.Wait() below should return the proper error. + // EPIPE is expected if the child already exited so not worth to log and kill the process. + if !errors.Is(err, syscall.EPIPE) { + logrus.Errorf("Failed to signal conmon to start: %v", err) + if err := cmd.Process.Kill(); err != nil && !errors.Is(err, syscall.ESRCH) { + logrus.Errorf("Failed to kill conmon after error: %v", err) + } + } + } + + /* Wait for initial setup and fork, and reap child */ + err = cmd.Wait() + if err != nil { + return 0, fmt.Errorf("conmon failed: %w", err) + } + + pid, err := readConmonPipeData(r.name, parentSyncPipe, ociLog) + if err != nil { + if err2 := r.DeleteContainer(ctr); err2 != nil { + logrus.Errorf("Removing container %s from runtime after creation failed", ctr.ID()) + } + return 0, err + } + ctr.state.PID = pid + + conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile) + if err != nil { + logrus.Warnf("Error reading conmon pid file for container %s: %v", ctr.ID(), err) + } else if conmonPID > 0 { + // conmon not having a pid file is a valid state, so don't set it if we don't have it + logrus.Infof("Got Conmon PID as %d", conmonPID) + ctr.state.ConmonPID = conmonPID + } + + runtimeRestoreDuration := func() int64 { + if restoreOptions != nil && restoreOptions.PrintStats { + return time.Since(runtimeRestoreStarted).Microseconds() + } + return 0 + }() + + // These fds were passed down to the runtime. Close them + // and not interfere + for _, f := range filesToClose { + errorhandling.CloseQuiet(f) + } + + return runtimeRestoreDuration, nil +} + +// configureConmonEnv gets the environment values to add to conmon's exec struct +func (r *ConmonOCIRuntime) configureConmonEnv() ([]string, error) { + env := os.Environ() + res := make([]string, 0, len(env)) + for _, v := range env { + if strings.HasPrefix(v, "NOTIFY_SOCKET=") { + // The NOTIFY_SOCKET must not leak into the environment. + continue + } + if strings.HasPrefix(v, "DBUS_SESSION_BUS_ADDRESS=") && !rootless.IsRootless() { + // The DBUS_SESSION_BUS_ADDRESS must not leak into the environment when running as root. + // This is because we want to use the system session for root containers, not the user session. + continue + } + res = append(res, v) + } + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return nil, err + } + + res = append(res, "XDG_RUNTIME_DIR="+runtimeDir) + return res, nil +} + +// sharedConmonArgs takes common arguments for exec and create/restore and formats them for the conmon CLI +// func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, persistDir, ociLogPath, logDriver, logTag string) ([]string, error) { +func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, persistDir, ociLogPath, logDriver, logTag string) ([]string, error) { + // Make the persists directory for the container after the ctr ID is appended to it in the caller + // This is needed as conmon writes the exit and oom file in the given persist directory path as just "exit" and "oom" + // So creating a directory with the container ID under the persist dir will help keep track of which container the + // exit and oom files belong to. + if err := os.MkdirAll(persistDir, 0o750); err != nil { + return nil, fmt.Errorf("creating OCI runtime oom files directory for ctr %q: %w", ctr.ID(), err) + } + + // set the conmon API version to be able to use the correct sync struct keys + args := []string{ + "--api-version", "1", + "-c", ctr.ID(), + "-u", cuuid, + "-r", r.path, + "-b", bundlePath, + "-p", pidPath, + "-n", ctr.Name(), + "--exit-dir", exitDir, + "--persist-dir", persistDir, + "--full-attach", + } + if len(r.runtimeFlags) > 0 { + rFlags := []string{} + for _, arg := range r.runtimeFlags { + rFlags = append(rFlags, "--runtime-arg", arg) + } + args = append(args, rFlags...) + } + + if ctr.CgroupManager() == config.SystemdCgroupsManager && !ctr.config.NoCgroups && ctr.config.CgroupsMode != cgroupSplit { + args = append(args, "-s") + } + + var logDriverArg string + switch logDriver { + case define.JournaldLogging: + logDriverArg = define.JournaldLogging + case define.NoLogging: + logDriverArg = define.NoLogging + case define.PassthroughLogging, define.PassthroughTTYLogging: + logDriverArg = define.PassthroughLogging + //lint:ignore ST1015 the default case has to be here + default: //nolint:gocritic + // No case here should happen except JSONLogging, but keep this here in case the options are extended + logrus.Errorf("%s logging specified but not supported. Choosing k8s-file logging instead", ctr.LogDriver()) + fallthrough + case "": + // to get here, either a user would specify `--log-driver ""`, or this came from another place in libpod + // since the former case is obscure, and the latter case isn't an error, let's silently fallthrough + fallthrough + case define.JSONLogging: + fallthrough + case define.KubernetesLogging: + logDriverArg = fmt.Sprintf("%s:%s", define.KubernetesLogging, logPath) + } + + args = append(args, "-l", logDriverArg) + logLevel := logrus.GetLevel() + args = append(args, "--log-level", logLevel.String()) + + logrus.Debugf("%s messages will be logged to syslog", r.conmonPath) + args = append(args, "--syslog") + + size := ctr.LogSizeMax() + if size > 0 { + args = append(args, "--log-size-max", strconv.FormatInt(size, 10)) + } + + if ociLogPath != "" { + args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLogPath)) + } + if logTag != "" { + args = append(args, "--log-tag", logTag) + } + if ctr.config.NoCgroups { + logrus.Debugf("Running with no Cgroups") + args = append(args, "--runtime-arg", "--cgroup-manager", "--runtime-arg", "disabled") + } + return args, nil +} + +// newPipe creates a unix socket pair for communication. +// Returns two files - first is parent, second is child. +func newPipe() (*os.File, *os.File, error) { + fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil +} + +// readConmonPidFile attempts to read conmon's pid from its pid file +func readConmonPidFile(pidFile string) (int, error) { + // Let's try reading the Conmon pid at the same time. + if pidFile != "" { + contents, err := os.ReadFile(pidFile) + if err != nil { + return -1, err + } + // Convert it to an int + conmonPID, err := strconv.Atoi(string(contents)) + if err != nil { + return -1, err + } + return conmonPID, nil + } + return 0, nil +} + +// readConmonPipeData attempts to read a syncInfo struct from the pipe +func readConmonPipeData(runtimeName string, pipe *os.File, ociLog string) (int, error) { + // syncInfo is used to return data from monitor process to daemon + type syncInfo struct { + Data int `json:"data"` + Message string `json:"message,omitempty"` + } + + // Wait to get container pid from conmon + type syncStruct struct { + si *syncInfo + err error + } + ch := make(chan syncStruct) + go func() { + var si *syncInfo + rdr := bufio.NewReader(pipe) + b, err := rdr.ReadBytes('\n') + // ignore EOF here, error is returned even when data was read + // if it is no valid json unmarshal will fail below + if err != nil && !errors.Is(err, io.EOF) { + ch <- syncStruct{err: err} + } + if err := json.Unmarshal(b, &si); err != nil { + ch <- syncStruct{err: fmt.Errorf("conmon bytes %q: %w", string(b), err)} + return + } + ch <- syncStruct{si: si} + }() + + var data int + select { + case ss := <-ch: + if ss.err != nil { + if ociLog != "" { + ociLogData, err := os.ReadFile(ociLog) + if err == nil { + var ociErr ociError + if err := json.Unmarshal(ociLogData, &ociErr); err == nil { + return -1, getOCIRuntimeError(runtimeName, ociErr.Msg) + } + } + } + return -1, fmt.Errorf("container create failed (no logs from conmon): %w", ss.err) + } + logrus.Debugf("Received: %d", ss.si.Data) + if ss.si.Data < 0 { + if ociLog != "" { + ociLogData, err := os.ReadFile(ociLog) + if err == nil { + var ociErr ociError + if err := json.Unmarshal(ociLogData, &ociErr); err == nil { + return ss.si.Data, getOCIRuntimeError(runtimeName, ociErr.Msg) + } + } + } + // If we failed to parse the JSON errors, then print the output as it is + if ss.si.Message != "" { + return ss.si.Data, getOCIRuntimeError(runtimeName, ss.si.Message) + } + return ss.si.Data, fmt.Errorf("container create failed: %w", define.ErrInternal) + } + data = ss.si.Data + case <-time.After(define.ContainerCreateTimeout): + return -1, fmt.Errorf("container creation timeout: %w", define.ErrInternal) + } + return data, nil +} + +// writeConmonPipeData writes nonce data to a pipe +func writeConmonPipeData(pipe *os.File) error { + someData := []byte{0} + _, err := pipe.Write(someData) + return err +} + +// formatRuntimeOpts prepends opts passed to it with --runtime-opt for passing to conmon +func formatRuntimeOpts(opts ...string) []string { + args := make([]string, 0, len(opts)*2) + for _, o := range opts { + args = append(args, "--runtime-opt", o) + } + return args +} + +// getConmonVersion returns a string representation of the conmon version. +func (r *ConmonOCIRuntime) getConmonVersion() (string, error) { + output, err := utils.ExecCmd(r.conmonPath, "--version") + if err != nil { + return "", err + } + return strings.TrimSuffix(strings.Replace(output, "\n", ", ", 1), "\n"), nil +} + +// getOCIRuntimeVersion returns a string representation of the OCI runtime's +// version. +func (r *ConmonOCIRuntime) getOCIRuntimeVersion() (string, error) { + output, err := utils.ExecCmd(r.path, "--version") + if err != nil { + return "", err + } + return strings.TrimSuffix(output, "\n"), nil +} + +// Copy data from container to HTTP connection, for terminal attach. +// Container is the container's attach socket connection, http is a buffer for +// the HTTP connection. cid is the ID of the container the attach session is +// running for (used solely for error messages). +func httpAttachTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter, cid string) error { + buf := make([]byte, bufferSize) + for { + numR, err := container.Read(buf) + logrus.Debugf("Read fd(%d) %d/%d bytes for container %s", int(buf[0]), numR, len(buf), cid) + + if numR > 0 { + switch buf[0] { + case AttachPipeStdout: + // Do nothing + default: + logrus.Errorf("Received unexpected attach type %+d, discarding %d bytes", buf[0], numR) + continue + } + + numW, err2 := http.Write(buf[1:numR]) + if err2 != nil { + if err != nil { + logrus.Errorf("Reading container %s STDOUT: %v", cid, err) + } + return err2 + } else if numW+1 != numR { + return io.ErrShortWrite + } + // We need to force the buffer to write immediately, so + // there isn't a delay on the terminal side. + if err2 := http.Flush(); err2 != nil { + if err != nil { + logrus.Errorf("Reading container %s STDOUT: %v", cid, err) + } + return err2 + } + } + if err != nil { + if err == io.EOF { + return nil + } + return err + } + } +} + +// Copy data from a container to an HTTP connection, for non-terminal attach. +// Appends a header to multiplex input. +func httpAttachNonTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter, cid string, stdin, stdout, stderr bool) error { + buf := make([]byte, bufferSize) + for { + numR, err := container.Read(buf) + if numR > 0 { + var headerBuf []byte + + // Subtract 1 because we strip the first byte (used for + // multiplexing by Conmon). + headerLen := uint32(numR - 1) + // Practically speaking, we could make this buf[0] - 1, + // but we need to validate it anyway. + switch buf[0] { + case AttachPipeStdin: + headerBuf = makeHTTPAttachHeader(0, headerLen) + if !stdin { + continue + } + case AttachPipeStdout: + if !stdout { + continue + } + headerBuf = makeHTTPAttachHeader(1, headerLen) + case AttachPipeStderr: + if !stderr { + continue + } + headerBuf = makeHTTPAttachHeader(2, headerLen) + default: + logrus.Errorf("Received unexpected attach type %+d, discarding %d bytes", buf[0], numR) + continue + } + + numH, err2 := http.Write(headerBuf) + if err2 != nil { + if err != nil { + logrus.Errorf("Reading container %s standard streams: %v", cid, err) + } + + return err2 + } + // Hardcoding header length is pretty gross, but + // fast. Should be safe, as this is a fixed part + // of the protocol. + if numH != 8 { + if err != nil { + logrus.Errorf("Reading container %s standard streams: %v", cid, err) + } + + return io.ErrShortWrite + } + + numW, err2 := http.Write(buf[1:numR]) + if err2 != nil { + if err != nil { + logrus.Errorf("Reading container %s standard streams: %v", cid, err) + } + + return err2 + } else if numW+1 != numR { + if err != nil { + logrus.Errorf("Reading container %s standard streams: %v", cid, err) + } + + return io.ErrShortWrite + } + // We need to force the buffer to write immediately, so + // there isn't a delay on the terminal side. + if err2 := http.Flush(); err2 != nil { + if err != nil { + logrus.Errorf("Reading container %s STDOUT: %v", cid, err) + } + return err2 + } + } + if err != nil { + if err == io.EOF { + return nil + } + + return err + } + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon_exec_common.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_exec_common.go new file mode 100644 index 00000000..45350f93 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_exec_common.go @@ -0,0 +1,789 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "errors" + "fmt" + "net/http" + "os" + "os/exec" + "path/filepath" + "slices" + "strconv" + "strings" + "syscall" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/errorhandling" + "github.com/containers/podman/v5/pkg/lookup" + "github.com/containers/podman/v5/pkg/pidhandle" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/detach" + "go.podman.io/common/pkg/resize" + "golang.org/x/sys/unix" +) + +// ExecContainer executes a command in a running container +func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams, newSize *resize.TerminalSize) (int, chan error, error) { + if options == nil { + return -1, nil, fmt.Errorf("must provide an ExecOptions struct to ExecContainer: %w", define.ErrInvalidArg) + } + if len(options.Cmd) == 0 { + return -1, nil, fmt.Errorf("must provide a command to execute: %w", define.ErrInvalidArg) + } + + if sessionID == "" { + return -1, nil, fmt.Errorf("must provide a session ID for exec: %w", define.ErrEmptyID) + } + + // TODO: Should we default this to false? + // Or maybe make streams mandatory? + attachStdin := true + if streams != nil { + attachStdin = streams.AttachInput + } + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = c.execOCILog(sessionID) + } + + execCmd, pipes, err := r.startExec(c, sessionID, options, attachStdin, ociLog) + if err != nil { + return -1, nil, err + } + + // Only close sync pipe. Start and attach are consumed in the attach + // goroutine. + defer func() { + if pipes.syncPipe != nil && !pipes.syncClosed { + errorhandling.CloseQuiet(pipes.syncPipe) + pipes.syncClosed = true + } + }() + + // TODO Only create if !detach + // Attach to the container before starting it + attachChan := make(chan error) + go func() { + // attachToExec is responsible for closing pipes + attachChan <- c.attachToExec(streams, options.DetachKeys, sessionID, pipes.startPipe, pipes.attachPipe, newSize) + close(attachChan) + }() + + if err := execCmd.Wait(); err != nil { + return -1, nil, fmt.Errorf("cannot run conmon: %w", err) + } + + pid, err := readConmonPipeData(r.name, pipes.syncPipe, ociLog) + + return pid, attachChan, err +} + +// ExecContainerHTTP executes a new command in an existing container and +// forwards its standard streams over an attach +func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, req *http.Request, w http.ResponseWriter, + streams *HTTPAttachStreams, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, newSize *resize.TerminalSize) (int, chan error, error) { + if streams != nil { + if !streams.Stdin && !streams.Stdout && !streams.Stderr { + return -1, nil, fmt.Errorf("must provide at least one stream to attach to: %w", define.ErrInvalidArg) + } + } + + if options == nil { + return -1, nil, fmt.Errorf("must provide exec options to ExecContainerHTTP: %w", define.ErrInvalidArg) + } + + detachString := config.DefaultDetachKeys + if options.DetachKeys != nil { + detachString = *options.DetachKeys + } + detachKeys, err := processDetachKeys(detachString) + if err != nil { + return -1, nil, err + } + + // TODO: Should we default this to false? + // Or maybe make streams mandatory? + attachStdin := true + if streams != nil { + attachStdin = streams.Stdin + } + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = ctr.execOCILog(sessionID) + } + + execCmd, pipes, err := r.startExec(ctr, sessionID, options, attachStdin, ociLog) + if err != nil { + return -1, nil, err + } + + // Only close sync pipe. Start and attach are consumed in the attach + // goroutine. + defer func() { + if pipes.syncPipe != nil && !pipes.syncClosed { + errorhandling.CloseQuiet(pipes.syncPipe) + pipes.syncClosed = true + } + }() + + attachChan := make(chan error) + conmonPipeDataChan := make(chan conmonPipeData) + go func() { + // attachToExec is responsible for closing pipes + attachChan <- attachExecHTTP(ctr, sessionID, req, w, streams, pipes, detachKeys, options.Terminal, cancel, hijackDone, holdConnOpen, execCmd, conmonPipeDataChan, ociLog, newSize, r.name) + close(attachChan) + }() + + // NOTE: the channel is needed to communicate conmon's data. In case + // of an error, the error will be written on the hijacked http + // connection such that remote clients will receive the error. + pipeData := <-conmonPipeDataChan + + return pipeData.pid, attachChan, pipeData.err +} + +// conmonPipeData contains the data when reading from conmon's pipe. +type conmonPipeData struct { + pid int + err error +} + +// ExecContainerDetached executes a command in a running container, but does +// not attach to it. +func (r *ConmonOCIRuntime) ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) { + if options == nil { + return -1, fmt.Errorf("must provide exec options to ExecContainerHTTP: %w", define.ErrInvalidArg) + } + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = ctr.execOCILog(sessionID) + } + + execCmd, pipes, err := r.startExec(ctr, sessionID, options, stdin, ociLog) + if err != nil { + return -1, err + } + + defer func() { + pipes.cleanup() + }() + + // Wait for Conmon to tell us we're ready to attach. + // We aren't actually *going* to attach, but this means that we're good + // to proceed. + if _, err := readConmonPipeData(r.name, pipes.attachPipe, ""); err != nil { + return -1, err + } + + // Start the exec session + if err := writeConmonPipeData(pipes.startPipe); err != nil { + return -1, err + } + + // Wait for conmon to succeed, when return. + if err := execCmd.Wait(); err != nil { + return -1, fmt.Errorf("cannot run conmon: %w", err) + } + + pid, err := readConmonPipeData(r.name, pipes.syncPipe, ociLog) + + return pid, err +} + +// ExecAttachResize resizes the TTY of the given exec session. +func (r *ConmonOCIRuntime) ExecAttachResize(ctr *Container, sessionID string, newSize resize.TerminalSize) error { + controlFile, err := openControlFile(ctr, ctr.execBundlePath(sessionID)) + if err != nil { + return err + } + defer controlFile.Close() + + if _, err = fmt.Fprintf(controlFile, "%d %d %d\n", 1, newSize.Height, newSize.Width); err != nil { + return fmt.Errorf("failed to write to ctl file to resize terminal: %w", err) + } + + return nil +} + +// ExecStopContainer stops a given exec session in a running container. +func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error { + pid, pidData, err := ctr.getExecSessionPID(sessionID) + if err != nil { + return err + } + + logrus.Debugf("Going to stop container %s exec session %s", ctr.ID(), sessionID) + + pidHandle, err := pidhandle.NewPIDHandleFromString(pid, pidData) + if err != nil { + return fmt.Errorf("getting the PID handle for pid %d from '%s': %w", pid, pidData, err) + } + defer pidHandle.Close() + + // Is the session dead? + sessionAlive, err := pidHandle.IsAlive() + if err != nil { + return fmt.Errorf("getting the process status for pid %d: %w", pid, err) + } + if !sessionAlive { + return nil + } + + if timeout > 0 { + // Use SIGTERM by default, then SIGSTOP after timeout. + logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGTERM", sessionID, pid, ctr.ID()) + if err := pidHandle.Kill(unix.SIGTERM); err != nil { + if err == unix.ESRCH { + return nil + } + return fmt.Errorf("killing container %s exec session %s PID %d with SIGTERM: %w", ctr.ID(), sessionID, pid, err) + } + + // Wait for the PID to stop + if err := waitPidStop(pid, time.Duration(timeout)*time.Second); err != nil { + logrus.Infof("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL: %v", ctr.ID(), sessionID, err) + } else { + // No error, container is dead + return nil + } + } + + // SIGTERM did not work. On to SIGKILL. + logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGKILL", sessionID, pid, ctr.ID()) + if err := pidHandle.Kill(unix.SIGKILL); err != nil { + if err == unix.ESRCH { + return nil + } + return fmt.Errorf("killing container %s exec session %s PID %d with SIGKILL: %w", ctr.ID(), sessionID, pid, err) + } + + // Wait for the PID to stop + if err := waitPidStop(pid, killContainerTimeout); err != nil { + return fmt.Errorf("timed out waiting for container %s exec session %s PID %d to stop after SIGKILL: %w", ctr.ID(), sessionID, pid, err) + } + + return nil +} + +// ExecUpdateStatus checks if the given exec session is still running. +func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) { + pid, pidData, err := ctr.getExecSessionPID(sessionID) + if err != nil { + return false, err + } + + logrus.Debugf("Checking status of container %s exec session %s", ctr.ID(), sessionID) + + pidHandle, err := pidhandle.NewPIDHandleFromString(pid, pidData) + if err != nil { + return false, fmt.Errorf("getting the PID handle for pid %d from '%s': %w", pid, pidData, err) + } + defer pidHandle.Close() + + // Is the session dead? + sessionAlive, err := pidHandle.IsAlive() + if err != nil { + return false, fmt.Errorf("getting the process status for pid %d: %w", pid, err) + } + + return sessionAlive, nil +} + +// ExecAttachSocketPath is the path to a container's exec session attach socket. +func (r *ConmonOCIRuntime) ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) { + // We don't even use container, so don't validity check it + if sessionID == "" { + return "", fmt.Errorf("must provide a valid session ID to get attach socket path: %w", define.ErrInvalidArg) + } + + return filepath.Join(ctr.execBundlePath(sessionID), "attach"), nil +} + +// This contains pipes used by the exec API. +type execPipes struct { + syncPipe *os.File + syncClosed bool + startPipe *os.File + startClosed bool + attachPipe *os.File + attachClosed bool +} + +func (p *execPipes) cleanup() { + if p.syncPipe != nil && !p.syncClosed { + errorhandling.CloseQuiet(p.syncPipe) + p.syncClosed = true + } + if p.startPipe != nil && !p.startClosed { + errorhandling.CloseQuiet(p.startPipe) + p.startClosed = true + } + if p.attachPipe != nil && !p.attachClosed { + errorhandling.CloseQuiet(p.attachPipe) + p.attachClosed = true + } +} + +// Start an exec session's conmon parent from the given options. +func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *ExecOptions, attachStdin bool, ociLog string) (_ *exec.Cmd, _ *execPipes, deferredErr error) { + pipes := new(execPipes) + + if options == nil { + return nil, nil, fmt.Errorf("must provide an ExecOptions struct to ExecContainer: %w", define.ErrInvalidArg) + } + if len(options.Cmd) == 0 { + return nil, nil, fmt.Errorf("must provide a command to execute: %w", define.ErrInvalidArg) + } + + if sessionID == "" { + return nil, nil, fmt.Errorf("must provide a session ID for exec: %w", define.ErrEmptyID) + } + + // create sync pipe to receive the pid + parentSyncPipe, childSyncPipe, err := newPipe() + if err != nil { + return nil, nil, fmt.Errorf("creating socket pair: %w", err) + } + pipes.syncPipe = parentSyncPipe + + defer func() { + if deferredErr != nil { + pipes.cleanup() + } + }() + + // create start pipe to set the cgroup before running + // attachToExec is responsible for closing parentStartPipe + childStartPipe, parentStartPipe, err := newPipe() + if err != nil { + return nil, nil, fmt.Errorf("creating socket pair: %w", err) + } + pipes.startPipe = parentStartPipe + + // create the attach pipe to allow attach socket to be created before + // $RUNTIME exec starts running. This is to make sure we can capture all output + // from the process through that socket, rather than half reading the log, half attaching to the socket + // attachToExec is responsible for closing parentAttachPipe + parentAttachPipe, childAttachPipe, err := newPipe() + if err != nil { + return nil, nil, fmt.Errorf("creating socket pair: %w", err) + } + pipes.attachPipe = parentAttachPipe + + childrenClosed := false + defer func() { + if !childrenClosed { + errorhandling.CloseQuiet(childSyncPipe) + errorhandling.CloseQuiet(childAttachPipe) + errorhandling.CloseQuiet(childStartPipe) + } + }() + + finalEnv := make([]string, 0, len(options.Env)) + for k, v := range options.Env { + finalEnv = append(finalEnv, fmt.Sprintf("%s=%s", k, v)) + } + + processFile, err := c.prepareProcessExec(options, finalEnv, sessionID) + if err != nil { + return nil, nil, err + } + defer processFile.Close() + + args, err := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), c.execPersistDir(sessionID), ociLog, define.NoLogging, c.config.LogTag) + if err != nil { + return nil, nil, err + } + + preserveFDs, filesToClose, extraFiles, err := getPreserveFdExtraFiles(options.PreserveFD, options.PreserveFDs) + if err != nil { + return nil, nil, err + } + + if preserveFDs > 0 { + args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(preserveFDs), 10))...) + } + + if options.Terminal { + args = append(args, "-t") + } + + if attachStdin { + args = append(args, "-i") + } + + // Append container ID and command + args = append(args, "-e") + // TODO make this optional when we can detach + args = append(args, "--exec-attach") + args = append(args, "--exec-process-spec", processFile.Name()) + + if len(options.ExitCommand) > 0 { + args = append(args, "--exit-command", options.ExitCommand[0]) + for _, arg := range options.ExitCommand[1:] { + args = append(args, []string{"--exit-command-arg", arg}...) + } + if options.ExitCommandDelay > 0 { + args = append(args, []string{"--exit-delay", strconv.FormatUint(uint64(options.ExitCommandDelay), 10)}...) + } + } + + logrus.WithFields(logrus.Fields{ + "args": args, + }).Debugf("running conmon: %s", r.conmonPath) + execCmd := exec.Command(r.conmonPath, args...) + + // TODO: This is commented because it doesn't make much sense in HTTP + // attach, and I'm not certain it does for non-HTTP attach as well. + // if streams != nil { + // // Don't add the InputStream to the execCmd. Instead, the data should be passed + // // through CopyDetachable + // if streams.AttachOutput { + // execCmd.Stdout = options.Streams.OutputStream + // } + // if streams.AttachError { + // execCmd.Stderr = options.Streams.ErrorStream + // } + // } + + conmonEnv, err := r.configureConmonEnv() + if err != nil { + return nil, nil, fmt.Errorf("configuring conmon env: %w", err) + } + + execCmd.ExtraFiles = extraFiles + + // we don't want to step on users fds they asked to preserve + // Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3 + execCmd.Env = r.conmonEnv + execCmd.Env = append(execCmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", preserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", preserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", preserveFDs+5)) + execCmd.Env = append(execCmd.Env, conmonEnv...) + + execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe) + execCmd.Dir = c.execBundlePath(sessionID) + execCmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + + err = execCmd.Start() + + // We don't need children pipes on the parent side + errorhandling.CloseQuiet(childSyncPipe) + errorhandling.CloseQuiet(childAttachPipe) + errorhandling.CloseQuiet(childStartPipe) + childrenClosed = true + + if err != nil { + return nil, nil, fmt.Errorf("cannot start container %s: %w", c.ID(), err) + } + if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { + return nil, nil, err + } + + // These fds were passed down to the runtime. Close them + // and not interfere + for _, f := range filesToClose { + errorhandling.CloseQuiet(f) + } + + return execCmd, pipes, nil +} + +// Attach to a container over HTTP +func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, execCmd *exec.Cmd, conmonPipeDataChan chan<- conmonPipeData, ociLog string, newSize *resize.TerminalSize, runtimeName string) (deferredErr error) { + // NOTE: As you may notice, the attach code is quite complex. + // Many things happen concurrently and yet are interdependent. + // If you ever change this function, make sure to write to the + // conmonPipeDataChan in case of an error. + + if pipes == nil || pipes.startPipe == nil || pipes.attachPipe == nil { + err := fmt.Errorf("must provide a start and attach pipe to finish an exec attach: %w", define.ErrInvalidArg) + conmonPipeDataChan <- conmonPipeData{-1, err} + return err + } + + defer func() { + if !pipes.startClosed { + errorhandling.CloseQuiet(pipes.startPipe) + pipes.startClosed = true + } + if !pipes.attachClosed { + errorhandling.CloseQuiet(pipes.attachPipe) + pipes.attachClosed = true + } + }() + + logrus.Debugf("Attaching to container %s exec session %s", c.ID(), sessionID) + + // set up the socket path, such that it is the correct length and location for exec + sockPath, err := c.execAttachSocketPath(sessionID) + if err != nil { + conmonPipeDataChan <- conmonPipeData{-1, err} + return err + } + + // 2: read from attachFd that the parent process has set up the console socket + if _, err := readConmonPipeData(runtimeName, pipes.attachPipe, ""); err != nil { + conmonPipeDataChan <- conmonPipeData{-1, err} + return err + } + + // resize before we start the container process + if newSize != nil { + err = c.ociRuntime.ExecAttachResize(c, sessionID, *newSize) + if err != nil { + logrus.Warnf("Resize failed: %v", err) + } + } + + // 2: then attach + conn, err := openUnixSocket(sockPath) + if err != nil { + conmonPipeDataChan <- conmonPipeData{-1, err} + return fmt.Errorf("failed to connect to container's attach socket: %v: %w", sockPath, err) + } + defer func() { + if err := conn.Close(); err != nil { + logrus.Errorf("Unable to close socket: %q", err) + } + }() + + attachStdout := true + attachStderr := true + attachStdin := true + if streams != nil { + attachStdout = streams.Stdout + attachStderr = streams.Stderr + attachStdin = streams.Stdin + } + + // Perform hijack + hijacker, ok := w.(http.Hijacker) + if !ok { + conmonPipeDataChan <- conmonPipeData{-1, err} + return errors.New("unable to hijack connection") + } + + httpCon, httpBuf, err := hijacker.Hijack() + if err != nil { + conmonPipeDataChan <- conmonPipeData{-1, err} + return fmt.Errorf("hijacking connection: %w", err) + } + + hijackDone <- true + + // Write a header to let the client know what happened + writeHijackHeader(r, httpBuf, isTerminal) + + // Force a flush after the header is written. + if err := httpBuf.Flush(); err != nil { + conmonPipeDataChan <- conmonPipeData{-1, err} + return fmt.Errorf("flushing HTTP hijack header: %w", err) + } + + go func() { + // Wait for conmon to succeed, when return. + if err := execCmd.Wait(); err != nil { + conmonPipeDataChan <- conmonPipeData{-1, err} + } else { + pid, err := readConmonPipeData(runtimeName, pipes.syncPipe, ociLog) + if err != nil { + hijackWriteError(err, c.ID(), isTerminal, httpBuf) + conmonPipeDataChan <- conmonPipeData{pid, err} + } else { + conmonPipeDataChan <- conmonPipeData{pid, err} + } + } + // We need to hold the connection open until the complete exec + // function has finished. This channel will be closed in a defer + // in that function, so we can wait for it here. + // Can't be a defer, because this would block the function from + // returning. + <-holdConnOpen + hijackWriteErrorAndClose(deferredErr, c.ID(), isTerminal, httpCon, httpBuf) + }() + + stdoutChan := make(chan error) + stdinChan := make(chan error) + + // Next, STDIN. Avoid entirely if attachStdin unset. + if attachStdin { + go func() { + logrus.Debugf("Beginning STDIN copy") + _, err := detach.Copy(conn, httpBuf, detachKeys) + logrus.Debugf("STDIN copy completed") + stdinChan <- err + }() + } + + // 4: send start message to child + if err := writeConmonPipeData(pipes.startPipe); err != nil { + return err + } + + // Handle STDOUT/STDERR *after* start message is sent + go func() { + var err error + if isTerminal { + // Hack: return immediately if attachStdout not set to + // emulate Docker. + // Basically, when terminal is set, STDERR goes nowhere. + // Everything does over STDOUT. + // Therefore, if not attaching STDOUT - we'll never copy + // anything from here. + logrus.Debugf("Performing terminal HTTP attach for container %s", c.ID()) + if attachStdout { + err = httpAttachTerminalCopy(conn, httpBuf, c.ID()) + } + } else { + logrus.Debugf("Performing non-terminal HTTP attach for container %s", c.ID()) + err = httpAttachNonTerminalCopy(conn, httpBuf, c.ID(), attachStdin, attachStdout, attachStderr) + } + stdoutChan <- err + logrus.Debugf("STDOUT/ERR copy completed") + }() + + for { + select { + case err := <-stdoutChan: + if err != nil { + return err + } + + return nil + case err := <-stdinChan: + if err != nil { + return err + } + // copy stdin is done, close it + if connErr := socketCloseWrite(conn); connErr != nil { + logrus.Errorf("Unable to close conn: %v", connErr) + } + case <-cancel: + return nil + } + } +} + +// prepareProcessExec returns the path of the process.json used in runc exec -p +// caller is responsible to close the returned *os.File if needed. +func (c *Container) prepareProcessExec(options *ExecOptions, env []string, sessionID string) (*os.File, error) { + f, err := os.CreateTemp(c.execBundlePath(sessionID), "exec-process-") + if err != nil { + return nil, err + } + pspec := new(spec.Process) + if err := JSONDeepCopy(c.config.Spec.Process, pspec); err != nil { + return nil, err + } + pspec.SelinuxLabel = c.config.ProcessLabel + pspec.Args = options.Cmd + + // We need to default this to false else it will inherit terminal as true + // from the container. + pspec.Terminal = false + if options.Terminal { + pspec.Terminal = true + } + if len(env) > 0 { + pspec.Env = append(pspec.Env, env...) + } + + // Add secret envs if they exist + manager, err := c.runtime.SecretsManager() + if err != nil { + return nil, err + } + for name, secr := range c.config.EnvSecrets { + _, data, err := manager.LookupSecretData(secr.Name) + if err != nil { + return nil, err + } + pspec.Env = append(pspec.Env, fmt.Sprintf("%s=%s", name, string(data))) + } + + if options.Cwd != "" { + pspec.Cwd = options.Cwd + } + + // if the user is empty, we should inherit the user that the container is currently running with + user := options.User + if user == "" { + logrus.Debugf("Set user to %s", c.config.User) + user = c.config.User + } + + overrides := c.getUserOverrides() + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides) + if err != nil { + return nil, err + } + + // The additional groups must always contain the user's primary group. + sgids := []uint32{uint32(execUser.Gid)} + + for _, sgid := range execUser.Sgids { + sgids = append(sgids, uint32(sgid)) + } + + // Always add the groups added through --group-add, no matter the exec UID:GID. + if len(c.config.Groups) > 0 { + additionalSgids, err := lookup.GetContainerGroups(c.config.Groups, c.state.Mountpoint, overrides) + if err != nil { + return nil, fmt.Errorf("looking up supplemental groups for container %s exec session %s: %w", c.ID(), sessionID, err) + } + sgids = append(sgids, additionalSgids...) + } + + // Avoid duplicates + slices.Sort(sgids) + sgids = slices.Compact(sgids) + + processUser := spec.User{ + UID: uint32(execUser.Uid), + GID: uint32(execUser.Gid), + AdditionalGids: sgids, + } + pspec.User = processUser + + if c.config.Umask != "" { + umask, err := c.umask() + if err != nil { + return nil, err + } + pspec.User.Umask = &umask + } + + if err := c.setProcessCapabilitiesExec(options, user, execUser, pspec); err != nil { + return nil, err + } + + hasHomeSet := false + for _, s := range pspec.Env { + if strings.HasPrefix(s, "HOME=") { + hasHomeSet = true + break + } + } + if !hasHomeSet { + pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home)) + } + + processJSON, err := json.Marshal(pspec) + if err != nil { + return nil, err + } + + if err := os.WriteFile(f.Name(), processJSON, 0o644); err != nil { + return nil, err + } + return f, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon_exec_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_exec_freebsd.go new file mode 100644 index 00000000..71323b0a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_exec_freebsd.go @@ -0,0 +1,12 @@ +//go:build !remote + +package libpod + +import ( + "github.com/moby/sys/user" + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +func (c *Container) setProcessCapabilitiesExec(_ *ExecOptions, _ string, _ *user.ExecUser, _ *spec.Process) error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon_exec_linux.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_exec_linux.go new file mode 100644 index 00000000..78c5c622 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_exec_linux.go @@ -0,0 +1,41 @@ +//go:build !remote + +package libpod + +import ( + "github.com/moby/sys/user" + spec "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/common/pkg/capabilities" +) + +func (c *Container) setProcessCapabilitiesExec(options *ExecOptions, user string, execUser *user.ExecUser, pspec *spec.Process) error { + ctrSpec, err := c.specFromState() + if err != nil { + return err + } + + allCaps, err := capabilities.BoundingSet() + if err != nil { + return err + } + if options.Privileged { + pspec.Capabilities.Bounding = allCaps + } else { + pspec.Capabilities.Bounding = ctrSpec.Process.Capabilities.Bounding + } + + // Always unset the inheritable capabilities similarly to what the Linux kernel does + // They are used only when using capabilities with uid != 0. + pspec.Capabilities.Inheritable = []string{} + + if execUser.Uid == 0 { + pspec.Capabilities.Effective = pspec.Capabilities.Bounding + pspec.Capabilities.Permitted = pspec.Capabilities.Bounding + } else if user == c.config.User { + pspec.Capabilities.Effective = ctrSpec.Process.Capabilities.Effective + pspec.Capabilities.Inheritable = ctrSpec.Process.Capabilities.Effective + pspec.Capabilities.Permitted = ctrSpec.Process.Capabilities.Effective + pspec.Capabilities.Ambient = ctrSpec.Process.Capabilities.Effective + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_freebsd.go new file mode 100644 index 00000000..a147b3af --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_freebsd.go @@ -0,0 +1,33 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "os" + "os/exec" +) + +func (r *ConmonOCIRuntime) createRootlessContainer(_ *Container, _ *ContainerCheckpointOptions, _ bool) (int64, error) { + return -1, errors.New("unsupported (*ConmonOCIRuntime) createRootlessContainer") +} + +// Run the closure with the container's socket label set +func (r *ConmonOCIRuntime) withContainerSocketLabel(_ *Container, closure func() error) error { + // No label support yet + return closure() +} + +// moveConmonToCgroupAndSignal gets a container's cgroupParent and moves the conmon process to that cgroup +// it then signals for conmon to start by sending nonce data down the start fd +func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(_ *Container, _ *exec.Cmd, startFd *os.File) error { + // No equivalent to cgroup on FreeBSD, just signal conmon to start + if err := writeConmonPipeData(startFd); err != nil { + return err + } + return nil +} + +func moveToRuntimeCgroup() error { + return errors.New("moveToRuntimeCgroup not supported on freebsd") +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_conmon_linux.go b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_linux.go new file mode 100644 index 00000000..02567c09 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_conmon_linux.go @@ -0,0 +1,395 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "sync" + + "github.com/containers/podman/v5/pkg/errorhandling" + "github.com/containers/podman/v5/pkg/rootless" + runcconfig "github.com/opencontainers/cgroups" + devices "github.com/opencontainers/cgroups/devices/config" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/systemd" + pmount "go.podman.io/storage/pkg/mount" + "golang.org/x/sys/unix" +) + +func (r *ConmonOCIRuntime) createRootlessContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions, hideFiles bool) (int64, error) { + type result struct { + restoreDuration int64 + err error + } + ch := make(chan result) + go func() { + runtime.LockOSThread() + restoreDuration, err := func() (int64, error) { + fd, err := os.Open(fmt.Sprintf("/proc/%d/task/%d/ns/mnt", os.Getpid(), unix.Gettid())) + if err != nil { + return 0, err + } + defer errorhandling.CloseQuiet(fd) + + rootPath, err := ctr.getRootPathForOCI() + if err != nil { + return 0, err + } + + // create a new mountns on the current thread + if err = unix.Unshare(unix.CLONE_NEWNS); err != nil { + return 0, err + } + defer func() { + err := unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS) + if err == nil { + // If we are able to reset the previous mount namespace, unlock the thread and reuse it + runtime.UnlockOSThread() + } else { + // otherwise, leave the thread locked and the Go runtime will terminate it + logrus.Errorf("Unable to reset the previous mount namespace: %q", err) + } + }() + + getMounts := sync.OnceValues(pmount.GetMounts) + + // bind mount the containers' mount path to the path where the OCI runtime expects it to be + // if the container is already mounted at the expected path, do not cover the mountpoint. + if rootPath != "" && filepath.Clean(ctr.state.Mountpoint) != filepath.Clean(rootPath) { + mounts, err := getMounts() + if err != nil { + return 0, err + } + + byMountpoint := make(map[string]*pmount.Info) + for _, m := range mounts { + byMountpoint[m.Mountpoint] = m + } + isShared := false + var parentMount string + for dir := filepath.Dir(rootPath); ; dir = filepath.Dir(dir) { + if m, found := byMountpoint[dir]; found { + //nolint:staticcheck // false positive?! it claims the value of parentMount is not used but we use it below for the mount + parentMount = dir + for o := range strings.SplitSeq(m.Optional, ",") { + opt := strings.Split(o, ":") + if opt[0] == "shared" { + isShared = true + break + } + } + break + } + if dir == "/" { + return 0, fmt.Errorf("cannot find mountpoint for the root path") + } + } + + // do not propagate the bind mount on the parent mount namespace + if err := unix.Mount("", parentMount, "", unix.MS_SLAVE, ""); err != nil { + return 0, fmt.Errorf("failed to make %s slave: %w", parentMount, err) + } + if err := unix.Mount(ctr.state.Mountpoint, rootPath, "", unix.MS_BIND, ""); err != nil { + return 0, fmt.Errorf("failed to bind mount %s to %s: %w", ctr.state.Mountpoint, rootPath, err) + } + if isShared { + // we need to restore the shared propagation of the parent mount so that we don't break -v $SRC:$DST:shared in the container + // if $SRC is on the same mount as the root path + if err := unix.Mount("", parentMount, "", unix.MS_SHARED, ""); err != nil { + return 0, fmt.Errorf("failed to restore MS_SHARED propagation for %s: %w", parentMount, err) + } + } + } + + if hideFiles { + // don't spread our mounts around. We are setting only /sys to be slave + // so that the cleanup process is still able to umount the storage and the + // changes are propagated to the host. + err = unix.Mount("/sys", "/sys", "none", unix.MS_REC|unix.MS_SLAVE, "") + if err != nil { + return 0, fmt.Errorf("cannot make /sys slave: %w", err) + } + mounts, err := getMounts() + if err != nil { + return 0, err + } + for _, m := range mounts { + if !strings.HasPrefix(m.Mountpoint, "/sys/kernel") { + continue + } + err = unix.Unmount(m.Mountpoint, 0) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return 0, fmt.Errorf("cannot unmount %s: %w", m.Mountpoint, err) + } + } + } + return r.createOCIContainer(ctr, restoreOptions) + }() + ch <- result{ + restoreDuration: restoreDuration, + err: err, + } + }() + res := <-ch + return res.restoreDuration, res.err +} + +// Run the closure with the container's socket label set +func (r *ConmonOCIRuntime) withContainerSocketLabel(ctr *Container, closure func() error) error { + runtime.LockOSThread() + if err := selinux.SetSocketLabel(ctr.ProcessLabel()); err != nil { + return err + } + err := closure() + // Ignore error returned from SetSocketLabel("") call, + // can't recover. + if labelErr := selinux.SetSocketLabel(""); labelErr == nil { + // Unlock the thread only if the process label could be restored + // successfully. Otherwise leave the thread locked and the Go runtime + // will terminate it once it returns to the threads pool. + runtime.UnlockOSThread() + } else { + logrus.Errorf("Unable to reset socket label: %q", labelErr) + } + return err +} + +// Create systemd unit name for cgroup scopes. +func createUnitName(prefix string, name string) string { + return fmt.Sprintf("%s-%s.scope", prefix, name) +} + +// moveConmonToCgroupAndSignal gets a container's cgroupParent and moves the conmon process to that cgroup +// it then signals for conmon to start by sending nonce data down the start fd +func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File) error { + mustCreateCgroup := !ctr.config.NoCgroups + + // If cgroup creation is disabled - just signal. + switch ctr.config.CgroupsMode { + case "disabled", "no-conmon", cgroupSplit: + mustCreateCgroup = false + } + + // $INVOCATION_ID is set by systemd when running as a service. + if ctr.runtime.RemoteURI() == "" && os.Getenv("INVOCATION_ID") != "" { + mustCreateCgroup = false + } + + if mustCreateCgroup { + // Usually rootless users are not allowed to configure cgroupfs. + // There are cases though, where it is allowed, e.g. if the cgroup + // is manually configured and chowned). Avoid detecting all + // such cases and simply use a lower log level. + logLevel := logrus.WarnLevel + if rootless.IsRootless() { + logLevel = logrus.InfoLevel + } + // TODO: This should be a switch - we are not guaranteed that + // there are only 2 valid cgroup managers + cgroupParent := ctr.CgroupParent() + cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon") + cgroupResources, err := GetLimits(ctr.LinuxResources()) + if err != nil { + logrus.StandardLogger().Log(logLevel, "Could not get ctr resources") + } + if ctr.CgroupManager() == config.SystemdCgroupsManager { + unitName := createUnitName("libpod-conmon", ctr.ID()) + realCgroupParent := cgroupParent + splitParent := strings.Split(cgroupParent, "/") + if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 { + realCgroupParent = splitParent[len(splitParent)-1] + } + + logrus.Infof("Running conmon under slice %s and unitName %s", realCgroupParent, unitName) + if err := systemd.RunUnderSystemdScope(cmd.Process.Pid, realCgroupParent, unitName); err != nil { + logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to systemd sandbox cgroup: %v", err) + } + } else { + control, err := cgroups.New(cgroupPath, &cgroupResources) + if err != nil { + logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } else if err := control.AddPid(cmd.Process.Pid); err != nil { + // we need to remove this defer and delete the cgroup once conmon exits + // maybe need a conmon monitor? + logrus.StandardLogger().Logf(logLevel, "Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } + } + } + + /* We set the cgroup, now the child can start creating children */ + return writeConmonPipeData(startFd) +} + +// GetLimits converts spec resource limits to cgroup consumable limits +func GetLimits(resource *spec.LinuxResources) (runcconfig.Resources, error) { + if resource == nil { + resource = &spec.LinuxResources{} + } + final := &runcconfig.Resources{} + devs := []*devices.Rule{} + + // Devices + for _, entry := range resource.Devices { + if entry.Major == nil || entry.Minor == nil { + continue + } + runeType := 'a' + switch entry.Type { + case "b": + runeType = 'b' + case "c": + runeType = 'c' + } + + devs = append(devs, &devices.Rule{ + Type: devices.Type(runeType), + Major: *entry.Major, + Minor: *entry.Minor, + Permissions: devices.Permissions(entry.Access), + Allow: entry.Allow, + }) + } + final.Devices = devs + + // HugepageLimits + pageLimits := []*runcconfig.HugepageLimit{} + for _, entry := range resource.HugepageLimits { + pageLimits = append(pageLimits, &runcconfig.HugepageLimit{ + Pagesize: entry.Pagesize, + Limit: entry.Limit, + }) + } + final.HugetlbLimit = pageLimits + + // Networking + netPriorities := []*runcconfig.IfPrioMap{} + if resource.Network != nil { + for _, entry := range resource.Network.Priorities { + netPriorities = append(netPriorities, &runcconfig.IfPrioMap{ + Interface: entry.Name, + Priority: int64(entry.Priority), + }) + } + } + final.NetPrioIfpriomap = netPriorities + rdma := make(map[string]runcconfig.LinuxRdma) + for name, entry := range resource.Rdma { + rdma[name] = runcconfig.LinuxRdma{HcaHandles: entry.HcaHandles, HcaObjects: entry.HcaObjects} + } + final.Rdma = rdma + + // Memory + if resource.Memory != nil { + if resource.Memory.Limit != nil { + final.Memory = *resource.Memory.Limit + } + if resource.Memory.Reservation != nil { + final.MemoryReservation = *resource.Memory.Reservation + } + if resource.Memory.Swap != nil { + final.MemorySwap = *resource.Memory.Swap + } + if resource.Memory.Swappiness != nil { + final.MemorySwappiness = resource.Memory.Swappiness + } + } + + // CPU + if resource.CPU != nil { + if resource.CPU.Period != nil { + final.CpuPeriod = *resource.CPU.Period + } + if resource.CPU.Quota != nil { + final.CpuQuota = *resource.CPU.Quota + } + if resource.CPU.RealtimePeriod != nil { + final.CpuRtPeriod = *resource.CPU.RealtimePeriod + } + if resource.CPU.RealtimeRuntime != nil { + final.CpuRtRuntime = *resource.CPU.RealtimeRuntime + } + if resource.CPU.Shares != nil { + final.CpuShares = *resource.CPU.Shares + } + final.CpusetCpus = resource.CPU.Cpus + final.CpusetMems = resource.CPU.Mems + } + + // BlkIO + if resource.BlockIO != nil { + if len(resource.BlockIO.ThrottleReadBpsDevice) > 0 { + for _, entry := range resource.BlockIO.ThrottleReadBpsDevice { + throttle := runcconfig.NewThrottleDevice(entry.Major, entry.Minor, entry.Rate) + final.BlkioThrottleReadBpsDevice = append(final.BlkioThrottleReadBpsDevice, throttle) + } + } + if len(resource.BlockIO.ThrottleWriteBpsDevice) > 0 { + for _, entry := range resource.BlockIO.ThrottleWriteBpsDevice { + throttle := runcconfig.NewThrottleDevice(entry.Major, entry.Minor, entry.Rate) + final.BlkioThrottleWriteBpsDevice = append(final.BlkioThrottleWriteBpsDevice, throttle) + } + } + if len(resource.BlockIO.ThrottleReadIOPSDevice) > 0 { + for _, entry := range resource.BlockIO.ThrottleReadIOPSDevice { + throttle := runcconfig.NewThrottleDevice(entry.Major, entry.Minor, entry.Rate) + final.BlkioThrottleReadIOPSDevice = append(final.BlkioThrottleReadIOPSDevice, throttle) + } + } + if len(resource.BlockIO.ThrottleWriteIOPSDevice) > 0 { + for _, entry := range resource.BlockIO.ThrottleWriteIOPSDevice { + throttle := runcconfig.NewThrottleDevice(entry.Major, entry.Minor, entry.Rate) + final.BlkioThrottleWriteIOPSDevice = append(final.BlkioThrottleWriteIOPSDevice, throttle) + } + } + if resource.BlockIO.LeafWeight != nil { + final.BlkioLeafWeight = *resource.BlockIO.LeafWeight + } + if resource.BlockIO.Weight != nil { + final.BlkioWeight = *resource.BlockIO.Weight + } + if len(resource.BlockIO.WeightDevice) > 0 { + for _, entry := range resource.BlockIO.WeightDevice { + var w, lw uint16 + if entry.Weight != nil { + w = *entry.Weight + } + if entry.LeafWeight != nil { + lw = *entry.LeafWeight + } + weight := runcconfig.NewWeightDevice(entry.Major, entry.Minor, w, lw) + final.BlkioWeightDevice = append(final.BlkioWeightDevice, weight) + } + } + } + + // Pids + if resource.Pids != nil { + final.PidsLimit = resource.Pids.Limit + } + + // Networking + if resource.Network != nil { + if resource.Network.ClassID != nil { + final.NetClsClassid = *resource.Network.ClassID + } + } + + // Unified state + final.Unified = resource.Unified + return *final, nil +} + +func moveToRuntimeCgroup() error { + return cgroups.MoveUnderCgroupSubtree("runtime") +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_missing.go b/vendor/github.com/containers/podman/v5/libpod/oci_missing.go new file mode 100644 index 00000000..a874d6bf --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_missing.go @@ -0,0 +1,249 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "net/http" + "path/filepath" + "sync" + + "github.com/containers/podman/v5/libpod/define" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/resize" +) + +var ( + // Only create each missing runtime once. + // Creation makes error messages we don't want to duplicate. + missingRuntimes map[string]*MissingRuntime + // We need a lock for this + missingRuntimesLock sync.Mutex +) + +// MissingRuntime is used when the OCI runtime requested by the container is +// missing (not installed or not in the configuration file). +type MissingRuntime struct { + // Name is the name of the missing runtime. Will be used in errors. + name string + // exitsDir is the directory for exit files. + exitsDir string + // persistDir is the directory for exit and oom files. + persistDir string +} + +// Get a new MissingRuntime for the given name. +// Requires a libpod Runtime so we can make a sane path for the exits dir. +func getMissingRuntime(name string, r *Runtime) OCIRuntime { + missingRuntimesLock.Lock() + defer missingRuntimesLock.Unlock() + + if missingRuntimes == nil { + missingRuntimes = make(map[string]*MissingRuntime) + } + + runtime, ok := missingRuntimes[name] + if ok { + return runtime + } + + // Once for each missing runtime, we want to error. + logrus.Errorf("OCI Runtime %s is in use by a container, but is not available (not in configuration file or not installed)", name) + + newRuntime := new(MissingRuntime) + newRuntime.name = name + newRuntime.exitsDir = filepath.Join(r.config.Engine.TmpDir, "exits") + newRuntime.persistDir = filepath.Join(r.config.Engine.TmpDir, "persist") + + missingRuntimes[name] = newRuntime + + return newRuntime +} + +// Name is the name of the missing runtime +func (r *MissingRuntime) Name() string { + return fmt.Sprintf("%s (missing/not available)", r.name) +} + +// Path is not available as the runtime is missing +func (r *MissingRuntime) Path() string { + return "(missing/not available)" +} + +// CreateContainer is not available as the runtime is missing +func (r *MissingRuntime) CreateContainer(_ *Container, _ *ContainerCheckpointOptions) (int64, error) { + return 0, r.printError() +} + +// StartContainer is not available as the runtime is missing +func (r *MissingRuntime) StartContainer(_ *Container) error { + return r.printError() +} + +// UpdateContainer is not available as the runtime is missing +func (r *MissingRuntime) UpdateContainer(_ *Container, _ *spec.LinuxResources) error { + return r.printError() +} + +// KillContainer is not available as the runtime is missing +// TODO: We could attempt to unix.Kill() the PID as recorded in the state if we +// really want to smooth things out? Won't be perfect, but if the container has +// a PID namespace it could be enough? +func (r *MissingRuntime) KillContainer(_ *Container, _ uint, _ bool) error { + return r.printError() +} + +// StopContainer is not available as the runtime is missing +func (r *MissingRuntime) StopContainer(_ *Container, _ uint, _ bool) error { + return r.printError() +} + +// DeleteContainer is not available as the runtime is missing +func (r *MissingRuntime) DeleteContainer(_ *Container) error { + return r.printError() +} + +// PauseContainer is not available as the runtime is missing +func (r *MissingRuntime) PauseContainer(_ *Container) error { + return r.printError() +} + +// UnpauseContainer is not available as the runtime is missing +func (r *MissingRuntime) UnpauseContainer(_ *Container) error { + return r.printError() +} + +// Attach is not available as the runtime is missing +func (r *MissingRuntime) Attach(_ *Container, _ *AttachOptions) error { + return r.printError() +} + +// HTTPAttach is not available as the runtime is missing +func (r *MissingRuntime) HTTPAttach(_ *Container, _ *http.Request, _ http.ResponseWriter, _ *HTTPAttachStreams, _ *string, _ <-chan bool, _ chan<- bool, _, _ bool) error { + return r.printError() +} + +// AttachResize is not available as the runtime is missing +func (r *MissingRuntime) AttachResize(_ *Container, _ resize.TerminalSize) error { + return r.printError() +} + +// ExecContainer is not available as the runtime is missing +func (r *MissingRuntime) ExecContainer(_ *Container, _ string, _ *ExecOptions, _ *define.AttachStreams, _ *resize.TerminalSize) (int, chan error, error) { + return -1, nil, r.printError() +} + +// ExecContainerHTTP is not available as the runtime is missing +func (r *MissingRuntime) ExecContainerHTTP(_ *Container, _ string, _ *ExecOptions, _ *http.Request, _ http.ResponseWriter, + _ *HTTPAttachStreams, _ <-chan bool, _ chan<- bool, _ <-chan bool, _ *resize.TerminalSize) (int, chan error, error) { + return -1, nil, r.printError() +} + +// ExecContainerDetached is not available as the runtime is missing +func (r *MissingRuntime) ExecContainerDetached(_ *Container, _ string, _ *ExecOptions, _ bool) (int, error) { + return -1, r.printError() +} + +// ExecAttachResize is not available as the runtime is missing. +func (r *MissingRuntime) ExecAttachResize(_ *Container, _ string, _ resize.TerminalSize) error { + return r.printError() +} + +// ExecStopContainer is not available as the runtime is missing. +// TODO: We can also investigate using unix.Kill() on the PID of the exec +// session here if we want to make stopping containers possible. Won't be +// perfect, though. +func (r *MissingRuntime) ExecStopContainer(_ *Container, _ string, _ uint) error { + return r.printError() +} + +// ExecUpdateStatus is not available as the runtime is missing. +func (r *MissingRuntime) ExecUpdateStatus(_ *Container, _ string) (bool, error) { + return false, r.printError() +} + +// CheckpointContainer is not available as the runtime is missing +func (r *MissingRuntime) CheckpointContainer(_ *Container, _ ContainerCheckpointOptions) (int64, error) { + return 0, r.printError() +} + +// CheckConmonRunning is not available as the runtime is missing +func (r *MissingRuntime) CheckConmonRunning(_ *Container) (bool, error) { + return false, r.printError() +} + +// SupportsCheckpoint returns false as checkpointing requires a working runtime +func (r *MissingRuntime) SupportsCheckpoint() bool { + return false +} + +// SupportsJSONErrors returns false as there is no runtime to give errors +func (r *MissingRuntime) SupportsJSONErrors() bool { + return false +} + +// SupportsNoCgroups returns false as there is no runtime to create containers +func (r *MissingRuntime) SupportsNoCgroups() bool { + return false +} + +// SupportsKVM checks if the OCI runtime supports running containers +// without KVM separation +func (r *MissingRuntime) SupportsKVM() bool { + return false +} + +// AttachSocketPath does not work as there is no runtime to attach to. +// (Theoretically we could follow ExitFilePath but there is no guarantee the +// container is running and thus has an attach socket...) +func (r *MissingRuntime) AttachSocketPath(_ *Container) (string, error) { + return "", r.printError() +} + +// ExecAttachSocketPath does not work as there is no runtime to attach to. +// (Again, we could follow ExitFilePath, but no guarantee there is an existing +// and running exec session) +func (r *MissingRuntime) ExecAttachSocketPath(_ *Container, _ string) (string, error) { + return "", r.printError() +} + +// ExitFilePath returns the exit file path for containers. +// Here, we mimic what ConmonOCIRuntime does, because there is a chance that the +// container in question is still running happily (config file modified to +// remove a runtime, for example). We can't find the runtime to do anything to +// the container, but Conmon should still place an exit file for it. +func (r *MissingRuntime) ExitFilePath(ctr *Container) (string, error) { + if ctr == nil { + return "", fmt.Errorf("must provide a valid container to get exit file path: %w", define.ErrInvalidArg) + } + return filepath.Join(r.exitsDir, ctr.ID()), nil +} + +// OOMFilePath returns the oom file path for a container. +// The oom file will only exist if the container was oom killed. +func (r *MissingRuntime) OOMFilePath(ctr *Container) (string, error) { + return filepath.Join(r.persistDir, ctr.ID(), "oom"), nil +} + +// PersistDirectoryPath is the path to the container's persist directory. +// It may include files like the exit file and OOM file. +func (r *MissingRuntime) PersistDirectoryPath(ctr *Container) (string, error) { + return filepath.Join(r.persistDir, ctr.ID()), nil +} + +// RuntimeInfo returns information on the missing runtime +func (r *MissingRuntime) RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) { + ocirt := define.OCIRuntimeInfo{ + Name: r.name, + Path: "missing", + Package: "missing", + Version: "missing", + } + return nil, &ocirt, nil +} + +// Return an error indicating the runtime is missing +func (r *MissingRuntime) printError() error { + return fmt.Errorf("runtime %s is missing: %w", r.name, define.ErrOCIRuntimeNotFound) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/oci_util.go b/vendor/github.com/containers/podman/v5/libpod/oci_util.go new file mode 100644 index 00000000..29695206 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/oci_util.go @@ -0,0 +1,170 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "net" + "os" + "regexp" + "strings" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" +) + +// Timeout before declaring that runtime has failed to kill a given +// container +const killContainerTimeout = 5 * time.Second + +// ociError is used to parse the OCI runtime JSON log. It is not part of the +// OCI runtime specifications, it follows what runc does +type ociError struct { + Level string `json:"level,omitempty"` + Time string `json:"time,omitempty"` + Msg string `json:"msg,omitempty"` +} + +// Bind ports to keep them closed on the host +func bindPorts(ports []types.PortMapping) ([]*os.File, error) { + var files []*os.File + sctpWarning := true + for _, port := range ports { + isV6 := net.ParseIP(port.HostIP).To4() == nil + if port.HostIP == "" { + isV6 = false + } + protocols := strings.SplitSeq(port.Protocol, ",") + for protocol := range protocols { + for i := uint16(0); i < port.Range; i++ { + f, err := bindPort(protocol, port.HostIP, port.HostPort+i, isV6, &sctpWarning) + if err != nil { + // close all open ports in case of early error so we do not + // rely garbage collector to close them + for _, f := range files { + f.Close() + } + return nil, err + } + if f != nil { + files = append(files, f) + } + } + } + } + return files, nil +} + +func bindPort(protocol, hostIP string, port uint16, isV6 bool, sctpWarning *bool) (*os.File, error) { + var file *os.File + switch protocol { + case "udp": + var ( + addr *net.UDPAddr + err error + ) + if isV6 { + addr, err = net.ResolveUDPAddr("udp6", fmt.Sprintf("[%s]:%d", hostIP, port)) + } else { + addr, err = net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", hostIP, port)) + } + if err != nil { + return nil, fmt.Errorf("cannot resolve the UDP address: %w", err) + } + + proto := "udp4" + if isV6 { + proto = "udp6" + } + server, err := net.ListenUDP(proto, addr) + if err != nil { + return nil, fmt.Errorf("cannot listen on the UDP port: %w", err) + } + file, err = server.File() + if err != nil { + return nil, fmt.Errorf("cannot get file for UDP socket: %w", err) + } + // close the listener + // note that this does not affect the fd, see the godoc for server.File() + err = server.Close() + if err != nil { + logrus.Warnf("Failed to close connection: %v", err) + } + + case "tcp": + var ( + addr *net.TCPAddr + err error + ) + if isV6 { + addr, err = net.ResolveTCPAddr("tcp6", fmt.Sprintf("[%s]:%d", hostIP, port)) + } else { + addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", hostIP, port)) + } + if err != nil { + return nil, fmt.Errorf("cannot resolve the TCP address: %w", err) + } + + proto := "tcp4" + if isV6 { + proto = "tcp6" + } + server, err := net.ListenTCP(proto, addr) + if err != nil { + return nil, fmt.Errorf("cannot listen on the TCP port: %w", err) + } + file, err = server.File() + if err != nil { + return nil, fmt.Errorf("cannot get file for TCP socket: %w", err) + } + // close the listener + // note that this does not affect the fd, see the godoc for server.File() + err = server.Close() + if err != nil { + logrus.Warnf("Failed to close connection: %v", err) + } + + case "sctp": + if *sctpWarning { + logrus.Info("Port reservation for SCTP is not supported") + *sctpWarning = false + } + default: + return nil, fmt.Errorf("unknown protocol %s", protocol) + } + return file, nil +} + +func getOCIRuntimeError(name, runtimeMsg string) error { + includeFullOutput := logrus.GetLevel() == logrus.DebugLevel + + if match := regexp.MustCompile("(?i).*permission denied.*|.*operation not permitted.*").FindString(runtimeMsg); match != "" { + errStr := match + if includeFullOutput { + errStr = runtimeMsg + } + return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrOCIRuntimePermissionDenied) + } + if match := regexp.MustCompile("(?i).*executable file not found in.*|.*no such file or directory.*|.*open executable.*").FindString(runtimeMsg); match != "" { + errStr := match + if includeFullOutput { + errStr = runtimeMsg + } + return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrOCIRuntimeNotFound) + } + if match := regexp.MustCompile("`/proc/[a-z0-9-].+/attr.*`").FindString(runtimeMsg); match != "" { + errStr := match + if includeFullOutput { + errStr = runtimeMsg + } + if strings.HasSuffix(match, "/exec`") { + return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrSetSecurityAttribute) + } else if strings.HasSuffix(match, "/current`") { + return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrGetSecurityAttribute) + } + return fmt.Errorf("%s: %s: %w", name, strings.Trim(errStr, "\n"), define.ErrSecurityAttribute) + } + return fmt.Errorf("%s: %s: %w", name, strings.Trim(runtimeMsg, "\n"), define.ErrOCIRuntime) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/options.go b/vendor/github.com/containers/podman/v5/libpod/options.go new file mode 100644 index 00000000..d5055d6c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/options.go @@ -0,0 +1,2288 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "fmt" + "maps" + "net" + "os" + "path/filepath" + "slices" + "strings" + "syscall" + "time" + + "github.com/containers/buildah/pkg/parse" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/pkg/namespaces" + "github.com/containers/podman/v5/pkg/util" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/sirupsen/logrus" + nettypes "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/secrets" + "go.podman.io/image/v5/manifest" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/regexp" +) + +var umaskRegex = regexp.Delayed(`^[0-7]{1,4}$`) + +// WithStorageConfig uses the given configuration to set up container storage. +// If this is not specified, the system default configuration will be used +// instead. +func WithStorageConfig(config storage.StoreOptions) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + setField := false + + if config.RunRoot != "" { + rt.storageConfig.RunRoot = config.RunRoot + rt.storageSet.RunRootSet = true + setField = true + } + + if config.GraphRoot != "" { + rt.storageConfig.GraphRoot = config.GraphRoot + rt.storageSet.GraphRootSet = true + setField = true + } + + graphDriverChanged := false + if config.GraphDriverName != "" { + rt.storageConfig.GraphDriverName = config.GraphDriverName + rt.storageSet.GraphDriverNameSet = true + setField = true + graphDriverChanged = true + } + + if config.GraphDriverOptions != nil { + if graphDriverChanged { + rt.storageConfig.GraphDriverOptions = make([]string, len(config.GraphDriverOptions)) + copy(rt.storageConfig.GraphDriverOptions, config.GraphDriverOptions) + } else { + rt.storageConfig.GraphDriverOptions = config.GraphDriverOptions + } + setField = true + } + + if config.UIDMap != nil { + rt.storageConfig.UIDMap = make([]idtools.IDMap, len(config.UIDMap)) + copy(rt.storageConfig.UIDMap, config.UIDMap) + } + + if config.GIDMap != nil { + rt.storageConfig.GIDMap = make([]idtools.IDMap, len(config.GIDMap)) + copy(rt.storageConfig.GIDMap, config.GIDMap) + } + + if config.PullOptions != nil { + rt.storageConfig.PullOptions = maps.Clone(config.PullOptions) + } + + // If any one of runroot, graphroot, graphdrivername, + // or graphdriveroptions are set, then GraphRoot and RunRoot + // must be set + if setField { + storeOpts, err := storage.DefaultStoreOptions() + if err != nil { + return err + } + if rt.storageConfig.GraphRoot == "" { + rt.storageConfig.GraphRoot = storeOpts.GraphRoot + } + if rt.storageConfig.RunRoot == "" { + rt.storageConfig.RunRoot = storeOpts.RunRoot + } + } + + return nil + } +} + +func WithTransientStore(transientStore bool) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.storageConfig.TransientStore = transientStore + + return nil + } +} + +func WithImageStore(imageStore string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.storageConfig.ImageStore = imageStore + + return nil + } +} + +// WithOCIRuntime specifies an OCI runtime to use for running containers. +func WithOCIRuntime(runtime string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + if runtime == "" { + return fmt.Errorf("must provide a valid path: %w", define.ErrInvalidArg) + } + + rt.config.Engine.OCIRuntime = runtime + + return nil + } +} + +// WithCtrOCIRuntime specifies an OCI runtime in container's config. +func WithCtrOCIRuntime(runtime string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.OCIRuntime = runtime + return nil + } +} + +// WithConmonPath specifies the path to the conmon binary which manages the +// runtime. +func WithConmonPath(path string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + if path == "" { + return fmt.Errorf("must provide a valid path: %w", define.ErrInvalidArg) + } + + rt.config.Engine.ConmonPath.Set([]string{path}) + + return nil + } +} + +// WithNetworkCmdPath specifies the path to the slirp4netns binary which manages the +// runtime. +func WithNetworkCmdPath(path string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.config.Engine.NetworkCmdPath = path + + return nil + } +} + +// WithNetworkBackend specifies the name of the network backend. +func WithNetworkBackend(name string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.config.Network.NetworkBackend = name + + return nil + } +} + +// WithCgroupManager specifies the manager implementation name which is used to +// handle cgroups for containers. +// Current valid values are "cgroupfs" and "systemd". +func WithCgroupManager(manager string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + if manager != config.CgroupfsCgroupsManager && manager != config.SystemdCgroupsManager { + return fmt.Errorf("cgroup manager must be one of %s and %s: %w", + config.CgroupfsCgroupsManager, config.SystemdCgroupsManager, define.ErrInvalidArg) + } + + rt.config.Engine.CgroupManager = manager + + return nil + } +} + +// WithStaticDir sets the directory that static runtime files which persist +// across reboots will be stored. +func WithStaticDir(dir string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.config.Engine.StaticDir = dir + + return nil + } +} + +// WithRegistriesConf configures the runtime to always use specified +// registries.conf for image processing. +func WithRegistriesConf(path string) RuntimeOption { + logrus.Debugf("Setting custom registries.conf: %q", path) + return func(rt *Runtime) error { + if err := fileutils.Exists(path); err != nil { + return fmt.Errorf("locating specified registries.conf: %w", err) + } + if rt.imageContext == nil { + rt.imageContext = &types.SystemContext{ + BigFilesTemporaryDir: parse.GetTempDir(), + } + } + + rt.imageContext.SystemRegistriesConfPath = path + return nil + } +} + +// WithDatabaseBackend configures the runtime's database backend. +func WithDatabaseBackend(value string) RuntimeOption { + logrus.Debugf("Setting custom database backend: %q", value) + return func(rt *Runtime) error { + // The value will be parsed later on. + rt.config.Engine.DBBackend = value + return nil + } +} + +// WithHooksDir sets the directories to look for OCI runtime hook configuration. +func WithHooksDir(hooksDirs ...string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + if slices.Contains(hooksDirs, "") { + return fmt.Errorf("empty-string hook directories are not supported: %w", define.ErrInvalidArg) + } + + rt.config.Engine.HooksDir.Set(hooksDirs) + return nil + } +} + +// WithCDI sets the devices to check for CDI configuration. +func WithCDI(devices []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.CDIDevices = devices + return nil + } +} + +func WithCDISpecDirs(cdiSpecDirs []string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.config.Engine.CdiSpecDirs.Set(cdiSpecDirs) + return nil + } +} + +// WithStorageOpts sets the devices to check for CDI configuration. +func WithStorageOpts(storageOpts map[string]string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.StorageOpts = storageOpts + return nil + } +} + +// WithDefaultMountsFile sets the file to look at for default mounts (mainly +// secrets). +// Note we are not saving this in the database as it is for testing purposes +// only. +func WithDefaultMountsFile(mountsFile string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + if mountsFile == "" { + return define.ErrInvalidArg + } + rt.config.Containers.DefaultMountsFile = mountsFile + return nil + } +} + +// WithTmpDir sets the directory that temporary runtime files which are not +// expected to survive across reboots will be stored. +// This should be located on a tmpfs mount (/tmp or /run for example). +func WithTmpDir(dir string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + rt.config.Engine.TmpDir = dir + + return nil + } +} + +// WithNetworkConfigDir sets the network configuration directory. +func WithNetworkConfigDir(dir string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.config.Network.NetworkConfigDir = dir + + return nil + } +} + +// WithNamespace sets the namespace for libpod. +// Namespaces are used to create scopes to separate containers and pods +// in the state. +// When namespace is set, libpod will only view containers and pods in +// the same namespace. All containers and pods created will default to +// the namespace set here. +// A namespace of "", the empty string, is equivalent to no namespace, +// and all containers and pods will be visible. +func WithNamespace(ns string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.config.Engine.Namespace = ns + + return nil + } +} + +// WithVolumePath sets the path under which all named volumes +// should be created. +// The path changes based on whether the user is running as root or not. +func WithVolumePath(volPath string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.config.Engine.VolumePath = volPath + rt.storageSet.VolumePathSet = true + + return nil + } +} + +// WithReset tells Libpod that the runtime will be used to perform a system +// reset. A number of checks at initialization are relaxed as the runtime is +// going to be used to remove all containers, pods, volumes, images, and +// networks. +func WithReset() RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.doReset = true + + return nil + } +} + +// WithRenumber tells Libpod that the runtime will be used to perform a system +// renumber. A number of checks on initialization related to locks are relaxed. +func WithRenumber() RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + rt.doRenumber = true + + return nil + } +} + +// WithEventsLogger sets the events backend to use. +// Currently supported values are "file" for file backend and "journald" for +// journald backend. +func WithEventsLogger(logger string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + + if !events.IsValidEventer(logger) { + return fmt.Errorf("%q is not a valid events backend: %w", logger, define.ErrInvalidArg) + } + + rt.config.Engine.EventsLogger = logger + return nil + } +} + +// WithEnableSDNotify sets a runtime option so we know whether to disable socket/FD +// listening +func WithEnableSDNotify() RuntimeOption { + return func(rt *Runtime) error { + rt.config.Engine.SDNotify = true + return nil + } +} + +// WithSyslog sets a runtime option so we know that we have to log to the syslog as well +func WithSyslog() RuntimeOption { + return func(rt *Runtime) error { + rt.syslog = true + return nil + } +} + +// WithRuntimeFlags adds the global runtime flags to the container config +func WithRuntimeFlags(runtimeFlags []string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return define.ErrRuntimeFinalized + } + rt.runtimeFlags = runtimeFlags + return nil + } +} + +// Container Creation Options + +// WithMaxLogSize sets the maximum size of container logs. +// Positive sizes are limits in bytes, -1 is unlimited. +func WithMaxLogSize(limit int64) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrRuntimeFinalized + } + ctr.config.LogSize = limit + + return nil + } +} + +// WithShmDir sets the directory that should be mounted on /dev/shm. +func WithShmDir(dir string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.ShmDir = dir + return nil + } +} + +// WithNoShm tells libpod whether to mount /dev/shm +func WithNoShm(mount bool) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.NoShm = mount + return nil + } +} + +// WithNoShmShare tells libpod whether to share containers /dev/shm with other containers +func WithNoShmShare(share bool) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.NoShmShare = share + return nil + } +} + +// WithSystemd turns on systemd mode in the container +func WithSystemd() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + t := true + ctr.config.Systemd = &t + return nil + } +} + +// WithSdNotifySocket sets the sd-notify of the container +func WithSdNotifySocket(socketPath string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.SdNotifySocket = socketPath + return nil + } +} + +// WithSdNotifyMode sets the sd-notify method +func WithSdNotifyMode(mode string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if err := define.ValidateSdNotifyMode(mode); err != nil { + return err + } + + ctr.config.SdNotifyMode = mode + return nil + } +} + +// WithShmSize sets the size of /dev/shm tmpfs mount. +func WithShmSize(size int64) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.ShmSize = size + return nil + } +} + +// WithShmSizeSystemd sets the size of systemd-specific mounts: +// +// /run +// /run/lock +// /var/log/journal +// /tmp +func WithShmSizeSystemd(size int64) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.ShmSizeSystemd = size + return nil + } +} + +// WithPrivileged sets the privileged flag in the container runtime. +func WithPrivileged(privileged bool) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.Privileged = privileged + return nil + } +} + +// WithReadWriteTmpfs sets up read-write tmpfs flag in the container runtime. +// Only Used if containers are run in ReadOnly mode. +func WithReadWriteTmpfs(readWriteTmpfs bool) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.ReadWriteTmpfs = readWriteTmpfs + return nil + } +} + +// WithSecLabels sets the labels for SELinux. +func WithSecLabels(labelOpts []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.LabelOpts = labelOpts + return nil + } +} + +// WithUser sets the user identity field in configuration. +// Valid uses [user | user:group | uid | uid:gid | user:gid | uid:group ]. +func WithUser(user string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.User = user + return nil + } +} + +// WithRootFSFromImage sets up a fresh root filesystem using the given image. +// If useImageConfig is specified, image volumes, environment variables, and +// other configuration from the image will be added to the config. +// TODO: Replace image name and ID with a libpod.Image struct when that is +// finished. +func WithRootFSFromImage(imageID, imageName, rawImageName string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.RootfsImageID = imageID + ctr.config.RootfsImageName = imageName + ctr.config.RawImageName = rawImageName + return nil + } +} + +// WithStdin keeps stdin on the container open to allow interaction. +func WithStdin() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.Stdin = true + + return nil + } +} + +// WithPod adds the container to a pod. +// Containers which join a pod can only join the Linux namespaces of other +// containers in the same pod. +// Containers can only join pods in the same libpod namespace. +func (r *Runtime) WithPod(pod *Pod) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if pod == nil { + return define.ErrInvalidArg + } + ctr.config.Pod = pod.ID() + + return nil + } +} + +// WithLabels adds labels to the container. +func WithLabels(labels map[string]string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.Labels = make(map[string]string) + maps.Copy(ctr.config.Labels, labels) + + return nil + } +} + +// WithName sets the container's name. +func WithName(name string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + name = strings.TrimPrefix(name, "/") + // Check the name against a regex + if !define.NameRegex.MatchString(name) { + return define.RegexError + } + + ctr.config.Name = name + + return nil + } +} + +// WithStopSignal sets the signal that will be sent to stop the container. +func WithStopSignal(signal syscall.Signal) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if signal == 0 { + return fmt.Errorf("stop signal cannot be 0: %w", define.ErrInvalidArg) + } else if signal > 64 { + return fmt.Errorf("stop signal cannot be greater than 64 (SIGRTMAX): %w", define.ErrInvalidArg) + } + + ctr.config.StopSignal = uint(signal) + + return nil + } +} + +// WithStopTimeout sets the time to after initial stop signal is sent to the +// container, before sending the kill signal. +func WithStopTimeout(timeout uint) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.StopTimeout = timeout + + return nil + } +} + +// WithTimeout sets the maximum time a container is allowed to run" +func WithTimeout(timeout uint) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.Timeout = timeout + + return nil + } +} + +// WithIDMappings sets the idmappings for the container +func WithIDMappings(idmappings storage.IDMappingOptions) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.IDMappings = idmappings + return nil + } +} + +// WithIPCNSFrom indicates that the container should join the IPC namespace of +// the given container. +// If the container has joined a pod, it can only join the namespaces of +// containers in the same pod. +func WithIPCNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if err := checkDependencyContainer(nsCtr, ctr); err != nil { + return err + } + + ctr.config.IPCNsCtr = nsCtr.ID() + + return nil + } +} + +// WithNetNSFrom indicates that the container should join the network namespace +// of the given container. +// If the container has joined a pod, it can only join the namespaces of +// containers in the same pod. +func WithNetNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if err := checkDependencyContainer(nsCtr, ctr); err != nil { + return err + } + + ctr.config.NetNsCtr = nsCtr.ID() + + return nil + } +} + +// WithPIDNSFrom indicates that the container should join the PID namespace of +// the given container. +// If the container has joined a pod, it can only join the namespaces of +// containers in the same pod. +func WithPIDNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if err := checkDependencyContainer(nsCtr, ctr); err != nil { + return err + } + + ctr.config.PIDNsCtr = nsCtr.ID() + + return nil + } +} + +// WithAddCurrentUserPasswdEntry indicates that container should add current +// user entry to /etc/passwd, since the UID will be mapped into the container, +// via user namespace +func WithAddCurrentUserPasswdEntry() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.AddCurrentUserPasswdEntry = true + return nil + } +} + +// WithUserNSFrom indicates that the container should join the user namespace of +// the given container. +// If the container has joined a pod, it can only join the namespaces of +// containers in the same pod. +func WithUserNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if err := checkDependencyContainer(nsCtr, ctr); err != nil { + return err + } + + ctr.config.UserNsCtr = nsCtr.ID() + if err := JSONDeepCopy(nsCtr.IDMappings(), &ctr.config.IDMappings); err != nil { + return err + } + // NewFromSpec() is deprecated according to its comment + // however the recommended replace just causes a nil map panic + g := generate.NewFromSpec(ctr.config.Spec) + + g.ClearLinuxUIDMappings() + for _, uidmap := range nsCtr.config.IDMappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + g.ClearLinuxGIDMappings() + for _, gidmap := range nsCtr.config.IDMappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + return nil + } +} + +// WithUTSNSFrom indicates that the container should join the UTS namespace of +// the given container. +// If the container has joined a pod, it can only join the namespaces of +// containers in the same pod. +func WithUTSNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if err := checkDependencyContainer(nsCtr, ctr); err != nil { + return err + } + + ctr.config.UTSNsCtr = nsCtr.ID() + + return nil + } +} + +// WithCgroupNSFrom indicates that the container should join the Cgroup namespace +// of the given container. +// If the container has joined a pod, it can only join the namespaces of +// containers in the same pod. +func WithCgroupNSFrom(nsCtr *Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if err := checkDependencyContainer(nsCtr, ctr); err != nil { + return err + } + + ctr.config.CgroupNsCtr = nsCtr.ID() + + return nil + } +} + +// WithDependencyCtrs sets dependency containers of the given container. +// Dependency containers must be running before this container is started. +func WithDependencyCtrs(ctrs []*Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + deps := make([]string, 0, len(ctrs)) + + for _, dep := range ctrs { + if err := checkDependencyContainer(dep, ctr); err != nil { + return err + } + + deps = append(deps, dep.ID()) + } + + ctr.config.Dependencies = deps + + return nil + } +} + +// WithNetNS indicates that the container should be given a new network +// namespace with a minimal configuration. +// An optional array of port mappings can be provided. +// Conflicts with WithNetNSFrom(). +func WithNetNS(portMappings []nettypes.PortMapping, postConfigureNetNS bool, netmode string, networks map[string]nettypes.PerNetworkOptions) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.PostConfigureNetNS = postConfigureNetNS + ctr.config.NetMode = namespaces.NetworkMode(netmode) + ctr.config.CreateNetNS = true + ctr.config.PortMappings = portMappings + + if !ctr.config.NetMode.IsBridge() && len(networks) > 0 { + return errors.New("cannot use networks when network mode is not bridge") + } + ctr.config.Networks = networks + + return nil + } +} + +// WithExposedPorts includes a set of ports that were exposed by the image in +// the container config, e.g. for display when the container is inspected. +func WithExposedPorts(exposedPorts map[uint16][]string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.ExposedPorts = exposedPorts + + return nil + } +} + +// WithNetworkOptions sets additional options for the networks. +func WithNetworkOptions(options map[string][]string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.NetworkOptions = options + + return nil + } +} + +// WithLogDriver sets the log driver for the container +func WithLogDriver(driver string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + switch driver { + case "": + return fmt.Errorf("log driver must be set: %w", define.ErrInvalidArg) + case define.JournaldLogging, define.KubernetesLogging, define.JSONLogging, define.NoLogging, define.PassthroughLogging, define.PassthroughTTYLogging: + break + default: + return fmt.Errorf("invalid log driver: %w", define.ErrInvalidArg) + } + + ctr.config.LogDriver = driver + + return nil + } +} + +// WithLogPath sets the path to the log file. +func WithLogPath(path string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + if path == "" { + return fmt.Errorf("log path must be set: %w", define.ErrInvalidArg) + } + if isDirectory(path) { + containerDir := filepath.Join(path, ctr.ID()) + if err := os.Mkdir(containerDir, 0o755); err != nil { + return fmt.Errorf("failed to create container log directory %s: %w", containerDir, err) + } + + ctr.config.LogPath = filepath.Join(containerDir, "ctr.log") + } else { + ctr.config.LogPath = path + } + + return nil + } +} + +// WithLogTag sets the tag to the log file. +func WithLogTag(tag string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + if tag == "" { + return fmt.Errorf("log tag must be set: %w", define.ErrInvalidArg) + } + + ctr.config.LogTag = tag + + return nil + } +} + +// WithCgroupsMode disables the creation of Cgroups for the conmon process. +func WithCgroupsMode(mode string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + switch mode { + case "disabled": + ctr.config.NoCgroups = true + ctr.config.CgroupsMode = mode + case "enabled", "no-conmon", cgroupSplit: + ctr.config.CgroupsMode = mode + default: + return fmt.Errorf("invalid cgroup mode %q: %w", mode, define.ErrInvalidArg) + } + + return nil + } +} + +// WithCgroupParent sets the Cgroup Parent of the new container. +func WithCgroupParent(parent string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if parent == "" { + return fmt.Errorf("cgroup parent cannot be empty: %w", define.ErrInvalidArg) + } + + ctr.config.CgroupParent = parent + + return nil + } +} + +// WithDNSSearch sets the additional search domains of a container. +func WithDNSSearch(searchDomains []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.DNSSearch = searchDomains + return nil + } +} + +// WithDNS sets additional name servers for the container. +func WithDNS(dnsServers []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + var dns []net.IP + for _, i := range dnsServers { + result := net.ParseIP(i) + if result == nil { + return fmt.Errorf("invalid IP address %s: %w", i, define.ErrInvalidArg) + } + dns = append(dns, result) + } + ctr.config.DNSServer = append(ctr.config.DNSServer, dns...) + + return nil + } +} + +// WithDNSOption sets additional dns options for the container. +func WithDNSOption(dnsOptions []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + if ctr.config.UseImageResolvConf { + return fmt.Errorf("cannot add DNS options if container will not create /etc/resolv.conf: %w", define.ErrInvalidArg) + } + ctr.config.DNSOption = append(ctr.config.DNSOption, dnsOptions...) + return nil + } +} + +// WithHosts sets additional host:IP for the hosts file. +func WithHosts(hosts []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.HostAdd = hosts + return nil + } +} + +// WithConmonPidFile specifies the path to the file that receives the pid of +// conmon. +func WithConmonPidFile(path string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.ConmonPidFile = path + return nil + } +} + +// WithGroups sets additional groups for the container, which are defined by +// the user. +func WithGroups(groups []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.Groups = groups + return nil + } +} + +// WithUserVolumes sets the user-added volumes of the container. +// These are not added to the container's spec, but will instead be used during +// commit to populate the volumes of the new image, and to trigger some OCI +// hooks that are only added if volume mounts are present. +// Furthermore, they are used in the output of inspect, to filter volumes - +// only volumes included in this list will be included in the output. +// Unless explicitly set, committed images will have no volumes. +// The given volumes slice must not be nil. +func WithUserVolumes(volumes []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if volumes == nil { + return define.ErrInvalidArg + } + + ctr.config.UserVolumes = make([]string, 0, len(volumes)) + ctr.config.UserVolumes = append(ctr.config.UserVolumes, volumes...) + return nil + } +} + +// WithEntrypoint sets the entrypoint of the container. +// This is not used to change the container's spec, but will instead be used +// during commit to populate the entrypoint of the new image. +// If not explicitly set it will default to the image's entrypoint. +// A nil entrypoint is allowed, and will clear entrypoint on the created image. +func WithEntrypoint(entrypoint []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.Entrypoint = make([]string, 0, len(entrypoint)) + ctr.config.Entrypoint = append(ctr.config.Entrypoint, entrypoint...) + return nil + } +} + +// WithCommand sets the command of the container. +// This is not used to change the container's spec, but will instead be used +// during commit to populate the command of the new image. +// If not explicitly set it will default to the image's command. +// A nil command is allowed, and will clear command on the created image. +func WithCommand(command []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.Command = make([]string, 0, len(command)) + ctr.config.Command = append(ctr.config.Command, command...) + return nil + } +} + +// WithRootFS sets the rootfs for the container. +// This creates a container from a directory on disk and not an image. +func WithRootFS(rootfs string, overlay bool, mapping *string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + if err := fileutils.Exists(rootfs); err != nil { + return err + } + ctr.config.Rootfs = rootfs + ctr.config.RootfsOverlay = overlay + ctr.config.RootfsMapping = mapping + return nil + } +} + +// WithUseImageResolvConf tells the container not to bind-mount resolv.conf in. +// This conflicts with other DNS-related options. +func WithUseImageResolvConf() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.UseImageResolvConf = true + + return nil + } +} + +// WithUseImageHostname tells the container not to bind-mount /etc/hostname in. +func WithUseImageHostname() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.UseImageHostname = true + + return nil + } +} + +// WithUseImageHosts tells the container not to bind-mount /etc/hosts in. +// This conflicts with WithHosts(). +func WithUseImageHosts() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.UseImageHosts = true + + return nil + } +} + +// WithRestartPolicy sets the container's restart policy. Valid values are +// "no", "on-failure", and "always". The empty string is allowed, and will be +// equivalent to "no". +func WithRestartPolicy(policy string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if err := define.ValidateRestartPolicy(policy); err != nil { + return err + } + + ctr.config.RestartPolicy = policy + + return nil + } +} + +// WithRestartRetries sets the number of retries to use when restarting a +// container with the "on-failure" restart policy. +// 0 is an allowed value, and indicates infinite retries. +func WithRestartRetries(tries uint) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.RestartRetries = tries + + return nil + } +} + +// WithNamedVolumes adds the given named volumes to the container. +func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + for _, vol := range volumes { + mountOpts, err := util.ProcessOptions(vol.Options, false, "") + if err != nil { + return fmt.Errorf("processing options for named volume %q mounted at %q: %w", vol.Name, vol.Dest, err) + } + + ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{ + Name: vol.Name, + Dest: vol.Dest, + Options: mountOpts, + IsAnonymous: vol.IsAnonymous, + SubPath: vol.SubPath, + }) + } + + return nil + } +} + +// WithOverlayVolumes adds the given overlay volumes to the container. +func WithOverlayVolumes(volumes []*ContainerOverlayVolume) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + for _, vol := range volumes { + ctr.config.OverlayVolumes = append(ctr.config.OverlayVolumes, &ContainerOverlayVolume{ + Dest: vol.Dest, + Source: vol.Source, + Options: vol.Options, + }) + } + + return nil + } +} + +// WithImageVolumes adds the given image volumes to the container. +func WithImageVolumes(volumes []*ContainerImageVolume) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + for _, vol := range volumes { + ctr.config.ImageVolumes = append(ctr.config.ImageVolumes, &ContainerImageVolume{ + Dest: vol.Dest, + Source: vol.Source, + ReadWrite: vol.ReadWrite, + SubPath: vol.SubPath, + }) + } + + return nil + } +} + +// WithImageVolumes adds the given image volumes to the container. +func WithArtifactVolumes(volumes []*ContainerArtifactVolume) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.ArtifactVolumes = volumes + + return nil + } +} + +// WithHealthCheck adds the healthcheck to the container config +func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.HealthCheckConfig = healthCheck + return nil + } +} + +// WithHealthCheckLogDestination adds the healthLogDestination to the container config +func WithHealthCheckLogDestination(destination string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + dest, err := define.GetValidHealthCheckDestination(destination) + if err != nil { + return err + } + ctr.config.HealthLogDestination = &dest + return nil + } +} + +// WithHealthCheckMaxLogCount adds the healthMaxLogCount to the container config +func WithHealthCheckMaxLogCount(maxLogCount uint) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.HealthMaxLogCount = &maxLogCount + return nil + } +} + +// WithHealthCheckMaxLogSize adds the healthMaxLogSize to the container config +func WithHealthCheckMaxLogSize(maxLogSize uint) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.HealthMaxLogSize = &maxLogSize + return nil + } +} + +// WithHealthCheckOnFailureAction adds an on-failure action to health-check config +func WithHealthCheckOnFailureAction(action define.HealthCheckOnFailureAction) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.HealthCheckOnFailureAction = action + return nil + } +} + +// WithPreserveFDs forwards from the process running Libpod into the container +// the given number of extra FDs (starting after the standard streams) to the created container +func WithPreserveFDs(fd uint) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.PreserveFDs = fd + return nil + } +} + +// WithPreserveFD forwards from the process running Libpod into the container +// the given list of extra FDs to the created container +func WithPreserveFD(fds []uint) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.PreserveFD = fds + return nil + } +} + +// WithCreateCommand adds the full command plus arguments of the current +// process to the container config. +func WithCreateCommand(cmd []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.CreateCommand = cmd + return nil + } +} + +// withIsInfra allows us to differentiate between infra containers and other containers +// within the container config +func withIsInfra() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.IsInfra = true + + return nil + } +} + +// withIsDefaultInfra allows us to differentiate between the default infra containers generated +// directly by podman and custom infra containers within the container config +func withIsDefaultInfra() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.IsDefaultInfra = true + + return nil + } +} + +// WithIsService allows us to differentiate between service containers and other container +// within the container config. It also sets the exit-code propagation of the +// service container. +func WithIsService(ecp define.KubeExitCodePropagation) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.IsService = true + ctr.config.KubeExitCodePropagation = ecp + return nil + } +} + +// WithCreateWorkingDir tells Podman to create the container's working directory +// if it does not exist. +func WithCreateWorkingDir() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.CreateWorkingDir = true + return nil + } +} + +// Volume Creation Options + +func WithVolumeIgnoreIfExist() VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + volume.ignoreIfExists = true + + return nil + } +} + +// WithVolumeName sets the name of the volume. +func WithVolumeName(name string) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + // Check the name against a regex + if !define.NameRegex.MatchString(name) { + return define.RegexError + } + volume.config.Name = name + + return nil + } +} + +// WithVolumeDriver sets the volume's driver. +// It is presently not implemented, but will be supported in a future Podman +// release. +func WithVolumeDriver(driver string) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.Driver = driver + return nil + } +} + +// WithVolumeLabels sets the labels of the volume. +func WithVolumeLabels(labels map[string]string) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.Labels = make(map[string]string) + maps.Copy(volume.config.Labels, labels) + + return nil + } +} + +// WithVolumeMountLabel sets the MountLabel of the volume. +func WithVolumeMountLabel(mountLabel string) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.MountLabel = mountLabel + return nil + } +} + +// WithVolumeOptions sets the options of the volume. +func WithVolumeOptions(options map[string]string) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.Options = make(map[string]string) + maps.Copy(volume.config.Options, options) + + return nil + } +} + +// WithVolumeUID sets the UID that the volume will be created as. +func WithVolumeUID(uid int) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.UID = uid + + return nil + } +} + +// WithVolumeSize sets the maximum size of the volume +func WithVolumeSize(size uint64) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.Size = size + + return nil + } +} + +// WithVolumeInodes sets the maximum inodes of the volume +func WithVolumeInodes(inodes uint64) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.Inodes = inodes + + return nil + } +} + +// WithVolumeGID sets the GID that the volume will be created as. +func WithVolumeGID(gid int) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.GID = gid + + return nil + } +} + +// WithVolumeNoChown prevents the volume from being chowned to the process uid at first use. +func WithVolumeNoChown() VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.state.NeedsChown = false + + return nil + } +} + +// WithVolumeDisableQuota prevents the volume from being assigned a quota. +func WithVolumeDisableQuota() VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.DisableQuota = true + + return nil + } +} + +// withSetAnon sets a bool notifying libpod that this volume is anonymous and +// should be removed when containers using it are removed and volumes are +// specified for removal. +func withSetAnon() VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.IsAnon = true + + return nil + } +} + +// WithVolumeDriverTimeout sets the volume creation timeout period. +// Only usable if a non-local volume driver is in use. +func WithVolumeDriverTimeout(timeout uint) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + if volume.config.Driver == "" || volume.config.Driver == define.VolumeDriverLocal { + return fmt.Errorf("Volume driver timeout can only be used with non-local volume drivers: %w", define.ErrInvalidArg) + } + + tm := timeout + + volume.config.Timeout = &tm + + return nil + } +} + +// WithTimezone sets the timezone in the container +func WithTimezone(path string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + if path != "local" { + // validate the format of the timezone specified if it's not "local" + _, err := time.LoadLocation(path) + if err != nil { + return fmt.Errorf("finding timezone: %w", err) + } + } + + ctr.config.Timezone = path + return nil + } +} + +// WithUmask sets the umask in the container +func WithUmask(umask string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + if !umaskRegex.MatchString(umask) { + return fmt.Errorf("invalid umask string %s: %w", umask, define.ErrInvalidArg) + } + ctr.config.Umask = umask + return nil + } +} + +// WithSecrets adds secrets to the container +func WithSecrets(containerSecrets []*ContainerSecret) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.Secrets = containerSecrets + return nil + } +} + +// WithEnvSecrets adds environment variable secrets to the container +func WithEnvSecrets(envSecrets map[string]string) CtrCreateOption { + return func(ctr *Container) error { + ctr.config.EnvSecrets = make(map[string]*secrets.Secret) + if ctr.valid { + return define.ErrCtrFinalized + } + manager, err := ctr.runtime.SecretsManager() + if err != nil { + return err + } + for target, src := range envSecrets { + secr, err := manager.Lookup(src) + if err != nil { + return err + } + ctr.config.EnvSecrets[target] = secr + } + return nil + } +} + +// WithPidFile adds pidFile to the container +func WithPidFile(pidFile string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.PidFile = pidFile + return nil + } +} + +// WithHostUsers indicates host users to add to /etc/passwd +func WithHostUsers(hostUsers []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.HostUsers = hostUsers + return nil + } +} + +// WithInitCtrType indicates the container is an initcontainer +func WithInitCtrType(containerType string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + // Make sure the type is valid + if containerType == define.OneShotInitContainer || containerType == define.AlwaysInitContainer { + ctr.config.InitContainerType = containerType + return nil + } + return fmt.Errorf("%s is invalid init container type", containerType) + } +} + +// WithHostDevice adds the original host src to the config +func WithHostDevice(dev []specs.LinuxDevice) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.DeviceHostSrc = dev + return nil + } +} + +// WithSelectedPasswordManagement makes it so that the container either does or does not set up /etc/passwd or /etc/group +func WithSelectedPasswordManagement(passwd *bool) CtrCreateOption { + return func(c *Container) error { + if c.valid { + return define.ErrCtrFinalized + } + c.config.Passwd = passwd + return nil + } +} + +// WithStartupHealthcheck sets a startup healthcheck for the container. +// Requires that a healthcheck must be set. +func WithStartupHealthcheck(startupHC *define.StartupHealthCheck) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.StartupHealthCheckConfig = new(define.StartupHealthCheck) + if err := JSONDeepCopy(startupHC, ctr.config.StartupHealthCheckConfig); err != nil { + return fmt.Errorf("error copying startup healthcheck into container: %w", err) + } + return nil + } +} + +// Pod Creation Options + +// WithPodCreateCommand adds the full command plus arguments of the current +// process to the pod config. +func WithPodCreateCommand(createCmd []string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + pod.config.CreateCommand = createCmd + return nil + } +} + +// WithPodName sets the name of the pod. +func WithPodName(name string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + // Check the name against a regex + if !define.NameRegex.MatchString(name) { + return define.RegexError + } + + pod.config.Name = name + + return nil + } +} + +// WithPodExitPolicy sets the exit policy of the pod. +func WithPodExitPolicy(policy string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + parsed, err := config.ParsePodExitPolicy(policy) + if err != nil { + return err + } + + pod.config.ExitPolicy = parsed + + return nil + } +} + +// WithPodRestartPolicy sets the restart policy of the pod. +func WithPodRestartPolicy(policy string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + switch policy { + //TODO: v5.0 if no restart policy is set, follow k8s convention and default to Always + case define.RestartPolicyNone, define.RestartPolicyNo, define.RestartPolicyOnFailure, define.RestartPolicyAlways, define.RestartPolicyUnlessStopped: + pod.config.RestartPolicy = policy + default: + return fmt.Errorf("%q is not a valid restart policy: %w", policy, define.ErrInvalidArg) + } + + return nil + } +} + +// WithPodRestartRetries sets the number of retries to use when restarting a +// container with the "on-failure" restart policy. +// 0 is an allowed value, and indicates infinite retries. +func WithPodRestartRetries(tries uint) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.RestartRetries = &tries + + return nil + } +} + +// WithPodHostname sets the hostname of the pod. +func WithPodHostname(hostname string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + // Check the hostname against a regex + if !define.NameRegex.MatchString(hostname) { + return define.RegexError + } + + pod.config.Hostname = hostname + + return nil + } +} + +// WithPodLabels sets the labels of a pod. +func WithPodLabels(labels map[string]string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.Labels = make(map[string]string) + maps.Copy(pod.config.Labels, labels) + + return nil + } +} + +// WithPodCgroupParent sets the Cgroup Parent of the pod. +func WithPodCgroupParent(path string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.CgroupParent = path + + return nil + } +} + +// WithPodParent tells containers in this pod to use the cgroup created for +// this pod. +// This can still be overridden at the container level by explicitly specifying +// a Cgroup parent. +func WithPodParent() PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.UsePodCgroup = true + + return nil + } +} + +// WithPodIPC tells containers in this pod to use the ipc namespace +// created for this pod. +// Containers in a pod will inherit the kernel namespaces from the +// first container added. +func WithPodIPC() PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.UsePodIPC = true + + return nil + } +} + +// WithPodNet tells containers in this pod to use the network namespace +// created for this pod. +// Containers in a pod will inherit the kernel namespaces from the +// first container added. +func WithPodNet() PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.UsePodNet = true + + return nil + } +} + +// WithPodUser tells containers in this pod to use the user namespace +// created for this pod. +// Containers in a pod will inherit the kernel namespaces from the +// first container added. +// TODO implement WithUserNSFrom, so WithUserNsFromPod functions properly +// Then this option can be added on the pod level +func WithPodUser() PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.UsePodUser = true + + return nil + } +} + +// WithPodPID tells containers in this pod to use the pid namespace +// created for this pod. +// Containers in a pod will inherit the kernel namespaces from the +// first container added. +func WithPodPID() PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.UsePodPID = true + + return nil + } +} + +// WithPodUTS tells containers in this pod to use the uts namespace +// created for this pod. +// Containers in a pod will inherit the kernel namespaces from the +// first container added. +func WithPodUTS() PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.UsePodUTS = true + + return nil + } +} + +// WithPodCgroup tells containers in this pod to use the cgroup namespace +// created for this pod. +// Containers in a pod will inherit the kernel namespaces from the first +// container added. +func WithPodCgroup() PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + pod.config.UsePodCgroupNS = true + + return nil + } +} + +// WithInfraContainer tells the pod to create a pause container +func WithInfraContainer() PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + pod.config.HasInfra = true + + return nil + } +} + +// WithServiceContainer associates the specified service container ID with the pod. +func WithServiceContainer(id string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + + ctr, err := pod.runtime.LookupContainer(id) + if err != nil { + return fmt.Errorf("looking up service container: %w", err) + } + + if err := ctr.addServicePodLocked(pod.ID()); err != nil { + return fmt.Errorf("associating service container %s with pod %s: %w", id, pod.ID(), err) + } + + pod.config.ServiceContainerID = id + return nil + } +} + +// WithPodResources sets resource limits to be applied to the pod's cgroup +// these will be inherited by all containers unless overridden. +func WithPodResources(resources specs.LinuxResources) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return define.ErrPodFinalized + } + pod.config.ResourceLimits = resources + return nil + } +} + +// WithVolatile sets the volatile flag for the container storage. +// The option can potentially cause data loss when used on a container that must survive a machine reboot. +func WithVolatile() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.Volatile = true + + return nil + } +} + +// WithChrootDirs is an additional set of directories that need to be +// treated as root directories. Standard bind mounts will be mounted +// into paths relative to these directories. +func WithChrootDirs(dirs []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.ChrootDirs = dirs + + return nil + } +} + +// WithPasswdEntry sets the entry to write to the /etc/passwd file. +func WithPasswdEntry(passwdEntry string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.PasswdEntry = passwdEntry + + return nil + } +} + +// WithGroupEntry sets the entry to write to the /etc/group file. +func WithGroupEntry(groupEntry string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.GroupEntry = groupEntry + + return nil + } +} + +// WithBaseHostsFile sets the option to copy /etc/hosts file. +func WithBaseHostsFile(baseHostsFile string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.BaseHostsFile = baseHostsFile + + return nil + } +} + +// WithMountAllDevices sets the option to mount all of a privileged container's +// host devices +func WithMountAllDevices() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.MountAllDevices = true + + return nil + } +} + +// WithLabelNested sets the LabelNested flag allowing label separation within container +func WithLabelNested(nested bool) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.LabelNested = nested + + return nil + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/plugin/volume_api.go b/vendor/github.com/containers/podman/v5/libpod/plugin/volume_api.go new file mode 100644 index 00000000..0e6ff913 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/plugin/volume_api.go @@ -0,0 +1,465 @@ +package plugin + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "os" + "path/filepath" + "slices" + "strings" + "sync" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/docker/go-plugins-helpers/sdk" + "github.com/docker/go-plugins-helpers/volume" + jsoniter "github.com/json-iterator/go" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/config" + "go.podman.io/storage/pkg/fileutils" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// Copied from docker/go-plugins-helpers/volume/api.go - not exported, so we +// need to do this to get at them. +// These are well-established paths that should not change unless the plugin API +// version changes. +var ( + activatePath = "/Plugin.Activate" + createPath = "/VolumeDriver.Create" + getPath = "/VolumeDriver.Get" + listPath = "/VolumeDriver.List" + removePath = "/VolumeDriver.Remove" + hostVirtualPath = "/VolumeDriver.Path" + mountPath = "/VolumeDriver.Mount" + unmountPath = "/VolumeDriver.Unmount" +) + +const ( + volumePluginType = "VolumeDriver" +) + +var ( + ErrNotPlugin = errors.New("target does not appear to be a valid plugin") + ErrNotVolumePlugin = errors.New("plugin is not a volume plugin") + ErrPluginRemoved = errors.New("plugin is no longer available (shut down?)") + + // This stores available, initialized volume plugins. + pluginsLock sync.Mutex + plugins map[string]*VolumePlugin +) + +// VolumePlugin is a single volume plugin. +type VolumePlugin struct { + // Name is the name of the volume plugin. This will be used to refer to + // it. + Name string + // SocketPath is the unix socket at which the plugin is accessed. + SocketPath string + // Client is the HTTP client we use to connect to the plugin. + Client *http.Client +} + +// This is the response from the activate endpoint of the API. +type activateResponse struct { + Implements []string +} + +// Validate that the given plugin is good to use. +// Add it to available plugins if so. +func validatePlugin(newPlugin *VolumePlugin) error { + // It's a socket. Is it a plugin? + // Hit the Activate endpoint to find out if it is, and if so what kind + req, err := http.NewRequest(http.MethodPost, "http://plugin"+activatePath, nil) + if err != nil { + return fmt.Errorf("making request to volume plugin %s activation endpoint: %w", newPlugin.Name, err) + } + + req.Header.Set("Host", newPlugin.getURI()) + req.Header.Set("Content-Type", sdk.DefaultContentTypeV1_1) + + resp, err := newPlugin.Client.Do(req) + if err != nil { + return fmt.Errorf("sending request to plugin %s activation endpoint: %w", newPlugin.Name, err) + } + defer resp.Body.Close() + + // Response code MUST be 200. Anything else, we have to assume it's not + // a valid plugin. + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("got status code %d from activation endpoint for plugin %s: %w", resp.StatusCode, newPlugin.Name, ErrNotPlugin) + } + + // Read and decode the body so we can tell if this is a volume plugin. + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("reading activation response body from plugin %s: %w", newPlugin.Name, err) + } + + respStruct := new(activateResponse) + if err := json.Unmarshal(respBytes, respStruct); err != nil { + return fmt.Errorf("unmarshalling plugin %s activation response: %w", newPlugin.Name, err) + } + + if !slices.Contains(respStruct.Implements, volumePluginType) { + return fmt.Errorf("plugin %s does not implement volume plugin, instead provides %s: %w", newPlugin.Name, strings.Join(respStruct.Implements, ", "), ErrNotVolumePlugin) + } + + if plugins == nil { + plugins = make(map[string]*VolumePlugin) + } + + plugins[newPlugin.Name] = newPlugin + + return nil +} + +// GetVolumePlugin gets a single volume plugin, with the given name, at the +// given path. +func GetVolumePlugin(name string, path string, timeout *uint, cfg *config.Config) (*VolumePlugin, error) { + pluginsLock.Lock() + defer pluginsLock.Unlock() + + plugin, exists := plugins[name] + if exists { + // This shouldn't be possible, but just in case... + if plugin.SocketPath != filepath.Clean(path) { + return nil, fmt.Errorf("requested path %q for volume plugin %s does not match pre-existing path for plugin, %q: %w", path, name, plugin.SocketPath, define.ErrInvalidArg) + } + + return plugin, nil + } + + // It's not cached. We need to get it. + + newPlugin := new(VolumePlugin) + newPlugin.Name = name + newPlugin.SocketPath = filepath.Clean(path) + + // Need an HTTP client to force a Unix connection. + // And since we can reuse it, might as well cache it. + client := new(http.Client) + client.Timeout = 5 * time.Second + if timeout != nil { + client.Timeout = time.Duration(*timeout) * time.Second + } else if cfg != nil { + client.Timeout = time.Duration(cfg.Engine.VolumePluginTimeout) * time.Second + } + // This bit borrowed from pkg/bindings/connection.go + client.Transport = &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, "unix", newPlugin.SocketPath) + }, + DisableCompression: true, + } + newPlugin.Client = client + + stat, err := os.Stat(newPlugin.SocketPath) + if err != nil { + return nil, fmt.Errorf("cannot access plugin %s socket %q: %w", name, newPlugin.SocketPath, err) + } + if stat.Mode()&os.ModeSocket == 0 { + return nil, fmt.Errorf("volume %s path %q is not a unix socket: %w", name, newPlugin.SocketPath, ErrNotPlugin) + } + + if err := validatePlugin(newPlugin); err != nil { + return nil, err + } + + return newPlugin, nil +} + +func (p *VolumePlugin) getURI() string { + return "unix://" + p.SocketPath +} + +// Verify the plugin is still available. +// Does not actually ping the API, just verifies that the socket still exists. +func (p *VolumePlugin) verifyReachable() error { + if err := fileutils.Exists(p.SocketPath); err != nil { + if os.IsNotExist(err) { + pluginsLock.Lock() + defer pluginsLock.Unlock() + delete(plugins, p.Name) + return fmt.Errorf("%s: %w", p.Name, ErrPluginRemoved) + } + + return fmt.Errorf("accessing plugin %s: %w", p.Name, err) + } + return nil +} + +// Send a request to the volume plugin for handling. +// Callers *MUST* close the response when they are done. +func (p *VolumePlugin) sendRequest(toJSON any, endpoint string) (*http.Response, error) { + var ( + reqJSON []byte + err error + ) + + if toJSON != nil { + reqJSON, err = json.Marshal(toJSON) + if err != nil { + return nil, fmt.Errorf("marshalling request JSON for volume plugin %s endpoint %s: %w", p.Name, endpoint, err) + } + } + + req, err := http.NewRequest(http.MethodPost, "http://plugin"+endpoint, bytes.NewReader(reqJSON)) + if err != nil { + return nil, fmt.Errorf("making request to volume plugin %s endpoint %s: %w", p.Name, endpoint, err) + } + + req.Header.Set("Host", p.getURI()) + req.Header.Set("Content-Type", sdk.DefaultContentTypeV1_1) + + resp, err := p.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("sending request to volume plugin %s endpoint %s: %w", p.Name, endpoint, err) + } + // We are *deliberately not closing* response here. It is the + // responsibility of the caller to do so after reading the response. + + return resp, nil +} + +// Turn an error response from a volume plugin into a well-formatted Go error. +func (p *VolumePlugin) makeErrorResponse(err, endpoint, volName string) error { + if err == "" { + err = "empty error from plugin" + } + if volName != "" { + return fmt.Errorf("on %s on volume %s in volume plugin %s: %w", endpoint, volName, p.Name, errors.New(err)) + } + return fmt.Errorf("on %s in volume plugin %s: %w", endpoint, p.Name, errors.New(err)) +} + +// Handle error responses from plugin +func (p *VolumePlugin) handleErrorResponse(resp *http.Response, endpoint, volName string) error { + // The official plugin reference implementation uses HTTP 500 for + // errors, but I don't think we can guarantee all plugins do that. + // Let's interpret anything other than 200 as an error. + // If there isn't an error, don't even bother decoding the response. + if resp.StatusCode != http.StatusOK { + errResp, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err) + } + + errStruct := new(volume.ErrorResponse) + if err := json.Unmarshal(errResp, errStruct); err != nil { + return fmt.Errorf("unmarshalling JSON response from volume plugin %s: %w", p.Name, err) + } + + return p.makeErrorResponse(errStruct.Err, endpoint, volName) + } + + return nil +} + +// CreateVolume creates a volume in the plugin. +func (p *VolumePlugin) CreateVolume(req *volume.CreateRequest) error { + if req == nil { + return fmt.Errorf("must provide non-nil request to CreateVolume: %w", define.ErrInvalidArg) + } + + if err := p.verifyReachable(); err != nil { + return err + } + + logrus.Infof("Creating volume %s using plugin %s", req.Name, p.Name) + + resp, err := p.sendRequest(req, createPath) + if err != nil { + return err + } + defer resp.Body.Close() + + return p.handleErrorResponse(resp, createPath, req.Name) +} + +// ListVolumes lists volumes available in the plugin. +func (p *VolumePlugin) ListVolumes() ([]*volume.Volume, error) { + if err := p.verifyReachable(); err != nil { + return nil, err + } + + logrus.Infof("Listing volumes using plugin %s", p.Name) + + resp, err := p.sendRequest(nil, listPath) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if err := p.handleErrorResponse(resp, listPath, ""); err != nil { + return nil, err + } + + volumeRespBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err) + } + + volumeResp := new(volume.ListResponse) + if err := json.Unmarshal(volumeRespBytes, volumeResp); err != nil { + return nil, fmt.Errorf("unmarshalling volume plugin %s list response: %w", p.Name, err) + } + + return volumeResp.Volumes, nil +} + +// GetVolume gets a single volume from the plugin. +func (p *VolumePlugin) GetVolume(req *volume.GetRequest) (*volume.Volume, error) { + if req == nil { + return nil, fmt.Errorf("must provide non-nil request to GetVolume: %w", define.ErrInvalidArg) + } + + if err := p.verifyReachable(); err != nil { + return nil, err + } + + logrus.Infof("Getting volume %s using plugin %s", req.Name, p.Name) + + resp, err := p.sendRequest(req, getPath) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if err := p.handleErrorResponse(resp, getPath, req.Name); err != nil { + return nil, err + } + + getRespBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err) + } + + getResp := new(volume.GetResponse) + if err := json.Unmarshal(getRespBytes, getResp); err != nil { + return nil, fmt.Errorf("unmarshalling volume plugin %s get response: %w", p.Name, err) + } + + return getResp.Volume, nil +} + +// RemoveVolume removes a single volume from the plugin. +func (p *VolumePlugin) RemoveVolume(req *volume.RemoveRequest) error { + if req == nil { + return fmt.Errorf("must provide non-nil request to RemoveVolume: %w", define.ErrInvalidArg) + } + + if err := p.verifyReachable(); err != nil { + return err + } + + logrus.Infof("Removing volume %s using plugin %s", req.Name, p.Name) + + resp, err := p.sendRequest(req, removePath) + if err != nil { + return err + } + defer resp.Body.Close() + + return p.handleErrorResponse(resp, removePath, req.Name) +} + +// GetVolumePath gets the path the given volume is mounted at. +func (p *VolumePlugin) GetVolumePath(req *volume.PathRequest) (string, error) { + if req == nil { + return "", fmt.Errorf("must provide non-nil request to GetVolumePath: %w", define.ErrInvalidArg) + } + + if err := p.verifyReachable(); err != nil { + return "", err + } + + logrus.Infof("Getting volume %s path using plugin %s", req.Name, p.Name) + + resp, err := p.sendRequest(req, hostVirtualPath) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if err := p.handleErrorResponse(resp, hostVirtualPath, req.Name); err != nil { + return "", err + } + + pathRespBytes, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err) + } + + pathResp := new(volume.PathResponse) + if err := json.Unmarshal(pathRespBytes, pathResp); err != nil { + return "", fmt.Errorf("unmarshalling volume plugin %s path response: %w", p.Name, err) + } + + return pathResp.Mountpoint, nil +} + +// MountVolume mounts the given volume. The ID argument is the ID of the +// mounting container, used for internal record-keeping by the plugin. Returns +// the path the volume has been mounted at. +func (p *VolumePlugin) MountVolume(req *volume.MountRequest) (string, error) { + if req == nil { + return "", fmt.Errorf("must provide non-nil request to MountVolume: %w", define.ErrInvalidArg) + } + + if err := p.verifyReachable(); err != nil { + return "", err + } + + logrus.Infof("Mounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID) + + resp, err := p.sendRequest(req, mountPath) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if err := p.handleErrorResponse(resp, mountPath, req.Name); err != nil { + return "", err + } + + mountRespBytes, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("reading response body from volume plugin %s: %w", p.Name, err) + } + + mountResp := new(volume.MountResponse) + if err := json.Unmarshal(mountRespBytes, mountResp); err != nil { + return "", fmt.Errorf("unmarshalling volume plugin %s path response: %w", p.Name, err) + } + + return mountResp.Mountpoint, nil +} + +// UnmountVolume unmounts the given volume. The ID argument is the ID of the +// container that is unmounting, used for internal record-keeping by the plugin. +func (p *VolumePlugin) UnmountVolume(req *volume.UnmountRequest) error { + if req == nil { + return fmt.Errorf("must provide non-nil request to UnmountVolume: %w", define.ErrInvalidArg) + } + + if err := p.verifyReachable(); err != nil { + return err + } + + logrus.Infof("Unmounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID) + + resp, err := p.sendRequest(req, unmountPath) + if err != nil { + return err + } + defer resp.Body.Close() + + return p.handleErrorResponse(resp, unmountPath, req.Name) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/pod.go b/vendor/github.com/containers/podman/v5/libpod/pod.go new file mode 100644 index 00000000..5ab43804 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/pod.go @@ -0,0 +1,535 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "fmt" + "maps" + "sort" + "strings" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/lock" + "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/common/pkg/config" +) + +// Pod represents a group of containers that are managed together. +// Any operations on a Pod that access state must begin with a call to +// updatePod(). +// There is no guarantee that state exists in a readable state before this call, +// and even if it does its contents will be out of date and must be refreshed +// from the database. +// Generally, this requirement applies only to top-level functions; helpers can +// assume their callers handled this requirement. Generally speaking, if a +// function takes the pod lock and accesses any part of state, it should +// updatePod() immediately after locking. +// Pod represents a group of containers that may share namespaces +type Pod struct { + config *PodConfig + state *podState + + valid bool + runtime *Runtime + lock lock.Locker +} + +// PodConfig represents a pod's static configuration +type PodConfig struct { + ID string `json:"id"` + Name string `json:"name"` + // Namespace the pod is in + Namespace string `json:"namespace,omitempty"` + + Hostname string `json:"hostname,omitempty"` + + // Labels contains labels applied to the pod + Labels map[string]string `json:"labels"` + // CgroupParent contains the pod's Cgroup parent + CgroupParent string `json:"cgroupParent"` + // UsePodCgroup indicates whether the pod will create its own Cgroup and + // join containers to it. + // If true, all containers joined to the pod will use the pod cgroup as + // their cgroup parent, and cannot set a different cgroup parent + UsePodCgroup bool `json:"sharesCgroup,omitempty"` + + // The following UsePod{kernelNamespace} indicate whether the containers + // in the pod will inherit the namespace from the first container in the pod. + UsePodPID bool `json:"sharesPid,omitempty"` + UsePodIPC bool `json:"sharesIpc,omitempty"` + UsePodNet bool `json:"sharesNet,omitempty"` + UsePodMount bool `json:"sharesMnt,omitempty"` + UsePodUser bool `json:"sharesUser,omitempty"` + UsePodUTS bool `json:"sharesUts,omitempty"` + UsePodCgroupNS bool `json:"sharesCgroupNS,omitempty"` + + HasInfra bool `json:"hasInfra,omitempty"` + + // ServiceContainerID is the main container of a service. A service + // consists of one or more pods. The service container is started + // before all pods and is stopped when the last pod stops. + // The service container allows for tracking and managing the entire + // life cycle of service which may be started via `podman-play-kube`. + ServiceContainerID string `json:"serviceContainerID,omitempty"` + + // Time pod was created + CreatedTime time.Time `json:"created"` + + // CreateCommand is the full command plus arguments of the process the + // container has been created with. + CreateCommand []string `json:"CreateCommand,omitempty"` + + // The pod's exit policy. + ExitPolicy config.PodExitPolicy `json:"ExitPolicy,omitempty"` + + // The pod's restart policy + RestartPolicy string `json:"RestartPolicy,omitempty"` + + // The max number of retries for a pod based on restart policy + RestartRetries *uint `json:"RestartRetries,omitempty"` + + // ID of the pod's lock + LockID uint32 `json:"lockID"` + + // ResourceLimits hold the pod level resource limits + ResourceLimits specs.LinuxResources +} + +// podState represents a pod's state +type podState struct { + // CgroupPath is the path to the pod's Cgroup + CgroupPath string `json:"cgroupPath"` + // InfraContainerID is the container that holds pod namespace information + // Most often an infra container + InfraContainerID string +} + +// ID retrieves the pod's ID +func (p *Pod) ID() string { + return p.config.ID +} + +// Name retrieves the pod's name +func (p *Pod) Name() string { + return p.config.Name +} + +// MountLabel returns the SELinux label associated with the pod +func (p *Pod) MountLabel() (string, error) { + if !p.HasInfraContainer() { + return "", nil + } + ctr, err := p.infraContainer() + if err != nil { + return "", err + } + return ctr.MountLabel(), nil +} + +// Namespace returns the pod's libpod namespace. +// Namespaces are used to logically separate containers and pods in the state. +func (p *Pod) Namespace() string { + return p.config.Namespace +} + +// ResourceLim returns the cpuset resource limits for the pod +func (p *Pod) ResourceLim() *specs.LinuxResources { + resCopy := &specs.LinuxResources{} + empty := &specs.LinuxResources{ + CPU: &specs.LinuxCPU{}, + } + if err := JSONDeepCopy(p.config.ResourceLimits, resCopy); err != nil { + return nil + } + if resCopy.CPU != nil { + return resCopy + } + + return empty +} + +// CPUPeriod returns the pod CPU period +func (p *Pod) CPUPeriod() uint64 { + resLim := p.ResourceLim() + if resLim.CPU == nil || resLim.CPU.Period == nil { + return 0 + } + return *resLim.CPU.Period +} + +// CPUQuota returns the pod CPU quota +func (p *Pod) CPUQuota() int64 { + resLim := p.ResourceLim() + if resLim.CPU == nil || resLim.CPU.Quota == nil { + return 0 + } + return *resLim.CPU.Quota +} + +// MemoryLimit returns the pod Memory Limit +func (p *Pod) MemoryLimit() uint64 { + resLim := p.ResourceLim() + if resLim.Memory == nil || resLim.Memory.Limit == nil { + return 0 + } + return uint64(*resLim.Memory.Limit) +} + +// MemorySwap returns the pod Memory swap limit +func (p *Pod) MemorySwap() uint64 { + resLim := p.ResourceLim() + if resLim.Memory == nil || resLim.Memory.Swap == nil { + return 0 + } + return uint64(*resLim.Memory.Swap) +} + +// BlkioWeight returns the pod blkio weight +func (p *Pod) BlkioWeight() uint64 { + resLim := p.ResourceLim() + if resLim.BlockIO == nil || resLim.BlockIO.Weight == nil { + return 0 + } + return uint64(*resLim.BlockIO.Weight) +} + +// CPUSetMems returns the pod CPUSet memory nodes +func (p *Pod) CPUSetMems() string { + resLim := p.ResourceLim() + if resLim.CPU == nil { + return "" + } + return resLim.CPU.Mems +} + +// CPUShares returns the pod cpu shares +func (p *Pod) CPUShares() uint64 { + resLim := p.ResourceLim() + if resLim.CPU == nil || resLim.CPU.Shares == nil { + return 0 + } + return *resLim.CPU.Shares +} + +// BlkiThrottleReadBps returns the pod throttle devices +func (p *Pod) BlkiThrottleReadBps() []define.InspectBlkioThrottleDevice { + resLim := p.ResourceLim() + if resLim.BlockIO == nil || resLim.BlockIO.ThrottleReadBpsDevice == nil { + return []define.InspectBlkioThrottleDevice{} + } + devs, err := blkioDeviceThrottle(nil, resLim.BlockIO.ThrottleReadBpsDevice) + if err != nil { + return []define.InspectBlkioThrottleDevice{} + } + return devs +} + +// BlkiThrottleWriteBps returns the pod throttle devices +func (p *Pod) BlkiThrottleWriteBps() []define.InspectBlkioThrottleDevice { + resLim := p.ResourceLim() + if resLim.BlockIO == nil || resLim.BlockIO.ThrottleWriteBpsDevice == nil { + return []define.InspectBlkioThrottleDevice{} + } + devs, err := blkioDeviceThrottle(nil, resLim.BlockIO.ThrottleWriteBpsDevice) + if err != nil { + return []define.InspectBlkioThrottleDevice{} + } + return devs +} + +// NetworkMode returns the Network mode given by the user ex: pod, private... +func (p *Pod) NetworkMode() string { + infra, err := p.runtime.GetContainer(p.state.InfraContainerID) + if err != nil { + return "" + } + return infra.NetworkMode() +} + +// Namespace Mode returns the given NS mode provided by the user ex: host, private... +func (p *Pod) NamespaceMode(kind specs.LinuxNamespaceType) string { + infra, err := p.runtime.GetContainer(p.state.InfraContainerID) + if err != nil { + return "" + } + ctrSpec := infra.config.Spec + if ctrSpec != nil && ctrSpec.Linux != nil { + for _, ns := range ctrSpec.Linux.Namespaces { + if ns.Type == kind { + if ns.Path != "" { + return fmt.Sprintf("ns:%s", ns.Path) + } + return "private" + } + } + return "host" + } + return "" +} + +// CPUQuota returns the pod CPU quota +func (p *Pod) VolumesFrom() []string { + if p.state.InfraContainerID == "" { + return nil + } + infra, err := p.runtime.GetContainer(p.state.InfraContainerID) + if err != nil { + return nil + } + if ctrs, ok := infra.config.Spec.Annotations[define.VolumesFromAnnotation]; ok { + return strings.Split(ctrs, ";") + } + return nil +} + +// Labels returns the pod's labels +func (p *Pod) Labels() map[string]string { + labels := make(map[string]string) + maps.Copy(labels, p.config.Labels) + + return labels +} + +// CreatedTime gets the time when the pod was created +func (p *Pod) CreatedTime() time.Time { + return p.config.CreatedTime +} + +// CreateCommand returns the os.Args of the process with which the pod has been +// created. +func (p *Pod) CreateCommand() []string { + return p.config.CreateCommand +} + +// CgroupParent returns the pod's Cgroup parent +func (p *Pod) CgroupParent() string { + return p.config.CgroupParent +} + +// SharesPID returns whether containers in pod +// default to use PID namespace of first container in pod +func (p *Pod) SharesPID() bool { + return p.config.UsePodPID +} + +// SharesIPC returns whether containers in pod +// default to use IPC namespace of first container in pod +func (p *Pod) SharesIPC() bool { + return p.config.UsePodIPC +} + +// SharesNet returns whether containers in pod +// default to use network namespace of first container in pod +func (p *Pod) SharesNet() bool { + return p.config.UsePodNet +} + +// SharesMount returns whether containers in pod +// default to use PID namespace of first container in pod +func (p *Pod) SharesMount() bool { + return p.config.UsePodMount +} + +// SharesUser returns whether containers in pod +// default to use user namespace of first container in pod +func (p *Pod) SharesUser() bool { + return p.config.UsePodUser +} + +// SharesUTS returns whether containers in pod +// default to use UTS namespace of first container in pod +func (p *Pod) SharesUTS() bool { + return p.config.UsePodUTS +} + +// SharesCgroup returns whether containers in the pod will default to this pod's +// cgroup instead of the default libpod parent +func (p *Pod) SharesCgroup() bool { + return p.config.UsePodCgroupNS +} + +// Hostname returns the hostname of the pod. +func (p *Pod) Hostname() string { + return p.config.Hostname +} + +// CgroupPath returns the path to the pod's Cgroup +func (p *Pod) CgroupPath() (string, error) { + p.lock.Lock() + defer p.lock.Unlock() + if err := p.updatePod(); err != nil { + return "", err + } + return p.state.CgroupPath, nil +} + +// HasContainer checks if a container is present in the pod +func (p *Pod) HasContainer(id string) (bool, error) { + if !p.valid { + return false, define.ErrPodRemoved + } + + return p.runtime.state.PodHasContainer(p, id) +} + +// AllContainersByID returns the container IDs of all the containers in the pod +func (p *Pod) AllContainersByID() ([]string, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, define.ErrPodRemoved + } + + return p.runtime.state.PodContainersByID(p) +} + +// AllContainers retrieves the containers in the pod +func (p *Pod) AllContainers() ([]*Container, error) { + if !p.valid { + return nil, define.ErrPodRemoved + } + p.lock.Lock() + defer p.lock.Unlock() + return p.allContainers() +} + +func (p *Pod) allContainers() ([]*Container, error) { + return p.runtime.state.PodContainers(p) +} + +// HasInfraContainer returns whether the pod will create an infra container +func (p *Pod) HasInfraContainer() bool { + return p.config.HasInfra +} + +// SharesNamespaces checks if the pod has any kernel namespaces set as shared. An infra container will not be +// created if no kernel namespaces are shared. +func (p *Pod) SharesNamespaces() bool { + return p.SharesPID() || p.SharesIPC() || p.SharesNet() || p.SharesMount() || p.SharesUser() || p.SharesUTS() +} + +// infraContainerID returns the infra ID without a lock +func (p *Pod) infraContainerID() (string, error) { + if err := p.updatePod(); err != nil { + return "", err + } + return p.state.InfraContainerID, nil +} + +// InfraContainerID returns the infra container ID for a pod. +// If the container returned is "", the pod has no infra container. +func (p *Pod) InfraContainerID() (string, error) { + p.lock.Lock() + defer p.lock.Unlock() + return p.infraContainerID() +} + +// infraContainer is the unlocked version of InfraContainer which returns the infra container +func (p *Pod) infraContainer() (*Container, error) { + id, err := p.infraContainerID() + if err != nil { + return nil, err + } + if id == "" { + return nil, fmt.Errorf("pod has no infra container: %w", define.ErrNoSuchCtr) + } + + return p.runtime.state.Container(id) +} + +// InfraContainer returns the infra container. +func (p *Pod) InfraContainer() (*Container, error) { + p.lock.Lock() + defer p.lock.Unlock() + return p.infraContainer() +} + +// TODO add pod batching +// Lock pod to avoid lock contention +// Store and lock all containers (no RemoveContainer in batch guarantees cache will not become stale) + +// PodContainerStats is an organization struct for pods and their containers +type PodContainerStats struct { + Pod *Pod + ContainerStats map[string]*define.ContainerStats +} + +// GetPodStats returns the stats for each of its containers +func (p *Pod) GetPodStats() (map[string]*define.ContainerStats, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if err := p.updatePod(); err != nil { + return nil, err + } + containers, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + newContainerStats := make(map[string]*define.ContainerStats) + for _, c := range containers { + newStats, err := c.GetContainerStats(nil) + if err != nil { + // If the container wasn't running ignore it + if errors.Is(err, define.ErrCtrStateInvalid) || errors.Is(err, define.ErrCtrStopped) { + continue + } + return nil, err + } + newContainerStats[c.ID()] = newStats + } + return newContainerStats, nil +} + +// ProcessLabel returns the SELinux label associated with the pod +func (p *Pod) ProcessLabel() (string, error) { + if !p.HasInfraContainer() { + return "", nil + } + ctr, err := p.infraContainer() + if err != nil { + return "", err + } + return ctr.ProcessLabel(), nil +} + +// initContainers returns the list of initcontainers +// in a pod sorted by create time +func (p *Pod) initContainers() ([]*Container, error) { + initCons := make([]*Container, 0) + // the pod is already locked when this is called + cons, err := p.allContainers() + if err != nil { + return nil, err + } + // Sort the pod containers by created time + sort.Slice(cons, func(i, j int) bool { return cons[i].CreatedTime().Before(cons[j].CreatedTime()) }) + // Iterate sorted containers and add ids for any init containers + for _, c := range cons { + if len(c.config.InitContainerType) > 0 { + initCons = append(initCons, c) + } + } + return initCons, nil +} + +func (p *Pod) Config() (*PodConfig, error) { + p.lock.Lock() + defer p.lock.Unlock() + + conf := &PodConfig{} + + err := JSONDeepCopy(p.config, conf) + + return conf, err +} + +// ConfigNoCopy returns the configuration used by the pod. +// Note that the returned value is not a copy and must hence +// only be used in a reading fashion. +func (p *Pod) ConfigNoCopy() *PodConfig { + return p.config +} diff --git a/vendor/github.com/containers/podman/v5/libpod/pod_api.go b/vendor/github.com/containers/podman/v5/libpod/pod_api.go new file mode 100644 index 00000000..adaff0a3 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/pod_api.go @@ -0,0 +1,763 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/pkg/parallel" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/cgroups" +) + +// startInitContainers starts a pod's init containers. +func (p *Pod) startInitContainers(ctx context.Context) error { + initCtrs, err := p.initContainers() + if err != nil { + return err + } + // Now iterate init containers + for _, initCon := range initCtrs { + if err := initCon.startNoPodLock(ctx, true); err != nil { + return err + } + // Check that the init container waited correctly and the exit + // code is good + rc, err := initCon.Wait(ctx) + if err != nil { + return err + } + if rc != 0 { + return fmt.Errorf("init container %s exited with code %d", initCon.ID(), rc) + } + // If the container is a once init container, we need to remove it + // after it runs + if initCon.config.InitContainerType == define.OneShotInitContainer { + icLock := initCon.lock + icLock.Lock() + var time *uint + opts := ctrRmOpts{ + RemovePod: true, + Timeout: time, + } + + if _, _, err := p.runtime.removeContainer(ctx, initCon, opts); err != nil { + icLock.Unlock() + return fmt.Errorf("failed to remove once init container %s: %w", initCon.ID(), err) + } + icLock.Unlock() + } + } + return nil +} + +// Start starts all containers within a pod. +// It combines the effects of Init() and Start() on a container. +// If a container has already been initialized it will be started, +// otherwise it will be initialized then started. +// Containers that are already running or have been paused are ignored +// All containers are started independently, in order dictated by their +// dependencies. +// An error and a map[string]error are returned. +// If the error is not nil and the map is nil, an error was encountered before +// any containers were started. +// If map is not nil, an error was encountered when starting one or more +// containers. The container ID is mapped to the error encountered. The error is +// set to ErrPodPartialFail. +// If both error and the map are nil, all containers were started successfully. +func (p *Pod) Start(ctx context.Context) (map[string]error, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, define.ErrPodRemoved + } + + if err := p.maybeStartServiceContainer(ctx); err != nil { + return nil, err + } + + // Before "regular" containers start in the pod, all init containers + // must have run and exited successfully. + if err := p.startInitContainers(ctx); err != nil { + return nil, err + } + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + // Build a dependency graph of containers in the pod + graph, err := BuildContainerGraph(allCtrs) + if err != nil { + return nil, fmt.Errorf("generating dependency graph for pod %s: %w", p.ID(), err) + } + // If there are no containers without dependencies, we can't start + // Error out + if len(graph.noDepNodes) == 0 { + return nil, fmt.Errorf("no containers in pod %s have no dependencies, cannot start pod: %w", p.ID(), define.ErrNoSuchCtr) + } + + ctrErrors := make(map[string]error) + ctrsVisited := make(map[string]bool) + + // Traverse the graph beginning at nodes with no dependencies + for _, node := range graph.noDepNodes { + startNode(ctx, node, false, ctrErrors, ctrsVisited, false) + } + + if len(ctrErrors) > 0 { + return ctrErrors, fmt.Errorf("starting some containers: %w", define.ErrPodPartialFail) + } + defer p.newPodEvent(events.Start) + return nil, nil +} + +// Stop stops all containers within a pod without a timeout. It assumes -1 for +// a timeout. +func (p *Pod) Stop(ctx context.Context, cleanup bool) (map[string]error, error) { + return p.StopWithTimeout(ctx, cleanup, -1) +} + +// StopWithTimeout stops all containers within a pod that are not already stopped +// Each container will use its own stop timeout. +// Only running containers will be stopped. Paused, stopped, or created +// containers will be ignored. +// If cleanup is true, mounts and network namespaces will be cleaned up after +// the container is stopped. +// All containers are stopped independently. An error stopping one container +// will not prevent other containers being stopped. +// An error and a map[string]error are returned. +// If the error is not nil and the map is nil, an error was encountered before +// any containers were stopped. +// If map is not nil, an error was encountered when stopping one or more +// containers. The container ID is mapped to the error encountered. The error is +// set to ErrPodPartialFail. +// If both error and the map are nil, all containers were stopped without error. +func (p *Pod) StopWithTimeout(ctx context.Context, cleanup bool, timeout int) (map[string]error, error) { + p.lock.Lock() + defer p.lock.Unlock() + + return p.stopWithTimeout(ctx, cleanup, timeout) +} + +func (p *Pod) stopWithTimeout(ctx context.Context, cleanup bool, timeout int) (map[string]error, error) { + if !p.valid { + return nil, define.ErrPodRemoved + } + + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + + p.newPodEvent(events.Stop) + + var ctrErrors map[string]error + + // Try and generate a graph of the pod for ordered stop. + graph, err := BuildContainerGraph(allCtrs) + if err != nil { + // Can't do an ordered stop, do it the old fashioned way. + logrus.Warnf("Unable to build graph for pod %s, switching to unordered stop: %v", p.ID(), err) + + ctrErrors = make(map[string]error) + for _, ctr := range allCtrs { + var err error + if timeout > -1 { + err = ctr.StopWithTimeout(uint(timeout)) + } else { + err = ctr.Stop() + } + if err != nil && !errors.Is(err, define.ErrCtrStateInvalid) && !errors.Is(err, define.ErrCtrStopped) { + ctrErrors[ctr.ID()] = err + } else if cleanup { + err := ctr.Cleanup(ctx, false) + if err != nil && !errors.Is(err, define.ErrCtrStateInvalid) && !errors.Is(err, define.ErrCtrStopped) { + ctrErrors[ctr.ID()] = err + } + } + } + } else { + var realTimeout *uint + if timeout > -1 { + innerTimeout := uint(timeout) + realTimeout = &innerTimeout + } + + ctrErrors, err = stopContainerGraph(ctx, graph, p, realTimeout, cleanup) + if err != nil { + return nil, err + } + } + + if len(ctrErrors) > 0 { + return ctrErrors, fmt.Errorf("stopping some containers: %w", define.ErrPodPartialFail) + } + + if err := p.maybeStopServiceContainer(); err != nil { + return nil, err + } + + if err := p.updatePod(); err != nil { + return nil, err + } + if err := p.removePodCgroup(); err != nil { + return nil, err + } + + return nil, nil +} + +// Stops the pod if only the infra containers remains running. +func (p *Pod) stopIfOnlyInfraRemains(ctx context.Context, ignoreID string) error { + p.lock.Lock() + defer p.lock.Unlock() + + infraID := "" + + if p.HasInfraContainer() { + infra, err := p.infraContainer() + if err != nil { + return err + } + infraID = infra.ID() + } + + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return err + } + + for _, ctr := range allCtrs { + if ctr.ID() == infraID || ctr.ID() == ignoreID { + continue + } + + state, err := ctr.State() + if err != nil { + return fmt.Errorf("getting state of container %s: %w", ctr.ID(), err) + } + + switch state { + case define.ContainerStateExited, + define.ContainerStateRemoving, + define.ContainerStateStopping, + define.ContainerStateUnknown: + continue + default: + return nil + } + } + + errs, err := p.stopWithTimeout(ctx, true, -1) + for ctr, e := range errs { + logrus.Errorf("Failed to stop container %s: %v", ctr, e) + } + return err +} + +// Cleanup cleans up all containers within a pod that have stopped. +// All containers are cleaned up independently. An error with one container will +// not prevent other containers being cleaned up. +// An error and a map[string]error are returned. +// If the error is not nil and the map is nil, an error was encountered before +// any containers were cleaned up. +// If map is not nil, an error was encountered when working on one or more +// containers. The container ID is mapped to the error encountered. The error is +// set to ErrPodPartialFail. +// If both error and the map are nil, all containers were paused without error +func (p *Pod) Cleanup(ctx context.Context) (map[string]error, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, define.ErrPodRemoved + } + + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + + ctrErrChan := make(map[string]<-chan error) + + // Enqueue a function for each container with the parallel executor. + for _, ctr := range allCtrs { + c := ctr + logrus.Debugf("Adding parallel job to clean up container %s", c.ID()) + retChan := parallel.Enqueue(ctx, func() error { + return c.Cleanup(ctx, false) + }) + + ctrErrChan[c.ID()] = retChan + } + + ctrErrors := make(map[string]error) + + // Get returned error for every container we worked on + for id, channel := range ctrErrChan { + if err := <-channel; err != nil { + if errors.Is(err, define.ErrCtrStateInvalid) || errors.Is(err, define.ErrCtrStopped) { + continue + } + ctrErrors[id] = err + } + } + + if len(ctrErrors) > 0 { + return ctrErrors, fmt.Errorf("cleaning up some containers: %w", define.ErrPodPartialFail) + } + + if err := p.maybeStopServiceContainer(); err != nil { + return nil, err + } + + return nil, nil +} + +// Pause pauses all containers within a pod that are running. +// Only running containers will be paused. Paused, stopped, or created +// containers will be ignored. +// All containers are paused independently. An error pausing one container +// will not prevent other containers being paused. +// An error and a map[string]error are returned. +// If the error is not nil and the map is nil, an error was encountered before +// any containers were paused. +// If map is not nil, an error was encountered when pausing one or more +// containers. The container ID is mapped to the error encountered. The error is +// set to ErrPodPartialFail. +// If both error and the map are nil, all containers were paused without error +func (p *Pod) Pause(ctx context.Context) (map[string]error, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, define.ErrPodRemoved + } + + if rootless.IsRootless() { + cgroupv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, fmt.Errorf("failed to determine cgroupversion: %w", err) + } + if !cgroupv2 { + return nil, fmt.Errorf("can not pause pods containing rootless containers with cgroup V1: %w", define.ErrNoCgroups) + } + } + + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + + ctrErrChan := make(map[string]<-chan error) + + // Enqueue a function for each container with the parallel executor. + for _, ctr := range allCtrs { + c := ctr + logrus.Debugf("Adding parallel job to pause container %s", c.ID()) + retChan := parallel.Enqueue(ctx, c.Pause) + + ctrErrChan[c.ID()] = retChan + } + + p.newPodEvent(events.Pause) + + ctrErrors := make(map[string]error) + + // Get returned error for every container we worked on + for id, channel := range ctrErrChan { + if err := <-channel; err != nil { + if errors.Is(err, define.ErrCtrStateInvalid) || errors.Is(err, define.ErrCtrStopped) { + continue + } + ctrErrors[id] = err + } + } + + if len(ctrErrors) > 0 { + return ctrErrors, fmt.Errorf("pausing some containers: %w", define.ErrPodPartialFail) + } + return nil, nil +} + +// Unpause unpauses all containers within a pod that are running. +// Only paused containers will be unpaused. Running, stopped, or created +// containers will be ignored. +// All containers are unpaused independently. An error unpausing one container +// will not prevent other containers being unpaused. +// An error and a map[string]error are returned. +// If the error is not nil and the map is nil, an error was encountered before +// any containers were unpaused. +// If map is not nil, an error was encountered when unpausing one or more +// containers. The container ID is mapped to the error encountered. The error is +// set to ErrPodPartialFail. +// If both error and the map are nil, all containers were unpaused without error. +func (p *Pod) Unpause(ctx context.Context) (map[string]error, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, define.ErrPodRemoved + } + + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + + ctrErrChan := make(map[string]<-chan error) + + // Enqueue a function for each container with the parallel executor. + for _, ctr := range allCtrs { + c := ctr + logrus.Debugf("Adding parallel job to unpause container %s", c.ID()) + retChan := parallel.Enqueue(ctx, c.Unpause) + + ctrErrChan[c.ID()] = retChan + } + + p.newPodEvent(events.Unpause) + + ctrErrors := make(map[string]error) + + // Get returned error for every container we worked on + for id, channel := range ctrErrChan { + if err := <-channel; err != nil { + if errors.Is(err, define.ErrCtrStateInvalid) || errors.Is(err, define.ErrCtrStopped) { + continue + } + ctrErrors[id] = err + } + } + + if len(ctrErrors) > 0 { + return ctrErrors, fmt.Errorf("unpausing some containers: %w", define.ErrPodPartialFail) + } + return nil, nil +} + +// Restart restarts all containers within a pod that are not paused or in an error state. +// It combines the effects of Stop() and Start() on a container +// Each container will use its own stop timeout. +// All containers are started independently, in order dictated by their +// dependencies. An error restarting one container +// will not prevent other containers being restarted. +// An error and a map[string]error are returned. +// If the error is not nil and the map is nil, an error was encountered before +// any containers were restarted. +// If map is not nil, an error was encountered when restarting one or more +// containers. The container ID is mapped to the error encountered. The error is +// set to ErrPodPartialFail. +// If both error and the map are nil, all containers were restarted without error. +func (p *Pod) Restart(ctx context.Context) (map[string]error, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, define.ErrPodRemoved + } + + if err := p.maybeStartServiceContainer(ctx); err != nil { + return nil, err + } + + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + + // Build a dependency graph of containers in the pod + graph, err := BuildContainerGraph(allCtrs) + if err != nil { + return nil, fmt.Errorf("generating dependency graph for pod %s: %w", p.ID(), err) + } + + ctrErrors := make(map[string]error) + ctrsVisited := make(map[string]bool) + + // If there are no containers without dependencies, we can't start + // Error out + if len(graph.noDepNodes) == 0 { + return nil, fmt.Errorf("no containers in pod %s have no dependencies, cannot start pod: %w", p.ID(), define.ErrNoSuchCtr) + } + + // Traverse the graph beginning at nodes with no dependencies + for _, node := range graph.noDepNodes { + startNode(ctx, node, false, ctrErrors, ctrsVisited, true) + } + + if len(ctrErrors) > 0 { + return ctrErrors, fmt.Errorf("stopping some containers: %w", define.ErrPodPartialFail) + } + p.newPodEvent(events.Stop) + p.newPodEvent(events.Start) + return nil, nil +} + +// Kill sends a signal to all running containers within a pod. +// Signals will only be sent to running containers. Containers that are not +// running will be ignored. All signals are sent independently, and sending will +// continue even if some containers encounter errors. +// An error and a map[string]error are returned. +// If the error is not nil and the map is nil, an error was encountered before +// any containers were signalled. +// If map is not nil, an error was encountered when signalling one or more +// containers. The container ID is mapped to the error encountered. The error is +// set to ErrPodPartialFail. +// If both error and the map are nil, all containers were signalled successfully. +func (p *Pod) Kill(ctx context.Context, signal uint) (map[string]error, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, define.ErrPodRemoved + } + + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + + ctrErrChan := make(map[string]<-chan error) + + // Enqueue a function for each container with the parallel executor. + for _, ctr := range allCtrs { + c := ctr + logrus.Debugf("Adding parallel job to kill container %s", c.ID()) + retChan := parallel.Enqueue(ctx, func() error { + return c.Kill(signal) + }) + + ctrErrChan[c.ID()] = retChan + } + + p.newPodEvent(events.Kill) + + ctrErrors := make(map[string]error) + + // Get returned error for every container we worked on + for id, channel := range ctrErrChan { + if err := <-channel; err != nil { + if errors.Is(err, define.ErrCtrStateInvalid) || errors.Is(err, define.ErrCtrStopped) { + continue + } + ctrErrors[id] = err + } + } + + if len(ctrErrors) > 0 { + return ctrErrors, fmt.Errorf("killing some containers: %w", define.ErrPodPartialFail) + } + + if err := p.maybeStopServiceContainer(); err != nil { + return nil, err + } + + return nil, nil +} + +// Status gets the status of all containers in the pod. +// Returns a map of Container ID to Container Status. +func (p *Pod) Status() (map[string]define.ContainerStatus, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, define.ErrPodRemoved + } + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + noInitCtrs := make([]*Container, 0) + // Do not add init containers into status + for _, ctr := range allCtrs { + if ctrType := ctr.config.InitContainerType; len(ctrType) < 1 { + noInitCtrs = append(noInitCtrs, ctr) + } + } + return containerStatusFromContainers(noInitCtrs) +} + +func containerStatusFromContainers(allCtrs []*Container) (map[string]define.ContainerStatus, error) { + status := make(map[string]define.ContainerStatus, len(allCtrs)) + for _, ctr := range allCtrs { + state, err := ctr.State() + + if err != nil { + return nil, err + } + + status[ctr.ID()] = state + } + + return status, nil +} + +// Inspect returns a PodInspect struct to describe the pod. +func (p *Pod) Inspect() (*define.InspectPodData, error) { + p.lock.Lock() + defer p.lock.Unlock() + if err := p.updatePod(); err != nil { + return nil, err + } + + containers, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + ctrs := make([]define.InspectPodContainerInfo, 0, len(containers)) + ctrStatuses := make(map[string]define.ContainerStatus, len(containers)) + for _, c := range containers { + containerStatus := "unknown" + // Ignoring possible errors here because we don't want this to be + // catastrophic in nature + containerState, err := c.State() + if err == nil { + containerStatus = containerState.String() + } + ctrs = append(ctrs, define.InspectPodContainerInfo{ + ID: c.ID(), + Name: c.Name(), + State: containerStatus, + }) + // Do not add init containers fdr status + if len(c.config.InitContainerType) < 1 { + ctrStatuses[c.ID()] = c.state.State + } + } + podState, err := createPodStatusResults(ctrStatuses) + if err != nil { + return nil, err + } + + namespaces := map[string]bool{ + "pid": p.config.UsePodPID, + "ipc": p.config.UsePodIPC, + "net": p.config.UsePodNet, + "mount": p.config.UsePodMount, + "user": p.config.UsePodUser, + "uts": p.config.UsePodUTS, + "cgroup": p.config.UsePodCgroupNS, + } + + sharesNS := []string{} + for nsStr, include := range namespaces { + if include { + sharesNS = append(sharesNS, nsStr) + } + } + + // Infra config contains detailed information on the pod's infra + // container. + var infraConfig *define.InspectPodInfraConfig + var inspectMounts []define.InspectMount + var devices []define.InspectDevice + var infraSecurity []string + if p.state.InfraContainerID != "" { + infra, err := p.runtime.GetContainer(p.state.InfraContainerID) + if err != nil { + return nil, err + } + infraConfig = new(define.InspectPodInfraConfig) + infraConfig.HostNetwork = p.NetworkMode() == "host" + infraConfig.StaticIP = infra.config.ContainerNetworkConfig.StaticIP + infraConfig.NoManageResolvConf = infra.config.UseImageResolvConf + infraConfig.NoManageHostname = infra.config.UseImageHostname + infraConfig.NoManageHosts = infra.config.UseImageHosts + infraConfig.CPUPeriod = p.CPUPeriod() + infraConfig.CPUQuota = p.CPUQuota() + infraConfig.CPUSetCPUs = p.ResourceLim().CPU.Cpus + infraConfig.PidNS = p.NamespaceMode(specs.PIDNamespace) + infraConfig.UserNS = p.NamespaceMode(specs.UserNamespace) + infraConfig.UtsNS = p.NamespaceMode(specs.UTSNamespace) + namedVolumes, mounts := infra.SortUserVolumes(infra.config.Spec) + inspectMounts, err = infra.GetMounts(namedVolumes, infra.config.ImageVolumes, mounts) + infraSecurity = infra.GetSecurityOptions() + if err != nil { + return nil, err + } + + if len(infra.config.ContainerNetworkConfig.DNSServer) > 0 { + infraConfig.DNSServer = make([]string, 0, len(infra.config.ContainerNetworkConfig.DNSServer)) + for _, entry := range infra.config.ContainerNetworkConfig.DNSServer { + infraConfig.DNSServer = append(infraConfig.DNSServer, entry.String()) + } + } + if len(infra.config.ContainerNetworkConfig.DNSSearch) > 0 { + infraConfig.DNSSearch = make([]string, 0, len(infra.config.ContainerNetworkConfig.DNSSearch)) + infraConfig.DNSSearch = append(infraConfig.DNSSearch, infra.config.ContainerNetworkConfig.DNSSearch...) + } + if len(infra.config.ContainerNetworkConfig.DNSOption) > 0 { + infraConfig.DNSOption = make([]string, 0, len(infra.config.ContainerNetworkConfig.DNSOption)) + infraConfig.DNSOption = append(infraConfig.DNSOption, infra.config.ContainerNetworkConfig.DNSOption...) + } + if len(infra.config.HostAdd) > 0 { + infraConfig.HostAdd = make([]string, 0, len(infra.config.HostAdd)) + infraConfig.HostAdd = append(infraConfig.HostAdd, infra.config.HostAdd...) + } + if len(infra.config.BaseHostsFile) > 0 { + infraConfig.HostsFile = infra.config.BaseHostsFile + } + + networks, err := infra.networks() + if err != nil { + return nil, err + } + netNames := make([]string, 0, len(networks)) + for name := range networks { + netNames = append(netNames, name) + } + if len(netNames) > 0 { + infraConfig.Networks = netNames + } + infraConfig.NetworkOptions = infra.config.ContainerNetworkConfig.NetworkOptions + infraConfig.PortBindings = makeInspectPortBindings(infra.config.ContainerNetworkConfig.PortMappings) + } + + inspectData := define.InspectPodData{ + ID: p.ID(), + Name: p.Name(), + Namespace: p.Namespace(), + Created: p.CreatedTime(), + CreateCommand: p.config.CreateCommand, + ExitPolicy: string(p.config.ExitPolicy), + State: podState, + Hostname: p.config.Hostname, + Labels: p.Labels(), + CreateCgroup: p.config.UsePodCgroup, + CgroupParent: p.CgroupParent(), + CgroupPath: p.state.CgroupPath, + CreateInfra: infraConfig != nil, + InfraContainerID: p.state.InfraContainerID, + InfraConfig: infraConfig, + SharedNamespaces: sharesNS, + NumContainers: uint(len(containers)), + Containers: ctrs, + CPUSetCPUs: p.ResourceLim().CPU.Cpus, + CPUPeriod: p.CPUPeriod(), + CPUQuota: p.CPUQuota(), + MemoryLimit: p.MemoryLimit(), + Mounts: inspectMounts, + Devices: devices, + BlkioDeviceReadBps: p.BlkiThrottleReadBps(), + VolumesFrom: p.VolumesFrom(), + SecurityOpts: infraSecurity, + MemorySwap: p.MemorySwap(), + BlkioWeight: p.BlkioWeight(), + CPUSetMems: p.CPUSetMems(), + BlkioDeviceWriteBps: p.BlkiThrottleWriteBps(), + CPUShares: p.CPUShares(), + RestartPolicy: p.config.RestartPolicy, + LockNumber: p.lock.ID(), + } + + return &inspectData, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/pod_internal.go b/vendor/github.com/containers/podman/v5/libpod/pod_internal.go new file mode 100644 index 00000000..b559ae7f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/pod_internal.go @@ -0,0 +1,78 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "time" + + "github.com/containers/podman/v5/libpod/define" + "go.podman.io/storage/pkg/stringid" +) + +// Creates a new, empty pod +func newPod(runtime *Runtime) *Pod { + pod := new(Pod) + pod.config = new(PodConfig) + pod.config.ID = stringid.GenerateRandomID() + pod.config.Labels = make(map[string]string) + pod.config.CreatedTime = time.Now() + // pod.config.InfraContainer = new(ContainerConfig) + pod.state = new(podState) + pod.runtime = runtime + + return pod +} + +// Update pod state from database +func (p *Pod) updatePod() error { + if err := p.runtime.state.UpdatePod(p); err != nil { + return err + } + + return nil +} + +// Save pod state to database +func (p *Pod) save() error { + if err := p.runtime.state.SavePod(p); err != nil { + return fmt.Errorf("saving pod %s state: %w", p.ID(), err) + } + + return nil +} + +// Refresh a pod's state after restart +// This cannot lock any other pod, but may lock individual containers, as those +// will have refreshed by the time pod refresh runs. +func (p *Pod) refresh() error { + // Need to do an update from the DB to pull potentially-missing state + if err := p.runtime.state.UpdatePod(p); err != nil { + return err + } + + if !p.valid { + return define.ErrPodRemoved + } + + // Retrieve the pod's lock + lock, err := p.runtime.lockManager.AllocateAndRetrieveLock(p.config.LockID) + if err != nil { + return fmt.Errorf("retrieving lock %d for pod %s: %w", p.config.LockID, p.ID(), err) + } + p.lock = lock + + if err := p.platformRefresh(); err != nil { + return err + } + + // Save changes + return p.save() +} + +// resetPodState resets state fields to default values. +// It is performed before a refresh and clears the state after a reboot. +// It does not save the results - assumes the database will do that for us. +func resetPodState(state *podState) { + state.CgroupPath = "" +} diff --git a/vendor/github.com/containers/podman/v5/libpod/pod_internal_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/pod_internal_freebsd.go new file mode 100644 index 00000000..f510ef37 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/pod_internal_freebsd.go @@ -0,0 +1,7 @@ +//go:build !remote + +package libpod + +func (p *Pod) platformRefresh() error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/pod_internal_linux.go b/vendor/github.com/containers/podman/v5/libpod/pod_internal_linux.go new file mode 100644 index 00000000..e4ea3d30 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/pod_internal_linux.go @@ -0,0 +1,36 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "path/filepath" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/config" +) + +func (p *Pod) platformRefresh() error { + // We need to recreate the pod's cgroup + if p.config.UsePodCgroup { + switch p.runtime.config.Engine.CgroupManager { + case config.SystemdCgroupsManager: + cgroupPath, err := systemdSliceFromPath(p.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", p.ID()), p.ResourceLim()) + if err != nil { + logrus.Errorf("Creating Cgroup for pod %s: %v", p.ID(), err) + } + p.state.CgroupPath = cgroupPath + case config.CgroupfsCgroupsManager: + if !rootless.IsRootless() || isRootlessCgroupSet(p.config.CgroupParent) { + p.state.CgroupPath = filepath.Join(p.config.CgroupParent, p.ID()) + + logrus.Debugf("setting pod cgroup to %s", p.state.CgroupPath) + } + default: + return fmt.Errorf("unknown cgroups manager %s specified: %w", p.runtime.config.Engine.CgroupManager, define.ErrInvalidArg) + } + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/pod_status.go b/vendor/github.com/containers/podman/v5/libpod/pod_status.go new file mode 100644 index 00000000..186709a2 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/pod_status.go @@ -0,0 +1,63 @@ +//go:build !remote + +package libpod + +import "github.com/containers/podman/v5/libpod/define" + +// GetPodStatus determines the status of the pod based on the +// statuses of the containers in the pod. +// Returns a string representation of the pod status +func (p *Pod) GetPodStatus() (string, error) { + ctrStatuses, err := p.Status() + if err != nil { + return define.PodStateErrored, err + } + return createPodStatusResults(ctrStatuses) +} + +func createPodStatusResults(ctrStatuses map[string]define.ContainerStatus) (string, error) { + ctrNum := len(ctrStatuses) + if ctrNum == 0 { + return define.PodStateCreated, nil + } + statuses := map[string]int{ + define.PodStateStopped: 0, + define.PodStateRunning: 0, + define.PodStatePaused: 0, + define.PodStateCreated: 0, + define.PodStateErrored: 0, + } + for _, ctrStatus := range ctrStatuses { + switch ctrStatus { + case define.ContainerStateExited: + fallthrough + case define.ContainerStateStopped: + statuses[define.PodStateStopped]++ + case define.ContainerStateRunning: + statuses[define.PodStateRunning]++ + case define.ContainerStatePaused: + statuses[define.PodStatePaused]++ + case define.ContainerStateCreated, define.ContainerStateConfigured: + statuses[define.PodStateCreated]++ + default: + statuses[define.PodStateErrored]++ + } + } + + switch { + case statuses[define.PodStateRunning] == ctrNum: + return define.PodStateRunning, nil + case statuses[define.PodStateRunning] > 0: + return define.PodStateDegraded, nil + case statuses[define.PodStatePaused] == ctrNum: + return define.PodStatePaused, nil + case statuses[define.PodStateStopped] == ctrNum: + return define.PodStateExited, nil + case statuses[define.PodStateStopped] > 0: + return define.PodStateStopped, nil + case statuses[define.PodStateErrored] > 0: + return define.PodStateErrored, nil + default: + return define.PodStateCreated, nil + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/pod_top_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/pod_top_freebsd.go new file mode 100644 index 00000000..01759441 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/pod_top_freebsd.go @@ -0,0 +1,78 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "strings" + + "github.com/containers/podman/v5/libpod/define" +) + +// GetPodPidInformation returns process-related data of all processes in +// the pod. The output data can be controlled via the `descriptors` +// argument which expects format descriptors and supports all AIXformat +// descriptors of ps (1) plus some additional ones to for instance inspect the +// set of effective capabilities. Each element in the returned string slice +// is a tab-separated string. +// +// For more details, please refer to github.com/containers/psgo. +func (p *Pod) GetPodPidInformation(descriptors []string) ([]string, error) { + // Default to 'ps -ef' compatible descriptors + if len(strings.Join(descriptors, "")) == 0 { + descriptors = []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "args"} + } + + jailNames := make([]string, 0) + ctrsInPod, err := p.AllContainers() + if err != nil { + return nil, err + } + for _, c := range ctrsInPod { + c.lock.Lock() + err := c.syncContainer() + c.lock.Unlock() + if err != nil { + return nil, err + } + + if c.state.State == define.ContainerStateRunning { + jailName, err := c.jailName() + if err != nil { + return nil, fmt.Errorf("getting jail name: %w", err) + } + jailNames = append(jailNames, jailName) + } + } + + // Also support comma-separated input. + psDescriptors := []string{} + for _, d := range descriptors { + for _, s := range strings.Split(d, ",") { + if s != "" { + psDescriptors = append(psDescriptors, s) + } + } + } + + // For consistency with pod_top_linux.go, only allow descriptor names + for _, d := range psDescriptors { + if _, ok := isDescriptor[d]; !ok { + return nil, fmt.Errorf("unknown descriptor: %s", d) + } + } + + args := []string{ + "-J", + strings.Join(jailNames, ","), + "-ao", + strings.Join(psDescriptors, ","), + } + + output, err := execPS(args) + if err != nil { + return nil, fmt.Errorf("executing ps(1): %w", err) + } + + return output, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/pod_top_linux.go b/vendor/github.com/containers/podman/v5/libpod/pod_top_linux.go new file mode 100644 index 00000000..76717899 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/pod_top_linux.go @@ -0,0 +1,68 @@ +//go:build !remote + +package libpod + +import ( + "strconv" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/psgo" +) + +// GetPodPidInformation returns process-related data of all processes in +// the pod. The output data can be controlled via the `descriptors` +// argument which expects format descriptors and supports all AIXformat +// descriptors of ps (1) plus some additional ones to for instance inspect the +// set of effective capabilities. Each element in the returned string slice +// is a tab-separated string. +// +// For more details, please refer to github.com/containers/psgo. +func (p *Pod) GetPodPidInformation(descriptors []string) ([]string, error) { + p.lock.Lock() + defer p.lock.Unlock() + + pids := make([]string, 0) + ctrsInPod, err := p.allContainers() + if err != nil { + return nil, err + } + for _, c := range ctrsInPod { + c.lock.Lock() + + if err := c.syncContainer(); err != nil { + c.lock.Unlock() + return nil, err + } + if c.state.State == define.ContainerStateRunning { + pid := strconv.Itoa(c.state.PID) + pids = append(pids, pid) + } + c.lock.Unlock() + } + + // Also support comma-separated input. + psgoDescriptors := []string{} + for _, d := range descriptors { + for s := range strings.SplitSeq(d, ",") { + if s != "" { + psgoDescriptors = append(psgoDescriptors, s) + } + } + } + + // NOTE: psgo returns a [][]string to give users the ability to apply + // filters on the data. We need to change the API here to return + // a [][]string if we want to make use of filtering. + opts := psgo.JoinNamespaceOpts{FillMappings: rootless.IsRootless()} + output, err := psgo.JoinNamespaceAndProcessInfoByPidsWithOptions(pids, psgoDescriptors, &opts) + if err != nil { + return nil, err + } + res := []string{} + for _, out := range output { + res = append(res, strings.Join(out, "\t")) + } + return res, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/reset.go b/vendor/github.com/containers/podman/v5/libpod/reset.go new file mode 100644 index 00000000..addb40c7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/reset.go @@ -0,0 +1,295 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/errorhandling" + "github.com/containers/podman/v5/pkg/util" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + "go.podman.io/common/libnetwork/types" + blobinfocache "go.podman.io/image/v5/pkg/blobinfocache" + "go.podman.io/storage" + "go.podman.io/storage/pkg/lockfile" + stypes "go.podman.io/storage/types" +) + +// removeAllDirs removes all Podman storage directories. It is intended to be +// used as a backup for reset() when that function cannot be used due to +// failures in initializing libpod. +// It does not expect that all the directories match what is in use by Podman, +// as this is a common failure point for `system reset`. As such, our ability to +// interface with containers and pods is somewhat limited. +// This function assumes that we do not have a working c/storage store. +func (r *Runtime) removeAllDirs() error { + var lastErr error + + // Grab the runtime alive lock. + // This ensures that no other Podman process can run while we are doing + // a reset, so no race conditions with containers/pods/etc being created + // while we are resetting storage. + // TODO: maybe want a helper for getting the path? This is duped from + // runtime.go + runtimeAliveLock := filepath.Join(r.config.Engine.TmpDir, "alive.lck") + aliveLock, err := lockfile.GetLockFile(runtimeAliveLock) + if err != nil { + logrus.Errorf("Lock runtime alive lock %s: %v", runtimeAliveLock, err) + } else { + aliveLock.Lock() + defer aliveLock.Unlock() + } + + // We do not have a store - so we can't really try and remove containers + // or pods or volumes... + // Try and remove the directories, in hopes that they are unmounted. + // This is likely to fail but it's the best we can do. + + // Volume path + if err := os.RemoveAll(r.config.Engine.VolumePath); err != nil { + lastErr = fmt.Errorf("removing volume path: %w", err) + } + + // Tmpdir + if err := os.RemoveAll(r.config.Engine.TmpDir); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = fmt.Errorf("removing tmp dir: %w", err) + } + + // Runroot + if err := os.RemoveAll(r.storageConfig.RunRoot); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = fmt.Errorf("removing run root: %w", err) + } + + // Static dir + if err := os.RemoveAll(r.config.Engine.StaticDir); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = fmt.Errorf("removing static dir: %w", err) + } + + // Graph root + if err := os.RemoveAll(r.storageConfig.GraphRoot); err != nil { + if lastErr != nil { + logrus.Errorf("Reset: %v", lastErr) + } + lastErr = fmt.Errorf("removing graph root: %w", err) + } + + return lastErr +} + +// Reset removes all Libpod files. +// All containers, images, volumes, pods, and networks will be removed. +// Calls Shutdown(), rendering the runtime unusable after this is run. +func (r *Runtime) Reset(ctx context.Context) error { + // Acquire the alive lock and hold it. + // Ensures that we don't let other Podman commands run while we are + // removing everything. + aliveLock, err := r.getRuntimeAliveLock() + if err != nil { + return fmt.Errorf("retrieving alive lock: %w", err) + } + aliveLock.Lock() + defer aliveLock.Unlock() + + if !r.valid { + return define.ErrRuntimeStopped + } + + var timeout uint = 0 + pods, err := r.GetAllPods() + if err != nil { + return err + } + for _, p := range pods { + if ctrs, err := r.RemovePod(ctx, p, true, true, &timeout); err != nil { + if errors.Is(err, define.ErrNoSuchPod) { + continue + } + for ctr, err := range ctrs { + if err != nil { + logrus.Errorf("Error removing pod %s container %s: %v", p.ID(), ctr, err) + } + } + logrus.Errorf("Removing Pod %s: %v", p.ID(), err) + } + } + + ctrs, err := r.GetAllContainers() + if err != nil { + return err + } + + for _, c := range ctrs { + if ctrs, _, err := r.RemoveContainerAndDependencies(ctx, c, true, true, &timeout); err != nil { + for ctr, err := range ctrs { + logrus.Errorf("Error removing container %s: %v", ctr, err) + } + + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + continue + } + + logrus.Errorf("Removing container %s: %v", c.ID(), err) + + // There's no point trying a storage container removal + // here. High likelihood it doesn't work + // (RemoveStorageContainer will refuse to remove if a + // container exists in the Libpod database) and, even if + // it would, we'll get the container later on during + // image removal. + } + } + + if err := r.stopPauseProcess(); err != nil { + logrus.Errorf("Stopping pause process: %v", err) + } + + // Volumes before images, as volumes can mount images. + volumes, err := r.state.AllVolumes() + if err != nil { + return err + } + for _, v := range volumes { + if err := r.RemoveVolume(ctx, v, true, &timeout); err != nil { + if errors.Is(err, define.ErrNoSuchVolume) { + continue + } + logrus.Errorf("Removing volume %s: %v", v.config.Name, err) + } + } + + // Set force and ignore. + // Ignore shouldn't be necessary, but it seems safer. We want everything + // gone anyways... + rmiOptions := &libimage.RemoveImagesOptions{ + Force: true, + Ignore: true, + RemoveContainerFunc: r.RemoveContainersForImageCallback(ctx, true), + Filters: []string{"readonly=false"}, + } + if _, rmiErrors := r.LibimageRuntime().RemoveImages(ctx, nil, rmiOptions); rmiErrors != nil { + return errorhandling.JoinErrors(rmiErrors) + } + + // remove all networks + nets, err := r.network.NetworkList() + if err != nil { + return err + } + for _, net := range nets { + // do not delete the default network + if net.Name == r.network.DefaultNetworkName() { + continue + } + // ignore not exists errors because of the TOCTOU problem + if err := r.network.NetworkRemove(net.Name); err != nil && !errors.Is(err, types.ErrNoSuchNetwork) { + logrus.Errorf("Removing network %s: %v", net.Name, err) + } + } + + xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR") + if xdgRuntimeDir != "" { + xdgRuntimeDir, err = filepath.EvalSymlinks(xdgRuntimeDir) + if err != nil { + return err + } + } + _, prevError := r.store.Shutdown(true) + graphRoot := filepath.Clean(r.store.GraphRoot()) + if graphRoot == xdgRuntimeDir { + if prevError != nil { + logrus.Error(prevError) + } + prevError = fmt.Errorf("failed to remove runtime graph root dir %s, since it is the same as XDG_RUNTIME_DIR", graphRoot) + } else { + if err := os.RemoveAll(graphRoot); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + } + runRoot := filepath.Clean(r.store.RunRoot()) + if runRoot == xdgRuntimeDir { + if prevError != nil { + logrus.Error(prevError) + } + prevError = fmt.Errorf("failed to remove runtime root dir %s, since it is the same as XDG_RUNTIME_DIR", runRoot) + } else { + if err := os.RemoveAll(runRoot); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + } + runtimeDir, err := util.GetRootlessRuntimeDir() + if err != nil { + return err + } + tempDir := r.config.Engine.TmpDir + if tempDir == runtimeDir { + tempDir = filepath.Join(tempDir, "containers") + } + if filepath.Clean(tempDir) == xdgRuntimeDir { + if prevError != nil { + logrus.Error(prevError) + } + prevError = fmt.Errorf("failed to remove runtime tmpdir %s, since it is the same as XDG_RUNTIME_DIR", tempDir) + } else { + if err := os.RemoveAll(tempDir); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + } + + if err := blobinfocache.CleanupDefaultCache(nil); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + + if storageConfPath, err := storage.DefaultConfigFile(); err == nil { + switch storageConfPath { + case stypes.SystemConfigFile: + break + default: + if _, err = os.Stat(storageConfPath); err == nil { + fmt.Printf(" A %q config file exists.\n", storageConfPath) + fmt.Println("Remove this file if you did not modify the configuration.") + } + } + } else { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + + // Shut down the runtime, it's no longer usable after mass-deletion. + if err := r.Shutdown(false); err != nil { + if prevError != nil { + logrus.Error(prevError) + } + prevError = err + } + + return prevError +} diff --git a/vendor/github.com/containers/podman/v5/libpod/rlimit_int64.go b/vendor/github.com/containers/podman/v5/libpod/rlimit_int64.go new file mode 100644 index 00000000..ba651d07 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/rlimit_int64.go @@ -0,0 +1,5 @@ +//go:build !remote && freebsd + +package libpod + +type rlimT int64 diff --git a/vendor/github.com/containers/podman/v5/libpod/rlimit_uint64.go b/vendor/github.com/containers/podman/v5/libpod/rlimit_uint64.go new file mode 100644 index 00000000..585cec21 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/rlimit_uint64.go @@ -0,0 +1,5 @@ +//go:build !remote && linux + +package libpod + +type rlimT uint64 diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime.go b/vendor/github.com/containers/podman/v5/libpod/runtime.go new file mode 100644 index 00000000..7e83fffc --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime.go @@ -0,0 +1,1448 @@ +//go:build !remote + +package libpod + +import ( + "bufio" + "context" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "slices" + "strings" + "sync" + "syscall" + "time" + + "github.com/containers/buildah/pkg/parse" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/libpod/lock" + "github.com/containers/podman/v5/libpod/plugin" + "github.com/containers/podman/v5/libpod/shutdown" + "github.com/containers/podman/v5/pkg/domain/entities" + "github.com/containers/podman/v5/pkg/domain/entities/reports" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/systemd" + "github.com/containers/podman/v5/pkg/util" + "github.com/docker/docker/pkg/namesgenerator" + "github.com/hashicorp/go-multierror" + jsoniter "github.com/json-iterator/go" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + "go.podman.io/common/libnetwork/network" + nettypes "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/common/pkg/config" + artStore "go.podman.io/common/pkg/libartifact/store" + "go.podman.io/common/pkg/secrets" + systemdCommon "go.podman.io/common/pkg/systemd" + "go.podman.io/image/v5/pkg/sysregistriesv2" + is "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/lockfile" + "go.podman.io/storage/pkg/unshare" +) + +// Set up the JSON library for all of Libpod +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// A RuntimeOption is a functional option which alters the Runtime created by +// NewRuntime +type RuntimeOption func(*Runtime) error + +type storageSet struct { + RunRootSet bool + GraphRootSet bool + StaticDirSet bool + VolumePathSet bool + GraphDriverNameSet bool + TmpDirSet bool +} + +// Runtime is the core libpod runtime +type Runtime struct { + config *config.Config + storageConfig storage.StoreOptions + storageSet storageSet + + state State + store storage.Store + storageService *storageService + imageContext *types.SystemContext + defaultOCIRuntime OCIRuntime + ociRuntimes map[string]OCIRuntime + runtimeFlags []string + network nettypes.ContainerNetwork + conmonPath string + libimageRuntime *libimage.Runtime + libimageEventsShutdown chan bool + lockManager lock.Manager + + // ArtifactStore returns the artifact store created from the runtime. + ArtifactStore func() (*artStore.ArtifactStore, error) + + // Worker + workerChannel chan func() + workerGroup sync.WaitGroup + + // syslog describes whenever logrus should log to the syslog as well. + // Note that the syslog hook will be enabled early in cmd/podman/syslog_linux.go + // This bool is just needed so that we can set it for netavark interface. + syslog bool + + // doReset indicates that the runtime will perform a system reset. + // A reset will remove all containers, pods, volumes, networks, etc. + // A number of validation checks are relaxed, or replaced with logic to + // remove as much of the runtime as possible if they fail. This ensures + // that even a broken Libpod can still be removed via `system reset`. + // This does not actually perform a `system reset`. That is done by + // calling "Reset()" on the returned runtime. + doReset bool + // doRenumber indicates that the runtime will perform a system renumber. + // A renumber will reassign lock numbers for all containers, pods, etc. + // This will not perform the renumber itself, but will ignore some + // errors related to lock initialization so a renumber can be performed + // if something has gone wrong. + doRenumber bool + + // valid indicates whether the runtime is ready to use. + // valid is set to true when a runtime is returned from GetRuntime(), + // and remains true until the runtime is shut down (rendering its + // storage unusable). When valid is false, the runtime cannot be used. + valid bool + + // mechanism to read and write even logs + eventer events.Eventer + + // secretsManager manages secrets + secretsManager *secrets.SecretsManager +} + +// SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set. +// containers/image uses XDG_RUNTIME_DIR to locate the auth file, XDG_CONFIG_HOME is +// use for the containers.conf configuration file. +func SetXdgDirs() error { + if !rootless.IsRootless() { + return nil + } + + // Set up XDG_RUNTIME_DIR + runtimeDir := os.Getenv("XDG_RUNTIME_DIR") + + if runtimeDir == "" { + var err error + runtimeDir, err = util.GetRootlessRuntimeDir() + if err != nil { + return err + } + } + if err := os.Setenv("XDG_RUNTIME_DIR", runtimeDir); err != nil { + return fmt.Errorf("cannot set XDG_RUNTIME_DIR: %w", err) + } + + if rootless.IsRootless() && os.Getenv("DBUS_SESSION_BUS_ADDRESS") == "" { + sessionAddr := filepath.Join(runtimeDir, "bus") + if err := fileutils.Exists(sessionAddr); err == nil { + os.Setenv("DBUS_SESSION_BUS_ADDRESS", fmt.Sprintf("unix:path=%s", sessionAddr)) + } + } + + // Set up XDG_CONFIG_HOME + if cfgHomeDir := os.Getenv("XDG_CONFIG_HOME"); cfgHomeDir == "" { + cfgHomeDir, err := util.GetRootlessConfigHomeDir() + if err != nil { + return err + } + if err := os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil { + return fmt.Errorf("cannot set XDG_CONFIG_HOME: %w", err) + } + } + return nil +} + +// NewRuntime creates a new container runtime +// Options can be passed to override the default configuration for the runtime +func NewRuntime(ctx context.Context, options ...RuntimeOption) (*Runtime, error) { + conf, err := config.Default() + if err != nil { + return nil, err + } + return newRuntimeFromConfig(ctx, conf, options...) +} + +func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...RuntimeOption) (*Runtime, error) { + runtime := new(Runtime) + + if conf.Engine.OCIRuntime == "" { + conf.Engine.OCIRuntime = "runc" + // If we're running on cgroups v2, default to using crun. + if onCgroupsv2, _ := cgroups.IsCgroup2UnifiedMode(); onCgroupsv2 { + conf.Engine.OCIRuntime = "crun" + } + } + + runtime.config = conf + + if err := SetXdgDirs(); err != nil { + return nil, err + } + + storeOpts, err := storage.DefaultStoreOptions() + if err != nil { + return nil, err + } + runtime.storageConfig = storeOpts + + // Overwrite config with user-given configuration options + for _, opt := range options { + if err := opt(runtime); err != nil { + return nil, fmt.Errorf("configuring runtime: %w", err) + } + } + + if err := makeRuntime(ctx, runtime); err != nil { + return nil, err + } + + if err := shutdown.Register("libpod", func(_ os.Signal) error { + if runtime.store != nil { + _, _ = runtime.store.Shutdown(false) + } + return nil + }); err != nil && !errors.Is(err, shutdown.ErrHandlerExists) { + logrus.Errorf("Registering shutdown handler for libpod: %v", err) + } + + if err := shutdown.Start(); err != nil { + return nil, fmt.Errorf("starting shutdown signal handler: %w", err) + } + + runtime.config.CheckCgroupsAndAdjustConfig() + + return runtime, nil +} + +func getLockManager(runtime *Runtime) (lock.Manager, error) { + var err error + var manager lock.Manager + + switch runtime.config.Engine.LockType { + case "file": + lockPath := filepath.Join(runtime.config.Engine.TmpDir, "locks") + manager, err = lock.OpenFileLockManager(lockPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + manager, err = lock.NewFileLockManager(lockPath) + if err != nil { + return nil, fmt.Errorf("failed to get new file lock manager: %w", err) + } + } else { + return nil, err + } + } + + case "", "shm": + lockPath := define.DefaultSHMLockPath + if rootless.IsRootless() { + lockPath = fmt.Sprintf("%s_%d", define.DefaultRootlessSHMLockPath, rootless.GetRootlessUID()) + } + // Set up the lock manager + manager, err = lock.OpenSHMLockManager(lockPath, runtime.config.Engine.NumLocks) + if err != nil { + switch { + case errors.Is(err, os.ErrNotExist): + manager, err = lock.NewSHMLockManager(lockPath, runtime.config.Engine.NumLocks) + if err != nil { + return nil, fmt.Errorf("failed to get new shm lock manager: %w", err) + } + case errors.Is(err, syscall.ERANGE) && runtime.doRenumber: + logrus.Debugf("Number of locks does not match - removing old locks") + + // ERANGE indicates a lock numbering mismatch. + // Since we're renumbering, this is not fatal. + // Remove the earlier set of locks and recreate. + if err := os.Remove(filepath.Join("/dev/shm", lockPath)); err != nil { + return nil, fmt.Errorf("removing libpod locks file %s: %w", lockPath, err) + } + + manager, err = lock.NewSHMLockManager(lockPath, runtime.config.Engine.NumLocks) + if err != nil { + return nil, err + } + default: + return nil, err + } + } + default: + return nil, fmt.Errorf("unknown lock type %s: %w", runtime.config.Engine.LockType, define.ErrInvalidArg) + } + return manager, nil +} + +func getDBState(runtime *Runtime) (State, error) { + // TODO - if we further break out the state implementation into + // libpod/state, the config could take care of the code below. It + // would further allow to move the types and consts into a coherent + // package. + backend, err := config.ParseDBBackend(runtime.config.Engine.DBBackend) + if err != nil { + return nil, err + } + + // get default boltdb path + baseDir := runtime.config.Engine.StaticDir + if runtime.storageConfig.TransientStore { + baseDir = runtime.config.Engine.TmpDir + } + boltDBPath := filepath.Join(baseDir, "bolt_state.db") + + switch backend { + case config.DBBackendDefault: + // for backwards compatibility check if boltdb exists, if it does not we use sqlite + if err := fileutils.Exists(boltDBPath); err != nil { + if errors.Is(err, fs.ErrNotExist) { + // need to set DBBackend string so podman info will show the backend name correctly + runtime.config.Engine.DBBackend = config.DBBackendSQLite.String() + return NewSqliteState(runtime) + } + // Return error here some other problem with the boltdb file, rather than silently + // switch to sqlite which would be hard to debug for the user return the error back + // as this likely a real bug. + return nil, err + } + runtime.config.Engine.DBBackend = config.DBBackendBoltDB.String() + fallthrough + case config.DBBackendBoltDB: + return NewBoltState(boltDBPath, runtime) + case config.DBBackendSQLite: + return NewSqliteState(runtime) + default: + return nil, fmt.Errorf("unrecognized database backend passed (%q): %w", backend.String(), define.ErrInvalidArg) + } +} + +// Make a new runtime based on the given configuration +// Sets up containers/storage, state store, OCI runtime +func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { + // Find a working conmon binary + cPath, err := runtime.config.FindConmon() + if err != nil { + return err + } + runtime.conmonPath = cPath + + if runtime.config.Engine.StaticDir == "" { + runtime.config.Engine.StaticDir = filepath.Join(runtime.storageConfig.GraphRoot, "libpod") + runtime.storageSet.StaticDirSet = true + } + + if runtime.config.Engine.VolumePath == "" { + runtime.config.Engine.VolumePath = filepath.Join(runtime.storageConfig.GraphRoot, "volumes") + runtime.storageSet.VolumePathSet = true + } + + // Make the static files directory if it does not exist + if err := os.MkdirAll(runtime.config.Engine.StaticDir, 0o700); err != nil { + // The directory is allowed to exist + if !errors.Is(err, os.ErrExist) { + return fmt.Errorf("creating runtime static files directory %q: %w", runtime.config.Engine.StaticDir, err) + } + } + + // Create the TmpDir if needed + if err := os.MkdirAll(runtime.config.Engine.TmpDir, 0o751); err != nil { + return fmt.Errorf("creating runtime temporary files directory: %w", err) + } + + // Create the volume path if needed. + // This is not strictly necessary at this point, but the path not + // existing can cause troubles with DB path validation on OSTree based + // systems. Ref: https://github.com/containers/podman/issues/23515 + if err := os.MkdirAll(runtime.config.Engine.VolumePath, 0o700); err != nil { + return fmt.Errorf("creating runtime volume path directory: %w", err) + } + + // Set up the state. + runtime.state, err = getDBState(runtime) + if err != nil { + return err + } + + // Grab config from the database so we can reset some defaults + dbConfig, err := runtime.state.GetDBConfig() + if err != nil { + if runtime.doReset { + // We can at least delete the DB and the static files + // directory. + // Can't safely touch anything else because we aren't + // sure of other directories. + if err := runtime.state.Close(); err != nil { + logrus.Errorf("Closing database connection: %v", err) + } else { + if err := os.RemoveAll(runtime.config.Engine.StaticDir); err != nil { + logrus.Errorf("Removing static files directory %v: %v", runtime.config.Engine.StaticDir, err) + } + } + } + + return fmt.Errorf("retrieving runtime configuration from database: %w", err) + } + + runtime.mergeDBConfig(dbConfig) + + checkCgroups2UnifiedMode(runtime) + + logrus.Debugf("Using graph driver %s", runtime.storageConfig.GraphDriverName) + logrus.Debugf("Using graph root %s", runtime.storageConfig.GraphRoot) + logrus.Debugf("Using run root %s", runtime.storageConfig.RunRoot) + logrus.Debugf("Using static dir %s", runtime.config.Engine.StaticDir) + logrus.Debugf("Using tmp dir %s", runtime.config.Engine.TmpDir) + logrus.Debugf("Using volume path %s", runtime.config.Engine.VolumePath) + logrus.Debugf("Using transient store: %v", runtime.storageConfig.TransientStore) + + // Validate our config against the database, now that we've set our + // final storage configuration + if err := runtime.state.ValidateDBConfig(runtime); err != nil { + // If we are performing a storage reset: continue on with a + // warning. Otherwise we can't `system reset` after a change to + // the core paths. + if !runtime.doReset { + return err + } + logrus.Errorf("Runtime paths differ from those stored in database, storage reset may not remove all files") + } + + if runtime.config.Engine.Namespace != "" { + return fmt.Errorf("namespaces are not supported by this version of Libpod, please unset the `namespace` field in containers.conf: %w", define.ErrNotImplemented) + } + + needsUserns := os.Geteuid() != 0 + if !needsUserns { + hasCapSysAdmin, err := unshare.HasCapSysAdmin() + if err != nil { + return err + } + needsUserns = !hasCapSysAdmin + } + // Set up containers/storage + var store storage.Store + if needsUserns { + logrus.Debug("Not configuring container store") + } else if err := runtime.configureStore(); err != nil { + // Make a best-effort attempt to clean up if performing a + // storage reset. + if runtime.doReset { + if err := runtime.removeAllDirs(); err != nil { + logrus.Errorf("Removing libpod directories: %v", err) + } + } + + return fmt.Errorf("configure storage: %w", err) + } + defer func() { + if retErr != nil && store != nil { + // Don't forcibly shut down + // We could be opening a store in use by another libpod + if _, err := store.Shutdown(false); err != nil { + logrus.Errorf("Removing store for partially-created runtime: %s", err) + } + } + }() + + // Set up the eventer + eventer, err := runtime.newEventer() + if err != nil { + return err + } + runtime.eventer = eventer + + // Set up containers/image + if runtime.imageContext == nil { + runtime.imageContext = &types.SystemContext{ + BigFilesTemporaryDir: parse.GetTempDir(), + } + } + runtime.imageContext.SignaturePolicyPath = runtime.config.Engine.SignaturePolicyPath + + // Get us at least one working OCI runtime. + runtime.ociRuntimes = make(map[string]OCIRuntime) + + // Initialize remaining OCI runtimes + for name, paths := range runtime.config.Engine.OCIRuntimes { + ociRuntime, err := newConmonOCIRuntime(name, paths, runtime.conmonPath, runtime.runtimeFlags, runtime.config) + if err != nil { + // Don't fatally error. + // This will allow us to ship configs including optional + // runtimes that might not be installed (crun, kata). + // Only an infof so default configs don't spec errors. + logrus.Debugf("Configured OCI runtime %s initialization failed: %v", name, err) + continue + } + + runtime.ociRuntimes[name] = ociRuntime + } + + // Do we have a default OCI runtime? + if runtime.config.Engine.OCIRuntime != "" { + // If the string starts with / it's a path to a runtime + // executable. + if strings.HasPrefix(runtime.config.Engine.OCIRuntime, "/") { + ociRuntime, err := newConmonOCIRuntime(runtime.config.Engine.OCIRuntime, []string{runtime.config.Engine.OCIRuntime}, runtime.conmonPath, runtime.runtimeFlags, runtime.config) + if err != nil { + return err + } + + runtime.ociRuntimes[runtime.config.Engine.OCIRuntime] = ociRuntime + runtime.defaultOCIRuntime = ociRuntime + } else { + ociRuntime, ok := runtime.ociRuntimes[runtime.config.Engine.OCIRuntime] + if !ok { + return fmt.Errorf("default OCI runtime %q not found: %w", runtime.config.Engine.OCIRuntime, define.ErrInvalidArg) + } + runtime.defaultOCIRuntime = ociRuntime + } + } + logrus.Debugf("Using OCI runtime %q", runtime.defaultOCIRuntime.Path()) + + // Do we have at least one valid OCI runtime? + if len(runtime.ociRuntimes) == 0 { + return fmt.Errorf("no OCI runtime has been configured: %w", define.ErrInvalidArg) + } + + // Do we have a default runtime? + if runtime.defaultOCIRuntime == nil { + return fmt.Errorf("no default OCI runtime was configured: %w", define.ErrInvalidArg) + } + + // the store is only set up when we are in the userns so we do the same for the network interface + if !needsUserns { + netBackend, netInterface, err := network.NetworkBackend(runtime.store, runtime.config, runtime.syslog) + if err != nil { + return err + } + runtime.config.Network.NetworkBackend = string(netBackend) + runtime.network = netInterface + + // Using sync once value to only init the store exactly once and only when it will be actually be used. + runtime.ArtifactStore = sync.OnceValues(func() (*artStore.ArtifactStore, error) { + return artStore.NewArtifactStore(filepath.Join(runtime.storageConfig.GraphRoot, "artifacts"), runtime.SystemContext()) + }) + } + + // We now need to see if the system has restarted + // We check for the presence of a file in our tmp directory to verify this + // This check must be locked to prevent races + runtimeAliveFile := filepath.Join(runtime.config.Engine.TmpDir, "alive") + aliveLock, err := runtime.getRuntimeAliveLock() + if err != nil { + return fmt.Errorf("acquiring runtime init lock: %w", err) + } + // Acquire the lock and hold it until we return + // This ensures that no two processes will be in runtime.refresh at once + aliveLock.Lock() + doRefresh := false + unLockFunc := aliveLock.Unlock + defer func() { + if unLockFunc != nil { + unLockFunc() + } + }() + + err = fileutils.Exists(runtimeAliveFile) + if err != nil { + // If we need to refresh, then it is safe to assume there are + // no containers running. Create immediately a namespace, as + // we will need to access the storage. + if needsUserns { + // warn users if mode is rootless and cgroup manager is systemd + // and no valid systemd session is present + // warn only whenever new namespace is created + if runtime.config.Engine.CgroupManager == config.SystemdCgroupsManager { + unified, _ := cgroups.IsCgroup2UnifiedMode() + if unified && rootless.IsRootless() && !systemd.IsSystemdSessionValid(rootless.GetRootlessUID()) { + logrus.Debug("Invalid systemd user session for current user") + } + } + unLockFunc() + unLockFunc = nil + pausePid, err := util.GetRootlessPauseProcessPidPath() + if err != nil { + return fmt.Errorf("could not get pause process pid file path: %w", err) + } + + // create the path in case it does not already exists + // https://github.com/containers/podman/issues/8539 + if err := os.MkdirAll(filepath.Dir(pausePid), 0o700); err != nil { + return fmt.Errorf("could not create pause process pid file directory: %w", err) + } + + became, ret, err := rootless.BecomeRootInUserNS(pausePid) + if err != nil { + return err + } + if became { + // Check if the pause process was created. If it was created, then + // move it to its own systemd scope. + systemdCommon.MovePauseProcessToScope(pausePid) + + // gocritic complains because defer is not run on os.Exit() + // However this is fine because the lock is released anyway when the process exits + //nolint:gocritic + os.Exit(ret) + } + } + // If the file doesn't exist, we need to refresh the state + // This will trigger on first use as well, but refreshing an + // empty state only creates a single file + // As such, it's not really a performance concern + if errors.Is(err, os.ErrNotExist) { + doRefresh = true + } else { + return fmt.Errorf("reading runtime status file %s: %w", runtimeAliveFile, err) + } + } + + runtime.lockManager, err = getLockManager(runtime) + if err != nil { + return err + } + + // Mark the runtime as valid - ready to be used, cannot be modified + // further. + // Need to do this *before* refresh as we can remove containers there. + // Should not be a big deal as we don't return it to users until after + // refresh runs. + runtime.valid = true + + // Setup the worker channel early to start accepting jobs from refresh, + // but do not start to execute the jobs right away. The runtime is not + // ready at this point. + runtime.setupWorkerQueue() + + // If we need to refresh the state, do it now - things are guaranteed to + // be set up by now. + if doRefresh { + // Ensure we have a store before refresh occurs + if runtime.store == nil { + if err := runtime.configureStore(); err != nil { + return fmt.Errorf("configure storage: %w", err) + } + } + + if err2 := runtime.refresh(ctx, runtimeAliveFile); err2 != nil { + return err2 + } + } + + // Check current boot ID - will be written to the alive file. + if err := runtime.checkBootID(runtimeAliveFile); err != nil { + return err + } + + runtime.startWorker() + + return nil +} + +// TmpDir gets the current Libpod temporary files directory. +func (r *Runtime) TmpDir() (string, error) { + if !r.valid { + return "", define.ErrRuntimeStopped + } + + return r.config.Engine.TmpDir, nil +} + +// GetConfig returns the configuration used by the runtime. +// Note that the returned value is not a copy and must hence +// only be used in a reading fashion. +func (r *Runtime) GetConfigNoCopy() (*config.Config, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + return r.config, nil +} + +// GetConfig returns a copy of the configuration used by the runtime. +// Please use GetConfigNoCopy() in case you only want to read from +// but not write to the returned config. +func (r *Runtime) GetConfig() (*config.Config, error) { + rtConfig, err := r.GetConfigNoCopy() + if err != nil { + return nil, err + } + + config := new(config.Config) + + // Copy so the caller won't be able to modify the actual config + if err := JSONDeepCopy(rtConfig, config); err != nil { + return nil, fmt.Errorf("copying config: %w", err) + } + + return config, nil +} + +// libimageEventsMap translates a libimage event type to a libpod event status. +var libimageEventsMap = map[libimage.EventType]events.Status{ + libimage.EventTypeImagePull: events.Pull, + libimage.EventTypeImagePullError: events.PullError, + libimage.EventTypeImagePush: events.Push, + libimage.EventTypeImageRemove: events.Remove, + libimage.EventTypeImageLoad: events.LoadFromArchive, + libimage.EventTypeImageSave: events.Save, + libimage.EventTypeImageTag: events.Tag, + libimage.EventTypeImageUntag: events.Untag, + libimage.EventTypeImageMount: events.Mount, + libimage.EventTypeImageUnmount: events.Unmount, +} + +// libimageEvents spawns a goroutine which will listen for events on +// the libimage.Runtime. The goroutine will be cleaned up implicitly +// when the main() exists. +func (r *Runtime) libimageEvents() { + r.libimageEventsShutdown = make(chan bool) + + toLibpodEventStatus := func(e *libimage.Event) events.Status { + status, found := libimageEventsMap[e.Type] + if !found { + return "Unknown" + } + return status + } + + eventChannel := r.libimageRuntime.EventChannel() + go func() { + sawShutdown := false + for { + // Make sure to read and write all events before + // shutting down. + for len(eventChannel) > 0 { + libimageEvent := <-eventChannel + e := events.Event{ + ID: libimageEvent.ID, + Name: libimageEvent.Name, + Status: toLibpodEventStatus(libimageEvent), + Time: libimageEvent.Time, + Type: events.Image, + } + if libimageEvent.Error != nil { + e.Error = libimageEvent.Error.Error() + } + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write image event: %q", err) + } + } + + if sawShutdown { + close(r.libimageEventsShutdown) + return + } + + select { + case <-r.libimageEventsShutdown: + sawShutdown = true + case <-time.After(100 * time.Millisecond): + } + } + }() +} + +// DeferredShutdown shuts down the runtime without exposing any +// errors. This is only meant to be used when the runtime is being +// shutdown within a defer statement; else use Shutdown +func (r *Runtime) DeferredShutdown(force bool) { + _ = r.Shutdown(force) +} + +// Shutdown shuts down the runtime and associated containers and storage +// If force is true, containers and mounted storage will be shut down before +// cleaning up; if force is false, an error will be returned if there are +// still containers running or mounted +func (r *Runtime) Shutdown(force bool) error { + if !r.valid { + return nil + } + + if r.workerChannel != nil { + r.workerGroup.Wait() + close(r.workerChannel) + } + + r.valid = false + + // Shutdown all containers if --force is given + if force { + ctrs, err := r.state.AllContainers(false) + if err != nil { + logrus.Errorf("Retrieving containers from database: %v", err) + } else { + for _, ctr := range ctrs { + if err := ctr.StopWithTimeout(r.config.Engine.StopTimeout); err != nil { + logrus.Errorf("Stopping container %s: %v", ctr.ID(), err) + } + } + } + } + + var lastError error + // If no store was requested, it can be nil and there is no need to + // attempt to shut it down + if r.store != nil { + // Wait for the events to be written. + if r.libimageEventsShutdown != nil { + // Tell loop to shutdown + r.libimageEventsShutdown <- true + // Wait for close to signal shutdown + <-r.libimageEventsShutdown + } + + // Note that the libimage runtime shuts down the store. + if err := r.libimageRuntime.Shutdown(force); err != nil { + lastError = fmt.Errorf("shutting down container storage: %w", err) + } + } + if err := r.state.Close(); err != nil { + if lastError != nil { + logrus.Error(lastError) + } + lastError = err + } + + return lastError +} + +// Reconfigures the runtime after a reboot +// Refreshes the state, recreating temporary files +// Does not check validity as the runtime is not valid until after this has run +func (r *Runtime) refresh(ctx context.Context, alivePath string) error { + logrus.Debugf("Podman detected system restart - performing state refresh") + + // Clear state of database if not running in container + if !graphRootMounted() { + // First clear the state in the database + if err := r.state.Refresh(); err != nil { + return err + } + } + + // Next refresh the state of all containers to recreate dirs and + // namespaces, and all the pods to recreate cgroups. + // Containers, pods, and volumes must also reacquire their locks. + ctrs, err := r.state.AllContainers(false) + if err != nil { + return fmt.Errorf("retrieving all containers from state: %w", err) + } + pods, err := r.state.AllPods() + if err != nil { + return fmt.Errorf("retrieving all pods from state: %w", err) + } + vols, err := r.state.AllVolumes() + if err != nil { + return fmt.Errorf("retrieving all volumes from state: %w", err) + } + // No locks are taken during pod, volume, and container refresh. + // Furthermore, the pod/volume/container refresh() functions are not + // allowed to take locks themselves. + // We cannot assume that any pod/volume/container has a valid lock until + // after this function has returned. + // The runtime alive lock should suffice to provide mutual exclusion + // until this has run. + for _, ctr := range ctrs { + if err := ctr.refresh(); err != nil { + logrus.Errorf("Refreshing container %s: %v", ctr.ID(), err) + } + // This is the only place it's safe to use ctr.state.State unlocked + // We're holding the alive lock, guaranteed to be the only Libpod on the system right now. + if (ctr.AutoRemove() && ctr.state.State == define.ContainerStateExited) || ctr.state.State == define.ContainerStateRemoving { + opts := ctrRmOpts{ + // Don't force-remove, we're supposed to be fresh off a reboot + // If we have to force something is seriously wrong + Force: false, + RemoveVolume: true, + } + // This container should have autoremoved before the + // reboot but did not. + // Get rid of it. + if _, _, err := r.removeContainer(ctx, ctr, opts); err != nil { + logrus.Errorf("Unable to remove container %s which should have autoremoved: %v", ctr.ID(), err) + } + } + } + for _, pod := range pods { + if err := pod.refresh(); err != nil { + logrus.Errorf("Refreshing pod %s: %v", pod.ID(), err) + } + } + for _, vol := range vols { + if err := vol.refresh(); err != nil { + logrus.Errorf("Refreshing volume %s: %v", vol.Name(), err) + } + } + + // Create a file indicating the runtime is alive and ready + file, err := os.OpenFile(alivePath, os.O_RDONLY|os.O_CREATE, 0o644) + if err != nil { + return fmt.Errorf("creating runtime status file: %w", err) + } + defer file.Close() + + r.NewSystemEvent(events.Refresh) + + return nil +} + +// Info returns the store and host information +func (r *Runtime) Info() (*define.Info, error) { + return r.info() +} + +// generateName generates a unique name for a container or pod. +func (r *Runtime) generateName() (string, error) { + for { + name := namesgenerator.GetRandomName(0) + // Make sure container with this name does not exist + if _, err := r.state.LookupContainer(name); err == nil { + continue + } else if !errors.Is(err, define.ErrNoSuchCtr) { + return "", err + } + // Make sure pod with this name does not exist + if _, err := r.state.LookupPod(name); err == nil { + continue + } else if !errors.Is(err, define.ErrNoSuchPod) { + return "", err + } + return name, nil + } + // The code should never reach here. +} + +// Configure store and image runtime +func (r *Runtime) configureStore() error { + store, err := storage.GetStore(r.storageConfig) + if err != nil { + return err + } + + r.store = store + is.Transport.SetStore(store) + + // Set up a storage service for creating container root filesystems from + // images + r.storageService = getStorageService(r.store) + + runtimeOptions := &libimage.RuntimeOptions{ + SystemContext: r.imageContext, + } + libimageRuntime, err := libimage.RuntimeFromStore(store, runtimeOptions) + if err != nil { + return err + } + r.libimageRuntime = libimageRuntime + // Run the libimage events routine. + r.libimageEvents() + + return nil +} + +// LibimageRuntime ... to allow for a step-by-step migration to libimage. +func (r *Runtime) LibimageRuntime() *libimage.Runtime { + return r.libimageRuntime +} + +// SystemContext returns the imagecontext +func (r *Runtime) SystemContext() *types.SystemContext { + // Return the context from the libimage runtime. libimage is sensitive + // to a number of env vars. + return r.libimageRuntime.SystemContext() +} + +// GetOCIRuntimePath retrieves the path of the default OCI runtime. +func (r *Runtime) GetOCIRuntimePath() string { + return r.defaultOCIRuntime.Path() +} + +// DefaultOCIRuntime return copy of Default OCI Runtime +func (r *Runtime) DefaultOCIRuntime() OCIRuntime { + return r.defaultOCIRuntime +} + +// StorageConfig retrieves the storage options for the container runtime +func (r *Runtime) StorageConfig() storage.StoreOptions { + return r.storageConfig +} + +func (r *Runtime) GarbageCollect() error { + return r.store.GarbageCollect() +} + +// RunRoot retrieves the current c/storage temporary directory in use by Libpod. +func (r *Runtime) RunRoot() string { + if r.store == nil { + return "" + } + return r.store.RunRoot() +} + +// GraphRoot retrieves the current c/storage directory in use by Libpod. +func (r *Runtime) GraphRoot() string { + if r.store == nil { + return "" + } + return r.store.GraphRoot() +} + +// GetPodName retrieves the pod name associated with a given full ID. +// If the given ID does not correspond to any existing Pod or Container, +// ErrNoSuchPod is returned. +func (r *Runtime) GetPodName(id string) (string, error) { + if !r.valid { + return "", define.ErrRuntimeStopped + } + + return r.state.GetPodName(id) +} + +// DBConfig is a set of Libpod runtime configuration settings that are saved in +// a State when it is first created, and can subsequently be retrieved. +type DBConfig struct { + LibpodRoot string + LibpodTmp string + StorageRoot string + StorageTmp string + GraphDriver string + VolumePath string +} + +// mergeDBConfig merges the configuration from the database. +func (r *Runtime) mergeDBConfig(dbConfig *DBConfig) { + c := &r.config.Engine + if !r.storageSet.RunRootSet && dbConfig.StorageTmp != "" { + if r.storageConfig.RunRoot != dbConfig.StorageTmp && + r.storageConfig.RunRoot != "" { + logrus.Debugf("Overriding run root %q with %q from database", + r.storageConfig.RunRoot, dbConfig.StorageTmp) + } + r.storageConfig.RunRoot = dbConfig.StorageTmp + } + + if !r.storageSet.GraphRootSet && dbConfig.StorageRoot != "" { + if r.storageConfig.GraphRoot != dbConfig.StorageRoot && + r.storageConfig.GraphRoot != "" { + logrus.Debugf("Overriding graph root %q with %q from database", + r.storageConfig.GraphRoot, dbConfig.StorageRoot) + } + r.storageConfig.GraphRoot = dbConfig.StorageRoot + } + + if !r.storageSet.GraphDriverNameSet && dbConfig.GraphDriver != "" { + if r.storageConfig.GraphDriverName != dbConfig.GraphDriver && + r.storageConfig.GraphDriverName != "" { + logrus.Errorf("User-selected graph driver %q overwritten by graph driver %q from database - delete libpod local files (%q) to resolve. May prevent use of images created by other tools", + r.storageConfig.GraphDriverName, dbConfig.GraphDriver, r.storageConfig.GraphRoot) + } + r.storageConfig.GraphDriverName = dbConfig.GraphDriver + } + + if !r.storageSet.StaticDirSet && dbConfig.LibpodRoot != "" { + if c.StaticDir != dbConfig.LibpodRoot && c.StaticDir != "" { + logrus.Debugf("Overriding static dir %q with %q from database", c.StaticDir, dbConfig.LibpodRoot) + } + c.StaticDir = dbConfig.LibpodRoot + } + + if !r.storageSet.TmpDirSet && dbConfig.LibpodTmp != "" { + if c.TmpDir != dbConfig.LibpodTmp && c.TmpDir != "" { + logrus.Debugf("Overriding tmp dir %q with %q from database", c.TmpDir, dbConfig.LibpodTmp) + } + c.TmpDir = dbConfig.LibpodTmp + } + + if !r.storageSet.VolumePathSet && dbConfig.VolumePath != "" { + if c.VolumePath != dbConfig.VolumePath && c.VolumePath != "" { + logrus.Debugf("Overriding volume path %q with %q from database", c.VolumePath, dbConfig.VolumePath) + } + c.VolumePath = dbConfig.VolumePath + } +} + +func (r *Runtime) EnableLabeling() bool { + return r.config.Containers.EnableLabeling +} + +// Reload reloads the configurations files +func (r *Runtime) Reload() error { + if err := r.reloadContainersConf(); err != nil { + return err + } + if err := r.reloadStorageConf(); err != nil { + return err + } + // Invalidate the registries.conf cache. The next invocation will + // reload all data. + sysregistriesv2.InvalidateCache() + return nil +} + +// reloadContainersConf reloads the containers.conf +func (r *Runtime) reloadContainersConf() error { + config, err := config.Reload() + if err != nil { + return err + } + r.config = config + logrus.Infof("Applied new containers configuration: %v", config) + return nil +} + +// reloadStorageConf reloads the storage.conf +func (r *Runtime) reloadStorageConf() error { + configFile, err := storage.DefaultConfigFile() + if err != nil { + return err + } + storage.ReloadConfigurationFile(configFile, &r.storageConfig) + logrus.Infof("Applied new storage configuration: %v", r.storageConfig) + return nil +} + +// getVolumePlugin gets a specific volume plugin. +func (r *Runtime) getVolumePlugin(volConfig *VolumeConfig) (*plugin.VolumePlugin, error) { + // There is no plugin for local. + name := volConfig.Driver + timeout := volConfig.Timeout + if name == define.VolumeDriverLocal || name == "" { + return nil, nil + } + + pluginPath, ok := r.config.Engine.VolumePlugins[name] + if !ok { + if name == define.VolumeDriverImage { + return nil, nil + } + return nil, fmt.Errorf("no volume plugin with name %s available: %w", name, define.ErrMissingPlugin) + } + + return plugin.GetVolumePlugin(name, pluginPath, timeout, r.config) +} + +// GetSecretsStorageDir returns the directory that the secrets manager should take +func (r *Runtime) GetSecretsStorageDir() string { + return filepath.Join(r.store.GraphRoot(), "secrets") +} + +// SecretsManager returns the directory that the secrets manager should take +func (r *Runtime) SecretsManager() (*secrets.SecretsManager, error) { + if r.secretsManager == nil { + manager, err := secrets.NewManager(r.GetSecretsStorageDir()) + if err != nil { + return nil, err + } + r.secretsManager = manager + } + return r.secretsManager, nil +} + +func graphRootMounted() bool { + f, err := os.OpenFile("/run/.containerenv", os.O_RDONLY, os.ModePerm) + if err != nil { + return false + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + if scanner.Text() == "graphRootMounted=1" { + return true + } + } + return false +} + +func (r *Runtime) graphRootMountedFlag(mounts []spec.Mount) string { + root := r.store.GraphRoot() + for _, val := range mounts { + if strings.HasPrefix(root, val.Source) { + return "graphRootMounted=1" + } + } + return "" +} + +// Returns a copy of the runtime alive lock +func (r *Runtime) getRuntimeAliveLock() (*lockfile.LockFile, error) { + return lockfile.GetLockFile(filepath.Join(r.config.Engine.TmpDir, "alive.lck")) +} + +// Network returns the network interface which is used by the runtime +func (r *Runtime) Network() nettypes.ContainerNetwork { + return r.network +} + +// GetDefaultNetworkName returns the network interface which is used by the runtime +func (r *Runtime) GetDefaultNetworkName() string { + return r.config.Network.DefaultNetwork +} + +// RemoteURI returns the API server URI +func (r *Runtime) RemoteURI() string { + return r.config.Engine.RemoteURI +} + +// SetRemoteURI records the API server URI +func (r *Runtime) SetRemoteURI(uri string) { + r.config.Engine.RemoteURI = uri +} + +// Get information on potential lock conflicts. +// Returns a map of lock number to object(s) using the lock, formatted as +// "container " or "volume " or "pod ", and an array of locks that +// are currently being held, formatted as []uint32. +// If the map returned is not empty, you should immediately renumber locks on +// the runtime, because you have a deadlock waiting to happen. +func (r *Runtime) LockConflicts() (map[uint32][]string, []uint32, error) { + // Make an internal map to store what lock is associated with what + locksInUse := make(map[uint32][]string) + + ctrs, err := r.state.AllContainers(false) + if err != nil { + return nil, nil, err + } + for _, ctr := range ctrs { + lockNum := ctr.lock.ID() + ctrString := fmt.Sprintf("container %s", ctr.ID()) + locksInUse[lockNum] = append(locksInUse[lockNum], ctrString) + } + + pods, err := r.state.AllPods() + if err != nil { + return nil, nil, err + } + for _, pod := range pods { + lockNum := pod.lock.ID() + podString := fmt.Sprintf("pod %s", pod.ID()) + locksInUse[lockNum] = append(locksInUse[lockNum], podString) + } + + volumes, err := r.state.AllVolumes() + if err != nil { + return nil, nil, err + } + for _, vol := range volumes { + lockNum := vol.lock.ID() + volString := fmt.Sprintf("volume %s", vol.Name()) + locksInUse[lockNum] = append(locksInUse[lockNum], volString) + } + + // Now go through and find any entries with >1 item associated + toReturn := make(map[uint32][]string) + for lockNum, objects := range locksInUse { + // If debug logging is requested, just spit out *every* lock in + // use. + logrus.Debugf("Lock number %d is in use by %v", lockNum, objects) + + if len(objects) > 1 { + toReturn[lockNum] = objects + } + } + + locksHeld, err := r.lockManager.LocksHeld() + if err != nil { + if errors.Is(err, define.ErrNotImplemented) { + logrus.Warnf("Could not retrieve currently taken locks as the lock backend does not support this operation") + return toReturn, []uint32{}, nil + } + + return nil, nil, err + } + + return toReturn, locksHeld, nil +} + +// PruneBuildContainers removes any build containers that were created during the build, +// but were not removed because the build was unexpectedly terminated. +// +// Note: This is not safe operation and should be executed only when no builds are in progress. It can interfere with builds in progress. +func (r *Runtime) PruneBuildContainers() ([]*reports.PruneReport, error) { + stageContainersPruneReports := []*reports.PruneReport{} + + containers, err := r.store.Containers() + if err != nil { + return stageContainersPruneReports, err + } + for _, container := range containers { + path, err := r.store.ContainerDirectory(container.ID) + if err != nil { + return stageContainersPruneReports, err + } + if err := fileutils.Exists(filepath.Join(path, "buildah.json")); err != nil { + continue + } + + report := &reports.PruneReport{ + Id: container.ID, + } + size, err := r.store.ContainerSize(container.ID) + if err != nil { + report.Err = err + } + report.Size = uint64(size) + + if err := r.store.DeleteContainer(container.ID); err != nil { + report.Err = errors.Join(report.Err, err) + } + stageContainersPruneReports = append(stageContainersPruneReports, report) + } + return stageContainersPruneReports, nil +} + +// SystemCheck checks our storage for consistency, and depending on the options +// specified, will attempt to remove anything which fails consistency checks. +func (r *Runtime) SystemCheck(_ context.Context, options entities.SystemCheckOptions) (entities.SystemCheckReport, error) { + what := storage.CheckEverything() + if options.Quick { + // Turn off checking layer digests and layer contents to do quick check. + // This is not a complete check like storage.CheckEverything(), and may fail detecting + // whether a file is missing from the image or its content has changed. + // In some cases it's desirable to trade check thoroughness for speed. + what = &storage.CheckOptions{ + LayerDigests: false, + LayerMountable: true, + LayerContents: false, + LayerData: true, + ImageData: true, + ContainerData: true, + } + } + if options.UnreferencedLayerMaximumAge != nil { + tmp := *options.UnreferencedLayerMaximumAge + what.LayerUnreferencedMaximumAge = &tmp + } + storageReport, err := r.store.Check(what) + if err != nil { + return entities.SystemCheckReport{}, err + } + if len(storageReport.Containers) == 0 && + len(storageReport.Layers) == 0 && + len(storageReport.ROLayers) == 0 && + len(storageReport.Images) == 0 && + len(storageReport.ROImages) == 0 { + // no errors detected + return entities.SystemCheckReport{}, nil + } + mapErrorSlicesToStringSlices := func(m map[string][]error) map[string][]string { + if len(m) == 0 { + return nil + } + mapped := make(map[string][]string, len(m)) + for k, errs := range m { + strs := make([]string, len(errs)) + for i, e := range errs { + strs[i] = e.Error() + } + mapped[k] = strs + } + return mapped + } + + report := entities.SystemCheckReport{ + Errors: true, + Layers: mapErrorSlicesToStringSlices(storageReport.Layers), + ROLayers: mapErrorSlicesToStringSlices(storageReport.ROLayers), + Images: mapErrorSlicesToStringSlices(storageReport.Images), + ROImages: mapErrorSlicesToStringSlices(storageReport.ROImages), + Containers: mapErrorSlicesToStringSlices(storageReport.Containers), + } + if !options.Repair && report.Errors { + // errors detected, no corrective measures to be taken + return report, err + } + + // get a list of images that we knew of before we tried to clean up any + // that were damaged + imagesBefore, err := r.store.Images() + if err != nil { + return report, fmt.Errorf("getting a list of images before attempting repairs: %w", err) + } + + repairOptions := storage.RepairOptions{ + RemoveContainers: options.RepairLossy, + } + var containers []*Container + if repairOptions.RemoveContainers { + // build a list of the containers that we claim as ours that we + // expect to be removing in a bit + for containerID := range storageReport.Containers { + ctr, lookupErr := r.state.LookupContainer(containerID) + if lookupErr != nil { + // we're about to remove it, so it's okay that + // it isn't even one of ours + continue + } + containers = append(containers, ctr) + } + } + + // run the cleanup + merr := multierror.Append(nil, r.store.Repair(storageReport, &repairOptions)...) + + if repairOptions.RemoveContainers { + // get the list of containers that storage will still admit to knowing about + containersAfter, err := r.store.Containers() + if err != nil { + merr = multierror.Append(merr, fmt.Errorf("getting a list of containers after attempting repairs: %w", err)) + } + for _, ctr := range containers { + // if one of our containers that we tried to remove is + // still on disk, report an error + if slices.IndexFunc(containersAfter, func(containerAfter storage.Container) bool { + return containerAfter.ID == ctr.ID() + }) != -1 { + merr = multierror.Append(merr, fmt.Errorf("clearing storage for container %s: %w", ctr.ID(), err)) + continue + } + // remove the container from our database + if removeErr := r.state.RemoveContainer(ctr); removeErr != nil { + merr = multierror.Append(merr, fmt.Errorf("updating state database to reflect removal of container %s: %w", ctr.ID(), removeErr)) + continue + } + if report.RemovedContainers == nil { + report.RemovedContainers = make(map[string]string) + } + report.RemovedContainers[ctr.ID()] = ctr.config.Name + } + } + + // get a list of images that are still around after we clean up any + // that were damaged + imagesAfter, err := r.store.Images() + if err != nil { + merr = multierror.Append(merr, fmt.Errorf("getting a list of images after attempting repairs: %w", err)) + } + for _, imageBefore := range imagesBefore { + if slices.IndexFunc(imagesAfter, func(imageAfter storage.Image) bool { + return imageAfter.ID == imageBefore.ID + }) == -1 { + if report.RemovedImages == nil { + report.RemovedImages = make(map[string][]string) + } + report.RemovedImages[imageBefore.ID] = slices.Clone(imageBefore.Names) + } + } + + if merr != nil { + err = merr.ErrorOrNil() + } + + return report, err +} + +func (r *Runtime) GetContainerExitCode(id string) (int32, error) { + return r.state.GetContainerExitCode(id) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_cstorage.go b/vendor/github.com/containers/podman/v5/libpod/runtime_cstorage.go new file mode 100644 index 00000000..ed16dcac --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_cstorage.go @@ -0,0 +1,135 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "fmt" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/sirupsen/logrus" + "go.podman.io/storage" +) + +// StorageContainer represents a container present in c/storage but not in +// libpod. +type StorageContainer struct { + ID string + Names []string + Image string + CreateTime time.Time + PresentInLibpod bool +} + +// ListStorageContainers lists all containers visible to c/storage. +func (r *Runtime) ListStorageContainers() ([]*StorageContainer, error) { + finalCtrs := []*StorageContainer{} + + ctrs, err := r.store.Containers() + if err != nil { + return nil, err + } + + for _, ctr := range ctrs { + storageCtr := new(StorageContainer) + storageCtr.ID = ctr.ID + storageCtr.Names = ctr.Names + storageCtr.Image = ctr.ImageID + storageCtr.CreateTime = ctr.Created + + // Look up if container is in state + hasCtr, err := r.state.HasContainer(ctr.ID) + if err != nil { + return nil, fmt.Errorf("looking up container %s in state: %w", ctr.ID, err) + } + + storageCtr.PresentInLibpod = hasCtr + + finalCtrs = append(finalCtrs, storageCtr) + } + + return finalCtrs, nil +} + +func (r *Runtime) StorageContainer(idOrName string) (*storage.Container, error) { + return r.store.Container(idOrName) +} + +// RemoveStorageContainer removes a container from c/storage. +// The container WILL NOT be removed if it exists in libpod. +// Accepts ID or full name of container. +// If force is set, the container will be unmounted first to ensure removal. +func (r *Runtime) RemoveStorageContainer(idOrName string, force bool) error { + targetID, err := r.store.Lookup(idOrName) + if err != nil { + if errors.Is(err, storage.ErrLayerUnknown) { + return fmt.Errorf("no container with ID or name %q found: %w", idOrName, define.ErrNoSuchCtr) + } + return fmt.Errorf("looking up container %q: %w", idOrName, err) + } + + // Lookup returns an ID but it's not guaranteed to be a container ID. + // So we can still error here. + ctr, err := r.store.Container(targetID) + if err != nil { + if errors.Is(err, storage.ErrContainerUnknown) { + return fmt.Errorf("%q does not refer to a container: %w", idOrName, define.ErrNoSuchCtr) + } + return fmt.Errorf("retrieving container %q: %w", idOrName, err) + } + + // Error out if the container exists in libpod + exists, err := r.state.HasContainer(ctr.ID) + if err != nil { + return err + } + if exists { + return fmt.Errorf("refusing to remove %q as it exists in libpod as container %s: %w", idOrName, ctr.ID, define.ErrCtrExists) + } + + // Error out if this is an image-backed volume + allVols, err := r.state.AllVolumes() + if err != nil { + return err + } + for _, vol := range allVols { + if vol.config.Driver == define.VolumeDriverImage && vol.config.StorageID == ctr.ID { + return fmt.Errorf("refusing to remove %q as it exists in libpod as an image-backed volume %s: %w", idOrName, vol.Name(), define.ErrCtrExists) + } + } + + if !force { + timesMounted, err := r.store.Mounted(ctr.ID) + if err != nil { + if errors.Is(err, storage.ErrContainerUnknown) { + // Container was removed from under us. + // It's gone, so don't bother erroring. + logrus.Infof("Storage for container %s already removed", ctr.ID) + return nil + } + logrus.Warnf("Checking if container %q is mounted, attempting to delete: %v", idOrName, err) + } + if timesMounted > 0 { + return fmt.Errorf("container %q is mounted and cannot be removed without using force: %w", idOrName, define.ErrCtrStateInvalid) + } + } else if _, err := r.store.Unmount(ctr.ID, true); err != nil { + if errors.Is(err, storage.ErrContainerUnknown) { + // Container again gone, no error + logrus.Infof("Storage for container %s already removed", ctr.ID) + return nil + } + logrus.Warnf("Unmounting container %q while attempting to delete storage: %v", idOrName, err) + } + + if err := r.store.DeleteContainer(ctr.ID); err != nil { + if errors.Is(err, storage.ErrNotAContainer) || errors.Is(err, storage.ErrContainerUnknown) { + // Container again gone, no error + logrus.Infof("Storage for container %s already removed", ctr.ID) + return nil + } + return fmt.Errorf("removing storage for container %q: %w", idOrName, err) + } + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_ctr.go b/vendor/github.com/containers/podman/v5/libpod/runtime_ctr.go new file mode 100644 index 00000000..bea313b2 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_ctr.go @@ -0,0 +1,1486 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + "io/fs" + "maps" + "os" + "path" + "path/filepath" + "slices" + "strings" + "time" + + "github.com/containers/buildah" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/libpod/shutdown" + "github.com/containers/podman/v5/pkg/domain/entities/reports" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/specgen" + "github.com/containers/podman/v5/pkg/util" + "github.com/docker/go-units" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/common/pkg/config" + "go.podman.io/storage" + "go.podman.io/storage/pkg/stringid" +) + +// Contains the public Runtime API for containers + +// A CtrCreateOption is a functional option which alters the Container created +// by NewContainer +type CtrCreateOption func(*Container) error + +// ContainerFilter is a function to determine whether a container is included +// in command output. Containers to be outputted are tested using the function. +// A true return will include the container, a false return will exclude it. +type ContainerFilter func(*Container) bool + +// NewContainer creates a new container from a given OCI config. +func (r *Runtime) NewContainer(ctx context.Context, rSpec *spec.Spec, spec *specgen.SpecGenerator, infra bool, options ...CtrCreateOption) (*Container, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + if infra { + options = append(options, withIsInfra()) + if len(spec.RawImageName) == 0 { + options = append(options, withIsDefaultInfra()) + } + } + return r.newContainer(ctx, rSpec, options...) +} + +func (r *Runtime) PrepareVolumeOnCreateContainer(_ context.Context, ctr *Container) error { + // Copy the content from the underlying image into the newly created + // volume if configured to do so. + if !r.config.Containers.PrepareVolumeOnCreate { + return nil + } + + defer func() { + if err := ctr.cleanupStorage(); err != nil { + logrus.Errorf("Cleaning up container storage %s: %v", ctr.ID(), err) + } + }() + + mountPoint, err := ctr.mountStorage() + if err == nil { + // Finish up mountStorage + ctr.state.Mounted = true + ctr.state.Mountpoint = mountPoint + if err = ctr.save(); err != nil { + logrus.Errorf("Saving container %s state: %v", ctr.ID(), err) + } + } + + return err +} + +// RestoreContainer re-creates a container from an imported checkpoint +func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config *ContainerConfig) (*Container, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + ctr, err := r.initContainerVariables(rSpec, config) + if err != nil { + return nil, fmt.Errorf("initializing container variables: %w", err) + } + // For an imported checkpoint no one has ever set the StartedTime. Set it now. + ctr.state.StartedTime = time.Now() + + // If the path to ConmonPidFile starts with the default value (RunRoot), then + // the user has not specified '--conmon-pidfile' during run or create (probably). + // In that case reset ConmonPidFile to be set to the default value later. + if strings.HasPrefix(ctr.config.ConmonPidFile, r.storageConfig.RunRoot) { + ctr.config.ConmonPidFile = "" + } + + // If the path to PidFile starts with the default value (RunRoot), then + // the user has not specified '--pidfile' during run or create (probably). + // In that case reset PidFile to be set to the default value later. + if strings.HasPrefix(ctr.config.PidFile, r.storageConfig.RunRoot) { + ctr.config.PidFile = "" + } + + return r.setupContainer(ctx, ctr) +} + +// RenameContainer renames the given container. +// Returns a copy of the container that has been renamed if successful. +func (r *Runtime) RenameContainer(_ context.Context, ctr *Container, newName string) (*Container, error) { + ctr.lock.Lock() + defer ctr.lock.Unlock() + + if err := ctr.syncContainer(); err != nil { + return nil, err + } + + newName = strings.TrimPrefix(newName, "/") + if newName == "" || !define.NameRegex.MatchString(newName) { + return nil, define.RegexError + } + + // We need to pull an updated config, in case another rename fired and + // the config was re-written. + newConf, err := r.state.GetContainerConfig(ctr.ID()) + if err != nil { + return nil, fmt.Errorf("retrieving container %s configuration from DB to remove: %w", ctr.ID(), err) + } + ctr.config = newConf + + logrus.Infof("Going to rename container %s from %q to %q", ctr.ID(), ctr.Name(), newName) + + // Step 1: Alter the config. Save the old name, we need it to rewrite + // the config. + oldName := ctr.config.Name + ctr.config.Name = newName + + // Step 2: rewrite the old container's config in the DB. + if err := r.state.SafeRewriteContainerConfig(ctr, oldName, ctr.config.Name, ctr.config); err != nil { + // Assume the rename failed. + // Set config back to the old name so reflect what is actually + // present in the DB. + ctr.config.Name = oldName + return nil, fmt.Errorf("renaming container %s: %w", ctr.ID(), err) + } + + // Step 3: rename the container in c/storage. + // This can fail if the name is already in use by a non-Podman + // container. This puts us in a bad spot - we've already renamed the + // container in Podman. We can swap the order, but then we have the + // opposite problem. Atomicity is a real problem here, with no easy + // solution. + if err := r.store.SetNames(ctr.ID(), []string{ctr.Name()}); err != nil { + return nil, err + } + + ctr.newContainerEvent(events.Rename) + return ctr, nil +} + +func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConfig) (*Container, error) { + if rSpec == nil { + return nil, fmt.Errorf("must provide a valid runtime spec to create container: %w", define.ErrInvalidArg) + } + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(ContainerState) + + if config == nil { + ctr.config.ID = stringid.GenerateRandomID() + size, err := units.FromHumanSize(r.config.Containers.ShmSize) + if useDevShm { + if err != nil { + return nil, fmt.Errorf("converting containers.conf ShmSize %s to an int: %w", r.config.Containers.ShmSize, err) + } + ctr.config.ShmSize = size + ctr.config.NoShm = false + ctr.config.NoShmShare = false + } else { + ctr.config.NoShm = true + ctr.config.NoShmShare = true + } + ctr.config.StopSignal = 15 + + ctr.config.StopTimeout = r.config.Engine.StopTimeout + } else { + // This is a restore from an imported checkpoint + ctr.restoreFromCheckpoint = true + if err := JSONDeepCopy(config, ctr.config); err != nil { + return nil, fmt.Errorf("copying container config for restore: %w", err) + } + // If the ID is empty a new name for the restored container was requested + if ctr.config.ID == "" { + ctr.config.ID = stringid.GenerateRandomID() + } + // Reset the log path to point to the default + ctr.config.LogPath = "" + // Later in validate() the check is for nil. JSONDeepCopy sets it to an empty + // object. Resetting it to nil if it was nil before. + if config.StaticMAC == nil { + ctr.config.StaticMAC = nil + } + } + + ctr.config.Spec = rSpec + ctr.config.CreatedTime = time.Now() + + ctr.state.BindMounts = make(map[string]string) + + ctr.config.OCIRuntime = r.defaultOCIRuntime.Name() + + // Set namespace based on current runtime namespace + // Do so before options run so they can override it + if r.config.Engine.Namespace != "" { + ctr.config.Namespace = r.config.Engine.Namespace + } + + ctr.runtime = r + + return ctr, nil +} + +func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ...CtrCreateOption) (*Container, error) { + var ctr *Container + var err error + + ctr, err = r.initContainerVariables(rSpec, nil) + + if err != nil { + return nil, fmt.Errorf("initializing container variables: %w", err) + } + + for _, option := range options { + if err := option(ctr); err != nil { + return nil, fmt.Errorf("running container create option: %w", err) + } + } + + return r.setupContainer(ctx, ctr) +} + +func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Container, retErr error) { + if ctr.IsDefaultInfra() || ctr.IsService() { + err := ctr.createInitRootfs() + if err != nil { + return nil, err + } + _, err = ctr.prepareCatatonitMount() + if err != nil { + return nil, err + } + } + + // normalize the networks to names + // the db backend only knows about network names so we have to make + // sure we do not use ids internally + if len(ctr.config.Networks) > 0 { + normalizeNetworks := make(map[string]types.PerNetworkOptions, len(ctr.config.Networks)) + // first get the already used interface names so we do not conflict + usedIfNames := make([]string, 0, len(ctr.config.Networks)) + for _, opts := range ctr.config.Networks { + if opts.InterfaceName != "" { + // check that no name is assigned to more than network + if slices.Contains(usedIfNames, opts.InterfaceName) { + return nil, fmt.Errorf("network interface name %q is already assigned to another network", opts.InterfaceName) + } + usedIfNames = append(usedIfNames, opts.InterfaceName) + } + } + i := 0 + for nameOrID, opts := range ctr.config.Networks { + netName, nicName, err := r.normalizeNetworkName(nameOrID) + if err != nil { + return nil, err + } + + // check whether interface is to be named as the network_interface + // when name left unspecified + if opts.InterfaceName == "" { + opts.InterfaceName = nicName + } + + // assign default interface name if empty + if opts.InterfaceName == "" { + for i < 100000 { + ifName := fmt.Sprintf("eth%d", i) + if !slices.Contains(usedIfNames, ifName) { + opts.InterfaceName = ifName + usedIfNames = append(usedIfNames, ifName) + break + } + i++ + } + // if still empty we did not find a free name + if opts.InterfaceName == "" { + return nil, errors.New("failed to find free network interface name") + } + } + opts.Aliases = append(opts.Aliases, getExtraNetworkAliases(ctr)...) + + normalizeNetworks[netName] = opts + } + ctr.config.Networks = normalizeNetworks + } + + // Validate the container + if err := ctr.validate(); err != nil { + return nil, err + } + if ctr.config.IsInfra { + ctr.config.StopTimeout = 10 + } + + // Inhibit shutdown until creation succeeds + shutdown.Inhibit() + defer shutdown.Uninhibit() + + // Allocate a lock for the container + lock, err := r.lockManager.AllocateLock() + if err != nil { + return nil, fmt.Errorf("allocating lock for new container: %w", err) + } + ctr.lock = lock + ctr.config.LockID = ctr.lock.ID() + logrus.Debugf("Allocated lock %d for container %s", ctr.lock.ID(), ctr.ID()) + + defer func() { + if retErr != nil { + if err := ctr.lock.Free(); err != nil { + logrus.Errorf("Freeing lock for container after creation failed: %v", err) + } + } + }() + + ctr.valid = true + ctr.state.State = define.ContainerStateConfigured + ctr.runtime = r + + if ctr.config.OCIRuntime == "" { + ctr.ociRuntime = r.defaultOCIRuntime + } else { + ociRuntime, ok := r.ociRuntimes[ctr.config.OCIRuntime] + if !ok { + return nil, fmt.Errorf("requested OCI runtime %s is not available: %w", ctr.config.OCIRuntime, define.ErrInvalidArg) + } + ctr.ociRuntime = ociRuntime + } + + // Check NoCgroups support + if ctr.config.NoCgroups { + if !ctr.ociRuntime.SupportsNoCgroups() { + return nil, fmt.Errorf("requested OCI runtime %s is not compatible with NoCgroups: %w", ctr.ociRuntime.Name(), define.ErrInvalidArg) + } + } + + var pod *Pod + if ctr.config.Pod != "" { + // Get the pod from state + pod, err = r.state.Pod(ctr.config.Pod) + if err != nil { + return nil, fmt.Errorf("cannot add container %s to pod %s: %w", ctr.ID(), ctr.config.Pod, err) + } + } + + // Check Cgroup parent sanity, and set it if it was not set. + // Only if we're actually configuring Cgroups. + if !ctr.config.NoCgroups { + ctr.config.CgroupManager = r.config.Engine.CgroupManager + switch r.config.Engine.CgroupManager { + case config.CgroupfsCgroupsManager: + if ctr.config.CgroupParent == "" { + if pod != nil && pod.config.UsePodCgroup && !ctr.IsInfra() { + podCgroup, err := pod.CgroupPath() + if err != nil { + return nil, fmt.Errorf("retrieving pod %s cgroup: %w", pod.ID(), err) + } + expectPodCgroup, err := ctr.expectPodCgroup() + if err != nil { + return nil, err + } + if expectPodCgroup && podCgroup == "" { + return nil, fmt.Errorf("pod %s cgroup is not set: %w", pod.ID(), define.ErrInternal) + } + canUseCgroup := !rootless.IsRootless() || isRootlessCgroupSet(podCgroup) + if canUseCgroup { + ctr.config.CgroupParent = podCgroup + } + } else if !rootless.IsRootless() { + ctr.config.CgroupParent = CgroupfsDefaultCgroupParent + } + } else if strings.HasSuffix(path.Base(ctr.config.CgroupParent), ".slice") { + return nil, fmt.Errorf("systemd slice received as cgroup parent when using cgroupfs: %w", define.ErrInvalidArg) + } + case config.SystemdCgroupsManager: + if ctr.config.CgroupParent == "" { + switch { + case pod != nil && pod.config.UsePodCgroup && !ctr.IsInfra(): + podCgroup, err := pod.CgroupPath() + if err != nil { + return nil, fmt.Errorf("retrieving pod %s cgroup: %w", pod.ID(), err) + } + expectPodCgroup, err := ctr.expectPodCgroup() + if err != nil { + return nil, err + } + if expectPodCgroup && podCgroup == "" { + return nil, fmt.Errorf("pod %s cgroup is not set: %w", pod.ID(), define.ErrInternal) + } + ctr.config.CgroupParent = podCgroup + case rootless.IsRootless() && ctr.config.CgroupsMode != cgroupSplit: + ctr.config.CgroupParent = SystemdDefaultRootlessCgroupParent + case ctr.config.CgroupsMode != cgroupSplit: + ctr.config.CgroupParent = SystemdDefaultCgroupParent + } + } else if len(ctr.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(ctr.config.CgroupParent), ".slice") { + return nil, fmt.Errorf("did not receive systemd slice as cgroup parent when using systemd to manage cgroups: %w", define.ErrInvalidArg) + } + default: + return nil, fmt.Errorf("unsupported Cgroup manager: %s - cannot validate cgroup parent: %w", r.config.Engine.CgroupManager, define.ErrInvalidArg) + } + } + + if ctr.config.Timezone == "" { + ctr.config.Timezone = r.config.Containers.TZ + } + + if ctr.restoreFromCheckpoint { + // Remove information about bind mount + // for new container from imported checkpoint + // NewFromSpec() is deprecated according to its comment + // however the recommended replace just causes a nil map panic + g := generate.NewFromSpec(ctr.config.Spec) + g.RemoveMount("/dev/shm") + ctr.config.ShmDir = "" + g.RemoveMount("/etc/resolv.conf") + g.RemoveMount("/etc/hostname") + g.RemoveMount("/etc/hosts") + g.RemoveMount("/run/.containerenv") + g.RemoveMount("/run/secrets") + g.RemoveMount("/var/run/.containerenv") + g.RemoveMount("/var/run/secrets") + + // Regenerate Cgroup paths so they don't point to the old + // container ID. + cgroupPath, err := ctr.getOCICgroupPath() + if err != nil { + return nil, err + } + g.SetLinuxCgroupsPath(cgroupPath) + } + + // Set up storage for the container + if err := ctr.setupStorage(ctx); err != nil { + return nil, err + } + defer func() { + if retErr != nil { + if err := ctr.teardownStorage(); err != nil { + logrus.Errorf("Removing partially-created container root filesystem: %v", err) + } + } + }() + + ctr.config.SecretsPath = filepath.Join(ctr.config.StaticDir, "secrets") + err = os.MkdirAll(ctr.config.SecretsPath, 0o755) + if err != nil { + return nil, err + } + for _, secr := range ctr.config.Secrets { + err = ctr.extractSecretToCtrStorage(secr) + if err != nil { + return nil, err + } + } + + if ctr.config.ConmonPidFile == "" { + ctr.config.ConmonPidFile = filepath.Join(ctr.state.RunDir, "conmon.pid") + } + + if ctr.config.PidFile == "" { + ctr.config.PidFile = filepath.Join(ctr.state.RunDir, "pidfile") + } + + // Go through named volumes and add them. + // If they don't exist they will be created using basic options. + for _, vol := range ctr.config.NamedVolumes { + isAnonymous := false + if vol.Name == "" { + // Anonymous volume. We'll need to create it. + // It needs a name first. + vol.Name = stringid.GenerateRandomID() + isAnonymous = true + } else { + // Check if it already exists + _, err := r.state.Volume(vol.Name) + if err == nil { + // The volume exists, we're good + // Make sure to drop all volume-opt options as they only apply to + // the volume create which we don't do again. + var volOpts []string + for _, opts := range vol.Options { + if !strings.HasPrefix(opts, "volume-opt") { + volOpts = append(volOpts, opts) + } + } + vol.Options = volOpts + continue + } else if !errors.Is(err, define.ErrNoSuchVolume) { + return nil, fmt.Errorf("retrieving named volume %s for new container: %w", vol.Name, err) + } + } + if vol.IsAnonymous { + // If SetAnonymous is true, make this an anonymous volume + // this is needed for emptyDir volumes from kube yamls + isAnonymous = true + } + + logrus.Debugf("Creating new volume %s for container", vol.Name) + + // The volume does not exist, so we need to create it. + volOptions := []VolumeCreateOption{ + WithVolumeName(vol.Name), + WithVolumeMountLabel(ctr.MountLabel()), + } + if isAnonymous { + volOptions = append(volOptions, withSetAnon()) + } + + // If volume-opts are set, parse and add driver opts. + if len(vol.Options) > 0 { + isDriverOpts := false + driverOpts := make(map[string]string) + var volOpts []string + for _, opts := range vol.Options { + if strings.HasPrefix(opts, "volume-opt") { + isDriverOpts = true + driverOptKey, driverOptValue, err := util.ParseDriverOpts(opts) + if err != nil { + return nil, err + } + driverOpts[driverOptKey] = driverOptValue + } else { + volOpts = append(volOpts, opts) + } + } + vol.Options = volOpts + if isDriverOpts { + parsedOptions := []VolumeCreateOption{WithVolumeOptions(driverOpts)} + volOptions = append(volOptions, parsedOptions...) + } + } + + volOptions = append(volOptions, WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())) + + _, err = r.newVolume(ctx, false, volOptions...) + if err != nil { + return nil, fmt.Errorf("creating named volume %q: %w", vol.Name, err) + } + } + + switch ctr.config.LogDriver { + case define.NoLogging, define.PassthroughLogging, define.JournaldLogging: + break + default: + if ctr.config.LogPath == "" { + ctr.config.LogPath = filepath.Join(ctr.config.StaticDir, "ctr.log") + } + } + + if useDevShm && !MountExists(ctr.config.Spec.Mounts, "/dev/shm") && ctr.config.ShmDir == "" && !ctr.config.NoShm { + ctr.config.ShmDir = filepath.Join(ctr.bundlePath(), "shm") + if err := os.MkdirAll(ctr.config.ShmDir, 0o700); err != nil { + if !os.IsExist(err) { + return nil, fmt.Errorf("unable to create shm dir: %w", err) + } + } + ctr.config.Mounts = append(ctr.config.Mounts, ctr.config.ShmDir) + } + + // Add the container to the state + // TODO: May be worth looking into recovering from name/ID collisions here + if ctr.config.Pod != "" { + // Lock the pod to ensure we can't add containers to pods + // being removed + pod.lock.Lock() + defer pod.lock.Unlock() + + if err := r.state.AddContainerToPod(pod, ctr); err != nil { + return nil, err + } + } else if err := r.state.AddContainer(ctr); err != nil { + return nil, err + } + + if ctr.runtime.config.Engine.EventsContainerCreateInspectData { + if err := ctr.newContainerEventWithInspectData(events.Create, define.HealthCheckResults{}, true); err != nil { + return nil, err + } + } else { + ctr.newContainerEvent(events.Create) + } + return ctr, nil +} + +// RemoveContainer removes the given container. If force is true, the container +// will be stopped first (otherwise, an error will be returned if the container +// is running). If removeVolume is specified, anonymous named volumes used by the +// container will be removed also (iff the container is the sole user of the +// volumes). Timeout sets the stop timeout for the container if it is running. +func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool, removeVolume bool, timeout *uint) error { + opts := ctrRmOpts{ + Force: force, + RemoveVolume: removeVolume, + Timeout: timeout, + } + + // NOTE: container will be locked down the road. There is no unlocked + // version of removeContainer. + _, _, err := r.removeContainer(ctx, c, opts) + return err +} + +// RemoveContainerAndDependencies removes the given container and all its +// dependencies. This may include pods (if the container or any of its +// dependencies is an infra or service container, the associated pod(s) will also +// be removed). Otherwise, it functions identically to RemoveContainer. +// Returns two arrays: containers removed, and pods removed. These arrays are +// always returned, even if error is set, and indicate any containers that were +// successfully removed prior to the error. +func (r *Runtime) RemoveContainerAndDependencies(ctx context.Context, c *Container, force bool, removeVolume bool, timeout *uint) (map[string]error, map[string]error, error) { + opts := ctrRmOpts{ + Force: force, + RemoveVolume: removeVolume, + RemoveDeps: true, + Timeout: timeout, + } + + // NOTE: container will be locked down the road. There is no unlocked + // version of removeContainer. + return r.removeContainer(ctx, c, opts) +} + +// Options for removeContainer +type ctrRmOpts struct { + // Whether to stop running container(s) + Force bool + // Whether to remove anonymous volumes used by removing the container + RemoveVolume bool + // Only set by `removePod` as `removeContainer` is being called as part + // of removing a whole pod. + RemovePod bool + // Whether to ignore dependencies of the container when removing + // (This is *DANGEROUS* and should not be used outside of non-graph + // traversal pod removal code). + IgnoreDeps bool + // Remove all the dependencies associated with the container. Can cause + // multiple containers, and possibly one or more pods, to be removed. + RemoveDeps bool + // Do not lock the pod that the container is part of (used only by + // recursive calls of removeContainer, used when removing dependencies) + NoLockPod bool + // Timeout to use when stopping the container. Only used if `Force` is + // true. + Timeout *uint +} + +// Internal function to remove a container. +// Locks the container, but does not lock the runtime. +// removePod is used only when removing pods. It instructs Podman to ignore +// infra container protections, and *not* remove from the database (as pod +// remove will handle that). +// ignoreDeps is *DANGEROUS* and should not be used outside of a very specific +// context (alternate pod removal code, where graph traversal is not possible). +// removeDeps instructs Podman to remove dependency containers (and possible +// a dependency pod if an infra container is involved). removeDeps conflicts +// with removePod - pods have their own dependency management. +// noLockPod is used for recursive removeContainer calls when the pod is already +// locked. +// TODO: At this point we should just start accepting an options struct +func (r *Runtime) removeContainer(ctx context.Context, c *Container, opts ctrRmOpts) (removedCtrs map[string]error, removedPods map[string]error, retErr error) { + removedCtrs = make(map[string]error) + removedPods = make(map[string]error) + + if !c.valid { + if ok, _ := r.state.HasContainer(c.ID()); !ok { + // Container probably already removed + // Or was never in the runtime to begin with + removedCtrs[c.ID()] = nil + return + } + } + + if opts.RemovePod && opts.RemoveDeps { + retErr = fmt.Errorf("cannot remove dependencies while also removing a pod: %w", define.ErrInvalidArg) + return + } + + // We need to refresh container config from the DB, to ensure that any + // changes (e.g. a rename) are picked up before we start removing. + // Since HasContainer above succeeded, we can safely assume the + // container exists. + // This is *very iffy* but it should be OK because the container won't + // exist once we're done. + newConf, err := r.state.GetContainerConfig(c.ID()) + if err != nil { + retErr = fmt.Errorf("retrieving container %s configuration from DB to remove: %w", c.ID(), err) + return + } + c.config = newConf + + logrus.Debugf("Removing container %s", c.ID()) + + // We need to lock the pod before we lock the container. + // To avoid races around removing a container and the pod it is in. + // Don't need to do this in pod removal case - we're evicting the entire + // pod. + var pod *Pod + runtime := c.runtime + if c.config.Pod != "" { + pod, err = r.state.Pod(c.config.Pod) + if err != nil { + // There's a potential race here where the pod we are in + // was already removed. + // If so, this container is also removed, as pods take + // all their containers with them. + // So if it's already gone, check if we are too. + if errors.Is(err, define.ErrNoSuchPod) { + // We could check the DB to see if we still + // exist, but that would be a serious violation + // of DB integrity. + // Mark this container as removed so there's no + // confusion, though. + removedCtrs[c.ID()] = nil + return + } + + retErr = err + return + } + + if !opts.RemovePod { + // Lock the pod while we're removing container + if pod.config.LockID == c.config.LockID { + retErr = fmt.Errorf("container %s and pod %s share lock ID %d: %w", c.ID(), pod.ID(), c.config.LockID, define.ErrWillDeadlock) + return + } + if !opts.NoLockPod { + pod.lock.Lock() + defer pod.lock.Unlock() + } + if err := pod.updatePod(); err != nil { + // As above, there's a chance the pod was + // already removed. + if errors.Is(err, define.ErrNoSuchPod) { + removedCtrs[c.ID()] = nil + return + } + + retErr = err + return + } + + infraID := pod.state.InfraContainerID + if c.ID() == infraID && !opts.RemoveDeps { + retErr = fmt.Errorf("container %s is the infra container of pod %s and cannot be removed without removing the pod", c.ID(), pod.ID()) + return + } + } + } + + // For pod removal, the container is already locked by the caller + locked := false + if !opts.RemovePod { + c.lock.Lock() + defer func() { + if locked { + c.lock.Unlock() + } + }() + locked = true + } + + if !r.valid { + retErr = define.ErrRuntimeStopped + return + } + + // Update the container to get current state + if err := c.syncContainer(); err != nil { + retErr = err + return + } + + serviceForPod := false + if c.IsService() { + for _, id := range c.state.Service.Pods { + depPod, err := c.runtime.LookupPod(id) + if err != nil { + if errors.Is(err, define.ErrNoSuchPod) { + continue + } + retErr = err + return + } + if !opts.RemoveDeps { + retErr = fmt.Errorf("container %s is the service container of pod(s) %s and cannot be removed without removing the pod(s)", c.ID(), strings.Join(c.state.Service.Pods, ",")) + return + } + // If we are the service container for the pod we are a + // member of: we need to remove that pod last, since + // this container is part of it. + if pod != nil && pod.ID() == depPod.ID() { + serviceForPod = true + continue + } + logrus.Infof("Removing pod %s as container %s is its service container", depPod.ID(), c.ID()) + podRemovedCtrs, err := r.RemovePod(ctx, depPod, true, opts.Force, opts.Timeout) + maps.Copy(removedCtrs, podRemovedCtrs) + if err != nil && !errors.Is(err, define.ErrNoSuchPod) && !errors.Is(err, define.ErrPodRemoved) { + removedPods[depPod.ID()] = err + retErr = fmt.Errorf("error removing container %s dependency pods: %w", c.ID(), err) + return + } + removedPods[depPod.ID()] = nil + } + } + if (serviceForPod || c.config.IsInfra) && !opts.RemovePod { + // We're going to remove the pod we are a part of. + // This will get rid of us as well, so we can just return + // immediately after. + if locked { + locked = false + c.lock.Unlock() + } + + logrus.Infof("Removing pod %s (dependency of container %s)", pod.ID(), c.ID()) + podRemovedCtrs, err := r.removePod(ctx, pod, true, opts.Force, opts.Timeout) + maps.Copy(removedCtrs, podRemovedCtrs) + if err != nil && !errors.Is(err, define.ErrNoSuchPod) && !errors.Is(err, define.ErrPodRemoved) { + removedPods[pod.ID()] = err + retErr = fmt.Errorf("error removing container %s pod: %w", c.ID(), err) + return + } + removedPods[pod.ID()] = nil + return + } + + // If we're not force-removing, we need to check if we're in a good + // state to remove. + if !opts.Force { + if err := c.checkReadyForRemoval(); err != nil { + retErr = err + return + } + } + + if c.state.State == define.ContainerStatePaused { + isV2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + retErr = err + return + } + // cgroups v1 and v2 handle signals on paused processes differently + if !isV2 { + if err := c.unpause(); err != nil { + retErr = err + return + } + } + if err := c.ociRuntime.KillContainer(c, 9, false); err != nil { + retErr = err + return + } + // Need to update container state to make sure we know it's stopped + if err := c.waitForExitFileAndSync(); err != nil { + retErr = err + return + } + } + + // Check that no other containers depend on the container. + // Only used if not removing a pod - pods guarantee that all + // deps will be evicted at the same time. + if !opts.IgnoreDeps { + deps, err := r.state.ContainerInUse(c) + if err != nil { + retErr = err + return + } + if !opts.RemoveDeps { + if len(deps) != 0 { + depsStr := strings.Join(deps, ", ") + retErr = fmt.Errorf("container %s has dependent containers which must be removed before it: %s: %w", c.ID(), depsStr, define.ErrCtrExists) + return + } + } + for _, depCtr := range deps { + dep, err := r.GetContainer(depCtr) + if err != nil { + retErr = err + return + } + logrus.Infof("Removing container %s (dependency of container %s)", dep.ID(), c.ID()) + recursiveOpts := ctrRmOpts{ + Force: opts.Force, + RemoveVolume: opts.RemoveVolume, + RemoveDeps: true, + NoLockPod: true, + Timeout: opts.Timeout, + } + ctrs, pods, err := r.removeContainer(ctx, dep, recursiveOpts) + for rmCtr, err := range ctrs { + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + removedCtrs[rmCtr] = nil + } else { + removedCtrs[rmCtr] = err + } + } + maps.Copy(removedPods, pods) + if err != nil && !errors.Is(err, define.ErrNoSuchCtr) && !errors.Is(err, define.ErrCtrRemoved) { + retErr = err + return + } + } + } + + // Check that the container's in a good state to be removed. + if c.ensureState(define.ContainerStateRunning, define.ContainerStateStopping) { + time := c.StopTimeout() + if opts.Timeout != nil { + time = *opts.Timeout + } + // Ignore ErrConmonDead - we couldn't retrieve the container's + // exit code properly, but it's still stopped. + if err := c.stop(time); err != nil && !errors.Is(err, define.ErrConmonDead) { + retErr = fmt.Errorf("cannot remove container %s as it could not be stopped: %w", c.ID(), err) + return + } + + // We unlocked as part of stop() above - there's a chance someone + // else got in and removed the container before we reacquired the + // lock. + // Do a quick ping of the database to check if the container + // still exists. + if ok, _ := r.state.HasContainer(c.ID()); !ok { + // When the container has already been removed, the OCI runtime directory remains. + if err := c.cleanupRuntime(ctx); err != nil { + retErr = fmt.Errorf("cleaning up container %s from OCI runtime: %w", c.ID(), err) + return + } + // Do not add to removed containers, someone else + // removed it. + return + } + } + + reportErrorf := func(msg string, args ...any) { + err := fmt.Errorf(msg, args...) // Always use fmt.Errorf instead of just logrus.Errorf(…) because the format string probably contains %w + if retErr == nil { + retErr = err + } else { + logrus.Errorf("%s", err.Error()) + } + } + + // Clean up network namespace, cgroups, mounts. + // Do this before we set ContainerStateRemoving, to ensure that we can + // actually remove from the OCI runtime. + if err := c.cleanup(ctx); err != nil { + reportErrorf("cleaning up container %s: %w", c.ID(), err) + } + + // Remove all active exec sessions + // removing the exec sessions might temporarily unlock the container's lock. Using it + // after setting the state to ContainerStateRemoving will prevent that the container is + // restarted + if err := c.removeAllExecSessions(); err != nil { + reportErrorf("removing exec sessions: %w", err) + } + + // Set ContainerStateRemoving as an intermediate state (we may get + // killed at any time) and save the container. + c.state.State = define.ContainerStateRemoving + + if err := c.save(); err != nil { + if !errors.Is(err, define.ErrCtrRemoved) { + reportErrorf("saving container: %w", err) + } + } + + // Stop the container's storage + if err := c.teardownStorage(); err != nil { + reportErrorf("cleaning up storage: %w", err) + } + + // Remove the container from the state + if c.config.Pod != "" { + // If we're removing the pod, the container will be evicted + // from the state elsewhere + if err := r.state.RemoveContainerFromPod(pod, c); err != nil { + reportErrorf("removing container %s from database: %w", c.ID(), err) + } + } else { + if err := r.state.RemoveContainer(c); err != nil { + reportErrorf("removing container %s from database: %w", c.ID(), err) + } + } + removedCtrs[c.ID()] = nil + + // Remove the container's CID file on container removal. + if cidFile, ok := c.config.Spec.Annotations[define.InspectAnnotationCIDFile]; ok { + if err := os.Remove(cidFile); err != nil && !errors.Is(err, os.ErrNotExist) { + reportErrorf("cleaning up CID file: %w", err) + } + } + + // Deallocate the container's lock + if err := c.lock.Free(); err != nil && !errors.Is(err, fs.ErrNotExist) { + reportErrorf("freeing lock for container %s: %w", c.ID(), err) + } + + // Set container as invalid so it can no longer be used + c.valid = false + + c.newContainerEvent(events.Remove) + + if !opts.RemoveVolume { + return + } + + for _, v := range c.config.NamedVolumes { + if volume, err := runtime.state.Volume(v.Name); err == nil { + if !volume.Anonymous() { + continue + } + if err := runtime.removeVolume(ctx, volume, false, opts.Timeout, false); err != nil && !errors.Is(err, define.ErrNoSuchVolume) { + if errors.Is(err, define.ErrVolumeBeingUsed) { + // Ignore error, since podman will report original error + volumesFrom, _ := c.volumesFrom() + if len(volumesFrom) > 0 { + logrus.Debugf("Cleaning up volume not possible since volume is in use (%s)", v.Name) + continue + } + } + logrus.Errorf("Cleaning up volume (%s): %v", v.Name, err) + } + } + } + + //nolint:nakedret + return +} + +// EvictContainer removes the given container partial or full ID or name, and +// returns the full ID of the evicted container and any error encountered. +// It should be used to remove a container when obtaining a Container struct +// pointer has failed. +// Running container will not be stopped. +// If removeVolume is specified, named volumes used by the container will +// be removed also if and only if the container is the sole user. +func (r *Runtime) EvictContainer(ctx context.Context, idOrName string, removeVolume bool) (string, error) { + return r.evictContainer(ctx, idOrName, removeVolume) +} + +// evictContainer is the internal function to handle container eviction based +// on its partial or full ID or name. +// It returns the full ID of the evicted container and any error encountered. +// This does not lock the runtime nor the container. +// removePod is used only when removing pods. It instructs Podman to ignore +// infra container protections, and *not* remove from the database (as pod +// remove will handle that). +func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVolume bool) (string, error) { + var err error + var timeout *uint + + if !r.valid { + return "", define.ErrRuntimeStopped + } + + id, err := r.state.LookupContainerID(idOrName) + if err != nil { + return "", err + } + + // Begin by trying a normal removal. Valid containers will be removed normally. + tmpCtr, err := r.state.Container(id) + if err == nil { + logrus.Infof("Container %s successfully retrieved from state, attempting normal removal", id) + // Assume force = true for the evict case + opts := ctrRmOpts{ + Force: true, + RemoveVolume: removeVolume, + Timeout: timeout, + } + _, _, err = r.removeContainer(ctx, tmpCtr, opts) + if !tmpCtr.valid { + // If the container is marked invalid, remove succeeded + // in kicking it out of the state - no need to continue. + return id, err + } + + if err == nil { + // Something has gone seriously wrong - no error but + // container was not removed. + logrus.Errorf("Container %s not removed with no error", id) + } else { + logrus.Warnf("Failed to removal container %s normally, proceeding with evict: %v", id, err) + } + } + + // Error out if the container does not exist in libpod + exists, err := r.state.HasContainer(id) + if err != nil { + return id, err + } + if !exists { + return id, err + } + + // Re-create a container struct for removal purposes + c := new(Container) + c.config, err = r.state.GetContainerConfig(id) + if err != nil { + return id, fmt.Errorf("failed to retrieve config for ctr ID %q: %w", id, err) + } + c.state = new(ContainerState) + + // We need to lock the pod before we lock the container. + // To avoid races around removing a container and the pod it is in. + // Don't need to do this in pod removal case - we're evicting the entire + // pod. + var pod *Pod + if c.config.Pod != "" { + pod, err = r.state.Pod(c.config.Pod) + if err != nil { + return id, fmt.Errorf("container %s is in pod %s, but pod cannot be retrieved: %w", c.ID(), pod.ID(), err) + } + + // Lock the pod while we're removing container + pod.lock.Lock() + defer pod.lock.Unlock() + if err := pod.updatePod(); err != nil { + return id, err + } + + infraID, err := pod.infraContainerID() + if err != nil { + return "", err + } + if c.ID() == infraID { + return id, fmt.Errorf("container %s is the infra container of pod %s and cannot be removed without removing the pod", c.ID(), pod.ID()) + } + } + + if c.IsService() { + report, err := c.canStopServiceContainer() + if err != nil { + return id, err + } + if !report.canBeStopped { + return id, fmt.Errorf("container %s is the service container of pod(s) %s and cannot be removed without removing the pod(s)", c.ID(), strings.Join(c.state.Service.Pods, ",")) + } + } + + var cleanupErr error + // Remove the container from the state + if c.config.Pod != "" { + // If we're removing the pod, the container will be evicted + // from the state elsewhere + if err := r.state.RemoveContainerFromPod(pod, c); err != nil { + cleanupErr = err + } + } else { + if err := r.state.RemoveContainer(c); err != nil { + cleanupErr = err + } + } + + // Unmount container mount points + for _, mount := range c.config.Mounts { + Unmount(mount) + } + + // Remove container from c/storage + if err := r.RemoveStorageContainer(id, true); err != nil { + if cleanupErr == nil { + cleanupErr = err + } + } + + if !removeVolume { + return id, cleanupErr + } + + for _, v := range c.config.NamedVolumes { + if volume, err := r.state.Volume(v.Name); err == nil { + if !volume.Anonymous() { + continue + } + if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed { + logrus.Errorf("Cleaning up volume (%s): %v", v.Name, err) + } + } + } + + return id, cleanupErr +} + +// GetContainer retrieves a container by its ID +func (r *Runtime) GetContainer(id string) (*Container, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + return r.state.Container(id) +} + +// HasContainer checks if a container with the given ID is present +func (r *Runtime) HasContainer(id string) (bool, error) { + if !r.valid { + return false, define.ErrRuntimeStopped + } + + return r.state.HasContainer(id) +} + +// LookupContainer looks up a container by its name or a partial ID +// If a partial ID is not unique, an error will be returned +func (r *Runtime) LookupContainer(idOrName string) (*Container, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + return r.state.LookupContainer(idOrName) +} + +// LookupContainerId looks up a container id by its name or a partial ID +// If a partial ID is not unique, an error will be returned +func (r *Runtime) LookupContainerID(idOrName string) (string, error) { + if !r.valid { + return "", define.ErrRuntimeStopped + } + return r.state.LookupContainerID(idOrName) +} + +// GetContainers retrieves all containers from the state. +// If `loadState` is set, the containers' state will be loaded as well. +// Filters can be provided which will determine what containers are included in +// the output. Multiple filters are handled by ANDing their output, so only +// containers matching all filters are returned +func (r *Runtime) GetContainers(loadState bool, filters ...ContainerFilter) ([]*Container, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + ctrs, err := r.state.AllContainers(loadState) + if err != nil { + return nil, err + } + + ctrsFiltered := applyContainersFilters(ctrs, filters...) + + return ctrsFiltered, nil +} + +// Applies container filters on bunch of containers +func applyContainersFilters(containers []*Container, filters ...ContainerFilter) []*Container { + ctrsFiltered := make([]*Container, 0, len(containers)) + + for _, ctr := range containers { + include := true + for _, filter := range filters { + if filter == nil { + continue + } + include = include && filter(ctr) + } + + if include { + ctrsFiltered = append(ctrsFiltered, ctr) + } + } + + return ctrsFiltered +} + +// GetAllContainers is a helper function for GetContainers +func (r *Runtime) GetAllContainers() ([]*Container, error) { + return r.state.AllContainers(false) +} + +// GetRunningContainers is a helper function for GetContainers +func (r *Runtime) GetRunningContainers() ([]*Container, error) { + running := func(c *Container) bool { + state, _ := c.State() + return state == define.ContainerStateRunning + } + return r.GetContainers(false, running) +} + +// GetContainersByList is a helper function for GetContainers +// which takes a []string of container IDs or names +func (r *Runtime) GetContainersByList(containers []string) ([]*Container, error) { + ctrs := make([]*Container, 0, len(containers)) + for _, inputContainer := range containers { + ctr, err := r.LookupContainer(inputContainer) + if err != nil { + return ctrs, fmt.Errorf("unable to look up container %s: %w", inputContainer, err) + } + ctrs = append(ctrs, ctr) + } + return ctrs, nil +} + +// GetLatestContainer returns a container object of the latest created container. +func (r *Runtime) GetLatestContainer() (*Container, error) { + lastCreatedIndex := -1 + var lastCreatedTime time.Time + ctrs, err := r.GetAllContainers() + if err != nil { + return nil, fmt.Errorf("unable to find latest container: %w", err) + } + if len(ctrs) == 0 { + return nil, define.ErrNoSuchCtr + } + for containerIndex, ctr := range ctrs { + createdTime := ctr.config.CreatedTime + if createdTime.After(lastCreatedTime) { + lastCreatedTime = createdTime + lastCreatedIndex = containerIndex + } + } + return ctrs[lastCreatedIndex], nil +} + +// GetExecSessionContainer gets the container that a given exec session ID is +// attached to. +func (r *Runtime) GetExecSessionContainer(id string) (*Container, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + ctrID, err := r.state.GetExecSession(id) + if err != nil { + return nil, err + } + + return r.state.Container(ctrID) +} + +// PruneContainers removes stopped and exited containers from localstorage. A set of optional filters +// can be provided to be more granular. +func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) ([]*reports.PruneReport, error) { + preports := make([]*reports.PruneReport, 0) + // We add getting the exited and stopped containers via a filter + containerStateFilter := func(c *Container) bool { + if c.PodID() != "" { + return false + } + state, err := c.State() + if err != nil { + logrus.Error(err) + return false + } + if state == define.ContainerStateStopped || state == define.ContainerStateExited || + state == define.ContainerStateCreated || state == define.ContainerStateConfigured { + return true + } + return false + } + filterFuncs = append(filterFuncs, containerStateFilter) + delContainers, err := r.GetContainers(false, filterFuncs...) + if err != nil { + return nil, err + } + for _, c := range delContainers { + report := new(reports.PruneReport) + report.Id = c.ID() + report.Err = nil + report.Size = 0 + size, err := c.RWSize() + if err != nil { + report.Err = err + preports = append(preports, report) + continue + } + var time *uint + err = r.RemoveContainer(context.Background(), c, false, false, time) + if err != nil { + report.Err = err + } else { + report.Size = (uint64)(size) + } + preports = append(preports, report) + } + return preports, nil +} + +// MountStorageContainer mounts the storage container's root filesystem +func (r *Runtime) MountStorageContainer(id string) (string, error) { + if _, err := r.GetContainer(id); err == nil { + return "", fmt.Errorf("ctr %s is a libpod container: %w", id, define.ErrCtrExists) + } + container, err := r.store.Container(id) + if err != nil { + return "", err + } + mountPoint, err := r.store.Mount(container.ID, "") + if err != nil { + return "", fmt.Errorf("mounting storage for container %s: %w", id, err) + } + return mountPoint, nil +} + +// UnmountStorageContainer unmounts the storage container's root filesystem +func (r *Runtime) UnmountStorageContainer(id string, force bool) (bool, error) { + if _, err := r.GetContainer(id); err == nil { + return false, fmt.Errorf("ctr %s is a libpod container: %w", id, define.ErrCtrExists) + } + container, err := r.store.Container(id) + if err != nil { + return false, err + } + return r.store.Unmount(container.ID, force) +} + +// MountedStorageContainer returns whether a storage container is mounted +// along with the mount path +func (r *Runtime) IsStorageContainerMounted(id string) (bool, string, error) { + var path string + if _, err := r.GetContainer(id); err == nil { + return false, "", fmt.Errorf("ctr %s is a libpod container: %w", id, define.ErrCtrExists) + } + + mountCnt, err := r.storageService.MountedContainerImage(id) + if err != nil { + return false, "", fmt.Errorf("get mount count of container: %w", err) + } + mounted := mountCnt > 0 + if mounted { + path, err = r.storageService.GetMountpoint(id) + if err != nil { + return false, "", fmt.Errorf("get container mount point: %w", err) + } + } + return mounted, path, nil +} + +// StorageContainers returns a list of containers from containers/storage that +// are not currently known to Podman. +func (r *Runtime) StorageContainers() ([]storage.Container, error) { + if r.store == nil { + return nil, define.ErrStoreNotInitialized + } + + storeContainers, err := r.store.Containers() + if err != nil { + return nil, fmt.Errorf("reading list of all storage containers: %w", err) + } + retCtrs := []storage.Container{} + for _, container := range storeContainers { + exists, err := r.state.HasContainer(container.ID) + if err != nil && err != define.ErrNoSuchCtr { + return nil, fmt.Errorf("failed to check if %s container exists in database: %w", container.ID, err) + } + if exists { + continue + } + retCtrs = append(retCtrs, container) + } + + return retCtrs, nil +} + +func (r *Runtime) IsBuildahContainer(id string) (bool, error) { + return buildah.IsContainer(id, r.store) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_ctr_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/runtime_ctr_freebsd.go new file mode 100644 index 00000000..0f6b9393 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_ctr_freebsd.go @@ -0,0 +1,7 @@ +//go:build !remote + +package libpod + +const ( + useDevShm = false +) diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_ctr_linux.go b/vendor/github.com/containers/podman/v5/libpod/runtime_ctr_linux.go new file mode 100644 index 00000000..d29b7351 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_ctr_linux.go @@ -0,0 +1,7 @@ +//go:build !remote + +package libpod + +const ( + useDevShm = true +) diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/runtime_freebsd.go new file mode 100644 index 00000000..35ba4700 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_freebsd.go @@ -0,0 +1,10 @@ +//go:build !remote + +package libpod + +func checkCgroups2UnifiedMode(_ *Runtime) { +} + +func (r *Runtime) checkBootID(_ string) error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_img.go b/vendor/github.com/containers/podman/v5/libpod/runtime_img.go new file mode 100644 index 00000000..98474ee9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_img.go @@ -0,0 +1,129 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + + buildahDefine "github.com/containers/buildah/define" + "github.com/containers/buildah/imagebuildah" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/sirupsen/logrus" + "go.podman.io/common/libimage" + "go.podman.io/image/v5/docker/reference" +) + +// Runtime API + +// RemoveContainersForImageCallback returns a callback that can be used in +// `libimage`. When forcefully removing images, containers using the image +// should be removed as well unless the request comes from the Docker compat API. +// The callback allows for more graceful removal as we can use the libpod-internal +// removal logic. +func (r *Runtime) RemoveContainersForImageCallback(ctx context.Context, force bool) libimage.RemoveContainerFunc { + return func(imageID string) error { + if !r.valid { + return define.ErrRuntimeStopped + } + ctrs, err := r.state.AllContainers(false) + if err != nil { + return err + } + for _, ctr := range ctrs { + if ctr.config.RootfsImageID != imageID { + continue + } + var timeout *uint + if ctr.config.IsInfra { + pod, err := r.state.Pod(ctr.config.Pod) + if err != nil { + return fmt.Errorf("container %s is in pod %s, but pod cannot be retrieved: %w", ctr.ID(), ctr.config.Pod, err) + } + if _, err := r.removePod(ctx, pod, true, force, timeout); err != nil { + return fmt.Errorf("removing image %s: container %s using image could not be removed: %w", imageID, ctr.ID(), err) + } + } else { + opts := ctrRmOpts{ + Force: force, + Timeout: timeout, + } + if _, _, err := r.removeContainer(ctx, ctr, opts); err != nil { + return fmt.Errorf("removing image %s: container %s using image could not be removed: %w", imageID, ctr.ID(), err) + } + } + } + + // Need to handle volumes with the image driver + vols, err := r.state.AllVolumes() + if err != nil { + return err + } + for _, vol := range vols { + if vol.config.Driver != define.VolumeDriverImage || vol.config.StorageImageID != imageID { + continue + } + // Do a force removal of the volume, and all containers + // using it. + if err := r.RemoveVolume(ctx, vol, true, nil); err != nil { + return fmt.Errorf("removing image %s: volume %s backed by image could not be removed: %w", imageID, vol.Name(), err) + } + } + + // Note that `libimage` will take care of removing any leftover + // containers from the storage. + return nil + } +} + +// IsExternalContainerCallback returns a callback that be used in `libimage` to +// figure out whether a given container is an external one. A container is +// considered external if it is not present in libpod's database. +func (r *Runtime) IsExternalContainerCallback(_ context.Context) libimage.IsExternalContainerFunc { + // NOTE: pruning external containers is subject to race conditions + // (e.g., when a container gets removed). To address this and similar + // races, pruning had to happen inside c/storage. Containers has to be + // labeled with "podman/libpod" along with callbacks similar to + // libimage. + return func(idOrName string) (bool, error) { + _, err := r.LookupContainer(idOrName) + if err == nil { + return false, nil + } + if errors.Is(err, define.ErrNoSuchCtr) { + return true, nil + } + isVol, err := r.state.ContainerIDIsVolume(idOrName) + if err == nil && !isVol { + return true, nil + } + return false, nil + } +} + +// newImageBuildCompleteEvent creates a new event based on completion of a built image +func (r *Runtime) newImageBuildCompleteEvent(idOrName string) { + e := events.NewEvent(events.Build) + e.Type = events.Image + e.Name = idOrName + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("Unable to write build event: %q", err) + } +} + +// Build adds the runtime to the imagebuildah call +func (r *Runtime) Build(ctx context.Context, options buildahDefine.BuildOptions, dockerfiles ...string) (string, reference.Canonical, error) { + if options.Runtime == "" { + options.Runtime = r.GetOCIRuntimePath() + } + options.NoPivotRoot = r.config.Engine.NoPivotRoot + + // share the network interface between podman and buildah + options.NetworkInterface = r.network + id, ref, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...) + // Write event for build completion + r.newImageBuildCompleteEvent(id) + return id, ref, err +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_linux.go b/vendor/github.com/containers/podman/v5/libpod/runtime_linux.go new file mode 100644 index 00000000..07f9db5e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_linux.go @@ -0,0 +1,67 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "fmt" + "os" + + "golang.org/x/sys/unix" + + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/systemd" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/cgroups" +) + +func checkCgroups2UnifiedMode(runtime *Runtime) { + unified, _ := cgroups.IsCgroup2UnifiedMode() + // DELETE ON RHEL9 + if !unified { + _, ok := os.LookupEnv("PODMAN_IGNORE_CGROUPSV1_WARNING") + if !ok { + logrus.Warn("Using cgroups-v1 which is deprecated in favor of cgroups-v2 with Podman v5 and will be removed in a future version. Set environment variable `PODMAN_IGNORE_CGROUPSV1_WARNING` to hide this warning.") + } + } + // DELETE ON RHEL9 + + if unified && rootless.IsRootless() && !systemd.IsSystemdSessionValid(rootless.GetRootlessUID()) { + // If user is rootless and XDG_RUNTIME_DIR is found, podman will not proceed with /tmp directory + // it will try to use existing XDG_RUNTIME_DIR + // if current user has no write access to XDG_RUNTIME_DIR we will fail later + if err := unix.Access(runtime.storageConfig.RunRoot, unix.W_OK); err != nil { + msg := fmt.Sprintf("RunRoot is pointing to a path (%s) which is not writable. Most likely podman will fail.", runtime.storageConfig.RunRoot) + if errors.Is(err, os.ErrNotExist) { + // if dir does not exist, try to create it + if err := os.MkdirAll(runtime.storageConfig.RunRoot, 0o700); err != nil { + logrus.Warn(msg) + } + } else { + logrus.Warnf("%s: %v", msg, err) + } + } + } +} + +// Check the current boot ID against the ID cached in the runtime alive file. +func (r *Runtime) checkBootID(runtimeAliveFile string) error { + systemBootID, err := os.ReadFile("/proc/sys/kernel/random/boot_id") + if err == nil { + podmanBootID, err := os.ReadFile(runtimeAliveFile) + if err != nil { + return fmt.Errorf("reading boot ID from runtime alive file: %w", err) + } + if len(podmanBootID) != 0 { + if string(systemBootID) != string(podmanBootID) { + return fmt.Errorf("current system boot ID differs from cached boot ID; an unhandled reboot has occurred. Please delete directories %q and %q and re-run Podman", r.storageConfig.RunRoot, r.config.Engine.TmpDir) + } + } else { + // Write the current boot ID to the alive file. + if err := os.WriteFile(runtimeAliveFile, systemBootID, 0o644); err != nil { + return fmt.Errorf("writing boot ID to runtime alive file: %w", err) + } + } + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_migrate.go b/vendor/github.com/containers/podman/v5/libpod/runtime_migrate.go new file mode 100644 index 00000000..6b2f9218 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_migrate.go @@ -0,0 +1,88 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "path/filepath" + + "github.com/containers/podman/v5/libpod/define" + "github.com/sirupsen/logrus" +) + +// Migrate stops the rootless pause process and performs any necessary database +// migrations that are required. It can also migrate all containers to a new OCI +// runtime, if requested. +func (r *Runtime) Migrate(newRuntime string) error { + // Acquire the alive lock and hold it. + // Ensures that we don't let other Podman commands run while we are + // rewriting things in the DB. + aliveLock, err := r.getRuntimeAliveLock() + if err != nil { + return fmt.Errorf("retrieving alive lock: %w", err) + } + aliveLock.Lock() + defer aliveLock.Unlock() + + if !r.valid { + return define.ErrRuntimeStopped + } + + runningContainers, err := r.GetRunningContainers() + if err != nil { + return err + } + + allCtrs, err := r.state.AllContainers(false) + if err != nil { + return err + } + + logrus.Infof("Stopping all containers") + for _, ctr := range runningContainers { + fmt.Printf("stopped %s\n", ctr.ID()) + if err := ctr.Stop(); err != nil { + return fmt.Errorf("cannot stop container %s: %w", ctr.ID(), err) + } + } + + // Did the user request a new runtime? + runtimeChangeRequested := newRuntime != "" + var requestedRuntime OCIRuntime + if runtimeChangeRequested { + runtime, exists := r.ociRuntimes[newRuntime] + if !exists { + return fmt.Errorf("change to runtime %q requested but no such runtime is defined: %w", newRuntime, define.ErrInvalidArg) + } + requestedRuntime = runtime + } + + for _, ctr := range allCtrs { + needsWrite := false + + // Reset pause process location + oldLocation := filepath.Join(ctr.state.RunDir, "conmon.pid") + if ctr.config.ConmonPidFile == oldLocation { + logrus.Infof("Changing conmon PID file for %s", ctr.ID()) + ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid") + needsWrite = true + } + + // Reset runtime + if runtimeChangeRequested && ctr.config.OCIRuntime != newRuntime { + logrus.Infof("Resetting container %s runtime to runtime %s", ctr.ID(), newRuntime) + ctr.config.OCIRuntime = newRuntime + ctr.ociRuntime = requestedRuntime + + needsWrite = true + } + + if needsWrite { + if err := r.state.RewriteContainerConfig(ctr, ctr.config); err != nil { + return fmt.Errorf("rewriting config for container %s: %w", ctr.ID(), err) + } + } + } + + return r.stopPauseProcess() +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_migrate_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/runtime_migrate_freebsd.go new file mode 100644 index 00000000..47c94a4d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_migrate_freebsd.go @@ -0,0 +1,7 @@ +//go:build !remote + +package libpod + +func (r *Runtime) stopPauseProcess() error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_migrate_linux.go b/vendor/github.com/containers/podman/v5/libpod/runtime_migrate_linux.go new file mode 100644 index 00000000..162d72ed --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_migrate_linux.go @@ -0,0 +1,40 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "os" + "strconv" + "syscall" + + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/util" +) + +func (r *Runtime) stopPauseProcess() error { + if rootless.IsRootless() { + pausePidPath, err := util.GetRootlessPauseProcessPidPath() + if err != nil { + return fmt.Errorf("could not get pause process pid file path: %w", err) + } + data, err := os.ReadFile(pausePidPath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf("cannot read pause process pid file: %w", err) + } + pausePid, err := strconv.Atoi(string(data)) + if err != nil { + return fmt.Errorf("cannot parse pause pid file %s: %w", pausePidPath, err) + } + if err := os.Remove(pausePidPath); err != nil { + return fmt.Errorf("cannot delete pause pid file %s: %w", pausePidPath, err) + } + if err := syscall.Kill(pausePid, syscall.SIGKILL); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_migrate_unsupported.go b/vendor/github.com/containers/podman/v5/libpod/runtime_migrate_unsupported.go new file mode 100644 index 00000000..6355ab4e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_migrate_unsupported.go @@ -0,0 +1,11 @@ +//go:build !remote && !linux && !freebsd + +package libpod + +import ( + "errors" +) + +func (r *Runtime) stopPauseProcess() error { + return errors.New("not implemented (*Runtime) stopPauseProcess") +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_pod.go b/vendor/github.com/containers/podman/v5/libpod/runtime_pod.go new file mode 100644 index 00000000..28b6619a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_pod.go @@ -0,0 +1,184 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + "slices" + "time" + + "github.com/containers/podman/v5/libpod/define" +) + +// Contains the public Runtime API for pods + +// A PodCreateOption is a functional option which alters the Pod created by +// NewPod +type PodCreateOption func(*Pod) error + +// PodFilter is a function to determine whether a pod is included in command +// output. Pods to be outputted are tested using the function. A true return +// will include the pod, a false return will exclude it. +type PodFilter func(*Pod) bool + +// RemovePod removes a pod +// If removeCtrs is specified, containers will be removed +// Otherwise, a pod that is not empty will return an error and not be removed +// If force is specified with removeCtrs, all containers will be stopped before +// being removed +// Otherwise, the pod will not be removed if any containers are running +func (r *Runtime) RemovePod(ctx context.Context, p *Pod, removeCtrs, force bool, timeout *uint) (map[string]error, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + if !p.valid { + if ok, _ := r.state.HasPod(p.ID()); !ok { + // Pod probably already removed + // Or was never in the runtime to begin with + return make(map[string]error), nil + } + } + + p.lock.Lock() + defer p.lock.Unlock() + + return r.removePod(ctx, p, removeCtrs, force, timeout) +} + +// GetPod retrieves a pod by its ID +func (r *Runtime) GetPod(id string) (*Pod, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + return r.state.Pod(id) +} + +// HasPod checks to see if a pod with the given ID exists +func (r *Runtime) HasPod(id string) (bool, error) { + if !r.valid { + return false, define.ErrRuntimeStopped + } + + return r.state.HasPod(id) +} + +// LookupPod retrieves a pod by its name or a partial ID +// If a partial ID is not unique, an error will be returned +func (r *Runtime) LookupPod(idOrName string) (*Pod, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + return r.state.LookupPod(idOrName) +} + +// Pods retrieves all pods +// Filters can be provided which will determine which pods are included in the +// output. Multiple filters are handled by ANDing their output, so only pods +// matching all filters are returned +func (r *Runtime) Pods(filters ...PodFilter) ([]*Pod, error) { + pods, err := r.GetAllPods() + if err != nil { + return nil, err + } + podsFiltered := make([]*Pod, 0, len(pods)) + for _, pod := range pods { + include := true + for _, filter := range filters { + include = include && filter(pod) + } + + if include { + podsFiltered = append(podsFiltered, pod) + } + } + + return podsFiltered, nil +} + +// GetAllPods retrieves all pods +func (r *Runtime) GetAllPods() ([]*Pod, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + return r.state.AllPods() +} + +// GetLatestPod returns a pod object of the latest created pod. +func (r *Runtime) GetLatestPod() (*Pod, error) { + lastCreatedIndex := -1 + var lastCreatedTime time.Time + pods, err := r.GetAllPods() + if err != nil { + return nil, fmt.Errorf("unable to get all pods: %w", err) + } + if len(pods) == 0 { + return nil, define.ErrNoSuchPod + } + for podIndex, pod := range pods { + createdTime := pod.config.CreatedTime + if createdTime.After(lastCreatedTime) { + lastCreatedTime = createdTime + lastCreatedIndex = podIndex + } + } + return pods[lastCreatedIndex], nil +} + +// GetRunningPods returns an array of running pods +func (r *Runtime) GetRunningPods() ([]*Pod, error) { + var ( + pods []string + runningPods []*Pod + ) + if !r.valid { + return nil, define.ErrRuntimeStopped + } + containers, err := r.GetRunningContainers() + if err != nil { + return nil, err + } + // Assemble running pods + for _, c := range containers { + if !slices.Contains(pods, c.PodID()) { + pods = append(pods, c.PodID()) + pod, err := r.GetPod(c.PodID()) + if err != nil { + if errors.Is(err, define.ErrPodRemoved) || errors.Is(err, define.ErrNoSuchPod) { + continue + } + return nil, err + } + runningPods = append(runningPods, pod) + } + } + return runningPods, nil +} + +// PrunePods removes unused pods and their containers from local storage. +func (r *Runtime) PrunePods(_ context.Context) (map[string]error, error) { + response := make(map[string]error) + states := []string{define.PodStateStopped, define.PodStateExited} + filterFunc := func(p *Pod) bool { + state, _ := p.GetPodStatus() + return slices.Contains(states, state) + } + pods, err := r.Pods(filterFunc) + if err != nil { + return nil, err + } + if len(pods) < 1 { + return response, nil + } + for _, pod := range pods { + var timeout *uint + _, err := r.removePod(context.TODO(), pod, true, false, timeout) + response[pod.ID()] = err + } + return response, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_pod_common.go b/vendor/github.com/containers/podman/v5/libpod/runtime_pod_common.go new file mode 100644 index 00000000..36e21c3f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_pod_common.go @@ -0,0 +1,310 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "context" + "errors" + "fmt" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/pkg/specgen" + "github.com/hashicorp/go-multierror" + "github.com/sirupsen/logrus" +) + +// NewPod makes a new, empty pod +func (r *Runtime) NewPod(_ context.Context, p specgen.PodSpecGenerator, options ...PodCreateOption) (_ *Pod, deferredErr error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + pod := newPod(r) + + // Set default namespace to runtime's namespace + // Do so before options run so they can override it + if r.config.Engine.Namespace != "" { + pod.config.Namespace = r.config.Engine.Namespace + } + + for _, option := range options { + if err := option(pod); err != nil { + return nil, fmt.Errorf("running pod create option: %w", err) + } + } + + // Allocate a lock for the pod + lock, err := r.lockManager.AllocateLock() + if err != nil { + return nil, fmt.Errorf("allocating lock for new pod: %w", err) + } + pod.lock = lock + pod.config.LockID = pod.lock.ID() + + defer func() { + if deferredErr != nil { + if err := pod.lock.Free(); err != nil { + logrus.Errorf("Freeing pod lock after failed creation: %v", err) + } + } + }() + + pod.valid = true + + parentCgroup, err := r.platformMakePod(pod, p.ResourceLimits) + if err != nil { + return nil, err + } + if p.InfraContainerSpec != nil { + p.InfraContainerSpec.CgroupParent = parentCgroup + } + + if !pod.HasInfraContainer() && pod.SharesNamespaces() { + return nil, errors.New("Pods must have an infra container to share namespaces") + } + if pod.HasInfraContainer() && !pod.SharesNamespaces() { + logrus.Infof("Pod has an infra container, but shares no namespaces") + } + + // Unless the user has specified a name, use a randomly generated one. + // Note that name conflicts may occur (see #11735), so we need to loop. + generateName := pod.config.Name == "" + var addPodErr error + for { + if generateName { + name, err := r.generateName() + if err != nil { + return nil, err + } + pod.config.Name = name + } + + if p.InfraContainerSpec != nil && p.InfraContainerSpec.Hostname == "" { + p.InfraContainerSpec.Hostname = pod.config.Name + } + if addPodErr = r.state.AddPod(pod); addPodErr == nil { + return pod, nil + } + if !generateName || (!errors.Is(addPodErr, define.ErrPodExists) && !errors.Is(addPodErr, define.ErrCtrExists)) { + break + } + } + return nil, fmt.Errorf("adding pod to state: %w", addPodErr) +} + +// AddInfra adds the created infra container to the pod state +func (r *Runtime) AddInfra(_ context.Context, pod *Pod, infraCtr *Container) (*Pod, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + pod.state.InfraContainerID = infraCtr.ID() + if err := pod.save(); err != nil { + return nil, err + } + pod.newPodEvent(events.Create) + return pod, nil +} + +// SavePod is a helper function to save the pod state from outside of libpod +func (r *Runtime) SavePod(pod *Pod) error { + if !r.valid { + return define.ErrRuntimeStopped + } + if err := pod.save(); err != nil { + return err + } + pod.newPodEvent(events.Create) + return nil +} + +// DO NOT USE THIS FUNCTION DIRECTLY. Use removePod(), below. It will call +// removeMalformedPod() if necessary. +func (r *Runtime) removeMalformedPod(ctx context.Context, p *Pod, ctrs []*Container, force bool, timeout *uint, ctrNamedVolumes map[string]*ContainerNamedVolume) (map[string]error, error) { + removedCtrs := make(map[string]error) + errored := false + for _, ctr := range ctrs { + err := func() error { + ctrLock := ctr.lock + ctrLock.Lock() + defer func() { + ctrLock.Unlock() + }() + + if err := ctr.syncContainer(); err != nil { + return err + } + + for _, vol := range ctr.config.NamedVolumes { + ctrNamedVolumes[vol.Name] = vol + } + + opts := ctrRmOpts{ + Force: force, + RemovePod: true, + IgnoreDeps: true, + Timeout: timeout, + } + _, _, err := r.removeContainer(ctx, ctr, opts) + return err + }() + removedCtrs[ctr.ID()] = err + if err != nil { + errored = true + } + } + + // So, technically, no containers have been *removed*. + // They're still in the DB. + // So just return nil for removed containers. Squash all the errors into + // a multierror so we don't lose them. + if errored { + var allErrors error + for ctr, err := range removedCtrs { + if err != nil { + allErrors = multierror.Append(allErrors, fmt.Errorf("removing container %s: %w", ctr, err)) + } + } + return nil, fmt.Errorf("no containers were removed due to the following errors: %w", allErrors) + } + + // Clear infra container ID before we remove the infra container. + // There is a potential issue if we don't do that, and removal is + // interrupted between RemoveAllContainers() below and the pod's removal + // later - we end up with a reference to a nonexistent infra container. + p.state.InfraContainerID = "" + if err := p.save(); err != nil { + return nil, err + } + + // Remove all containers in the pod from the state. + if err := r.state.RemovePodContainers(p); err != nil { + // If this fails, there isn't much more we can do. + // The containers in the pod are unusable, but they still exist, + // so pod removal will fail. + return nil, err + } + + return removedCtrs, nil +} + +func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool, timeout *uint) (map[string]error, error) { + removedCtrs := make(map[string]error) + + if err := p.updatePod(); err != nil { + return nil, err + } + + ctrs, err := r.state.PodContainers(p) + if err != nil { + return nil, err + } + numCtrs := len(ctrs) + + // If the only running container in the pod is the pause container, remove the pod and container unconditionally. + pauseCtrID := p.state.InfraContainerID + if numCtrs == 1 && ctrs[0].ID() == pauseCtrID { + removeCtrs = true + force = true + } + if !removeCtrs && numCtrs > 0 { + return nil, fmt.Errorf("pod %s contains containers and cannot be removed: %w", p.ID(), define.ErrCtrExists) + } + + var ( + removalErr error + ctrNamedVolumes map[string]*ContainerNamedVolume + ) + + // Build a graph of all containers in the pod. + graph, err := BuildContainerGraph(ctrs) + if err != nil { + // We have to allow the pod to be removed. + // But let's only do it if force is set. + if !force { + return nil, fmt.Errorf("cannot create container graph for pod %s: %w", p.ID(), err) + } + + removalErr = fmt.Errorf("creating container graph for pod %s failed, fell back to loop removal: %w", p.ID(), err) + + removedCtrs, err = r.removeMalformedPod(ctx, p, ctrs, force, timeout, ctrNamedVolumes) + if err != nil { + logrus.Errorf("Error creating container graph for pod %s: %v. Falling back to loop removal.", p.ID(), err) + return removedCtrs, err + } + } else { + var ( + ctrErrors map[string]error + ctrsVisited map[string]bool + ) + + ctrNamedVolumes, ctrsVisited, ctrErrors, err = removeContainerGraph(ctx, graph, p, timeout, force) + if err != nil { + return nil, err + } + + // Finalize the removed containers list + for ctr := range ctrsVisited { + removedCtrs[ctr] = ctrErrors[ctr] + } + + if len(ctrErrors) > 0 { + return removedCtrs, fmt.Errorf("not all containers could be removed from pod %s: %w", p.ID(), define.ErrRemovingCtrs) + } + } + + for volName := range ctrNamedVolumes { + volume, err := r.state.Volume(volName) + if err != nil && !errors.Is(err, define.ErrNoSuchVolume) { + logrus.Errorf("Retrieving volume %s: %v", volName, err) + continue + } + if !volume.Anonymous() { + continue + } + if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil { + // If the anonymous volume is still being used that means it was likely transferred + // to another container via --volumes-from so no need to log this as real error. + if errors.Is(err, define.ErrNoSuchVolume) || errors.Is(err, define.ErrVolumeRemoved) || errors.Is(err, define.ErrVolumeBeingUsed) { + continue + } + logrus.Errorf("Removing volume %s: %v", volName, err) + } + } + + // Remove pod cgroup + if err := p.removePodCgroup(); err != nil { + if removalErr == nil { + removalErr = fmt.Errorf("removing pod %s cgroup: %w", p.ID(), err) + } else { + logrus.Errorf("Deleting pod %s cgroup %s: %v", p.ID(), p.state.CgroupPath, err) + } + } + + if err := p.maybeRemoveServiceContainer(); err != nil { + return removedCtrs, err + } + + // Remove pod from state + if err := r.state.RemovePod(p); err != nil { + if removalErr != nil { + logrus.Errorf("%v", removalErr) + } + return removedCtrs, err + } + + // Mark pod invalid + p.valid = false + p.newPodEvent(events.Remove) + + // Deallocate the pod lock + if err := p.lock.Free(); err != nil { + if removalErr == nil { + removalErr = fmt.Errorf("freeing pod %s lock: %w", p.ID(), err) + } else { + logrus.Errorf("Freeing pod %s lock: %v", p.ID(), err) + } + } + + return removedCtrs, removalErr +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_pod_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/runtime_pod_freebsd.go new file mode 100644 index 00000000..68842065 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_pod_freebsd.go @@ -0,0 +1,15 @@ +//go:build !remote + +package libpod + +import ( + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +func (r *Runtime) platformMakePod(_ *Pod, _ *spec.LinuxResources) (string, error) { + return "", nil +} + +func (p *Pod) removePodCgroup() error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_pod_linux.go b/vendor/github.com/containers/podman/v5/libpod/runtime_pod_linux.go new file mode 100644 index 00000000..0dec268d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_pod_linux.go @@ -0,0 +1,149 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "path" + "path/filepath" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/common/pkg/config" +) + +func (r *Runtime) platformMakePod(pod *Pod, resourceLimits *spec.LinuxResources) (string, error) { + cgroupParent := "" + // Check Cgroup parent sanity, and set it if it was not set + if r.config.Cgroups() != "disabled" { + switch r.config.Engine.CgroupManager { + case config.CgroupfsCgroupsManager: + canUseCgroup := !rootless.IsRootless() || isRootlessCgroupSet(pod.config.CgroupParent) + if canUseCgroup { + // need to actually create parent here + if pod.config.CgroupParent == "" { + pod.config.CgroupParent = CgroupfsDefaultCgroupParent + } else if strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { + return "", fmt.Errorf("systemd slice received as cgroup parent when using cgroupfs: %w", define.ErrInvalidArg) + } + // If we are set to use pod cgroups, set the cgroup parent that + // all containers in the pod will share + if pod.config.UsePodCgroup { + pod.state.CgroupPath = filepath.Join(pod.config.CgroupParent, pod.ID()) + cgroupParent = pod.state.CgroupPath + // cgroupfs + rootless = permission denied when creating the cgroup. + if !rootless.IsRootless() { + res, err := GetLimits(resourceLimits) + if err != nil { + return "", err + } + res.SkipDevices = true + // Need to both create and update the cgroup + // rather than create a new path in c/common for pod cgroup creation + // just create as if it is a ctr and then update figures out that we need to + // populate the resource limits on the pod level + cgc, err := cgroups.New(pod.state.CgroupPath, &res) + if err != nil { + return "", err + } + err = cgc.Update(&res) + if err != nil { + return "", err + } + } + } + } + case config.SystemdCgroupsManager: + if pod.config.CgroupParent == "" { + if rootless.IsRootless() { + pod.config.CgroupParent = SystemdDefaultRootlessCgroupParent + } else { + pod.config.CgroupParent = SystemdDefaultCgroupParent + } + } else if len(pod.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { + return "", fmt.Errorf("did not receive systemd slice as cgroup parent when using systemd to manage cgroups: %w", define.ErrInvalidArg) + } + // If we are set to use pod cgroups, set the cgroup parent that + // all containers in the pod will share + if pod.config.UsePodCgroup { + cgroupPath, err := systemdSliceFromPath(pod.config.CgroupParent, fmt.Sprintf("libpod_pod_%s", pod.ID()), resourceLimits) + if err != nil { + return "", fmt.Errorf("unable to create pod cgroup for pod %s: %w", pod.ID(), err) + } + pod.state.CgroupPath = cgroupPath + cgroupParent = pod.state.CgroupPath + } + default: + return "", fmt.Errorf("unsupported Cgroup manager: %s - cannot validate cgroup parent: %w", r.config.Engine.CgroupManager, define.ErrInvalidArg) + } + } + + if pod.config.UsePodCgroup { + logrus.Debugf("Got pod cgroup as %s", pod.state.CgroupPath) + } + + return cgroupParent, nil +} + +func (p *Pod) removePodCgroup() error { + // Remove pod cgroup, if present + if p.state.CgroupPath == "" { + return nil + } + logrus.Debugf("Removing pod cgroup %s", p.state.CgroupPath) + + cgroup, err := cgroups.GetOwnCgroup() + if err != nil { + return err + } + + // if we are trying to delete a cgroup that is our ancestor, we need to move the + // current process out of it before the cgroup is destroyed. + if isSubDir(cgroup, string(filepath.Separator)+p.state.CgroupPath) { + parent := path.Dir(p.state.CgroupPath) + if err := cgroups.MoveUnderCgroup(parent, "cleanup", nil); err != nil { + return err + } + } + + switch p.runtime.config.Engine.CgroupManager { + case config.SystemdCgroupsManager: + if err := deleteSystemdCgroup(p.state.CgroupPath, p.ResourceLim()); err != nil { + return fmt.Errorf("removing pod %s cgroup: %w", p.ID(), err) + } + case config.CgroupfsCgroupsManager: + // Delete the cgroupfs cgroup + // Make sure the conmon cgroup is deleted first + // Since the pod is almost gone, don't bother failing + // hard - instead, just log errors. + conmonCgroupPath := filepath.Join(p.state.CgroupPath, "conmon") + conmonCgroup, err := cgroups.Load(conmonCgroupPath) + if err != nil && err != cgroups.ErrCgroupDeleted && err != cgroups.ErrCgroupV1Rootless { + return fmt.Errorf("retrieving pod %s conmon cgroup: %w", p.ID(), err) + } + if err == nil { + if err = conmonCgroup.Delete(); err != nil { + return fmt.Errorf("removing pod %s conmon cgroup: %w", p.ID(), err) + } + } + cgroup, err := cgroups.Load(p.state.CgroupPath) + if err != nil && err != cgroups.ErrCgroupDeleted && err != cgroups.ErrCgroupV1Rootless { + return fmt.Errorf("retrieving pod %s cgroup: %w", p.ID(), err) + } + if err == nil { + if err := cgroup.Delete(); err != nil { + return fmt.Errorf("removing pod %s cgroup: %w", p.ID(), err) + } + } + default: + // This should be caught much earlier, but let's still + // keep going so we make sure to evict the pod before + // ending up with an inconsistent state. + return fmt.Errorf("unrecognized cgroup manager %s when removing pod %s cgroups: %w", p.runtime.config.Engine.CgroupManager, p.ID(), define.ErrInternal) + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_pre_go1.20.go b/vendor/github.com/containers/podman/v5/libpod/runtime_pre_go1.20.go new file mode 100644 index 00000000..94195f35 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_pre_go1.20.go @@ -0,0 +1,17 @@ +// In go 1.20 and later, the global RNG is automatically initialized. +// Ref: https://pkg.go.dev/math/rand@go1.20#Seed +//go:build !go1.20 && !remote + +package libpod + +import ( + "math/rand" + "time" +) + +func init() { + // generateName calls namesgenerator.GetRandomName which the + // global RNG from math/rand. Seed it here to make sure we + // don't get the same name every time. + rand.Seed(time.Now().UnixNano()) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_renumber.go b/vendor/github.com/containers/podman/v5/libpod/runtime_renumber.go new file mode 100644 index 00000000..953e8324 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_renumber.go @@ -0,0 +1,96 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" +) + +// RenumberLocks reassigns lock numbers for all containers and pods in the +// state. This should NOT be run while there are other Libpod +func (r *Runtime) RenumberLocks() error { + // TODO: It would be desirable to make it impossible to call this until all + // other libpod sessions are dead. + // Possibly use a read-write file lock, with all non-renumber podmans owning the + // lock as read, renumber attempting to take a write lock? + // The alternative is some sort of session tracking, and I don't know how + // reliable that can be. + + // Acquire the alive lock and hold it. + // Ensures that we don't let other Podman commands run while we are + // changing around lock numbers. + aliveLock, err := r.getRuntimeAliveLock() + if err != nil { + return fmt.Errorf("retrieving alive lock: %w", err) + } + aliveLock.Lock() + defer aliveLock.Unlock() + + if !r.valid { + return define.ErrRuntimeStopped + } + + // Start off by deallocating all locks + if err := r.lockManager.FreeAllLocks(); err != nil { + return err + } + + allCtrs, err := r.state.AllContainers(false) + if err != nil { + return err + } + for _, ctr := range allCtrs { + lock, err := r.lockManager.AllocateLock() + if err != nil { + return fmt.Errorf("allocating lock for container %s: %w", ctr.ID(), err) + } + + ctr.config.LockID = lock.ID() + + // Write the new lock ID + if err := r.state.RewriteContainerConfig(ctr, ctr.config); err != nil { + return err + } + } + allPods, err := r.state.AllPods() + if err != nil { + return err + } + for _, pod := range allPods { + lock, err := r.lockManager.AllocateLock() + if err != nil { + return fmt.Errorf("allocating lock for pod %s: %w", pod.ID(), err) + } + + pod.config.LockID = lock.ID() + + // Write the new lock ID + if err := r.state.RewritePodConfig(pod, pod.config); err != nil { + return err + } + } + allVols, err := r.state.AllVolumes() + if err != nil { + return err + } + for _, vol := range allVols { + lock, err := r.lockManager.AllocateLock() + if err != nil { + return fmt.Errorf("allocating lock for volume %s: %w", vol.Name(), err) + } + + vol.config.LockID = lock.ID() + + // Write the new lock ID + if err := r.state.RewriteVolumeConfig(vol, vol.config); err != nil { + return err + } + } + + r.NewSystemEvent(events.Renumber) + + return r.Shutdown(false) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_volume.go b/vendor/github.com/containers/podman/v5/libpod/runtime_volume.go new file mode 100644 index 00000000..ff04eec3 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_volume.go @@ -0,0 +1,143 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + "github.com/containers/podman/v5/pkg/domain/entities/reports" +) + +// Contains the public Runtime API for volumes + +// A VolumeCreateOption is a functional option which alters the Volume created by +// NewVolume +type VolumeCreateOption func(*Volume) error + +// VolumeFilter is a function to determine whether a volume is included in command +// output. Volumes to be outputted are tested using the function. a true return will +// include the volume, a false return will exclude it. +type VolumeFilter func(*Volume) bool + +// RemoveVolume removes a volumes +func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool, timeout *uint) error { + if !r.valid { + return define.ErrRuntimeStopped + } + + return r.removeVolume(ctx, v, force, timeout, false) +} + +// GetVolume retrieves a volume given its full name. +func (r *Runtime) GetVolume(name string) (*Volume, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + vol, err := r.state.Volume(name) + if err != nil { + return nil, err + } + + return vol, nil +} + +// LookupVolume retrieves a volume by unambiguous partial name. +func (r *Runtime) LookupVolume(name string) (*Volume, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + vol, err := r.state.LookupVolume(name) + if err != nil { + return nil, err + } + + return vol, nil +} + +// HasVolume checks to see if a volume with the given name exists +func (r *Runtime) HasVolume(name string) (bool, error) { + if !r.valid { + return false, define.ErrRuntimeStopped + } + + return r.state.HasVolume(name) +} + +// Volumes retrieves all volumes +// Filters can be provided which will determine which volumes are included in the +// output. If multiple filters are used, a volume will be returned if +// any of the filters are matched +func (r *Runtime) Volumes(filters ...VolumeFilter) ([]*Volume, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + vols, err := r.state.AllVolumes() + if err != nil { + return nil, err + } + + if len(filters) == 0 { + return vols, nil + } + + volsFiltered := make([]*Volume, 0, len(vols)) + for _, vol := range vols { + include := false + for _, filter := range filters { + include = include || filter(vol) + } + + if include { + volsFiltered = append(volsFiltered, vol) + } + } + + return volsFiltered, nil +} + +// GetAllVolumes retrieves all the volumes +func (r *Runtime) GetAllVolumes() ([]*Volume, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + + return r.state.AllVolumes() +} + +// PruneVolumes removes unused volumes from the system +func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) ([]*reports.PruneReport, error) { + preports := make([]*reports.PruneReport, 0) + vols, err := r.Volumes(filterFuncs...) + if err != nil { + return nil, err + } + + for _, vol := range vols { + report := new(reports.PruneReport) + volSize, err := vol.Size() + if err != nil { + volSize = 0 + } + report.Size = volSize + report.Id = vol.Name() + var timeout *uint + if err := r.RemoveVolume(ctx, vol, false, timeout); err != nil { + if !errors.Is(err, define.ErrVolumeBeingUsed) && !errors.Is(err, define.ErrVolumeRemoved) { + report.Err = err + } else { + // We didn't remove the volume for some reason + continue + } + } else { + vol.newVolumeEvent(events.Prune) + } + preports = append(preports, report) + } + return preports, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_volume_common.go b/vendor/github.com/containers/podman/v5/libpod/runtime_volume_common.go new file mode 100644 index 00000000..e67bc97e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_volume_common.go @@ -0,0 +1,504 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + volplugin "github.com/containers/podman/v5/libpod/plugin" + pluginapi "github.com/docker/go-plugins-helpers/volume" + "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" + "go.podman.io/storage" + "go.podman.io/storage/drivers/quota" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/stringid" +) + +const volumeSuffix = "+volume" + +// NewVolume creates a new empty volume +func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + return r.newVolume(ctx, false, options...) +} + +// newVolume creates a new empty volume with the given options. +// The createPluginVolume can be set to true to make it not create the volume in the volume plugin, +// this is required for the UpdateVolumePlugins() function. If you are not sure, set this to false. +func (r *Runtime) newVolume(ctx context.Context, noCreatePluginVolume bool, options ...VolumeCreateOption) (_ *Volume, deferredErr error) { + volume := newVolume(r) + for _, option := range options { + if err := option(volume); err != nil { + return nil, fmt.Errorf("running volume create option: %w", err) + } + } + + if volume.config.Name == "" { + volume.config.Name = stringid.GenerateRandomID() + } + if volume.config.Driver == "" { + volume.config.Driver = define.VolumeDriverLocal + } + volume.config.CreatedTime = time.Now() + + // Check if volume with given name exists. + exists, err := r.state.HasVolume(volume.config.Name) + if err != nil { + return nil, fmt.Errorf("checking if volume with name %s exists: %w", volume.config.Name, err) + } + if exists { + if volume.ignoreIfExists { + existingVolume, err := r.state.Volume(volume.config.Name) + if err != nil { + return nil, fmt.Errorf("reading volume from state: %w", err) + } + return existingVolume, nil + } else { + return nil, fmt.Errorf("volume with name %s already exists: %w", volume.config.Name, define.ErrVolumeExists) + } + } + + // Plugin can be nil if driver is local, but that's OK - superfluous + // assignment doesn't hurt much. + plugin, err := r.getVolumePlugin(volume.config) + if err != nil { + return nil, fmt.Errorf("volume %s uses volume plugin %s but it could not be retrieved: %w", volume.config.Name, volume.config.Driver, err) + } + volume.plugin = plugin + + if volume.config.Driver == define.VolumeDriverLocal { + logrus.Debugf("Validating options for local driver") + // Validate options + for key, val := range volume.config.Options { + switch strings.ToLower(key) { + case "device": + if strings.ToLower(volume.config.Options["type"]) == define.TypeBind { + if err := fileutils.Exists(val); err != nil { + return nil, fmt.Errorf("invalid volume option %s for driver 'local': %w", key, err) + } + } + case "o", "type", "uid", "gid", "size", "inodes", "noquota", "copy", "nocopy": + // Do nothing, valid keys + default: + return nil, fmt.Errorf("invalid mount option %s for driver 'local': %w", key, define.ErrInvalidArg) + } + } + } else if volume.config.Driver == define.VolumeDriverImage && !volume.UsesVolumeDriver() { + logrus.Debugf("Creating image-based volume") + var imgString string + // Validate options + for key, val := range volume.config.Options { + switch strings.ToLower(key) { + case "image": + imgString = val + default: + return nil, fmt.Errorf("invalid mount option %s for driver 'image': %w", key, define.ErrInvalidArg) + } + } + + if imgString == "" { + return nil, fmt.Errorf("must provide an image name when creating a volume with the image driver: %w", define.ErrInvalidArg) + } + + // Look up the image + image, _, err := r.libimageRuntime.LookupImage(imgString, nil) + if err != nil { + return nil, fmt.Errorf("looking up image %s to create volume failed: %w", imgString, err) + } + + // Generate a c/storage name and ID for the volume. + // Use characters Podman does not allow for the name, to ensure + // no collision with containers. + volume.config.StorageID = stringid.GenerateRandomID() + volume.config.StorageName = volume.config.Name + volumeSuffix + volume.config.StorageImageID = image.ID() + + // Create a backing container in c/storage. + storageConfig := storage.ContainerOptions{ + LabelOpts: []string{"filetype:container_file_t", "level:s0"}, + } + if len(volume.config.MountLabel) > 0 { + context, err := selinux.NewContext(volume.config.MountLabel) + if err != nil { + return nil, fmt.Errorf("failed to get SELinux context from %s: %w", volume.config.MountLabel, err) + } + storageConfig.LabelOpts = []string{fmt.Sprintf("filetype:%s", context["type"])} + } + if _, err := r.storageService.CreateContainerStorage(ctx, r.imageContext, imgString, image.ID(), volume.config.StorageName, volume.config.StorageID, storageConfig); err != nil { + return nil, fmt.Errorf("creating backing storage for image driver: %w", err) + } + defer func() { + if deferredErr != nil { + if err := r.storageService.DeleteContainer(volume.config.StorageID); err != nil { + logrus.Errorf("Error removing volume %s backing storage: %v", volume.config.Name, err) + } + } + }() + } + + // Now we get conditional: we either need to make the volume in the + // volume plugin, or on disk if not using a plugin. + if volume.plugin != nil && !noCreatePluginVolume { + // We can't chown, or relabel, or similar the path the volume is + // using, because it's not managed by us. + // TODO: reevaluate this once we actually have volume plugins in + // use in production - it may be safe, but I can't tell without + // knowing what the actual plugin does... + if err := makeVolumeInPluginIfNotExist(volume.config.Name, volume.config.Options, volume.plugin); err != nil { + return nil, err + } + } else { + // Create the mountpoint of this volume + volPathRoot := filepath.Join(r.config.Engine.VolumePath, volume.config.Name) + if err := os.MkdirAll(volPathRoot, 0o700); err != nil { + return nil, fmt.Errorf("creating volume directory %q: %w", volPathRoot, err) + } + if err := idtools.SafeChown(volPathRoot, volume.config.UID, volume.config.GID); err != nil { + return nil, fmt.Errorf("chowning volume directory %q to %d:%d: %w", volPathRoot, volume.config.UID, volume.config.GID, err) + } + + // Setting quotas must happen *before* the _data inner directory + // is created, as the volume must be empty for the quota to be + // properly applied - if any subdirectories exist before the + // quota is applied, the quota will not be applied to them. + switch { + case volume.config.DisableQuota: + if volume.config.Size > 0 || volume.config.Inodes > 0 { + return nil, errors.New("volume options size and inodes cannot be used without quota") + } + case volume.config.Options["type"] == define.TypeTmpfs: + // tmpfs only supports Size + if volume.config.Inodes > 0 { + return nil, errors.New("volume option inodes not supported on tmpfs filesystem") + } + case volume.config.Inodes > 0 || volume.config.Size > 0: + projectQuotaSupported := false + q, err := quota.NewControl(r.config.Engine.VolumePath) + if err == nil { + projectQuotaSupported = true + } + if !projectQuotaSupported { + return nil, errors.New("volume options size and inodes not supported. Filesystem does not support Project Quota") + } + quota := quota.Quota{ + Inodes: volume.config.Inodes, + Size: volume.config.Size, + } + // Must use volPathRoot not fullVolPath, as we need the + // base path for the volume - without the `_data` + // subdirectory - so the quota ID assignment logic works + // properly. + if err := q.SetQuota(volPathRoot, quota); err != nil { + return nil, fmt.Errorf("failed to set size quota size=%d inodes=%d for volume directory %q: %w", volume.config.Size, volume.config.Inodes, volPathRoot, err) + } + } + + fullVolPath := filepath.Join(volPathRoot, "_data") + if err := os.MkdirAll(fullVolPath, 0o755); err != nil { + return nil, fmt.Errorf("creating volume directory %q: %w", fullVolPath, err) + } + if err := idtools.SafeChown(fullVolPath, volume.config.UID, volume.config.GID); err != nil { + return nil, fmt.Errorf("chowning volume directory %q to %d:%d: %w", fullVolPath, volume.config.UID, volume.config.GID, err) + } + if err := LabelVolumePath(fullVolPath, volume.config.MountLabel); err != nil { + return nil, err + } + volume.config.MountPoint = fullVolPath + } + + lock, err := r.lockManager.AllocateLock() + if err != nil { + return nil, fmt.Errorf("allocating lock for new volume: %w", err) + } + volume.lock = lock + volume.config.LockID = volume.lock.ID() + + defer func() { + if deferredErr != nil { + if err := volume.lock.Free(); err != nil { + logrus.Errorf("Freeing volume lock after failed creation: %v", err) + } + } + }() + + volume.valid = true + + // Add the volume to state + if err := r.state.AddVolume(volume); err != nil { + return nil, fmt.Errorf("adding volume to state: %w", err) + } + defer volume.newVolumeEvent(events.Create) + return volume, nil +} + +// UpdateVolumePlugins reads all volumes from all configured volume plugins and +// imports them into the libpod db. It also checks if existing libpod volumes +// are removed in the plugin, in this case we try to remove it from libpod. +// On errors we continue and try to do as much as possible. all errors are +// returned as array in the returned struct. +// This function has many race conditions, it is best effort but cannot guarantee +// a perfect state since plugins can be modified from the outside at any time. +func (r *Runtime) UpdateVolumePlugins(ctx context.Context) *define.VolumeReload { + var ( + added []string + removed []string + errs []error + allPluginVolumes = map[string]struct{}{} + ) + + for driverName, socket := range r.config.Engine.VolumePlugins { + driver, err := volplugin.GetVolumePlugin(driverName, socket, nil, r.config) + if err != nil { + errs = append(errs, err) + continue + } + vols, err := driver.ListVolumes() + if err != nil { + errs = append(errs, fmt.Errorf("failed to read volumes from plugin %q: %w", driverName, err)) + continue + } + for _, vol := range vols { + allPluginVolumes[vol.Name] = struct{}{} + if _, err := r.newVolume(ctx, true, WithVolumeName(vol.Name), WithVolumeDriver(driverName)); err != nil { + // If the volume exists this is not an error, just ignore it and log. It is very likely + // that the volume from the plugin was already in our db. + if !errors.Is(err, define.ErrVolumeExists) { + errs = append(errs, err) + continue + } + logrus.Infof("Volume %q already exists: %v", vol.Name, err) + continue + } + added = append(added, vol.Name) + } + } + + libpodVolumes, err := r.state.AllVolumes() + if err != nil { + errs = append(errs, fmt.Errorf("cannot delete dangling plugin volumes: failed to read libpod volumes: %w", err)) + } + for _, vol := range libpodVolumes { + if vol.UsesVolumeDriver() { + if _, ok := allPluginVolumes[vol.Name()]; !ok { + // The volume is no longer in the plugin. Let's remove it from the libpod db. + if err := r.removeVolume(ctx, vol, false, nil, true); err != nil { + if errors.Is(err, define.ErrVolumeBeingUsed) { + // Volume is still used by at least one container. This is very bad, + // the plugin no longer has this but we still need it. + errs = append(errs, fmt.Errorf("volume was removed from the plugin %q but containers still require it: %w", vol.config.Driver, err)) + continue + } + if errors.Is(err, define.ErrNoSuchVolume) || errors.Is(err, define.ErrVolumeRemoved) || errors.Is(err, define.ErrMissingPlugin) { + // Volume was already removed, no problem just ignore it and continue. + continue + } + + // some other error + errs = append(errs, err) + continue + } + // Volume was successfully removed + removed = append(removed, vol.Name()) + } + } + } + + return &define.VolumeReload{ + Added: added, + Removed: removed, + Errors: errs, + } +} + +// makeVolumeInPluginIfNotExist makes a volume in the given volume plugin if it +// does not already exist. +func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin *volplugin.VolumePlugin) error { + // Ping the volume plugin to see if it exists first. + // If it does, use the existing volume in the plugin. + // Options may not match exactly, but not much we can do about + // that. Not complaining avoids a lot of the sync issues we see + // with c/storage and libpod DB. + needsCreate := true + getReq := new(pluginapi.GetRequest) + getReq.Name = name + if resp, err := plugin.GetVolume(getReq); err == nil { + // TODO: What do we do if we get a 200 response, but the + // Volume is nil? The docs on the Plugin API are very + // nonspecific, so I don't know if this is valid or + // not... + if resp != nil { + needsCreate = false + logrus.Infof("Volume %q already exists in plugin %q, using existing volume", name, plugin.Name) + } + } + if needsCreate { + createReq := new(pluginapi.CreateRequest) + createReq.Name = name + createReq.Options = options + if err := plugin.CreateVolume(createReq); err != nil { + return fmt.Errorf("creating volume %q in plugin %s: %w", name, plugin.Name, err) + } + } + + return nil +} + +// removeVolume removes the specified volume from state as well tears down its mountpoint and storage. +// ignoreVolumePlugin is used to only remove the volume from the db and not the plugin, +// this is required when the volume was already removed from the plugin, i.e. in UpdateVolumePlugins(). +func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint, ignoreVolumePlugin bool) error { + if !v.valid { + if ok, _ := r.state.HasVolume(v.Name()); !ok { + return nil + } + return define.ErrVolumeRemoved + } + + // DANGEROUS: Do not lock here yet because we might needed to remove containers first. + // In general we must always acquire the ctr lock before a volume lock so we cannot lock. + // THIS MUST BE DONE to prevent ABBA deadlocks. + // It also means the are several races around creating containers with volumes and removing + // them in parallel. However that problem exists regadless of taking the lock here or not. + + deps, err := r.state.VolumeInUse(v) + if err != nil { + return err + } + if len(deps) != 0 { + depsStr := strings.Join(deps, ", ") + if !force { + return fmt.Errorf("volume %s is being used by the following container(s): %s: %w", v.Name(), depsStr, define.ErrVolumeBeingUsed) + } + + // We need to remove all containers using the volume + for _, dep := range deps { + ctr, err := r.state.Container(dep) + if err != nil { + // If the container's removed, no point in + // erroring. + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + continue + } + + return fmt.Errorf("removing container %s that depends on volume %s: %w", dep, v.Name(), err) + } + + logrus.Debugf("Removing container %s (depends on volume %q)", ctr.ID(), v.Name()) + + opts := ctrRmOpts{ + Force: force, + Timeout: timeout, + } + if _, _, err := r.removeContainer(ctx, ctr, opts); err != nil { + return fmt.Errorf("removing container %s that depends on volume %s: %w", ctr.ID(), v.Name(), err) + } + } + } + + // Ok now we are good to lock. + v.lock.Lock() + defer v.lock.Unlock() + + // Update volume status to pick up a potential removal from state + if err := v.update(); err != nil { + return err + } + + // If the volume is still mounted - force unmount it + if err := v.unmount(true); err != nil { + if force { + // If force is set, evict the volume, even if errors + // occur. Otherwise we'll never be able to get rid of + // them. + logrus.Errorf("Unmounting volume %s: %v", v.Name(), err) + } else { + return fmt.Errorf("unmounting volume %s: %w", v.Name(), err) + } + } + + // Set volume as invalid so it can no longer be used + v.valid = false + + var removalErr error + + // If we use a volume plugin, we need to remove from the plugin. + if v.UsesVolumeDriver() && !ignoreVolumePlugin { + canRemove := true + + // Do we have a volume driver? + if v.plugin == nil { + canRemove = false + removalErr = fmt.Errorf("cannot remove volume %s from plugin %s, but it has been removed from Podman: %w", v.Name(), v.Driver(), define.ErrMissingPlugin) + } else { + // Ping the plugin first to verify the volume still + // exists. + // We're trying to be very tolerant of missing volumes + // in the backend, to avoid the problems we see with + // sync between c/storage and the Libpod DB. + getReq := new(pluginapi.GetRequest) + getReq.Name = v.Name() + if _, err := v.plugin.GetVolume(getReq); err != nil { + canRemove = false + removalErr = fmt.Errorf("volume %s could not be retrieved from plugin %s, but it has been removed from Podman: %w", v.Name(), v.Driver(), err) + } + } + if canRemove { + req := new(pluginapi.RemoveRequest) + req.Name = v.Name() + if err := v.plugin.RemoveVolume(req); err != nil { + return fmt.Errorf("volume %s could not be removed from plugin %s: %w", v.Name(), v.Driver(), err) + } + } + } else if v.config.Driver == define.VolumeDriverImage { + if err := v.runtime.storageService.DeleteContainer(v.config.StorageID); err != nil { + // Storage container is already gone, no problem. + if !errors.Is(err, storage.ErrNotAContainer) && !errors.Is(err, storage.ErrContainerUnknown) { + return fmt.Errorf("removing volume %s storage: %w", v.Name(), err) + } + logrus.Infof("Storage for volume %s already removed", v.Name()) + } + } + + // Remove the volume from the state + if err := r.state.RemoveVolume(v); err != nil { + if removalErr != nil { + logrus.Errorf("Removing volume %s from plugin %s: %v", v.Name(), v.Driver(), removalErr) + } + return fmt.Errorf("removing volume %s: %w", v.Name(), err) + } + + // Free the volume's lock + if err := v.lock.Free(); err != nil { + if removalErr == nil { + removalErr = fmt.Errorf("freeing lock for volume %s: %w", v.Name(), err) + } else { + logrus.Errorf("Freeing lock for volume %q: %v", v.Name(), err) + } + } + + // Delete the mountpoint path of the volume, that is delete the volume + // from /var/lib/containers/storage/volumes + if err := v.teardownStorage(); err != nil { + if removalErr == nil { + removalErr = fmt.Errorf("cleaning up volume storage for %q: %w", v.Name(), err) + } else { + logrus.Errorf("Cleaning up volume storage for volume %q: %v", v.Name(), err) + } + } + + defer v.newVolumeEvent(events.Remove) + logrus.Debugf("Removed volume %s", v.Name()) + return removalErr +} diff --git a/vendor/github.com/containers/podman/v5/libpod/runtime_worker.go b/vendor/github.com/containers/podman/v5/libpod/runtime_worker.go new file mode 100644 index 00000000..484e1ffe --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/runtime_worker.go @@ -0,0 +1,23 @@ +//go:build !remote + +package libpod + +func (r *Runtime) setupWorkerQueue() { + r.workerChannel = make(chan func(), 10) +} + +func (r *Runtime) startWorker() { + go func() { + for w := range r.workerChannel { + w() + r.workerGroup.Done() + } + }() +} + +func (r *Runtime) queueWork(f func()) { + r.workerGroup.Add(1) + go func() { + r.workerChannel <- f + }() +} diff --git a/vendor/github.com/containers/podman/v5/libpod/service.go b/vendor/github.com/containers/podman/v5/libpod/service.go new file mode 100644 index 00000000..e5cbb7b5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/service.go @@ -0,0 +1,313 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "fmt" + + "github.com/containers/podman/v5/libpod/define" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// A service consists of one or more pods. The service container is started +// before all pods and is stopped when the last pod stops. The service +// container allows for tracking and managing the entire life cycle of service +// which may be started via `podman-play-kube`. +type Service struct { + // Pods running as part of the service. + Pods []string `json:"servicePods"` +} + +// Indicates whether the pod is associated with a service container. +// The pod is expected to be updated and locked. +func (p *Pod) hasServiceContainer() bool { + return p.config.ServiceContainerID != "" +} + +// Returns the pod's service container. +// The pod is expected to be updated and locked. +func (p *Pod) serviceContainer() (*Container, error) { + id := p.config.ServiceContainerID + if id == "" { + return nil, fmt.Errorf("pod has no service container: %w", define.ErrNoSuchCtr) + } + return p.runtime.state.Container(id) +} + +// ServiceContainer returns the service container. +func (p *Pod) ServiceContainer() (*Container, error) { + p.lock.Lock() + defer p.lock.Unlock() + if err := p.updatePod(); err != nil { + return nil, err + } + return p.serviceContainer() +} + +func (c *Container) addServicePodLocked(id string) error { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return err + } + c.state.Service.Pods = append(c.state.Service.Pods, id) + return c.save() +} + +// IsService returns true when the container is a "service container". +func (c *Container) IsService() bool { + return c.config.IsService +} + +// serviceContainerReport bundles information when checking whether a service +// container can be stopped. +type serviceContainerReport struct { + // Indicates whether the service container can be stopped or not. + canBeStopped bool + // Number of all known containers below the service container. + numContainers int + // Number of containers below the service containers that exited + // non-zero. + failedContainers int +} + +// canStopServiceContainerLocked returns true if all pods of the service are stopped. +// Note that the method acquires the container lock. +func (c *Container) canStopServiceContainerLocked() (*serviceContainerReport, error) { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return nil, err + } + + if !c.IsService() { + return nil, fmt.Errorf("internal error: checking service: container %s is not a service container", c.ID()) + } + + return c.canStopServiceContainer() +} + +// canStopServiceContainer returns true if all pods of the service are stopped. +// Note that the method expects the container to be locked. +func (c *Container) canStopServiceContainer() (*serviceContainerReport, error) { + report := serviceContainerReport{canBeStopped: true} + for _, id := range c.state.Service.Pods { + pod, err := c.runtime.LookupPod(id) + if err != nil { + if errors.Is(err, define.ErrNoSuchPod) { + continue + } + return nil, err + } + + status, err := pod.GetPodStatus() + if err != nil { + if errors.Is(err, define.ErrNoSuchPod) { + continue + } + return nil, err + } + + switch status { + case define.PodStateStopped, define.PodStateExited, define.PodStateErrored: + podCtrs, err := c.runtime.state.PodContainers(pod) + if err != nil { + return nil, err + } + for _, pc := range podCtrs { + if pc.IsInfra() { + continue // ignore infra containers + } + exitCode, err := c.runtime.state.GetContainerExitCode(pc.ID()) + if err != nil { + return nil, err + } + if exitCode != 0 { + report.failedContainers++ + } + report.numContainers++ + } + default: + // Service container cannot be stopped, so we can + // return early. + report.canBeStopped = false + return &report, nil + } + } + + return &report, nil +} + +// Checks whether the service container can be stopped and does so. +func (p *Pod) maybeStopServiceContainer() error { + if !p.hasServiceContainer() { + return nil + } + + serviceCtr, err := p.serviceContainer() + if err != nil { + if errors.Is(err, define.ErrNoSuchCtr) { + return nil + } + return fmt.Errorf("getting pod's service container: %w", err) + } + // Checking whether the service can be stopped must be done in + // the runtime's work queue to resolve ABBA dead locks in the + // pod->container->servicePods hierarchy. + p.runtime.queueWork(func() { + logrus.Debugf("Pod %s has a service %s: checking if it can be stopped", p.ID(), serviceCtr.ID()) + report, err := serviceCtr.canStopServiceContainerLocked() + if err != nil { + logrus.Errorf("Checking whether service of container %s can be stopped: %v", serviceCtr.ID(), err) + return + } + if !report.canBeStopped { + return + } + + // Now either kill or stop the service container, depending on the configured exit policy. + stop := func() { + // Note that the service container runs catatonit which + // will exit gracefully on SIGINT. + logrus.Debugf("Stopping service container %s", serviceCtr.ID()) + if err := serviceCtr.Kill(uint(unix.SIGINT)); err != nil && !errors.Is(err, define.ErrCtrStateInvalid) { + logrus.Debugf("Error stopping service container %s: %v", serviceCtr.ID(), err) + } + } + + kill := func() { + logrus.Debugf("Killing service container %s", serviceCtr.ID()) + if err := serviceCtr.Kill(uint(unix.SIGKILL)); err != nil && !errors.Is(err, define.ErrCtrStateInvalid) { + logrus.Debugf("Error killing service container %s: %v", serviceCtr.ID(), err) + } + } + + switch serviceCtr.config.KubeExitCodePropagation { + case define.KubeExitCodePropagationNone: + stop() + case define.KubeExitCodePropagationAny: + if report.failedContainers > 0 { + kill() + } else { + stop() + } + case define.KubeExitCodePropagationAll: + if report.failedContainers == report.numContainers { + kill() + } else { + stop() + } + default: + logrus.Errorf("Internal error: cannot stop service container %s: unknown exit policy %q", serviceCtr.ID(), serviceCtr.config.KubeExitCodePropagation.String()) + } + }) + return nil +} + +// Starts the pod's service container if it's not already running. +func (p *Pod) maybeStartServiceContainer(ctx context.Context) error { + if !p.hasServiceContainer() { + return nil + } + + serviceCtr, err := p.serviceContainer() + if err != nil { + return fmt.Errorf("getting pod's service container: %w", err) + } + + serviceCtr.lock.Lock() + defer serviceCtr.lock.Unlock() + + if err := serviceCtr.syncContainer(); err != nil { + return err + } + + if serviceCtr.state.State == define.ContainerStateRunning { + return nil + } + + // Restart will reinit among other things. + return serviceCtr.restartWithTimeout(ctx, 0) +} + +// canRemoveServiceContainer returns true if all pods of the service are removed. +// Note that the method acquires the container lock. +func (c *Container) canRemoveServiceContainer() (bool, error) { + if !c.IsService() { + return false, fmt.Errorf("internal error: checking service: container %s is not a service container", c.ID()) + } + + for _, id := range c.state.Service.Pods { + if _, err := c.runtime.LookupPod(id); err != nil { + if errors.Is(err, define.ErrNoSuchPod) { + continue + } + return false, err + } + return false, nil + } + + return true, nil +} + +// Checks whether the service container can be removed and does so. +// It also unlinks the pod from the service container. +func (p *Pod) maybeRemoveServiceContainer() error { + if !p.hasServiceContainer() { + return nil + } + + serviceCtr, err := p.serviceContainer() + if err != nil { + if errors.Is(err, define.ErrNoSuchCtr) { + return nil + } + return fmt.Errorf("getting pod's service container: %w", err) + } + // Checking whether the service can be stopped must be done in + // the runtime's work queue to resolve ABBA dead locks in the + // pod->container->servicePods hierarchy. + p.runtime.queueWork(func() { + logrus.Debugf("Pod %s has a service %s: checking if it can be removed", p.ID(), serviceCtr.ID()) + canRemove, err := func() (bool, error) { // Anonymous func for easy locking + serviceCtr.lock.Lock() + defer serviceCtr.lock.Unlock() + if err := serviceCtr.syncContainer(); err != nil { + return false, err + } + + // Unlink the pod from the service container. + servicePods := make([]string, 0, len(serviceCtr.state.Service.Pods)-1) + for _, id := range serviceCtr.state.Service.Pods { + if id != p.ID() { + servicePods = append(servicePods, id) + } + } + serviceCtr.state.Service.Pods = servicePods + if err := serviceCtr.save(); err != nil { + return false, err + } + + return serviceCtr.canRemoveServiceContainer() + }() + if err != nil { + if !errors.Is(err, define.ErrNoSuchCtr) { + logrus.Errorf("Checking whether service container %s can be removed: %v", serviceCtr.ID(), err) + } + return + } + if !canRemove { + return + } + logrus.Debugf("Removing service container %s", serviceCtr.ID()) + if err := p.runtime.RemoveContainer(context.Background(), serviceCtr, true, false, nil); err != nil { + if !errors.Is(err, define.ErrNoSuchCtr) { + logrus.Errorf("Removing service container %s: %v", serviceCtr.ID(), err) + } + } + }) + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/shutdown/handler.go b/vendor/github.com/containers/podman/v5/libpod/shutdown/handler.go new file mode 100644 index 00000000..ac41899e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/shutdown/handler.go @@ -0,0 +1,168 @@ +package shutdown + +import ( + "errors" + "os" + "os/signal" + "sync" + "syscall" + "time" + + logrusImport "github.com/sirupsen/logrus" +) + +var ( + ErrHandlerExists = errors.New("handler with given name already exists") +) + +var ( + stopped bool + sigChan chan os.Signal + cancelChan chan bool + // Synchronize accesses to the map + handlerLock sync.Mutex + // Definitions of all on-shutdown handlers + handlers map[string]func(os.Signal) error + // Ordering that on-shutdown handlers will be invoked. + handlerOrder []string + shutdownInhibit sync.RWMutex + logrus = logrusImport.WithField("PID", os.Getpid()) + ErrNotStarted = errors.New("shutdown signal handler has not yet been started") + // exitCode used to exit once we are done with all signal handlers, by default 1 + exitCode = 1 +) + +// SetExitCode when we exit after we ran all shutdown handlers, it should be positive. +func SetExitCode(i int) { + exitCode = i +} + +// Start begins handling SIGTERM and SIGINT and will run the given on-signal +// handlers when one is called and then exit with the exit code of 1 if not +// overwritten with SetExitCode(). This can be cancelled by calling Stop(). +func Start() error { + if sigChan != nil { + // Already running, do nothing. + return nil + } + + sigChan = make(chan os.Signal, 2) + cancelChan = make(chan bool, 1) + stopped = false + + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + go func() { + select { + case <-cancelChan: + logrus.Infof("Received shutdown.Stop(), terminating!") + signal.Stop(sigChan) + close(sigChan) + close(cancelChan) + stopped = true + return + case sig := <-sigChan: + logrus.Infof("Received shutdown signal %q, terminating!", sig.String()) + shutdownInhibit.Lock() + handlerLock.Lock() + + for _, name := range handlerOrder { + handler, ok := handlers[name] + if !ok { + logrus.Errorf("Shutdown handler %q definition not found!", name) + continue + } + + logrus.Infof("Invoking shutdown handler %q", name) + start := time.Now() + if err := handler(sig); err != nil { + logrus.Errorf("Running shutdown handler %q: %v", name, err) + } + logrus.Debugf("Completed shutdown handler %q, duration %v", name, + time.Since(start).Round(time.Second)) + } + handlerLock.Unlock() + shutdownInhibit.Unlock() + os.Exit(exitCode) + return + } + }() + + return nil +} + +// Stop the shutdown signal handler. +func Stop() error { + if cancelChan == nil { + return ErrNotStarted + } + if stopped { + return nil + } + + // if the signal handler is running, wait that it terminates + handlerLock.Lock() + defer handlerLock.Unlock() + // it doesn't need to be in the critical section, but staticcheck complains if + // the critical section is empty. + cancelChan <- true + + return nil +} + +// Inhibit temporarily inhibit signals from shutting down Libpod. +func Inhibit() { + shutdownInhibit.RLock() +} + +// Uninhibit stop inhibiting signals from shutting down Libpod. +func Uninhibit() { + shutdownInhibit.RUnlock() +} + +// Register registers a function that will be executed when Podman is terminated +// by a signal. Handlers are invoked LIFO - the last handler registered is the +// first run. +func Register(name string, handler func(os.Signal) error) error { + handlerLock.Lock() + defer handlerLock.Unlock() + + if handlers == nil { + handlers = make(map[string]func(os.Signal) error) + } + + if _, ok := handlers[name]; ok { + return ErrHandlerExists + } + + handlers[name] = handler + handlerOrder = append([]string{name}, handlerOrder...) + + return nil +} + +// Unregister un-registers a given shutdown handler. +func Unregister(name string) error { + handlerLock.Lock() + defer handlerLock.Unlock() + + if handlers == nil { + return nil + } + + if _, ok := handlers[name]; !ok { + return nil + } + + delete(handlers, name) + + newOrder := []string{} + for _, checkName := range handlerOrder { + if checkName != name { + newOrder = append(newOrder, checkName) + } + } + handlerOrder = newOrder + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/sqlite_state.go b/vendor/github.com/containers/podman/v5/libpod/sqlite_state.go new file mode 100644 index 00000000..f99cd41d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/sqlite_state.go @@ -0,0 +1,2339 @@ +//go:build !remote + +package libpod + +import ( + "database/sql" + "errors" + "fmt" + "os" + "path/filepath" + goruntime "runtime" + "strings" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" + "go.podman.io/storage" + + // SQLite backend for database/sql + _ "github.com/mattn/go-sqlite3" +) + +const schemaVersion = 1 + +// SQLiteState is a state implementation backed by a SQLite database +type SQLiteState struct { + valid bool + conn *sql.DB + runtime *Runtime +} + +const ( + // Deal with timezone automatically. + sqliteOptionLocation = "_loc=auto" + // Force an fsync after each transaction (https://www.sqlite.org/pragma.html#pragma_synchronous). + sqliteOptionSynchronous = "&_sync=FULL" + // Allow foreign keys (https://www.sqlite.org/pragma.html#pragma_foreign_keys). + sqliteOptionForeignKeys = "&_foreign_keys=1" + // Make sure that transactions happen exclusively. + sqliteOptionTXLock = "&_txlock=exclusive" + // Enforce case sensitivity for LIKE + sqliteOptionCaseSensitiveLike = "&_cslike=TRUE" + + // Assembled sqlite options used when opening the database. + sqliteOptions = "db.sql?" + + sqliteOptionLocation + + sqliteOptionSynchronous + + sqliteOptionForeignKeys + + sqliteOptionTXLock + + sqliteOptionCaseSensitiveLike +) + +// NewSqliteState creates a new SQLite-backed state database. +func NewSqliteState(runtime *Runtime) (_ State, defErr error) { + logrus.Info("Using sqlite as database backend") + state := new(SQLiteState) + + basePath := runtime.storageConfig.GraphRoot + if runtime.storageConfig.TransientStore { + basePath = runtime.storageConfig.RunRoot + } else if !runtime.storageSet.StaticDirSet { + basePath = runtime.config.Engine.StaticDir + } + + // c/storage is set up *after* the DB - so even though we use the c/s + // root (or, for transient, runroot) dir, we need to make the dir + // ourselves. + if err := os.MkdirAll(basePath, 0o700); err != nil { + return nil, fmt.Errorf("creating root directory: %w", err) + } + + // Make sure busy timeout is set to high value to keep retrying when the db is locked. + // Timeout is in ms, so set it to 100s to have enough time to retry the operations. + // Some users might want to experiment with different timeout values (#23236) + // DO NOT DOCUMENT or recommend PODMAN_SQLITE_BUSY_TIMEOUT outside of testing. + busyTimeout := "100000" + if env, ok := os.LookupEnv("PODMAN_SQLITE_BUSY_TIMEOUT"); ok { + logrus.Debugf("PODMAN_SQLITE_BUSY_TIMEOUT is set to %s", env) + busyTimeout = env + } + sqliteOptionBusyTimeout := "&_busy_timeout=" + busyTimeout + + conn, err := sql.Open("sqlite3", filepath.Join(basePath, sqliteOptions+sqliteOptionBusyTimeout)) + if err != nil { + return nil, fmt.Errorf("initializing sqlite database: %w", err) + } + defer func() { + if defErr != nil { + if err := conn.Close(); err != nil { + logrus.Errorf("Error closing SQLite DB connection: %v", err) + } + } + }() + + if err := initSQLiteDB(conn); err != nil { + return nil, err + } + + state.conn = conn + state.valid = true + state.runtime = runtime + + return state, nil +} + +// Close closes the state and prevents further use +func (s *SQLiteState) Close() error { + if err := s.conn.Close(); err != nil { + return err + } + + s.valid = false + return nil +} + +// Refresh clears container and pod states after a reboot +func (s *SQLiteState) Refresh() (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + // Retrieve all containers, pods, and volumes. + // Maps are indexed by ID (or volume name) so we know which goes where, + // and store the marshalled state JSON + ctrStates := make(map[string]string) + podStates := make(map[string]string) + volumeStates := make(map[string]string) + + ctrRows, err := s.conn.Query("SELECT ID, JSON FROM ContainerState;") + if err != nil { + return fmt.Errorf("querying for container states: %w", err) + } + defer ctrRows.Close() + + for ctrRows.Next() { + var ( + id, stateJSON string + ) + if err := ctrRows.Scan(&id, &stateJSON); err != nil { + return fmt.Errorf("scanning container state row: %w", err) + } + + ctrState := new(ContainerState) + + if err := json.Unmarshal([]byte(stateJSON), ctrState); err != nil { + return fmt.Errorf("unmarshalling container state json: %w", err) + } + + // Refresh the state + resetContainerState(ctrState) + + newJSON, err := json.Marshal(ctrState) + if err != nil { + return fmt.Errorf("marshalling container state json: %w", err) + } + + ctrStates[id] = string(newJSON) + } + if err := ctrRows.Err(); err != nil { + return err + } + + podRows, err := s.conn.Query("SELECT ID, JSON FROM PodState;") + if err != nil { + return fmt.Errorf("querying for pod states: %w", err) + } + defer podRows.Close() + + for podRows.Next() { + var ( + id, stateJSON string + ) + if err := podRows.Scan(&id, &stateJSON); err != nil { + return fmt.Errorf("scanning pod state row: %w", err) + } + + podState := new(podState) + + if err := json.Unmarshal([]byte(stateJSON), podState); err != nil { + return fmt.Errorf("unmarshalling pod state json: %w", err) + } + + // Refresh the state + resetPodState(podState) + + newJSON, err := json.Marshal(podState) + if err != nil { + return fmt.Errorf("marshalling pod state json: %w", err) + } + + podStates[id] = string(newJSON) + } + if err := podRows.Err(); err != nil { + return err + } + + volRows, err := s.conn.Query("SELECT Name, JSON FROM VolumeState;") + if err != nil { + return fmt.Errorf("querying for volume states: %w", err) + } + defer volRows.Close() + + for volRows.Next() { + var ( + name, stateJSON string + ) + + if err := volRows.Scan(&name, &stateJSON); err != nil { + return fmt.Errorf("scanning volume state row: %w", err) + } + + volState := new(VolumeState) + + if err := json.Unmarshal([]byte(stateJSON), volState); err != nil { + return fmt.Errorf("unmarshalling volume state json: %w", err) + } + + // Refresh the state + resetVolumeState(volState) + + newJSON, err := json.Marshal(volState) + if err != nil { + return fmt.Errorf("marshalling volume state json: %w", err) + } + + volumeStates[name] = string(newJSON) + } + if err := volRows.Err(); err != nil { + return err + } + + // Write updated states back to DB, and perform additional maintenance + // (Remove exit codes and exec sessions) + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning refresh transaction: %w", err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to refresh database state: %v", err) + } + } + }() + + for id, json := range ctrStates { + if _, err := tx.Exec("UPDATE ContainerState SET JSON=? WHERE ID=?;", json, id); err != nil { + return fmt.Errorf("updating container state: %w", err) + } + } + for id, json := range podStates { + if _, err := tx.Exec("UPDATE PodState SET JSON=? WHERE ID=?;", json, id); err != nil { + return fmt.Errorf("updating pod state: %w", err) + } + } + for name, json := range volumeStates { + if _, err := tx.Exec("UPDATE VolumeState SET JSON=? WHERE Name=?;", json, name); err != nil { + return fmt.Errorf("updating volume state: %w", err) + } + } + + if _, err := tx.Exec("DELETE FROM ContainerExitCode;"); err != nil { + return fmt.Errorf("removing container exit codes: %w", err) + } + + if _, err := tx.Exec("DELETE FROM ContainerExecSession;"); err != nil { + return fmt.Errorf("removing container exec sessions: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction: %w", err) + } + + return nil +} + +// GetDBConfig retrieves runtime configuration fields that were created when +// the database was first initialized +func (s *SQLiteState) GetDBConfig() (*DBConfig, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + cfg := new(DBConfig) + var staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumeDir string + + row := s.conn.QueryRow("SELECT StaticDir, TmpDir, GraphRoot, RunRoot, GraphDriver, VolumeDir FROM DBConfig;") + + if err := row.Scan(&staticDir, &tmpDir, &graphRoot, &runRoot, &graphDriver, &volumeDir); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return cfg, nil + } + return nil, fmt.Errorf("retrieving DB config: %w", err) + } + + cfg.LibpodRoot = staticDir + cfg.LibpodTmp = tmpDir + cfg.StorageRoot = graphRoot + cfg.StorageTmp = runRoot + cfg.GraphDriver = graphDriver + cfg.VolumePath = volumeDir + + return cfg, nil +} + +// ValidateDBConfig validates paths in the given runtime against the database +func (s *SQLiteState) ValidateDBConfig(_ *Runtime) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + storeOpts, err := storage.DefaultStoreOptions() + if err != nil { + return err + } + + const createRow = ` + INSERT INTO DBconfig VALUES ( + ?, ?, ?, + ?, ?, ?, + ?, ?, ? + );` + + var ( + dbOS, staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumePath string + runtimeOS = goruntime.GOOS + runtimeStaticDir = filepath.Clean(s.runtime.config.Engine.StaticDir) + runtimeTmpDir = filepath.Clean(s.runtime.config.Engine.TmpDir) + runtimeGraphRoot = filepath.Clean(s.runtime.StorageConfig().GraphRoot) + runtimeRunRoot = filepath.Clean(s.runtime.StorageConfig().RunRoot) + runtimeGraphDriver = s.runtime.StorageConfig().GraphDriverName + runtimeVolumePath = filepath.Clean(s.runtime.config.Engine.VolumePath) + ) + + // Some fields may be empty, indicating they are set to the default. + // If so, grab the default from c/storage for them. + if runtimeGraphRoot == "" { + runtimeGraphRoot = storeOpts.GraphRoot + } + if runtimeRunRoot == "" { + runtimeRunRoot = storeOpts.RunRoot + } + if runtimeGraphDriver == "" { + runtimeGraphDriver = storeOpts.GraphDriverName + } + + // We have to do this in a transaction to ensure mutual exclusion. + // Otherwise we have a race - multiple processes can be checking the + // row's existence simultaneously, both try to create it, second one to + // get the transaction lock gets an error. + // TODO: The transaction isn't strictly necessary, and there's a (small) + // chance it's a perf hit. If it is, we can move it entirely within the + // `errors.Is()` block below, with extra validation to ensure the row + // still does not exist (and, if it does, to retry this function). + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning database validation transaction: %w", err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to validate database: %v", err) + } + } + }() + + row := tx.QueryRow("SELECT Os, StaticDir, TmpDir, GraphRoot, RunRoot, GraphDriver, VolumeDir FROM DBConfig;") + + if err := row.Scan(&dbOS, &staticDir, &tmpDir, &graphRoot, &runRoot, &graphDriver, &volumePath); err != nil { + if errors.Is(err, sql.ErrNoRows) { + if _, err := tx.Exec(createRow, 1, schemaVersion, runtimeOS, + runtimeStaticDir, runtimeTmpDir, runtimeGraphRoot, + runtimeRunRoot, runtimeGraphDriver, runtimeVolumePath); err != nil { + return fmt.Errorf("adding DB config row: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing write of database validation row: %w", err) + } + + return nil + } + + return fmt.Errorf("retrieving DB config: %w", err) + } + + // Sometimes, for as-yet unclear reasons, the database value ends up set + // to the empty string. If it does, this evaluation is always going to + // fail, and libpod will be unusable. + // At this point, the check is effectively meaningless - we don't + // actually know the settings we should be checking against. The best + // thing we can do (and what BoltDB did in this case) is to compare + // against the default, on the assumption that is what was in use. + // TODO: We can't remove this code without breaking existing SQLite DBs + // that already have incorrect values in the database, but we should + // investigate why this is happening and try and prevent the creation of + // new databases with these garbage checks. + if graphRoot == "" { + logrus.Debugf("Database uses empty-string graph root, substituting default %q", storeOpts.GraphRoot) + graphRoot = storeOpts.GraphRoot + } + if runRoot == "" { + logrus.Debugf("Database uses empty-string run root, substituting default %q", storeOpts.RunRoot) + runRoot = storeOpts.RunRoot + } + if graphDriver == "" { + logrus.Debugf("Database uses empty-string graph driver, substituting default %q", storeOpts.GraphDriverName) + graphDriver = storeOpts.GraphDriverName + } + + checkField := func(fieldName, dbVal, ourVal string, isPath bool) error { + if isPath { + // Tolerate symlinks when possible - most relevant for OStree systems + // and rootless containers, where we want to put containers in /home, + // which is symlinked to /var/home. + // Ignore ENOENT as reasonable, as some paths may not exist in early Libpod + // init. + if dbVal != "" { + checkedVal, err := evalSymlinksIfExists(dbVal) + if err != nil { + return fmt.Errorf("cannot evaluate symlinks on DB %s path %q: %w", fieldName, dbVal, err) + } + dbVal = checkedVal + } + if ourVal != "" { + checkedVal, err := evalSymlinksIfExists(ourVal) + if err != nil { + return fmt.Errorf("cannot evaluate symlinks on our %s path %q: %w", fieldName, ourVal, err) + } + ourVal = checkedVal + } + } + + if dbVal != ourVal { + return fmt.Errorf("database %s %q does not match our %s %q: %w", fieldName, dbVal, fieldName, ourVal, define.ErrDBBadConfig) + } + + return nil + } + + if err := checkField("os", dbOS, runtimeOS, false); err != nil { + return err + } + if err := checkField("static dir", staticDir, runtimeStaticDir, true); err != nil { + return err + } + if err := checkField("tmp dir", tmpDir, runtimeTmpDir, true); err != nil { + return err + } + if err := checkField("graph root", graphRoot, runtimeGraphRoot, true); err != nil { + return err + } + if err := checkField("run root", runRoot, runtimeRunRoot, true); err != nil { + return err + } + if err := checkField("graph driver", graphDriver, runtimeGraphDriver, false); err != nil { + return err + } + if err := checkField("volume path", volumePath, runtimeVolumePath, true); err != nil { + return err + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing database validation row: %w", err) + } + // Do not return any error after the commit call because the defer will + // try to roll back the transaction which results in an logged error. + + return nil +} + +// GetContainerName returns the name of the container associated with a given +// ID. Returns ErrNoSuchCtr if the ID does not exist. +func (s *SQLiteState) GetContainerName(id string) (string, error) { + if id == "" { + return "", define.ErrEmptyID + } + + if !s.valid { + return "", define.ErrDBClosed + } + + var name string + + row := s.conn.QueryRow("SELECT Name FROM ContainerConfig WHERE ID=?;", id) + if err := row.Scan(&name); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return "", define.ErrNoSuchCtr + } + + return "", fmt.Errorf("looking up container %s name: %w", id, err) + } + + return name, nil +} + +// GetPodName returns the name of the pod associated with a given ID. +// Returns ErrNoSuchPod if the ID does not exist. +func (s *SQLiteState) GetPodName(id string) (string, error) { + if id == "" { + return "", define.ErrEmptyID + } + + if !s.valid { + return "", define.ErrDBClosed + } + + var name string + + row := s.conn.QueryRow("SELECT Name FROM PodConfig WHERE ID=?;", id) + if err := row.Scan(&name); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return "", define.ErrNoSuchPod + } + + return "", fmt.Errorf("looking up pod %s name: %w", id, err) + } + + return name, nil +} + +// Container retrieves a single container from the state by its full ID +func (s *SQLiteState) Container(id string) (*Container, error) { + if id == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + ctrConfig, err := s.getCtrConfig(id) + if err != nil { + return nil, err + } + + ctr := new(Container) + ctr.config = ctrConfig + ctr.state = new(ContainerState) + ctr.runtime = s.runtime + + if err := finalizeCtrSqlite(ctr); err != nil { + return nil, err + } + + return ctr, nil +} + +// LookupContainerID retrieves a container ID from the state by full or unique +// partial ID or name +func (s *SQLiteState) LookupContainerID(idOrName string) (string, error) { + if idOrName == "" { + return "", define.ErrEmptyID + } + + if !s.valid { + return "", define.ErrDBClosed + } + + rows, err := s.conn.Query("SELECT ID, Name FROM ContainerConfig WHERE ContainerConfig.Name=? OR (ContainerConfig.ID LIKE ?);", idOrName, idOrName+"%") + if err != nil { + return "", fmt.Errorf("looking up container %q in database: %w", idOrName, err) + } + defer rows.Close() + + var ( + id, name string + resCount uint + ) + for rows.Next() { + if err := rows.Scan(&id, &name); err != nil { + return "", fmt.Errorf("retrieving container %q ID from database: %w", idOrName, err) + } + if name == idOrName { + return id, nil + } + resCount++ + } + if err := rows.Err(); err != nil { + return "", err + } + if resCount == 0 { + return "", define.ErrNoSuchCtr + } else if resCount > 1 { + return "", fmt.Errorf("more than one result for container %q: %w", idOrName, define.ErrCtrExists) + } + + return id, nil +} + +// LookupContainer retrieves a container from the state by full or unique +// partial ID or name +func (s *SQLiteState) LookupContainer(idOrName string) (*Container, error) { + if idOrName == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + rows, err := s.conn.Query("SELECT JSON, Name FROM ContainerConfig WHERE ContainerConfig.Name=? OR (ContainerConfig.ID LIKE ?);", idOrName, idOrName+"%") + if err != nil { + return nil, fmt.Errorf("looking up container %q in database: %w", idOrName, err) + } + defer rows.Close() + + var ( + rawJSON, name string + exactName bool + resCount uint + ) + for rows.Next() { + if err := rows.Scan(&rawJSON, &name); err != nil { + return nil, fmt.Errorf("retrieving container %q ID from database: %w", idOrName, err) + } + if name == idOrName { + exactName = true + break + } + resCount++ + } + if err := rows.Err(); err != nil { + return nil, err + } + if !exactName { + if resCount == 0 { + return nil, fmt.Errorf("no container with name or ID %q found: %w", idOrName, define.ErrNoSuchCtr) + } else if resCount > 1 { + return nil, fmt.Errorf("more than one result for container %q: %w", idOrName, define.ErrCtrExists) + } + } + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(ContainerState) + ctr.runtime = s.runtime + + if err := json.Unmarshal([]byte(rawJSON), ctr.config); err != nil { + return nil, fmt.Errorf("unmarshalling container config JSON: %w", err) + } + + if err := finalizeCtrSqlite(ctr); err != nil { + return nil, err + } + + return ctr, nil +} + +// HasContainer checks if a container is present in the state +func (s *SQLiteState) HasContainer(id string) (bool, error) { + if id == "" { + return false, define.ErrEmptyID + } + + if !s.valid { + return false, define.ErrDBClosed + } + + row := s.conn.QueryRow("SELECT 1 FROM ContainerConfig WHERE ID=?;", id) + + var check int + if err := row.Scan(&check); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return false, fmt.Errorf("looking up container %s in database: %w", id, err) + } else if check != 1 { + return false, fmt.Errorf("check digit for container %s lookup incorrect: %w", id, define.ErrInternal) + } + + return true, nil +} + +// AddContainer adds a container to the state +// The container being added cannot belong to a pod +func (s *SQLiteState) AddContainer(ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if ctr.config.Pod != "" { + return fmt.Errorf("cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod: %w", define.ErrInvalidArg) + } + + return s.addContainer(ctr) +} + +// RemoveContainer removes a container from the state +// Only removes containers not in pods - for containers that are a member of a +// pod, use RemoveContainerFromPod +func (s *SQLiteState) RemoveContainer(ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if ctr.config.Pod != "" { + return fmt.Errorf("container %s is part of a pod, use RemoveContainerFromPod instead: %w", ctr.ID(), define.ErrPodExists) + } + + return s.removeContainer(ctr) +} + +// UpdateContainer updates a container's state from the database +func (s *SQLiteState) UpdateContainer(ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + row := s.conn.QueryRow("SELECT JSON FROM ContainerState WHERE ID=?;", ctr.ID()) + + var rawJSON string + if err := row.Scan(&rawJSON); err != nil { + if errors.Is(err, sql.ErrNoRows) { + // Container was removed + ctr.valid = false + return fmt.Errorf("no container with ID %s found in database: %w", ctr.ID(), define.ErrNoSuchCtr) + } + } + + newState := new(ContainerState) + if err := json.Unmarshal([]byte(rawJSON), newState); err != nil { + return fmt.Errorf("unmarshalling container %s state JSON: %w", ctr.ID(), err) + } + + ctr.state = newState + + return nil +} + +// SaveContainer saves a container's current state in the database +func (s *SQLiteState) SaveContainer(ctr *Container) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + stateJSON, err := json.Marshal(ctr.state) + if err != nil { + return fmt.Errorf("marshalling container %s state JSON: %w", ctr.ID(), err) + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning container %s save transaction: %w", ctr.ID(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to save container %s state: %v", ctr.ID(), err) + } + } + }() + + result, err := tx.Exec("UPDATE ContainerState SET JSON=? WHERE ID=?;", stateJSON, ctr.ID()) + if err != nil { + return fmt.Errorf("writing container %s state: %w", ctr.ID(), err) + } + rows, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("retrieving container %s save rows affected: %w", ctr.ID(), err) + } + if rows == 0 { + ctr.valid = false + return define.ErrNoSuchCtr + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing container %s state: %w", ctr.ID(), err) + } + + return nil +} + +// ContainerInUse checks if other containers depend on the given container +// It returns a slice of the IDs of the containers depending on the given +// container. If the slice is empty, no containers depend on the given container +func (s *SQLiteState) ContainerInUse(ctr *Container) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + rows, err := s.conn.Query("SELECT ID FROM ContainerDependency WHERE DependencyID=?;", ctr.ID()) + if err != nil { + return nil, fmt.Errorf("retrieving containers that depend on container %s: %w", ctr.ID(), err) + } + defer rows.Close() + + deps := []string{} + for rows.Next() { + var dep string + if err := rows.Scan(&dep); err != nil { + return nil, fmt.Errorf("reading containers that depend on %s: %w", ctr.ID(), err) + } + deps = append(deps, dep) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return deps, nil +} + +// AllContainers retrieves all the containers in the database +// If `loadState` is set, the containers' state will be loaded as well. +func (s *SQLiteState) AllContainers(loadState bool) ([]*Container, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + ctrs := []*Container{} + + if loadState { + rows, err := s.conn.Query("SELECT ContainerConfig.JSON, ContainerState.JSON AS StateJSON FROM ContainerConfig INNER JOIN ContainerState ON ContainerConfig.ID = ContainerState.ID;") + if err != nil { + return nil, fmt.Errorf("retrieving all containers from database: %w", err) + } + defer rows.Close() + + for rows.Next() { + var configJSON, stateJSON string + if err := rows.Scan(&configJSON, &stateJSON); err != nil { + return nil, fmt.Errorf("scanning container from database: %w", err) + } + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(ContainerState) + ctr.runtime = s.runtime + + if err := json.Unmarshal([]byte(configJSON), ctr.config); err != nil { + return nil, fmt.Errorf("unmarshalling container config: %w", err) + } + if err := json.Unmarshal([]byte(stateJSON), ctr.state); err != nil { + return nil, fmt.Errorf("unmarshalling container %s state: %w", ctr.ID(), err) + } + + ctrs = append(ctrs, ctr) + } + if err := rows.Err(); err != nil { + return nil, err + } + } else { + rows, err := s.conn.Query("SELECT JSON FROM ContainerConfig;") + if err != nil { + return nil, fmt.Errorf("retrieving all containers from database: %w", err) + } + defer rows.Close() + + for rows.Next() { + var rawJSON string + if err := rows.Scan(&rawJSON); err != nil { + return nil, fmt.Errorf("scanning container from database: %w", err) + } + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(ContainerState) + ctr.runtime = s.runtime + + if err := json.Unmarshal([]byte(rawJSON), ctr.config); err != nil { + return nil, fmt.Errorf("unmarshalling container config: %w", err) + } + + ctrs = append(ctrs, ctr) + } + if err := rows.Err(); err != nil { + return nil, err + } + } + + for _, ctr := range ctrs { + if err := finalizeCtrSqlite(ctr); err != nil { + return nil, err + } + } + + return ctrs, nil +} + +// GetNetworks returns the networks this container is a part of. +func (s *SQLiteState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + // if the network mode is not bridge return no networks + if !ctr.config.NetMode.IsBridge() { + return nil, nil + } + + cfg, err := s.getCtrConfig(ctr.ID()) + if err != nil { + if errors.Is(err, define.ErrNoSuchCtr) { + ctr.valid = false + } + return nil, err + } + + return cfg.Networks, nil +} + +// NetworkConnect adds the given container to the given network. If aliases are +// specified, those will be added to the given network. +func (s *SQLiteState) NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error { + return s.networkModify(ctr, network, opts, true, false) +} + +// NetworkModify will allow you to set new options on an existing connected network +func (s *SQLiteState) NetworkModify(ctr *Container, network string, opts types.PerNetworkOptions) error { + return s.networkModify(ctr, network, opts, false, false) +} + +// NetworkDisconnect disconnects the container from the given network, also +// removing any aliases in the network. +func (s *SQLiteState) NetworkDisconnect(ctr *Container, network string) error { + return s.networkModify(ctr, network, types.PerNetworkOptions{}, false, true) +} + +// GetContainerConfig returns a container config from the database by full ID +func (s *SQLiteState) GetContainerConfig(id string) (*ContainerConfig, error) { + if len(id) == 0 { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + return s.getCtrConfig(id) +} + +// AddContainerExitCode adds the exit code for the specified container to the database. +func (s *SQLiteState) AddContainerExitCode(id string, exitCode int32) (defErr error) { + if len(id) == 0 { + return define.ErrEmptyID + } + + if !s.valid { + return define.ErrDBClosed + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning transaction to add exit code: %w", err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to add exit code: %v", err) + } + } + }() + + if _, err := tx.Exec("INSERT OR REPLACE INTO ContainerExitCode VALUES (?, ?, ?);", id, time.Now().Unix(), exitCode); err != nil { + return fmt.Errorf("adding container %s exit code %d: %w", id, exitCode, err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction to add exit code: %w", err) + } + + return nil +} + +// GetContainerExitCode returns the exit code for the specified container. +func (s *SQLiteState) GetContainerExitCode(id string) (int32, error) { + if len(id) == 0 { + return -1, define.ErrEmptyID + } + + if !s.valid { + return -1, define.ErrDBClosed + } + + row := s.conn.QueryRow("SELECT ExitCode FROM ContainerExitCode WHERE ID=?;", id) + var exitCode int32 = -1 + if err := row.Scan(&exitCode); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return -1, fmt.Errorf("getting exit code of container %s from DB: %w", id, define.ErrNoSuchExitCode) + } + return -1, fmt.Errorf("scanning exit code of container %s: %w", id, err) + } + + return exitCode, nil +} + +// GetContainerExitCodeTimeStamp returns the time stamp when the exit code of +// the specified container was added to the database. +func (s *SQLiteState) GetContainerExitCodeTimeStamp(id string) (*time.Time, error) { + if len(id) == 0 { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + row := s.conn.QueryRow("SELECT Timestamp FROM ContainerExitCode WHERE ID=?;", id) + + var timestamp int64 + if err := row.Scan(×tamp); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("getting timestamp for exit code of container %s from DB: %w", id, define.ErrNoSuchExitCode) + } + return nil, fmt.Errorf("scanning exit timestamp of container %s: %w", id, err) + } + + result := time.Unix(timestamp, 0) + + return &result, nil +} + +// PruneContainerExitCodes removes exit codes older than 5 minutes unless the associated +// container still exists. +func (s *SQLiteState) PruneContainerExitCodes() (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + fiveMinsAgo := time.Now().Add(-5 * time.Minute).Unix() + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning transaction to remove old timestamps: %w", err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to remove old timestamps: %v", err) + } + } + }() + + if _, err := tx.Exec("DELETE FROM ContainerExitCode WHERE (Timestamp <= ?) AND (ID NOT IN (SELECT ID FROM ContainerConfig))", fiveMinsAgo); err != nil { + return fmt.Errorf("removing exit codes with timestamps older than 5 minutes: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction to remove old timestamps: %w", err) + } + + return nil +} + +// AddExecSession adds an exec session to the state. +func (s *SQLiteState) AddExecSession(ctr *Container, session *ExecSession) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning container %s exec session %s add transaction: %w", ctr.ID(), session.Id, err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to add container %s exec session %s: %v", ctr.ID(), session.Id, err) + } + } + }() + + if _, err := tx.Exec("INSERT INTO ContainerExecSession VALUES (?, ?);", session.Id, ctr.ID()); err != nil { + return fmt.Errorf("adding container %s exec session %s to database: %w", ctr.ID(), session.Id, err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing container %s exec session %s addition: %w", ctr.ID(), session.Id, err) + } + + return nil +} + +// GetExecSession returns the ID of the container an exec session is associated +// with. +func (s *SQLiteState) GetExecSession(id string) (string, error) { + if !s.valid { + return "", define.ErrDBClosed + } + + if id == "" { + return "", define.ErrEmptyID + } + + row := s.conn.QueryRow("SELECT ContainerID FROM ContainerExecSession WHERE ID=?;", id) + + var ctrID string + if err := row.Scan(&ctrID); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return "", fmt.Errorf("no exec session with ID %s found: %w", id, define.ErrNoSuchExecSession) + } + return "", fmt.Errorf("retrieving exec session %s from database: %w", id, err) + } + + return ctrID, nil +} + +// RemoveExecSession removes references to the given exec session in the +// database. +func (s *SQLiteState) RemoveExecSession(session *ExecSession) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning container %s exec session %s remove transaction: %w", session.ContainerId, session.Id, err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to remove container %s exec session %s: %v", session.ContainerId, session.Id, err) + } + } + }() + + result, err := tx.Exec("DELETE FROM ContainerExecSession WHERE ID=?;", session.Id) + if err != nil { + return fmt.Errorf("removing container %s exec session %s from database: %w", session.ContainerId, session.Id, err) + } + rows, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("retrieving container %s exec session %s removal rows modified: %w", session.ContainerId, session.Id, err) + } + if rows == 0 { + return define.ErrNoSuchExecSession + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing container %s exec session %s removal: %w", session.ContainerId, session.Id, err) + } + + return nil +} + +// GetContainerExecSessions retrieves the IDs of all exec sessions running in a +// container that the database is aware of (IE, were added via AddExecSession). +func (s *SQLiteState) GetContainerExecSessions(ctr *Container) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + rows, err := s.conn.Query("SELECT ID FROM ContainerExecSession WHERE ContainerID=?;", ctr.ID()) + if err != nil { + return nil, fmt.Errorf("querying container %s exec sessions: %w", ctr.ID(), err) + } + defer rows.Close() + + var sessions []string + for rows.Next() { + var session string + if err := rows.Scan(&session); err != nil { + return nil, fmt.Errorf("scanning container %s exec sessions row: %w", ctr.ID(), err) + } + sessions = append(sessions, session) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return sessions, nil +} + +// RemoveContainerExecSessions removes all exec sessions attached to a given +// container. +func (s *SQLiteState) RemoveContainerExecSessions(ctr *Container) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning container %s exec session removal transaction: %w", ctr.ID(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to remove container %s exec sessions: %v", ctr.ID(), err) + } + } + }() + + if _, err := tx.Exec("DELETE FROM ContainerExecSession WHERE ContainerID=?;", ctr.ID()); err != nil { + return fmt.Errorf("removing container %s exec sessions from database: %w", ctr.ID(), err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing container %s exec session removal: %w", ctr.ID(), err) + } + + return nil +} + +// RewriteContainerConfig rewrites a container's configuration. +// DO NOT USE TO: Change container dependencies, change pod membership, change +// container ID. +// WARNING: This function is DANGEROUS. Do not use without reading the full +// comment on this function in state.go. +// TODO: Once BoltDB is removed, this can be combined with SafeRewriteContainerConfig. +func (s *SQLiteState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + return s.rewriteContainerConfig(ctr, newCfg) +} + +// SafeRewriteContainerConfig rewrites a container's configuration in a more +// limited fashion than RewriteContainerConfig. It is marked as safe to use +// under most circumstances, unlike RewriteContainerConfig. +// DO NOT USE TO: Change container dependencies, change pod membership, change +// locks, change container ID. +// TODO: Once BoltDB is removed, this can be combined with RewriteContainerConfig. +func (s *SQLiteState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if newName != "" && newCfg.Name != newName { + return fmt.Errorf("new name %s for container %s must match name in given container config: %w", newName, ctr.ID(), define.ErrInvalidArg) + } + if newName != "" && oldName == "" { + return fmt.Errorf("must provide old name for container if a new name is given: %w", define.ErrInvalidArg) + } + + return s.rewriteContainerConfig(ctr, newCfg) +} + +// RewritePodConfig rewrites a pod's configuration. +// WARNING: This function is DANGEROUS. Do not use without reading the full +// comment on this function in state.go. +func (s *SQLiteState) RewritePodConfig(pod *Pod, newCfg *PodConfig) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + json, err := json.Marshal(newCfg) + if err != nil { + return fmt.Errorf("error marshalling pod %s config JSON: %w", pod.ID(), err) + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning transaction to rewrite pod %s config: %w", pod.ID(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to rewrite pod %s config: %v", pod.ID(), err) + } + } + }() + + results, err := tx.Exec("UPDATE PodConfig SET Name=?, JSON=? WHERE ID=?;", newCfg.Name, json, pod.ID()) + if err != nil { + return fmt.Errorf("updating pod config table with new configuration for pod %s: %w", pod.ID(), err) + } + rows, err := results.RowsAffected() + if err != nil { + return fmt.Errorf("retrieving pod %s config rewrite rows affected: %w", pod.ID(), err) + } + if rows == 0 { + pod.valid = false + return fmt.Errorf("no pod with ID %s found in DB: %w", pod.ID(), define.ErrNoSuchPod) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction to rewrite pod %s config: %w", pod.ID(), err) + } + + return nil +} + +// RewriteVolumeConfig rewrites a volume's configuration. +// WARNING: This function is DANGEROUS. Do not use without reading the full +// comment on this function in state.go. +func (s *SQLiteState) RewriteVolumeConfig(volume *Volume, newCfg *VolumeConfig) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !volume.valid { + return define.ErrVolumeRemoved + } + + json, err := json.Marshal(newCfg) + if err != nil { + return fmt.Errorf("error marshalling volume %s new config JSON: %w", volume.Name(), err) + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning transaction to rewrite volume %s config: %w", volume.Name(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to rewrite volume %s config: %v", volume.Name(), err) + } + } + }() + + results, err := tx.Exec("UPDATE VolumeConfig SET Name=?, JSON=? WHERE Name=?;", newCfg.Name, json, volume.Name()) + if err != nil { + return fmt.Errorf("updating volume config table with new configuration for volume %s: %w", volume.Name(), err) + } + rows, err := results.RowsAffected() + if err != nil { + return fmt.Errorf("retrieving volume %s config rewrite rows affected: %w", volume.Name(), err) + } + if rows == 0 { + volume.valid = false + return fmt.Errorf("no volume with name %q found in DB: %w", volume.Name(), define.ErrNoSuchVolume) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction to rewrite volume %s config: %w", volume.Name(), err) + } + + return nil +} + +// Pod retrieves a pod given its full ID +func (s *SQLiteState) Pod(id string) (*Pod, error) { + if id == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + row := s.conn.QueryRow("SELECT JSON FROM PodConfig WHERE ID=?;", id) + var rawJSON string + if err := row.Scan(&rawJSON); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, define.ErrNoSuchPod + } + return nil, fmt.Errorf("retrieving pod %s config from DB: %w", id, err) + } + + ctrCfg := new(ContainerConfig) + if err := json.Unmarshal([]byte(rawJSON), ctrCfg); err != nil { + return nil, fmt.Errorf("unmarshalling container %s config: %w", id, err) + } + + return s.createPod(rawJSON) +} + +// LookupPod retrieves a pod from a full or unique partial ID, or a name. +func (s *SQLiteState) LookupPod(idOrName string) (*Pod, error) { + if idOrName == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + rows, err := s.conn.Query("SELECT JSON, Name FROM PodConfig WHERE PodConfig.Name=? OR (PodConfig.ID LIKE ?);", idOrName, idOrName+"%") + if err != nil { + return nil, fmt.Errorf("looking up pod %q in database: %w", idOrName, err) + } + defer rows.Close() + + var ( + rawJSON, name string + exactName bool + resCount uint + ) + for rows.Next() { + if err := rows.Scan(&rawJSON, &name); err != nil { + return nil, fmt.Errorf("error retrieving pod %q ID from database: %w", idOrName, err) + } + if name == idOrName { + exactName = true + break + } + resCount++ + } + if err := rows.Err(); err != nil { + return nil, err + } + if !exactName { + if resCount == 0 { + return nil, fmt.Errorf("no pod with name or ID %s found: %w", idOrName, define.ErrNoSuchPod) + } else if resCount > 1 { + return nil, fmt.Errorf("more than one result for pod %q: %w", idOrName, define.ErrCtrExists) + } + } + + return s.createPod(rawJSON) +} + +// HasPod checks if a pod with the given ID exists in the state +func (s *SQLiteState) HasPod(id string) (bool, error) { + if id == "" { + return false, define.ErrEmptyID + } + + if !s.valid { + return false, define.ErrDBClosed + } + + row := s.conn.QueryRow("SELECT 1 FROM PodConfig WHERE ID=?;", id) + + var check int + if err := row.Scan(&check); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return false, fmt.Errorf("looking up pod %s in database: %w", id, err) + } else if check != 1 { + return false, fmt.Errorf("check digit for pod %s lookup incorrect: %w", id, define.ErrInternal) + } + + return true, nil +} + +// PodHasContainer checks if the given pod has a container with the given ID +func (s *SQLiteState) PodHasContainer(pod *Pod, id string) (bool, error) { + if id == "" { + return false, define.ErrEmptyID + } + + if !s.valid { + return false, define.ErrDBClosed + } + + if !pod.valid { + return false, define.ErrPodRemoved + } + + var check int + row := s.conn.QueryRow("SELECT 1 FROM ContainerConfig WHERE ID=? AND PodID=?;", id, pod.ID()) + if err := row.Scan(&check); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return false, fmt.Errorf("checking if pod %s has container %s in database: %w", pod.ID(), id, err) + } else if check != 1 { + return false, fmt.Errorf("check digit for pod %s lookup incorrect: %w", id, define.ErrInternal) + } + + return true, nil +} + +// PodContainersByID returns the IDs of all containers present in the given pod +func (s *SQLiteState) PodContainersByID(pod *Pod) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !pod.valid { + return nil, define.ErrPodRemoved + } + + rows, err := s.conn.Query("SELECT ID FROM ContainerConfig WHERE PodID=?;", pod.ID()) + if err != nil { + return nil, fmt.Errorf("retrieving container IDs of pod %s from database: %w", pod.ID(), err) + } + defer rows.Close() + + var ids []string + for rows.Next() { + var id string + if err := rows.Scan(&id); err != nil { + return nil, fmt.Errorf("scanning container from database: %w", err) + } + + ids = append(ids, id) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return ids, nil +} + +// PodContainers returns all the containers present in the given pod +func (s *SQLiteState) PodContainers(pod *Pod) ([]*Container, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !pod.valid { + return nil, define.ErrPodRemoved + } + + rows, err := s.conn.Query("SELECT JSON FROM ContainerConfig WHERE PodID=?;", pod.ID()) + if err != nil { + return nil, fmt.Errorf("retrieving containers of pod %s from database: %w", pod.ID(), err) + } + defer rows.Close() + + var ctrs []*Container + for rows.Next() { + var rawJSON string + if err := rows.Scan(&rawJSON); err != nil { + return nil, fmt.Errorf("scanning container from database: %w", err) + } + + ctr := new(Container) + ctr.config = new(ContainerConfig) + ctr.state = new(ContainerState) + ctr.runtime = s.runtime + + if err := json.Unmarshal([]byte(rawJSON), ctr.config); err != nil { + return nil, fmt.Errorf("unmarshalling container config: %w", err) + } + + ctrs = append(ctrs, ctr) + } + if err := rows.Err(); err != nil { + return nil, err + } + + for _, ctr := range ctrs { + if err := finalizeCtrSqlite(ctr); err != nil { + return nil, err + } + } + + return ctrs, nil +} + +// AddPod adds the given pod to the state. +func (s *SQLiteState) AddPod(pod *Pod) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + infraID := sql.NullString{} + if pod.state.InfraContainerID != "" { + if err := infraID.Scan(pod.state.InfraContainerID); err != nil { + return fmt.Errorf("scanning infra container ID %q: %w", pod.state.InfraContainerID, err) + } + } + + configJSON, err := json.Marshal(pod.config) + if err != nil { + return fmt.Errorf("marshalling pod config json: %w", err) + } + + stateJSON, err := json.Marshal(pod.state) + if err != nil { + return fmt.Errorf("marshalling pod state json: %w", err) + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning pod create transaction: %w", err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to create pod: %v", err) + } + } + }() + + // TODO: explore whether there's a more idiomatic way to do error checks for the name. + // There is a sqlite3.ErrConstraintUnique error but I (vrothberg) couldn't find a way + // to work with the returned errors yet. + var check int + row := tx.QueryRow("SELECT 1 FROM PodConfig WHERE Name=?;", pod.Name()) + if err := row.Scan(&check); err != nil { + if !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("checking if pod name %s exists in database: %w", pod.Name(), err) + } + } else if check != 0 { + return fmt.Errorf("name %q is in use: %w", pod.Name(), define.ErrPodExists) + } + + if _, err := tx.Exec("INSERT INTO IDNamespace VALUES (?);", pod.ID()); err != nil { + return fmt.Errorf("adding pod id to database: %w", err) + } + if _, err := tx.Exec("INSERT INTO PodConfig VALUES (?, ?, ?);", pod.ID(), pod.Name(), configJSON); err != nil { + return fmt.Errorf("adding pod config to database: %w", err) + } + if _, err := tx.Exec("INSERT INTO PodState VALUES (?, ?, ?);", pod.ID(), infraID, stateJSON); err != nil { + return fmt.Errorf("adding pod state to database: %w", err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction: %w", err) + } + + return nil +} + +// RemovePod removes the given pod from the state. +// Only empty pods can be removed. +func (s *SQLiteState) RemovePod(pod *Pod) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning pod %s removal transaction: %w", pod.ID(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to remove pod %s: %v", pod.ID(), err) + } + } + }() + + var check int + row := tx.QueryRow("SELECT 1 FROM ContainerConfig WHERE PodID=? AND ID!=?;", pod.ID(), pod.state.InfraContainerID) + if err := row.Scan(&check); err != nil { + if !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("checking if pod %s has containers in database: %w", pod.ID(), err) + } + } else if check != 0 { + return fmt.Errorf("pod %s is not empty: %w", pod.ID(), define.ErrCtrExists) + } + + checkResult := func(result sql.Result) error { + rows, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("retrieving pod %s delete rows affected: %w", pod.ID(), err) + } + if rows == 0 { + pod.valid = false + return define.ErrNoSuchPod + } + return nil + } + + result, err := tx.Exec("DELETE FROM IDNamespace WHERE ID=?;", pod.ID()) + if err != nil { + return fmt.Errorf("removing pod %s id from database: %w", pod.ID(), err) + } + if err := checkResult(result); err != nil { + return err + } + + result, err = tx.Exec("DELETE FROM PodConfig WHERE ID=?;", pod.ID()) + if err != nil { + return fmt.Errorf("removing pod %s config from database: %w", pod.ID(), err) + } + if err := checkResult(result); err != nil { + return err + } + + result, err = tx.Exec("DELETE FROM PodState WHERE ID=?;", pod.ID()) + if err != nil { + return fmt.Errorf("removing pod %s state from database: %w", pod.ID(), err) + } + if err := checkResult(result); err != nil { + return err + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing pod %s removal transaction: %w", pod.ID(), err) + } + + return nil +} + +// RemovePodContainers removes all containers in a pod. +func (s *SQLiteState) RemovePodContainers(pod *Pod) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning removal transaction for containers of pod %s: %w", pod.ID(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to remove containers of pod %s: %v", pod.ID(), err) + } + } + }() + + rows, err := tx.Query("SELECT ID FROM ContainerConfig WHERE PodID=?;", pod.ID()) + if err != nil { + return fmt.Errorf("retrieving container IDs of pod %s from database: %w", pod.ID(), err) + } + defer rows.Close() + + for rows.Next() { + var id string + if err := rows.Scan(&id); err != nil { + return fmt.Errorf("scanning container from database: %w", err) + } + + if err := s.removeContainerWithTx(id, tx); err != nil { + return err + } + } + if err := rows.Err(); err != nil { + return err + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing pod containers %s removal transaction: %w", pod.ID(), err) + } + + return nil +} + +// AddContainerToPod adds the given container to an existing pod +// The container will be added to the state and the pod +func (s *SQLiteState) AddContainerToPod(pod *Pod, ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if ctr.config.Pod != pod.ID() { + return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr) + } + + return s.addContainer(ctr) +} + +// RemoveContainerFromPod removes a container from an existing pod +// The container will also be removed from the state +func (s *SQLiteState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + if ctr.config.Pod == "" { + return fmt.Errorf("container %s is not part of a pod, use RemoveContainer instead: %w", ctr.ID(), define.ErrNoSuchPod) + } + + if ctr.config.Pod != pod.ID() { + return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrInvalidArg) + } + + return s.removeContainer(ctr) +} + +// UpdatePod updates a pod's state from the database. +func (s *SQLiteState) UpdatePod(pod *Pod) error { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + row := s.conn.QueryRow("SELECT JSON FROM PodState WHERE ID=?;", pod.ID()) + + var rawJSON string + if err := row.Scan(&rawJSON); err != nil { + if errors.Is(err, sql.ErrNoRows) { + // Pod was removed + pod.valid = false + return fmt.Errorf("no pod with ID %s found in database: %w", pod.ID(), define.ErrNoSuchPod) + } + } + + newState := new(podState) + if err := json.Unmarshal([]byte(rawJSON), newState); err != nil { + return fmt.Errorf("unmarshalling pod %s state JSON: %w", pod.ID(), err) + } + + pod.state = newState + + return nil +} + +// SavePod saves a pod's state to the database. +func (s *SQLiteState) SavePod(pod *Pod) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !pod.valid { + return define.ErrPodRemoved + } + + stateJSON, err := json.Marshal(pod.state) + if err != nil { + return fmt.Errorf("marshalling pod %s state JSON: %w", pod.ID(), err) + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning pod %s save transaction: %w", pod.ID(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to save pod %s state: %v", pod.ID(), err) + } + } + }() + + result, err := tx.Exec("UPDATE PodState SET JSON=? WHERE ID=?;", stateJSON, pod.ID()) + if err != nil { + return fmt.Errorf("writing pod %s state: %w", pod.ID(), err) + } + rows, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("retrieving pod %s save rows affected: %w", pod.ID(), err) + } + if rows == 0 { + pod.valid = false + return define.ErrNoSuchPod + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing pod %s state: %w", pod.ID(), err) + } + + return nil +} + +// AllPods returns all pods present in the state. +func (s *SQLiteState) AllPods() ([]*Pod, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + pods := []*Pod{} + rows, err := s.conn.Query("SELECT JSON FROM PodConfig;") + if err != nil { + return nil, fmt.Errorf("retrieving all pods from database: %w", err) + } + defer rows.Close() + + for rows.Next() { + var rawJSON string + if err := rows.Scan(&rawJSON); err != nil { + return nil, fmt.Errorf("scanning pod from database: %w", err) + } + + pod, err := s.createPod(rawJSON) + if err != nil { + return nil, err + } + + pods = append(pods, pod) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return pods, nil +} + +// AddVolume adds the given volume to the state. It also adds ctrDepID to +// the sub bucket holding the container dependencies that this volume has +func (s *SQLiteState) AddVolume(volume *Volume) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !volume.valid { + return define.ErrVolumeRemoved + } + + cfgJSON, err := json.Marshal(volume.config) + if err != nil { + return fmt.Errorf("marshalling volume %s configuration json: %w", volume.Name(), err) + } + + volState := volume.state + if volState == nil { + volState = new(VolumeState) + } + + stateJSON, err := json.Marshal(volState) + if err != nil { + return fmt.Errorf("marshalling volume %s state json: %w", volume.Name(), err) + } + + storageID := sql.NullString{} + if volume.config.StorageID != "" { + storageID.Valid = true + storageID.String = volume.config.StorageID + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning volume create transaction: %w", err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to create volume: %v", err) + } + } + }() + + // TODO: There has to be a better way of doing this + var check int + row := tx.QueryRow("SELECT 1 FROM VolumeConfig WHERE Name=?;", volume.Name()) + if err := row.Scan(&check); err != nil { + if !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("checking if volume name %s exists in database: %w", volume.Name(), err) + } + } else if check != 0 { + return fmt.Errorf("name %q is in use: %w", volume.Name(), define.ErrVolumeExists) + } + + if _, err := tx.Exec("INSERT INTO VolumeConfig VALUES (?, ?, ?);", volume.Name(), storageID, cfgJSON); err != nil { + return fmt.Errorf("adding volume %s config to database: %w", volume.Name(), err) + } + + if _, err := tx.Exec("INSERT INTO VolumeState VALUES (?, ?);", volume.Name(), stateJSON); err != nil { + return fmt.Errorf("adding volume %s state to database: %w", volume.Name(), err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction: %w", err) + } + + return nil +} + +// RemoveVolume removes the given volume from the state +func (s *SQLiteState) RemoveVolume(volume *Volume) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning volume %s removal transaction: %w", volume.Name(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to remove volume %s: %v", volume.Name(), err) + } + } + }() + + rows, err := tx.Query("SELECT ContainerID FROM ContainerVolume WHERE VolumeName=?;", volume.Name()) + if err != nil { + return fmt.Errorf("querying for containers using volume %s: %w", volume.Name(), err) + } + defer rows.Close() + + var ctrs []string + for rows.Next() { + var ctr string + if err := rows.Scan(&ctr); err != nil { + return fmt.Errorf("error scanning row for containers using volume %s: %w", volume.Name(), err) + } + ctrs = append(ctrs, ctr) + } + if err := rows.Err(); err != nil { + return err + } + if len(ctrs) > 0 { + return fmt.Errorf("volume %s is in use by containers %s: %w", volume.Name(), strings.Join(ctrs, ","), define.ErrVolumeBeingUsed) + } + + // TODO TODO TODO: + // Need to verify that at least 1 row was deleted from VolumeConfig. + // Otherwise return ErrNoSuchVolume + + if _, err := tx.Exec("DELETE FROM VolumeConfig WHERE Name=?;", volume.Name()); err != nil { + return fmt.Errorf("removing volume %s config from DB: %w", volume.Name(), err) + } + + if _, err := tx.Exec("DELETE FROM VolumeState WHERE Name=?;", volume.Name()); err != nil { + return fmt.Errorf("removing volume %s state from DB: %w", volume.Name(), err) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction to remove volume %s: %w", volume.Name(), err) + } + + return nil +} + +// UpdateVolume updates the volume's state from the database. +func (s *SQLiteState) UpdateVolume(volume *Volume) error { + if !s.valid { + return define.ErrDBClosed + } + + if !volume.valid { + return define.ErrVolumeRemoved + } + + row := s.conn.QueryRow("SELECT JSON FROM VolumeState WHERE Name=?;", volume.Name()) + + var stateJSON string + if err := row.Scan(&stateJSON); err != nil { + if errors.Is(err, sql.ErrNoRows) { + volume.valid = false + return define.ErrNoSuchVolume + } + return fmt.Errorf("scanning volume %s state JSON: %w", volume.Name(), err) + } + + newState := new(VolumeState) + if err := json.Unmarshal([]byte(stateJSON), newState); err != nil { + return fmt.Errorf("unmarshalling volume %s state: %w", volume.Name(), err) + } + + volume.state = newState + + return nil +} + +// SaveVolume saves the volume's state to the database. +func (s *SQLiteState) SaveVolume(volume *Volume) (defErr error) { + if !s.valid { + return define.ErrDBClosed + } + + if !volume.valid { + return define.ErrVolumeRemoved + } + + stateJSON, err := json.Marshal(volume.state) + if err != nil { + return fmt.Errorf("marshalling volume %s state JSON: %w", volume.Name(), err) + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning transaction to rewrite volume %s state: %w", volume.Name(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to rewrite volume %s state: %v", volume.Name(), err) + } + } + }() + + results, err := tx.Exec("UPDATE VolumeState SET JSON=? WHERE Name=?;", stateJSON, volume.Name()) + if err != nil { + return fmt.Errorf("updating volume %s state in DB: %w", volume.Name(), err) + } + rows, err := results.RowsAffected() + if err != nil { + return fmt.Errorf("retrieving volume %s state rewrite rows affected: %w", volume.Name(), err) + } + if rows == 0 { + volume.valid = false + return define.ErrNoSuchVolume + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction to rewrite volume %s state: %w", volume.Name(), err) + } + + return nil +} + +// AllVolumes returns all volumes present in the state. +func (s *SQLiteState) AllVolumes() ([]*Volume, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + rows, err := s.conn.Query("SELECT JSON FROM VolumeConfig;") + if err != nil { + return nil, fmt.Errorf("querying database for all volumes: %w", err) + } + defer rows.Close() + + var volumes []*Volume + + for rows.Next() { + var configJSON string + if err := rows.Scan(&configJSON); err != nil { + return nil, fmt.Errorf("scanning volume config from database: %w", err) + } + vol := new(Volume) + vol.config = new(VolumeConfig) + vol.state = new(VolumeState) + vol.runtime = s.runtime + + if err := json.Unmarshal([]byte(configJSON), vol.config); err != nil { + return nil, fmt.Errorf("unmarshalling volume config: %w", err) + } + + if err := finalizeVolumeSqlite(vol); err != nil { + return nil, err + } + + volumes = append(volumes, vol) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return volumes, nil +} + +// Volume retrieves a volume from full name. +func (s *SQLiteState) Volume(name string) (*Volume, error) { + if name == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + row := s.conn.QueryRow("SELECT JSON FROM VolumeConfig WHERE Name=?;", name) + + var configJSON string + + if err := row.Scan(&configJSON); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, define.ErrNoSuchVolume + } + return nil, fmt.Errorf("querying volume %s: %w", name, err) + } + + vol := new(Volume) + vol.config = new(VolumeConfig) + vol.state = new(VolumeState) + vol.runtime = s.runtime + + if err := json.Unmarshal([]byte(configJSON), vol.config); err != nil { + return nil, fmt.Errorf("unmarshalling volume %s config JSON: %w", name, err) + } + + if err := finalizeVolumeSqlite(vol); err != nil { + return nil, err + } + + return vol, nil +} + +// LookupVolume locates a volume from a unique partial name. +func (s *SQLiteState) LookupVolume(name string) (*Volume, error) { + if name == "" { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + escaper := strings.NewReplacer("\\", "\\\\", "_", "\\_", "%", "\\%") + queryString := escaper.Replace(name) + "%" + rows, err := s.conn.Query("SELECT Name, JSON FROM VolumeConfig WHERE Name LIKE ? ESCAPE '\\' ORDER BY LENGTH(Name) ASC;", queryString) + if err != nil { + return nil, fmt.Errorf("querying database for volume %s: %w", name, err) + } + defer rows.Close() + + var foundName, configJSON string + for rows.Next() { + if foundName != "" { + return nil, fmt.Errorf("more than one result for volume name %s: %w", name, define.ErrVolumeExists) + } + if err := rows.Scan(&foundName, &configJSON); err != nil { + return nil, fmt.Errorf("retrieving volume %s config from database: %w", name, err) + } + if foundName == name { + break + } + } + if err := rows.Err(); err != nil { + return nil, err + } + if foundName == "" { + return nil, fmt.Errorf("no volume with name %q found: %w", name, define.ErrNoSuchVolume) + } + + vol := new(Volume) + vol.config = new(VolumeConfig) + vol.state = new(VolumeState) + vol.runtime = s.runtime + + if err := json.Unmarshal([]byte(configJSON), vol.config); err != nil { + return nil, fmt.Errorf("unmarshalling volume %s config JSON: %w", name, err) + } + + if err := finalizeVolumeSqlite(vol); err != nil { + return nil, err + } + + return vol, nil +} + +// HasVolume returns true if the given volume exists in the state. +// Otherwise it returns false. +func (s *SQLiteState) HasVolume(name string) (bool, error) { + if name == "" { + return false, define.ErrEmptyID + } + + if !s.valid { + return false, define.ErrDBClosed + } + + row := s.conn.QueryRow("SELECT 1 FROM VolumeConfig WHERE Name=?;", name) + + var check int + if err := row.Scan(&check); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return false, fmt.Errorf("looking up volume %s in database: %w", name, err) + } + if check != 1 { + return false, fmt.Errorf("check digit for volume %s lookup incorrect: %w", name, define.ErrInternal) + } + + return true, nil +} + +// VolumeInUse checks if any container is using the volume. +// It returns a slice of the IDs of the containers using the given +// volume. If the slice is empty, no containers use the given volume. +func (s *SQLiteState) VolumeInUse(volume *Volume) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !volume.valid { + return nil, define.ErrVolumeRemoved + } + + rows, err := s.conn.Query("SELECT ContainerID FROM ContainerVolume WHERE VolumeName=?;", volume.Name()) + if err != nil { + return nil, fmt.Errorf("querying database for containers using volume %s: %w", volume.Name(), err) + } + defer rows.Close() + + var ctrs []string + for rows.Next() { + var ctr string + if err := rows.Scan(&ctr); err != nil { + return nil, fmt.Errorf("scanning container ID for container using volume %s: %w", volume.Name(), err) + } + ctrs = append(ctrs, ctr) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return ctrs, nil +} + +// ContainerIDIsVolume checks if the given c/storage container ID is used as +// backing storage for a volume. +func (s *SQLiteState) ContainerIDIsVolume(id string) (bool, error) { + if !s.valid { + return false, define.ErrDBClosed + } + + row := s.conn.QueryRow("SELECT 1 FROM VolumeConfig WHERE StorageID=?;", id) + var checkDigit int + if err := row.Scan(&checkDigit); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return false, fmt.Errorf("error retrieving volumes using storage ID %s: %w", id, err) + } + if checkDigit != 1 { + return false, fmt.Errorf("check digit for volumes using storage ID %s was incorrect: %w", id, define.ErrInternal) + } + + return true, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/sqlite_state_internal.go b/vendor/github.com/containers/podman/v5/libpod/sqlite_state_internal.go new file mode 100644 index 00000000..a2953806 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/sqlite_state_internal.go @@ -0,0 +1,580 @@ +//go:build !remote + +package libpod + +import ( + "database/sql" + "errors" + "fmt" + "os" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" + + // SQLite backend for database/sql + _ "github.com/mattn/go-sqlite3" +) + +func initSQLiteDB(conn *sql.DB) (defErr error) { + // Start with a transaction to avoid "database locked" errors. + // See https://github.com/mattn/go-sqlite3/issues/274#issuecomment-1429054597 + tx, err := conn.Begin() + if err != nil { + return fmt.Errorf("beginning transaction: %w", err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to create tables: %v", err) + } + } + }() + + sameSchema, err := migrateSchemaIfNecessary(tx) + if err != nil { + return err + } + if !sameSchema { + if err := createSQLiteTables(tx); err != nil { + return err + } + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction: %w", err) + } + return nil +} + +func migrateSchemaIfNecessary(tx *sql.Tx) (bool, error) { + // First, check if the DBConfig table exists + checkRow := tx.QueryRow("SELECT 1 FROM sqlite_master WHERE type='table' AND name='DBConfig';") + var check int + if err := checkRow.Scan(&check); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return false, fmt.Errorf("checking if DB config table exists: %w", err) + } + if check != 1 { + // Table does not exist, fresh database, no need to migrate. + return false, nil + } + + row := tx.QueryRow("SELECT SchemaVersion FROM DBConfig;") + var schemaVer int + if err := row.Scan(&schemaVer); err != nil { + if errors.Is(err, sql.ErrNoRows) { + // Brand-new, unpopulated DB. + // Schema was just created, so it has to be the latest. + return false, nil + } + return false, fmt.Errorf("scanning schema version from DB config: %w", err) + } + + // If the schema version 0 or less, it's invalid + if schemaVer <= 0 { + return false, fmt.Errorf("database schema version %d is invalid: %w", schemaVer, define.ErrInternal) + } + + // Same schema -> nothing do to. + if schemaVer == schemaVersion { + return true, nil + } + + // If the DB is a later schema than we support, we have to error + if schemaVer > schemaVersion { + return false, fmt.Errorf("database has schema version %d while this libpod version only supports version %d: %w", + schemaVer, schemaVersion, define.ErrInternal) + } + + // Perform schema migration here, one version at a time. + + return false, nil +} + +// Initialize all required tables for the SQLite state +func createSQLiteTables(tx *sql.Tx) error { + // Technically we could split the "CREATE TABLE IF NOT EXISTS" and ");" + // bits off each command and add them in the for loop where we actually + // run the SQL, but that seems unnecessary. + const dbConfig = ` + CREATE TABLE IF NOT EXISTS DBConfig( + ID INTEGER PRIMARY KEY NOT NULL, + SchemaVersion INTEGER NOT NULL, + OS TEXT NOT NULL, + StaticDir TEXT NOT NULL, + TmpDir TEXT NOT NULL, + GraphRoot TEXT NOT NULL, + RunRoot TEXT NOT NULL, + GraphDriver TEXT NOT NULL, + VolumeDir TEXT NOT NULL, + CHECK (ID IN (1)) + );` + + const idNamespace = ` + CREATE TABLE IF NOT EXISTS IDNamespace( + ID TEXT PRIMARY KEY NOT NULL + );` + + const containerConfig = ` + CREATE TABLE IF NOT EXISTS ContainerConfig( + ID TEXT PRIMARY KEY NOT NULL, + Name TEXT UNIQUE NOT NULL, + PodID TEXT, + JSON TEXT NOT NULL, + FOREIGN KEY (ID) REFERENCES IDNamespace(ID) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (ID) REFERENCES ContainerState(ID) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (PodID) REFERENCES PodConfig(ID) + );` + + const containerState = ` + CREATE TABLE IF NOT EXISTS ContainerState( + ID TEXT PRIMARY KEY NOT NULL, + State INTEGER NOT NULL, + ExitCode INTEGER, + JSON TEXT NOT NULL, + FOREIGN KEY (ID) REFERENCES ContainerConfig(ID) DEFERRABLE INITIALLY DEFERRED, + CHECK (ExitCode BETWEEN -1 AND 255) + );` + + const containerExecSession = ` + CREATE TABLE IF NOT EXISTS ContainerExecSession( + ID TEXT PRIMARY KEY NOT NULL, + ContainerID TEXT NOT NULL, + FOREIGN KEY (ContainerID) REFERENCES ContainerConfig(ID) + );` + + const containerDependency = ` + CREATE TABLE IF NOT EXISTS ContainerDependency( + ID TEXT NOT NULL, + DependencyID TEXT NOT NULL, + PRIMARY KEY (ID, DependencyID), + FOREIGN KEY (ID) REFERENCES ContainerConfig(ID) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (DependencyID) REFERENCES ContainerConfig(ID), + CHECK (ID <> DependencyID) + );` + + const containerVolume = ` + CREATE TABLE IF NOT EXISTS ContainerVolume( + ContainerID TEXT NOT NULL, + VolumeName TEXT NOT NULL, + PRIMARY KEY (ContainerID, VolumeName), + FOREIGN KEY (ContainerID) REFERENCES ContainerConfig(ID) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (VolumeName) REFERENCES VolumeConfig(Name) + );` + + const containerExitCode = ` + CREATE TABLE IF NOT EXISTS ContainerExitCode( + ID TEXT PRIMARY KEY NOT NULL, + Timestamp INTEGER NOT NULL, + ExitCode INTEGER NOT NULL, + CHECK (ExitCode BETWEEN -1 AND 255) + );` + + const podConfig = ` + CREATE TABLE IF NOT EXISTS PodConfig( + ID TEXT PRIMARY KEY NOT NULL, + Name TEXT UNIQUE NOT NULL, + JSON TEXT NOT NULL, + FOREIGN KEY (ID) REFERENCES IDNamespace(ID) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (ID) REFERENCES PodState(ID) DEFERRABLE INITIALLY DEFERRED + );` + + const podState = ` + CREATE TABLE IF NOT EXISTS PodState( + ID TEXT PRIMARY KEY NOT NULL, + InfraContainerID TEXT, + JSON TEXT NOT NULL, + FOREIGN KEY (ID) REFERENCES PodConfig(ID) DEFERRABLE INITIALLY DEFERRED, + FOREIGN KEY (InfraContainerID) REFERENCES ContainerConfig(ID) DEFERRABLE INITIALLY DEFERRED + );` + + const volumeConfig = ` + CREATE TABLE IF NOT EXISTS VolumeConfig( + Name TEXT PRIMARY KEY NOT NULL, + StorageID TEXT, + JSON TEXT NOT NULL, + FOREIGN KEY (Name) REFERENCES VolumeState(Name) DEFERRABLE INITIALLY DEFERRED + );` + + const volumeState = ` + CREATE TABLE IF NOT EXISTS VolumeState( + Name TEXT PRIMARY KEY NOT NULL, + JSON TEXT NOT NULL, + FOREIGN KEY (Name) REFERENCES VolumeConfig(Name) DEFERRABLE INITIALLY DEFERRED + );` + + tables := map[string]string{ + "DBConfig": dbConfig, + "IDNamespace": idNamespace, + "ContainerConfig": containerConfig, + "ContainerState": containerState, + "ContainerExecSession": containerExecSession, + "ContainerDependency": containerDependency, + "ContainerVolume": containerVolume, + "ContainerExitCode": containerExitCode, + "PodConfig": podConfig, + "PodState": podState, + "VolumeConfig": volumeConfig, + "VolumeState": volumeState, + } + + for tblName, cmd := range tables { + if _, err := tx.Exec(cmd); err != nil { + return fmt.Errorf("creating table %s: %w", tblName, err) + } + } + return nil +} + +// Get the config of a container with the given ID from the database +func (s *SQLiteState) getCtrConfig(id string) (*ContainerConfig, error) { + row := s.conn.QueryRow("SELECT JSON FROM ContainerConfig WHERE ID=?;", id) + + var rawJSON string + if err := row.Scan(&rawJSON); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, define.ErrNoSuchCtr + } + return nil, fmt.Errorf("retrieving container %s config from DB: %w", id, err) + } + + ctrCfg := new(ContainerConfig) + + if err := json.Unmarshal([]byte(rawJSON), ctrCfg); err != nil { + return nil, fmt.Errorf("unmarshalling container %s config: %w", id, err) + } + + return ctrCfg, nil +} + +// Finalize a container that was pulled out of the database. +func finalizeCtrSqlite(ctr *Container) error { + // Get the lock + lock, err := ctr.runtime.lockManager.RetrieveLock(ctr.config.LockID) + if err != nil { + return fmt.Errorf("retrieving lock for container %s: %w", ctr.ID(), err) + } + ctr.lock = lock + + // Get the OCI runtime + if ctr.config.OCIRuntime == "" { + ctr.ociRuntime = ctr.runtime.defaultOCIRuntime + } else { + // Handle legacy containers which might use a literal path for + // their OCI runtime name. + runtimeName := ctr.config.OCIRuntime + ociRuntime, ok := ctr.runtime.ociRuntimes[runtimeName] + if !ok { + runtimeSet := false + + // If the path starts with a / and exists, make a new + // OCI runtime for it using the full path. + if strings.HasPrefix(runtimeName, "/") { + if stat, err := os.Stat(runtimeName); err == nil && !stat.IsDir() { + newOCIRuntime, err := newConmonOCIRuntime(runtimeName, []string{runtimeName}, ctr.runtime.conmonPath, ctr.runtime.runtimeFlags, ctr.runtime.config) + if err == nil { + // TODO: There is a potential risk of concurrent map modification here. + // This is an unlikely case, though. + ociRuntime = newOCIRuntime + ctr.runtime.ociRuntimes[runtimeName] = ociRuntime + runtimeSet = true + } + } + } + + if !runtimeSet { + // Use a MissingRuntime implementation + ociRuntime = getMissingRuntime(runtimeName, ctr.runtime) + } + } + ctr.ociRuntime = ociRuntime + } + + ctr.valid = true + + return nil +} + +// Finalize a pod that was pulled out of the database. +func (s *SQLiteState) createPod(rawJSON string) (*Pod, error) { + config := new(PodConfig) + if err := json.Unmarshal([]byte(rawJSON), config); err != nil { + return nil, fmt.Errorf("unmarshalling pod config: %w", err) + } + lock, err := s.runtime.lockManager.RetrieveLock(config.LockID) + if err != nil { + return nil, fmt.Errorf("retrieving lock for pod %s: %w", config.ID, err) + } + + pod := new(Pod) + pod.config = config + pod.state = new(podState) + pod.lock = lock + pod.runtime = s.runtime + pod.valid = true + + return pod, nil +} + +// Finalize a volume that was pulled out of the database +func finalizeVolumeSqlite(vol *Volume) error { + // Get the lock + lock, err := vol.runtime.lockManager.RetrieveLock(vol.config.LockID) + if err != nil { + return fmt.Errorf("retrieving lock for volume %s: %w", vol.Name(), err) + } + vol.lock = lock + + // Retrieve volume driver + if vol.UsesVolumeDriver() { + plugin, err := vol.runtime.getVolumePlugin(vol.config) + if err != nil { + // We want to fail gracefully here, to ensure that we + // can still remove volumes even if their plugin is + // missing. Otherwise, we end up with volumes that + // cannot even be retrieved from the database and will + // cause things like `volume ls` to fail. + logrus.Errorf("Volume %s uses volume plugin %s, but it cannot be accessed - some functionality may not be available: %v", vol.Name(), vol.config.Driver, err) + } else { + vol.plugin = plugin + } + } + + vol.valid = true + + return nil +} + +func (s *SQLiteState) rewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) (defErr error) { + json, err := json.Marshal(newCfg) + if err != nil { + return fmt.Errorf("error marshalling container %s new config JSON: %w", ctr.ID(), err) + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning transaction to rewrite container %s config: %w", ctr.ID(), err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to rewrite container %s config: %v", ctr.ID(), err) + } + } + }() + + results, err := tx.Exec("UPDATE ContainerConfig SET Name=?, JSON=? WHERE ID=?;", newCfg.Name, json, ctr.ID()) + if err != nil { + return fmt.Errorf("updating container config table with new configuration for container %s: %w", ctr.ID(), err) + } + rows, err := results.RowsAffected() + if err != nil { + return fmt.Errorf("retrieving container %s config rewrite rows affected: %w", ctr.ID(), err) + } + if rows == 0 { + ctr.valid = false + return define.ErrNoSuchCtr + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction to rewrite container %s config: %w", ctr.ID(), err) + } + + return nil +} + +func (s *SQLiteState) addContainer(ctr *Container) (defErr error) { + configJSON, err := json.Marshal(ctr.config) + if err != nil { + return fmt.Errorf("marshalling container config json: %w", err) + } + + stateJSON, err := json.Marshal(ctr.state) + if err != nil { + return fmt.Errorf("marshalling container state json: %w", err) + } + deps := ctr.Dependencies() + + podID := sql.NullString{} + if ctr.config.Pod != "" { + podID.Valid = true + podID.String = ctr.config.Pod + } + + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning container create transaction: %w", err) + } + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to create container: %v", err) + } + } + }() + + // TODO: There has to be a better way of doing this + var check int + row := tx.QueryRow("SELECT 1 FROM ContainerConfig WHERE Name=?;", ctr.Name()) + if err := row.Scan(&check); err != nil { + if !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("checking if container name %s exists in database: %w", ctr.Name(), err) + } + } else if check != 0 { + return fmt.Errorf("name %q is in use: %w", ctr.Name(), define.ErrCtrExists) + } + + if _, err := tx.Exec("INSERT INTO IDNamespace VALUES (?);", ctr.ID()); err != nil { + return fmt.Errorf("adding container id to database: %w", err) + } + if _, err := tx.Exec("INSERT INTO ContainerConfig VALUES (?, ?, ?, ?);", ctr.ID(), ctr.Name(), podID, configJSON); err != nil { + return fmt.Errorf("adding container config to database: %w", err) + } + if _, err := tx.Exec("INSERT INTO ContainerState VALUES (?, ?, ?, ?);", ctr.ID(), int(ctr.state.State), ctr.state.ExitCode, stateJSON); err != nil { + return fmt.Errorf("adding container state to database: %w", err) + } + for _, dep := range deps { + // Check if the dependency is in the same pod + var depPod sql.NullString + row := tx.QueryRow("SELECT PodID FROM ContainerConfig WHERE ID=?;", dep) + if err := row.Scan(&depPod); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("container dependency %s does not exist in database: %w", dep, define.ErrNoSuchCtr) + } + } + switch { + case ctr.config.Pod == "" && depPod.Valid: + return fmt.Errorf("container dependency %s is part of a pod, but container is not: %w", dep, define.ErrInvalidArg) + case ctr.config.Pod != "" && !depPod.Valid: + return fmt.Errorf("container dependency %s is not part of pod, but this container belongs to pod %s: %w", dep, ctr.config.Pod, define.ErrInvalidArg) + case ctr.config.Pod != "" && depPod.String != ctr.config.Pod: + return fmt.Errorf("container dependency %s is part of pod %s but container is part of pod %s, pods must match: %w", dep, depPod.String, ctr.config.Pod, define.ErrInvalidArg) + } + + if _, err := tx.Exec("INSERT INTO ContainerDependency VALUES (?, ?);", ctr.ID(), dep); err != nil { + return fmt.Errorf("adding container dependency %s to database: %w", dep, err) + } + } + volMap := make(map[string]bool) + for _, vol := range ctr.config.NamedVolumes { + if _, ok := volMap[vol.Name]; !ok { + if _, err := tx.Exec("INSERT INTO ContainerVolume VALUES (?, ?);", ctr.ID(), vol.Name); err != nil { + return fmt.Errorf("adding container volume %s to database: %w", vol.Name, err) + } + volMap[vol.Name] = true + } + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing transaction: %w", err) + } + + return nil +} + +// removeContainer remove the specified container from the database. +func (s *SQLiteState) removeContainer(ctr *Container) (defErr error) { + tx, err := s.conn.Begin() + if err != nil { + return fmt.Errorf("beginning container %s removal transaction: %w", ctr.ID(), err) + } + + defer func() { + if defErr != nil { + if err := tx.Rollback(); err != nil { + logrus.Errorf("Rolling back transaction to remove container %s: %v", ctr.ID(), err) + } + } + }() + + if err := s.removeContainerWithTx(ctr.ID(), tx); err != nil { + return err + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("committing container %s removal transaction: %w", ctr.ID(), err) + } + + return nil +} + +// removeContainerWithTx removes the container with the specified transaction. +// Callers are responsible for committing. +func (s *SQLiteState) removeContainerWithTx(id string, tx *sql.Tx) error { + // TODO TODO TODO: + // Need to verify that at least 1 row was deleted from ContainerConfig. + // Otherwise return ErrNoSuchCtr. + if _, err := tx.Exec("DELETE FROM IDNamespace WHERE ID=?;", id); err != nil { + return fmt.Errorf("removing container %s id from database: %w", id, err) + } + if _, err := tx.Exec("DELETE FROM ContainerConfig WHERE ID=?;", id); err != nil { + return fmt.Errorf("removing container %s config from database: %w", id, err) + } + if _, err := tx.Exec("DELETE FROM ContainerState WHERE ID=?;", id); err != nil { + return fmt.Errorf("removing container %s state from database: %w", id, err) + } + if _, err := tx.Exec("DELETE FROM ContainerDependency WHERE ID=?;", id); err != nil { + return fmt.Errorf("removing container %s dependencies from database: %w", id, err) + } + if _, err := tx.Exec("DELETE FROM ContainerVolume WHERE ContainerID=?;", id); err != nil { + return fmt.Errorf("removing container %s volumes from database: %w", id, err) + } + if _, err := tx.Exec("DELETE FROM ContainerExecSession WHERE ContainerID=?;", id); err != nil { + return fmt.Errorf("removing container %s exec sessions from database: %w", id, err) + } + return nil +} + +// networkModify allows you to modify or add a new network, to add a new network use the new bool +func (s *SQLiteState) networkModify(ctr *Container, network string, opts types.PerNetworkOptions, new, disconnect bool) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if network == "" { + return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg) + } + + if new && disconnect { + return fmt.Errorf("new and disconnect are mutually exclusive: %w", define.ErrInvalidArg) + } + + // Grab a fresh copy of the config, in case anything changed + newCfg, err := s.getCtrConfig(ctr.ID()) + if err != nil && errors.Is(err, define.ErrNoSuchCtr) { + ctr.valid = false + return define.ErrNoSuchCtr + } + + _, ok := newCfg.Networks[network] + if new && ok { + return fmt.Errorf("container %s is already connected to network %s: %w", ctr.ID(), network, define.ErrNetworkConnected) + } + if !ok && (!new || disconnect) { + return fmt.Errorf("container %s is not connected to network %s: %w", ctr.ID(), network, define.ErrNoSuchNetwork) + } + + if !disconnect { + if newCfg.Networks == nil { + newCfg.Networks = make(map[string]types.PerNetworkOptions) + } + newCfg.Networks[network] = opts + } else { + delete(newCfg.Networks, network) + } + + if err := s.rewriteContainerConfig(ctr, newCfg); err != nil { + return err + } + + ctr.config = newCfg + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/state.go b/vendor/github.com/containers/podman/v5/libpod/state.go new file mode 100644 index 00000000..a07ee92a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/state.go @@ -0,0 +1,280 @@ +//go:build !remote + +package libpod + +import "go.podman.io/common/libnetwork/types" + +// State is a storage backend for libpod's current state. +// A State is only initialized once per instance of libpod. +// As such, initialization methods for State implementations may safely assume +// they will be run as a singleton. +// For all container and pod retrieval methods, a State must retrieve the +// Configuration struct of the container or pod and include it in the returned +// struct. The State of the container or pod may optionally be included as well, +// but this is not a requirement. +// As such, all containers and pods must be synced with the database via the +// UpdateContainer and UpdatePod calls before any state-specific information is +// retrieved after they are pulled from the database. +// Generally speaking, the syncContainer() call should be run at the beginning +// of all API operations, which will silently handle this. +type State interface { //nolint:interfacebloat + // Close performs any pre-exit cleanup (e.g. closing database + // connections) that may be required + Close() error + + // Refresh clears container and pod states after a reboot + Refresh() error + + // GetDBConfig retrieves several paths configured within the database + // when it was created - namely, Libpod root and tmp dirs, c/storage + // root and tmp dirs, and c/storage graph driver. + // This is not implemented by the in-memory state, as it has no need to + // validate runtime configuration. + GetDBConfig() (*DBConfig, error) + + // ValidateDBConfig validates the config in the given Runtime struct + // against paths stored in the configured database. + // Libpod root and tmp dirs and c/storage root and tmp dirs and graph + // driver are validated. + // This is not implemented by the in-memory state, as it has no need to + // validate runtime configuration that may change over multiple runs of + // the program. + ValidateDBConfig(runtime *Runtime) error + + // Resolve an ID to a Container Name. + GetContainerName(id string) (string, error) + // Resolve an ID to a Pod Name. + GetPodName(id string) (string, error) + + // Return a container from the database from its full ID. + // If the container is not in the set namespace, an error will be + // returned. + Container(id string) (*Container, error) + // Return a container ID from the database by full or partial ID or full + // name. + LookupContainerID(idOrName string) (string, error) + // Return a container from the database by full or partial ID or full + // name. + // Containers not in the set namespace will be ignored. + LookupContainer(idOrName string) (*Container, error) + // Check if a container with the given full ID exists in the database. + // If the container exists but is not in the set namespace, false will + // be returned. + HasContainer(id string) (bool, error) + // Adds container to state. + // The container cannot be part of a pod. + // The container must have globally unique name and ID - pod names and + // IDs also conflict with container names and IDs. + // The container must be in the set namespace if a namespace has been + // set. + // All containers this container depends on must be part of the same + // namespace and must not be joined to a pod. + AddContainer(ctr *Container) error + // Removes container from state. + // Containers that are part of pods must use RemoveContainerFromPod. + // The container must be part of the set namespace. + // All dependencies must be removed first. + // All exec sessions referencing the container must be removed first. + RemoveContainer(ctr *Container) error + // UpdateContainer updates a container's state from the backing store. + // The container must be part of the set namespace. + UpdateContainer(ctr *Container) error + // SaveContainer saves a container's current state to the backing store. + // The container must be part of the set namespace. + SaveContainer(ctr *Container) error + // ContainerInUse checks if other containers depend upon a given + // container. + // It returns a slice of the IDs of containers which depend on the given + // container. If the slice is empty, no container depend on the given + // container. + // A container cannot be removed if other containers depend on it. + // The container being checked must be part of the set namespace. + ContainerInUse(ctr *Container) ([]string, error) + // Retrieves all containers presently in state. + // If `loadState` is set, the containers' state will be loaded as well. + // If a namespace is set, only containers within the namespace will be + // returned. + AllContainers(loadState bool) ([]*Container, error) + + // Get networks the container is currently connected to. + GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error) + // Add the container to the given network with the given options + NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error + // Modify the container network with the given options. + NetworkModify(ctr *Container, network string, opts types.PerNetworkOptions) error + // Remove the container from the given network, removing all aliases for + // the container in that network in the process. + NetworkDisconnect(ctr *Container, network string) error + + // Return a container config from the database by full ID + GetContainerConfig(id string) (*ContainerConfig, error) + + // Add the exit code for the specified container to the database. + AddContainerExitCode(id string, exitCode int32) error + // Return the exit code for the specified container. + GetContainerExitCode(id string) (int32, error) + // Remove exit codes older than 5 minutes. + PruneContainerExitCodes() error + + // Add creates a reference to an exec session in the database. + // The container the exec session is attached to will be recorded. + // The container state will not be modified. + // The actual exec session itself is part of the container's state. + // We assume higher-level callers will add the session by saving the + // container's state before calling this. This only ensures that the ID + // of the exec session is associated with the ID of the container. + // Implementations may, but are not required to, verify that the state + // of the given container has an exec session with the ID given. + AddExecSession(ctr *Container, session *ExecSession) error + // Get retrieves the container a given exec session is attached to. + GetExecSession(id string) (string, error) + // Remove a reference to an exec session from the database. + // This will not modify container state to remove the exec session there + // and instead only removes the session ID -> container ID reference + // added by AddExecSession. + RemoveExecSession(session *ExecSession) error + // Get the IDs of all exec sessions attached to a given container. + GetContainerExecSessions(ctr *Container) ([]string, error) + // Remove all exec sessions for a single container. + // Usually used as part of removing the container. + // As with RemoveExecSession, container state will not be modified. + RemoveContainerExecSessions(ctr *Container) error + + // ContainerIDIsVolume checks if the given container ID is in use by a + // volume. + // Some volumes are backed by a c/storage container. These do not have a + // corresponding Container struct in Libpod, but rather a Volume. + // This determines if a given ID from c/storage is used as a backend by + // a Podman volume. + ContainerIDIsVolume(id string) (bool, error) + + // PLEASE READ FULL DESCRIPTION BEFORE USING. + // Rewrite a container's configuration. + // This function breaks libpod's normal prohibition on a read-only + // configuration, and as such should be used EXTREMELY SPARINGLY and + // only in very specific circumstances. + // Specifically, it is ONLY safe to use thing function to make changes + // that result in a functionally identical configuration (migrating to + // newer, but identical, configuration fields), or during libpod init + // WHILE HOLDING THE ALIVE LOCK (to prevent other libpod instances from + // being initialized). + // Most things in config can be changed by this, but container ID and + // name ABSOLUTELY CANNOT BE ALTERED. If you do so, there is a high + // potential for database corruption. + // There are a lot of capital letters and conditions here, but the short + // answer is this: use this only very sparingly, and only if you really + // know what you're doing. + // TODO: Once BoltDB is removed, RewriteContainerConfig and + // SafeRewriteContainerConfig can be merged. + RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error + // This is a more limited version of RewriteContainerConfig, though it + // comes with the added ability to alter a container's name. In exchange + // it loses the ability to manipulate the container's locks. + // It is not intended to be as restrictive as RewriteContainerConfig, in + // that we allow it to be run while other Podman processes are running, + // and without holding the alive lock. + // Container ID and pod membership still *ABSOLUTELY CANNOT* be altered. + // Also, you cannot change a container's dependencies - shared namespace + // containers or generic dependencies - at present. This is + // theoretically possible but not yet implemented. + // If newName is not "" the container will be renamed to the new name. + // The oldName parameter is only required if newName is given. + SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error + // PLEASE READ THE DESCRIPTION FOR RewriteContainerConfig BEFORE USING. + // This function is identical to RewriteContainerConfig, save for the + // fact that it is used with pods instead. + // It is subject to the same conditions as RewriteContainerConfig. + // Please do not use this unless you know what you're doing. + RewritePodConfig(pod *Pod, newCfg *PodConfig) error + // PLEASE READ THE DESCRIPTION FOR RewriteContainerConfig BEFORE USING. + // This function is identical to RewriteContainerConfig, save for the + // fact that it is used with volumes instead. + // It is subject to the same conditions as RewriteContainerConfig. + // The exception is that volumes do not have IDs, so only volume name + // cannot be altered. + // Please do not use this unless you know what you're doing. + RewriteVolumeConfig(volume *Volume, newCfg *VolumeConfig) error + + // Accepts full ID of pod. + // If the pod given is not in the set namespace, an error will be + // returned. + Pod(id string) (*Pod, error) + // Accepts full or partial IDs (as long as they are unique) and names. + // Pods not in the set namespace are ignored. + LookupPod(idOrName string) (*Pod, error) + // Checks if a pod with the given ID is present in the state. + // If the given pod is not in the set namespace, false is returned. + HasPod(id string) (bool, error) + // Check if a pod has a container with the given ID. + // The pod must be part of the set namespace. + PodHasContainer(pod *Pod, ctrID string) (bool, error) + // Get the IDs of all containers in a pod. + // The pod must be part of the set namespace. + PodContainersByID(pod *Pod) ([]string, error) + // Get all the containers in a pod. + // The pod must be part of the set namespace. + PodContainers(pod *Pod) ([]*Container, error) + // Adds pod to state. + // The pod must be part of the set namespace. + // The pod's name and ID must be globally unique. + AddPod(pod *Pod) error + // Removes pod from state. + // Only empty pods can be removed from the state. + // The pod must be part of the set namespace. + RemovePod(pod *Pod) error + // Remove all containers from a pod. + // Used to simultaneously remove containers that might otherwise have + // dependency issues. + // Will fail if a dependency outside the pod is encountered. + // The pod must be part of the set namespace. + RemovePodContainers(pod *Pod) error + // AddContainerToPod adds a container to an existing pod. + // The container given will be added to the state and the pod. + // The container and its dependencies must be part of the given pod, + // and the given pod's namespace. + // The pod must be part of the set namespace. + // The pod must already exist in the state. + // The container's name and ID must be globally unique. + AddContainerToPod(pod *Pod, ctr *Container) error + // RemoveContainerFromPod removes a container from an existing pod. + // The container will also be removed from the state. + // The container must be in the given pod, and the pod must be in the + // set namespace. + RemoveContainerFromPod(pod *Pod, ctr *Container) error + // UpdatePod updates a pod's state from the database. + // The pod must be in the set namespace. + UpdatePod(pod *Pod) error + // SavePod saves a pod's state to the database. + // The pod must be in the set namespace. + SavePod(pod *Pod) error + // Retrieves all pods presently in state. + // If a namespace has been set, only pods in that namespace will be + // returned. + AllPods() ([]*Pod, error) + + // Volume accepts full name of volume + // If the volume doesn't exist, an error will be returned + Volume(volName string) (*Volume, error) + // LookupVolume accepts an unambiguous partial name or full name of a + // volume. Ambiguous names will result in an error. + LookupVolume(name string) (*Volume, error) + // HasVolume returns true if volName exists in the state, + // otherwise it returns false + HasVolume(volName string) (bool, error) + // VolumeInUse goes through the container dependencies of a volume + // and checks if the volume is being used by any container. If it is + // a slice of container IDs using the volume is returned + VolumeInUse(volume *Volume) ([]string, error) + // AddVolume adds the specified volume to state. The volume's name + // must be unique within the list of existing volumes + AddVolume(volume *Volume) error + // RemoveVolume removes the specified volume. + // Only volumes that have no container dependencies can be removed + RemoveVolume(volume *Volume) error + // UpdateVolume updates the volume's state from the database. + UpdateVolume(volume *Volume) error + // SaveVolume saves a volume's state to the database. + SaveVolume(volume *Volume) error + // AllVolumes returns all the volumes available in the state + AllVolumes() ([]*Volume, error) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/stats_common.go b/vendor/github.com/containers/podman/v5/libpod/stats_common.go new file mode 100644 index 00000000..9d301a69 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/stats_common.go @@ -0,0 +1,59 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "fmt" + + "github.com/containers/podman/v5/libpod/define" +) + +// GetContainerStats gets the running stats for a given container. +// The previousStats is used to correctly calculate cpu percentages. You +// should pass nil if there is no previous stat for this container. +func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) { + stats := new(define.ContainerStats) + stats.ContainerID = c.ID() + stats.Name = c.Name() + + if c.config.NoCgroups { + return nil, fmt.Errorf("cannot run top on container %s as it did not create a cgroup: %w", c.ID(), define.ErrNoCgroups) + } + + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + if err := c.syncContainer(); err != nil { + return stats, err + } + } + + // returns stats with the fields' default values respective of their type + if c.state.State != define.ContainerStateRunning && c.state.State != define.ContainerStatePaused { + return stats, nil + } + + if previousStats == nil { + previousStats = &define.ContainerStats{ + // if we have no prev stats use the container start time as prev time + // otherwise we cannot correctly calculate the CPU percentage + SystemNano: uint64(c.state.StartedTime.UnixNano()), + } + } + + netStats, err := getContainerNetIO(c) + if err != nil { + return nil, err + } + stats.Network = netStats + + if err := c.getPlatformContainerStats(stats, previousStats); err != nil { + return nil, err + } + return stats, nil +} + +// GetOnlineCPUs returns the number of online CPUs as set in the container cpu-set using sched_getaffinity +func GetOnlineCPUs(container *Container) (int, error) { + return getOnlineCPUs(container) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/stats_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/stats_freebsd.go new file mode 100644 index 00000000..2f7dca63 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/stats_freebsd.go @@ -0,0 +1,136 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "math" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rctl" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/system" + "golang.org/x/sys/unix" +) + +// getPlatformContainerStats gets the platform-specific running stats +// for a given container. The previousStats is used to correctly +// calculate cpu percentages. You should pass nil if there is no +// previous stat for this container. +func (c *Container) getPlatformContainerStats(stats *define.ContainerStats, previousStats *define.ContainerStats) error { + now := uint64(time.Now().UnixNano()) + + jailName, err := c.jailName() + if err != nil { + return fmt.Errorf("getting jail name: %w", err) + } + + entries, err := rctl.GetRacct("jail:" + jailName) + if err != nil { + return fmt.Errorf("unable to read accounting for %s: %w", jailName, err) + } + + // If the current total usage is less than what was previously + // recorded then it means the container was restarted and runs + // in a new jail + if dur, ok := entries["wallclock"]; ok { + if previousStats.Duration > dur*1000000000 { + previousStats = &define.ContainerStats{} //nolint:wastedassign // TODO: figure this out. + } + } + + for key, val := range entries { + switch key { + case "cputime": // CPU time, in seconds + stats.CPUNano = val * 1000000000 + stats.AvgCPU = calculateCPUPercent(stats.CPUNano, 0, now, uint64(c.state.StartedTime.UnixNano())) + case "datasize": // data size, in bytes + case "stacksize": // stack size, in bytes + case "coredumpsize": // core dump size, in bytes + case "memoryuse": // resident set size, in bytes + stats.MemUsage = val + case "memorylocked": // locked memory, in bytes + case "maxproc": // number of processes + stats.PIDs = val + case "openfiles": // file descriptor table size + case "vmemoryuse": // address space limit, in bytes + case "pseudoterminals": // number of PTYs + case "swapuse": // swap space that may be reserved or used, in bytes + case "nthr": // number of threads + case "msgqqueued": // number of queued SysV messages + case "msgqsize": // SysV message queue size, in bytes + case "nmsgq": // number of SysV message queues + case "nsem": // number of SysV semaphores + case "nsemop": // number of SysV semaphores modified in a single semop(2) call + case "nshm": // number of SysV shared memory segments + case "shmsize": // SysV shared memory size, in bytes + case "wallclock": // wallclock time, in seconds + stats.Duration = val * 1000000000 + stats.UpTime = time.Duration(stats.Duration) + case "pcpu": // %CPU, in percents of a single CPU core + stats.CPU = float64(val) + case "readbps": // filesystem reads, in bytes per second + stats.BlockInput = val + case "writebps": // filesystem writes, in bytes per second + stats.BlockOutput = val + case "readiops": // filesystem reads, in operations per second + case "writeiops": // filesystem writes, in operations per second + } + } + stats.MemLimit = c.getMemLimit() + stats.SystemNano = now + + return nil +} + +// getMemLimit returns the memory limit for a container +func (c *Container) getMemLimit() uint64 { + memLimit := uint64(math.MaxUint64) + + resources := c.LinuxResources() + if resources != nil && resources.Memory != nil && resources.Memory.Limit != nil { + memLimit = uint64(*resources.Memory.Limit) + } + + mi, err := system.ReadMemInfo() + if err != nil { + logrus.Errorf("ReadMemInfo error: %v", err) + return 0 + } + + physicalLimit := uint64(mi.MemTotal) + + if memLimit <= 0 || memLimit > physicalLimit { + return physicalLimit + } + + return memLimit +} + +// calculateCPUPercent calculates the cpu usage using the latest measurement in stats. +// previousCPU is the last value of stats.CPU.Usage.Total measured at the time previousSystem. +// +// (now - previousSystem) is the time delta in nanoseconds, between the measurement in previousCPU +// +// and the updated value in stats. +func calculateCPUPercent(currentCPU, previousCPU, now, previousSystem uint64) float64 { + var ( + cpuPercent = 0.0 + cpuDelta = float64(currentCPU - previousCPU) + systemDelta = float64(now - previousSystem) + ) + if systemDelta > 0.0 && cpuDelta > 0.0 { + // gets a ratio of container cpu usage total, and multiplies that by 100 to get a percentage + cpuPercent = (cpuDelta / systemDelta) * 100 + } + return cpuPercent +} + +func getOnlineCPUs(container *Container) (int, error) { + if container.state.State != define.ContainerStateRunning { + return -1, fmt.Errorf("container %s is not running: %w", container.ID(), define.ErrCtrStopped) + } + n, err := unix.SysctlUint32("hw.ncpu") + return int(n), err +} diff --git a/vendor/github.com/containers/podman/v5/libpod/stats_linux.go b/vendor/github.com/containers/podman/v5/libpod/stats_linux.go new file mode 100644 index 00000000..8ee32cdd --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/stats_linux.go @@ -0,0 +1,139 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "fmt" + "strings" + "syscall" + "time" + + "github.com/containers/podman/v5/libpod/define" + runccgroup "github.com/opencontainers/cgroups" + "go.podman.io/common/pkg/cgroups" + "golang.org/x/sys/unix" +) + +// getPlatformContainerStats gets the platform-specific running stats +// for a given container. The previousStats is used to correctly +// calculate cpu percentages. You should pass nil if there is no +// previous stat for this container. +func (c *Container) getPlatformContainerStats(stats *define.ContainerStats, previousStats *define.ContainerStats) error { + if c.config.NoCgroups { + return fmt.Errorf("cannot run top on container %s as it did not create a cgroup: %w", c.ID(), define.ErrNoCgroups) + } + + cgroupPath, err := c.cGroupPath() + if err != nil { + return err + } + cgroup, err := cgroups.Load(cgroupPath) + if err != nil { + return fmt.Errorf("unable to load cgroup at %s: %w", cgroupPath, err) + } + + // Ubuntu does not have swap memory in cgroups because swap is often not enabled. + cgroupStats, err := cgroup.Stat() + if err != nil { + // cgroup.Stat() is not an atomic operation, so it is possible that the cgroup is removed + // while Stat() is running. Try to catch this case and return a more specific error. + if (errors.Is(err, cgroups.ErrStatCgroup) || errors.Is(err, unix.ENODEV)) && !cgroupExist(cgroupPath) { + return fmt.Errorf("cgroup %s does not exist: %w", cgroupPath, define.ErrCtrStopped) + } + return fmt.Errorf("unable to obtain cgroup stats: %w", err) + } + conState := c.state.State + + // If the current total usage in the cgroup is less than what was previously + // recorded then it means the container was restarted and runs in a new cgroup + if previousStats.Duration > cgroupStats.CpuStats.CpuUsage.TotalUsage { + previousStats = &define.ContainerStats{} + } + + previousCPU := previousStats.CPUNano + now := uint64(time.Now().UnixNano()) + stats.Duration = cgroupStats.CpuStats.CpuUsage.TotalUsage + stats.UpTime = time.Duration(stats.Duration) + stats.CPU = calculateCPUPercent(cgroupStats, previousCPU, now, previousStats.SystemNano) + // calc the average cpu usage for the time the container is running + stats.AvgCPU = calculateCPUPercent(cgroupStats, 0, now, uint64(c.state.StartedTime.UnixNano())) + stats.MemUsage = cgroupStats.MemoryStats.Usage.Usage + stats.MemLimit = c.getMemLimit(cgroupStats.MemoryStats.Usage.Limit) + stats.MemPerc = (float64(stats.MemUsage) / float64(stats.MemLimit)) * 100 + stats.PIDs = 0 + if conState == define.ContainerStateRunning || conState == define.ContainerStatePaused { + stats.PIDs = cgroupStats.PidsStats.Current + } + stats.BlockInput, stats.BlockOutput = calculateBlockIO(cgroupStats) + stats.CPUNano = cgroupStats.CpuStats.CpuUsage.TotalUsage + stats.CPUSystemNano = cgroupStats.CpuStats.CpuUsage.UsageInKernelmode + stats.SystemNano = now + stats.PerCPU = cgroupStats.CpuStats.CpuUsage.PercpuUsage + + return nil +} + +// getMemLimit returns the memory limit for a container +func (c *Container) getMemLimit(memLimit uint64) uint64 { + si := &syscall.Sysinfo_t{} + err := syscall.Sysinfo(si) + if err != nil { + return memLimit + } + + //nolint:unconvert + physicalLimit := uint64(si.Totalram) + + if memLimit <= 0 || memLimit > physicalLimit { + return physicalLimit + } + + return memLimit +} + +// calculateCPUPercent calculates the cpu usage using the latest measurement in stats. +// previousCPU is the last value of stats.CPU.Usage.Total measured at the time previousSystem. +// +// (now - previousSystem) is the time delta in nanoseconds, between the measurement in previousCPU +// +// and the updated value in stats. +func calculateCPUPercent(stats *runccgroup.Stats, previousCPU, now, previousSystem uint64) float64 { + var ( + cpuPercent = 0.0 + cpuDelta = float64(stats.CpuStats.CpuUsage.TotalUsage - previousCPU) + systemDelta = float64(now - previousSystem) + ) + if systemDelta > 0.0 && cpuDelta > 0.0 { + // gets a ratio of container cpu usage total, and multiplies that by 100 to get a percentage + cpuPercent = (cpuDelta / systemDelta) * 100 + } + return cpuPercent +} + +func calculateBlockIO(stats *runccgroup.Stats) (read uint64, write uint64) { + for _, blkIOEntry := range stats.BlkioStats.IoServiceBytesRecursive { + switch strings.ToLower(blkIOEntry.Op) { + case "read": + read += blkIOEntry.Value + case "write": + write += blkIOEntry.Value + } + } + return +} + +func getOnlineCPUs(container *Container) (int, error) { + ctrPID, err := container.PID() + if err != nil { + return -1, fmt.Errorf("failed to obtain Container %s PID: %w", container.Name(), err) + } + if ctrPID == 0 { + return ctrPID, define.ErrCtrStopped + } + var cpuSet unix.CPUSet + if err := unix.SchedGetaffinity(ctrPID, &cpuSet); err != nil { + return -1, fmt.Errorf("failed to obtain Container %s online cpus: %w", container.Name(), err) + } + return cpuSet.Count(), nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/storage.go b/vendor/github.com/containers/podman/v5/libpod/storage.go new file mode 100644 index 00000000..0acfc2af --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/storage.go @@ -0,0 +1,309 @@ +//go:build !remote + +package libpod + +import ( + "context" + "errors" + "time" + + "github.com/containers/podman/v5/libpod/define" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" + istorage "go.podman.io/image/v5/storage" + "go.podman.io/image/v5/types" + "go.podman.io/storage" + "go.podman.io/storage/pkg/idtools" +) + +type storageService struct { + store storage.Store +} + +// getStorageService returns a storageService which can create container root +// filesystems from images +func getStorageService(store storage.Store) *storageService { + return &storageService{store: store} +} + +// ContainerInfo wraps a subset of information about a container: the locations +// of its nonvolatile and volatile per-container directories, along with a copy +// of the configuration blob from the image that was used to create the +// container, if the image had a configuration. +// It also returns the ProcessLabel and MountLabel selected for the container +type ContainerInfo struct { + Dir string + RunDir string + Config *v1.Image + ProcessLabel string + MountLabel string + UIDMap []idtools.IDMap + GIDMap []idtools.IDMap +} + +// RuntimeContainerMetadata is the structure that we encode as JSON and store +// in the metadata field of storage.Container objects. It is used for +// specifying attributes containers when they are being created, and allows a +// container's MountLabel, and possibly other values, to be modified in one +// read/write cycle via calls to storageService.ContainerMetadata, +// RuntimeContainerMetadata.SetMountLabel, and +// storageService.SetContainerMetadata. +type RuntimeContainerMetadata struct { + // The provided name and the ID of the image that was used to + // instantiate the container. + ImageName string `json:"image-name"` // Applicable to both PodSandboxes and Containers + ImageID string `json:"image-id"` // Applicable to both PodSandboxes and Containers + // The container's name, which for an infrastructure container is usually PodName + "-infra". + ContainerName string `json:"name"` // Applicable to both PodSandboxes and Containers, mandatory + CreatedAt int64 `json:"created-at"` // Applicable to both PodSandboxes and Containers + MountLabel string `json:"mountlabel,omitempty"` // Applicable to both PodSandboxes and Containers +} + +// SetMountLabel updates the mount label held by a RuntimeContainerMetadata +// object. +func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) { + metadata.MountLabel = mountLabel +} + +// CreateContainerStorage creates the storage end of things. We already have the container spec created. +// imageID and imageName must both be either empty or non-empty. +// TO-DO We should be passing in an Image object in the future. +func (r *storageService) CreateContainerStorage(ctx context.Context, systemContext *types.SystemContext, imageName, imageID, containerName, containerID string, options storage.ContainerOptions) (_ ContainerInfo, retErr error) { + var imageConfig *v1.Image + if imageID != "" { + if containerName == "" { + return ContainerInfo{}, define.ErrEmptyID + } + // Check if we have the specified image. + ref, err := istorage.Transport.NewStoreReference(r.store, nil, imageID) + if err != nil { + return ContainerInfo{}, err + } + // Pull out a copy of the image's configuration. + image, err := ref.NewImage(ctx, systemContext) + if err != nil { + return ContainerInfo{}, err + } + defer image.Close() + + // Get OCI configuration of image + imageConfig, err = image.OCIConfig(ctx) + if err != nil { + return ContainerInfo{}, err + } + } + + // Build metadata to store with the container. + metadata := RuntimeContainerMetadata{ + ImageName: imageName, + ImageID: imageID, + ContainerName: containerName, + CreatedAt: time.Now().Unix(), + } + mdata, err := json.Marshal(&metadata) + if err != nil { + return ContainerInfo{}, err + } + + // Build the container. + names := []string{containerName} + + container, err := r.store.CreateContainer(containerID, names, imageID, "", string(mdata), &options) + if err != nil { + logrus.Debugf("Failed to create container %s(%s): %v", metadata.ContainerName, containerID, err) + + return ContainerInfo{}, err + } + logrus.Debugf("Created container %q", container.ID) + + // If anything fails after this point, we need to delete the incomplete + // container before returning. + defer func() { + if retErr != nil { + if err := r.store.DeleteContainer(container.ID); err != nil { + logrus.Infof("Error deleting partially-created container %q: %v", container.ID, err) + + return + } + logrus.Infof("Deleted partially-created container %q", container.ID) + } + }() + + // Add a name to the container's layer so that it's easier to follow + // what's going on if we're just looking at the storage-eye view of things. + layerName := metadata.ContainerName + "-layer" + names, err = r.store.Names(container.LayerID) + if err != nil { + return ContainerInfo{}, err + } + names = append(names, layerName) + err = r.store.SetNames(container.LayerID, names) + if err != nil { + return ContainerInfo{}, err + } + + // Find out where the container work directories are, so that we can return them. + containerDir, err := r.store.ContainerDirectory(container.ID) + if err != nil { + return ContainerInfo{}, err + } + logrus.Debugf("Container %q has work directory %q", container.ID, containerDir) + + containerRunDir, err := r.store.ContainerRunDirectory(container.ID) + if err != nil { + return ContainerInfo{}, err + } + logrus.Debugf("Container %q has run directory %q", container.ID, containerRunDir) + + return ContainerInfo{ + UIDMap: container.UIDMap, + GIDMap: container.GIDMap, + Dir: containerDir, + RunDir: containerRunDir, + Config: imageConfig, + ProcessLabel: container.ProcessLabel(), + MountLabel: container.MountLabel(), + }, nil +} + +func (r *storageService) DeleteContainer(idOrName string) error { + if idOrName == "" { + return define.ErrEmptyID + } + container, err := r.store.Container(idOrName) + if err != nil { + return err + } + err = r.store.DeleteContainer(container.ID) + if err != nil { + if errors.Is(err, storage.ErrNotAContainer) || errors.Is(err, storage.ErrContainerUnknown) { + logrus.Infof("Storage for container %s already removed", container.ID) + } else { + logrus.Debugf("Failed to delete container %q: %v", container.ID, err) + return err + } + } + return nil +} + +func (r *storageService) SetContainerMetadata(idOrName string, metadata RuntimeContainerMetadata) error { + mdata, err := json.Marshal(&metadata) + if err != nil { + logrus.Debugf("Failed to encode metadata for %q: %v", idOrName, err) + return err + } + return r.store.SetMetadata(idOrName, string(mdata)) +} + +func (r *storageService) GetContainerMetadata(idOrName string) (RuntimeContainerMetadata, error) { + metadata := RuntimeContainerMetadata{} + mdata, err := r.store.Metadata(idOrName) + if err != nil { + return metadata, err + } + if err = json.Unmarshal([]byte(mdata), &metadata); err != nil { + return metadata, err + } + return metadata, nil +} + +func (r *storageService) MountContainerImage(idOrName string) (string, error) { + container, err := r.store.Container(idOrName) + if err != nil { + if errors.Is(err, storage.ErrContainerUnknown) { + return "", define.ErrNoSuchCtr + } + return "", err + } + metadata := RuntimeContainerMetadata{} + if err = json.Unmarshal([]byte(container.Metadata), &metadata); err != nil { + return "", err + } + mountPoint, err := r.store.Mount(container.ID, metadata.MountLabel) + if err != nil { + logrus.Debugf("Failed to mount container %q: %v", container.ID, err) + return "", err + } + logrus.Debugf("Mounted container %q at %q", container.ID, mountPoint) + return mountPoint, nil +} + +func (r *storageService) UnmountContainerImage(idOrName string, force bool) (bool, error) { + if idOrName == "" { + return false, define.ErrEmptyID + } + container, err := r.store.Container(idOrName) + if err != nil { + return false, err + } + + if !force { + mounted, err := r.store.Mounted(container.ID) + if err != nil { + return false, err + } + if mounted == 0 { + return false, storage.ErrLayerNotMounted + } + } + mounted, err := r.store.Unmount(container.ID, force) + if err != nil { + logrus.Debugf("Failed to unmount container %q: %v", container.ID, err) + return false, err + } + logrus.Debugf("Unmounted container %q", container.ID) + return mounted, nil +} + +func (r *storageService) MountedContainerImage(idOrName string) (int, error) { + if idOrName == "" { + return 0, define.ErrEmptyID + } + container, err := r.store.Container(idOrName) + if err != nil { + return 0, err + } + mounted, err := r.store.Mounted(container.ID) + if err != nil { + return 0, err + } + return mounted, nil +} + +func (r *storageService) GetMountpoint(id string) (string, error) { + container, err := r.store.Container(id) + if err != nil { + if errors.Is(err, storage.ErrContainerUnknown) { + return "", define.ErrNoSuchCtr + } + return "", err + } + layer, err := r.store.Layer(container.LayerID) + if err != nil { + return "", err + } + + return layer.MountPoint, nil +} + +func (r *storageService) GetWorkDir(id string) (string, error) { + container, err := r.store.Container(id) + if err != nil { + if errors.Is(err, storage.ErrContainerUnknown) { + return "", define.ErrNoSuchCtr + } + return "", err + } + return r.store.ContainerDirectory(container.ID) +} + +func (r *storageService) GetRunDir(id string) (string, error) { + container, err := r.store.Container(id) + if err != nil { + if errors.Is(err, storage.ErrContainerUnknown) { + return "", define.ErrNoSuchCtr + } + return "", err + } + return r.store.ContainerRunDirectory(container.ID) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/util.go b/vendor/github.com/containers/podman/v5/libpod/util.go new file mode 100644 index 00000000..a6b76f5d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/util.go @@ -0,0 +1,294 @@ +//go:build !remote + +package libpod + +import ( + "bufio" + "encoding/binary" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/api/handlers/utils/apiutil" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" + "go.podman.io/storage/pkg/fileutils" + "golang.org/x/sys/unix" +) + +// MountExists returns true if dest exists in the list of mounts +func MountExists(specMounts []spec.Mount, dest string) bool { + for _, m := range specMounts { + if m.Destination == dest { + return true + } + } + return false +} + +func parts(m spec.Mount) int { + // We must special case a root mount /. + // The count of "/" and "/proc" are both 1 but of course logically "/" must + // be mounted before "/proc" as such set the count to 0. + if m.Destination == "/" { + return 0 + } + return strings.Count(filepath.Clean(m.Destination), string(os.PathSeparator)) +} + +func sortMounts(m []spec.Mount) []spec.Mount { + slices.SortStableFunc(m, func(a, b spec.Mount) int { + aLen := parts(a) + bLen := parts(b) + if aLen < bLen { + return -1 + } + if aLen == bLen { + return 0 + } + return 1 + }) + return m +} + +// JSONDeepCopy performs a deep copy by performing a JSON encode/decode of the +// given structures. From and To should be identically typed structs. +func JSONDeepCopy(from, to any) error { + tmp, err := json.Marshal(from) + if err != nil { + return err + } + return json.Unmarshal(tmp, to) +} + +// DefaultSeccompPath returns the path to the default seccomp.json file +// if it exists, first it checks OverrideSeccomp and then default. +// If neither exist function returns "" +func DefaultSeccompPath() (string, error) { + def, err := config.Default() + if err != nil { + return "", err + } + if def.Containers.SeccompProfile != "" { + return def.Containers.SeccompProfile, nil + } + + err = fileutils.Exists(config.SeccompOverridePath) + if err == nil { + return config.SeccompOverridePath, nil + } + if !os.IsNotExist(err) { + return "", err + } + if err := fileutils.Exists(config.SeccompDefaultPath); err != nil { + if !os.IsNotExist(err) { + return "", err + } + return "", nil + } + return config.SeccompDefaultPath, nil +} + +// CheckDependencyContainer verifies the given container can be used as a +// dependency of another container. +// Both the dependency to check and the container that will be using the +// dependency must be passed in. +// It is assumed that ctr is locked, and depCtr is unlocked. +func checkDependencyContainer(depCtr, ctr *Container) error { + state, err := depCtr.State() + if err != nil { + return fmt.Errorf("accessing dependency container %s state: %w", depCtr.ID(), err) + } + if state == define.ContainerStateRemoving { + return fmt.Errorf("cannot use container %s as a dependency as it is being removed: %w", depCtr.ID(), define.ErrCtrStateInvalid) + } + + if depCtr.ID() == ctr.ID() { + return fmt.Errorf("must specify another container: %w", define.ErrInvalidArg) + } + + if ctr.config.Pod != "" && depCtr.PodID() != ctr.config.Pod { + return fmt.Errorf("container has joined pod %s and dependency container %s is not a member of the pod: %w", ctr.config.Pod, depCtr.ID(), define.ErrInvalidArg) + } + + return nil +} + +// hijackWriteError writes an error to a hijacked HTTP session. +func hijackWriteError(toWrite error, cid string, terminal bool, httpBuf *bufio.ReadWriter) { + if toWrite != nil && !errors.Is(toWrite, define.ErrDetach) { + errString := fmt.Appendf(nil, "Error: %v\n", toWrite) + if !terminal { + // We need a header. + header := makeHTTPAttachHeader(2, uint32(len(errString))) + if _, err := httpBuf.Write(header); err != nil { + logrus.Errorf("Writing header for container %s attach connection error: %v", cid, err) + } + } + if _, err := httpBuf.Write(errString); err != nil { + logrus.Errorf("Writing error to container %s HTTP attach connection: %v", cid, err) + } + if err := httpBuf.Flush(); err != nil { + logrus.Errorf("Flushing HTTP buffer for container %s HTTP attach connection: %v", cid, err) + } + } +} + +// hijackWriteErrorAndClose writes an error to a hijacked HTTP session and +// closes it. Intended to HTTPAttach function. +// If error is nil, it will not be written; we'll only close the connection. +func hijackWriteErrorAndClose(toWrite error, cid string, terminal bool, httpCon io.Closer, httpBuf *bufio.ReadWriter) { + hijackWriteError(toWrite, cid, terminal, httpBuf) + + if err := httpCon.Close(); err != nil { + logrus.Errorf("Closing container %s HTTP attach connection: %v", cid, err) + } +} + +// makeHTTPAttachHeader makes an 8-byte HTTP header for a buffer of the given +// length and stream. Accepts an integer indicating which stream we are sending +// to (STDIN = 0, STDOUT = 1, STDERR = 2). +func makeHTTPAttachHeader(stream byte, length uint32) []byte { + header := make([]byte, 8) + header[0] = stream + binary.BigEndian.PutUint32(header[4:], length) + return header +} + +// writeHijackHeader writes a header appropriate for the type of HTTP Hijack +// that occurred in a hijacked HTTP connection used for attach. +func writeHijackHeader(r *http.Request, conn io.Writer, tty bool) { + // AttachHeader is the literal header sent for upgraded/hijacked connections for + // attach, sourced from Docker at: + // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go + // Using literally to ensure compatibility with existing clients. + + // New docker API uses a different header for the non tty case. + // Lets do the same for libpod. Only do this for the new api versions to not break older clients. + header := "application/vnd.docker.raw-stream" + if !tty { + version := "4.7.0" + if !apiutil.IsLibpodRequest(r) { + version = "1.42.0" // docker only used two digest "1.42" but our semver lib needs the extra .0 to work + } + if _, err := apiutil.SupportedVersion(r, ">= "+version); err == nil { + header = "application/vnd.docker.multiplexed-stream" + } + } + + c := r.Header.Get("Connection") + proto := r.Header.Get("Upgrade") + if len(proto) == 0 || !strings.EqualFold(c, "Upgrade") { + // OK - can't upgrade if not requested or protocol is not specified + fmt.Fprintf(conn, + "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n", header) + } else { + // Upgraded + fmt.Fprintf(conn, + "HTTP/1.1 101 UPGRADED\r\nContent-Type: %s\r\nConnection: Upgrade\r\nUpgrade: %s\r\n\r\n", + header, proto) + } +} + +// Generate inspect-formatted port mappings from the format used in our config file +func makeInspectPortBindings(bindings []types.PortMapping) map[string][]define.InspectHostPort { + portBindings := make(map[string][]define.InspectHostPort) + for _, port := range bindings { + for protocol := range strings.SplitSeq(port.Protocol, ",") { + for i := uint16(0); i < port.Range; i++ { + key := fmt.Sprintf("%d/%s", port.ContainerPort+i, protocol) + hostPorts := portBindings[key] + var hostIP = port.HostIP + if len(port.HostIP) == 0 { + hostIP = "0.0.0.0" + } + hostPorts = append(hostPorts, define.InspectHostPort{ + HostIP: hostIP, + HostPort: strconv.FormatUint(uint64(port.HostPort+i), 10), + }) + portBindings[key] = hostPorts + } + } + } + + return portBindings +} + +// Add exposed ports to inspect port bindings. These must be done on a per-container basis, not per-netns basis. +func addInspectPortsExpose(expose map[uint16][]string, portBindings map[string][]define.InspectHostPort) { + for port, protocols := range expose { + for _, protocol := range protocols { + key := fmt.Sprintf("%d/%s", port, protocol) + if _, ok := portBindings[key]; !ok { + portBindings[key] = nil + } + } + } +} + +// Write a given string to a new file at a given path. +// Will error if a file with the given name already exists. +// Will be chown'd to the UID/GID provided and have the provided SELinux label +// set. +func writeStringToPath(path, contents, mountLabel string, uid, gid int) error { + f, err := os.Create(path) + if err != nil { + return fmt.Errorf("unable to create %s: %w", path, err) + } + defer f.Close() + if err := f.Chown(uid, gid); err != nil { + return err + } + + if _, err := f.WriteString(contents); err != nil { + return fmt.Errorf("unable to write %s: %w", path, err) + } + // Relabel runDirResolv for the container + if err := label.Relabel(path, mountLabel, false); err != nil { + if errors.Is(err, unix.ENOTSUP) { + logrus.Debugf("Labeling not supported on %q", path) + return nil + } + return err + } + + return nil +} + +// If the given path exists, evaluate any symlinks in it. If it does not, clean +// the path and return it. Used to try and verify path equality in a somewhat +// sane fashion. +func evalSymlinksIfExists(toCheck string) (string, error) { + checkedVal, err := filepath.EvalSymlinks(toCheck) + if err != nil { + // If the error is not ENOENT, something more serious has gone + // wrong, return it. + if !errors.Is(err, fs.ErrNotExist) { + return "", err + } + // This is an ENOENT. On ENOENT, EvalSymlinks returns "". + // We don't want that. Return a cleaned version of the original + // path. + return filepath.Clean(toCheck), nil + } + return checkedVal, nil +} + +func isDirectory(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + return info.IsDir() +} diff --git a/vendor/github.com/containers/podman/v5/libpod/util_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/util_freebsd.go new file mode 100644 index 00000000..98af9a64 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/util_freebsd.go @@ -0,0 +1,26 @@ +//go:build !remote + +package libpod + +import ( + "syscall" + + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// No equivalent on FreeBSD? +func LabelVolumePath(_, _ string) error { + return nil +} + +// Unmount umounts a target directory +func Unmount(mount string) { + if err := unix.Unmount(mount, unix.MNT_FORCE); err != nil { + if err != syscall.EINVAL { + logrus.Warnf("Failed to unmount %s : %v", mount, err) + } else { + logrus.Debugf("failed to unmount %s : %v", mount, err) + } + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/util_linux.go b/vendor/github.com/containers/podman/v5/libpod/util_linux.go new file mode 100644 index 00000000..ead130b2 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/util_linux.go @@ -0,0 +1,165 @@ +//go:build !remote + +package libpod + +import ( + "errors" + "fmt" + "path/filepath" + "strings" + "syscall" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/storage/pkg/fileutils" + "golang.org/x/sys/unix" +) + +func cgroupExist(path string) bool { + cgroupv2, _ := cgroups.IsCgroup2UnifiedMode() + var fullPath string + if cgroupv2 { + fullPath = filepath.Join("/sys/fs/cgroup", path) + } else { + fullPath = filepath.Join("/sys/fs/cgroup/memory", path) + } + return fileutils.Exists(fullPath) == nil +} + +// systemdSliceFromPath makes a new systemd slice under the given parent with +// the given name. +// The parent must be a slice. The name must NOT include ".slice" +func systemdSliceFromPath(parent, name string, resources *spec.LinuxResources) (string, error) { + cgroupPath, systemdPath, err := assembleSystemdCgroupName(parent, name) + if err != nil { + return "", err + } + + logrus.Debugf("Created cgroup path %s for parent %s and name %s", systemdPath, parent, name) + + if !cgroupExist(cgroupPath) { + if err := makeSystemdCgroup(systemdPath, resources); err != nil { + return "", fmt.Errorf("creating cgroup %s: %w", cgroupPath, err) + } + } + + logrus.Debugf("Created cgroup %s", systemdPath) + + return cgroupPath, nil +} + +func getDefaultSystemdCgroup() string { + if rootless.IsRootless() { + return SystemdDefaultRootlessCgroupParent + } + return SystemdDefaultCgroupParent +} + +// makeSystemdCgroup creates a systemd Cgroup at the given location. +func makeSystemdCgroup(path string, resources *spec.LinuxResources) error { + res, err := GetLimits(resources) + if err != nil { + return err + } + controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup(), &res) + if err != nil { + return err + } + + if rootless.IsRootless() { + return controller.CreateSystemdUserUnit(path, rootless.GetRootlessUID()) + } + err = controller.CreateSystemdUnit(path) + if err != nil { + return err + } + return nil +} + +// deleteSystemdCgroup deletes the systemd cgroup at the given location +func deleteSystemdCgroup(path string, resources *spec.LinuxResources) error { + res, err := GetLimits(resources) + if err != nil { + return err + } + controller, err := cgroups.NewSystemd(getDefaultSystemdCgroup(), &res) + if err != nil { + return err + } + if rootless.IsRootless() { + conn, err := cgroups.UserConnection(rootless.GetRootlessUID()) + if err != nil { + return err + } + defer conn.Close() + return controller.DeleteByPathConn(path, conn) + } + + return controller.DeleteByPath(path) +} + +// assembleSystemdCgroupName creates a systemd cgroup path given a base and +// a new component to add. It also returns the path to the cgroup as it accessible +// below the cgroup mounts. +// The base MUST be systemd slice (end in .slice) +func assembleSystemdCgroupName(baseSlice, newSlice string) (string, string, error) { + const sliceSuffix = ".slice" + + if !strings.HasSuffix(baseSlice, sliceSuffix) { + return "", "", fmt.Errorf("cannot assemble cgroup path with base %q - must end in .slice: %w", baseSlice, define.ErrInvalidArg) + } + + noSlice := strings.TrimSuffix(baseSlice, sliceSuffix) + systemdPath := fmt.Sprintf("%s/%s-%s%s", baseSlice, noSlice, newSlice, sliceSuffix) + + if rootless.IsRootless() { + // When we run as rootless, the cgroup has a path like the following: + ///sys/fs/cgroup/user.slice/user-@$UID.slice/user@$UID.service/user.slice/user-libpod_pod_$POD_ID.slice + uid := rootless.GetRootlessUID() + raw := fmt.Sprintf("user.slice/user-%d.slice/user@%d.service/%s/%s-%s%s", uid, uid, baseSlice, noSlice, newSlice, sliceSuffix) + return raw, systemdPath, nil + } + return systemdPath, systemdPath, nil +} + +var lvpRelabel = label.Relabel +var lvpInitLabels = label.InitLabels +var lvpReleaseLabel = selinux.ReleaseLabel + +// LabelVolumePath takes a mount path for a volume and gives it an +// selinux label of either shared or not +func LabelVolumePath(path, mountLabel string) error { + if mountLabel == "" { + var err error + _, mountLabel, err = lvpInitLabels([]string{}) + if err != nil { + return fmt.Errorf("getting default mountlabels: %w", err) + } + lvpReleaseLabel(mountLabel) + } + + if err := lvpRelabel(path, mountLabel, true); err != nil { + if errors.Is(err, unix.ENOTSUP) { + logrus.Debugf("Labeling not supported on %q", path) + } else { + return fmt.Errorf("setting selinux label for %s to %q as shared: %w", path, mountLabel, err) + } + } + return nil +} + +// Unmount umounts a target directory +func Unmount(mount string) { + if err := unix.Unmount(mount, unix.MNT_DETACH); err != nil { + if err != syscall.EINVAL { + logrus.Warnf("Failed to unmount %s : %v", mount, err) + } else { + logrus.Debugf("failed to unmount %s : %v", mount, err) + } + } +} diff --git a/vendor/github.com/containers/podman/v5/libpod/volume.go b/vendor/github.com/containers/podman/v5/libpod/volume.go new file mode 100644 index 00000000..27108e88 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/volume.go @@ -0,0 +1,350 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "io" + "maps" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/lock" + "github.com/containers/podman/v5/libpod/plugin" + "github.com/containers/podman/v5/utils" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/directory" +) + +// Volume is a libpod named volume. +// Named volumes may be shared by multiple containers, and may be created using +// more complex options than normal bind mounts. They may be backed by a mounted +// filesystem on the host. +type Volume struct { + config *VolumeConfig + state *VolumeState + + ignoreIfExists bool + valid bool + plugin *plugin.VolumePlugin + runtime *Runtime + lock lock.Locker +} + +// VolumeConfig holds the volume's immutable configuration. +type VolumeConfig struct { + // Name of the volume. + Name string `json:"name"` + // ID of the volume's lock. + LockID uint32 `json:"lockID"` + // Labels for the volume. + Labels map[string]string `json:"labels"` + // The volume driver. Empty string or local does not activate a volume + // driver, all other values will. + Driver string `json:"volumeDriver"` + // The location the volume is mounted at. + MountPoint string `json:"mountPoint"` + // Time the volume was created. + CreatedTime time.Time `json:"createdAt"` + // Options to pass to the volume driver. For the local driver, this is + // a list of mount options. For other drivers, they are passed to the + // volume driver handling the volume. + Options map[string]string `json:"volumeOptions,omitempty"` + // Whether this volume is anonymous (will be removed on container exit) + IsAnon bool `json:"isAnon"` + // UID the volume will be created as. + UID int `json:"uid"` + // GID the volume will be created as. + GID int `json:"gid"` + // Size maximum of the volume. + Size uint64 `json:"size"` + // Inodes maximum of the volume. + Inodes uint64 `json:"inodes"` + // DisableQuota indicates that the volume should completely disable using any + // quota tracking. + DisableQuota bool `json:"disableQuota,omitempty"` + // Timeout allows users to override the default driver timeout of 5 seconds + Timeout *uint `json:"timeout,omitempty"` + // StorageName is the name of the volume in c/storage. Only used for + // image volumes. + StorageName string `json:"storageName,omitempty"` + // StorageID is the ID of the volume in c/storage. Only used for image + // volumes. + StorageID string `json:"storageID,omitempty"` + // StorageImageID is the ID of the image the volume was based off of. + // Only used for image volumes. + StorageImageID string `json:"storageImageID,omitempty"` + // MountLabel is the SELinux label to assign to mount points + MountLabel string `json:"mountlabel,omitempty"` +} + +// VolumeState holds the volume's mutable state. +// Volumes are not guaranteed to have a state. Only volumes using the Local +// driver that have mount options set will create a state. +type VolumeState struct { + // Mountpoint is the location where the volume was mounted. + // This is only used for volumes using a volume plugin, which will mount + // at non-standard locations. + MountPoint string `json:"mountPoint,omitempty"` + // MountCount is the number of times this volume has been requested to + // be mounted. + // It is incremented on mount() and decremented on unmount(). + // On incrementing from 0, the volume will be mounted on the host. + // On decrementing to 0, the volume will be unmounted on the host. + MountCount uint `json:"mountCount"` + // NeedsCopyUp indicates that the next time the volume is mounted into + // a container, the container will "copy up" the contents of the + // mountpoint into the volume. + // This should only be done once. As such, this is set at container + // create time, then cleared after the copy up is done and never set + // again. + NeedsCopyUp bool `json:"notYetMounted,omitempty"` + // NeedsChown indicates that the next time the volume is mounted into + // a container, the container will chown the volume to the container process + // UID/GID. + NeedsChown bool `json:"notYetChowned,omitempty"` + // Indicates that a copy-up event occurred during the current mount of + // the volume into a container. + // We use this to determine if a chown is appropriate. + CopiedUp bool `json:"copiedUp,omitempty"` + // UIDChowned is the UID the volume was chowned to. + UIDChowned int `json:"uidChowned,omitempty"` + // GIDChowned is the GID the volume was chowned to. + GIDChowned int `json:"gidChowned,omitempty"` +} + +// Name retrieves the volume's name +func (v *Volume) Name() string { + return v.config.Name +} + +// Returns the size on disk of volume +func (v *Volume) Size() (uint64, error) { + size, err := directory.Size(v.config.MountPoint) + return uint64(size), err +} + +// Driver retrieves the volume's driver. +func (v *Volume) Driver() string { + return v.config.Driver +} + +// Scope retrieves the volume's scope. +// Libpod does not implement volume scoping, and this is provided solely for +// Docker compatibility. It returns only "local". +func (v *Volume) Scope() string { + return "local" +} + +// Labels returns the volume's labels +func (v *Volume) Labels() map[string]string { + labels := make(map[string]string) + maps.Copy(labels, v.config.Labels) + return labels +} + +// MountPoint returns the volume's mountpoint on the host +func (v *Volume) MountPoint() (string, error) { + // For the sake of performance, avoid locking unless we have to. + if v.UsesVolumeDriver() || v.config.Driver == define.VolumeDriverImage { + v.lock.Lock() + defer v.lock.Unlock() + + if err := v.update(); err != nil { + return "", err + } + } + + return v.mountPoint(), nil +} + +// MountCount returns the volume's mountcount on the host from state +// Useful in determining if volume is using plugin or a filesystem mount and its mount +func (v *Volume) MountCount() (uint, error) { + v.lock.Lock() + defer v.lock.Unlock() + if err := v.update(); err != nil { + return 0, err + } + return v.state.MountCount, nil +} + +// Internal-only helper for volume mountpoint +func (v *Volume) mountPoint() string { + if v.UsesVolumeDriver() || v.config.Driver == define.VolumeDriverImage { + return v.state.MountPoint + } + + return v.config.MountPoint +} + +// Options return the volume's options +func (v *Volume) Options() map[string]string { + options := make(map[string]string) + maps.Copy(options, v.config.Options) + return options +} + +// Anonymous returns whether this volume is anonymous. Anonymous volumes were +// created with a container, and will be removed when that container is removed. +func (v *Volume) Anonymous() bool { + return v.config.IsAnon +} + +// UID returns the UID the volume will be created as. +func (v *Volume) UID() (int, error) { + v.lock.Lock() + defer v.lock.Unlock() + + if err := v.update(); err != nil { + return -1, err + } + + return v.uid(), nil +} + +// Internal, unlocked accessor for UID. +func (v *Volume) uid() int { + if v.state.UIDChowned > 0 { + return v.state.UIDChowned + } + return v.config.UID +} + +// GID returns the GID the volume will be created as. +func (v *Volume) GID() (int, error) { + v.lock.Lock() + defer v.lock.Unlock() + + if err := v.update(); err != nil { + return -1, err + } + + return v.gid(), nil +} + +// Internal, unlocked accessor for GID. +func (v *Volume) gid() int { + if v.state.GIDChowned > 0 { + return v.state.GIDChowned + } + return v.config.GID +} + +// CreatedTime returns the time the volume was created at. It was not tracked +// for some time, so older volumes may not contain one. +func (v *Volume) CreatedTime() time.Time { + return v.config.CreatedTime +} + +// Config returns the volume's configuration. +func (v *Volume) Config() (*VolumeConfig, error) { + config := VolumeConfig{} + err := JSONDeepCopy(v.config, &config) + return &config, err +} + +// VolumeInUse goes through the container dependencies of a volume +// and checks if the volume is being used by any container. +func (v *Volume) VolumeInUse() ([]string, error) { + v.lock.Lock() + defer v.lock.Unlock() + + if !v.valid { + return nil, define.ErrVolumeRemoved + } + return v.runtime.state.VolumeInUse(v) +} + +// IsDangling returns whether this volume is dangling (unused by any +// containers). +func (v *Volume) IsDangling() (bool, error) { + ctrs, err := v.VolumeInUse() + if err != nil { + return false, err + } + return len(ctrs) == 0, nil +} + +// UsesVolumeDriver determines whether the volume uses a volume driver. Volume +// drivers are pluggable backends for volumes that will manage the storage and +// mounting. +func (v *Volume) UsesVolumeDriver() bool { + if v.config.Driver == define.VolumeDriverImage { + if _, ok := v.runtime.config.Engine.VolumePlugins[v.config.Driver]; ok { + return true + } + return false + } + return v.config.Driver != define.VolumeDriverLocal && v.config.Driver != "" +} + +func (v *Volume) Mount() (string, error) { + v.lock.Lock() + defer v.lock.Unlock() + err := v.mount() + return v.config.MountPoint, err +} + +func (v *Volume) Unmount() error { + v.lock.Lock() + defer v.lock.Unlock() + return v.unmount(false) +} + +func (v *Volume) NeedsMount() bool { + return v.needsMount() +} + +// Export volume to tar. +// Returns a ReadCloser which points to a tar of all the volume's contents. +func (v *Volume) Export() (io.ReadCloser, error) { + v.lock.Lock() + err := v.mount() + mountPoint := v.mountPoint() + v.lock.Unlock() + if err != nil { + return nil, err + } + defer func() { + v.lock.Lock() + defer v.lock.Unlock() + + if err := v.unmount(false); err != nil { + logrus.Errorf("Error unmounting volume %s: %v", v.Name(), err) + } + }() + + volContents, err := utils.TarWithChroot(mountPoint) + if err != nil { + return nil, fmt.Errorf("creating tar of volume %s contents: %w", v.Name(), err) + } + + return volContents, nil +} + +// Import a volume from a tar file, provided as an io.Reader. +func (v *Volume) Import(r io.Reader) error { + v.lock.Lock() + err := v.mount() + mountPoint := v.mountPoint() + v.lock.Unlock() + if err != nil { + return err + } + defer func() { + v.lock.Lock() + defer v.lock.Unlock() + + if err := v.unmount(false); err != nil { + logrus.Errorf("Error unmounting volume %s: %v", v.Name(), err) + } + }() + + if err := archive.Untar(r, mountPoint, nil); err != nil { + return fmt.Errorf("extracting into volume %s: %w", v.Name(), err) + } + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/volume_inspect.go b/vendor/github.com/containers/podman/v5/libpod/volume_inspect.go new file mode 100644 index 00000000..fc4316a4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/volume_inspect.go @@ -0,0 +1,76 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "maps" + + "github.com/containers/podman/v5/libpod/define" + pluginapi "github.com/docker/go-plugins-helpers/volume" + "github.com/sirupsen/logrus" +) + +// Inspect provides detailed information about the configuration of the given +// volume. +func (v *Volume) Inspect() (*define.InspectVolumeData, error) { + if !v.valid { + return nil, define.ErrVolumeRemoved + } + + v.lock.Lock() + defer v.lock.Unlock() + + if err := v.update(); err != nil { + return nil, err + } + + data := new(define.InspectVolumeData) + + data.Mountpoint = v.config.MountPoint + if v.UsesVolumeDriver() { + logrus.Debugf("Querying volume plugin %s for status", v.config.Driver) + data.Mountpoint = v.state.MountPoint + + if v.plugin == nil { + return nil, fmt.Errorf("volume %s uses volume plugin %s but it is not available, cannot inspect: %w", v.Name(), v.config.Driver, define.ErrMissingPlugin) + } + + // Retrieve status for the volume. + // Need to query the volume driver. + req := new(pluginapi.GetRequest) + req.Name = v.Name() + resp, err := v.plugin.GetVolume(req) + if err != nil { + return nil, fmt.Errorf("retrieving volume %s information from plugin %s: %w", v.Name(), v.Driver(), err) + } + if resp != nil { + data.Status = resp.Status + } + } + + data.Name = v.config.Name + data.Driver = v.config.Driver + data.CreatedAt = v.config.CreatedTime + data.Labels = make(map[string]string) + maps.Copy(data.Labels, v.config.Labels) + data.Scope = v.Scope() + data.Options = make(map[string]string) + maps.Copy(data.Options, v.config.Options) + data.UID = v.uid() + data.GID = v.gid() + data.Anonymous = v.config.IsAnon + data.MountCount = v.state.MountCount + data.NeedsCopyUp = v.state.NeedsCopyUp + data.NeedsChown = v.state.NeedsChown + data.StorageID = v.config.StorageID + data.LockNumber = v.lock.ID() + + if v.config.Timeout != nil { + data.Timeout = *v.config.Timeout + } else if v.UsesVolumeDriver() { + data.Timeout = v.runtime.config.Engine.VolumePluginTimeout + } + + return data, nil +} diff --git a/vendor/github.com/containers/podman/v5/libpod/volume_internal.go b/vendor/github.com/containers/podman/v5/libpod/volume_internal.go new file mode 100644 index 00000000..e5125823 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/volume_internal.go @@ -0,0 +1,114 @@ +//go:build !remote + +package libpod + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/containers/podman/v5/libpod/define" +) + +// Creates a new volume +func newVolume(runtime *Runtime) *Volume { + volume := new(Volume) + volume.config = new(VolumeConfig) + volume.state = new(VolumeState) + volume.runtime = runtime + volume.config.Labels = make(map[string]string) + volume.config.Options = make(map[string]string) + volume.state.NeedsCopyUp = true + volume.state.NeedsChown = true + return volume +} + +// teardownStorage deletes the volume from volumePath +func (v *Volume) teardownStorage() error { + if v.UsesVolumeDriver() { + return nil + } + + // TODO: Should this be converted to use v.config.MountPoint? + return os.RemoveAll(filepath.Join(v.runtime.config.Engine.VolumePath, v.Name())) +} + +// Volumes with options set, or a filesystem type, or a device to mount need to +// be mounted and unmounted. +func (v *Volume) needsMount() bool { + // Non-local driver always needs mount + if v.UsesVolumeDriver() { + return true + } + + // Image driver always needs mount + if v.config.Driver == define.VolumeDriverImage { + return true + } + + // Commit 28138dafcc added the UID and GID options to this map + // However we should only mount when options other than uid and gid are set. + // see https://github.com/containers/podman/issues/10620 + index := 0 + if _, ok := v.config.Options["UID"]; ok { + index++ + } + if _, ok := v.config.Options["GID"]; ok { + index++ + } + if _, ok := v.config.Options["SIZE"]; ok { + index++ + } + if _, ok := v.config.Options["NOQUOTA"]; ok { + index++ + } + if _, ok := v.config.Options["nocopy"]; ok { + index++ + } + if _, ok := v.config.Options["copy"]; ok { + index++ + } + // when uid or gid is set there is also the "o" option + // set so we have to ignore this one as well + if index > 0 { + index++ + } + // Local driver with options other than uid,gid needs mount + return len(v.config.Options) > index +} + +// update() updates the volume state from the DB. +func (v *Volume) update() error { + if err := v.runtime.state.UpdateVolume(v); err != nil { + return err + } + if !v.valid { + return define.ErrVolumeRemoved + } + return nil +} + +// save() saves the volume state to the DB +func (v *Volume) save() error { + return v.runtime.state.SaveVolume(v) +} + +// Refresh volume state after a restart. +func (v *Volume) refresh() error { + lock, err := v.runtime.lockManager.AllocateAndRetrieveLock(v.config.LockID) + if err != nil { + return fmt.Errorf("acquiring lock %d for volume %s: %w", v.config.LockID, v.Name(), err) + } + v.lock = lock + + return nil +} + +// resetVolumeState resets state fields to default values. +// It is performed before a refresh and clears the state after a reboot. +// It does not save the results - assumes the database will do that for us. +func resetVolumeState(state *VolumeState) { + state.MountCount = 0 + state.MountPoint = "" + state.CopiedUp = false +} diff --git a/vendor/github.com/containers/podman/v5/libpod/volume_internal_common.go b/vendor/github.com/containers/podman/v5/libpod/volume_internal_common.go new file mode 100644 index 00000000..c9a20820 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/volume_internal_common.go @@ -0,0 +1,193 @@ +//go:build !remote && (linux || freebsd) + +package libpod + +import ( + "errors" + "fmt" + "os/exec" + "strings" + + "github.com/containers/podman/v5/libpod/define" + pluginapi "github.com/docker/go-plugins-helpers/volume" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// This is a pseudo-container ID to use when requesting a mount or unmount from +// the volume plugins. +// This is the shas256 of the string "placeholder\n". +const pseudoCtrID = "2f73349cfc4630255319c6c8dfc1b46a8996ace9d14d8e07563b165915918ec2" + +// mount mounts the volume if necessary. +// A mount is necessary if a volume has any options set. +// If a mount is necessary, v.state.MountCount will be incremented. +// If it was 0 when the increment occurred, the volume will be mounted on the +// host. Otherwise, we assume it is already mounted. +// Must be done while the volume is locked. +// Is a no-op on volumes that do not require a mount (as defined by +// volumeNeedsMount()). +func (v *Volume) mount() error { + if !v.needsMount() { + return nil + } + + // Update the volume from the DB to get an accurate mount counter. + if err := v.update(); err != nil { + return err + } + + // If the count is non-zero, the volume is already mounted. + // Nothing to do. + if v.state.MountCount > 0 { + v.state.MountCount++ + logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount) + return v.save() + } + + // Volume plugins implement their own mount counter, based on the ID of + // the mounting container. But we already have one, and honestly I trust + // ours more. So hardcode container ID to something reasonable, and use + // the same one for everything. + if v.UsesVolumeDriver() { + if v.plugin == nil { + return fmt.Errorf("volume plugin %s (needed by volume %s) missing: %w", v.Driver(), v.Name(), define.ErrMissingPlugin) + } + + req := new(pluginapi.MountRequest) + req.Name = v.Name() + req.ID = pseudoCtrID + mountPoint, err := v.plugin.MountVolume(req) + if err != nil { + return err + } + + v.state.MountCount++ + v.state.MountPoint = mountPoint + return v.save() + } else if v.config.Driver == define.VolumeDriverImage { + mountPoint, err := v.runtime.storageService.MountContainerImage(v.config.StorageID) + if err != nil { + return fmt.Errorf("mounting volume %s image failed: %w", v.Name(), err) + } + + v.state.MountCount++ + v.state.MountPoint = mountPoint + return v.save() + } + + volDevice := v.config.Options["device"] + volType := v.config.Options["type"] + volOptions := v.config.Options["o"] + + // Some filesystems (tmpfs) don't have a device, but we still need to + // give the kernel something. + if volDevice == "" && volType != "" { + volDevice = volType + } + + // We need to use the actual mount command. + // Convincing unix.Mount to use the same semantics as the mount command + // itself seems prohibitively difficult. + // TODO: might want to cache this path in the runtime? + mountPath, err := exec.LookPath("mount") + if err != nil { + return fmt.Errorf("locating 'mount' binary: %w", err) + } + mountArgs := []string{} + if volOptions != "" { + mountArgs = append(mountArgs, "-o", volOptions) + } + switch volType { + case "": + case define.TypeBind: + mountArgs = append(mountArgs, "-o", volType) + default: + mountArgs = append(mountArgs, "-t", volType) + } + + mountArgs = append(mountArgs, volDevice, v.config.MountPoint) + mountCmd := exec.Command(mountPath, mountArgs...) + + logrus.Debugf("Running mount command: %s %s", mountPath, strings.Join(mountArgs, " ")) + if output, err := mountCmd.CombinedOutput(); err != nil { + logrus.Debugf("Mount %v failed with %v", mountCmd, err) + return errors.New(string(output)) + } + + logrus.Debugf("Mounted volume %s", v.Name()) + + // Increment the mount counter + v.state.MountCount++ + logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount) + return v.save() +} + +// unmount unmounts the volume if necessary. +// Unmounting a volume that is not mounted is a no-op. +// Unmounting a volume that does not require a mount is a no-op. +// The volume must be locked for this to occur. +// The mount counter will be decremented if non-zero. If the counter reaches 0, +// the volume will really be unmounted, as no further containers are using the +// volume. +// If force is set, the volume will be unmounted regardless of mount counter. +func (v *Volume) unmount(force bool) error { + if !v.needsMount() { + return nil + } + + // Update the volume from the DB to get an accurate mount counter. + if err := v.update(); err != nil { + return err + } + + if v.state.MountCount == 0 { + logrus.Debugf("Volume %s already unmounted", v.Name()) + return nil + } + + if !force { + v.state.MountCount-- + } else { + v.state.MountCount = 0 + } + + logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount) + + if v.state.MountCount == 0 { + if v.UsesVolumeDriver() { + if v.plugin == nil { + return fmt.Errorf("volume plugin %s (needed by volume %s) missing: %w", v.Driver(), v.Name(), define.ErrMissingPlugin) + } + + req := new(pluginapi.UnmountRequest) + req.Name = v.Name() + req.ID = pseudoCtrID + if err := v.plugin.UnmountVolume(req); err != nil { + return err + } + + v.state.MountPoint = "" + return v.save() + } else if v.config.Driver == define.VolumeDriverImage { + if _, err := v.runtime.storageService.UnmountContainerImage(v.config.StorageID, force); err != nil { + return fmt.Errorf("unmounting volume %s image: %w", v.Name(), err) + } + + v.state.MountPoint = "" + return v.save() + } + + // Unmount the volume + if err := detachUnmount(v.config.MountPoint); err != nil { + if err == unix.EINVAL { + // Ignore EINVAL - the mount no longer exists. + return nil + } + return fmt.Errorf("unmounting volume %s: %w", v.Name(), err) + } + logrus.Debugf("Unmounted volume %s", v.Name()) + } + + return v.save() +} diff --git a/vendor/github.com/containers/podman/v5/libpod/volume_internal_freebsd.go b/vendor/github.com/containers/podman/v5/libpod/volume_internal_freebsd.go new file mode 100644 index 00000000..7e16429e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/volume_internal_freebsd.go @@ -0,0 +1,11 @@ +//go:build !remote + +package libpod + +import ( + "golang.org/x/sys/unix" +) + +func detachUnmount(mountPoint string) error { + return unix.Unmount(mountPoint, unix.MNT_FORCE) +} diff --git a/vendor/github.com/containers/podman/v5/libpod/volume_internal_linux.go b/vendor/github.com/containers/podman/v5/libpod/volume_internal_linux.go new file mode 100644 index 00000000..4c84e27f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/libpod/volume_internal_linux.go @@ -0,0 +1,11 @@ +//go:build !remote + +package libpod + +import ( + "golang.org/x/sys/unix" +) + +func detachUnmount(mountPoint string) error { + return unix.Unmount(mountPoint, unix.MNT_DETACH) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/annotations/annotations.go b/vendor/github.com/containers/podman/v5/pkg/annotations/annotations.go new file mode 100644 index 00000000..d6db0cf0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/annotations/annotations.go @@ -0,0 +1,23 @@ +package annotations + +const ( + // SandboxID is the sandbox ID annotation. + SandboxID = "io.kubernetes.cri-o.SandboxID" + + // ContainerManager is the annotation key for indicating the creator and + // manager of the container. + ContainerManager = "io.container.manager" +) + +// ContainerType values +const ( + // ContainerTypeSandbox represents a pod sandbox container. + ContainerTypeSandbox = "sandbox" + + // ContainerTypeContainer represents a container running within a pod. + ContainerTypeContainer = "container" +) + +// ContainerManagerLibpod indicates that libpod created and manages the +// container. +const ContainerManagerLibpod = "libpod" diff --git a/vendor/github.com/containers/podman/v5/pkg/annotations/validate.go b/vendor/github.com/containers/podman/v5/pkg/annotations/validate.go new file mode 100644 index 00000000..1e9c3bf8 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/annotations/validate.go @@ -0,0 +1,125 @@ +package annotations + +import ( + "errors" + "fmt" + "regexp" + "strings" + + "github.com/containers/podman/v5/libpod/define" +) + +// regexErrorMsg returns a string explanation of a regex validation failure. +func regexErrorMsg(msg string, fmt string, examples ...string) string { + if len(examples) == 0 { + return msg + " (regex used for validation is '" + fmt + "')" + } + msg += " (e.g. " + for i := range examples { + if i > 0 { + msg += " or " + } + msg += "'" + examples[i] + "', " + } + msg += "regex used for validation is '" + fmt + "')" + return msg +} + +const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?" +const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*" +const dns1123SubdomainErrorMsg string = "annotations must be formatted as a valid lowercase RFC1123 subdomain of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character" + +// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123) +const DNS1123SubdomainMaxLength int = 253 + +var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$") + +// isDNS1123Subdomain tests for a string that conforms to the definition of a +// subdomain in DNS (RFC 1123). +func isDNS1123Subdomain(value string) error { + if len(value) > DNS1123SubdomainMaxLength { + return fmt.Errorf("prefix part must be no more than %d characters", DNS1123SubdomainMaxLength) + } + + if !dns1123SubdomainRegexp.MatchString(value) { + return errors.New(regexErrorMsg(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com")) + } + + return nil +} + +const qnameCharFmt string = "[A-Za-z0-9]" +const qnameExtCharFmt string = "[-A-Za-z0-9_.]" +const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt +const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character" +const qualifiedNameMaxLength int = 63 + +var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$") + +// isQualifiedName tests whether the value passed is what Kubernetes calls a +// "qualified name". This is a format used in various places throughout the +// system. If the value is not valid, a list of error strings is returned. +// Otherwise an empty list (or nil) is returned. +func isQualifiedName(value string) error { + parts := strings.Split(value, "/") + var name string + + switch len(parts) { + case 1: + name = parts[0] + case 2: + var prefix string + prefix, name = parts[0], parts[1] + if len(prefix) == 0 { + return fmt.Errorf("prefix part of %s must be non-empty", value) + } else if err := isDNS1123Subdomain(prefix); err != nil { + return err + } + default: + return fmt.Errorf("a qualified name of %s "+ + regexErrorMsg(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+ + " with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')", value) + } + + if len(name) == 0 { + return fmt.Errorf("name part of %s must be non-empty", value) + } else if len(name) > qualifiedNameMaxLength { + return fmt.Errorf("name part of %s must be no more than %d characters", value, qualifiedNameMaxLength) + } + + if !qualifiedNameRegexp.MatchString(name) { + return fmt.Errorf("name part of %s "+ + regexErrorMsg(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"), value) + } + + return nil +} + +func validateAnnotationsSize(annotations map[string]string) error { + var totalSize int64 + for k, v := range annotations { + totalSize += (int64)(len(k)) + (int64)(len(v)) + } + if totalSize > (int64)(define.TotalAnnotationSizeLimitB) { + return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, define.TotalAnnotationSizeLimitB) + } + return nil +} + +// ValidateAnnotations validates that a set of annotations are correctly +// defined. +func ValidateAnnotations(annotations map[string]string) error { + for k := range annotations { + // The rule is QualifiedName except that case doesn't matter, + // so convert to lowercase before checking. + if err := isQualifiedName(strings.ToLower(k)); err != nil { + return err + } + } + + if err := validateAnnotationsSize(annotations); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/api/handlers/decoder.go b/vendor/github.com/containers/podman/v5/pkg/api/handlers/decoder.go new file mode 100644 index 00000000..6a3cbf8d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/api/handlers/decoder.go @@ -0,0 +1,163 @@ +//go:build !remote + +package handlers + +import ( + "encoding/json" + "reflect" + "strconv" + "strings" + "syscall" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/util" + "github.com/gorilla/schema" + "github.com/sirupsen/logrus" + "go.podman.io/image/v5/types" +) + +// NewAPIDecoder returns a configured schema.Decoder +func NewAPIDecoder() *schema.Decoder { + d := schema.NewDecoder() + d.IgnoreUnknownKeys(true) + d.RegisterConverter(map[string][]string{}, convertURLValuesString) + d.RegisterConverter(time.Time{}, convertTimeString) + d.RegisterConverter(define.ContainerStatus(0), convertContainerStatusString) + d.RegisterConverter(map[string]string{}, convertStringMap) + + var Signal syscall.Signal + d.RegisterConverter(Signal, convertSignal) + + d.RegisterConverter(types.OptionalBoolUndefined, convertOptionalBool) + + return d +} + +func NewCompatAPIDecoder() *schema.Decoder { + dec := NewAPIDecoder() + + // mimic behaviour of github.com/docker/docker/api/server/httputils.BoolValue() + dec.RegisterConverter(true, func(s string) reflect.Value { + s = strings.ToLower(strings.TrimSpace(s)) + return reflect.ValueOf(s != "" && s != "0" && s != "no" && s != "false" && s != "none") + }) + dec.RegisterConverter(types.OptionalBoolUndefined, func(s string) reflect.Value { + if len(s) == 0 { + return reflect.ValueOf(types.OptionalBoolUndefined) + } + s = strings.ToLower(strings.TrimSpace(s)) + if s != "0" && s != "no" && s != "false" && s != "none" { + return reflect.ValueOf(types.OptionalBoolTrue) + } + return reflect.ValueOf(types.OptionalBoolFalse) + }) + + return dec +} + +// On client: +// +// v := map[string][]string{ +// "dangling": {"true"}, +// } +// +// payload, err := jsoniter.MarshalToString(v) +// if err != nil { +// panic(err) +// } +// payload = url.QueryEscape(payload) +func convertURLValuesString(query string) reflect.Value { + f := map[string][]string{} + + err := json.Unmarshal([]byte(query), &f) + if err != nil { + logrus.Infof("convertURLValuesString: Failed to Unmarshal %s: %s", query, err.Error()) + } + + return reflect.ValueOf(f) +} + +func convertStringMap(query string) reflect.Value { + res := make(map[string]string) + err := json.Unmarshal([]byte(query), &res) + if err != nil { + logrus.Infof("convertStringMap: Failed to Unmarshal %s: %s", query, err.Error()) + } + return reflect.ValueOf(res) +} + +func convertContainerStatusString(query string) reflect.Value { + result, err := define.StringToContainerStatus(query) + if err != nil { + logrus.Infof("convertContainerStatusString: Failed to parse %s: %s", query, err.Error()) + + // We return nil here instead of result because reflect.ValueOf().IsValid() will be true + // in github.com/gorilla/schema's decoder, which means there's no parsing error + return reflect.ValueOf(nil) + } + + return reflect.ValueOf(result) +} + +// isZero() can be used to determine if parsing failed. +func convertTimeString(query string) reflect.Value { + var ( + err error + t time.Time + ) + + for _, f := range []string{ + time.UnixDate, + time.ANSIC, + time.RFC1123, + time.RFC1123Z, + time.RFC3339, + time.RFC3339Nano, + time.RFC822, + time.RFC822Z, + time.RFC850, + time.RubyDate, + time.Stamp, + time.StampMicro, + time.StampMilli, + time.StampNano, + } { + t, err = time.Parse(f, query) + if err == nil { + return reflect.ValueOf(t) + } + + if _, isParseError := err.(*time.ParseError); isParseError { + // Try next format + continue + } else { + break + } + } + + // We've exhausted all formats, or something bad happened + if err != nil { + logrus.Infof("convertTimeString: Failed to parse %s: %s", query, err.Error()) + } + return reflect.ValueOf(time.Time{}) +} + +func convertSignal(query string) reflect.Value { + signal, err := util.ParseSignal(query) + if err != nil { + logrus.Infof("convertSignal: Failed to parse %s: %s", query, err.Error()) + } + return reflect.ValueOf(signal) +} + +func convertOptionalBool(s string) reflect.Value { + if len(s) == 0 { + return reflect.ValueOf(types.OptionalBoolUndefined) + } + val, _ := strconv.ParseBool(s) + if val { + return reflect.ValueOf(types.OptionalBoolTrue) + } + return reflect.ValueOf(types.OptionalBoolFalse) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/api/handlers/types.go b/vendor/github.com/containers/podman/v5/pkg/api/handlers/types.go new file mode 100644 index 00000000..6a79103b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/api/handlers/types.go @@ -0,0 +1,190 @@ +package handlers + +import ( + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/domain/entities" + docker "github.com/docker/docker/api/types" + dockerBackend "github.com/docker/docker/api/types/backend" + dockerContainer "github.com/docker/docker/api/types/container" + dockerImage "github.com/docker/docker/api/types/image" + dockerNetwork "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/api/types/system" + "github.com/docker/docker/api/types/volume" + "github.com/opencontainers/runtime-spec/specs-go" +) + +type AuthConfig struct { + registry.AuthConfig +} + +type ImageInspect struct { + dockerImage.InspectResponse + // When you embed a struct, the fields of the embedded struct are "promoted" to the outer struct. + // If a field in the outer struct has the same name as a field in the embedded struct, + // the outer struct's field will shadow or override the embedded one allowing for a clean way to + // hide fields from the swagger spec that still exist in the libraries struct. + Container string `json:"-"` + ContainerConfig string `json:"-"` + VirtualSize string `json:"-"` +} + +type ContainerConfig struct { + dockerContainer.Config +} + +type LibpodImagesPullReport struct { + entities.ImagePullReport +} + +// LibpodImagesRemoveReport is the return type for image removal via the rest +// api. +type LibpodImagesRemoveReport struct { + entities.ImageRemoveReport + // Image removal requires is to return data and an error. + Errors []string +} + +// LibpodImagesResolveReport includes a list of fully-qualified image references. +type LibpodImagesResolveReport struct { + // Fully-qualified image references. + Names []string +} + +type ContainersPruneReport struct { + dockerContainer.PruneReport +} + +type ContainersPruneReportLibpod struct { + ID string `json:"Id"` + SpaceReclaimed int64 `json:"Size"` + // Error which occurred during prune operation (if any). + // This field is optional and may be omitted if no error occurred. + // + // Extensions: + // x-omitempty: true + // x-nullable: true + PruneError string `json:"Err,omitempty"` +} + +type LibpodContainersRmReport struct { + ID string `json:"Id"` + // Error which occurred during Rm operation (if any). + // This field is optional and may be omitted if no error occurred. + // + // Extensions: + // x-omitempty: true + // x-nullable: true + RmError string `json:"Err,omitempty"` +} + +// UpdateEntities used to wrap the oci resource spec in a swagger model +// swagger:model +type UpdateEntities struct { + specs.LinuxResources + define.UpdateHealthCheckConfig + define.UpdateContainerDevicesLimits + Env []string + UnsetEnv []string +} + +type Info struct { + system.Info + BuildahVersion string + CPURealtimePeriod bool + CPURealtimeRuntime bool + CgroupVersion string + Rootless bool + SwapFree int64 + SwapTotal int64 + Uptime string +} + +type Container struct { + docker.Container + dockerBackend.ContainerCreateConfig +} + +type DiskUsage struct { + docker.DiskUsage +} + +type VolumesPruneReport struct { + volume.PruneReport +} + +type ImagesPruneReport struct { + dockerImage.PruneReport +} + +type BuildCachePruneReport struct { + docker.BuildCachePruneReport +} + +type NetworkPruneReport struct { + dockerNetwork.PruneReport +} + +type ConfigCreateResponse struct { + docker.ConfigCreateResponse +} + +type PushResult struct { + docker.PushResult +} + +type BuildResult struct { + docker.BuildResult +} + +type ContainerWaitOKBody struct { + StatusCode int + Error *struct { + Message string + } +} + +// CreateContainerConfig used when compatible endpoint creates a container +// swagger:model +type CreateContainerConfig struct { + Name string // container name + dockerContainer.Config // desired container configuration + HostConfig dockerContainer.HostConfig // host dependent configuration for container + NetworkingConfig dockerNetwork.NetworkingConfig // network configuration for container + EnvMerge []string // preprocess env variables from image before injecting into containers + UnsetEnv []string // unset specified default environment variables + UnsetEnvAll bool // unset all default environment variables +} + +type ContainerTopOKBody struct { + dockerContainer.ContainerTopOKBody +} + +type PodTopOKBody struct { + dockerContainer.ContainerTopOKBody +} + +// HistoryResponse provides details on image layers +type HistoryResponse struct { + ID string `json:"Id"` + Created int64 + CreatedBy string + Tags []string + Size int64 + Comment string +} + +type ExecCreateConfig struct { + dockerContainer.ExecOptions +} + +type ExecStartConfig struct { + Detach bool `json:"Detach"` + Tty bool `json:"Tty"` + Height uint16 `json:"h"` + Width uint16 `json:"w"` +} + +type ExecRemoveConfig struct { + Force bool `json:"Force"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/api/handlers/types/types.go b/vendor/github.com/containers/podman/v5/pkg/api/handlers/types/types.go new file mode 100644 index 00000000..7329fc7a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/api/handlers/types/types.go @@ -0,0 +1,21 @@ +package types + +import "github.com/containers/podman/v5/pkg/domain/entities" + +// LibpodImagesRemoveReport is the return type for image removal via the rest +// api. +type LibpodImagesRemoveReport struct { + entities.ImageRemoveReport + // Image removal requires is to return data and an error. + Errors []string +} + +// HistoryResponse provides details on image layers +type HistoryResponse struct { + ID string `json:"Id"` + Created int64 + CreatedBy string + Tags []string + Size int64 + Comment string +} diff --git a/vendor/github.com/containers/podman/v5/pkg/api/handlers/utils/apiutil/apiutil.go b/vendor/github.com/containers/podman/v5/pkg/api/handlers/utils/apiutil/apiutil.go new file mode 100644 index 00000000..41037c04 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/api/handlers/utils/apiutil/apiutil.go @@ -0,0 +1,78 @@ +//go:build !remote + +package apiutil + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/blang/semver/v4" + "github.com/containers/podman/v5/version" + "github.com/gorilla/mux" +) + +var ( + // ErrVersionNotGiven returned when version not given by client + ErrVersionNotGiven = errors.New("version not given in URL path") + // ErrVersionNotSupported returned when given version is too old + ErrVersionNotSupported = errors.New("given version is not supported") +) + +// IsLibpodRequest returns true if the request related to a libpod endpoint +// (e.g., /v2/libpod/...). +func IsLibpodRequest(r *http.Request) bool { + split := strings.Split(r.URL.String(), "/") + return len(split) >= 3 && split[2] == "libpod" +} + +// IsLibpodLocalRequest returns true if the request related to a libpod local endpoint +// (e.g., /v2/libpod/local...). +func IsLibpodLocalRequest(r *http.Request) bool { + split := strings.Split(r.URL.String(), "/") + return len(split) >= 4 && split[2] == "libpod" && split[3] == "local" +} + +// SupportedVersion validates that the version provided by client is included in the given condition +// https://github.com/blang/semver#ranges provides the details for writing conditions +// If a version is not given in URL path, ErrVersionNotGiven is returned +func SupportedVersion(r *http.Request, condition string) (semver.Version, error) { + version := semver.Version{} + val, ok := mux.Vars(r)["version"] + if !ok { + return version, ErrVersionNotGiven + } + safeVal, err := url.PathUnescape(val) + if err != nil { + return version, fmt.Errorf("unable to unescape given API version: %q: %w", val, err) + } + version, err = semver.ParseTolerant(safeVal) + if err != nil { + return version, fmt.Errorf("unable to parse given API version: %q from %q: %w", safeVal, val, err) + } + + inRange, err := semver.ParseRange(condition) + if err != nil { + return version, err + } + + if inRange(version) { + return version, nil + } + return version, ErrVersionNotSupported +} + +// SupportedVersionWithDefaults validates that the version provided by client valid is supported by server +// minimal API version <= client path version <= maximum API version focused on the endpoint tree from URL +func SupportedVersionWithDefaults(r *http.Request) (semver.Version, error) { + tree := version.Compat + if IsLibpodRequest(r) { + tree = version.Libpod + } + + return SupportedVersion(r, + fmt.Sprintf(">=%s <=%s", version.APIVersion[tree][version.MinimalAPI].String(), + version.APIVersion[tree][version.CurrentAPI].String())) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/auth/auth.go b/vendor/github.com/containers/podman/v5/pkg/auth/auth.go new file mode 100644 index 00000000..f9b9b436 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/auth/auth.go @@ -0,0 +1,343 @@ +package auth + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "os" + "strings" + + dockerAPITypes "github.com/docker/docker/api/types/registry" + "github.com/sirupsen/logrus" + imageAuth "go.podman.io/image/v5/pkg/docker/config" + "go.podman.io/image/v5/types" +) + +// xRegistryAuthHeader is the key to the encoded registry authentication configuration in an http-request header. +// This header supports one registry per header occurrence. To support N registries provide N headers, one per registry. +// As of Docker API 1.40 and Libpod API 1.0.0, this header is supported by all endpoints. +const xRegistryAuthHeader = "X-Registry-Auth" + +// xRegistryConfigHeader is the key to the encoded registry authentication configuration in an http-request header. +// This header supports N registries in one header via a Base64 encoded, JSON map. +// As of Docker API 1.40 and Libpod API 2.0.0, this header is supported by build endpoints. +const xRegistryConfigHeader = "X-Registry-Config" + +// GetCredentials queries the http.Request for X-Registry-.* headers and extracts +// the necessary authentication information for libpod operations, possibly +// creating a config file. If that is the case, the caller must call RemoveAuthFile. +func GetCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) { + nonemptyHeaderValue := func(key string) ([]string, bool) { + hdr := r.Header.Values(key) + return hdr, len(hdr) > 0 + } + var override *types.DockerAuthConfig + var fileContents map[string]types.DockerAuthConfig + var headerName string + var err error + if hdr, ok := nonemptyHeaderValue(xRegistryConfigHeader); ok { + headerName = xRegistryConfigHeader + override, fileContents, err = getConfigCredentials(r, hdr) + } else if hdr, ok := nonemptyHeaderValue(xRegistryAuthHeader); ok { + headerName = xRegistryAuthHeader + override, fileContents, err = getAuthCredentials(hdr) + } else { + return nil, "", nil + } + if err != nil { + return nil, "", fmt.Errorf("failed to parse %q header for %s: %w", headerName, r.URL.String(), err) + } + + var authFile string + if fileContents == nil { + authFile = "" + } else { + authFile, err = authConfigsToAuthFile(fileContents) + if err != nil { + return nil, "", fmt.Errorf("failed to parse %q header for %s: %w", headerName, r.URL.String(), err) + } + } + return override, authFile, nil +} + +// getConfigCredentials extracts one or more docker.AuthConfig from a request and its +// xRegistryConfigHeader value. An empty key will be used as default while a named registry will be +// returned as types.DockerAuthConfig +func getConfigCredentials(r *http.Request, headers []string) (*types.DockerAuthConfig, map[string]types.DockerAuthConfig, error) { + var auth *types.DockerAuthConfig + configs := make(map[string]types.DockerAuthConfig) + + for _, h := range headers { + param, err := base64.URLEncoding.DecodeString(h) + if err != nil { + return nil, nil, fmt.Errorf("failed to decode %q: %w", xRegistryConfigHeader, err) + } + + ac := make(map[string]dockerAPITypes.AuthConfig) + err = json.Unmarshal(param, &ac) + if err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal %q: %w", xRegistryConfigHeader, err) + } + + for k, v := range ac { + configs[k] = dockerAuthToImageAuth(v) + } + } + + // Empty key implies no registry given in API + if c, found := configs[""]; found { + auth = &c + } + + // Override any default given above if specialized credentials provided + if registries, found := r.URL.Query()["registry"]; found { + for _, r := range registries { + for k, v := range configs { + if strings.Contains(k, r) { + v := v + auth = &v + break + } + } + if auth != nil { + break + } + } + + if auth == nil { + logrus.Debugf("%q header found in request, but \"registry=%v\" query parameter not provided", + xRegistryConfigHeader, registries) + } else { + logrus.Debugf("%q header found in request for username %q", xRegistryConfigHeader, auth.Username) + } + } + + return auth, configs, nil +} + +// getAuthCredentials extracts one or more DockerAuthConfigs from an xRegistryAuthHeader +// value. The header could specify a single-auth config in which case the +// first return value is set. In case of a multi-auth header, the contents are +// returned in the second return value. +func getAuthCredentials(headers []string) (*types.DockerAuthConfig, map[string]types.DockerAuthConfig, error) { + authHeader := headers[0] + + // First look for a multi-auth header (i.e., a map). + authConfigs, err := parseMultiAuthHeader(authHeader) + if err == nil { + return nil, authConfigs, nil + } + + // Fallback to looking for a single-auth header (i.e., one config). + authConfig, err := parseSingleAuthHeader(authHeader) + if err != nil { + return nil, nil, err + } + return &authConfig, nil, nil +} + +// MakeXRegistryConfigHeader returns a map with the "X-Registry-Config" header set, which can +// conveniently be used in the http stack. +func MakeXRegistryConfigHeader(sys *types.SystemContext, username, password string) (http.Header, error) { + if sys == nil { + sys = &types.SystemContext{} + } + authConfigs, err := imageAuth.GetAllCredentials(sys) + if err != nil { + return nil, err + } + + if username != "" { + authConfigs[""] = types.DockerAuthConfig{ + Username: username, + Password: password, + } + } + + if len(authConfigs) == 0 { + return nil, nil + } + content, err := encodeMultiAuthConfigs(authConfigs) + if err != nil { + return nil, err + } + return http.Header{xRegistryConfigHeader: []string{content}}, nil +} + +// MakeXRegistryAuthHeader returns a map with the "X-Registry-Auth" header set, which can +// conveniently be used in the http stack. +func MakeXRegistryAuthHeader(sys *types.SystemContext, username, password string) (http.Header, error) { + if username != "" { + content, err := encodeSingleAuthConfig(types.DockerAuthConfig{Username: username, Password: password}) + if err != nil { + return nil, err + } + return http.Header{xRegistryAuthHeader: []string{content}}, nil + } + + if sys == nil { + sys = &types.SystemContext{} + } + authConfigs, err := imageAuth.GetAllCredentials(sys) + if err != nil { + return nil, err + } + content, err := encodeMultiAuthConfigs(authConfigs) + if err != nil { + return nil, err + } + return http.Header{xRegistryAuthHeader: []string{content}}, nil +} + +// RemoveAuthfile is a convenience function that is meant to be called in a +// deferred statement. If non-empty, it removes the specified authfile and log +// errors. It's meant to reduce boilerplate code at call sites of +// `GetCredentials`. +func RemoveAuthfile(authfile string) { + if authfile == "" { + return + } + if err := os.Remove(authfile); err != nil { + logrus.Errorf("Removing temporary auth file %q: %v", authfile, err) + } +} + +// encodeSingleAuthConfig serializes the auth configuration as a base64 encoded JSON payload. +func encodeSingleAuthConfig(authConfig types.DockerAuthConfig) (string, error) { + conf := imageAuthToDockerAuth(authConfig) + buf, err := json.Marshal(conf) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(buf), nil +} + +// encodeMultiAuthConfigs serializes the auth configurations as a base64 encoded JSON payload. +func encodeMultiAuthConfigs(authConfigs map[string]types.DockerAuthConfig) (string, error) { + confs := make(map[string]dockerAPITypes.AuthConfig) + for registry, authConf := range authConfigs { + confs[registry] = imageAuthToDockerAuth(authConf) + } + buf, err := json.Marshal(confs) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(buf), nil +} + +// authConfigsToAuthFile stores the specified auth configs in a temporary files +// and returns its path. The file can later be used as an auth file for contacting +// one or more container registries. If tmpDir is empty, the system's default +// TMPDIR will be used. +func authConfigsToAuthFile(authConfigs map[string]types.DockerAuthConfig) (string, error) { + // Initialize an empty temporary JSON file. + tmpFile, err := os.CreateTemp("", "auth.json.") + if err != nil { + return "", err + } + if _, err := tmpFile.Write([]byte{'{', '}'}); err != nil { + return "", fmt.Errorf("initializing temporary auth file: %w", err) + } + if err := tmpFile.Close(); err != nil { + return "", fmt.Errorf("closing temporary auth file: %w", err) + } + authFilePath := tmpFile.Name() + + // Now use the c/image packages to store the credentials. It's battle + // tested, and we make sure to use the same code as the image backend. + sys := types.SystemContext{AuthFilePath: authFilePath} + for authFileKey, config := range authConfigs { + key := normalizeAuthFileKey(authFileKey) + + // Note that we do not validate the credentials here. We assume + // that all credentials are valid. They'll be used on demand + // later. + if err := imageAuth.SetAuthentication(&sys, key, config.Username, config.Password); err != nil { + return "", fmt.Errorf("storing credentials in temporary auth file (key: %q / %q, user: %q): %w", authFileKey, key, config.Username, err) + } + } + + return authFilePath, nil +} + +// normalizeAuthFileKey takes an auth file key and converts it into a new-style credential key +// in the canonical format, as interpreted by c/image/pkg/docker/config. +func normalizeAuthFileKey(authFileKey string) string { + stripped := strings.TrimPrefix(authFileKey, "http://") + stripped = strings.TrimPrefix(stripped, "https://") + + if stripped != authFileKey { // URLs are interpreted to mean complete registries + stripped, _, _ = strings.Cut(stripped, "/") + } + + // Only non-namespaced registry names (or URLs) need to be normalized; repo namespaces + // always use the simple format. + switch stripped { + case "registry-1.docker.io", "index.docker.io": + return "docker.io" + default: + return stripped + } +} + +// dockerAuthToImageAuth converts a docker auth config to one we're using +// internally from c/image. Note that the Docker types look slightly +// different, so we need to convert to be extra sure we're not running into +// undesired side-effects when unmarshalling directly to our types. +func dockerAuthToImageAuth(authConfig dockerAPITypes.AuthConfig) types.DockerAuthConfig { + return types.DockerAuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + IdentityToken: authConfig.IdentityToken, + } +} + +// reverse conversion of `dockerAuthToImageAuth`. +func imageAuthToDockerAuth(authConfig types.DockerAuthConfig) dockerAPITypes.AuthConfig { + return dockerAPITypes.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + IdentityToken: authConfig.IdentityToken, + } +} + +// parseSingleAuthHeader extracts a DockerAuthConfig from an xRegistryAuthHeader value. +// The header content is a single DockerAuthConfig. +func parseSingleAuthHeader(authHeader string) (types.DockerAuthConfig, error) { + // Accept "null" and handle it as empty value for compatibility reason with Docker. + // Some java docker clients pass this value, e.g. this one used in Eclipse. + if len(authHeader) == 0 || authHeader == "null" { + return types.DockerAuthConfig{}, nil + } + + authConfig := dockerAPITypes.AuthConfig{} + authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader)) + if err := json.NewDecoder(authJSON).Decode(&authConfig); err != nil { + return types.DockerAuthConfig{}, err + } + return dockerAuthToImageAuth(authConfig), nil +} + +// parseMultiAuthHeader extracts a DockerAuthConfig from an xRegistryAuthHeader value. +// The header content is a map[string]DockerAuthConfigs. +func parseMultiAuthHeader(authHeader string) (map[string]types.DockerAuthConfig, error) { + // Accept "null" and handle it as empty value for compatibility reason with Docker. + // Some java docker clients pass this value, e.g. this one used in Eclipse. + if len(authHeader) == 0 || authHeader == "null" { + return nil, nil + } + + dockerAuthConfigs := make(map[string]dockerAPITypes.AuthConfig) + authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader)) + if err := json.NewDecoder(authJSON).Decode(&dockerAuthConfigs); err != nil { + return nil, err + } + + // Now convert to the internal types. + authConfigs := make(map[string]types.DockerAuthConfig) + for server := range dockerAuthConfigs { + authConfigs[server] = dockerAuthToImageAuth(dockerAuthConfigs[server]) + } + return authConfigs, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/README.md b/vendor/github.com/containers/podman/v5/pkg/bindings/README.md new file mode 100644 index 00000000..773de5e8 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/README.md @@ -0,0 +1,244 @@ +# Podman Golang bindings +The Podman Go bindings are a set of functions to allow developers to execute Podman operations from within their Go based application. The Go bindings +connect to a Podman service which can run locally or on a remote machine. You can perform many operations including pulling and listing images, starting, +stopping or inspecting containers. Currently, the Podman repository has bindings available for operations on images, containers, pods, +networks and manifests among others. + +## Quick Start +The bindings require that the Podman system service is running for the specified user. This can be done with systemd using the `systemctl` command or manually +by calling the service directly. + +### Starting the service with system +The command to start the Podman service differs slightly depending on the user that is running the service. For a rootful service, +start the service like this: +``` +# systemctl start podman.socket +``` +For a non-privileged, aka rootless, user, start the service like this: + +``` +$ systemctl start --user podman.socket +``` + +### Starting the service manually +It can be handy to run the system service manually. Doing so allows you to enable debug messaging. +``` +$ podman --log-level=debug system service -t0 +``` +If you do not provide a specific path for the socket, a default is provided. The location of that socket for +rootful connections is `/run/podman/podman.sock` and for rootless it is `/run/user/USERID#/podman/podman.sock`. For more +information about the Podman system service, see `man podman-system-service`. + +### Creating a connection +Ensure the [required dependencies](https://podman.io/getting-started/installation#build-and-run-dependencies) are installed, +as they will be required to compile a Go program making use of the bindings. + + +The first step for using the bindings is to create a connection to the socket. As mentioned earlier, the destination +of the socket depends on the user who owns it. In this case, a rootful connection is made. + +``` +import ( + "context" + "fmt" + "os" + + "github.com/containers/podman/v5/pkg/bindings" +) + +func main() { + conn, err := bindings.NewConnection(context.Background(), "unix:///run/podman/podman.sock") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + +} +``` +The `conn` variable returned from the `bindings.NewConnection` function can then be used in subsequent function calls +to interact with containers. + +### Examples +The following examples build upon the connection example from above. They are all rootful connections as well. + +Note: Optional arguments to the bindings methods are set using With*() methods on *Option structures. +Composite types are not duplicated rather the address is used. As such, you should not change an underlying +field between initializing the *Option structure and calling the bindings method. + +#### Inspect a container +The following example obtains the inspect information for a container named `foorbar` and then prints +the container's ID. Note the use of optional inspect options for size. +``` +import ( + "context" + "fmt" + "os" + + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/bindings/containers" +) + +func main() { + conn, err := bindings.NewConnection(context.Background(), "unix:///run/podman/podman.sock") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + inspectData, err := containers.Inspect(conn, "foobar", new(containers.InspectOptions).WithSize(true)) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + // Print the container ID + fmt.Println(inspectData.ID) +} +``` + +#### Pull an image +The following example pulls the image `quay.ioo/libpod/alpine_nginx` to the local image store. +``` +import ( + "context" + "fmt" + "os" + + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/bindings/images" +) + +func main() { + conn, err := bindings.NewConnection(context.Background(), "unix:///run/podman/podman.sock") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + _, err = images.Pull(conn, "quay.io/libpod/alpine_nginx", nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +``` + +#### Pull an image, create a container, and start the container +The following example pulls the `quay.io/libpod/alpine_nginx` image and then creates a container named `foobar` +from it. And finally, it starts the container. +``` +import ( + "context" + "fmt" + "os" + + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/bindings/containers" + "github.com/containers/podman/v5/pkg/bindings/images" + "github.com/containers/podman/v5/pkg/specgen" +) + +func main() { + conn, err := bindings.NewConnection(context.Background(), "unix:///run/podman/podman.sock") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + _, err = images.Pull(conn, "quay.io/libpod/alpine_nginx", nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + s := specgen.NewSpecGenerator("quay.io/libpod/alpine_nginx", false) + s.Name = "foobar" + createResponse, err := containers.CreateWithSpec(conn, s, nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Container created.") + if err := containers.Start(conn, createResponse.ID, nil); err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Container started.") +} +``` + +## Debugging tips + +To debug in a development setup, you can start the Podman system service +in debug mode like: + +```bash +$ podman --log-level=debug system service -t 0 +``` + +The `--log-level=debug` echoes all the logged requests and is useful to +trace the execution path at a finer granularity. A snippet of a sample run looks like: + +```bash +INFO[0000] podman filtering at log level debug +DEBU[0000] Called service.PersistentPreRunE(podman --log-level=debug system service -t0) +DEBU[0000] Ignoring libpod.conf EventsLogger setting "/home/lsm5/.config/containers/containers.conf". Use "journald" if you want to change this setting and remove libpod.conf files. +DEBU[0000] Reading configuration file "/usr/share/containers/containers.conf" +DEBU[0000] Merged system config "/usr/share/containers/containers.conf": {Editors note: the remainder of this line was removed due to Jekyll formatting errors.} +DEBU[0000] Using conmon: "/usr/bin/conmon" +DEBU[0000] Initializing boltdb state at /home/lsm5/.local/share/containers/storage/libpod/bolt_state.db +DEBU[0000] Overriding run root "/run/user/1000/containers" with "/run/user/1000" from database +DEBU[0000] Using graph driver overlay +DEBU[0000] Using graph root /home/lsm5/.local/share/containers/storage +DEBU[0000] Using run root /run/user/1000 +DEBU[0000] Using static dir /home/lsm5/.local/share/containers/storage/libpod +DEBU[0000] Using tmp dir /run/user/1000/libpod/tmp +DEBU[0000] Using volume path /home/lsm5/.local/share/containers/storage/volumes +DEBU[0000] Set libpod namespace to "" +DEBU[0000] Not configuring container store +DEBU[0000] Initializing event backend file +DEBU[0000] using runtime "/usr/bin/runc" +DEBU[0000] using runtime "/usr/bin/crun" +WARN[0000] Error initializing configured OCI runtime kata: no valid executable found for OCI runtime kata: invalid argument +DEBU[0000] using runtime "/usr/bin/crun" +INFO[0000] Setting parallel job count to 25 +INFO[0000] podman filtering at log level debug +DEBU[0000] Called service.PersistentPreRunE(podman --log-level=debug system service -t0) +DEBU[0000] Ignoring libpod.conf EventsLogger setting "/home/lsm5/.config/containers/containers.conf". Use "journald" if you want to change this setting and remove libpod.conf files. +DEBU[0000] Reading configuration file "/usr/share/containers/containers.conf" +``` + +If the Podman system service has been started via systemd socket activation, +you can view the logs using journalctl. The logs after a sample run look like: + +```bash +$ journalctl --user --no-pager -u podman.socket +-- Reboot -- +Jul 22 13:50:40 nagato.nanadai.me systemd[1048]: Listening on Podman API Socket. +$ +``` + +```bash +$ journalctl --user --no-pager -u podman.service +Jul 22 13:50:53 nagato.nanadai.me systemd[1048]: Starting Podman API Service... +Jul 22 13:50:54 nagato.nanadai.me podman[1527]: time="2020-07-22T13:50:54-04:00" level=error msg="Error refreshing volume 38480630a8bdaa3e1a0ebd34c94038591b0d7ad994b37be5b4f2072bb6ef0879: error acquiring lock 0 for volume 38480630a8bdaa3e1a0ebd34c94038591b0d7ad994b37be5b4f2072bb6ef0879: file exists" +Jul 22 13:50:54 nagato.nanadai.me podman[1527]: time="2020-07-22T13:50:54-04:00" level=error msg="Error refreshing volume 47d410af4d762a0cc456a89e58f759937146fa3be32b5e95a698a1d4069f4024: error acquiring lock 0 for volume 47d410af4d762a0cc456a89e58f759937146fa3be32b5e95a698a1d4069f4024: file exists" +Jul 22 13:50:54 nagato.nanadai.me podman[1527]: time="2020-07-22T13:50:54-04:00" level=error msg="Error refreshing volume 86e73f082e344dad38c8792fb86b2017c4f133f2a8db87f239d1d28a78cf0868: error acquiring lock 0 for volume 86e73f082e344dad38c8792fb86b2017c4f133f2a8db87f239d1d28a78cf0868: file exists" +Jul 22 13:50:54 nagato.nanadai.me podman[1527]: time="2020-07-22T13:50:54-04:00" level=error msg="Error refreshing volume 9a16ea764be490a5563e384d9074ab0495e4d9119be380c664037d6cf1215631: error acquiring lock 0 for volume 9a16ea764be490a5563e384d9074ab0495e4d9119be380c664037d6cf1215631: file exists" +Jul 22 13:50:54 nagato.nanadai.me podman[1527]: time="2020-07-22T13:50:54-04:00" level=error msg="Error refreshing volume bfd6b2a97217f8655add13e0ad3f6b8e1c79bc1519b7a1e15361a107ccf57fc0: error acquiring lock 0 for volume bfd6b2a97217f8655add13e0ad3f6b8e1c79bc1519b7a1e15361a107ccf57fc0: file exists" +Jul 22 13:50:54 nagato.nanadai.me podman[1527]: time="2020-07-22T13:50:54-04:00" level=error msg="Error refreshing volume f9b9f630982452ebcbed24bd229b142fbeecd5d4c85791fca440b21d56fef563: error acquiring lock 0 for volume f9b9f630982452ebcbed24bd229b142fbeecd5d4c85791fca440b21d56fef563: file exists" +Jul 22 13:50:54 nagato.nanadai.me podman[1527]: Trying to pull registry.fedoraproject.org/fedora:latest... +Jul 22 13:50:55 nagato.nanadai.me podman[1527]: Getting image source signatures +Jul 22 13:50:55 nagato.nanadai.me podman[1527]: Copying blob sha256:dd9f43919ba05f05d4f783c31e83e5e776c4f5d29dd72b9ec5056b9576c10053 +Jul 22 13:50:55 nagato.nanadai.me podman[1527]: Copying config sha256:00ff39a8bf19f810a7e641f7eb3ddc47635913a19c4996debd91fafb6b379069 +Jul 22 13:50:55 nagato.nanadai.me podman[1527]: Writing manifest to image destination +Jul 22 13:50:55 nagato.nanadai.me podman[1527]: Storing signatures +Jul 22 13:50:55 nagato.nanadai.me systemd[1048]: podman.service: unit configures an IP firewall, but not running as root. +Jul 22 13:50:55 nagato.nanadai.me systemd[1048]: (This warning is only shown for the first unit using IP firewalling.) +Jul 22 13:51:15 nagato.nanadai.me systemd[1048]: podman.service: Succeeded. +Jul 22 13:51:15 nagato.nanadai.me systemd[1048]: Finished Podman API Service. +Jul 22 13:51:15 nagato.nanadai.me systemd[1048]: podman.service: Consumed 1.339s CPU time. +$ +``` + +You can also verify that the information being passed back and forth is correct by putting +with a tool like `socat`, which can dump what the socket is seeing. + +## Reducing Binary Size with "remote" Build Tag +When building a program that uses the Podman Go bindings, you can reduce the binary size by passing the "remote" build tag to the go build command. This tag excludes code related to local Podman operations, which is not needed for applications that only interact with Podman over a network. diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/connection.go b/vendor/github.com/containers/podman/v5/pkg/bindings/connection.go new file mode 100644 index 00000000..b9aceff1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/connection.go @@ -0,0 +1,531 @@ +package bindings + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "os/user" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/blang/semver/v4" + "github.com/containers/podman/v5/pkg/util/tlsutil" + "github.com/containers/podman/v5/version" + "github.com/kevinburke/ssh_config" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/ssh" + "go.podman.io/storage/pkg/fileutils" + "golang.org/x/net/proxy" +) + +type APIResponse struct { + *http.Response + Request *http.Request +} + +type Connection struct { + URI *url.URL + Client *http.Client + tls bool +} + +type valueKey string + +const ( + clientKey = valueKey("Client") + versionKey = valueKey("ServiceVersion") + machineModeKey = valueKey("MachineMode") +) + +type ConnectError struct { + Err error +} + +func (c ConnectError) Error() string { + return "unable to connect to Podman socket: " + c.Err.Error() +} + +func (c ConnectError) Unwrap() error { + return c.Err +} + +func newConnectError(err error) error { + return ConnectError{Err: err} +} + +// GetClient from context build by NewConnection() +func GetClient(ctx context.Context) (*Connection, error) { + if c, ok := ctx.Value(clientKey).(*Connection); ok { + return c, nil + } + return nil, fmt.Errorf("%s not set in context", clientKey) +} + +func GetMachineMode(ctx context.Context) bool { + if v, ok := ctx.Value(machineModeKey).(bool); ok { + return v + } + return false +} + +// ServiceVersion from context build by NewConnection() +func ServiceVersion(ctx context.Context) *semver.Version { + if v, ok := ctx.Value(versionKey).(*semver.Version); ok { + return v + } + return new(semver.Version) +} + +// JoinURL elements with '/' +func JoinURL(elements ...string) string { + return "/" + strings.Join(elements, "/") +} + +// NewConnection creates a new service connection without an identity +func NewConnection(ctx context.Context, uri string) (context.Context, error) { + return NewConnectionWithOptions(ctx, Options{URI: uri}) +} + +// NewConnectionWithIdentity takes a URI as a string and returns a context with the +// Connection embedded as a value. This context needs to be passed to each +// endpoint to work correctly. +// +// A valid URI connection should be scheme:// +// For example tcp://localhost: +// or unix:///run/podman/podman.sock +// or ssh://@[:port]/run/podman/podman.sock +func NewConnectionWithIdentity(ctx context.Context, uri string, identity string, machine bool) (context.Context, error) { + return NewConnectionWithOptions(ctx, Options{URI: uri, Identity: identity, Machine: machine}) +} + +type Options struct { + URI string + Identity string + TLSCertFile string + TLSKeyFile string + TLSCAFile string + Machine bool +} + +func orEnv(s string, env string) string { + if len(s) != 0 { + return s + } + s, _ = os.LookupEnv(env) + return s +} + +func NewConnectionWithOptions(ctx context.Context, opts Options) (context.Context, error) { + var err error + + uri := orEnv(opts.URI, "CONTAINER_HOST") + identity := orEnv(opts.Identity, "CONTAINER_SSHKEY") + + _url, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("value of CONTAINER_HOST is not a valid url: %s: %w", uri, err) + } + + // Now we set up the http Client to use the connection above + var connection Connection + switch _url.Scheme { + case "ssh": + conn, err := sshClient(_url, uri, identity, opts.Machine) + if err != nil { + return nil, err + } + connection = conn + case "unix": + if !strings.HasPrefix(uri, "unix:///") { + // autofix unix://path_element vs unix:///path_element + _url.Path = JoinURL(_url.Host, _url.Path) + _url.Host = "" + } + connection = unixClient(_url) + case "tcp": + if !strings.HasPrefix(uri, "tcp://") { + return nil, errors.New("tcp URIs should begin with tcp://") + } + conn, err := tcpClient(_url, opts.TLSCertFile, opts.TLSKeyFile, opts.TLSCAFile) + if err != nil { + return nil, newConnectError(err) + } + connection = conn + default: + return nil, fmt.Errorf("unable to create connection. %q is not a supported schema", _url.Scheme) + } + + ctx = context.WithValue(ctx, clientKey, &connection) + serviceVersion, err := pingNewConnection(ctx) + if err != nil { + return nil, newConnectError(err) + } + ctx = context.WithValue(ctx, versionKey, serviceVersion) + + ctx = context.WithValue(ctx, machineModeKey, opts.Machine) + return ctx, nil +} + +func sshClient(_url *url.URL, uri string, identity string, machine bool) (Connection, error) { + var ( + err error + port int + ) + connection := Connection{ + URI: _url, + } + userinfo := _url.User + + if _url.Port() != "" { + port, err = strconv.Atoi(_url.Port()) + if err != nil { + return connection, err + } + } + + // only parse ssh_config when we are not connecting to a machine + // For machine connections we always have the full URL in the + // system connection so reading the file is just unnecessary. + if !machine { + alias := _url.Hostname() + cfg := ssh_config.DefaultUserSettings + cfg.IgnoreErrors = true + found := false + + if userinfo == nil { + if val := cfg.Get(alias, "User"); val != "" { + userinfo = url.User(val) + found = true + } + } + // not in url or ssh_config so default to current user + if userinfo == nil { + u, err := user.Current() + if err != nil { + return connection, fmt.Errorf("current user could not be determined: %w", err) + } + userinfo = url.User(u.Username) + } + + if val := cfg.Get(alias, "Hostname"); val != "" { + uri = val + found = true + } + + if port == 0 { + if val := cfg.Get(alias, "Port"); val != "" { + if val != ssh_config.Default("Port") { + port, err = strconv.Atoi(val) + if err != nil { + return connection, fmt.Errorf("port is not an int: %s: %w", val, err) + } + found = true + } + } + } + // not in ssh config or url so use default 22 port + if port == 0 { + port = 22 + } + + if identity == "" { + if val := cfg.Get(alias, "IdentityFile"); val != "" { + // we get default IdentityFile value (~/.ssh/identity) every time + // checking if we got default + defaultIdentityPath := val == ssh_config.Default("IdentityFile") + + identity = strings.Trim(val, "\"") + + if strings.HasPrefix(identity, "~/") { + homedir, err := os.UserHomeDir() + if err != nil { + return connection, fmt.Errorf("failed to find home dir: %w", err) + } + + identity = filepath.Join(homedir, identity[2:]) + } + + // if we have default value but no file exists ignoring identity + if err := fileutils.Exists(identity); err != nil && defaultIdentityPath { + identity = "" + } else { + found = true + } + } + } + + if found { + logrus.Debugf("ssh_config alias found: %s", alias) + logrus.Debugf(" User: %s", userinfo.Username()) + logrus.Debugf(" Hostname: %s", uri) + logrus.Debugf(" Port: %d", port) + logrus.Debugf(" IdentityFile: %q", identity) + } + } + conn, err := ssh.Dial(&ssh.ConnectionDialOptions{ + Host: uri, + Identity: identity, + User: userinfo, + Port: port, + InsecureIsMachineConnection: machine, + }, ssh.GolangMode) + if err != nil { + return connection, newConnectError(err) + } + if _url.Path == "" { + session, err := conn.NewSession() + if err != nil { + return connection, err + } + defer session.Close() + + var b bytes.Buffer + session.Stdout = &b + if err := session.Run( + "podman info --format '{{.Host.RemoteSocket.Path}}'"); err != nil { + return connection, err + } + val := strings.TrimSuffix(b.String(), "\n") + _url.Path = val + } + dialContext := func(_ context.Context, _, _ string) (net.Conn, error) { + return ssh.DialNet(conn, "unix", _url) + } + connection.Client = &http.Client{ + Transport: &http.Transport{ + DialContext: dialContext, + }, + } + return connection, nil +} + +func tcpClient(_url *url.URL, tlsCertFile, tlsKeyFile, tlsCAFile string) (Connection, error) { + connection := Connection{ + URI: _url, + } + dialContext := func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("tcp", _url.Host) + } + // use proxy if env `CONTAINER_PROXY` set + if proxyURI, found := os.LookupEnv("CONTAINER_PROXY"); found { + proxyURL, err := url.Parse(proxyURI) + if err != nil { + return connection, fmt.Errorf("value of CONTAINER_PROXY is not a valid url: %s: %w", proxyURI, err) + } + proxyDialer, err := proxy.FromURL(proxyURL, proxy.Direct) + if err != nil { + return connection, fmt.Errorf("unable to dial to proxy %s, %w", proxyURI, err) + } + dialContext = func(_ context.Context, _, _ string) (net.Conn, error) { + logrus.Debugf("use proxy %s, but proxy dialer does not support dial timeout", proxyURI) + return proxyDialer.Dial("tcp", _url.Host) + } + if f, ok := proxyDialer.(proxy.ContextDialer); ok { + dialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { + // the default tcp dial timeout seems to be 75s, podman-remote will retry 3 times before exit. + // here we change proxy dial timeout to 3s + logrus.Debugf("use proxy %s with dial timeout 3s", proxyURI) + ctx, cancel := context.WithTimeout(ctx, time.Second*3) + defer cancel() // It's safe to cancel, `f.DialContext` only use ctx for returning the Conn, not the lifetime of the Conn. + return f.DialContext(ctx, "tcp", _url.Host) + } + } + } + transport := http.Transport{ + DialContext: dialContext, + DisableCompression: true, + } + if len(tlsCAFile) != 0 || len(tlsCertFile) != 0 || len(tlsKeyFile) != 0 { + logrus.Debugf("using TLS cert=%s key=%s ca=%s", tlsCertFile, tlsKeyFile, tlsCAFile) + transport.TLSClientConfig = &tls.Config{} + connection.tls = true + } + if len(tlsCAFile) != 0 { + pool, err := tlsutil.ReadCertBundle(tlsCAFile) + if err != nil { + return connection, fmt.Errorf("unable to read CA bundle: %w", err) + } + transport.TLSClientConfig.RootCAs = pool + } + if (len(tlsCertFile) == 0) != (len(tlsKeyFile) == 0) { + return connection, fmt.Errorf("TLS Key and Certificate must both or neither be provided") + } + if len(tlsCertFile) != 0 && len(tlsKeyFile) != 0 { + keyPair, err := tls.LoadX509KeyPair(tlsCertFile, tlsKeyFile) + if err != nil { + return connection, fmt.Errorf("unable to read TLS key pair: %w", err) + } + transport.TLSClientConfig.Certificates = append(transport.TLSClientConfig.Certificates, keyPair) + } + connection.Client = &http.Client{ + Transport: &transport, + } + return connection, nil +} + +// pingNewConnection pings to make sure the RESTFUL service is up +// and running. it should only be used when initializing a connection +func pingNewConnection(ctx context.Context) (*semver.Version, error) { + client, err := GetClient(ctx) + if err != nil { + return nil, err + } + // the ping endpoint sits at / in this case + response, err := client.DoRequest(ctx, nil, http.MethodGet, "/_ping", nil, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode == http.StatusOK { + versionHdr := response.Header.Get("Libpod-API-Version") + if versionHdr == "" { + logrus.Warn("Service did not provide Libpod-API-Version Header") + return new(semver.Version), nil + } + versionSrv, err := semver.ParseTolerant(versionHdr) + if err != nil { + return nil, err + } + + switch version.APIVersion[version.Libpod][version.MinimalAPI].Compare(versionSrv) { + case -1, 0: + // Server's job when Client version is equal or older + return &versionSrv, nil + case 1: + return nil, fmt.Errorf("server API version is too old. Client %q server %q", + version.APIVersion[version.Libpod][version.MinimalAPI].String(), versionSrv.String()) + } + } + return nil, fmt.Errorf("ping response was %d", response.StatusCode) +} + +func unixClient(_url *url.URL) Connection { + connection := Connection{URI: _url} + connection.Client = &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, "unix", _url.Path) + }, + DisableCompression: true, + }, + } + return connection +} + +// DoRequest assembles the http request and returns the response. +// The caller must close the response body. +func (c *Connection) DoRequest(ctx context.Context, httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, headers http.Header, pathValues ...string) (*APIResponse, error) { + var ( + err error + response *http.Response + ) + + params := make([]any, len(pathValues)+1) + + if v := headers.Values("API-Version"); len(v) > 0 { + params[0] = v[0] + } else { + // Including the semver suffices breaks older services... so do not include them + v := version.APIVersion[version.Libpod][version.CurrentAPI] + params[0] = fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) + } + + for i, pv := range pathValues { + // url.URL lacks the semantics for escaping embedded path parameters... so we manually + // escape each one and assume the caller included the correct formatting in "endpoint" + params[i+1] = url.PathEscape(pv) + } + + baseURL := "http://d" + if c.URI.Scheme == "tcp" { + var scheme string + if c.tls { + scheme = "https" + } else { + scheme = "http" + } + // Allow path prefixes for tcp connections to match Docker behavior + baseURL = scheme + "://" + c.URI.Host + c.URI.Path + } + uri := fmt.Sprintf(baseURL+"/v%s/libpod"+endpoint, params...) + logrus.Debugf("DoRequest Method: %s URI: %v", httpMethod, uri) + + req, err := http.NewRequestWithContext(ctx, httpMethod, uri, httpBody) + if err != nil { + return nil, err + } + if len(queryParams) > 0 { + req.URL.RawQuery = queryParams.Encode() + } + + for key, val := range headers { + if key == "API-Version" { + continue + } + + for _, v := range val { + req.Header.Add(key, v) + } + } + + // Give the Do three chances in the case of a comm/service hiccup + for i := 1; i <= 3; i++ { + response, err = c.Client.Do(req) //nolint:bodyclose // The caller has to close the body. + if err == nil { + break + } + time.Sleep(time.Duration(i*100) * time.Millisecond) + } + return &APIResponse{response, req}, err +} + +// GetDialer returns raw Transport.DialContext from client +func (c *Connection) GetDialer(ctx context.Context) (net.Conn, error) { + client := c.Client + transport := client.Transport.(*http.Transport) + if transport.DialContext != nil && transport.TLSClientConfig == nil { + return transport.DialContext(ctx, c.URI.Scheme, c.URI.String()) + } + + return nil, errors.New("unable to get dial context") +} + +// IsInformational returns true if the response code is 1xx +func (h *APIResponse) IsInformational() bool { + return h.Response.StatusCode/100 == 1 +} + +// IsSuccess returns true if the response code is 2xx +func (h *APIResponse) IsSuccess() bool { + return h.Response.StatusCode/100 == 2 +} + +// IsRedirection returns true if the response code is 3xx +func (h *APIResponse) IsRedirection() bool { + return h.Response.StatusCode/100 == 3 +} + +// IsClientError returns true if the response code is 4xx +func (h *APIResponse) IsClientError() bool { + return h.Response.StatusCode/100 == 4 +} + +// IsConflictError returns true if the response code is 409 +func (h *APIResponse) IsConflictError() bool { + return h.Response.StatusCode == http.StatusConflict +} + +// IsServerError returns true if the response code is 5xx +func (h *APIResponse) IsServerError() bool { + return h.Response.StatusCode/100 == 5 +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/archive.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/archive.go new file mode 100644 index 00000000..f24efdf6 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/archive.go @@ -0,0 +1,108 @@ +package containers + +import ( + "context" + "errors" + "io" + "net/http" + "net/url" + + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/copy" + "github.com/containers/podman/v5/pkg/domain/entities/types" +) + +// Stat checks if the specified path is on the container. Note that the stat +// report may be set even in case of an error. This happens when the path +// resolves to symlink pointing to a non-existent path. +func Stat(ctx context.Context, nameOrID string, path string) (*types.ContainerStatReport, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + params.Set("path", path) + + response, err := conn.DoRequest(ctx, nil, http.MethodHead, "/containers/%s/archive", params, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + var finalErr error + if response.StatusCode == http.StatusNotFound { + finalErr = copy.ErrENOENT + } else if response.StatusCode != http.StatusOK { + finalErr = errors.New(response.Status) + } + + var statReport *types.ContainerStatReport + + fileInfo, err := copy.ExtractFileInfoFromHeader(&response.Header) + if err != nil && finalErr == nil { + return nil, err + } + + if fileInfo != nil { + statReport = &types.ContainerStatReport{FileInfo: *fileInfo} + } + + return statReport, finalErr +} + +func CopyFromArchive(ctx context.Context, nameOrID string, path string, reader io.Reader) (types.ContainerCopyFunc, error) { + return CopyFromArchiveWithOptions(ctx, nameOrID, path, reader, nil) +} + +// CopyFromArchiveWithOptions copy files into container +func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path string, reader io.Reader, options *CopyOptions) (types.ContainerCopyFunc, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + params, err := options.ToParams() + if err != nil { + return nil, err + } + + params.Set("path", path) + + return func() error { + response, err := conn.DoRequest(ctx, reader, http.MethodPut, "/containers/%s/archive", params, nil, nameOrID) + if err != nil { + return err + } + + if response.StatusCode != http.StatusOK { + return errors.New(response.Status) + } + return response.Process(nil) + }, nil +} + +// CopyToArchive copy files from container +func CopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (types.ContainerCopyFunc, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + params.Set("path", path) + + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/archive", params, nil, nameOrID) + if err != nil { + return nil, err + } + + if response.StatusCode != http.StatusOK { + defer response.Body.Close() + return nil, response.Process(nil) + } + + return func() error { + defer response.Body.Close() + _, err := io.Copy(writer, response.Body) + return err + }, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/attach.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/attach.go new file mode 100644 index 00000000..a5c10085 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/attach.go @@ -0,0 +1,617 @@ +package containers + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "reflect" + "strconv" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/moby/term" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/detach" + terminal "golang.org/x/term" +) + +// The CloseWriter interface is used to determine whether we can do a one-sided +// close of a hijacked connection. +type CloseWriter interface { + CloseWrite() error +} + +// Attach attaches to a running container. +// +// NOTE: When stdin is provided, this function currently leaks a goroutine reading from that stream +// even if the ctx is cancelled. The goroutine will only exit if the input stream is closed. For example, +// if stdin is `os.Stdin` attached to a tty, the goroutine will consume a chunk of user input from the +// terminal even after the container has exited. In this scenario the os.Stdin stream will not be expected +// to be closed. +func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Writer, stderr io.Writer, attachReady chan bool, options *AttachOptions) error { + if options == nil { + options = new(AttachOptions) + } + isSet := struct { + stdin bool + stdout bool + stderr bool + }{ + stdin: stdin != nil && !reflect.ValueOf(stdin).IsNil(), + stdout: stdout != nil && !reflect.ValueOf(stdout).IsNil(), + stderr: stderr != nil && !reflect.ValueOf(stderr).IsNil(), + } + // Ensure golang can determine that interfaces are "really" nil + if !isSet.stdin { + stdin = (io.Reader)(nil) + } + if !isSet.stdout { + stdout = (io.Writer)(nil) + } + if !isSet.stderr { + stderr = (io.Writer)(nil) + } + + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + + // Do we need to wire in stdin? + ctnr, err := Inspect(ctx, nameOrID, new(InspectOptions).WithSize(false)) + if err != nil { + return err + } + + params, err := options.ToParams() + if err != nil { + return err + } + detachKeysInBytes := []byte{} + if options.Changed("DetachKeys") { + params.Add("detachKeys", options.GetDetachKeys()) + + detachKeysInBytes, err = term.ToBytes(options.GetDetachKeys()) + if err != nil { + return fmt.Errorf("invalid detach keys: %w", err) + } + } + if isSet.stdin { + params.Add("stdin", "true") + } + if isSet.stdout { + params.Add("stdout", "true") + } + if isSet.stderr { + params.Add("stderr", "true") + } + + // Unless all requirements are met, don't use "stdin" is a terminal + file, ok := stdin.(*os.File) + outFile, outOk := stdout.(*os.File) + needTTY := ok && outOk && terminal.IsTerminal(int(file.Fd())) && ctnr.Config.Tty + if needTTY { + state, err := setRawTerminal(file) + if err != nil { + return err + } + defer func() { + if err := terminal.Restore(int(file.Fd()), state); err != nil { + logrus.Errorf("Unable to restore terminal: %q", err) + } + logrus.SetFormatter(&logrus.TextFormatter{}) + }() + } + + cw, socket, err := newUpgradeRequest(ctx, conn, nil, fmt.Sprintf("/containers/%s/attach", nameOrID), params) + if err != nil { + return err + } + defer socket.Close() + + if needTTY { + winChange := make(chan os.Signal, 1) + winCtx, winCancel := context.WithCancel(ctx) + defer winCancel() + notifyWinChange(winCtx, winChange, file, outFile) + attachHandleResize(ctx, winCtx, winChange, false, nameOrID, file, outFile) + } + + // If we are attaching around a start, we need to "signal" + // back that we are in fact attached so that started does + // not execute before we can attach. + if attachReady != nil { + attachReady <- true + } + + stdoutChan := make(chan error) + stdinChan := make(chan error, 1) // stdin channel should not block + + if isSet.stdin { + go func() { + logrus.Debugf("Copying STDIN to socket") + + _, err := detach.Copy(socket, stdin, detachKeysInBytes) + // Ignore "closed network connection" as it occurs when the container ends, which is expected. + // This avoids noisy logs but does not fix the goroutine leak + // https://github.com/containers/podman/issues/25344 + if err != nil && err != define.ErrDetach && !errors.Is(err, net.ErrClosed) { + logrus.Errorf("Failed to write input to service: %v", err) + } + if err == nil { + cw.CloseWrite() + } + stdinChan <- err + }() + } + + buffer := make([]byte, 1024) + if ctnr.Config.Tty { + go func() { + logrus.Debugf("Copying STDOUT of container in terminal mode") + + if !isSet.stdout { + stdoutChan <- fmt.Errorf("container %q requires stdout to be set", ctnr.ID) + } + // If not multiplex'ed, read from server and write to stdout + _, err := io.Copy(stdout, socket) + + stdoutChan <- err + }() + + for { + select { + case err := <-stdoutChan: + if err != nil { + return err + } + + return nil + case err := <-stdinChan: + if err != nil { + return err + } + + return <-stdoutChan + } + } + } else { + logrus.Debugf("Copying standard streams of container %q in non-terminal mode", ctnr.ID) + for { + // Read multiplexed channels and write to appropriate stream + fd, l, err := DemuxHeader(socket, buffer) + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return err + } + frame, err := DemuxFrame(socket, buffer, l) + if err != nil { + return err + } + + switch fd { + case 0: + if isSet.stdout { + if _, err := stdout.Write(frame); err != nil { + return err + } + } + case 1: + if isSet.stdout { + if _, err := stdout.Write(frame); err != nil { + return err + } + } + case 2: + if isSet.stderr { + if _, err := stderr.Write(frame); err != nil { + return err + } + } + case 3: + return fmt.Errorf("from service from stream: %s", frame) + default: + return fmt.Errorf("unrecognized channel '%d' in header, 0-3 supported", fd) + } + } + } +} + +// DemuxHeader reads header for stream from server multiplexed stdin/stdout/stderr/2nd error channel +func DemuxHeader(r io.Reader, buffer []byte) (fd, sz int, err error) { + _, err = io.ReadFull(r, buffer[0:8]) + if err != nil { + return + } + + fd = int(buffer[0]) + if fd < 0 || fd > 3 { + err = fmt.Errorf(`channel "%d" found, 0-3 supported: %w`, fd, ErrLostSync) + return + } + + sz = int(binary.BigEndian.Uint32(buffer[4:8])) + return +} + +// DemuxFrame reads contents for frame from server multiplexed stdin/stdout/stderr/2nd error channel +func DemuxFrame(r io.Reader, buffer []byte, length int) (frame []byte, err error) { + if len(buffer) < length { + buffer = append(buffer, make([]byte, length-len(buffer)+1)...) + } + + _, err = io.ReadFull(r, buffer[0:length]) + if err != nil { + return nil, err + } + + return buffer[0:length], nil +} + +// ResizeContainerTTY sets container's TTY height and width in characters +func ResizeContainerTTY(ctx context.Context, nameOrID string, options *ResizeTTYOptions) error { + if options == nil { + options = new(ResizeTTYOptions) + } + return resizeTTY(ctx, bindings.JoinURL("containers", nameOrID, "resize"), options.Height, options.Width) +} + +// ResizeExecTTY sets session's TTY height and width in characters +func ResizeExecTTY(ctx context.Context, sessionID string, options *ResizeExecTTYOptions) error { + if options == nil { + options = new(ResizeExecTTYOptions) + } + return resizeTTY(ctx, bindings.JoinURL("exec", sessionID, "resize"), options.Height, options.Width) +} + +// resizeTTY set size of TTY of container +func resizeTTY(ctx context.Context, endpoint string, height *int, width *int) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + + params := url.Values{} + if height != nil { + params.Set("h", strconv.Itoa(*height)) + } + if width != nil { + params.Set("w", strconv.Itoa(*width)) + } + params.Set("running", "true") + rsp, err := conn.DoRequest(ctx, nil, http.MethodPost, endpoint, params, nil) + if err != nil { + return err + } + defer rsp.Body.Close() + + return rsp.Process(nil) +} + +type rawFormatter struct { + logrus.TextFormatter +} + +func (f *rawFormatter) Format(entry *logrus.Entry) ([]byte, error) { + buffer, err := f.TextFormatter.Format(entry) + if err != nil { + return buffer, err + } + return append(buffer, '\r'), nil +} + +// This is intended to not be run as a goroutine, handling resizing for a container +// or exec session. It will call resize once and then starts a goroutine which calls resize on winChange +func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, isExec bool, id string, file *os.File, outFile *os.File) { + resize := func() { + w, h, err := getTermSize(file, outFile) + if err != nil { + logrus.Warnf("Failed to obtain TTY size: %v", err) + } + + var resizeErr error + if isExec { + resizeErr = ResizeExecTTY(ctx, id, new(ResizeExecTTYOptions).WithHeight(h).WithWidth(w)) + } else { + resizeErr = ResizeContainerTTY(ctx, id, new(ResizeTTYOptions).WithHeight(h).WithWidth(w)) + } + if resizeErr != nil { + logrus.Debugf("Failed to resize TTY: %v", resizeErr) + } + } + + resize() + + go func() { + for { + select { + case <-winCtx.Done(): + return + case <-winChange: + resize() + } + } + }() +} + +// Configure the given terminal for raw mode +func setRawTerminal(file *os.File) (*terminal.State, error) { + state, err := makeRawTerm(file) + if err != nil { + return nil, err + } + + logrus.SetFormatter(&rawFormatter{}) + + return state, err +} + +// ExecStartAndAttach starts and attaches to a given exec session. +// +// NOTE: When options.GetAttachInput() is true, this function currently leaks a goroutine reading from that stream +// even if the ctx is cancelled. The goroutine will only exit if the input stream is closed. For example, +// if stdin is `os.Stdin` attached to a tty, the goroutine will consume a chunk of user input from the +// terminal even after the exec session has exited. In this scenario the os.Stdin stream will not be expected +// to be closed. +func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStartAndAttachOptions) error { + if options == nil { + options = new(ExecStartAndAttachOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + + // TODO: Make this configurable (can't use streams' InputStream as it's + // buffered) + terminalFile := os.Stdin + terminalOutFile := os.Stdout + + logrus.Debugf("Starting & Attaching to exec session ID %q", sessionID) + + // We need to inspect the exec session first to determine whether to use + // -t. + resp, err := conn.DoRequest(ctx, nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID) + if err != nil { + return err + } + defer resp.Body.Close() + + respStruct := new(define.InspectExecSession) + if err := resp.Process(respStruct); err != nil { + return err + } + isTerm := true + if respStruct.ProcessConfig != nil { + isTerm = respStruct.ProcessConfig.Tty + } + + // If we are in TTY mode, we need to set raw mode for the terminal. + // TODO: Share all of this with Attach() for containers. + needTTY := terminalFile != nil && terminal.IsTerminal(int(terminalFile.Fd())) && isTerm + + body := struct { + Detach bool `json:"Detach"` + TTY bool `json:"Tty"` + Height uint16 `json:"h"` + Width uint16 `json:"w"` + }{ + Detach: false, + TTY: needTTY, + } + + if needTTY { + state, err := setRawTerminal(terminalFile) + if err != nil { + return err + } + defer func() { + if err := terminal.Restore(int(terminalFile.Fd()), state); err != nil { + logrus.Errorf("Unable to restore terminal: %q", err) + } + logrus.SetFormatter(&logrus.TextFormatter{}) + }() + w, h, err := getTermSize(terminalFile, terminalOutFile) + if err != nil { + logrus.Warnf("Failed to obtain TTY size: %v", err) + } + body.Width = uint16(w) + body.Height = uint16(h) + } + + bodyJSON, err := json.Marshal(body) + if err != nil { + return err + } + + cw, socket, err := newUpgradeRequest(ctx, conn, bytes.NewReader(bodyJSON), fmt.Sprintf("/exec/%s/start", sessionID), nil) + if err != nil { + return err + } + defer socket.Close() + + if needTTY { + winChange := make(chan os.Signal, 1) + winCtx, winCancel := context.WithCancel(ctx) + defer winCancel() + + notifyWinChange(winCtx, winChange, terminalFile, terminalOutFile) + attachHandleResize(ctx, winCtx, winChange, true, sessionID, terminalFile, terminalOutFile) + } + + if options.GetAttachInput() { + go func() { + logrus.Debugf("Copying STDIN to socket") + _, err := detach.Copy(socket, options.InputStream, []byte{}) + // Ignore "closed network connection" as it occurs when the exec ends, which is expected. + // This avoids noisy logs but does not fix the goroutine leak + // https://github.com/containers/podman/issues/25344 + if err != nil && !errors.Is(err, net.ErrClosed) { + logrus.Errorf("Failed to write input to service: %v", err) + } + + if err == nil { + cw.CloseWrite() + } + }() + } + + buffer := make([]byte, 1024) + if isTerm { + logrus.Debugf("Handling terminal attach to exec") + if !options.GetAttachOutput() { + return fmt.Errorf("exec session %s has a terminal and must have STDOUT enabled", sessionID) + } + // If not multiplex'ed, read from server and write to stdout + _, err := detach.Copy(options.GetOutputStream(), socket, []byte{}) + if err != nil { + return err + } + } else { + logrus.Debugf("Handling non-terminal attach to exec") + for { + // Read multiplexed channels and write to appropriate stream + fd, l, err := DemuxHeader(socket, buffer) + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return err + } + frame, err := DemuxFrame(socket, buffer, l) + if err != nil { + return err + } + + switch fd { + case 0: + if options.GetAttachInput() { + // Write STDIN to STDOUT (echoing characters + // typed by another attach session) + if _, err := options.GetOutputStream().Write(frame); err != nil { + return err + } + } + case 1: + if options.GetAttachOutput() { + if _, err := options.GetOutputStream().Write(frame); err != nil { + return err + } + } + case 2: + if options.GetAttachError() { + if _, err := options.GetErrorStream().Write(frame); err != nil { + return err + } + } + case 3: + return fmt.Errorf("from service from stream: %s", frame) + default: + return fmt.Errorf("unrecognized channel '%d' in header, 0-3 supported", fd) + } + } + } + return nil +} + +type closeWrite struct { + // sock is the underlying socket of the connection. + // Do not use that field directly. + sock net.Conn +} + +func (cw *closeWrite) CloseWrite() { + if closeWrite, ok := cw.sock.(CloseWriter); ok { + logrus.Debugf("Closing STDIN") + if err := closeWrite.CloseWrite(); err != nil { + logrus.Warnf("Failed to close STDIN for writing: %v", err) + } + } +} + +// newUpgradeRequest performs a new http Upgrade request, it return the closeWrite which should be used +// to close the STDIN side used and the ReadWriter which MUST be uses to write/read from the connection +// and which must closed when finished. Do not access the new.Conn in closeWrite directly. +func newUpgradeRequest(ctx context.Context, conn *bindings.Connection, body io.Reader, path string, params url.Values) (*closeWrite, io.ReadWriteCloser, error) { + headers := http.Header{ + "Connection": []string{"Upgrade"}, + "Upgrade": []string{"tcp"}, + } + + // FIXME: This is one giant race condition. Let's hope no-one uses this same client until we're done! + var socket net.Conn + socketSet := false + dialContext := conn.Client.Transport.(*http.Transport).DialContext + tlsConfig := conn.Client.Transport.(*http.Transport).TLSClientConfig + t := &http.Transport{ + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + c, err := dialContext(ctx, network, address) + if err != nil { + return nil, err + } + if !socketSet { + socket = c + socketSet = true + } + return c, err + }, + DialTLSContext: func(ctx context.Context, network, address string) (net.Conn, error) { + c, err := dialContext(ctx, network, address) + if err != nil { + return nil, err + } + var cfg *tls.Config + if tlsConfig == nil { + cfg = new(tls.Config) + } else { + cfg = tlsConfig.Clone() + } + if cfg.ServerName == "" { + var firstTLSHost string + if firstTLSHost, _, err = net.SplitHostPort(address); err != nil { + return nil, err + } + cfg.ServerName = firstTLSHost + } + c = tls.Client(c, cfg) + if !socketSet { + socket = c + socketSet = true + } + return c, err + }, + IdleConnTimeout: time.Duration(0), + TLSClientConfig: tlsConfig, + } + conn.Client.Transport = t + response, err := conn.DoRequest(ctx, body, http.MethodPost, path, params, headers) + if err != nil { + return nil, nil, err + } + + if response.StatusCode != http.StatusSwitchingProtocols { + defer response.Body.Close() + if err := response.Process(nil); err != nil { + return nil, nil, err + } + return nil, nil, fmt.Errorf("incorrect server response code %d, expected %d", response.StatusCode, http.StatusSwitchingProtocols) + } + rw, ok := response.Body.(io.ReadWriteCloser) + if !ok { + response.Body.Close() + return nil, nil, errors.New("internal error: cannot cast to http response Body to io.ReadWriteCloser") + } + + return &closeWrite{sock: socket}, rw, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/checkpoint.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/checkpoint.go new file mode 100644 index 00000000..f9f78f35 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/checkpoint.go @@ -0,0 +1,106 @@ +package containers + +import ( + "context" + "io" + "net/http" + "os" + + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/types" +) + +// Checkpoint checkpoints the given container (identified by nameOrID). All additional +// options are options and allow for more fine grained control of the checkpoint process. +func Checkpoint(ctx context.Context, nameOrID string, options *CheckpointOptions) (*types.CheckpointReport, error) { + var report types.CheckpointReport + if options == nil { + options = new(CheckpointOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + + // "export" is a bool for the server so override it in the parameters + // if set. + export := false + if options.Export != nil && *options.Export != "" { + export = true + params.Set("export", "true") + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK || !export { + return &report, response.Process(&report) + } + + f, err := os.OpenFile(*options.Export, os.O_RDWR|os.O_CREATE, 0o600) + if err != nil { + return nil, err + } + defer f.Close() + if _, err := io.Copy(f, response.Body); err != nil { + return nil, err + } + + return &types.CheckpointReport{}, nil +} + +// Restore restores a checkpointed container to running. The container is identified by the nameOrID option. All +// additional options are optional and allow finer control of the restore process. +func Restore(ctx context.Context, nameOrID string, options *RestoreOptions) (*types.RestoreReport, error) { + var report types.RestoreReport + if options == nil { + options = new(RestoreOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + + for _, p := range options.PublishPorts { + params.Add("publishPorts", p) + } + + params.Del("ImportArchive") // The import key is a reserved golang term + + // Open the to-be-imported archive if needed. + var r io.Reader + i := options.GetImportArchive() + if i == "" { + // backwards compat, ImportAchive is a typo but we still have to + // support this to avoid breaking users + // TODO: remove ImportAchive with 5.0 + i = options.GetImportAchive() + } + if i != "" { + params.Set("import", "true") + r, err = os.Open(i) + if err != nil { + return nil, err + } + // Hard-code the name since it will be ignored in any case. + nameOrID = "import" + } + + response, err := conn.DoRequest(ctx, r, http.MethodPost, "/containers/%s/restore", params, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &report, response.Process(&report) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/commit.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/commit.go new file mode 100644 index 00000000..a14591ad --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/commit.go @@ -0,0 +1,97 @@ +package containers + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/bindings/images" + "github.com/containers/podman/v5/pkg/domain/entities/types" + "go.podman.io/storage/pkg/regexp" +) + +var iidRegex = regexp.Delayed(`^[0-9a-f]{12}`) + +// Commit creates a container image from a container. The container is defined by nameOrID. Use +// the CommitOptions for finer grain control on characteristics of the resulting image. +func Commit(ctx context.Context, nameOrID string, options *CommitOptions) (types.IDResponse, error) { + if options == nil { + options = new(CommitOptions) + } + id := types.IDResponse{} + conn, err := bindings.GetClient(ctx) + if err != nil { + return id, err + } + params, err := options.ToParams() + if err != nil { + return types.IDResponse{}, err + } + params.Set("container", nameOrID) + var requestBody io.Reader + if options.Config != nil { + requestBody = *options.Config + } + response, err := conn.DoRequest(ctx, requestBody, http.MethodPost, "/commit", params, nil) + if err != nil { + return id, err + } + defer response.Body.Close() + + if !response.IsSuccess() { + return id, response.Process(err) + } + + if !options.GetStream() { + return id, response.Process(&id) + } + stderr := os.Stderr + body := response.Body.(io.Reader) + dec := json.NewDecoder(body) + for { + var s images.BuildResponse + select { + // FIXME(vrothberg): it seems we always hit the EOF case below, + // even when the server quit but it seems desirable to + // distinguish a proper build from a transient EOF. + case <-response.Request.Context().Done(): + return id, nil + default: + // non-blocking select + } + + if err := dec.Decode(&s); err != nil { + if errors.Is(err, io.ErrUnexpectedEOF) { + return id, fmt.Errorf("server probably quit: %w", err) + } + // EOF means the stream is over in which case we need + // to have read the id. + if errors.Is(err, io.EOF) && id.ID != "" { + break + } + return id, fmt.Errorf("decoding stream: %w", err) + } + + switch { + case s.Stream != "": + raw := []byte(s.Stream) + stderr.Write(raw) + if iidRegex.Match(raw) { + id.ID = strings.TrimSuffix(s.Stream, "\n") + return id, nil + } + case s.Error != nil: + // If there's an error, return directly. The stream + // will be closed on return. + return id, errors.New(s.Error.Message) + default: + return id, errors.New("failed to parse build results stream, unexpected input") + } + } + return id, response.Process(&id) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/containers.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/containers.go new file mode 100644 index 00000000..b42ce583 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/containers.go @@ -0,0 +1,467 @@ +package containers + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/api/handlers" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/reports" + "github.com/containers/podman/v5/pkg/domain/entities/types" +) + +var ( + ErrLostSync = errors.New("lost synchronization with multiplexed stream") +) + +// List obtains a list of containers in local storage. All parameters to this method are optional. +// The filters are used to determine which containers are listed. The last parameter indicates to only return +// the most recent number of containers. The pod and size booleans indicate that pod information and rootfs +// size information should also be included. Finally, the sync bool synchronizes the OCI runtime and +// container state. +func List(ctx context.Context, options *ListOptions) ([]types.ListContainer, error) { + if options == nil { + options = new(ListOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + var containers []types.ListContainer + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/json", params, nil) + if err != nil { + return containers, err + } + defer response.Body.Close() + + return containers, response.Process(&containers) +} + +// Prune removes stopped and exited containers from local storage. The optional filters can be +// used for more granular selection of containers. The main error returned indicates if there were runtime +// errors like finding containers. Errors specific to the removal of a container are in the PruneContainerResponse +// structure. +func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, error) { + if options == nil { + options = new(PruneOptions) + } + var reports []*reports.PruneReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/prune", params, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return reports, response.Process(&reports) +} + +// Remove removes a container from local storage. The force bool designates +// that the container should be removed forcibly (example, even it is running). +// The volumes bool dictates that a container's volumes should also be removed. +// The All option indicates that all containers should be removed +// The Ignore option indicates that if a container did not exist, ignore the error +func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) ([]*reports.RmReport, error) { + if options == nil { + options = new(RemoveOptions) + } + var reports []*reports.RmReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return reports, err + } + params, err := options.ToParams() + if err != nil { + return reports, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID) + if err != nil { + return reports, err + } + defer response.Body.Close() + + return reports, response.Process(&reports) +} + +// Inspect returns low level information about a Container. The nameOrID can be a container name +// or a partial/full ID. The size bool determines whether the size of the container's root filesystem +// should be calculated. Calculating the size of a container requires extra work from the filesystem and +// is therefore slower. +func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*define.InspectContainerData, error) { + if options == nil { + options = new(InspectOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/json", params, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + inspect := define.InspectContainerData{} + return &inspect, response.Process(&inspect) +} + +// Kill sends a given signal to a given container. The signal should be the string +// representation of a signal like 'SIGKILL'. The nameOrID can be a container name +// or a partial/full ID +func Kill(ctx context.Context, nameOrID string, options *KillOptions) error { + if options == nil { + options = new(KillOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params, err := options.ToParams() + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/kill", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// Pause pauses a given container. The nameOrID can be a container name +// or a partial/full ID. +func Pause(ctx context.Context, nameOrID string, options *PauseOptions) error { + if options == nil { + options = new(PauseOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/pause", nil, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// Restart restarts a running container. The nameOrID can be a container name +// or a partial/full ID. The optional timeout specifies the number of seconds to wait +// for the running container to stop before killing it. +func Restart(ctx context.Context, nameOrID string, options *RestartOptions) error { + if options == nil { + options = new(RestartOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params, err := options.ToParams() + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/restart", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// Start starts a non-running container.The nameOrID can be a container name +// or a partial/full ID. The optional parameter for detach keys are to override the default +// detach key sequence. +func Start(ctx context.Context, nameOrID string, options *StartOptions) error { + if options == nil { + options = new(StartOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params, err := options.ToParams() + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/start", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +func Stats(ctx context.Context, containers []string, options *StatsOptions) (chan types.ContainerStatsReport, error) { + if options == nil { + options = new(StatsOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + for _, c := range containers { + params.Add("containers", c) + } + + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/stats", params, nil) + if err != nil { + return nil, err + } + if !response.IsSuccess() { + return nil, response.Process(nil) + } + + statsChan := make(chan types.ContainerStatsReport) + + go func() { + defer close(statsChan) + defer response.Body.Close() + + dec := json.NewDecoder(response.Body) + doStream := true + if options.Changed("Stream") { + doStream = options.GetStream() + } + + streamLabel: // label to flatten the scope + select { + case <-response.Request.Context().Done(): + return // lost connection - maybe the server quit + default: + // fall through and do some work + } + + var report types.ContainerStatsReport + if err := dec.Decode(&report); err != nil { + report = types.ContainerStatsReport{Error: err} + } + statsChan <- report + + if report.Error != nil || !doStream { + return + } + goto streamLabel + }() + + return statsChan, nil +} + +// Top gathers statistics about the running processes in a container. The nameOrID can be a container name +// or a partial/full ID. The descriptors allow for specifying which data to collect from the process. +func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, error) { + if options == nil { + options = new(TopOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + if options.Changed("Descriptors") { + psArgs := options.GetDescriptors() + for _, arg := range psArgs { + params.Add("ps_args", arg) + } + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/top", params, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + body := handlers.ContainerTopOKBody{} + if err = response.Process(&body); err != nil { + return nil, err + } + + // handlers.ContainerTopOKBody{} returns a slice of slices where each cell in the top table is an item. + // In libpod land, we're just using a slice with cells being split by tabs, which allows for an idiomatic + // usage of the tabwriter. + topOutput := []string{strings.Join(body.Titles, "\t")} + for _, out := range body.Processes { + topOutput = append(topOutput, strings.Join(out, "\t")) + } + + return topOutput, err +} + +// Unpause resumes the given paused container. The nameOrID can be a container name +// or a partial/full ID. +func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) error { + if options == nil { + options = new(UnpauseOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/unpause", nil, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// Wait blocks until the given container reaches a condition. If not provided, the condition will +// default to stopped. If the condition is stopped, an exit code for the container will be provided. The +// nameOrID can be a container name or a partial/full ID. +func Wait(ctx context.Context, nameOrID string, options *WaitOptions) (int32, error) { + if options == nil { + options = new(WaitOptions) + } else if len(options.Condition) > 0 && len(options.Conditions) > 0 { + return -1, fmt.Errorf("%q field cannot be used with deprecated %q field", "Conditions", "Condition") + } + var exitCode int32 + conn, err := bindings.GetClient(ctx) + if err != nil { + return exitCode, err + } + params, err := options.ToParams() + if err != nil { + return exitCode, err + } + delete(params, "conditions") // They're called "condition" + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID) + if err != nil { + return exitCode, err + } + defer response.Body.Close() + + return exitCode, response.Process(&exitCode) +} + +// Exists is a quick, light-weight way to determine if a given container +// exists in local storage. The nameOrID can be a container name +// or a partial/full ID. +func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return false, err + } + params, err := options.ToParams() + if err != nil { + return false, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/exists", params, nil, nameOrID) + if err != nil { + return false, err + } + defer response.Body.Close() + + return response.IsSuccess(), nil +} + +// Stop stops a running container. The timeout is optional. The nameOrID can be a container name +// or a partial/full ID +func Stop(ctx context.Context, nameOrID string, options *StopOptions) error { + if options == nil { + options = new(StopOptions) + } + params, err := options.ToParams() + if err != nil { + return err + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/stop", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + if options.GetIgnore() && response.StatusCode == http.StatusNotFound { + return nil + } + + return response.Process(nil) +} + +// Export creates a tarball of the given name or ID of a container. It +// requires an io.Writer be provided to write the tarball. +func Export(ctx context.Context, nameOrID string, w io.Writer, options *ExportOptions) error { + if options == nil { + options = new(ExportOptions) + } + _ = options + params := url.Values{} + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/export", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + if response.IsSuccess() { + _, err = io.Copy(w, response.Body) + return err + } + return response.Process(nil) +} + +// ContainerInit takes a created container and executes all of the +// preparations to run the container except it will not start +// or attach to the container +func ContainerInit(ctx context.Context, nameOrID string, options *InitOptions) error { + if options == nil { + options = new(InitOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/init", nil, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode == http.StatusNotModified { + return fmt.Errorf("container %s has already been created in runtime: %w", nameOrID, define.ErrCtrStateInvalid) + } + return response.Process(nil) +} + +// Deprecated: This function always returns false, the server API endpoint never existed. +func ShouldRestart(_ context.Context, _ string, _ *ShouldRestartOptions) (bool, error) { + return false, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/create.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/create.go new file mode 100644 index 00000000..7b39e848 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/create.go @@ -0,0 +1,36 @@ +package containers + +import ( + "context" + "net/http" + "strings" + + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/containers/podman/v5/pkg/specgen" + jsoniter "github.com/json-iterator/go" +) + +func CreateWithSpec(ctx context.Context, s *specgen.SpecGenerator, options *CreateOptions) (types.ContainerCreateResponse, error) { + var ccr types.ContainerCreateResponse + if options == nil { + options = new(CreateOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return ccr, err + } + specgenString, err := jsoniter.MarshalToString(s) + if err != nil { + return ccr, err + } + stringReader := strings.NewReader(specgenString) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/create", nil, nil) + if err != nil { + return ccr, err + } + defer response.Body.Close() + + return ccr, response.Process(&ccr) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/diff.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/diff.go new file mode 100644 index 00000000..c196ce06 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/diff.go @@ -0,0 +1,33 @@ +package containers + +import ( + "context" + "net/http" + + "github.com/containers/podman/v5/pkg/bindings" + "go.podman.io/storage/pkg/archive" +) + +// Diff provides the changes between two container layers +func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive.Change, error) { + if options == nil { + options = new(DiffOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/changes", params, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + var changes []archive.Change + return changes, response.Process(&changes) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/exec.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/exec.go new file mode 100644 index 00000000..a8419e3e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/exec.go @@ -0,0 +1,156 @@ +package containers + +import ( + "bytes" + "context" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/api/handlers" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/types" + jsoniter "github.com/json-iterator/go" + "github.com/sirupsen/logrus" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// ExecCreate creates a new exec session in an existing container. +// The exec session will not be started; that is done with ExecStart. +// Returns ID of new exec session, or an error if one occurred. +func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreateConfig) (string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + + if config == nil { + return "", errors.New("must provide a configuration for exec session") + } + + requestJSON, err := json.Marshal(config) + if err != nil { + return "", fmt.Errorf("marshalling exec config to JSON: %w", err) + } + jsonReader := strings.NewReader(string(requestJSON)) + + resp, err := conn.DoRequest(ctx, jsonReader, http.MethodPost, "/containers/%s/exec", nil, nil, nameOrID) + if err != nil { + return "", err + } + defer resp.Body.Close() + + respStruct := new(types.IDResponse) + if err := resp.Process(respStruct); err != nil { + return "", err + } + + return respStruct.ID, nil +} + +// ExecInspect inspects an existing exec session, returning detailed information +// about it. +func ExecInspect(ctx context.Context, sessionID string, options *ExecInspectOptions) (*define.InspectExecSession, error) { + if options == nil { + options = new(ExecInspectOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + logrus.Debugf("Inspecting session ID %q", sessionID) + + resp, err := conn.DoRequest(ctx, nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + respStruct := new(define.InspectExecSession) + if err := resp.Process(respStruct); err != nil { + return nil, err + } + + return respStruct, nil +} + +// ExecStart starts (but does not attach to) a given exec session. +func ExecStart(ctx context.Context, sessionID string, options *ExecStartOptions) error { + if options == nil { + options = new(ExecStartOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + + logrus.Debugf("Starting exec session ID %q", sessionID) + + // We force Detach to true + body := struct { + Detach bool `json:"Detach"` + }{ + Detach: true, + } + bodyJSON, err := json.Marshal(body) + if err != nil { + return err + } + + resp, err := conn.DoRequest(ctx, bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID) + if err != nil { + return err + } + defer resp.Body.Close() + + return resp.Process(nil) +} + +// ExecRemove removes a given exec session. +func ExecRemove(ctx context.Context, sessionID string, options *ExecRemoveOptions) error { + v := bindings.ServiceVersion(ctx) + // The exec remove endpoint was added in 4.8. + if v.Major < 4 || (v.Major == 4 && v.Minor < 8) { + // Do no call this endpoint as it will not be supported on the server and throw an "NOT FOUND" error. + return bindings.NewAPIVersionError("/exec/{id}/remove", v, "4.8.0") + } + if options == nil { + options = new(ExecRemoveOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + + logrus.Debugf("Removing exec session ID %q", sessionID) + + // We force Detach to true + body := struct { + Force bool `json:"Force"` + }{ + Force: false, + } + + if options.Force != nil { + body.Force = *options.Force + } + + bodyJSON, err := json.Marshal(body) + if err != nil { + return err + } + + resp, err := conn.DoRequest(ctx, bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/remove", nil, nil, sessionID) + if err != nil { + return err + } + defer resp.Body.Close() + + return resp.Process(nil) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/healthcheck.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/healthcheck.go new file mode 100644 index 00000000..7f396fcc --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/healthcheck.go @@ -0,0 +1,32 @@ +package containers + +import ( + "context" + "net/http" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/bindings" +) + +// RunHealthCheck executes the container's healthcheck and returns the health status of the +// container. +func RunHealthCheck(ctx context.Context, nameOrID string, options *HealthCheckOptions) (*define.HealthCheckResults, error) { + if options == nil { + options = new(HealthCheckOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + var ( + status define.HealthCheckResults + ) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/healthcheck", nil, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &status, response.Process(&status) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/logs.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/logs.go new file mode 100644 index 00000000..8796c3a7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/logs.go @@ -0,0 +1,70 @@ +package containers + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "strconv" + + "github.com/containers/podman/v5/pkg/bindings" +) + +// Logs obtains a container's logs given the options provided. The logs are then sent to the +// stdout|stderr channels as strings. +func Logs(ctx context.Context, nameOrID string, options *LogOptions, stdoutChan, stderrChan chan string) error { + if options == nil { + options = new(LogOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params, err := options.ToParams() + if err != nil { + return err + } + // The API requires either stdout|stderr be used. If neither are specified, we specify stdout + if options.Stdout == nil && options.Stderr == nil { + params.Set("stdout", strconv.FormatBool(true)) + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/logs", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + // if not success handle and return possible error message + if !response.IsSuccess() && !response.IsInformational() { + return response.Process(nil) + } + + buffer := make([]byte, 1024) + for { + fd, l, err := DemuxHeader(response.Body, buffer) + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return err + } + frame, err := DemuxFrame(response.Body, buffer, l) + if err != nil { + return err + } + + switch fd { + case 0: + stdoutChan <- string(frame) + case 1: + stdoutChan <- string(frame) + case 2: + stderrChan <- string(frame) + case 3: + return errors.New("from service in stream: " + string(frame)) + default: + return fmt.Errorf("unrecognized input header: %d", fd) + } + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/mount.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/mount.go new file mode 100644 index 00000000..63aaf5c1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/mount.go @@ -0,0 +1,71 @@ +package containers + +import ( + "context" + "net/http" + + "github.com/containers/podman/v5/pkg/bindings" +) + +// Mount mounts an existing container to the filesystem. It returns the path +// of the mounted container in string format. +func Mount(ctx context.Context, nameOrID string, options *MountOptions) (string, error) { + if options == nil { + options = new(MountOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + var ( + path string + ) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/mount", nil, nil, nameOrID) + if err != nil { + return path, err + } + defer response.Body.Close() + + return path, response.Process(&path) +} + +// Unmount unmounts a container from the filesystem. The container must not be running +// or the unmount will fail. +func Unmount(ctx context.Context, nameOrID string, options *UnmountOptions) error { + if options == nil { + options = new(UnmountOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/unmount", nil, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// GetMountedContainerPaths returns a map of mounted containers and their mount locations. +func GetMountedContainerPaths(ctx context.Context, options *MountedContainerPathsOptions) (map[string]string, error) { + if options == nil { + options = new(MountedContainerPathsOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + mounts := make(map[string]string) + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/showmounted", nil, nil) + if err != nil { + return mounts, err + } + defer response.Body.Close() + + return mounts, response.Process(&mounts) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/rename.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/rename.go new file mode 100644 index 00000000..4642547b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/rename.go @@ -0,0 +1,30 @@ +package containers + +import ( + "context" + "net/http" + + "github.com/containers/podman/v5/pkg/bindings" +) + +// Rename an existing container. +func Rename(ctx context.Context, nameOrID string, options *RenameOptions) error { + if options == nil { + options = new(RenameOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params, err := options.ToParams() + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/rename", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/term_unix.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/term_unix.go new file mode 100644 index 00000000..900f6e23 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/term_unix.go @@ -0,0 +1,24 @@ +//go:build !windows + +package containers + +import ( + "context" + "os" + "os/signal" + + sig "github.com/containers/podman/v5/pkg/signal" + "golang.org/x/term" +) + +func makeRawTerm(stdin *os.File) (*term.State, error) { + return term.MakeRaw(int(stdin.Fd())) +} + +func notifyWinChange(_ context.Context, winChange chan os.Signal, _ *os.File, _ *os.File) { + signal.Notify(winChange, sig.SIGWINCH) +} + +func getTermSize(stdin *os.File, _ *os.File) (width, height int, err error) { + return term.GetSize(int(stdin.Fd())) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/term_windows.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/term_windows.go new file mode 100644 index 00000000..ffda48b4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/term_windows.go @@ -0,0 +1,67 @@ +package containers + +import ( + "context" + "os" + "time" + + sig "github.com/containers/podman/v5/pkg/signal" + "golang.org/x/sys/windows" + "golang.org/x/term" +) + +func makeRawTerm(stdin *os.File) (*term.State, error) { + state, err := term.MakeRaw(int(stdin.Fd())) + if err != nil { + return nil, err + } + + // Attempt VT if supported (recent versions of Windows 10+) + var raw uint32 + handle := windows.Handle(stdin.Fd()) + if err := windows.GetConsoleMode(handle, &raw); err != nil { + return nil, err + } + + tryVT := raw | windows.ENABLE_VIRTUAL_TERMINAL_INPUT + + if err := windows.SetConsoleMode(handle, tryVT); err != nil { + if err := windows.SetConsoleMode(handle, raw); err != nil { + return nil, err + } + } + + return state, nil +} + +func notifyWinChange(ctx context.Context, winChange chan os.Signal, _ *os.File, stdout *os.File) { + // Simulate WINCH with polling + go func() { + var lastW int + var lastH int + + d := time.Millisecond * 250 + timer := time.NewTimer(d) + defer timer.Stop() + for ; ; timer.Reset(d) { + select { + case <-ctx.Done(): + return + case <-timer.C: + } + + w, h, err := term.GetSize(int(stdout.Fd())) + if err != nil { + continue + } + if w != lastW || h != lastH { + winChange <- sig.SIGWINCH + lastW, lastH = w, h + } + } + }() +} + +func getTermSize(_ *os.File, stdout *os.File) (width, height int, err error) { + return term.GetSize(int(stdout.Fd())) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types.go new file mode 100644 index 00000000..85a82484 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types.go @@ -0,0 +1,345 @@ +package containers + +import ( + "bufio" + "io" + + "github.com/containers/podman/v5/libpod/define" +) + +// LogOptions describe finer control of log content or +// how the content is formatted. +// +//go:generate go run ../generator/generator.go LogOptions +type LogOptions struct { + Follow *bool + Since *string + Stderr *bool + Stdout *bool + Tail *string + Timestamps *bool + Until *string +} + +// CommitOptions describe details about the resulting committed +// image as defined by repo and tag. None of these options +// are required. +// +//go:generate go run ../generator/generator.go CommitOptions +type CommitOptions struct { + Author *string + Changes []string + Config *io.Reader `schema:"-"` + Comment *string + Format *string + Pause *bool + Stream *bool + Squash *bool + Repo *string + Tag *string +} + +// AttachOptions are optional options for attaching to containers +// +//go:generate go run ../generator/generator.go AttachOptions +type AttachOptions struct { + DetachKeys *string // Keys to detach from running container + Logs *bool // Flag to return all logs from container when true + Stream *bool // Flag only return container logs when false and Logs is true +} + +// CheckpointOptions are optional options for checkpointing containers +// +//go:generate go run ../generator/generator.go CheckpointOptions +type CheckpointOptions struct { + Export *string + CreateImage *string + IgnoreRootfs *bool + Keep *bool + LeaveRunning *bool + TCPEstablished *bool + PrintStats *bool + PreCheckpoint *bool + WithPrevious *bool + FileLocks *bool +} + +// RestoreOptions are optional options for restoring containers +// +//go:generate go run ../generator/generator.go RestoreOptions +type RestoreOptions struct { + IgnoreRootfs *bool + IgnoreVolumes *bool + IgnoreStaticIP *bool + IgnoreStaticMAC *bool + // ImportAchive is the path to an archive which contains the checkpoint data. + // + // Deprecated: Use ImportArchive instead. This field name is a typo and + // will be removed in a future major release. + ImportAchive *string + // ImportArchive is the path to an archive which contains the checkpoint data. + // ImportArchive is preferred over ImportAchive when both are set. + ImportArchive *string + Keep *bool + Name *string + TCPEstablished *bool + TCPClose *bool + Pod *string + PrintStats *bool + PublishPorts []string + FileLocks *bool +} + +// CreateOptions are optional options for creating containers +// +//go:generate go run ../generator/generator.go CreateOptions +type CreateOptions struct{} + +// DiffOptions are optional options for creating containers +// +//go:generate go run ../generator/generator.go DiffOptions +type DiffOptions struct { + // By the default diff will compare against the parent layer. Change the Parent if you want to compare against something else. + Parent *string + // Change the type the backend should match. This can be set to "all", "container" or "image". + DiffType *string +} + +// ExecInspectOptions are optional options for inspecting +// exec sessions +// +//go:generate go run ../generator/generator.go ExecInspectOptions +type ExecInspectOptions struct{} + +// ExecStartOptions are optional options for starting +// exec sessions +// +//go:generate go run ../generator/generator.go ExecStartOptions +type ExecStartOptions struct { +} + +// HealthCheckOptions are optional options for checking +// the health of a container +// +//go:generate go run ../generator/generator.go HealthCheckOptions +type HealthCheckOptions struct{} + +// MountOptions are optional options for mounting +// containers +// +//go:generate go run ../generator/generator.go MountOptions +type MountOptions struct{} + +// UnmountOptions are optional options for unmounting +// containers +// +//go:generate go run ../generator/generator.go UnmountOptions +type UnmountOptions struct{} + +// MountedContainerPathsOptions are optional options for getting +// container mount paths +// +//go:generate go run ../generator/generator.go MountedContainerPathsOptions +type MountedContainerPathsOptions struct{} + +// ListOptions are optional options for listing containers +// +//go:generate go run ../generator/generator.go ListOptions +type ListOptions struct { + All *bool + External *bool + Filters map[string][]string + Last *int + Namespace *bool + Size *bool + Sync *bool +} + +// PruneOptions are optional options for pruning containers +// +//go:generate go run ../generator/generator.go PruneOptions +type PruneOptions struct { + Filters map[string][]string +} + +// RemoveOptions are optional options for removing containers +// +//go:generate go run ../generator/generator.go RemoveOptions +type RemoveOptions struct { + Depend *bool + Ignore *bool + Force *bool + Volumes *bool + Timeout *uint +} + +// InspectOptions are optional options for inspecting containers +// +//go:generate go run ../generator/generator.go InspectOptions +type InspectOptions struct { + Size *bool +} + +// KillOptions are optional options for killing containers +// +//go:generate go run ../generator/generator.go KillOptions +type KillOptions struct { + Signal *string +} + +// PauseOptions are optional options for pausing containers +// +//go:generate go run ../generator/generator.go PauseOptions +type PauseOptions struct{} + +// RestartOptions are optional options for restarting containers +// +//go:generate go run ../generator/generator.go RestartOptions +type RestartOptions struct { + Timeout *int +} + +// StartOptions are optional options for starting containers +// +//go:generate go run ../generator/generator.go StartOptions +type StartOptions struct { + DetachKeys *string + Recursive *bool +} + +// StatsOptions are optional options for getting stats on containers +// +//go:generate go run ../generator/generator.go StatsOptions +type StatsOptions struct { + All *bool + Stream *bool + Interval *int +} + +// TopOptions are optional options for getting running +// processes in containers +// +//go:generate go run ../generator/generator.go TopOptions +type TopOptions struct { + Descriptors *[]string +} + +// UnpauseOptions are optional options for unpausing containers +// +//go:generate go run ../generator/generator.go UnpauseOptions +type UnpauseOptions struct{} + +// WaitOptions are optional options for waiting on containers +// +//go:generate go run ../generator/generator.go WaitOptions +type WaitOptions struct { + // Conditions to wait on. Includes container statuses such as + // "running" or "stopped" and health-related values such "healthy". + Conditions []string `schema:"condition"` + // Time interval to wait before polling for completion. + Interval *string + // Container status to wait on. + // Deprecated: use Conditions instead. + Condition []define.ContainerStatus +} + +// StopOptions are optional options for stopping containers +// +//go:generate go run ../generator/generator.go StopOptions +type StopOptions struct { + Ignore *bool + Timeout *uint +} + +// ExportOptions are optional options for exporting containers +// +//go:generate go run ../generator/generator.go ExportOptions +type ExportOptions struct{} + +// InitOptions are optional options for initing containers +// +//go:generate go run ../generator/generator.go InitOptions +type InitOptions struct{} + +// ShouldRestartOptions +// +//go:generate go run ../generator/generator.go ShouldRestartOptions +type ShouldRestartOptions struct{} + +// RenameOptions are options for renaming containers. +// The Name field is required. +// +//go:generate go run ../generator/generator.go RenameOptions +type RenameOptions struct { + Name *string +} + +// ResizeTTYOptions are optional options for resizing +// container TTYs +// +//go:generate go run ../generator/generator.go ResizeTTYOptions +type ResizeTTYOptions struct { + Height *int + Width *int + Running *bool +} + +// ResizeExecTTYOptions are optional options for resizing +// container ExecTTYs +// +//go:generate go run ../generator/generator.go ResizeExecTTYOptions +type ResizeExecTTYOptions struct { + Height *int + Width *int +} + +// ExecStartAndAttachOptions are optional options for resizing +// container ExecTTYs +// +//go:generate go run ../generator/generator.go ExecStartAndAttachOptions +type ExecStartAndAttachOptions struct { + // OutputStream will be attached to container's STDOUT + OutputStream *io.Writer + // ErrorStream will be attached to container's STDERR + ErrorStream *io.Writer + // InputStream will be attached to container's STDIN + InputStream *bufio.Reader + // AttachOutput is whether to attach to STDOUT + // If false, stdout will not be attached + AttachOutput *bool + // AttachError is whether to attach to STDERR + // If false, stdout will not be attached + AttachError *bool + // AttachInput is whether to attach to STDIN + // If false, stdout will not be attached + AttachInput *bool +} + +// ExistsOptions are optional options for checking if a container exists +// +//go:generate go run ../generator/generator.go ExistsOptions +type ExistsOptions struct { + // External checks for containers created outside of Podman + External *bool +} + +// CopyOptions are options for copying to containers. +// +//go:generate go run ../generator/generator.go CopyOptions +type CopyOptions struct { + // If used with CopyFromArchive and set to true it will change ownership of files from the source tar archive + // to the primary uid/gid of the target container. + Chown *bool `schema:"copyUIDGID"` + // Map to translate path names. + Rename map[string]string + // NoOverwriteDirNonDir when true prevents an existing directory or file from being overwritten + // by the other type. + NoOverwriteDirNonDir *bool +} + +// ExecRemoveOptions are optional options for removing an exec session +// +//go:generate go run ../generator/generator.go ExecRemoveOptions +type ExecRemoveOptions struct { + Force *bool +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_attach_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_attach_options.go new file mode 100644 index 00000000..6c272f33 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_attach_options.go @@ -0,0 +1,63 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *AttachOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *AttachOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithDetachKeys set keys to detach from running container +func (o *AttachOptions) WithDetachKeys(value string) *AttachOptions { + o.DetachKeys = &value + return o +} + +// GetDetachKeys returns value of keys to detach from running container +func (o *AttachOptions) GetDetachKeys() string { + if o.DetachKeys == nil { + var z string + return z + } + return *o.DetachKeys +} + +// WithLogs set flag to return all logs from container when true +func (o *AttachOptions) WithLogs(value bool) *AttachOptions { + o.Logs = &value + return o +} + +// GetLogs returns value of flag to return all logs from container when true +func (o *AttachOptions) GetLogs() bool { + if o.Logs == nil { + var z bool + return z + } + return *o.Logs +} + +// WithStream set flag only return container logs when false and Logs is true +func (o *AttachOptions) WithStream(value bool) *AttachOptions { + o.Stream = &value + return o +} + +// GetStream returns value of flag only return container logs when false and Logs is true +func (o *AttachOptions) GetStream() bool { + if o.Stream == nil { + var z bool + return z + } + return *o.Stream +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_checkpoint_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_checkpoint_options.go new file mode 100644 index 00000000..16bd91a7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_checkpoint_options.go @@ -0,0 +1,168 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *CheckpointOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *CheckpointOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithExport set field Export to given value +func (o *CheckpointOptions) WithExport(value string) *CheckpointOptions { + o.Export = &value + return o +} + +// GetExport returns value of field Export +func (o *CheckpointOptions) GetExport() string { + if o.Export == nil { + var z string + return z + } + return *o.Export +} + +// WithCreateImage set field CreateImage to given value +func (o *CheckpointOptions) WithCreateImage(value string) *CheckpointOptions { + o.CreateImage = &value + return o +} + +// GetCreateImage returns value of field CreateImage +func (o *CheckpointOptions) GetCreateImage() string { + if o.CreateImage == nil { + var z string + return z + } + return *o.CreateImage +} + +// WithIgnoreRootfs set field IgnoreRootfs to given value +func (o *CheckpointOptions) WithIgnoreRootfs(value bool) *CheckpointOptions { + o.IgnoreRootfs = &value + return o +} + +// GetIgnoreRootfs returns value of field IgnoreRootfs +func (o *CheckpointOptions) GetIgnoreRootfs() bool { + if o.IgnoreRootfs == nil { + var z bool + return z + } + return *o.IgnoreRootfs +} + +// WithKeep set field Keep to given value +func (o *CheckpointOptions) WithKeep(value bool) *CheckpointOptions { + o.Keep = &value + return o +} + +// GetKeep returns value of field Keep +func (o *CheckpointOptions) GetKeep() bool { + if o.Keep == nil { + var z bool + return z + } + return *o.Keep +} + +// WithLeaveRunning set field LeaveRunning to given value +func (o *CheckpointOptions) WithLeaveRunning(value bool) *CheckpointOptions { + o.LeaveRunning = &value + return o +} + +// GetLeaveRunning returns value of field LeaveRunning +func (o *CheckpointOptions) GetLeaveRunning() bool { + if o.LeaveRunning == nil { + var z bool + return z + } + return *o.LeaveRunning +} + +// WithTCPEstablished set field TCPEstablished to given value +func (o *CheckpointOptions) WithTCPEstablished(value bool) *CheckpointOptions { + o.TCPEstablished = &value + return o +} + +// GetTCPEstablished returns value of field TCPEstablished +func (o *CheckpointOptions) GetTCPEstablished() bool { + if o.TCPEstablished == nil { + var z bool + return z + } + return *o.TCPEstablished +} + +// WithPrintStats set field PrintStats to given value +func (o *CheckpointOptions) WithPrintStats(value bool) *CheckpointOptions { + o.PrintStats = &value + return o +} + +// GetPrintStats returns value of field PrintStats +func (o *CheckpointOptions) GetPrintStats() bool { + if o.PrintStats == nil { + var z bool + return z + } + return *o.PrintStats +} + +// WithPreCheckpoint set field PreCheckpoint to given value +func (o *CheckpointOptions) WithPreCheckpoint(value bool) *CheckpointOptions { + o.PreCheckpoint = &value + return o +} + +// GetPreCheckpoint returns value of field PreCheckpoint +func (o *CheckpointOptions) GetPreCheckpoint() bool { + if o.PreCheckpoint == nil { + var z bool + return z + } + return *o.PreCheckpoint +} + +// WithWithPrevious set field WithPrevious to given value +func (o *CheckpointOptions) WithWithPrevious(value bool) *CheckpointOptions { + o.WithPrevious = &value + return o +} + +// GetWithPrevious returns value of field WithPrevious +func (o *CheckpointOptions) GetWithPrevious() bool { + if o.WithPrevious == nil { + var z bool + return z + } + return *o.WithPrevious +} + +// WithFileLocks set field FileLocks to given value +func (o *CheckpointOptions) WithFileLocks(value bool) *CheckpointOptions { + o.FileLocks = &value + return o +} + +// GetFileLocks returns value of field FileLocks +func (o *CheckpointOptions) GetFileLocks() bool { + if o.FileLocks == nil { + var z bool + return z + } + return *o.FileLocks +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_commit_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_commit_options.go new file mode 100644 index 00000000..c74e2bab --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_commit_options.go @@ -0,0 +1,169 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "io" + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *CommitOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *CommitOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAuthor set field Author to given value +func (o *CommitOptions) WithAuthor(value string) *CommitOptions { + o.Author = &value + return o +} + +// GetAuthor returns value of field Author +func (o *CommitOptions) GetAuthor() string { + if o.Author == nil { + var z string + return z + } + return *o.Author +} + +// WithChanges set field Changes to given value +func (o *CommitOptions) WithChanges(value []string) *CommitOptions { + o.Changes = value + return o +} + +// GetChanges returns value of field Changes +func (o *CommitOptions) GetChanges() []string { + if o.Changes == nil { + var z []string + return z + } + return o.Changes +} + +// WithConfig set field Config to given value +func (o *CommitOptions) WithConfig(value io.Reader) *CommitOptions { + o.Config = &value + return o +} + +// GetConfig returns value of field Config +func (o *CommitOptions) GetConfig() io.Reader { + if o.Config == nil { + var z io.Reader + return z + } + return *o.Config +} + +// WithComment set field Comment to given value +func (o *CommitOptions) WithComment(value string) *CommitOptions { + o.Comment = &value + return o +} + +// GetComment returns value of field Comment +func (o *CommitOptions) GetComment() string { + if o.Comment == nil { + var z string + return z + } + return *o.Comment +} + +// WithFormat set field Format to given value +func (o *CommitOptions) WithFormat(value string) *CommitOptions { + o.Format = &value + return o +} + +// GetFormat returns value of field Format +func (o *CommitOptions) GetFormat() string { + if o.Format == nil { + var z string + return z + } + return *o.Format +} + +// WithPause set field Pause to given value +func (o *CommitOptions) WithPause(value bool) *CommitOptions { + o.Pause = &value + return o +} + +// GetPause returns value of field Pause +func (o *CommitOptions) GetPause() bool { + if o.Pause == nil { + var z bool + return z + } + return *o.Pause +} + +// WithStream set field Stream to given value +func (o *CommitOptions) WithStream(value bool) *CommitOptions { + o.Stream = &value + return o +} + +// GetStream returns value of field Stream +func (o *CommitOptions) GetStream() bool { + if o.Stream == nil { + var z bool + return z + } + return *o.Stream +} + +// WithSquash set field Squash to given value +func (o *CommitOptions) WithSquash(value bool) *CommitOptions { + o.Squash = &value + return o +} + +// GetSquash returns value of field Squash +func (o *CommitOptions) GetSquash() bool { + if o.Squash == nil { + var z bool + return z + } + return *o.Squash +} + +// WithRepo set field Repo to given value +func (o *CommitOptions) WithRepo(value string) *CommitOptions { + o.Repo = &value + return o +} + +// GetRepo returns value of field Repo +func (o *CommitOptions) GetRepo() string { + if o.Repo == nil { + var z string + return z + } + return *o.Repo +} + +// WithTag set field Tag to given value +func (o *CommitOptions) WithTag(value string) *CommitOptions { + o.Tag = &value + return o +} + +// GetTag returns value of field Tag +func (o *CommitOptions) GetTag() string { + if o.Tag == nil { + var z string + return z + } + return *o.Tag +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_copy_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_copy_options.go new file mode 100644 index 00000000..b6cf40d7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_copy_options.go @@ -0,0 +1,63 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *CopyOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *CopyOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithChown set field Chown to given value +func (o *CopyOptions) WithChown(value bool) *CopyOptions { + o.Chown = &value + return o +} + +// GetChown returns value of field Chown +func (o *CopyOptions) GetChown() bool { + if o.Chown == nil { + var z bool + return z + } + return *o.Chown +} + +// WithRename set field Rename to given value +func (o *CopyOptions) WithRename(value map[string]string) *CopyOptions { + o.Rename = value + return o +} + +// GetRename returns value of field Rename +func (o *CopyOptions) GetRename() map[string]string { + if o.Rename == nil { + var z map[string]string + return z + } + return o.Rename +} + +// WithNoOverwriteDirNonDir set field NoOverwriteDirNonDir to given value +func (o *CopyOptions) WithNoOverwriteDirNonDir(value bool) *CopyOptions { + o.NoOverwriteDirNonDir = &value + return o +} + +// GetNoOverwriteDirNonDir returns value of field NoOverwriteDirNonDir +func (o *CopyOptions) GetNoOverwriteDirNonDir() bool { + if o.NoOverwriteDirNonDir == nil { + var z bool + return z + } + return *o.NoOverwriteDirNonDir +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_create_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_create_options.go new file mode 100644 index 00000000..099685ec --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_create_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *CreateOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *CreateOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_diff_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_diff_options.go new file mode 100644 index 00000000..75f7907d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_diff_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *DiffOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *DiffOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithParent set field Parent to given value +func (o *DiffOptions) WithParent(value string) *DiffOptions { + o.Parent = &value + return o +} + +// GetParent returns value of field Parent +func (o *DiffOptions) GetParent() string { + if o.Parent == nil { + var z string + return z + } + return *o.Parent +} + +// WithDiffType set field DiffType to given value +func (o *DiffOptions) WithDiffType(value string) *DiffOptions { + o.DiffType = &value + return o +} + +// GetDiffType returns value of field DiffType +func (o *DiffOptions) GetDiffType() string { + if o.DiffType == nil { + var z string + return z + } + return *o.DiffType +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execinspect_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execinspect_options.go new file mode 100644 index 00000000..e9631183 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execinspect_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExecInspectOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExecInspectOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execremove_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execremove_options.go new file mode 100644 index 00000000..cfc6d8a4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execremove_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExecRemoveOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExecRemoveOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithForce set field Force to given value +func (o *ExecRemoveOptions) WithForce(value bool) *ExecRemoveOptions { + o.Force = &value + return o +} + +// GetForce returns value of field Force +func (o *ExecRemoveOptions) GetForce() bool { + if o.Force == nil { + var z bool + return z + } + return *o.Force +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execstart_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execstart_options.go new file mode 100644 index 00000000..bbfb4a30 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execstart_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExecStartOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExecStartOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execstartandattach_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execstartandattach_options.go new file mode 100644 index 00000000..1b8e4872 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_execstartandattach_options.go @@ -0,0 +1,110 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "bufio" + "io" + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExecStartAndAttachOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExecStartAndAttachOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithOutputStream set field OutputStream to given value +func (o *ExecStartAndAttachOptions) WithOutputStream(value io.Writer) *ExecStartAndAttachOptions { + o.OutputStream = &value + return o +} + +// GetOutputStream returns value of field OutputStream +func (o *ExecStartAndAttachOptions) GetOutputStream() io.Writer { + if o.OutputStream == nil { + var z io.Writer + return z + } + return *o.OutputStream +} + +// WithErrorStream set field ErrorStream to given value +func (o *ExecStartAndAttachOptions) WithErrorStream(value io.Writer) *ExecStartAndAttachOptions { + o.ErrorStream = &value + return o +} + +// GetErrorStream returns value of field ErrorStream +func (o *ExecStartAndAttachOptions) GetErrorStream() io.Writer { + if o.ErrorStream == nil { + var z io.Writer + return z + } + return *o.ErrorStream +} + +// WithInputStream set field InputStream to given value +func (o *ExecStartAndAttachOptions) WithInputStream(value bufio.Reader) *ExecStartAndAttachOptions { + o.InputStream = &value + return o +} + +// GetInputStream returns value of field InputStream +func (o *ExecStartAndAttachOptions) GetInputStream() bufio.Reader { + if o.InputStream == nil { + var z bufio.Reader + return z + } + return *o.InputStream +} + +// WithAttachOutput set field AttachOutput to given value +func (o *ExecStartAndAttachOptions) WithAttachOutput(value bool) *ExecStartAndAttachOptions { + o.AttachOutput = &value + return o +} + +// GetAttachOutput returns value of field AttachOutput +func (o *ExecStartAndAttachOptions) GetAttachOutput() bool { + if o.AttachOutput == nil { + var z bool + return z + } + return *o.AttachOutput +} + +// WithAttachError set field AttachError to given value +func (o *ExecStartAndAttachOptions) WithAttachError(value bool) *ExecStartAndAttachOptions { + o.AttachError = &value + return o +} + +// GetAttachError returns value of field AttachError +func (o *ExecStartAndAttachOptions) GetAttachError() bool { + if o.AttachError == nil { + var z bool + return z + } + return *o.AttachError +} + +// WithAttachInput set field AttachInput to given value +func (o *ExecStartAndAttachOptions) WithAttachInput(value bool) *ExecStartAndAttachOptions { + o.AttachInput = &value + return o +} + +// GetAttachInput returns value of field AttachInput +func (o *ExecStartAndAttachOptions) GetAttachInput() bool { + if o.AttachInput == nil { + var z bool + return z + } + return *o.AttachInput +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_exists_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_exists_options.go new file mode 100644 index 00000000..91a7ceec --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_exists_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExistsOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExistsOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithExternal set field External to given value +func (o *ExistsOptions) WithExternal(value bool) *ExistsOptions { + o.External = &value + return o +} + +// GetExternal returns value of field External +func (o *ExistsOptions) GetExternal() bool { + if o.External == nil { + var z bool + return z + } + return *o.External +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_export_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_export_options.go new file mode 100644 index 00000000..81c94f2b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_export_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExportOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExportOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_healthcheck_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_healthcheck_options.go new file mode 100644 index 00000000..e346d482 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_healthcheck_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *HealthCheckOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *HealthCheckOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_init_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_init_options.go new file mode 100644 index 00000000..2f06ae25 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_init_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *InitOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *InitOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_inspect_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_inspect_options.go new file mode 100644 index 00000000..29fa6fed --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_inspect_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *InspectOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *InspectOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithSize set field Size to given value +func (o *InspectOptions) WithSize(value bool) *InspectOptions { + o.Size = &value + return o +} + +// GetSize returns value of field Size +func (o *InspectOptions) GetSize() bool { + if o.Size == nil { + var z bool + return z + } + return *o.Size +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_kill_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_kill_options.go new file mode 100644 index 00000000..8cb98552 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_kill_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *KillOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *KillOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithSignal set field Signal to given value +func (o *KillOptions) WithSignal(value string) *KillOptions { + o.Signal = &value + return o +} + +// GetSignal returns value of field Signal +func (o *KillOptions) GetSignal() string { + if o.Signal == nil { + var z string + return z + } + return *o.Signal +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_list_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_list_options.go new file mode 100644 index 00000000..c6a25908 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_list_options.go @@ -0,0 +1,123 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ListOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ListOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAll set field All to given value +func (o *ListOptions) WithAll(value bool) *ListOptions { + o.All = &value + return o +} + +// GetAll returns value of field All +func (o *ListOptions) GetAll() bool { + if o.All == nil { + var z bool + return z + } + return *o.All +} + +// WithExternal set field External to given value +func (o *ListOptions) WithExternal(value bool) *ListOptions { + o.External = &value + return o +} + +// GetExternal returns value of field External +func (o *ListOptions) GetExternal() bool { + if o.External == nil { + var z bool + return z + } + return *o.External +} + +// WithFilters set field Filters to given value +func (o *ListOptions) WithFilters(value map[string][]string) *ListOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *ListOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} + +// WithLast set field Last to given value +func (o *ListOptions) WithLast(value int) *ListOptions { + o.Last = &value + return o +} + +// GetLast returns value of field Last +func (o *ListOptions) GetLast() int { + if o.Last == nil { + var z int + return z + } + return *o.Last +} + +// WithNamespace set field Namespace to given value +func (o *ListOptions) WithNamespace(value bool) *ListOptions { + o.Namespace = &value + return o +} + +// GetNamespace returns value of field Namespace +func (o *ListOptions) GetNamespace() bool { + if o.Namespace == nil { + var z bool + return z + } + return *o.Namespace +} + +// WithSize set field Size to given value +func (o *ListOptions) WithSize(value bool) *ListOptions { + o.Size = &value + return o +} + +// GetSize returns value of field Size +func (o *ListOptions) GetSize() bool { + if o.Size == nil { + var z bool + return z + } + return *o.Size +} + +// WithSync set field Sync to given value +func (o *ListOptions) WithSync(value bool) *ListOptions { + o.Sync = &value + return o +} + +// GetSync returns value of field Sync +func (o *ListOptions) GetSync() bool { + if o.Sync == nil { + var z bool + return z + } + return *o.Sync +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_log_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_log_options.go new file mode 100644 index 00000000..f6efbaa9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_log_options.go @@ -0,0 +1,123 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *LogOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *LogOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithFollow set field Follow to given value +func (o *LogOptions) WithFollow(value bool) *LogOptions { + o.Follow = &value + return o +} + +// GetFollow returns value of field Follow +func (o *LogOptions) GetFollow() bool { + if o.Follow == nil { + var z bool + return z + } + return *o.Follow +} + +// WithSince set field Since to given value +func (o *LogOptions) WithSince(value string) *LogOptions { + o.Since = &value + return o +} + +// GetSince returns value of field Since +func (o *LogOptions) GetSince() string { + if o.Since == nil { + var z string + return z + } + return *o.Since +} + +// WithStderr set field Stderr to given value +func (o *LogOptions) WithStderr(value bool) *LogOptions { + o.Stderr = &value + return o +} + +// GetStderr returns value of field Stderr +func (o *LogOptions) GetStderr() bool { + if o.Stderr == nil { + var z bool + return z + } + return *o.Stderr +} + +// WithStdout set field Stdout to given value +func (o *LogOptions) WithStdout(value bool) *LogOptions { + o.Stdout = &value + return o +} + +// GetStdout returns value of field Stdout +func (o *LogOptions) GetStdout() bool { + if o.Stdout == nil { + var z bool + return z + } + return *o.Stdout +} + +// WithTail set field Tail to given value +func (o *LogOptions) WithTail(value string) *LogOptions { + o.Tail = &value + return o +} + +// GetTail returns value of field Tail +func (o *LogOptions) GetTail() string { + if o.Tail == nil { + var z string + return z + } + return *o.Tail +} + +// WithTimestamps set field Timestamps to given value +func (o *LogOptions) WithTimestamps(value bool) *LogOptions { + o.Timestamps = &value + return o +} + +// GetTimestamps returns value of field Timestamps +func (o *LogOptions) GetTimestamps() bool { + if o.Timestamps == nil { + var z bool + return z + } + return *o.Timestamps +} + +// WithUntil set field Until to given value +func (o *LogOptions) WithUntil(value string) *LogOptions { + o.Until = &value + return o +} + +// GetUntil returns value of field Until +func (o *LogOptions) GetUntil() string { + if o.Until == nil { + var z string + return z + } + return *o.Until +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_mount_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_mount_options.go new file mode 100644 index 00000000..332f0180 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_mount_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *MountOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *MountOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_mountedcontainerpaths_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_mountedcontainerpaths_options.go new file mode 100644 index 00000000..8c5494fa --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_mountedcontainerpaths_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *MountedContainerPathsOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *MountedContainerPathsOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_pause_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_pause_options.go new file mode 100644 index 00000000..14b18a87 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_pause_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *PauseOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *PauseOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_prune_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_prune_options.go new file mode 100644 index 00000000..69d619d1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_prune_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *PruneOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *PruneOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithFilters set field Filters to given value +func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *PruneOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_remove_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_remove_options.go new file mode 100644 index 00000000..e7da10a0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_remove_options.go @@ -0,0 +1,93 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *RemoveOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *RemoveOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithDepend set field Depend to given value +func (o *RemoveOptions) WithDepend(value bool) *RemoveOptions { + o.Depend = &value + return o +} + +// GetDepend returns value of field Depend +func (o *RemoveOptions) GetDepend() bool { + if o.Depend == nil { + var z bool + return z + } + return *o.Depend +} + +// WithIgnore set field Ignore to given value +func (o *RemoveOptions) WithIgnore(value bool) *RemoveOptions { + o.Ignore = &value + return o +} + +// GetIgnore returns value of field Ignore +func (o *RemoveOptions) GetIgnore() bool { + if o.Ignore == nil { + var z bool + return z + } + return *o.Ignore +} + +// WithForce set field Force to given value +func (o *RemoveOptions) WithForce(value bool) *RemoveOptions { + o.Force = &value + return o +} + +// GetForce returns value of field Force +func (o *RemoveOptions) GetForce() bool { + if o.Force == nil { + var z bool + return z + } + return *o.Force +} + +// WithVolumes set field Volumes to given value +func (o *RemoveOptions) WithVolumes(value bool) *RemoveOptions { + o.Volumes = &value + return o +} + +// GetVolumes returns value of field Volumes +func (o *RemoveOptions) GetVolumes() bool { + if o.Volumes == nil { + var z bool + return z + } + return *o.Volumes +} + +// WithTimeout set field Timeout to given value +func (o *RemoveOptions) WithTimeout(value uint) *RemoveOptions { + o.Timeout = &value + return o +} + +// GetTimeout returns value of field Timeout +func (o *RemoveOptions) GetTimeout() uint { + if o.Timeout == nil { + var z uint + return z + } + return *o.Timeout +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_rename_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_rename_options.go new file mode 100644 index 00000000..57258121 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_rename_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *RenameOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *RenameOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithName set field Name to given value +func (o *RenameOptions) WithName(value string) *RenameOptions { + o.Name = &value + return o +} + +// GetName returns value of field Name +func (o *RenameOptions) GetName() string { + if o.Name == nil { + var z string + return z + } + return *o.Name +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_resizeexectty_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_resizeexectty_options.go new file mode 100644 index 00000000..0741f4ea --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_resizeexectty_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ResizeExecTTYOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ResizeExecTTYOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithHeight set field Height to given value +func (o *ResizeExecTTYOptions) WithHeight(value int) *ResizeExecTTYOptions { + o.Height = &value + return o +} + +// GetHeight returns value of field Height +func (o *ResizeExecTTYOptions) GetHeight() int { + if o.Height == nil { + var z int + return z + } + return *o.Height +} + +// WithWidth set field Width to given value +func (o *ResizeExecTTYOptions) WithWidth(value int) *ResizeExecTTYOptions { + o.Width = &value + return o +} + +// GetWidth returns value of field Width +func (o *ResizeExecTTYOptions) GetWidth() int { + if o.Width == nil { + var z int + return z + } + return *o.Width +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_resizetty_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_resizetty_options.go new file mode 100644 index 00000000..c336f6fe --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_resizetty_options.go @@ -0,0 +1,63 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ResizeTTYOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ResizeTTYOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithHeight set field Height to given value +func (o *ResizeTTYOptions) WithHeight(value int) *ResizeTTYOptions { + o.Height = &value + return o +} + +// GetHeight returns value of field Height +func (o *ResizeTTYOptions) GetHeight() int { + if o.Height == nil { + var z int + return z + } + return *o.Height +} + +// WithWidth set field Width to given value +func (o *ResizeTTYOptions) WithWidth(value int) *ResizeTTYOptions { + o.Width = &value + return o +} + +// GetWidth returns value of field Width +func (o *ResizeTTYOptions) GetWidth() int { + if o.Width == nil { + var z int + return z + } + return *o.Width +} + +// WithRunning set field Running to given value +func (o *ResizeTTYOptions) WithRunning(value bool) *ResizeTTYOptions { + o.Running = &value + return o +} + +// GetRunning returns value of field Running +func (o *ResizeTTYOptions) GetRunning() bool { + if o.Running == nil { + var z bool + return z + } + return *o.Running +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_restart_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_restart_options.go new file mode 100644 index 00000000..e27e881a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_restart_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *RestartOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *RestartOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithTimeout set field Timeout to given value +func (o *RestartOptions) WithTimeout(value int) *RestartOptions { + o.Timeout = &value + return o +} + +// GetTimeout returns value of field Timeout +func (o *RestartOptions) GetTimeout() int { + if o.Timeout == nil { + var z int + return z + } + return *o.Timeout +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_restore_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_restore_options.go new file mode 100644 index 00000000..74348e05 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_restore_options.go @@ -0,0 +1,228 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *RestoreOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *RestoreOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithIgnoreRootfs set field IgnoreRootfs to given value +func (o *RestoreOptions) WithIgnoreRootfs(value bool) *RestoreOptions { + o.IgnoreRootfs = &value + return o +} + +// GetIgnoreRootfs returns value of field IgnoreRootfs +func (o *RestoreOptions) GetIgnoreRootfs() bool { + if o.IgnoreRootfs == nil { + var z bool + return z + } + return *o.IgnoreRootfs +} + +// WithIgnoreVolumes set field IgnoreVolumes to given value +func (o *RestoreOptions) WithIgnoreVolumes(value bool) *RestoreOptions { + o.IgnoreVolumes = &value + return o +} + +// GetIgnoreVolumes returns value of field IgnoreVolumes +func (o *RestoreOptions) GetIgnoreVolumes() bool { + if o.IgnoreVolumes == nil { + var z bool + return z + } + return *o.IgnoreVolumes +} + +// WithIgnoreStaticIP set field IgnoreStaticIP to given value +func (o *RestoreOptions) WithIgnoreStaticIP(value bool) *RestoreOptions { + o.IgnoreStaticIP = &value + return o +} + +// GetIgnoreStaticIP returns value of field IgnoreStaticIP +func (o *RestoreOptions) GetIgnoreStaticIP() bool { + if o.IgnoreStaticIP == nil { + var z bool + return z + } + return *o.IgnoreStaticIP +} + +// WithIgnoreStaticMAC set field IgnoreStaticMAC to given value +func (o *RestoreOptions) WithIgnoreStaticMAC(value bool) *RestoreOptions { + o.IgnoreStaticMAC = &value + return o +} + +// GetIgnoreStaticMAC returns value of field IgnoreStaticMAC +func (o *RestoreOptions) GetIgnoreStaticMAC() bool { + if o.IgnoreStaticMAC == nil { + var z bool + return z + } + return *o.IgnoreStaticMAC +} + +// WithImportAchive set field ImportAchive to given value +func (o *RestoreOptions) WithImportAchive(value string) *RestoreOptions { + o.ImportAchive = &value + return o +} + +// GetImportAchive returns value of field ImportAchive +func (o *RestoreOptions) GetImportAchive() string { + if o.ImportAchive == nil { + var z string + return z + } + return *o.ImportAchive +} + +// WithImportArchive set field ImportArchive to given value +func (o *RestoreOptions) WithImportArchive(value string) *RestoreOptions { + o.ImportArchive = &value + return o +} + +// GetImportArchive returns value of field ImportArchive +func (o *RestoreOptions) GetImportArchive() string { + if o.ImportArchive == nil { + var z string + return z + } + return *o.ImportArchive +} + +// WithKeep set field Keep to given value +func (o *RestoreOptions) WithKeep(value bool) *RestoreOptions { + o.Keep = &value + return o +} + +// GetKeep returns value of field Keep +func (o *RestoreOptions) GetKeep() bool { + if o.Keep == nil { + var z bool + return z + } + return *o.Keep +} + +// WithName set field Name to given value +func (o *RestoreOptions) WithName(value string) *RestoreOptions { + o.Name = &value + return o +} + +// GetName returns value of field Name +func (o *RestoreOptions) GetName() string { + if o.Name == nil { + var z string + return z + } + return *o.Name +} + +// WithTCPEstablished set field TCPEstablished to given value +func (o *RestoreOptions) WithTCPEstablished(value bool) *RestoreOptions { + o.TCPEstablished = &value + return o +} + +// GetTCPEstablished returns value of field TCPEstablished +func (o *RestoreOptions) GetTCPEstablished() bool { + if o.TCPEstablished == nil { + var z bool + return z + } + return *o.TCPEstablished +} + +// WithTCPClose set field TCPClose to given value +func (o *RestoreOptions) WithTCPClose(value bool) *RestoreOptions { + o.TCPClose = &value + return o +} + +// GetTCPClose returns value of field TCPClose +func (o *RestoreOptions) GetTCPClose() bool { + if o.TCPClose == nil { + var z bool + return z + } + return *o.TCPClose +} + +// WithPod set field Pod to given value +func (o *RestoreOptions) WithPod(value string) *RestoreOptions { + o.Pod = &value + return o +} + +// GetPod returns value of field Pod +func (o *RestoreOptions) GetPod() string { + if o.Pod == nil { + var z string + return z + } + return *o.Pod +} + +// WithPrintStats set field PrintStats to given value +func (o *RestoreOptions) WithPrintStats(value bool) *RestoreOptions { + o.PrintStats = &value + return o +} + +// GetPrintStats returns value of field PrintStats +func (o *RestoreOptions) GetPrintStats() bool { + if o.PrintStats == nil { + var z bool + return z + } + return *o.PrintStats +} + +// WithPublishPorts set field PublishPorts to given value +func (o *RestoreOptions) WithPublishPorts(value []string) *RestoreOptions { + o.PublishPorts = value + return o +} + +// GetPublishPorts returns value of field PublishPorts +func (o *RestoreOptions) GetPublishPorts() []string { + if o.PublishPorts == nil { + var z []string + return z + } + return o.PublishPorts +} + +// WithFileLocks set field FileLocks to given value +func (o *RestoreOptions) WithFileLocks(value bool) *RestoreOptions { + o.FileLocks = &value + return o +} + +// GetFileLocks returns value of field FileLocks +func (o *RestoreOptions) GetFileLocks() bool { + if o.FileLocks == nil { + var z bool + return z + } + return *o.FileLocks +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_shouldrestart_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_shouldrestart_options.go new file mode 100644 index 00000000..e968731b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_shouldrestart_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ShouldRestartOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ShouldRestartOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_start_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_start_options.go new file mode 100644 index 00000000..fb77b421 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_start_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *StartOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *StartOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithDetachKeys set field DetachKeys to given value +func (o *StartOptions) WithDetachKeys(value string) *StartOptions { + o.DetachKeys = &value + return o +} + +// GetDetachKeys returns value of field DetachKeys +func (o *StartOptions) GetDetachKeys() string { + if o.DetachKeys == nil { + var z string + return z + } + return *o.DetachKeys +} + +// WithRecursive set field Recursive to given value +func (o *StartOptions) WithRecursive(value bool) *StartOptions { + o.Recursive = &value + return o +} + +// GetRecursive returns value of field Recursive +func (o *StartOptions) GetRecursive() bool { + if o.Recursive == nil { + var z bool + return z + } + return *o.Recursive +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_stats_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_stats_options.go new file mode 100644 index 00000000..fbae6bd4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_stats_options.go @@ -0,0 +1,63 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *StatsOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *StatsOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAll set field All to given value +func (o *StatsOptions) WithAll(value bool) *StatsOptions { + o.All = &value + return o +} + +// GetAll returns value of field All +func (o *StatsOptions) GetAll() bool { + if o.All == nil { + var z bool + return z + } + return *o.All +} + +// WithStream set field Stream to given value +func (o *StatsOptions) WithStream(value bool) *StatsOptions { + o.Stream = &value + return o +} + +// GetStream returns value of field Stream +func (o *StatsOptions) GetStream() bool { + if o.Stream == nil { + var z bool + return z + } + return *o.Stream +} + +// WithInterval set field Interval to given value +func (o *StatsOptions) WithInterval(value int) *StatsOptions { + o.Interval = &value + return o +} + +// GetInterval returns value of field Interval +func (o *StatsOptions) GetInterval() int { + if o.Interval == nil { + var z int + return z + } + return *o.Interval +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_stop_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_stop_options.go new file mode 100644 index 00000000..eea280e3 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_stop_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *StopOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *StopOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithIgnore set field Ignore to given value +func (o *StopOptions) WithIgnore(value bool) *StopOptions { + o.Ignore = &value + return o +} + +// GetIgnore returns value of field Ignore +func (o *StopOptions) GetIgnore() bool { + if o.Ignore == nil { + var z bool + return z + } + return *o.Ignore +} + +// WithTimeout set field Timeout to given value +func (o *StopOptions) WithTimeout(value uint) *StopOptions { + o.Timeout = &value + return o +} + +// GetTimeout returns value of field Timeout +func (o *StopOptions) GetTimeout() uint { + if o.Timeout == nil { + var z uint + return z + } + return *o.Timeout +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_top_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_top_options.go new file mode 100644 index 00000000..cfd21ecc --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_top_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *TopOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *TopOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithDescriptors set field Descriptors to given value +func (o *TopOptions) WithDescriptors(value []string) *TopOptions { + o.Descriptors = &value + return o +} + +// GetDescriptors returns value of field Descriptors +func (o *TopOptions) GetDescriptors() []string { + if o.Descriptors == nil { + var z []string + return z + } + return *o.Descriptors +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_unmount_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_unmount_options.go new file mode 100644 index 00000000..0752556a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_unmount_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *UnmountOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *UnmountOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_unpause_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_unpause_options.go new file mode 100644 index 00000000..2f91a7d3 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_unpause_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *UnpauseOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *UnpauseOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_wait_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_wait_options.go new file mode 100644 index 00000000..fcb9a914 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/types_wait_options.go @@ -0,0 +1,64 @@ +// Code generated by go generate; DO NOT EDIT. +package containers + +import ( + "net/url" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *WaitOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *WaitOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithConditions set field Conditions to given value +func (o *WaitOptions) WithConditions(value []string) *WaitOptions { + o.Conditions = value + return o +} + +// GetConditions returns value of field Conditions +func (o *WaitOptions) GetConditions() []string { + if o.Conditions == nil { + var z []string + return z + } + return o.Conditions +} + +// WithInterval set field Interval to given value +func (o *WaitOptions) WithInterval(value string) *WaitOptions { + o.Interval = &value + return o +} + +// GetInterval returns value of field Interval +func (o *WaitOptions) GetInterval() string { + if o.Interval == nil { + var z string + return z + } + return *o.Interval +} + +// WithCondition set field Condition to given value +func (o *WaitOptions) WithCondition(value []define.ContainerStatus) *WaitOptions { + o.Condition = value + return o +} + +// GetCondition returns value of field Condition +func (o *WaitOptions) GetCondition() []define.ContainerStatus { + if o.Condition == nil { + var z []define.ContainerStatus + return z + } + return o.Condition +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/containers/update.go b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/update.go new file mode 100644 index 00000000..574d602b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/containers/update.go @@ -0,0 +1,56 @@ +package containers + +import ( + "context" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/containers/podman/v5/pkg/api/handlers" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/types" + jsoniter "github.com/json-iterator/go" +) + +func Update(ctx context.Context, options *types.ContainerUpdateOptions) (string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + + params := url.Values{} + if options.RestartPolicy != nil { + params.Set("restartPolicy", *options.RestartPolicy) + if options.RestartRetries != nil { + params.Set("restartRetries", strconv.Itoa(int(*options.RestartRetries))) + } + } + + updateEntities := &handlers.UpdateEntities{ + Env: options.Env, + UnsetEnv: options.UnsetEnv, + } + if options.Resources != nil { + updateEntities.LinuxResources = *options.Resources + } + if options.ChangedHealthCheckConfiguration != nil { + updateEntities.UpdateHealthCheckConfig = *options.ChangedHealthCheckConfiguration + } + if options.DevicesLimits != nil { + updateEntities.UpdateContainerDevicesLimits = *options.DevicesLimits + } + + requestData, err := jsoniter.MarshalToString(updateEntities) + if err != nil { + return "", err + } + stringReader := strings.NewReader(requestData) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/%s/update", params, nil, options.NameOrID) + if err != nil { + return "", err + } + defer response.Body.Close() + + return options.NameOrID, response.Process(nil) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/doc.go b/vendor/github.com/containers/podman/v5/pkg/bindings/doc.go new file mode 100644 index 00000000..20b4c42e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/doc.go @@ -0,0 +1,5 @@ +package bindings + +/* + See https://github.com/containers/podman/blob/main/pkg/bindings/README.md for details. +*/ diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/errors.go b/vendor/github.com/containers/podman/v5/pkg/bindings/errors.go new file mode 100644 index 00000000..660ee9e0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/errors.go @@ -0,0 +1,91 @@ +package bindings + +import ( + "encoding/json" + "errors" + "fmt" + "io" + + "github.com/blang/semver/v4" + "github.com/containers/podman/v5/pkg/errorhandling" +) + +var ( + ErrNotImplemented = errors.New("function not implemented") +) + +func handleError(data []byte, unmarshalErrorInto any) error { + if err := json.Unmarshal(data, unmarshalErrorInto); err != nil { + return fmt.Errorf("unmarshalling error into %#v, data %q: %w", unmarshalErrorInto, string(data), err) + } + return unmarshalErrorInto.(error) +} + +// Process drains the response body, and processes the HTTP status code +// Note: Closing the response.Body is left to the caller +func (h *APIResponse) Process(unmarshalInto any) error { + return h.ProcessWithError(unmarshalInto, &errorhandling.ErrorModel{}) +} + +// ProcessWithError drains the response body, and processes the HTTP status code +// Note: Closing the response.Body is left to the caller +func (h *APIResponse) ProcessWithError(unmarshalInto any, unmarshalErrorInto any) error { + data, err := io.ReadAll(h.Response.Body) + if err != nil { + return fmt.Errorf("unable to process API response: %w", err) + } + if h.IsSuccess() || h.IsRedirection() { + if unmarshalInto != nil { + if err := json.Unmarshal(data, unmarshalInto); err != nil { + return fmt.Errorf("unmarshalling into %#v, data %q: %w", unmarshalInto, string(data), err) + } + return nil + } + return nil + } + + if h.IsConflictError() { + return handleError(data, unmarshalErrorInto) + } + if h.Response.Header.Get("Content-Type") == "application/json" { + return handleError(data, &errorhandling.ErrorModel{}) + } + return &errorhandling.ErrorModel{ + Message: string(data), + ResponseCode: h.Response.StatusCode, + } +} + +func CheckResponseCode(inError error) (int, error) { + switch e := inError.(type) { + case *errorhandling.ErrorModel: + return e.Code(), nil + case *errorhandling.PodConflictErrorModel: + return e.Code(), nil + default: + return -1, errors.New("is not type ErrorModel") + } +} + +type APIVersionError struct { + endpoint string + serverVersion *semver.Version + requiredVersion string +} + +// NewAPIVersionError create bindings error when the endpoint on the server is not supported +// because the version is to old. +// - endpoint is the name for the endpoint (e.g. /containers/json) +// - version is the server API version +// - requiredVersion is the server version need to use said endpoint +func NewAPIVersionError(endpoint string, version *semver.Version, requiredVersion string) *APIVersionError { + return &APIVersionError{ + endpoint: endpoint, + serverVersion: version, + requiredVersion: requiredVersion, + } +} + +func (e *APIVersionError) Error() string { + return fmt.Sprintf("API server version is %s, need at least %s to call %s", e.serverVersion.String(), e.requiredVersion, e.endpoint) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/build.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/build.go new file mode 100644 index 00000000..fa220d9b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/build.go @@ -0,0 +1,1232 @@ +package images + +import ( + "archive/tar" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "mime/multipart" + "net/http" + "net/url" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + + "github.com/blang/semver/v4" + "github.com/containers/buildah/define" + "github.com/containers/podman/v5/internal/remote_build_helpers" + ldefine "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/auth" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/containers/podman/v5/pkg/specgen" + "github.com/containers/podman/v5/pkg/util" + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/go-units" + "github.com/hashicorp/go-multierror" + jsoniter "github.com/json-iterator/go" + gzip "github.com/klauspost/pgzip" + "github.com/sirupsen/logrus" + imageTypes "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/ioutils" + "go.podman.io/storage/pkg/regexp" +) + +type devino struct { + Dev uint64 + Ino uint64 +} + +var iidRegex = regexp.Delayed(`^[0-9a-f]{12}`) + +type BuildResponse struct { + Stream string `json:"stream,omitempty"` + Error *jsonmessage.JSONError `json:"errorDetail,omitempty"` + // NOTE: `error` is being deprecated check https://github.com/moby/moby/blob/master/pkg/jsonmessage/jsonmessage.go#L148 + ErrorMessage string `json:"error,omitempty"` // deprecate this slowly + Aux json.RawMessage `json:"aux,omitempty"` +} + +// BuildFilePaths contains the file paths and exclusion patterns for the build context. +type BuildFilePaths struct { + tarContent []string + newContainerFiles []string // dockerfile paths, relative to context dir, ToSlash()ed + dontexcludes []string + excludes []string +} + +// RequestParts contains the components of an HTTP request for the build API. +type RequestParts struct { + Headers http.Header + Params url.Values + Body io.ReadCloser +} + +// Modify the build contexts that uses a local windows path. The windows path is +// converted into the corresping guest path in the default Windows machine +// (e.g. C:\test ==> /mnt/c/test). +func convertAdditionalBuildContexts(additionalBuildContexts map[string]*define.AdditionalBuildContext) { + for _, context := range additionalBuildContexts { + if !context.IsImage && !context.IsURL { + path, err := specgen.ConvertWinMountPath(context.Value) + // It's not worth failing if the path can't be converted + if err == nil { + context.Value = path + } + } + } +} + +// convertVolumeSrcPath converts windows paths in the HOST-DIR part of a volume +// into the corresponding path in the default Windows machine. +// (e.g. C:\test:/src/docs ==> /mnt/c/test:/src/docs). +// If any error occurs while parsing the volume string, the original volume +// string is returned. +func convertVolumeSrcPath(volume string) string { + splitVol := specgen.SplitVolumeString(volume) + if len(splitVol) < 2 || len(splitVol) > 3 { + return volume + } + convertedSrcPath, err := specgen.ConvertWinMountPath(splitVol[0]) + if err != nil { + return volume + } + if len(splitVol) == 2 { + return convertedSrcPath + ":" + splitVol[1] + } else { + return convertedSrcPath + ":" + splitVol[1] + ":" + splitVol[2] + } +} + +// isSupportedVersion checks if the server version is greater than or equal to the specified minimum version. +// It extracts version numbers from the server version string, removing any suffixes like -dev or -rc, +// and compares them using semantic versioning. +func isSupportedVersion(ctx context.Context, minVersion string) (bool, error) { + serverVersion := bindings.ServiceVersion(ctx) + + // Extract just the version numbers (remove -dev, -rc, etc) + versionStr := serverVersion.String() + if idx := strings.Index(versionStr, "-"); idx > 0 { + versionStr = versionStr[:idx] + } + + serverVer, err := semver.ParseTolerant(versionStr) + if err != nil { + return false, fmt.Errorf("parsing server version %q: %w", serverVersion, err) + } + + minMultipartVersion, _ := semver.ParseTolerant(minVersion) + + return serverVer.GTE(minMultipartVersion), nil +} + +// prepareParams converts BuildOptions into URL parameters for the build API request. +// It handles various build options including capabilities, annotations, CPU settings, +// devices, labels, platforms, volumes, and other build configuration parameters. +func prepareParams(options types.BuildOptions) (url.Values, error) { + params := url.Values{} + + if caps := options.AddCapabilities; len(caps) > 0 { + c, err := jsoniter.MarshalToString(caps) + if err != nil { + return nil, err + } + params.Add("addcaps", c) + } + + if annotations := options.Annotations; len(annotations) > 0 { + l, err := jsoniter.MarshalToString(annotations) + if err != nil { + return nil, err + } + params.Set("annotations", l) + } + + if cppflags := options.CPPFlags; len(cppflags) > 0 { + l, err := jsoniter.MarshalToString(cppflags) + if err != nil { + return nil, err + } + params.Set("cppflags", l) + } + + if options.AllPlatforms { + params.Add("allplatforms", "1") + } + + params.Add("t", options.Output) + for _, tag := range options.AdditionalTags { + params.Add("t", tag) + } + + if options.IDMappingOptions != nil { + idmappingsOptions, err := jsoniter.Marshal(options.IDMappingOptions) + if err != nil { + return nil, err + } + params.Set("idmappingoptions", string(idmappingsOptions)) + } + if buildArgs := options.Args; len(buildArgs) > 0 { + bArgs, err := jsoniter.MarshalToString(buildArgs) + if err != nil { + return nil, err + } + params.Set("buildargs", bArgs) + } + if excludes := options.Excludes; len(excludes) > 0 { + bArgs, err := jsoniter.MarshalToString(excludes) + if err != nil { + return nil, err + } + params.Set("excludes", bArgs) + } + if cpuPeriod := options.CommonBuildOpts.CPUPeriod; cpuPeriod > 0 { + params.Set("cpuperiod", strconv.Itoa(int(cpuPeriod))) + } + if cpuQuota := options.CommonBuildOpts.CPUQuota; cpuQuota > 0 { + params.Set("cpuquota", strconv.Itoa(int(cpuQuota))) + } + if cpuSetCpus := options.CommonBuildOpts.CPUSetCPUs; len(cpuSetCpus) > 0 { + params.Set("cpusetcpus", cpuSetCpus) + } + if cpuSetMems := options.CommonBuildOpts.CPUSetMems; len(cpuSetMems) > 0 { + params.Set("cpusetmems", cpuSetMems) + } + if cpuShares := options.CommonBuildOpts.CPUShares; cpuShares > 0 { + params.Set("cpushares", strconv.Itoa(int(cpuShares))) + } + if len(options.CommonBuildOpts.CgroupParent) > 0 { + params.Set("cgroupparent", options.CommonBuildOpts.CgroupParent) + } + + params.Set("networkmode", strconv.Itoa(int(options.ConfigureNetwork))) + params.Set("outputformat", options.OutputFormat) + + if devices := options.Devices; len(devices) > 0 { + d, err := jsoniter.MarshalToString(devices) + if err != nil { + return nil, err + } + params.Add("devices", d) + } + + if dnsservers := options.CommonBuildOpts.DNSServers; len(dnsservers) > 0 { + c, err := jsoniter.MarshalToString(dnsservers) + if err != nil { + return nil, err + } + params.Add("dnsservers", c) + } + if dnsoptions := options.CommonBuildOpts.DNSOptions; len(dnsoptions) > 0 { + c, err := jsoniter.MarshalToString(dnsoptions) + if err != nil { + return nil, err + } + params.Add("dnsoptions", c) + } + if dnssearch := options.CommonBuildOpts.DNSSearch; len(dnssearch) > 0 { + c, err := jsoniter.MarshalToString(dnssearch) + if err != nil { + return nil, err + } + params.Add("dnssearch", c) + } + + if caps := options.DropCapabilities; len(caps) > 0 { + c, err := jsoniter.MarshalToString(caps) + if err != nil { + return nil, err + } + params.Add("dropcaps", c) + } + + if options.ForceRmIntermediateCtrs { + params.Set("forcerm", "1") + } + if options.RemoveIntermediateCtrs { + params.Set("rm", "1") + } else { + params.Set("rm", "0") + } + if options.CommonBuildOpts.OmitHistory { + params.Set("omithistory", "1") + } else { + params.Set("omithistory", "0") + } + if len(options.From) > 0 { + params.Set("from", options.From) + } + if options.IgnoreUnrecognizedInstructions { + params.Set("ignore", "1") + } + switch options.CreatedAnnotation { + case imageTypes.OptionalBoolFalse: + params.Set("createdannotation", "0") + case imageTypes.OptionalBoolTrue: + params.Set("createdannotation", "1") + } + switch options.InheritLabels { + case imageTypes.OptionalBoolFalse: + params.Set("inheritlabels", "0") + case imageTypes.OptionalBoolTrue: + params.Set("inheritlabels", "1") + } + + if options.InheritAnnotations == imageTypes.OptionalBoolFalse { + params.Set("inheritannotations", "0") + } else { + params.Set("inheritannotations", "1") + } + + params.Set("isolation", strconv.Itoa(int(options.Isolation))) + if options.CommonBuildOpts.HTTPProxy { + params.Set("httpproxy", "1") + } + if options.Jobs != nil { + params.Set("jobs", strconv.FormatUint(uint64(*options.Jobs), 10)) + } + if labels := options.Labels; len(labels) > 0 { + l, err := jsoniter.MarshalToString(labels) + if err != nil { + return nil, err + } + params.Set("labels", l) + } + + if opt := options.CommonBuildOpts.LabelOpts; len(opt) > 0 { + o, err := jsoniter.MarshalToString(opt) + if err != nil { + return nil, err + } + params.Set("labelopts", o) + } + + if len(options.CommonBuildOpts.SeccompProfilePath) > 0 { + params.Set("seccomp", options.CommonBuildOpts.SeccompProfilePath) + } + + if len(options.CommonBuildOpts.ApparmorProfile) > 0 { + params.Set("apparmor", options.CommonBuildOpts.ApparmorProfile) + } + + for _, layerLabel := range options.LayerLabels { + params.Add("layerLabel", layerLabel) + } + if options.Layers { + params.Set("layers", "1") + } + if options.LogRusage { + params.Set("rusage", "1") + } + if len(options.RusageLogFile) > 0 { + params.Set("rusagelogfile", options.RusageLogFile) + } + + params.Set("retry", strconv.Itoa(options.MaxPullPushRetries)) + params.Set("retry-delay", options.PullPushRetryDelay.String()) + + if len(options.Manifest) > 0 { + params.Set("manifest", options.Manifest) + } + if options.CacheFrom != nil { + cacheFrom := []string{} + for _, cacheSrc := range options.CacheFrom { + cacheFrom = append(cacheFrom, cacheSrc.String()) + } + cacheFromJSON, err := jsoniter.MarshalToString(cacheFrom) + if err != nil { + return nil, err + } + params.Set("cachefrom", cacheFromJSON) + } + + switch options.SkipUnusedStages { + case imageTypes.OptionalBoolTrue: + params.Set("skipunusedstages", "1") + case imageTypes.OptionalBoolFalse: + params.Set("skipunusedstages", "0") + } + + if options.CacheTo != nil { + cacheTo := []string{} + for _, cacheSrc := range options.CacheTo { + cacheTo = append(cacheTo, cacheSrc.String()) + } + cacheToJSON, err := jsoniter.MarshalToString(cacheTo) + if err != nil { + return nil, err + } + params.Set("cacheto", cacheToJSON) + } + if int64(options.CacheTTL) != 0 { + params.Set("cachettl", options.CacheTTL.String()) + } + if memSwap := options.CommonBuildOpts.MemorySwap; memSwap > 0 { + params.Set("memswap", strconv.Itoa(int(memSwap))) + } + if mem := options.CommonBuildOpts.Memory; mem > 0 { + params.Set("memory", strconv.Itoa(int(mem))) + } + switch options.CompatVolumes { + case imageTypes.OptionalBoolTrue: + params.Set("compatvolumes", "1") + case imageTypes.OptionalBoolFalse: + params.Set("compatvolumes", "0") + } + if options.NoCache { + params.Set("nocache", "1") + } + if options.CommonBuildOpts.NoHosts { + params.Set("nohosts", "1") + } + if t := options.Output; len(t) > 0 { + params.Set("output", t) + } + if t := options.OSVersion; len(t) > 0 { + params.Set("osversion", t) + } + for _, t := range options.OSFeatures { + params.Set("osfeature", t) + } + var platform string + if len(options.OS) > 0 { + platform = options.OS + } + if len(options.Architecture) > 0 { + if len(platform) == 0 { + platform = "linux" + } + platform += "/" + options.Architecture + } else if len(platform) > 0 { + platform += "/" + runtime.GOARCH + } + if len(platform) > 0 { + params.Set("platform", platform) + } + if len(options.Platforms) > 0 { + params.Del("platform") + for _, platformSpec := range options.Platforms { + // podman-cli will send empty struct, in such + // case don't add platform to param and let the + // build backend decide the default platform. + if platformSpec.OS == "" && platformSpec.Arch == "" && platformSpec.Variant == "" { + continue + } + platform = platformSpec.OS + "/" + platformSpec.Arch + if platformSpec.Variant != "" { + platform += "/" + platformSpec.Variant + } + params.Add("platform", platform) + } + } + + for _, volume := range options.CommonBuildOpts.Volumes { + params.Add("volume", convertVolumeSrcPath(volume)) + } + + for _, group := range options.GroupAdd { + params.Add("groupadd", group) + } + + params.Set("pullpolicy", options.PullPolicy.String()) + + switch options.CommonBuildOpts.IdentityLabel { + case imageTypes.OptionalBoolTrue: + params.Set("identitylabel", "1") + case imageTypes.OptionalBoolFalse: + params.Set("identitylabel", "0") + } + if options.Quiet { + params.Set("q", "1") + } + if options.RemoveIntermediateCtrs { + params.Set("rm", "1") + } + if len(options.Target) > 0 { + params.Set("target", options.Target) + } + + if hosts := options.CommonBuildOpts.AddHost; len(hosts) > 0 { + h, err := jsoniter.MarshalToString(hosts) + if err != nil { + return nil, err + } + params.Set("extrahosts", h) + } + if nsoptions := options.NamespaceOptions; len(nsoptions) > 0 { + ns, err := jsoniter.MarshalToString(nsoptions) + if err != nil { + return nil, err + } + params.Set("nsoptions", ns) + } + if shmSize := options.CommonBuildOpts.ShmSize; len(shmSize) > 0 { + shmBytes, err := units.RAMInBytes(shmSize) + if err != nil { + return nil, err + } + params.Set("shmsize", strconv.Itoa(int(shmBytes))) + } + if options.Squash { + params.Set("squash", "1") + } + + if options.SourceDateEpoch != nil { + t := options.SourceDateEpoch + params.Set("sourcedateepoch", strconv.FormatInt(t.Unix(), 10)) + } + if options.RewriteTimestamp { + params.Set("rewritetimestamp", "1") + } else { + params.Set("rewritetimestamp", "0") + } + if options.Timestamp != nil { + t := options.Timestamp + params.Set("timestamp", strconv.FormatInt(t.Unix(), 10)) + } + + if len(options.CommonBuildOpts.Ulimit) > 0 { + ulimitsJSON, err := json.Marshal(options.CommonBuildOpts.Ulimit) + if err != nil { + return nil, err + } + params.Set("ulimits", string(ulimitsJSON)) + } + + for _, env := range options.Envs { + params.Add("setenv", env) + } + + for _, uenv := range options.UnsetEnvs { + params.Add("unsetenv", uenv) + } + + for _, ulabel := range options.UnsetLabels { + params.Add("unsetlabel", ulabel) + } + + for _, uannotation := range options.UnsetAnnotations { + params.Add("unsetannotation", uannotation) + } + + return params, nil +} + +// prepareAuthHeaders sets up authentication headers for the build request. +// It handles Docker authentication configuration and TLS verification settings +// from the system context. +func prepareAuthHeaders(options types.BuildOptions, requestParts *RequestParts) (*RequestParts, error) { + var err error + + if options.SystemContext == nil { + return requestParts, err + } + + if options.SystemContext.DockerAuthConfig != nil { + requestParts.Headers, err = auth.MakeXRegistryAuthHeader(options.SystemContext, options.SystemContext.DockerAuthConfig.Username, options.SystemContext.DockerAuthConfig.Password) + } else { + requestParts.Headers, err = auth.MakeXRegistryConfigHeader(options.SystemContext, "", "") + } + if options.SystemContext.DockerInsecureSkipTLSVerify == imageTypes.OptionalBoolTrue { + requestParts.Params.Set("tlsVerify", "false") + } + + return requestParts, err +} + +// prepareContainerFiles processes container files (Dockerfiles/Containerfiles) for the build. +// It handles URLs, stdin input, symlinks, and determines which files need to be included +// in the tar archive versus which are already in the context directory. +// The stdinDestination parameter specifies where to save stdin content when processing /dev/stdin. +// WARNING: Caller must ensure tempManager.Cleanup() is called to remove any temporary files created. +func prepareContainerFiles(containerFiles []string, contextDir string, stdinDestination string, tempManager *remote_build_helpers.TempFileManager) (*BuildFilePaths, error) { + out := BuildFilePaths{ + tarContent: []string{contextDir}, + newContainerFiles: []string{}, // dockerfile paths, relative to context dir, ToSlash()ed + dontexcludes: []string{"!Dockerfile", "!Containerfile", "!.dockerignore", "!.containerignore"}, + excludes: []string{}, + } + + for _, c := range containerFiles { + // Do not add path to containerfile if it is a URL + if strings.HasPrefix(c, "http://") || strings.HasPrefix(c, "https://") { + out.newContainerFiles = append(out.newContainerFiles, c) + continue + } + if c == "/dev/stdin" { + stdinFile, err := tempManager.CreateTempFileFromReader(stdinDestination, "podman-build-stdin-*", os.Stdin) + if err != nil { + return nil, fmt.Errorf("processing stdin: %w", err) + } + c = stdinFile + } + c = filepath.Clean(c) + cfDir := filepath.Dir(c) + if absDir, err := filepath.EvalSymlinks(cfDir); err == nil { + name := filepath.ToSlash(strings.TrimPrefix(c, cfDir+string(filepath.Separator))) + c = filepath.Join(absDir, name) + } + + containerfile, err := filepath.Abs(c) + if err != nil { + logrus.Errorf("Cannot find absolute path of %v: %v", c, err) + return nil, err + } + + // Check if Containerfile is in the context directory, if so truncate the context directory off path + // Do NOT add to tarfile + if after, ok := strings.CutPrefix(containerfile, contextDir+string(filepath.Separator)); ok { + containerfile = after + out.dontexcludes = append(out.dontexcludes, "!"+containerfile) + out.dontexcludes = append(out.dontexcludes, "!"+containerfile+".dockerignore") + out.dontexcludes = append(out.dontexcludes, "!"+containerfile+".containerignore") + } else { + // If Containerfile does not exist, assume it is in context directory and do Not add to tarfile + if err := fileutils.Lexists(containerfile); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + containerfile = c + out.dontexcludes = append(out.dontexcludes, "!"+containerfile) + out.dontexcludes = append(out.dontexcludes, "!"+containerfile+".dockerignore") + out.dontexcludes = append(out.dontexcludes, "!"+containerfile+".containerignore") + } else { + // If Containerfile does exist and not in the context directory, add it to the tarfile + out.tarContent = append(out.tarContent, containerfile) + } + } + out.newContainerFiles = append(out.newContainerFiles, filepath.ToSlash(containerfile)) + } + + return &out, nil +} + +// prepareSecrets processes build secrets by creating temporary files for them. +// It moves secrets to the context directory and modifies the secret configuration +// to use relative paths suitable for remote builds. +// WARNING: Caller must ensure tempManager.Cleanup() is called to remove any temporary files created. +func prepareSecrets(secrets []string, contextDir string, tempManager *remote_build_helpers.TempFileManager) ([]string, []string, error) { + if len(secrets) == 0 { + return nil, nil, nil + } + + secretsForRemote := []string{} + tarContent := []string{} + + for _, secret := range secrets { + secretOpt := strings.Split(secret, ",") + modifiedOpt := []string{} + for _, token := range secretOpt { + opt, val, hasVal := strings.Cut(token, "=") + if hasVal { + if opt == "src" { + // read specified secret into a tmp file + // move tmp file to tar and change secret source to relative tmp file + tmpSecretFilePath, err := tempManager.CreateTempSecret(val, contextDir) + if err != nil { + return nil, nil, err + } + + // add tmp file to context dir + tarContent = append(tarContent, tmpSecretFilePath) + + modifiedSrc := fmt.Sprintf("src=%s", filepath.Base(tmpSecretFilePath)) + modifiedOpt = append(modifiedOpt, modifiedSrc) + } else { + modifiedOpt = append(modifiedOpt, token) + } + } + } + secretsForRemote = append(secretsForRemote, strings.Join(modifiedOpt, ",")) + } + + return secretsForRemote, tarContent, nil +} + +// prepareRemoteRequestBody creates the request body for the build API call. +// It handles both simple tar archives and multipart form data for builds with +// additional build contexts, supporting URLs, images, and local directories. +// WARNING: Caller must close request body. +func prepareRemoteRequestBody(ctx context.Context, requestParts *RequestParts, buildFilePaths *BuildFilePaths, options types.BuildOptions) (*RequestParts, error) { + tarfile, err := nTar(append(buildFilePaths.excludes, buildFilePaths.dontexcludes...), buildFilePaths.tarContent...) + if err != nil { + logrus.Errorf("Cannot tar container entries %v error: %v", buildFilePaths.tarContent, err) + return nil, err + } + + var contentType string + + // If there are additional build contexts, we need to handle them based on the server version + // podman version >= 5.6.0 supports multipart/form-data for additional build contexts that + // are local directories or archives. URLs and images are still sent as query parameters. + isSupported, err := isSupportedVersion(ctx, "5.6.0") + if err != nil { + return nil, err + } + + if len(options.SBOMScanOptions) > 0 { + for _, sbomScanOpts := range options.SBOMScanOptions { + if sbomScanOpts.SBOMOutput != "" { + requestParts.Params.Set("sbom-output", sbomScanOpts.SBOMOutput) + } + + if sbomScanOpts.PURLOutput != "" { + requestParts.Params.Set("sbom-purl-output", sbomScanOpts.PURLOutput) + } + + if sbomScanOpts.ImageSBOMOutput != "" { + requestParts.Params.Set("sbom-image-output", sbomScanOpts.ImageSBOMOutput) + } + + if sbomScanOpts.ImagePURLOutput != "" { + requestParts.Params.Set("sbom-image-purl-output", sbomScanOpts.ImagePURLOutput) + } + + if sbomScanOpts.Image != "" { + requestParts.Params.Set("sbom-scanner-image", sbomScanOpts.Image) + } + + if commands := sbomScanOpts.Commands; len(commands) > 0 { + c, err := jsoniter.MarshalToString(commands) + if err != nil { + return nil, err + } + requestParts.Params.Add("sbom-scanner-command", c) + } + + if sbomScanOpts.MergeStrategy != "" { + requestParts.Params.Set("sbom-merge-strategy", string(sbomScanOpts.MergeStrategy)) + } + } + } + + if len(options.AdditionalBuildContexts) == 0 { + requestParts.Body = tarfile + logrus.Debugf("Using main build context: %q", options.ContextDirectory) + return requestParts, nil + } + + if !isSupported { + convertAdditionalBuildContexts(options.AdditionalBuildContexts) + additionalBuildContextMap, err := jsoniter.Marshal(options.AdditionalBuildContexts) + if err != nil { + return nil, err + } + requestParts.Params.Set("additionalbuildcontexts", string(additionalBuildContextMap)) + + requestParts.Body = tarfile + logrus.Debugf("Using main build context: %q", options.ContextDirectory) + return requestParts, nil + } + + imageContexts := make(map[string]string) + urlContexts := make(map[string]string) + localContexts := make(map[string]*define.AdditionalBuildContext) + + for name, context := range options.AdditionalBuildContexts { + switch { + case context.IsImage: + imageContexts[name] = context.Value + case context.IsURL: + urlContexts[name] = context.Value + default: + localContexts[name] = context + } + } + + logrus.Debugf("URL Contexts: %v", urlContexts) + for name, url := range urlContexts { + requestParts.Params.Add("additionalbuildcontexts", fmt.Sprintf("%s=url:%s", name, url)) + } + + logrus.Debugf("Image Contexts: %v", imageContexts) + for name, imageRef := range imageContexts { + requestParts.Params.Add("additionalbuildcontexts", fmt.Sprintf("%s=image:%s", name, imageRef)) + } + + if len(localContexts) == 0 { + requestParts.Body = tarfile + logrus.Debugf("Using main build context: %q", options.ContextDirectory) + return requestParts, nil + } + // Multipart request structure: + // - "MainContext": The main build context as a tar file + // - "build-context-": Each additional local context as a tar file + logrus.Debugf("Using additional local build contexts: %v", localContexts) + pr, pw := io.Pipe() + writer := multipart.NewWriter(pw) + contentType = writer.FormDataContentType() + requestParts.Body = pr + + if requestParts.Headers == nil { + requestParts.Headers = make(http.Header) + } + requestParts.Headers.Set("Content-Type", contentType) + + go func() { + defer pw.Close() + defer writer.Close() + + mainContext, err := writer.CreateFormFile("MainContext", "MainContext.tar") + if err != nil { + pw.CloseWithError(fmt.Errorf("creating form file for main context: %w", err)) + return + } + + if _, err := io.Copy(mainContext, tarfile); err != nil { + pw.CloseWithError(fmt.Errorf("copying main context: %w", err)) + return + } + + defer func() { + if err := tarfile.Close(); err != nil { + logrus.Errorf("failed to close context tarfile: %v\n", err) + } + }() + + for name, context := range localContexts { + logrus.Debugf("Processing additional local context: %s", name) + part, err := writer.CreateFormFile(fmt.Sprintf("build-context-%s", name), name) + if err != nil { + pw.CloseWithError(fmt.Errorf("creating form file for context %q: %w", name, err)) + return + } + + // Context is already a tar + if archive.IsArchivePath(context.Value) { + file, err := os.Open(context.Value) + if err != nil { + pw.CloseWithError(fmt.Errorf("opening archive %q: %w", name, err)) + return + } + if _, err := io.Copy(part, file); err != nil { + file.Close() + pw.CloseWithError(fmt.Errorf("copying context %q: %w", name, err)) + return + } + file.Close() + } else { + tarContent, err := nTar(nil, context.Value) + if err != nil { + pw.CloseWithError(fmt.Errorf("creating tar content %q: %w", name, err)) + return + } + if _, err = io.Copy(part, tarContent); err != nil { + pw.CloseWithError(fmt.Errorf("copying tar content %q: %w", name, err)) + return + } + if err := tarContent.Close(); err != nil { + logrus.Errorf("Error closing tar content for context %q: %v\n", name, err) + } + } + } + }() + logrus.Debugf("Multipart body is created with content type: %s", contentType) + + return requestParts, nil +} + +// executeBuildRequest sends the build request to the API endpoint and returns the response. +// It handles the HTTP request creation and error checking for the build operation. +// WARNING: Caller must close the response body. +func executeBuildRequest(ctx context.Context, endpoint string, requestParts *RequestParts) (*bindings.APIResponse, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(ctx, requestParts.Body, http.MethodPost, endpoint, requestParts.Params, requestParts.Headers) + if err != nil { + return nil, err + } + + if !response.IsSuccess() { + return nil, response.Process(err) + } + + return response, nil +} + +// processBuildResponse processes the streaming build response from the API. +// It reads the JSON stream, extracts build output and errors, writes to stdout, +// and returns a build report with the final image ID. +func processBuildResponse(response *bindings.APIResponse, stdout io.Writer, saveFormat string) (*types.BuildReport, error) { + body := response.Body.(io.Reader) + if logrus.IsLevelEnabled(logrus.DebugLevel) { + if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found { + if keep, _ := strconv.ParseBool(v); keep { + t, _ := os.CreateTemp("", "build_*_client") + defer t.Close() + body = io.TeeReader(response.Body, t) + } + } + } + + dec := json.NewDecoder(body) + + var id string + for { + var s BuildResponse + select { + // FIXME(vrothberg): it seems we always hit the EOF case below, + // even when the server quit but it seems desirable to + // distinguish a proper build from a transient EOF. + case <-response.Request.Context().Done(): + return &types.BuildReport{ID: id, SaveFormat: saveFormat}, nil + default: + // non-blocking select + } + + if err := dec.Decode(&s); err != nil { + if errors.Is(err, io.ErrUnexpectedEOF) { + return nil, fmt.Errorf("server probably quit: %w", err) + } + // EOF means the stream is over in which case we need + // to have read the id. + if errors.Is(err, io.EOF) && id != "" { + break + } + return &types.BuildReport{ID: id, SaveFormat: saveFormat}, fmt.Errorf("decoding stream: %w", err) + } + + switch { + case s.Stream != "": + raw := []byte(s.Stream) + stdout.Write(raw) + if iidRegex.Match(raw) { + id = strings.TrimSuffix(s.Stream, "\n") + } + case s.Error != nil: + // If there's an error, return directly. The stream + // will be closed on return. + return &types.BuildReport{ID: id, SaveFormat: saveFormat}, errors.New(s.Error.Message) + default: + return &types.BuildReport{ID: id, SaveFormat: saveFormat}, errors.New("failed to parse build results stream, unexpected input") + } + } + return &types.BuildReport{ID: id, SaveFormat: saveFormat}, nil +} + +// prepareLocalRequestBody prepares HTTP request parameters for local build API calls. +// It sets up local context directory and additional build contexts using already translated paths. +func prepareLocalRequestBody(_ context.Context, requestParts *RequestParts, _ *BuildFilePaths, options types.BuildOptions) (*RequestParts, error) { + requestParts.Params.Set("localcontextdir", options.ContextDirectory) + + for name, context := range options.AdditionalBuildContexts { + switch { + case context.IsImage: + requestParts.Params.Add("additionalbuildcontexts", fmt.Sprintf("%s=image:%s", name, context.Value)) + case context.IsURL: + requestParts.Params.Add("additionalbuildcontexts", fmt.Sprintf("%s=url:%s", name, context.Value)) + default: + requestParts.Params.Add("additionalbuildcontexts", fmt.Sprintf("%s=localpath:%s", name, context.Value)) + } + } + return requestParts, nil +} + +// BuildFromServerContext performs a container image build using the local build API to build image with files present on server. +// +// Unlike the standard Build function, this uses existing files on the remote server's filesystem +// rather than uploading build contexts. The containerFiles and options parameters should contain +// already translated paths pointing to files on the remote server, making it suitable for scenarios +// where build contexts already exist on the server (e.g., shared filesystems, mounted volumes). +// +// The context directory and containerFiles paths must be accessible on the remote server. +// Missing paths will result in build errors. +// +// Returns a BuildReport containing the final image ID and save format. +func BuildFromServerContext(ctx context.Context, containerFiles []string, options types.BuildOptions) (*types.BuildReport, error) { + return build(ctx, containerFiles, options, "/local/build", prepareLocalRequestBody) +} + +// Build performs a container image build on the remote API using the standard build API. +// +// Prepares build contexts and container files by creating tar archives from local directories, +// processes build secrets and authentication, and streams the build to the remote server. +// Supports additional build contexts (URLs, images, local directories) via multipart uploads +// for servers >= v5.6.0, otherwise uses query parameters for compatibility. +// +// Returns a BuildReport containing the final image ID and save format. +func Build(ctx context.Context, containerFiles []string, options types.BuildOptions) (*types.BuildReport, error) { + return build(ctx, containerFiles, options, "/build", prepareRemoteRequestBody) +} + +type prepareRequestBodyFunc func(ctx context.Context, requestParts *RequestParts, buildFilePaths *BuildFilePaths, options types.BuildOptions) (*RequestParts, error) + +func build(ctx context.Context, containerFiles []string, options types.BuildOptions, endpoint string, prepareRequestBody prepareRequestBodyFunc) (*types.BuildReport, error) { + if options.CommonBuildOpts == nil { + options.CommonBuildOpts = new(define.CommonBuildOptions) + } + + tempManager := remote_build_helpers.NewTempFileManager() + defer tempManager.Cleanup() + + params_, err := prepareParams(options) + if err != nil { + return nil, err + } + + var headers http.Header + var requestBody io.ReadCloser + requestParts := &RequestParts{ + Params: params_, + Headers: headers, + Body: requestBody, + } + + var contextDir string + if contextDir, err = filepath.EvalSymlinks(options.ContextDirectory); err == nil { + options.ContextDirectory = contextDir + } + + requestParts, err = prepareAuthHeaders(options, requestParts) + if err != nil { + return nil, err + } + + contextDirAbs, err := filepath.Abs(options.ContextDirectory) + if err != nil { + logrus.Errorf("Cannot find absolute path of %v: %v", options.ContextDirectory, err) + return nil, err + } + stdinDestination := "" + if endpoint == "/local/build" { + stdinDestination = contextDirAbs + } + buildFilePaths, err := prepareContainerFiles(containerFiles, contextDirAbs, stdinDestination, tempManager) + if err != nil { + return nil, err + } + + if len(buildFilePaths.newContainerFiles) > 0 { + cFileJSON, err := json.Marshal(buildFilePaths.newContainerFiles) + if err != nil { + return nil, err + } + requestParts.Params.Set("dockerfile", string(cFileJSON)) + } + + buildFilePaths.excludes = options.Excludes + if len(buildFilePaths.excludes) == 0 { + buildFilePaths.excludes, _, err = util.ParseDockerignore(buildFilePaths.newContainerFiles, options.ContextDirectory) + if err != nil { + return nil, err + } + } + + // build secrets are usually absolute host path or relative to context dir on host + // in any case move secret to current context and ship the tar. + secretsForRemote, secretsTarContent, err := prepareSecrets(options.CommonBuildOpts.Secrets, options.ContextDirectory, tempManager) + if err != nil { + return nil, err + } + + if len(secretsForRemote) > 0 { + c, err := jsoniter.MarshalToString(secretsForRemote) + if err != nil { + return nil, err + } + requestParts.Params.Add("secrets", c) + buildFilePaths.tarContent = append(buildFilePaths.tarContent, secretsTarContent...) + } + + requestParts, err = prepareRequestBody(ctx, requestParts, buildFilePaths, options) + if err != nil { + return nil, fmt.Errorf("building tar file: %w", err) + } + defer func() { + if requestParts.Body != nil { + if err := requestParts.Body.Close(); err != nil { + logrus.Errorf("failed to close build request body: %v\n", err) + } + } + }() + + response, err := executeBuildRequest(ctx, endpoint, requestParts) + if err != nil { + return nil, err + } + defer response.Body.Close() + + saveFormat := ldefine.OCIArchive + if options.OutputFormat == define.Dockerv2ImageManifest { + saveFormat = ldefine.V2s2Archive + } + + stdout := io.Writer(os.Stdout) + if options.Out != nil { + stdout = options.Out + } + + return processBuildResponse(response, stdout, saveFormat) +} + +func nTar(excludes []string, sources ...string) (io.ReadCloser, error) { + pm, err := fileutils.NewPatternMatcher(excludes) + if err != nil { + return nil, fmt.Errorf("processing excludes list %v: %w", excludes, err) + } + + if len(sources) == 0 { + return nil, errors.New("no source(s) provided for build") + } + + pr, pw := io.Pipe() + gw := gzip.NewWriter(pw) + tw := tar.NewWriter(gw) + + var merr *multierror.Error + go func() { + defer pw.Close() + defer gw.Close() + defer tw.Close() + seen := make(map[devino]string) + for i, src := range sources { + source, err := filepath.Abs(src) + if err != nil { + logrus.Errorf("Cannot stat one of source context: %v", err) + merr = multierror.Append(merr, err) + return + } + err = filepath.WalkDir(source, func(path string, dentry fs.DirEntry, err error) error { + if err != nil { + return err + } + + separator := string(filepath.Separator) + // check if what we are given is an empty dir, if so then continue w/ it. Else return. + // if we are given a file or a symlink, we do not want to exclude it. + if source == path { + separator = "" + if dentry.IsDir() { + var p *os.File + p, err = os.Open(path) + if err != nil { + return err + } + defer p.Close() + _, err = p.Readdir(1) + if err == nil { + return nil // non empty root dir, need to return + } + if err != io.EOF { + logrus.Errorf("While reading directory %v: %v", path, err) + } + } + } + var name string + if i == 0 { + name = filepath.ToSlash(strings.TrimPrefix(path, source+separator)) + } else { + if !dentry.Type().IsRegular() { + return fmt.Errorf("path %s must be a regular file", path) + } + name = filepath.ToSlash(path) + } + // If name is absolute path, then it has to be containerfile outside of build context. + // If not, we should check it for being excluded via pattern matcher. + if !filepath.IsAbs(name) { + excluded, err := pm.Matches(name) //nolint:staticcheck + if err != nil { + return fmt.Errorf("checking if %q is excluded: %w", name, err) + } + if excluded { + // Note: filepath.SkipDir is not possible to use given .dockerignore semantics. + // An exception to exclusions may include an excluded directory, therefore we + // are required to visit all files. :( + return nil + } + } + switch { + case dentry.Type().IsRegular(): // add file item + info, err := dentry.Info() + if err != nil { + return err + } + di, isHardLink := checkHardLink(info) + + hdr, err := tar.FileInfoHeader(info, "") + if err != nil { + return err + } + hdr.Uid, hdr.Gid = 0, 0 + orig, ok := seen[di] + if ok { + hdr.Typeflag = tar.TypeLink + hdr.Linkname = orig + hdr.Size = 0 + hdr.Name = name + return tw.WriteHeader(hdr) + } + f, err := os.Open(path) + if err != nil { + return err + } + + hdr.Name = name + if err := tw.WriteHeader(hdr); err != nil { + f.Close() + return err + } + + _, err = io.Copy(tw, f) + f.Close() + if err == nil && isHardLink { + seen[di] = name + } + return err + case dentry.IsDir(): // add folders + info, err := dentry.Info() + if err != nil { + return err + } + hdr, lerr := tar.FileInfoHeader(info, name) + if lerr != nil { + return lerr + } + hdr.Name = name + hdr.Uid, hdr.Gid = 0, 0 + if lerr := tw.WriteHeader(hdr); lerr != nil { + return lerr + } + case dentry.Type()&os.ModeSymlink != 0: // add symlinks as it, not content + link, err := os.Readlink(path) + if err != nil { + return err + } + info, err := dentry.Info() + if err != nil { + return err + } + hdr, lerr := tar.FileInfoHeader(info, link) + if lerr != nil { + return lerr + } + hdr.Name = name + hdr.Uid, hdr.Gid = 0, 0 + if lerr := tw.WriteHeader(hdr); lerr != nil { + return lerr + } + } // skip other than file,folder and symlinks + return nil + }) + merr = multierror.Append(merr, err) + } + }() + rc := ioutils.NewReadCloserWrapper(pr, func() error { + if merr != nil { + merr = multierror.Append(merr, pr.Close()) + return merr.ErrorOrNil() + } + return pr.Close() + }) + return rc, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/build_unix.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/build_unix.go new file mode 100644 index 00000000..968f1232 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/build_unix.go @@ -0,0 +1,16 @@ +//go:build !windows + +package images + +import ( + "os" + "syscall" +) + +func checkHardLink(fi os.FileInfo) (devino, bool) { + st := fi.Sys().(*syscall.Stat_t) + return devino{ + Dev: uint64(st.Dev), //nolint:unconvert,nolintlint + Ino: st.Ino, + }, st.Nlink > 1 +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/build_windows.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/build_windows.go new file mode 100644 index 00000000..b796f4da --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/build_windows.go @@ -0,0 +1,9 @@ +package images + +import ( + "os" +) + +func checkHardLink(_ os.FileInfo) (devino, bool) { + return devino{}, false +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/diff.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/diff.go new file mode 100644 index 00000000..eaa576c6 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/diff.go @@ -0,0 +1,30 @@ +package images + +import ( + "context" + "net/http" + + "github.com/containers/podman/v5/pkg/bindings" + "go.podman.io/storage/pkg/archive" +) + +// Diff provides the changes between two container layers +func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive.Change, error) { + if options == nil { + options = new(DiffOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/changes", nil, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + var changes []archive.Change + return changes, response.Process(&changes) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/images.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/images.go new file mode 100644 index 00000000..40201be0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/images.go @@ -0,0 +1,347 @@ +package images + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + + handlersTypes "github.com/containers/podman/v5/pkg/api/handlers/types" + "github.com/containers/podman/v5/pkg/auth" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/reports" + "github.com/containers/podman/v5/pkg/domain/entities/types" + imageTypes "go.podman.io/image/v5/types" +) + +// Exists a lightweight way to determine if an image exists in local storage. It returns a +// boolean response. +func Exists(ctx context.Context, nameOrID string, _ *ExistsOptions) (bool, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return false, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/exists", nil, nil, nameOrID) + if err != nil { + return false, err + } + defer response.Body.Close() + + return response.IsSuccess(), nil +} + +// List returns a list of images in local storage. The all boolean and filters parameters are optional +// ways to alter the image query. +func List(ctx context.Context, options *ListOptions) ([]*types.ImageSummary, error) { + if options == nil { + options = new(ListOptions) + } + var imageSummary []*types.ImageSummary + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/json", params, nil) + if err != nil { + return imageSummary, err + } + defer response.Body.Close() + + return imageSummary, response.Process(&imageSummary) +} + +// Get performs an image inspect. To have the on-disk size of the image calculated, you can +// use the optional size parameter. +func GetImage(ctx context.Context, nameOrID string, options *GetOptions) (*types.ImageInspectReport, error) { + if options == nil { + options = new(GetOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + inspectedData := types.ImageInspectReport{} + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/json", params, nil, nameOrID) + if err != nil { + return &inspectedData, err + } + defer response.Body.Close() + + return &inspectedData, response.Process(&inspectedData) +} + +// Tree retrieves a "tree" based representation of the given image +func Tree(ctx context.Context, nameOrID string, options *TreeOptions) (*types.ImageTreeReport, error) { + if options == nil { + options = new(TreeOptions) + } + var report types.ImageTreeReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/tree", params, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &report, response.Process(&report) +} + +// History returns the parent layers of an image. +func History(ctx context.Context, nameOrID string, options *HistoryOptions) ([]*handlersTypes.HistoryResponse, error) { + if options == nil { + options = new(HistoryOptions) + } + _ = options + var history []*handlersTypes.HistoryResponse + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/history", nil, nil, nameOrID) + if err != nil { + return history, err + } + defer response.Body.Close() + + return history, response.Process(&history) +} + +func Load(ctx context.Context, r io.Reader) (*types.ImageLoadReport, error) { + var report types.ImageLoadReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, r, http.MethodPost, "/images/load", nil, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &report, response.Process(&report) +} + +func LoadLocal(ctx context.Context, path string) (*types.ImageLoadReport, error) { + var report types.ImageLoadReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + params := url.Values{} + params.Set("path", path) + + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/local/images/load", params, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &report, response.Process(&report) +} + +// Export saves images from local storage as a tarball or image archive. The optional format +// parameter is used to change the format of the output. +func Export(ctx context.Context, nameOrIDs []string, w io.Writer, options *ExportOptions) error { + if options == nil { + options = new(ExportOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params, err := options.ToParams() + if err != nil { + return err + } + for _, ref := range nameOrIDs { + params.Add("references", ref) + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/export", params, nil) + if err != nil { + return err + } + defer response.Body.Close() + + if response.IsSuccess() || response.IsRedirection() { + _, err = io.Copy(w, response.Body) + return err + } + return response.Process(nil) +} + +// Prune removes unused images from local storage. The optional filters can be used to further +// define which images should be pruned. +func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, error) { + var ( + deleted []*reports.PruneReport + ) + if options == nil { + options = new(PruneOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/prune", params, nil) + if err != nil { + return deleted, err + } + defer response.Body.Close() + + return deleted, response.Process(&deleted) +} + +// Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required. +func Tag(ctx context.Context, nameOrID, tag, repo string, options *TagOptions) error { + if options == nil { + options = new(TagOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params := url.Values{} + params.Set("tag", tag) + params.Set("repo", repo) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/%s/tag", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// Untag removes a name from locally-stored image. Both the tag and repo parameters are required. +func Untag(ctx context.Context, nameOrID, tag, repo string, options *UntagOptions) error { + if options == nil { + options = new(UntagOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params := url.Values{} + params.Set("tag", tag) + params.Set("repo", repo) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/%s/untag", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// Import adds the given image to the local image store. This can be done by file and the given reader +// or via the url parameter. Additional metadata can be associated with the image by using the changes and +// message parameters. The image can also be tagged given a reference. One of url OR r must be provided. +func Import(ctx context.Context, r io.Reader, options *ImportOptions) (*types.ImageImportReport, error) { + if options == nil { + options = new(ImportOptions) + } + var report types.ImageImportReport + if r != nil && options.URL != nil { + return nil, errors.New("url and r parameters cannot be used together") + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, r, http.MethodPost, "/images/import", params, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &report, response.Process(&report) +} + +// Search is the binding for libpod's v2 endpoints for Search images. +func Search(ctx context.Context, term string, options *SearchOptions) ([]types.ImageSearchReport, error) { + if options == nil { + options = new(SearchOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + params.Set("term", term) + + // SkipTLSVerify is special. It's not being serialized by ToParams() + // because we need to flip the boolean. + if options.SkipTLSVerify != nil { + params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) + } + + header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/search", params, header) + if err != nil { + return nil, err + } + defer response.Body.Close() + + results := []types.ImageSearchReport{} + if err := response.Process(&results); err != nil { + return nil, err + } + + return results, nil +} + +func Scp(ctx context.Context, source, _ *string, options ScpOptions) (reports.ScpReport, error) { + rep := reports.ScpReport{} + + conn, err := bindings.GetClient(ctx) + if err != nil { + return rep, err + } + params, err := options.ToParams() + if err != nil { + return rep, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, fmt.Sprintf("/images/scp/%s", *source), params, nil) + if err != nil { + return rep, err + } + defer response.Body.Close() + + return rep, response.Process(&rep) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/pull.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/pull.go new file mode 100644 index 00000000..f65f654e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/pull.go @@ -0,0 +1,109 @@ +package images + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "strconv" + + "github.com/containers/podman/v5/pkg/auth" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/containers/podman/v5/pkg/errorhandling" + imgTypes "go.podman.io/image/v5/types" +) + +// Pull is the binding for libpod's v2 endpoints for pulling images. Note that +// `rawImage` must be a reference to a registry (i.e., of docker transport or be +// normalized to one). Other transports are rejected as they do not make sense +// in a remote context. Progress reported on stderr +func Pull(ctx context.Context, rawImage string, options *PullOptions) ([]string, error) { + if options == nil { + options = new(PullOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + params.Set("reference", rawImage) + + // SkipTLSVerify is special. It's not being serialized by ToParams() + // because we need to flip the boolean. + if options.SkipTLSVerify != nil { + params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) + } + + header, err := auth.MakeXRegistryAuthHeader(&imgTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/pull", params, header) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if !response.IsSuccess() { + return nil, response.Process(err) + } + + var writer io.Writer + if options.GetQuiet() { + writer = io.Discard + } else if progressWriter := options.GetProgressWriter(); progressWriter != nil { + writer = progressWriter + } else { + // Historically push writes status to stderr + writer = os.Stderr + } + + dec := json.NewDecoder(response.Body) + var images []string + var pullErrors []error +LOOP: + for { + var report types.ImagePullReport + if err := dec.Decode(&report); err != nil { + if errors.Is(err, io.EOF) { + // end of stream, exit loop + break + } + // Decoder error, it is unlikely that the next call would work again + // so exit here as well, the Decoder can store the error and always + // return the same one for all future calls which then causes a + // infinity loop and memory leak in pullErrors. + // https://github.com/containers/podman/issues/25974 + pullErrors = append(pullErrors, fmt.Errorf("failed to decode message from stream: %w", err)) + break + } + + select { + case <-response.Request.Context().Done(): + break LOOP + default: + // non-blocking select + } + + switch { + case report.Stream != "": + fmt.Fprint(writer, report.Stream) + case report.Error != "": + pullErrors = append(pullErrors, errors.New(report.Error)) + case len(report.Images) > 0: + images = report.Images + case report.ID != "": + default: + return images, fmt.Errorf("failed to parse pull results stream, unexpected input: %v", report) + } + } + return images, errorhandling.JoinErrors(pullErrors) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/push.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/push.go new file mode 100644 index 00000000..292fb8ce --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/push.go @@ -0,0 +1,101 @@ +package images + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "strconv" + + "github.com/containers/podman/v5/pkg/auth" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/types" + imageTypes "go.podman.io/image/v5/types" +) + +// Push is the binding for libpod's endpoints for push images. Note that +// `source` must be a referring to an image in the remote's container storage. +// The destination must be a reference to a registry (i.e., of docker transport +// or be normalized to one). Other transports are rejected as they do not make +// sense in a remote context. +func Push(ctx context.Context, source string, destination string, options *PushOptions) error { + if options == nil { + options = new(PushOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) + if err != nil { + return err + } + + params, err := options.ToParams() + if err != nil { + return err + } + // SkipTLSVerify is special. It's not being serialized by ToParams() + // because we need to flip the boolean. + if options.SkipTLSVerify != nil { + params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) + } + params.Set("destination", destination) + + path := fmt.Sprintf("/images/%s/push", source) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, path, params, header) + if err != nil { + return err + } + defer response.Body.Close() + + if !response.IsSuccess() { + return response.Process(err) + } + + var writer io.Writer + if options.GetQuiet() { + writer = io.Discard + } else if progressWriter := options.GetProgressWriter(); progressWriter != nil { + writer = progressWriter + } else { + // Historically push writes status to stderr + writer = os.Stderr + } + + dec := json.NewDecoder(response.Body) +LOOP: + for { + var report types.ImagePushStream + if err := dec.Decode(&report); err != nil { + if errors.Is(err, io.EOF) { + break + } + return fmt.Errorf("failed to decode message from stream: %w", err) + } + + select { + case <-response.Request.Context().Done(): + break LOOP + default: + // non-blocking select + } + + switch { + case report.Stream != "": + fmt.Fprint(writer, report.Stream) + case report.ManifestDigest != "": + options.ManifestDigest = &report.ManifestDigest + case report.Error != "": + // There can only be one error. + return errors.New(report.Error) + default: + return fmt.Errorf("failed to parse push results stream, unexpected input: %v", report) + } + } + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/rm.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/rm.go new file mode 100644 index 00000000..4fd35c55 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/rm.go @@ -0,0 +1,43 @@ +package images + +import ( + "context" + "net/http" + + handlersTypes "github.com/containers/podman/v5/pkg/api/handlers/types" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/containers/podman/v5/pkg/errorhandling" +) + +// Remove removes one or more images from the local storage. Use optional force option to remove an +// image, even if it's used by containers. +func Remove(ctx context.Context, images []string, options *RemoveOptions) (*types.ImageRemoveReport, []error) { + if options == nil { + options = new(RemoveOptions) + } + var report handlersTypes.LibpodImagesRemoveReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, []error{err} + } + + params, err := options.ToParams() + if err != nil { + return nil, nil + } + for _, image := range images { + params.Add("images", image) + } + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/images/remove", params, nil) + if err != nil { + return nil, []error{err} + } + defer response.Body.Close() + + if err := response.Process(&report); err != nil { + return nil, []error{err} + } + + return &report.ImageRemoveReport, errorhandling.StringsToErrors(report.Errors) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types.go new file mode 100644 index 00000000..0db6d6f0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types.go @@ -0,0 +1,250 @@ +package images + +import ( + "io" + + "github.com/containers/podman/v5/pkg/domain/entities/types" +) + +// RemoveOptions are optional options for image removal +// +//go:generate go run ../generator/generator.go RemoveOptions +type RemoveOptions struct { + // All removes all images + All *bool + // Forces removes all containers based on the image + Force *bool + // Ignore if a specified image does not exist and do not throw an error. + Ignore *bool + // Confirms if given name is a manifest list and removes it, otherwise returns error. + LookupManifest *bool + // Does not remove dangling parent images + NoPrune *bool +} + +// DiffOptions are optional options image diffs +// +//go:generate go run ../generator/generator.go DiffOptions +type DiffOptions struct { + // By the default diff will compare against the parent layer. Change the Parent if you want to compare against something else. + Parent *string + // Change the type the backend should match. This can be set to "all", "container" or "image". + DiffType *string +} + +// ListOptions are optional options for listing images +// +//go:generate go run ../generator/generator.go ListOptions +type ListOptions struct { + // All lists all image in the image store including dangling images + All *bool + // filters that can be used to get a more specific list of images + Filters map[string][]string +} + +// GetOptions are optional options for inspecting an image +// +//go:generate go run ../generator/generator.go GetOptions +type GetOptions struct { + // Size computes the amount of storage the image consumes + Size *bool +} + +// TreeOptions are optional options for a tree-based representation +// of the image +// +//go:generate go run ../generator/generator.go TreeOptions +type TreeOptions struct { + // WhatRequires ... + WhatRequires *bool +} + +// HistoryOptions are optional options image history +// +//go:generate go run ../generator/generator.go HistoryOptions +type HistoryOptions struct { +} + +// LoadOptions are optional options for loading an image +// +//go:generate go run ../generator/generator.go LoadOptions +type LoadOptions struct { + // Reference is the name of the loaded image + Reference *string +} + +// ExportOptions are optional options for exporting images +// +//go:generate go run ../generator/generator.go ExportOptions +type ExportOptions struct { + // Compress the image + Compress *bool + // Format of the output + Format *string + // Accept uncompressed layers when copying OCI images. + OciAcceptUncompressedLayers *bool +} + +// PruneOptions are optional options for pruning images +// +//go:generate go run ../generator/generator.go PruneOptions +type PruneOptions struct { + // Prune all images + All *bool + // Prune images even when they're used by external containers + External *bool + // Prune persistent build cache + BuildCache *bool + // Filters to apply when pruning images + Filters map[string][]string +} + +// TagOptions are optional options for tagging images +// +//go:generate go run ../generator/generator.go TagOptions +type TagOptions struct { +} + +// UntagOptions are optional options for untagging images +// +//go:generate go run ../generator/generator.go UntagOptions +type UntagOptions struct { +} + +// ImportOptions are optional options for importing images +// +//go:generate go run ../generator/generator.go ImportOptions +type ImportOptions struct { + // Changes to be applied to the image + Changes *[]string + // Message to be applied to the image + Message *string + // Reference is a tag to be applied to the image + Reference *string + // Url to option image to import. Cannot be used with the reader + URL *string + // OS for the imported image + OS *string + // Architecture for the imported image + Architecture *string + // Variant for the imported image + Variant *string +} + +// PushOptions are optional options for importing images +// +//go:generate go run ../generator/generator.go PushOptions +type PushOptions struct { + // All indicates whether to push all images related to the image list + All *bool + // Authfile is the path to the authentication file. Ignored for remote + // calls. + Authfile *string + // Compress tarball image layers when pushing to a directory using the 'dir' transport. + Compress *bool + // CompressionFormat is the format to use for the compression of the blobs + CompressionFormat *string + // CompressionLevel is the level to use for the compression of the blobs + CompressionLevel *int + // ForceCompressionFormat ensures that the compression algorithm set in + // CompressionFormat is used exclusively, and blobs of other compression + // algorithms are not reused. + ForceCompressionFormat *bool + // Add existing instances with requested compression algorithms to manifest list + AddCompression []string + // Manifest type of the pushed image + Format *string + // Password for authenticating against the registry. + Password *string `schema:"-"` + // ProgressWriter is a writer where push progress are sent. + // Since API handler for image push is quiet by default, WithQuiet(false) is necessary for + // the writer to receive progress messages. + ProgressWriter *io.Writer `schema:"-"` + // SkipTLSVerify to skip HTTPS and certificate verification. + SkipTLSVerify *bool `schema:"-"` + // RemoveSignatures Discard any pre-existing signatures in the image. + RemoveSignatures *bool + // Retry number of times to retry push in case of failure + Retry *uint + // RetryDelay between retries in case of push failures + RetryDelay *string + // Username for authenticating against the registry. + Username *string `schema:"-"` + // Quiet can be specified to suppress progress when pushing. + Quiet *bool + + // Manifest of the pushed image. Set by images.Push. + ManifestDigest *string +} + +// SearchOptions are optional options for searching images on registries +// +//go:generate go run ../generator/generator.go SearchOptions +type SearchOptions struct { + // Authfile is the path to the authentication file. Ignored for remote + // calls. + Authfile *string + // Filters for the search results. + Filters map[string][]string + // Limit the number of results. + Limit *int + // SkipTLSVerify to skip HTTPS and certificate verification. + SkipTLSVerify *bool `schema:"-"` + // ListTags search the available tags of the repository + ListTags *bool + // Username for authenticating against the registry. + Username *string `schema:"-"` + // Password for authenticating against the registry. + Password *string `schema:"-"` +} + +// PullOptions are optional options for pulling images +// +//go:generate go run ../generator/generator.go PullOptions +type PullOptions struct { + // AllTags can be specified to pull all tags of an image. Note + // that this only works if the image does not include a tag. + AllTags *bool + // Arch will overwrite the local architecture for image pulls. + Arch *string + // Authfile is the path to the authentication file. Ignored for remote + // calls. + Authfile *string + // OS will overwrite the local operating system (OS) for image + // pulls. + OS *string + // Policy is the pull policy. Supported values are "missing", "never", + // "newer", "always". An empty string defaults to "always". + Policy *string + // Password for authenticating against the registry. + Password *string `schema:"-"` + // ProgressWriter is a writer where pull progress are sent. + ProgressWriter *io.Writer `schema:"-"` + // Quiet can be specified to suppress pull progress when pulling. Ignored + // for remote calls. + Quiet *bool + // Retry number of times to retry pull in case of failure + Retry *uint + // RetryDelay between retries in case of pull failures + RetryDelay *string + // SkipTLSVerify to skip HTTPS and certificate verification. + SkipTLSVerify *bool `schema:"-"` + // Username for authenticating against the registry. + Username *string `schema:"-"` + // Variant will overwrite the local variant for image pulls. + Variant *string +} + +// BuildOptions are optional options for building images +type BuildOptions = types.BuildOptions + +// ExistsOptions are optional options for checking if an image exists +// +//go:generate go run ../generator/generator.go ExistsOptions +type ExistsOptions struct { +} + +type ScpOptions struct { + Quiet *bool + Destination *string +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_diff_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_diff_options.go new file mode 100644 index 00000000..18bcf952 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_diff_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *DiffOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *DiffOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithParent set field Parent to given value +func (o *DiffOptions) WithParent(value string) *DiffOptions { + o.Parent = &value + return o +} + +// GetParent returns value of field Parent +func (o *DiffOptions) GetParent() string { + if o.Parent == nil { + var z string + return z + } + return *o.Parent +} + +// WithDiffType set field DiffType to given value +func (o *DiffOptions) WithDiffType(value string) *DiffOptions { + o.DiffType = &value + return o +} + +// GetDiffType returns value of field DiffType +func (o *DiffOptions) GetDiffType() string { + if o.DiffType == nil { + var z string + return z + } + return *o.DiffType +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_exists_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_exists_options.go new file mode 100644 index 00000000..e31138d4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_exists_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExistsOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExistsOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_export_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_export_options.go new file mode 100644 index 00000000..0cc3da8c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_export_options.go @@ -0,0 +1,63 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExportOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExportOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithCompress set field Compress to given value +func (o *ExportOptions) WithCompress(value bool) *ExportOptions { + o.Compress = &value + return o +} + +// GetCompress returns value of field Compress +func (o *ExportOptions) GetCompress() bool { + if o.Compress == nil { + var z bool + return z + } + return *o.Compress +} + +// WithFormat set field Format to given value +func (o *ExportOptions) WithFormat(value string) *ExportOptions { + o.Format = &value + return o +} + +// GetFormat returns value of field Format +func (o *ExportOptions) GetFormat() string { + if o.Format == nil { + var z string + return z + } + return *o.Format +} + +// WithOciAcceptUncompressedLayers set field OciAcceptUncompressedLayers to given value +func (o *ExportOptions) WithOciAcceptUncompressedLayers(value bool) *ExportOptions { + o.OciAcceptUncompressedLayers = &value + return o +} + +// GetOciAcceptUncompressedLayers returns value of field OciAcceptUncompressedLayers +func (o *ExportOptions) GetOciAcceptUncompressedLayers() bool { + if o.OciAcceptUncompressedLayers == nil { + var z bool + return z + } + return *o.OciAcceptUncompressedLayers +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_get_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_get_options.go new file mode 100644 index 00000000..84295a75 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_get_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *GetOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *GetOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithSize set field Size to given value +func (o *GetOptions) WithSize(value bool) *GetOptions { + o.Size = &value + return o +} + +// GetSize returns value of field Size +func (o *GetOptions) GetSize() bool { + if o.Size == nil { + var z bool + return z + } + return *o.Size +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_history_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_history_options.go new file mode 100644 index 00000000..3dc43cc4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_history_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *HistoryOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *HistoryOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_import_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_import_options.go new file mode 100644 index 00000000..aafb8de7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_import_options.go @@ -0,0 +1,123 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ImportOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ImportOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithChanges set field Changes to given value +func (o *ImportOptions) WithChanges(value []string) *ImportOptions { + o.Changes = &value + return o +} + +// GetChanges returns value of field Changes +func (o *ImportOptions) GetChanges() []string { + if o.Changes == nil { + var z []string + return z + } + return *o.Changes +} + +// WithMessage set field Message to given value +func (o *ImportOptions) WithMessage(value string) *ImportOptions { + o.Message = &value + return o +} + +// GetMessage returns value of field Message +func (o *ImportOptions) GetMessage() string { + if o.Message == nil { + var z string + return z + } + return *o.Message +} + +// WithReference set field Reference to given value +func (o *ImportOptions) WithReference(value string) *ImportOptions { + o.Reference = &value + return o +} + +// GetReference returns value of field Reference +func (o *ImportOptions) GetReference() string { + if o.Reference == nil { + var z string + return z + } + return *o.Reference +} + +// WithURL set field URL to given value +func (o *ImportOptions) WithURL(value string) *ImportOptions { + o.URL = &value + return o +} + +// GetURL returns value of field URL +func (o *ImportOptions) GetURL() string { + if o.URL == nil { + var z string + return z + } + return *o.URL +} + +// WithOS set field OS to given value +func (o *ImportOptions) WithOS(value string) *ImportOptions { + o.OS = &value + return o +} + +// GetOS returns value of field OS +func (o *ImportOptions) GetOS() string { + if o.OS == nil { + var z string + return z + } + return *o.OS +} + +// WithArchitecture set field Architecture to given value +func (o *ImportOptions) WithArchitecture(value string) *ImportOptions { + o.Architecture = &value + return o +} + +// GetArchitecture returns value of field Architecture +func (o *ImportOptions) GetArchitecture() string { + if o.Architecture == nil { + var z string + return z + } + return *o.Architecture +} + +// WithVariant set field Variant to given value +func (o *ImportOptions) WithVariant(value string) *ImportOptions { + o.Variant = &value + return o +} + +// GetVariant returns value of field Variant +func (o *ImportOptions) GetVariant() string { + if o.Variant == nil { + var z string + return z + } + return *o.Variant +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_list_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_list_options.go new file mode 100644 index 00000000..4c378e1e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_list_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ListOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ListOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAll set field All to given value +func (o *ListOptions) WithAll(value bool) *ListOptions { + o.All = &value + return o +} + +// GetAll returns value of field All +func (o *ListOptions) GetAll() bool { + if o.All == nil { + var z bool + return z + } + return *o.All +} + +// WithFilters set field Filters to given value +func (o *ListOptions) WithFilters(value map[string][]string) *ListOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *ListOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_load_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_load_options.go new file mode 100644 index 00000000..470e4293 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_load_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *LoadOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *LoadOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithReference set field Reference to given value +func (o *LoadOptions) WithReference(value string) *LoadOptions { + o.Reference = &value + return o +} + +// GetReference returns value of field Reference +func (o *LoadOptions) GetReference() string { + if o.Reference == nil { + var z string + return z + } + return *o.Reference +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_prune_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_prune_options.go new file mode 100644 index 00000000..bbd12a16 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_prune_options.go @@ -0,0 +1,78 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *PruneOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *PruneOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAll set field All to given value +func (o *PruneOptions) WithAll(value bool) *PruneOptions { + o.All = &value + return o +} + +// GetAll returns value of field All +func (o *PruneOptions) GetAll() bool { + if o.All == nil { + var z bool + return z + } + return *o.All +} + +// WithExternal set field External to given value +func (o *PruneOptions) WithExternal(value bool) *PruneOptions { + o.External = &value + return o +} + +// GetExternal returns value of field External +func (o *PruneOptions) GetExternal() bool { + if o.External == nil { + var z bool + return z + } + return *o.External +} + +// WithBuildCache set field BuildCache to given value +func (o *PruneOptions) WithBuildCache(value bool) *PruneOptions { + o.BuildCache = &value + return o +} + +// GetBuildCache returns value of field BuildCache +func (o *PruneOptions) GetBuildCache() bool { + if o.BuildCache == nil { + var z bool + return z + } + return *o.BuildCache +} + +// WithFilters set field Filters to given value +func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *PruneOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_pull_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_pull_options.go new file mode 100644 index 00000000..7a10a5c3 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_pull_options.go @@ -0,0 +1,214 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "io" + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *PullOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *PullOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAllTags set field AllTags to given value +func (o *PullOptions) WithAllTags(value bool) *PullOptions { + o.AllTags = &value + return o +} + +// GetAllTags returns value of field AllTags +func (o *PullOptions) GetAllTags() bool { + if o.AllTags == nil { + var z bool + return z + } + return *o.AllTags +} + +// WithArch set field Arch to given value +func (o *PullOptions) WithArch(value string) *PullOptions { + o.Arch = &value + return o +} + +// GetArch returns value of field Arch +func (o *PullOptions) GetArch() string { + if o.Arch == nil { + var z string + return z + } + return *o.Arch +} + +// WithAuthfile set field Authfile to given value +func (o *PullOptions) WithAuthfile(value string) *PullOptions { + o.Authfile = &value + return o +} + +// GetAuthfile returns value of field Authfile +func (o *PullOptions) GetAuthfile() string { + if o.Authfile == nil { + var z string + return z + } + return *o.Authfile +} + +// WithOS set field OS to given value +func (o *PullOptions) WithOS(value string) *PullOptions { + o.OS = &value + return o +} + +// GetOS returns value of field OS +func (o *PullOptions) GetOS() string { + if o.OS == nil { + var z string + return z + } + return *o.OS +} + +// WithPolicy set field Policy to given value +func (o *PullOptions) WithPolicy(value string) *PullOptions { + o.Policy = &value + return o +} + +// GetPolicy returns value of field Policy +func (o *PullOptions) GetPolicy() string { + if o.Policy == nil { + var z string + return z + } + return *o.Policy +} + +// WithPassword set field Password to given value +func (o *PullOptions) WithPassword(value string) *PullOptions { + o.Password = &value + return o +} + +// GetPassword returns value of field Password +func (o *PullOptions) GetPassword() string { + if o.Password == nil { + var z string + return z + } + return *o.Password +} + +// WithProgressWriter set field ProgressWriter to given value +func (o *PullOptions) WithProgressWriter(value io.Writer) *PullOptions { + o.ProgressWriter = &value + return o +} + +// GetProgressWriter returns value of field ProgressWriter +func (o *PullOptions) GetProgressWriter() io.Writer { + if o.ProgressWriter == nil { + var z io.Writer + return z + } + return *o.ProgressWriter +} + +// WithQuiet set field Quiet to given value +func (o *PullOptions) WithQuiet(value bool) *PullOptions { + o.Quiet = &value + return o +} + +// GetQuiet returns value of field Quiet +func (o *PullOptions) GetQuiet() bool { + if o.Quiet == nil { + var z bool + return z + } + return *o.Quiet +} + +// WithRetry set field Retry to given value +func (o *PullOptions) WithRetry(value uint) *PullOptions { + o.Retry = &value + return o +} + +// GetRetry returns value of field Retry +func (o *PullOptions) GetRetry() uint { + if o.Retry == nil { + var z uint + return z + } + return *o.Retry +} + +// WithRetryDelay set field RetryDelay to given value +func (o *PullOptions) WithRetryDelay(value string) *PullOptions { + o.RetryDelay = &value + return o +} + +// GetRetryDelay returns value of field RetryDelay +func (o *PullOptions) GetRetryDelay() string { + if o.RetryDelay == nil { + var z string + return z + } + return *o.RetryDelay +} + +// WithSkipTLSVerify set field SkipTLSVerify to given value +func (o *PullOptions) WithSkipTLSVerify(value bool) *PullOptions { + o.SkipTLSVerify = &value + return o +} + +// GetSkipTLSVerify returns value of field SkipTLSVerify +func (o *PullOptions) GetSkipTLSVerify() bool { + if o.SkipTLSVerify == nil { + var z bool + return z + } + return *o.SkipTLSVerify +} + +// WithUsername set field Username to given value +func (o *PullOptions) WithUsername(value string) *PullOptions { + o.Username = &value + return o +} + +// GetUsername returns value of field Username +func (o *PullOptions) GetUsername() string { + if o.Username == nil { + var z string + return z + } + return *o.Username +} + +// WithVariant set field Variant to given value +func (o *PullOptions) WithVariant(value string) *PullOptions { + o.Variant = &value + return o +} + +// GetVariant returns value of field Variant +func (o *PullOptions) GetVariant() string { + if o.Variant == nil { + var z string + return z + } + return *o.Variant +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_push_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_push_options.go new file mode 100644 index 00000000..686636cd --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_push_options.go @@ -0,0 +1,274 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "io" + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *PushOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *PushOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAll set field All to given value +func (o *PushOptions) WithAll(value bool) *PushOptions { + o.All = &value + return o +} + +// GetAll returns value of field All +func (o *PushOptions) GetAll() bool { + if o.All == nil { + var z bool + return z + } + return *o.All +} + +// WithAuthfile set field Authfile to given value +func (o *PushOptions) WithAuthfile(value string) *PushOptions { + o.Authfile = &value + return o +} + +// GetAuthfile returns value of field Authfile +func (o *PushOptions) GetAuthfile() string { + if o.Authfile == nil { + var z string + return z + } + return *o.Authfile +} + +// WithCompress set field Compress to given value +func (o *PushOptions) WithCompress(value bool) *PushOptions { + o.Compress = &value + return o +} + +// GetCompress returns value of field Compress +func (o *PushOptions) GetCompress() bool { + if o.Compress == nil { + var z bool + return z + } + return *o.Compress +} + +// WithCompressionFormat set field CompressionFormat to given value +func (o *PushOptions) WithCompressionFormat(value string) *PushOptions { + o.CompressionFormat = &value + return o +} + +// GetCompressionFormat returns value of field CompressionFormat +func (o *PushOptions) GetCompressionFormat() string { + if o.CompressionFormat == nil { + var z string + return z + } + return *o.CompressionFormat +} + +// WithCompressionLevel set field CompressionLevel to given value +func (o *PushOptions) WithCompressionLevel(value int) *PushOptions { + o.CompressionLevel = &value + return o +} + +// GetCompressionLevel returns value of field CompressionLevel +func (o *PushOptions) GetCompressionLevel() int { + if o.CompressionLevel == nil { + var z int + return z + } + return *o.CompressionLevel +} + +// WithForceCompressionFormat set field ForceCompressionFormat to given value +func (o *PushOptions) WithForceCompressionFormat(value bool) *PushOptions { + o.ForceCompressionFormat = &value + return o +} + +// GetForceCompressionFormat returns value of field ForceCompressionFormat +func (o *PushOptions) GetForceCompressionFormat() bool { + if o.ForceCompressionFormat == nil { + var z bool + return z + } + return *o.ForceCompressionFormat +} + +// WithAddCompression set field AddCompression to given value +func (o *PushOptions) WithAddCompression(value []string) *PushOptions { + o.AddCompression = value + return o +} + +// GetAddCompression returns value of field AddCompression +func (o *PushOptions) GetAddCompression() []string { + if o.AddCompression == nil { + var z []string + return z + } + return o.AddCompression +} + +// WithFormat set field Format to given value +func (o *PushOptions) WithFormat(value string) *PushOptions { + o.Format = &value + return o +} + +// GetFormat returns value of field Format +func (o *PushOptions) GetFormat() string { + if o.Format == nil { + var z string + return z + } + return *o.Format +} + +// WithPassword set field Password to given value +func (o *PushOptions) WithPassword(value string) *PushOptions { + o.Password = &value + return o +} + +// GetPassword returns value of field Password +func (o *PushOptions) GetPassword() string { + if o.Password == nil { + var z string + return z + } + return *o.Password +} + +// WithProgressWriter set field ProgressWriter to given value +func (o *PushOptions) WithProgressWriter(value io.Writer) *PushOptions { + o.ProgressWriter = &value + return o +} + +// GetProgressWriter returns value of field ProgressWriter +func (o *PushOptions) GetProgressWriter() io.Writer { + if o.ProgressWriter == nil { + var z io.Writer + return z + } + return *o.ProgressWriter +} + +// WithSkipTLSVerify set field SkipTLSVerify to given value +func (o *PushOptions) WithSkipTLSVerify(value bool) *PushOptions { + o.SkipTLSVerify = &value + return o +} + +// GetSkipTLSVerify returns value of field SkipTLSVerify +func (o *PushOptions) GetSkipTLSVerify() bool { + if o.SkipTLSVerify == nil { + var z bool + return z + } + return *o.SkipTLSVerify +} + +// WithRemoveSignatures set field RemoveSignatures to given value +func (o *PushOptions) WithRemoveSignatures(value bool) *PushOptions { + o.RemoveSignatures = &value + return o +} + +// GetRemoveSignatures returns value of field RemoveSignatures +func (o *PushOptions) GetRemoveSignatures() bool { + if o.RemoveSignatures == nil { + var z bool + return z + } + return *o.RemoveSignatures +} + +// WithRetry set field Retry to given value +func (o *PushOptions) WithRetry(value uint) *PushOptions { + o.Retry = &value + return o +} + +// GetRetry returns value of field Retry +func (o *PushOptions) GetRetry() uint { + if o.Retry == nil { + var z uint + return z + } + return *o.Retry +} + +// WithRetryDelay set field RetryDelay to given value +func (o *PushOptions) WithRetryDelay(value string) *PushOptions { + o.RetryDelay = &value + return o +} + +// GetRetryDelay returns value of field RetryDelay +func (o *PushOptions) GetRetryDelay() string { + if o.RetryDelay == nil { + var z string + return z + } + return *o.RetryDelay +} + +// WithUsername set field Username to given value +func (o *PushOptions) WithUsername(value string) *PushOptions { + o.Username = &value + return o +} + +// GetUsername returns value of field Username +func (o *PushOptions) GetUsername() string { + if o.Username == nil { + var z string + return z + } + return *o.Username +} + +// WithQuiet set field Quiet to given value +func (o *PushOptions) WithQuiet(value bool) *PushOptions { + o.Quiet = &value + return o +} + +// GetQuiet returns value of field Quiet +func (o *PushOptions) GetQuiet() bool { + if o.Quiet == nil { + var z bool + return z + } + return *o.Quiet +} + +// WithManifestDigest set field ManifestDigest to given value +func (o *PushOptions) WithManifestDigest(value string) *PushOptions { + o.ManifestDigest = &value + return o +} + +// GetManifestDigest returns value of field ManifestDigest +func (o *PushOptions) GetManifestDigest() string { + if o.ManifestDigest == nil { + var z string + return z + } + return *o.ManifestDigest +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_remove_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_remove_options.go new file mode 100644 index 00000000..68d24149 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_remove_options.go @@ -0,0 +1,93 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *RemoveOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *RemoveOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAll set field All to given value +func (o *RemoveOptions) WithAll(value bool) *RemoveOptions { + o.All = &value + return o +} + +// GetAll returns value of field All +func (o *RemoveOptions) GetAll() bool { + if o.All == nil { + var z bool + return z + } + return *o.All +} + +// WithForce set field Force to given value +func (o *RemoveOptions) WithForce(value bool) *RemoveOptions { + o.Force = &value + return o +} + +// GetForce returns value of field Force +func (o *RemoveOptions) GetForce() bool { + if o.Force == nil { + var z bool + return z + } + return *o.Force +} + +// WithIgnore set field Ignore to given value +func (o *RemoveOptions) WithIgnore(value bool) *RemoveOptions { + o.Ignore = &value + return o +} + +// GetIgnore returns value of field Ignore +func (o *RemoveOptions) GetIgnore() bool { + if o.Ignore == nil { + var z bool + return z + } + return *o.Ignore +} + +// WithLookupManifest set field LookupManifest to given value +func (o *RemoveOptions) WithLookupManifest(value bool) *RemoveOptions { + o.LookupManifest = &value + return o +} + +// GetLookupManifest returns value of field LookupManifest +func (o *RemoveOptions) GetLookupManifest() bool { + if o.LookupManifest == nil { + var z bool + return z + } + return *o.LookupManifest +} + +// WithNoPrune set field NoPrune to given value +func (o *RemoveOptions) WithNoPrune(value bool) *RemoveOptions { + o.NoPrune = &value + return o +} + +// GetNoPrune returns value of field NoPrune +func (o *RemoveOptions) GetNoPrune() bool { + if o.NoPrune == nil { + var z bool + return z + } + return *o.NoPrune +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_scp_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_scp_options.go new file mode 100644 index 00000000..e92b3dc0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_scp_options.go @@ -0,0 +1,12 @@ +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// ToParams formats struct fields to be passed to API service +func (o *ScpOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_search_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_search_options.go new file mode 100644 index 00000000..7da357ba --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_search_options.go @@ -0,0 +1,123 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *SearchOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *SearchOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAuthfile set field Authfile to given value +func (o *SearchOptions) WithAuthfile(value string) *SearchOptions { + o.Authfile = &value + return o +} + +// GetAuthfile returns value of field Authfile +func (o *SearchOptions) GetAuthfile() string { + if o.Authfile == nil { + var z string + return z + } + return *o.Authfile +} + +// WithFilters set field Filters to given value +func (o *SearchOptions) WithFilters(value map[string][]string) *SearchOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *SearchOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} + +// WithLimit set field Limit to given value +func (o *SearchOptions) WithLimit(value int) *SearchOptions { + o.Limit = &value + return o +} + +// GetLimit returns value of field Limit +func (o *SearchOptions) GetLimit() int { + if o.Limit == nil { + var z int + return z + } + return *o.Limit +} + +// WithSkipTLSVerify set field SkipTLSVerify to given value +func (o *SearchOptions) WithSkipTLSVerify(value bool) *SearchOptions { + o.SkipTLSVerify = &value + return o +} + +// GetSkipTLSVerify returns value of field SkipTLSVerify +func (o *SearchOptions) GetSkipTLSVerify() bool { + if o.SkipTLSVerify == nil { + var z bool + return z + } + return *o.SkipTLSVerify +} + +// WithListTags set field ListTags to given value +func (o *SearchOptions) WithListTags(value bool) *SearchOptions { + o.ListTags = &value + return o +} + +// GetListTags returns value of field ListTags +func (o *SearchOptions) GetListTags() bool { + if o.ListTags == nil { + var z bool + return z + } + return *o.ListTags +} + +// WithUsername set field Username to given value +func (o *SearchOptions) WithUsername(value string) *SearchOptions { + o.Username = &value + return o +} + +// GetUsername returns value of field Username +func (o *SearchOptions) GetUsername() string { + if o.Username == nil { + var z string + return z + } + return *o.Username +} + +// WithPassword set field Password to given value +func (o *SearchOptions) WithPassword(value string) *SearchOptions { + o.Password = &value + return o +} + +// GetPassword returns value of field Password +func (o *SearchOptions) GetPassword() string { + if o.Password == nil { + var z string + return z + } + return *o.Password +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_tag_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_tag_options.go new file mode 100644 index 00000000..39582afd --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_tag_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *TagOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *TagOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_tree_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_tree_options.go new file mode 100644 index 00000000..f87517cb --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_tree_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *TreeOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *TreeOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithWhatRequires set field WhatRequires to given value +func (o *TreeOptions) WithWhatRequires(value bool) *TreeOptions { + o.WhatRequires = &value + return o +} + +// GetWhatRequires returns value of field WhatRequires +func (o *TreeOptions) GetWhatRequires() bool { + if o.WhatRequires == nil { + var z bool + return z + } + return *o.WhatRequires +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_untag_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_untag_options.go new file mode 100644 index 00000000..4fdb689a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/images/types_untag_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package images + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *UntagOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *UntagOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/internal/util/util.go b/vendor/github.com/containers/podman/v5/pkg/bindings/internal/util/util.go new file mode 100644 index 00000000..9271d1df --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/internal/util/util.go @@ -0,0 +1,109 @@ +package util + +import ( + "errors" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + + jsoniter "github.com/json-iterator/go" +) + +func IsSimpleType(f reflect.Value) bool { + if _, ok := f.Interface().(fmt.Stringer); ok { + return true + } + + switch f.Kind() { + case reflect.Bool, reflect.Int, reflect.Int64, reflect.Uint, reflect.Uint64, reflect.String: + return true + } + + return false +} + +func SimpleTypeToParam(f reflect.Value) string { + if s, ok := f.Interface().(fmt.Stringer); ok { + return s.String() + } + + switch f.Kind() { + case reflect.Bool: + return strconv.FormatBool(f.Bool()) + case reflect.Int, reflect.Int64: + // f.Int() is always an int64 + return strconv.FormatInt(f.Int(), 10) + case reflect.Uint, reflect.Uint64: + // f.Uint() is always an uint64 + return strconv.FormatUint(f.Uint(), 10) + case reflect.String: + return f.String() + } + + panic("the input parameter is not a simple type") +} + +func Changed(o any, fieldName string) bool { + r := reflect.ValueOf(o) + value := reflect.Indirect(r).FieldByName(fieldName) + return !value.IsNil() +} + +func ToParams(o any) (url.Values, error) { + params := url.Values{} + if o == nil || reflect.ValueOf(o).IsNil() { + return params, nil + } + json := jsoniter.ConfigCompatibleWithStandardLibrary + s := reflect.ValueOf(o) + if reflect.Ptr == s.Kind() { + s = s.Elem() + } + sType := s.Type() + for i := 0; i < s.NumField(); i++ { + fieldName := sType.Field(i).Name + if !Changed(o, fieldName) { + continue + } + fieldName = strings.ToLower(fieldName) + f := s.Field(i) + if reflect.Ptr == f.Kind() { + f = f.Elem() + } + paramName := fieldName + if pn, ok := sType.Field(i).Tag.Lookup("schema"); ok { + if pn == "-" { + continue + } + paramName = pn + } + switch { + case IsSimpleType(f): + params.Set(paramName, SimpleTypeToParam(f)) + case f.Kind() == reflect.Slice: + for i := 0; i < f.Len(); i++ { + elem := f.Index(i) + if IsSimpleType(elem) { + params.Add(paramName, SimpleTypeToParam(elem)) + } else { + return nil, errors.New("slices must contain only simple types") + } + } + case f.Kind() == reflect.Map: + lowerCaseKeys := make(map[string]any) + iter := f.MapRange() + for iter.Next() { + lowerCaseKeys[iter.Key().Interface().(string)] = iter.Value().Interface() + } + s, err := json.MarshalToString(lowerCaseKeys) + if err != nil { + return nil, err + } + + params.Set(paramName, s) + } + } + return params, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/network.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/network.go new file mode 100644 index 00000000..d563b1fc --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/network.go @@ -0,0 +1,238 @@ +package network + +import ( + "context" + "net/http" + "net/url" + "strings" + + "github.com/containers/podman/v5/pkg/bindings" + entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types" + jsoniter "github.com/json-iterator/go" + "go.podman.io/common/libnetwork/types" +) + +// Create makes a new network configuration +func Create(ctx context.Context, network *types.Network) (types.Network, error) { + return CreateWithOptions(ctx, network, nil) +} + +func CreateWithOptions(ctx context.Context, network *types.Network, extraCreateOptions *ExtraCreateOptions) (types.Network, error) { + var report types.Network + conn, err := bindings.GetClient(ctx) + if err != nil { + return report, err + } + + var params url.Values + if extraCreateOptions != nil { + params, err = extraCreateOptions.ToParams() + if err != nil { + return report, err + } + } + + // create empty network if the caller did not provide one + if network == nil { + network = &types.Network{} + } + networkConfig, err := jsoniter.MarshalToString(*network) + if err != nil { + return report, err + } + reader := strings.NewReader(networkConfig) + response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/networks/create", params, nil) + if err != nil { + return report, err + } + defer response.Body.Close() + + return report, response.Process(&report) +} + +// Updates an existing netavark network config +func Update(ctx context.Context, netNameOrID string, options *UpdateOptions) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + networkConfig, err := jsoniter.MarshalToString(options) + if err != nil { + return err + } + reader := strings.NewReader(networkConfig) + response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/networks/%s/update", nil, nil, netNameOrID) + if err != nil { + return err + } + defer response.Body.Close() + return response.Process(nil) +} + +// Inspect returns information about a network configuration +func Inspect(ctx context.Context, nameOrID string, _ *InspectOptions) (entitiesTypes.NetworkInspectReport, error) { + var net entitiesTypes.NetworkInspectReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return net, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/networks/%s/json", nil, nil, nameOrID) + if err != nil { + return net, err + } + defer response.Body.Close() + + return net, response.Process(&net) +} + +// Remove deletes a defined network configuration by name. The optional force boolean +// will remove all containers associated with the network when set to true. A slice +// of NetworkRemoveReports are returned. +func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) ([]*entitiesTypes.NetworkRmReport, error) { + var reports []*entitiesTypes.NetworkRmReport + if options == nil { + options = new(RemoveOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/networks/%s", params, nil, nameOrID) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return reports, response.Process(&reports) +} + +// List returns a summary of all network configurations +func List(ctx context.Context, options *ListOptions) ([]types.Network, error) { + var netList []types.Network + if options == nil { + options = new(ListOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/networks/json", params, nil) + if err != nil { + return netList, err + } + defer response.Body.Close() + + return netList, response.Process(&netList) +} + +// Disconnect removes a container from a given network +func Disconnect(ctx context.Context, networkName string, containerNameOrID string, options *DisconnectOptions) error { + if options == nil { + options = new(DisconnectOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + // Disconnect sends everything in body + disconnect := struct { + Container string + Force bool + }{ + Container: containerNameOrID, + } + if force := options.GetForce(); options.Changed("Force") { + disconnect.Force = force + } + + body, err := jsoniter.MarshalToString(disconnect) + if err != nil { + return err + } + stringReader := strings.NewReader(body) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/disconnect", nil, nil, networkName) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// Connect adds a container to a network +func Connect(ctx context.Context, networkName string, containerNameOrID string, options *types.PerNetworkOptions) error { + if options == nil { + options = new(types.PerNetworkOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + // Connect sends everything in body + connect := entitiesTypes.NetworkConnectOptions{ + Container: containerNameOrID, + PerNetworkOptions: *options, + } + + body, err := jsoniter.MarshalToString(connect) + if err != nil { + return err + } + stringReader := strings.NewReader(body) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/connect", nil, nil, networkName) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// Exists returns true if a given network exists +func Exists(ctx context.Context, nameOrID string, _ *ExistsOptions) (bool, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return false, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/networks/%s/exists", nil, nil, nameOrID) + if err != nil { + return false, err + } + defer response.Body.Close() + + return response.IsSuccess(), nil +} + +// Prune removes unused networks +func Prune(ctx context.Context, options *PruneOptions) ([]*entitiesTypes.NetworkPruneReport, error) { + if options == nil { + options = new(PruneOptions) + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + var ( + prunedNetworks []*entitiesTypes.NetworkPruneReport + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/networks/prune", params, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return prunedNetworks, response.Process(&prunedNetworks) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types.go new file mode 100644 index 00000000..d99f7841 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types.go @@ -0,0 +1,103 @@ +package network + +import ( + "net" +) + +// CreateOptions are optional options for creating networks +// +//go:generate go run ../generator/generator.go CreateOptions +type CreateOptions struct { + // DisableDNS turns off use of DNSMasq for name resolution + // on the network + DisableDNS *bool + // Driver is the name of network driver + Driver *string + // Gateway of the network + Gateway *net.IP + // Internal turns off communication outside the networking + // being created + Internal *bool + // Labels are metadata that can be associated with the network + Labels map[string]string + // MacVLAN is the name of the macvlan network to associate with + MacVLAN *string + // Range is the CIDR description of leasable IP addresses + IPRange *net.IPNet `scheme:"range"` + // Subnet to use + Subnet *net.IPNet + // IPv6 means the network is ipv6 capable + IPv6 *bool + // Options are a mapping of driver options and values. + Options map[string]string + // Name of the network + Name *string +} + +// InspectOptions are optional options for inspecting networks +// +//go:generate go run ../generator/generator.go InspectOptions +type InspectOptions struct { +} + +// RemoveOptions are optional options for inspecting networks +// +//go:generate go run ../generator/generator.go RemoveOptions +type RemoveOptions struct { + // Force removes the network even if it is being used + Force *bool + Timeout *uint +} + +// ListOptions are optional options for listing networks +// +//go:generate go run ../generator/generator.go ListOptions +type ListOptions struct { + // Filters are applied to the list of networks to be more + // specific on the output + Filters map[string][]string +} + +// NetworkUpdateOptions describes options to update a network +// +//go:generate go run ../generator/generator.go UpdateOptions +type UpdateOptions struct { + AddDNSServers []string `json:"adddnsservers"` + RemoveDNSServers []string `json:"removednsservers"` +} + +// DisconnectOptions are optional options for disconnecting +// containers from a network +// +//go:generate go run ../generator/generator.go DisconnectOptions +type DisconnectOptions struct { + // Force indicates to remove the container from + // the network forcibly + Force *bool +} + +// ExistsOptions are optional options for checking +// if a network exists +// +//go:generate go run ../generator/generator.go ExistsOptions +type ExistsOptions struct { +} + +// PruneOptions are optional options for removing unused +// networks +// +//go:generate go run ../generator/generator.go PruneOptions +type PruneOptions struct { + // Filters are applied to the prune of networks to be more + // specific on choosing + Filters map[string][]string +} + +// ExtraCreateOptions are optional additional configuration flags for creating Networks +// that are not part of the network configuration +// +//go:generate go run ../generator/generator.go ExtraCreateOptions +type ExtraCreateOptions struct { + // IgnoreIfExists if true, do not fail if the network already exists + IgnoreIfExists *bool `schema:"ignoreIfExists"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_create_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_create_options.go new file mode 100644 index 00000000..2bfa13a1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_create_options.go @@ -0,0 +1,184 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net" + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *CreateOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *CreateOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithDisableDNS set field DisableDNS to given value +func (o *CreateOptions) WithDisableDNS(value bool) *CreateOptions { + o.DisableDNS = &value + return o +} + +// GetDisableDNS returns value of field DisableDNS +func (o *CreateOptions) GetDisableDNS() bool { + if o.DisableDNS == nil { + var z bool + return z + } + return *o.DisableDNS +} + +// WithDriver set field Driver to given value +func (o *CreateOptions) WithDriver(value string) *CreateOptions { + o.Driver = &value + return o +} + +// GetDriver returns value of field Driver +func (o *CreateOptions) GetDriver() string { + if o.Driver == nil { + var z string + return z + } + return *o.Driver +} + +// WithGateway set field Gateway to given value +func (o *CreateOptions) WithGateway(value net.IP) *CreateOptions { + o.Gateway = &value + return o +} + +// GetGateway returns value of field Gateway +func (o *CreateOptions) GetGateway() net.IP { + if o.Gateway == nil { + var z net.IP + return z + } + return *o.Gateway +} + +// WithInternal set field Internal to given value +func (o *CreateOptions) WithInternal(value bool) *CreateOptions { + o.Internal = &value + return o +} + +// GetInternal returns value of field Internal +func (o *CreateOptions) GetInternal() bool { + if o.Internal == nil { + var z bool + return z + } + return *o.Internal +} + +// WithLabels set field Labels to given value +func (o *CreateOptions) WithLabels(value map[string]string) *CreateOptions { + o.Labels = value + return o +} + +// GetLabels returns value of field Labels +func (o *CreateOptions) GetLabels() map[string]string { + if o.Labels == nil { + var z map[string]string + return z + } + return o.Labels +} + +// WithMacVLAN set field MacVLAN to given value +func (o *CreateOptions) WithMacVLAN(value string) *CreateOptions { + o.MacVLAN = &value + return o +} + +// GetMacVLAN returns value of field MacVLAN +func (o *CreateOptions) GetMacVLAN() string { + if o.MacVLAN == nil { + var z string + return z + } + return *o.MacVLAN +} + +// WithIPRange set field IPRange to given value +func (o *CreateOptions) WithIPRange(value net.IPNet) *CreateOptions { + o.IPRange = &value + return o +} + +// GetIPRange returns value of field IPRange +func (o *CreateOptions) GetIPRange() net.IPNet { + if o.IPRange == nil { + var z net.IPNet + return z + } + return *o.IPRange +} + +// WithSubnet set field Subnet to given value +func (o *CreateOptions) WithSubnet(value net.IPNet) *CreateOptions { + o.Subnet = &value + return o +} + +// GetSubnet returns value of field Subnet +func (o *CreateOptions) GetSubnet() net.IPNet { + if o.Subnet == nil { + var z net.IPNet + return z + } + return *o.Subnet +} + +// WithIPv6 set field IPv6 to given value +func (o *CreateOptions) WithIPv6(value bool) *CreateOptions { + o.IPv6 = &value + return o +} + +// GetIPv6 returns value of field IPv6 +func (o *CreateOptions) GetIPv6() bool { + if o.IPv6 == nil { + var z bool + return z + } + return *o.IPv6 +} + +// WithOptions set field Options to given value +func (o *CreateOptions) WithOptions(value map[string]string) *CreateOptions { + o.Options = value + return o +} + +// GetOptions returns value of field Options +func (o *CreateOptions) GetOptions() map[string]string { + if o.Options == nil { + var z map[string]string + return z + } + return o.Options +} + +// WithName set field Name to given value +func (o *CreateOptions) WithName(value string) *CreateOptions { + o.Name = &value + return o +} + +// GetName returns value of field Name +func (o *CreateOptions) GetName() string { + if o.Name == nil { + var z string + return z + } + return *o.Name +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_disconnect_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_disconnect_options.go new file mode 100644 index 00000000..987138f5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_disconnect_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *DisconnectOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *DisconnectOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithForce set field Force to given value +func (o *DisconnectOptions) WithForce(value bool) *DisconnectOptions { + o.Force = &value + return o +} + +// GetForce returns value of field Force +func (o *DisconnectOptions) GetForce() bool { + if o.Force == nil { + var z bool + return z + } + return *o.Force +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_exists_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_exists_options.go new file mode 100644 index 00000000..72c936a6 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_exists_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExistsOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExistsOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_extracreate_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_extracreate_options.go new file mode 100644 index 00000000..6f74842c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_extracreate_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExtraCreateOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExtraCreateOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithIgnoreIfExists set field IgnoreIfExists to given value +func (o *ExtraCreateOptions) WithIgnoreIfExists(value bool) *ExtraCreateOptions { + o.IgnoreIfExists = &value + return o +} + +// GetIgnoreIfExists returns value of field IgnoreIfExists +func (o *ExtraCreateOptions) GetIgnoreIfExists() bool { + if o.IgnoreIfExists == nil { + var z bool + return z + } + return *o.IgnoreIfExists +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_inspect_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_inspect_options.go new file mode 100644 index 00000000..d9cc782b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_inspect_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *InspectOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *InspectOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_list_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_list_options.go new file mode 100644 index 00000000..b1f214cd --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_list_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ListOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ListOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithFilters set field Filters to given value +func (o *ListOptions) WithFilters(value map[string][]string) *ListOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *ListOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_prune_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_prune_options.go new file mode 100644 index 00000000..6036648e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_prune_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *PruneOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *PruneOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithFilters set field Filters to given value +func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *PruneOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_remove_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_remove_options.go new file mode 100644 index 00000000..426fdb1c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_remove_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *RemoveOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *RemoveOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithForce set field Force to given value +func (o *RemoveOptions) WithForce(value bool) *RemoveOptions { + o.Force = &value + return o +} + +// GetForce returns value of field Force +func (o *RemoveOptions) GetForce() bool { + if o.Force == nil { + var z bool + return z + } + return *o.Force +} + +// WithTimeout set field Timeout to given value +func (o *RemoveOptions) WithTimeout(value uint) *RemoveOptions { + o.Timeout = &value + return o +} + +// GetTimeout returns value of field Timeout +func (o *RemoveOptions) GetTimeout() uint { + if o.Timeout == nil { + var z uint + return z + } + return *o.Timeout +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_update_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_update_options.go new file mode 100644 index 00000000..424d0671 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/network/types_update_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *UpdateOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *UpdateOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAddDNSServers set field AddDNSServers to given value +func (o *UpdateOptions) WithAddDNSServers(value []string) *UpdateOptions { + o.AddDNSServers = value + return o +} + +// GetAddDNSServers returns value of field AddDNSServers +func (o *UpdateOptions) GetAddDNSServers() []string { + if o.AddDNSServers == nil { + var z []string + return z + } + return o.AddDNSServers +} + +// WithRemoveDNSServers set field RemoveDNSServers to given value +func (o *UpdateOptions) WithRemoveDNSServers(value []string) *UpdateOptions { + o.RemoveDNSServers = value + return o +} + +// GetRemoveDNSServers returns value of field RemoveDNSServers +func (o *UpdateOptions) GetRemoveDNSServers() []string { + if o.RemoveDNSServers == nil { + var z []string + return z + } + return o.RemoveDNSServers +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/system/info.go b/vendor/github.com/containers/podman/v5/pkg/bindings/system/info.go new file mode 100644 index 00000000..bf704813 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/system/info.go @@ -0,0 +1,25 @@ +package system + +import ( + "context" + "net/http" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/bindings" +) + +// Info returns information about the libpod environment and its stores +func Info(ctx context.Context, _ *InfoOptions) (*define.Info, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/info", nil, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + info := define.Info{} + return &info, response.Process(&info) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/system/system.go b/vendor/github.com/containers/podman/v5/pkg/bindings/system/system.go new file mode 100644 index 00000000..b2eacd4a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/system/system.go @@ -0,0 +1,172 @@ +package system + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/sirupsen/logrus" +) + +// Events allows you to monitor libdpod related events like container creation and +// removal. The events are then passed to the eventChan provided. The optional cancelChan +// can be used to cancel the read of events and close down the HTTP connection. +func Events(ctx context.Context, eventChan chan types.Event, cancelChan chan bool, options *EventsOptions) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params, err := options.ToParams() + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/events", params, nil) + if err != nil { + return err + } + + if cancelChan != nil { + go func() { + <-cancelChan + if err := response.Body.Close(); err != nil { + logrus.Errorf("Unable to close event response body: %v", err) + } + }() + } + + if response.StatusCode != http.StatusOK { + defer response.Body.Close() + return response.Process(nil) + } + + go func() { + defer response.Body.Close() + defer close(eventChan) + dec := json.NewDecoder(response.Body) + for err = (error)(nil); err == nil; { + var e = types.Event{} + err = dec.Decode(&e) + if err == nil { + eventChan <- e + } + } + }() + return nil +} + +// Prune removes all unused system data. +func Prune(ctx context.Context, options *PruneOptions) (*types.SystemPruneReport, error) { + var ( + report types.SystemPruneReport + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/system/prune", params, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &report, response.Process(&report) +} + +func Check(ctx context.Context, options *CheckOptions) (*types.SystemCheckReport, error) { + var report types.SystemCheckReport + + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/system/check", params, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &report, response.Process(&report) +} + +func Version(ctx context.Context, options *VersionOptions) (*types.SystemVersionReport, error) { + var ( + component types.SystemComponentVersion + report types.SystemVersionReport + ) + if options == nil { + options = new(VersionOptions) + } + _ = options + version, err := define.GetVersion() + if err != nil { + return nil, err + } + report.Client = &version + + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/version", nil, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if err = response.Process(&component); err != nil { + return nil, err + } + + b, _ := time.Parse(time.RFC3339, component.BuildTime) + report.Server = &define.Version{ + APIVersion: component.APIVersion, + Version: component.Version.Version, + GoVersion: component.GoVersion, + GitCommit: component.GitCommit, + BuiltTime: time.Unix(b.Unix(), 0).Format(time.ANSIC), + Built: b.Unix(), + OsArch: fmt.Sprintf("%s/%s", component.Os, component.Arch), + Os: component.Os, + } + + for _, c := range component.Components { + if c.Name == "Podman Engine" { + report.Server.APIVersion = c.Details["APIVersion"] + } + } + return &report, err +} + +// DiskUsage returns information about image, container, and volume disk +// consumption +func DiskUsage(ctx context.Context, options *DiskOptions) (*types.SystemDfReport, error) { + var report types.SystemDfReport + if options == nil { + options = new(DiskOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/system/df", nil, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &report, response.Process(&report) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/system/types.go b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types.go new file mode 100644 index 00000000..a6611497 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types.go @@ -0,0 +1,51 @@ +package system + +// EventsOptions are optional options for monitoring events +// +//go:generate go run ../generator/generator.go EventsOptions +type EventsOptions struct { + Filters map[string][]string + Since *string + Stream *bool + Until *string +} + +// PruneOptions are optional options for pruning +// +//go:generate go run ../generator/generator.go PruneOptions +type PruneOptions struct { + All *bool + Filters map[string][]string + Volumes *bool + External *bool + Build *bool +} + +// VersionOptions are optional options for getting version info +// +//go:generate go run ../generator/generator.go VersionOptions +type VersionOptions struct { +} + +// DiskOptions are optional options for getting storage consumption +// +//go:generate go run ../generator/generator.go DiskOptions +type DiskOptions struct { +} + +// InfoOptions are optional options for getting info +// about libpod +// +//go:generate go run ../generator/generator.go InfoOptions +type InfoOptions struct { +} + +// CheckOptions are optional options for storage consistency check/repair +// +//go:generate go run ../generator/generator.go CheckOptions +type CheckOptions struct { + Quick *bool `schema:"quick"` + Repair *bool `schema:"repair"` + RepairLossy *bool `schema:"repair_lossy"` + UnreferencedLayerMaximumAge *string `schema:"unreferenced_layer_max_age"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_check_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_check_options.go new file mode 100644 index 00000000..374f142d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_check_options.go @@ -0,0 +1,78 @@ +// Code generated by go generate; DO NOT EDIT. +package system + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *CheckOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *CheckOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithQuick set field Quick to given value +func (o *CheckOptions) WithQuick(value bool) *CheckOptions { + o.Quick = &value + return o +} + +// GetQuick returns value of field Quick +func (o *CheckOptions) GetQuick() bool { + if o.Quick == nil { + var z bool + return z + } + return *o.Quick +} + +// WithRepair set field Repair to given value +func (o *CheckOptions) WithRepair(value bool) *CheckOptions { + o.Repair = &value + return o +} + +// GetRepair returns value of field Repair +func (o *CheckOptions) GetRepair() bool { + if o.Repair == nil { + var z bool + return z + } + return *o.Repair +} + +// WithRepairLossy set field RepairLossy to given value +func (o *CheckOptions) WithRepairLossy(value bool) *CheckOptions { + o.RepairLossy = &value + return o +} + +// GetRepairLossy returns value of field RepairLossy +func (o *CheckOptions) GetRepairLossy() bool { + if o.RepairLossy == nil { + var z bool + return z + } + return *o.RepairLossy +} + +// WithUnreferencedLayerMaximumAge set field UnreferencedLayerMaximumAge to given value +func (o *CheckOptions) WithUnreferencedLayerMaximumAge(value string) *CheckOptions { + o.UnreferencedLayerMaximumAge = &value + return o +} + +// GetUnreferencedLayerMaximumAge returns value of field UnreferencedLayerMaximumAge +func (o *CheckOptions) GetUnreferencedLayerMaximumAge() string { + if o.UnreferencedLayerMaximumAge == nil { + var z string + return z + } + return *o.UnreferencedLayerMaximumAge +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_disk_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_disk_options.go new file mode 100644 index 00000000..0acb3c55 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_disk_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package system + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *DiskOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *DiskOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_events_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_events_options.go new file mode 100644 index 00000000..3a8920cf --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_events_options.go @@ -0,0 +1,78 @@ +// Code generated by go generate; DO NOT EDIT. +package system + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *EventsOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *EventsOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithFilters set field Filters to given value +func (o *EventsOptions) WithFilters(value map[string][]string) *EventsOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *EventsOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} + +// WithSince set field Since to given value +func (o *EventsOptions) WithSince(value string) *EventsOptions { + o.Since = &value + return o +} + +// GetSince returns value of field Since +func (o *EventsOptions) GetSince() string { + if o.Since == nil { + var z string + return z + } + return *o.Since +} + +// WithStream set field Stream to given value +func (o *EventsOptions) WithStream(value bool) *EventsOptions { + o.Stream = &value + return o +} + +// GetStream returns value of field Stream +func (o *EventsOptions) GetStream() bool { + if o.Stream == nil { + var z bool + return z + } + return *o.Stream +} + +// WithUntil set field Until to given value +func (o *EventsOptions) WithUntil(value string) *EventsOptions { + o.Until = &value + return o +} + +// GetUntil returns value of field Until +func (o *EventsOptions) GetUntil() string { + if o.Until == nil { + var z string + return z + } + return *o.Until +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_info_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_info_options.go new file mode 100644 index 00000000..dce750c4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_info_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package system + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *InfoOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *InfoOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_prune_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_prune_options.go new file mode 100644 index 00000000..4e39c0b1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_prune_options.go @@ -0,0 +1,93 @@ +// Code generated by go generate; DO NOT EDIT. +package system + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *PruneOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *PruneOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAll set field All to given value +func (o *PruneOptions) WithAll(value bool) *PruneOptions { + o.All = &value + return o +} + +// GetAll returns value of field All +func (o *PruneOptions) GetAll() bool { + if o.All == nil { + var z bool + return z + } + return *o.All +} + +// WithFilters set field Filters to given value +func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *PruneOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} + +// WithVolumes set field Volumes to given value +func (o *PruneOptions) WithVolumes(value bool) *PruneOptions { + o.Volumes = &value + return o +} + +// GetVolumes returns value of field Volumes +func (o *PruneOptions) GetVolumes() bool { + if o.Volumes == nil { + var z bool + return z + } + return *o.Volumes +} + +// WithExternal set field External to given value +func (o *PruneOptions) WithExternal(value bool) *PruneOptions { + o.External = &value + return o +} + +// GetExternal returns value of field External +func (o *PruneOptions) GetExternal() bool { + if o.External == nil { + var z bool + return z + } + return *o.External +} + +// WithBuild set field Build to given value +func (o *PruneOptions) WithBuild(value bool) *PruneOptions { + o.Build = &value + return o +} + +// GetBuild returns value of field Build +func (o *PruneOptions) GetBuild() bool { + if o.Build == nil { + var z bool + return z + } + return *o.Build +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_version_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_version_options.go new file mode 100644 index 00000000..d13f2439 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/system/types_version_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package system + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *VersionOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *VersionOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types.go b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types.go new file mode 100644 index 00000000..791fc124 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types.go @@ -0,0 +1,45 @@ +package volumes + +// CreateOptions are optional options for creating volumes +// +//go:generate go run ../generator/generator.go CreateOptions +type CreateOptions struct { +} + +// InspectOptions are optional options for inspecting volumes +// +//go:generate go run ../generator/generator.go InspectOptions +type InspectOptions struct { +} + +// ListOptions are optional options for listing volumes +// +//go:generate go run ../generator/generator.go ListOptions +type ListOptions struct { + // Filters applied to the listing of volumes + Filters map[string][]string +} + +// PruneOptions are optional options for pruning volumes +// +//go:generate go run ../generator/generator.go PruneOptions +type PruneOptions struct { + // Filters applied to the pruning of volumes + Filters map[string][]string +} + +// RemoveOptions are optional options for removing volumes +// +//go:generate go run ../generator/generator.go RemoveOptions +type RemoveOptions struct { + // Force removes the volume even if it is being used + Force *bool + Timeout *uint +} + +// ExistsOptions are optional options for checking +// if a volume exists +// +//go:generate go run ../generator/generator.go ExistsOptions +type ExistsOptions struct { +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_create_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_create_options.go new file mode 100644 index 00000000..98ac9d74 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_create_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package volumes + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *CreateOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *CreateOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_exists_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_exists_options.go new file mode 100644 index 00000000..70f5f5cb --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_exists_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package volumes + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ExistsOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ExistsOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_inspect_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_inspect_options.go new file mode 100644 index 00000000..913faa5e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_inspect_options.go @@ -0,0 +1,18 @@ +// Code generated by go generate; DO NOT EDIT. +package volumes + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *InspectOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *InspectOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_list_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_list_options.go new file mode 100644 index 00000000..2db1d37a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_list_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package volumes + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *ListOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *ListOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithFilters set field Filters to given value +func (o *ListOptions) WithFilters(value map[string][]string) *ListOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *ListOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_prune_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_prune_options.go new file mode 100644 index 00000000..ad4346a8 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_prune_options.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +package volumes + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *PruneOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *PruneOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithFilters set field Filters to given value +func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions { + o.Filters = value + return o +} + +// GetFilters returns value of field Filters +func (o *PruneOptions) GetFilters() map[string][]string { + if o.Filters == nil { + var z map[string][]string + return z + } + return o.Filters +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_remove_options.go b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_remove_options.go new file mode 100644 index 00000000..577633e2 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/types_remove_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package volumes + +import ( + "net/url" + + "github.com/containers/podman/v5/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *RemoveOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *RemoveOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithForce set field Force to given value +func (o *RemoveOptions) WithForce(value bool) *RemoveOptions { + o.Force = &value + return o +} + +// GetForce returns value of field Force +func (o *RemoveOptions) GetForce() bool { + if o.Force == nil { + var z bool + return z + } + return *o.Force +} + +// WithTimeout set field Timeout to given value +func (o *RemoveOptions) WithTimeout(value uint) *RemoveOptions { + o.Timeout = &value + return o +} + +// GetTimeout returns value of field Timeout +func (o *RemoveOptions) GetTimeout() uint { + if o.Timeout == nil { + var z uint + return z + } + return *o.Timeout +} diff --git a/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/volumes.go b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/volumes.go new file mode 100644 index 00000000..bc82b09c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/bindings/volumes/volumes.go @@ -0,0 +1,179 @@ +package volumes + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + + "github.com/containers/podman/v5/pkg/bindings" + "github.com/containers/podman/v5/pkg/domain/entities/reports" + entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types" + jsoniter "github.com/json-iterator/go" +) + +// Create creates a volume given its configuration. +func Create(ctx context.Context, config entitiesTypes.VolumeCreateOptions, options *CreateOptions) (*entitiesTypes.VolumeConfigResponse, error) { + var ( + v entitiesTypes.VolumeConfigResponse + ) + if options == nil { + options = new(CreateOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + createString, err := jsoniter.MarshalToString(config) + if err != nil { + return nil, err + } + stringReader := strings.NewReader(createString) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/volumes/create", nil, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return &v, response.Process(&v) +} + +// Inspect returns low-level information about a volume. +func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*entitiesTypes.VolumeConfigResponse, error) { + var ( + inspect entitiesTypes.VolumeConfigResponse + ) + if options == nil { + options = new(InspectOptions) + } + _ = options + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/%s/json", nil, nil, nameOrID) + if err != nil { + return &inspect, err + } + defer response.Body.Close() + + return &inspect, response.Process(&inspect) +} + +// List returns the configurations for existing volumes in the form of a slice. Optionally, filters +// can be used to refine the list of volumes. +func List(ctx context.Context, options *ListOptions) ([]*entitiesTypes.VolumeListReport, error) { + var ( + vols []*entitiesTypes.VolumeListReport + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/json", params, nil) + if err != nil { + return vols, err + } + defer response.Body.Close() + + return vols, response.Process(&vols) +} + +// Prune removes unused volumes from the local filesystem. +func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, error) { + var ( + pruned []*reports.PruneReport + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params, err := options.ToParams() + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/volumes/prune", params, nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return pruned, response.Process(&pruned) +} + +// Remove deletes the given volume from storage. The optional force parameter +// is used to remove a volume even if it is being used by a container. +func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params, err := options.ToParams() + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/volumes/%s", params, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} + +// Exists returns true if a given volume exists +func Exists(ctx context.Context, nameOrID string, _ *ExistsOptions) (bool, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return false, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/%s/exists", nil, nil, nameOrID) + if err != nil { + return false, err + } + defer response.Body.Close() + + return response.IsSuccess(), nil +} + +// Export exports a volume to the given path +func Export(ctx context.Context, nameOrID string, exportTo io.Writer) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/%s/export", nil, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + if response.IsSuccess() || response.IsRedirection() { + if _, err := io.Copy(exportTo, response.Body); err != nil { + return fmt.Errorf("writing volume %s contents to file: %w", nameOrID, err) + } + } + return response.Process(nil) +} + +// Import imports the given tar into the given volume +func Import(ctx context.Context, nameOrID string, importFrom io.Reader) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + + response, err := conn.DoRequest(ctx, importFrom, http.MethodPost, "/volumes/%s/import", nil, nil, nameOrID) + if err != nil { + return err + } + defer response.Body.Close() + + return response.Process(nil) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/checkpoint/crutils/checkpoint_restore_utils.go b/vendor/github.com/containers/podman/v5/pkg/checkpoint/crutils/checkpoint_restore_utils.go new file mode 100644 index 00000000..1709429b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/checkpoint/crutils/checkpoint_restore_utils.go @@ -0,0 +1,267 @@ +package crutils + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + + metadata "github.com/checkpoint-restore/checkpointctl/lib" + "github.com/checkpoint-restore/go-criu/v7/stats" + "github.com/opencontainers/selinux/go-selinux/label" + "go.podman.io/storage/pkg/archive" +) + +// This file mainly exists to make the checkpoint/restore functions +// available for other users. One possible candidate would be CRI-O. + +// CRImportCheckpointWithoutConfig imports the checkpoint archive (input) +// into the directory destination without "config.dump" and "spec.dump" +func CRImportCheckpointWithoutConfig(destination, input string) error { + archiveFile, err := os.Open(input) + if err != nil { + return fmt.Errorf("failed to open checkpoint archive %s for import: %w", input, err) + } + + defer archiveFile.Close() + options := &archive.TarOptions{ + ExcludePatterns: []string{ + // Import everything else besides the container config + metadata.ConfigDumpFile, + metadata.SpecDumpFile, + }, + } + if err = archive.Untar(archiveFile, destination, options); err != nil { + return fmt.Errorf("unpacking of checkpoint archive %s failed: %w", input, err) + } + + return nil +} + +// CRImportCheckpointConfigOnly only imports the checkpoint configuration +// from the checkpoint archive (input) into the directory destination. +// Only the files "config.dump" and "spec.dump" are extracted. +func CRImportCheckpointConfigOnly(destination, input string) error { + archiveFile, err := os.Open(input) + if err != nil { + return fmt.Errorf("failed to open checkpoint archive %s for import: %w", input, err) + } + + defer archiveFile.Close() + options := &archive.TarOptions{ + // Here we only need the files config.dump and spec.dump + ExcludePatterns: []string{ + "ctr.log", + "artifacts", + stats.StatsDump, + metadata.RootFsDiffTar, + metadata.DeletedFilesFile, + metadata.NetworkStatusFile, + metadata.CheckpointDirectory, + metadata.CheckpointVolumesDirectory, + }, + } + if err = archive.Untar(archiveFile, destination, options); err != nil { + return fmt.Errorf("unpacking of checkpoint archive %s failed: %w", input, err) + } + + return nil +} + +// CRRemoveDeletedFiles loads the list of deleted files and if +// it exists deletes all files listed. +func CRRemoveDeletedFiles(id, baseDirectory, containerRootDirectory string) error { + deletedFiles, _, err := metadata.ReadContainerCheckpointDeletedFiles(baseDirectory) + if os.IsNotExist(err) { + // No files to delete. Just return + return nil + } + + if err != nil { + return fmt.Errorf("failed to read deleted files file: %w", err) + } + + for _, deleteFile := range deletedFiles { + // Using RemoveAll as deletedFiles, which is generated from 'podman diff' + // lists completely deleted directories as a single entry: 'D /root'. + if err := os.RemoveAll(filepath.Join(containerRootDirectory, deleteFile)); err != nil { + return fmt.Errorf("failed to delete files from container %s during restore: %w", id, err) + } + } + + return nil +} + +// CRApplyRootFsDiffTar applies the tar archive found in baseDirectory with the +// root file system changes on top of containerRootDirectory +func CRApplyRootFsDiffTar(baseDirectory, containerRootDirectory string) error { + rootfsDiffPath := filepath.Join(baseDirectory, metadata.RootFsDiffTar) + // Only do this if a rootfs-diff.tar actually exists + rootfsDiffFile, err := os.Open(rootfsDiffPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return fmt.Errorf("failed to open root file-system diff file: %w", err) + } + defer rootfsDiffFile.Close() + + if err := archive.Untar(rootfsDiffFile, containerRootDirectory, nil); err != nil { + return fmt.Errorf("failed to apply root file-system diff file %s: %w", rootfsDiffPath, err) + } + + return nil +} + +// CRCreateRootFsDiffTar goes through the 'changes' and can create two files: +// * metadata.RootFsDiffTar will contain all new and changed files +// * metadata.DeletedFilesFile will contain a list of deleted files +// With these two files it is possible to restore the container file system to the same +// state it was during checkpointing. +// Changes to directories (owner, mode) are not handled. +func CRCreateRootFsDiffTar(changes *[]archive.Change, mountPoint, destination string) (includeFiles []string, err error) { + if len(*changes) == 0 { + return includeFiles, nil + } + + var rootfsIncludeFiles []string + var deletedFiles []string + + rootfsDiffPath := filepath.Join(destination, metadata.RootFsDiffTar) + + for _, file := range *changes { + if file.Kind == archive.ChangeAdd { + rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path) + continue + } + if file.Kind == archive.ChangeDelete { + deletedFiles = append(deletedFiles, file.Path) + continue + } + fileName, err := os.Stat(file.Path) + if err != nil { + continue + } + if !fileName.IsDir() && file.Kind == archive.ChangeModify { + rootfsIncludeFiles = append(rootfsIncludeFiles, file.Path) + continue + } + } + + if len(rootfsIncludeFiles) > 0 { + rootfsTar, err := archive.TarWithOptions(mountPoint, &archive.TarOptions{ + Compression: archive.Uncompressed, + IncludeSourceDir: true, + IncludeFiles: rootfsIncludeFiles, + }) + if err != nil { + return includeFiles, fmt.Errorf("exporting root file-system diff to %q: %w", rootfsDiffPath, err) + } + rootfsDiffFile, err := os.Create(rootfsDiffPath) + if err != nil { + return includeFiles, fmt.Errorf("creating root file-system diff file %q: %w", rootfsDiffPath, err) + } + defer rootfsDiffFile.Close() + if _, err = io.Copy(rootfsDiffFile, rootfsTar); err != nil { + return includeFiles, err + } + + includeFiles = append(includeFiles, metadata.RootFsDiffTar) + } + + if len(deletedFiles) == 0 { + return includeFiles, nil + } + + if _, err := metadata.WriteJSONFile(deletedFiles, destination, metadata.DeletedFilesFile); err != nil { + return includeFiles, nil + } + + includeFiles = append(includeFiles, metadata.DeletedFilesFile) + + return includeFiles, nil +} + +// CRCreateFileWithLabel creates an empty file and sets the corresponding ('fileLabel') +// SELinux label on the file. +// This is necessary for CRIU log files because CRIU infects the processes in +// the container with a 'parasite' and this will also try to write to the log files +// from the context of the container processes. +func CRCreateFileWithLabel(directory, fileName, fileLabel string) error { + logFileName := filepath.Join(directory, fileName) + + logFile, err := os.OpenFile(logFileName, os.O_CREATE, 0o600) + if err != nil { + return fmt.Errorf("failed to create file %q: %w", logFileName, err) + } + defer logFile.Close() + if err = label.SetFileLabel(logFileName, fileLabel); err != nil { + return fmt.Errorf("failed to label file %q: %w", logFileName, err) + } + + return nil +} + +// CRRuntimeSupportsCheckpointRestore tests if the given runtime at 'runtimePath' +// supports checkpointing. The checkpoint restore interface has no definition +// but crun implements all commands just as runc does. What runc does is the +// official definition of the checkpoint/restore interface. +func CRRuntimeSupportsCheckpointRestore(runtimePath string) bool { + // Check if the runtime implements checkpointing. Currently only + // runc's and crun's checkpoint/restore implementation is supported. + cmd := exec.Command(runtimePath, "checkpoint", "--help") + if err := cmd.Start(); err != nil { + return false + } + if err := cmd.Wait(); err == nil { + return true + } + return false +} + +// CRRuntimeSupportsPodCheckpointRestore tests if the runtime at 'runtimePath' +// supports restoring into existing Pods. The runtime needs to support +// the CRIU option --lsm-mount-context and the existence of this is checked +// by this function. In addition it is necessary to at least have CRIU 3.16. +func CRRuntimeSupportsPodCheckpointRestore(runtimePath string) bool { + cmd := exec.Command(runtimePath, "restore", "--lsm-mount-context") + out, _ := cmd.CombinedOutput() + + // check for runc + if bytes.Contains(out, []byte("flag needs an argument")) { + return true + } + + // check for crun + if bytes.Contains(out, []byte("requires an argument")) { + return true + } + + return false +} + +// CRGetRuntimeFromArchive extracts the checkpoint metadata from the +// given checkpoint archive and returns the runtime used to create +// the given checkpoint archive. +func CRGetRuntimeFromArchive(input string) (*string, error) { + dir, err := os.MkdirTemp("", "checkpoint") + if err != nil { + return nil, err + } + defer os.RemoveAll(dir) + + if err := CRImportCheckpointConfigOnly(dir, input); err != nil { + return nil, err + } + + // Load config.dump from temporary directory + ctrConfig := new(metadata.ContainerConfig) + if _, err = metadata.ReadJSONFile(ctrConfig, dir, metadata.ConfigDumpFile); err != nil { + return nil, err + } + + return &ctrConfig.OCIRuntime, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/copy/fileinfo.go b/vendor/github.com/containers/podman/v5/pkg/copy/fileinfo.go new file mode 100644 index 00000000..01a9dae7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/copy/fileinfo.go @@ -0,0 +1,110 @@ +package copy + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/containers/podman/v5/libpod/define" +) + +// XDockerContainerPathStatHeader is the *key* in http headers pointing to the +// base64 encoded JSON payload of stating a path in a container. +const XDockerContainerPathStatHeader = "X-Docker-Container-Path-Stat" + +// ErrENOENT mimics the stdlib's ErrENOENT and can be used to implement custom logic +// while preserving the user-visible error message. +var ErrENOENT = errors.New("no such file or directory") + +// FileInfo describes a file or directory and is returned by +// (*CopyItem).Stat(). +type FileInfo = define.FileInfo + +// EncodeFileInfo serializes the specified FileInfo as a base64 encoded JSON +// payload. Intended for Docker compat. +func EncodeFileInfo(info *FileInfo) (string, error) { + buf, err := json.Marshal(&info) + if err != nil { + return "", fmt.Errorf("failed to serialize file stats: %w", err) + } + return base64.URLEncoding.EncodeToString(buf), nil +} + +// ExtractFileInfoFromHeader extracts a base64 encoded JSON payload of a +// FileInfo in the http header. If no such header entry is found, nil is +// returned. Intended for Docker compat. +func ExtractFileInfoFromHeader(header *http.Header) (*FileInfo, error) { + rawData := header.Get(XDockerContainerPathStatHeader) + if len(rawData) == 0 { + return nil, nil + } + + info := FileInfo{} + base64Decoder := base64.NewDecoder(base64.URLEncoding, strings.NewReader(rawData)) + if err := json.NewDecoder(base64Decoder).Decode(&info); err != nil { + return nil, err + } + + return &info, nil +} + +// ResolveHostPath resolves the specified, possibly relative, path on the host. +func ResolveHostPath(path string) (*FileInfo, error) { + resolvedHostPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + resolvedHostPath = PreserveBasePath(path, resolvedHostPath) + + statInfo, err := os.Stat(resolvedHostPath) + if err != nil { + if os.IsNotExist(err) { + return nil, ErrENOENT + } + return nil, err + } + + return &FileInfo{ + Name: statInfo.Name(), + Size: statInfo.Size(), + Mode: statInfo.Mode(), + ModTime: statInfo.ModTime(), + IsDir: statInfo.IsDir(), + LinkTarget: resolvedHostPath, + }, nil +} + +// PreserveBasePath makes sure that the original base path (e.g., "/" or "./") +// is preserved. The filepath API among tends to clean up a bit too much but +// we *must* preserve this data by all means. +func PreserveBasePath(original, resolved string) string { + // Ensure paths are in platform semantics (replace / with \ on Windows) + resolved = filepath.FromSlash(resolved) + original = filepath.FromSlash(original) + + if filepath.Base(resolved) != "." && filepath.Base(original) == "." { + if !hasTrailingPathSeparator(resolved) { + // Add a separator if it doesn't already end with one (a cleaned + // path would only end in a separator if it is the root). + resolved += string(filepath.Separator) + } + resolved += "." + } + + if !hasTrailingPathSeparator(resolved) && hasTrailingPathSeparator(original) { + resolved += string(filepath.Separator) + } + + return resolved +} + +// hasTrailingPathSeparator returns whether the given +// path ends with the system's path separator character. +func hasTrailingPathSeparator(path string) bool { + return len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/copy/parse.go b/vendor/github.com/containers/podman/v5/pkg/copy/parse.go new file mode 100644 index 00000000..f42ef90e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/copy/parse.go @@ -0,0 +1,57 @@ +package copy + +import ( + "fmt" + "path/filepath" + "strings" +) + +// ParseSourceAndDestination parses the source and destination input into a +// possibly specified container and path. The input format is described in +// podman-cp(1) as "[nameOrID:]path". Colons in paths are supported as long +// they start with a dot or slash. +// +// It returns, in order, the source container and path, followed by the +// destination container and path, and an error. Note that exactly one +// container must be specified. +func ParseSourceAndDestination(source, destination string) (string, string, string, string, error) { + sourceContainer, sourcePath := parseUserInput(source) + destContainer, destPath := parseUserInput(destination) + + if len(sourcePath) == 0 || len(destPath) == 0 { + return "", "", "", "", fmt.Errorf("invalid arguments %q, %q: you must specify paths", source, destination) + } + + return sourceContainer, sourcePath, destContainer, destPath, nil +} + +// parseUserInput parses the input string and returns, if specified, the name +// or ID of the container and the path. The input format is described in +// podman-cp(1) as "[nameOrID:]path". Colons in paths are supported as long +// they start with a dot or slash. +func parseUserInput(input string) (container string, path string) { + if len(input) == 0 { + return + } + path = input + + // If the input starts with a dot or slash, it cannot refer to a + // container. + if input[0] == '.' || input[0] == '/' { + return + } + + // If the input is an absolute path, it cannot refer to a container. + // This is necessary because absolute paths on Windows will include + // a colon, which would cause the drive letter to be parsed as a + // container name. + if filepath.IsAbs(input) { + return + } + + if parsedContainer, parsedPath, ok := strings.Cut(path, ":"); ok { + container = parsedContainer + path = parsedPath + } + return +} diff --git a/vendor/github.com/containers/podman/v5/pkg/criu/criu.go b/vendor/github.com/containers/podman/v5/pkg/criu/criu.go new file mode 100644 index 00000000..0b0bbff5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/criu/criu.go @@ -0,0 +1,8 @@ +package criu + +// MinCriuVersion for Podman at least CRIU 3.11 is required +const MinCriuVersion = 31100 + +// PodCriuVersion is the version of CRIU needed for +// checkpointing and restoring containers out of and into Pods. +const PodCriuVersion = 31600 diff --git a/vendor/github.com/containers/podman/v5/pkg/criu/criu_linux.go b/vendor/github.com/containers/podman/v5/pkg/criu/criu_linux.go new file mode 100644 index 00000000..75fd9626 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/criu/criu_linux.go @@ -0,0 +1,45 @@ +//go:build linux + +package criu + +import ( + "fmt" + + "github.com/checkpoint-restore/go-criu/v7" + "github.com/checkpoint-restore/go-criu/v7/rpc" + + "google.golang.org/protobuf/proto" +) + +// CheckForCriu uses CRIU's go bindings to check if the CRIU +// binary exists and if it at least the version Podman needs. +func CheckForCriu(version int) error { + c := criu.MakeCriu() + criuVersion, err := c.GetCriuVersion() + if err != nil { + return fmt.Errorf("failed to check for criu version: %w", err) + } + + if criuVersion >= version { + return nil + } + return fmt.Errorf("checkpoint/restore requires at least CRIU %d, current version is %d", version, criuVersion) +} + +func MemTrack() bool { + features, err := criu.MakeCriu().FeatureCheck( + &rpc.CriuFeatures{ + MemTrack: proto.Bool(true), + }, + ) + if err != nil { + return false + } + + return features.GetMemTrack() +} + +func GetCriuVersion() (int, error) { + c := criu.MakeCriu() + return c.GetCriuVersion() +} diff --git a/vendor/github.com/containers/podman/v5/pkg/criu/criu_unsupported.go b/vendor/github.com/containers/podman/v5/pkg/criu/criu_unsupported.go new file mode 100644 index 00000000..bc46d477 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/criu/criu_unsupported.go @@ -0,0 +1,17 @@ +//go:build !linux + +package criu + +import "fmt" + +func CheckForCriu(_ int) error { + return fmt.Errorf("CheckForCriu not supported on this platform") +} + +func MemTrack() bool { + return false +} + +func GetCriuVersion() (int, error) { + return MinCriuVersion, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/ctime/ctime.go b/vendor/github.com/containers/podman/v5/pkg/ctime/ctime.go new file mode 100644 index 00000000..f5c69c7e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/ctime/ctime.go @@ -0,0 +1,12 @@ +// Package ctime includes a utility for determining file-creation times. +package ctime + +import ( + "os" + "time" +) + +// Created returns the file-creation time. +func Created(fi os.FileInfo) time.Time { + return created(fi) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/ctime/ctime_linux.go b/vendor/github.com/containers/podman/v5/pkg/ctime/ctime_linux.go new file mode 100644 index 00000000..226aeba7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/ctime/ctime_linux.go @@ -0,0 +1,15 @@ +//go:build linux + +package ctime + +import ( + "os" + "syscall" + "time" +) + +func created(fi os.FileInfo) time.Time { + st := fi.Sys().(*syscall.Stat_t) + //nolint:unconvert // need to type cast on some cpu architectures + return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec)) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/ctime/ctime_unsupported.go b/vendor/github.com/containers/podman/v5/pkg/ctime/ctime_unsupported.go new file mode 100644 index 00000000..2b93c5eb --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/ctime/ctime_unsupported.go @@ -0,0 +1,12 @@ +//go:build !linux + +package ctime + +import ( + "os" + "time" +) + +func created(fi os.FileInfo) time.Time { + return fi.ModTime() +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/apply.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/apply.go new file mode 100644 index 00000000..e6d0775e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/apply.go @@ -0,0 +1,21 @@ +package entities + +var ( + TypePVC = "PersistentVolumeClaim" + TypePod = "Pod" + TypeService = "Service" +) + +// ApplyOptions controls the deployment of kube yaml files to a Kubernetes Cluster +type ApplyOptions struct { + // Kubeconfig - path to the cluster's kubeconfig file. + Kubeconfig string + // Namespace - namespace to deploy the workload in on the cluster. + Namespace string + // CACertFile - the path to the CA cert file for the Kubernetes cluster. + CACertFile string + // File - the path to the Kubernetes yaml to deploy. + File string + // Service - creates a service for the container being deployed. + Service bool +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/artifact.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/artifact.go new file mode 100644 index 00000000..00521dc5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/artifact.go @@ -0,0 +1,107 @@ +package entities + +import ( + "io" + + encconfig "github.com/containers/ocicrypt/config" + entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types" + libartifactTypes "go.podman.io/common/pkg/libartifact/types" + "go.podman.io/image/v5/types" +) + +type ArtifactAddOptions struct { + Annotations map[string]string + ArtifactMIMEType string + Append bool + FileMIMEType string + Replace bool +} + +type ArtifactAddReport = entitiesTypes.ArtifactAddReport + +type ArtifactExtractOptions struct { + // Title annotation value to extract only a single blob matching that name. + // Conflicts with Digest. Optional. + Title string + // Digest of the blob to extract. + // Conflicts with Title. Optional. + Digest string + // ExcludeTitle option allows single blobs to be exported + // with their title/filename empty. Optional. + // Default: False + ExcludeTitle bool +} + +type ArtifactBlob = libartifactTypes.ArtifactBlob + +type ArtifactInspectOptions struct { + // Note: Remote is not currently implemented but will be used for + // remote inspect of artifacts on registries + Remote bool +} + +type ArtifactListOptions struct{} + +type ArtifactListReport = entitiesTypes.ArtifactListReport + +type ArtifactPullOptions struct { + // containers-auth.json(5) file to use when authenticating against + // container registries. + AuthFilePath string + // Path to the certificates directory. + CertDirPath string + // Allow contacting registries over HTTP, or HTTPS with failed TLS + // verification. Note that this does not affect other TLS connections. + InsecureSkipTLSVerify types.OptionalBool + // Maximum number of retries with exponential backoff when facing + // transient network errors. + // Default 3. + MaxRetries *uint + // RetryDelay used for the exponential back off of MaxRetries. + // Default 1 time.Second. + RetryDelay string + // OciDecryptConfig contains the config that can be used to decrypt an image if it is + // encrypted if non-nil. If nil, it does not attempt to decrypt an image. + OciDecryptConfig *encconfig.DecryptConfig + // Quiet can be specified to suppress pull progress when pulling. Ignored + // for remote calls. //TODO: Verify that claim + Quiet bool + // SignaturePolicyPath to overwrite the default one. + SignaturePolicyPath string + // Writer is used to display copy information including progress bars. + Writer io.Writer + + // ----- credentials -------------------------------------------------- + + // Username to use when authenticating at a container registry. + Username string + // Password to use when authenticating at a container registry. + Password string + // IdentityToken is used to authenticate the user and get + // an access token for the registry. + IdentityToken string `json:"identitytoken,omitempty"` +} + +type ArtifactPullReport = entitiesTypes.ArtifactPullReport + +type ArtifactPushOptions struct { + ImagePushOptions + DigestFile string + EncryptLayers []int + EncryptionKeys []string +} + +type ArtifactPushReport = entitiesTypes.ArtifactPushReport + +type ArtifactRemoveOptions struct { + // Remove all artifacts + All bool + // Artifacts is a list of Artifact IDs or names to remove + Artifacts []string + // Ignore if a specified artifact does not exist and do not throw any error. + Ignore bool +} + +type ArtifactRemoveReport = entitiesTypes.ArtifactRemoveReport + +type ArtifactInspectReport = entitiesTypes.ArtifactInspectReport diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/auto-update.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/auto-update.go new file mode 100644 index 00000000..92968479 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/auto-update.go @@ -0,0 +1,36 @@ +package entities + +import "go.podman.io/image/v5/types" + +// AutoUpdateOptions are the options for running auto-update. +type AutoUpdateOptions struct { + // Authfile to use when contacting registries. + Authfile string + // Only check for but do not perform any update. If an update is + // pending, it will be indicated in the Updated field of + // AutoUpdateReport. + DryRun bool + // If restarting the service with the new image failed, restart it + // another time with the previous image. + Rollback bool + // Allow contacting registries over HTTP, or HTTPS with failed TLS + // verification. Note that this does not affect other TLS connections. + InsecureSkipTLSVerify types.OptionalBool +} + +// AutoUpdateReport contains the results from running auto-update. +type AutoUpdateReport struct { + // ID of the container *before* an update. + ContainerID string + // Name of the container *before* an update. + ContainerName string + // Name of the image. + ImageName string + // The configured auto-update policy. + Policy string + // SystemdUnit running a container configured for auto updates. + SystemdUnit string + // Indicates the update status: true, false, failed, pending (see + // DryRun). + Updated string +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/container_ps.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/container_ps.go new file mode 100644 index 00000000..b408eec0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/container_ps.go @@ -0,0 +1,108 @@ +package entities + +import ( + "errors" + "sort" + "strings" + + "github.com/containers/podman/v5/pkg/domain/entities/types" +) + +// ExternalContainerFilter is a function to determine whether a container list is included +// in command output. Container lists to be outputted are tested using the function. +// A true return will include the container list, a false return will exclude it. +type ExternalContainerFilter func(*ListContainer) bool + +// ListContainer describes a container suitable for listing +type ListContainer = types.ListContainer + +// ListContainerNamespaces contains the identifiers of the container's Linux namespaces +type ListContainerNamespaces = types.ListContainerNamespaces + +type SortListContainers []ListContainer + +func (a SortListContainers) Len() int { return len(a) } +func (a SortListContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type psSortedCommand struct{ SortListContainers } + +func (a psSortedCommand) Less(i, j int) bool { + return strings.Join(a.SortListContainers[i].Command, " ") < strings.Join(a.SortListContainers[j].Command, " ") +} + +type psSortedID struct{ SortListContainers } + +func (a psSortedID) Less(i, j int) bool { + return a.SortListContainers[i].ID < a.SortListContainers[j].ID +} + +type psSortedImage struct{ SortListContainers } + +func (a psSortedImage) Less(i, j int) bool { + return a.SortListContainers[i].Image < a.SortListContainers[j].Image +} + +type psSortedNames struct{ SortListContainers } + +func (a psSortedNames) Less(i, j int) bool { + return a.SortListContainers[i].Names[0] < a.SortListContainers[j].Names[0] +} + +type psSortedPod struct{ SortListContainers } + +func (a psSortedPod) Less(i, j int) bool { + return a.SortListContainers[i].Pod < a.SortListContainers[j].Pod +} + +type psSortedRunningFor struct{ SortListContainers } + +func (a psSortedRunningFor) Less(i, j int) bool { + return a.SortListContainers[i].StartedAt < a.SortListContainers[j].StartedAt +} + +type psSortedStatus struct{ SortListContainers } + +func (a psSortedStatus) Less(i, j int) bool { + return a.SortListContainers[i].State < a.SortListContainers[j].State +} + +type psSortedSize struct{ SortListContainers } + +func (a psSortedSize) Less(i, j int) bool { + if a.SortListContainers[i].Size == nil || a.SortListContainers[j].Size == nil { + return false + } + return a.SortListContainers[i].Size.RootFsSize < a.SortListContainers[j].Size.RootFsSize +} + +type PsSortedCreateTime struct{ SortListContainers } + +func (a PsSortedCreateTime) Less(i, j int) bool { + return a.SortListContainers[i].Created.Before(a.SortListContainers[j].Created) +} + +func SortPsOutput(sortBy string, psOutput SortListContainers) (SortListContainers, error) { + switch sortBy { + case "id": + sort.Sort(psSortedID{psOutput}) + case "image": + sort.Sort(psSortedImage{psOutput}) + case "command": + sort.Sort(psSortedCommand{psOutput}) + case "runningfor": + sort.Sort(psSortedRunningFor{psOutput}) + case "status": + sort.Sort(psSortedStatus{psOutput}) + case "size": + sort.Sort(psSortedSize{psOutput}) + case "names": + sort.Sort(psSortedNames{psOutput}) + case "created": + sort.Sort(PsSortedCreateTime{psOutput}) + case "pod": + sort.Sort(psSortedPod{psOutput}) + default: + return nil, errors.New("invalid option for --sort, options are: command, created, id, image, names, runningfor, size, or status") + } + return psOutput, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/containers.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/containers.go new file mode 100644 index 00000000..ad89b0f4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/containers.go @@ -0,0 +1,500 @@ +package entities + +import ( + "io" + "net/url" + "os" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/containers/podman/v5/pkg/specgen" + nettypes "go.podman.io/common/libnetwork/types" + imageTypes "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/archive" +) + +// ContainerRunlabelOptions are the options to execute container-runlabel. +type ContainerRunlabelOptions struct { + // Authfile - path to an authentication file. + Authfile string + // CertDir - path to a directory containing TLS certifications and + // keys. + CertDir string + // Credentials - `user:password` to use when pulling an image. + Credentials string + // Display - do not execute but print the command. + Display bool + // Replace - replace an existing container with a new one from the + // image. + Replace bool + // Name - use this name when executing the runlabel container. + Name string + // Optional1 - fist optional parameter for install. + Optional1 string + // Optional2 - second optional parameter for install. + Optional2 string + // Optional3 - third optional parameter for install. + Optional3 string + // Pull - pull the specified image if it's not in the local storage. + Pull bool + // Quiet - suppress output when pulling images. + Quiet bool + // SignaturePolicy - path to a signature-policy file. + SignaturePolicy string + // SkipTLSVerify - skip HTTPS and certificate verifications when + // contacting registries. + SkipTLSVerify imageTypes.OptionalBool +} + +// ContainerRunlabelReport contains the results from executing container-runlabel. +type ContainerRunlabelReport struct{} + +// WaitOptions are arguments for waiting for a container. +type WaitOptions struct { + // Conditions to wait on. Includes container statuses such as + // "running" or "stopped" and health-related values such "healthy". + Conditions []string + // Time interval to wait before polling for completion. + Interval time.Duration + // Ignore errors when a specified container is missing and mark its + // return code as -1. + Ignore bool + // Use the latest created container. + Latest bool + // Wait for exit of first container which matches conditions, ignore other ones. + ExitFirstMatch bool +} + +// WaitReport is the result of waiting a container. +type WaitReport struct { + // Error while waiting. + Error error + // ExitCode of the container. + ExitCode int32 +} + +type BoolReport struct { + Value bool +} + +// StringSliceReport wraps a string slice. +type StringSliceReport struct { + Value []string +} + +type PauseUnPauseOptions struct { + Filters map[string][]string + All bool + Latest bool +} + +type PauseUnpauseReport struct { + Err error + Id string + RawInput string +} + +type StopOptions struct { + Filters map[string][]string + All bool + Ignore bool + Latest bool + Timeout *uint +} + +type StopReport struct { + Err error + Id string + RawInput string +} + +type TopOptions struct { + // CLI flags. + ListDescriptors bool + Latest bool + + // Options for the API. + Descriptors []string + NameOrID string +} + +type KillOptions struct { + All bool + Latest bool + Signal string +} + +type KillReport struct { + Err error + Id string + RawInput string +} + +type RestartOptions struct { + Filters map[string][]string + All bool + Latest bool + Running bool + Timeout *uint +} + +type RestartReport struct { + Err error + Id string + RawInput string +} + +type RmOptions struct { + Filters map[string][]string + All bool + Depend bool + Force bool + Ignore bool + Latest bool + Timeout *uint + Volumes bool +} + +type ContainerInspectReport struct { + *define.InspectContainerData +} + +type ContainerStatReport = types.ContainerStatReport + +type CommitOptions struct { + Author string + Changes []string + Config []byte + Format string + ImageName string + IncludeVolumes bool + Message string + Pause bool + Quiet bool + Squash bool + Writer io.Writer +} + +type CopyOptions struct { + // If used with ContainerCopyFromArchive and set to true + // it will change ownership of files from the source tar archive + // to the primary uid/gid of the destination container. + Chown bool + // Map to translate path names. + Rename map[string]string + // NoOverwriteDirNonDir when true prevents an existing directory or file from being overwritten + // by the other type + NoOverwriteDirNonDir bool +} + +type CommitReport struct { + Id string +} + +type ContainerExportOptions struct { + Output io.Writer +} + +type CheckpointOptions struct { + All bool + Export string + CreateImage string + IgnoreRootFS bool + IgnoreVolumes bool + Keep bool + Latest bool + LeaveRunning bool + TCPEstablished bool + PreCheckPoint bool + WithPrevious bool + Compression archive.Compression + PrintStats bool + FileLocks bool +} + +type CheckpointReport = types.CheckpointReport + +type RestoreOptions struct { + All bool + IgnoreRootFS bool + IgnoreVolumes bool + IgnoreStaticIP bool + IgnoreStaticMAC bool + Import string + CheckpointImage bool + Keep bool + Latest bool + Name string + TCPEstablished bool + TCPClose bool + ImportPrevious string + PublishPorts []string + Pod string + PrintStats bool + FileLocks bool +} + +type RestoreReport = types.RestoreReport + +type ContainerCreateReport struct { + Id string +} + +// AttachOptions describes the cli and other values +// needed to perform an attach +type AttachOptions struct { + DetachKeys string + Latest bool + NoStdin bool + SigProxy bool + Stdin *os.File + Stdout *os.File + Stderr *os.File +} + +// ContainerLogsOptions describes the options to extract container logs. +type ContainerLogsOptions struct { + // Show extra details provided to the logs. + Details bool + // Follow the log output. + Follow bool + // Display logs for the latest container only. Ignored on the remote client. + Latest bool + // Show container names in the output. + Names bool + // Show logs since this timestamp. + Since time.Time + // Show logs until this timestamp. + Until time.Time + // Number of lines to display at the end of the output. + Tail int64 + // Show timestamps in the logs. + Timestamps bool + // Show different colors in the logs. + Colors bool + // Write the stdout to this Writer. + StdoutWriter io.Writer + // Write the stderr to this Writer. + StderrWriter io.Writer +} + +// ExecOptions describes the cli values to exec into +// a container +type ExecOptions struct { + Cmd []string + DetachKeys string + Envs map[string]string + Interactive bool + Latest bool + PreserveFDs uint + PreserveFD []uint + Privileged bool + Tty bool + User string + WorkDir string +} + +// ContainerExistsOptions describes the cli values to check if a container exists +type ContainerExistsOptions struct { + External bool +} + +// ContainerStartOptions describes the val from the +// CLI needed to start a container +type ContainerStartOptions struct { + Filters map[string][]string + All bool + Attach bool + DetachKeys string + Interactive bool + Latest bool + SigProxy bool + Stdout *os.File + Stderr *os.File + Stdin *os.File +} + +// ContainerStartReport describes the response from starting +// containers from the cli +type ContainerStartReport struct { + Id string + RawInput string + Err error + ExitCode int +} + +// ContainerListOptions describes the CLI options +// for listing containers +type ContainerListOptions struct { + All bool + Filters map[string][]string + Format string + Last int + Latest bool + Namespace bool + Pod bool + Quiet bool + Size bool + External bool + Sort string + Sync bool + Watch uint +} + +// ContainerRunOptions describes the options needed +// to run a container from the CLI +type ContainerRunOptions struct { + CIDFile string + Detach bool + DetachKeys string + ErrorStream *os.File + InputStream *os.File + OutputStream *os.File + PreserveFDs uint + PreserveFD []uint + Rm bool + SigProxy bool + Spec *specgen.SpecGenerator + Passwd bool +} + +// ContainerRunReport describes the results of running +// a container +type ContainerRunReport struct { + ExitCode int + Id string +} + +// ContainerCleanupOptions are the CLI values for the +// cleanup command +type ContainerCleanupOptions struct { + All bool + Exec string + Latest bool + Remove bool + RemoveImage bool + StoppedOnly bool // Only cleanup if the container is stopped, i.e. was running before +} + +// ContainerCleanupReport describes the response from a +// container cleanup +type ContainerCleanupReport struct { + CleanErr error + Id string + RawInput string + RmErr error + RmiErr error +} + +// ContainerInitOptions describes input options +// for the container init cli +type ContainerInitOptions struct { + All bool + Latest bool +} + +// ContainerInitReport describes the results of a +// container init +type ContainerInitReport struct { + Err error + Id string + RawInput string +} + +// ContainerMountOptions describes the input values for mounting containers +// in the CLI +type ContainerMountOptions struct { + All bool + Format string + Latest bool + NoTruncate bool +} + +// ContainerUnmountOptions are the options from the cli for unmounting +type ContainerUnmountOptions struct { + All bool + Force bool + Latest bool +} + +// ContainerMountReport describes the response from container mount +type ContainerMountReport struct { + Err error + Id string + Name string + Path string +} + +// ContainerUnmountReport describes the response from umounting a container +type ContainerUnmountReport struct { + Err error + Id string +} + +// ContainerPruneOptions describes the options needed +// to prune a container from the CLI +type ContainerPruneOptions struct { + Filters url.Values `json:"filters" schema:"filters"` +} + +// ContainerPortOptions describes the options to obtain +// port information on containers +type ContainerPortOptions struct { + All bool + Latest bool +} + +// ContainerPortReport describes the output needed for +// the CLI to output ports +type ContainerPortReport struct { + Id string + Ports []nettypes.PortMapping +} + +// ContainerCpOptions describes input options for cp. +type ContainerCpOptions struct { + // Pause the container while copying. + Pause bool + // Extract the tarfile into the destination directory. + Extract bool + // OverwriteDirNonDir allows for overwriting a directory with a + // non-directory and vice versa. + OverwriteDirNonDir bool +} + +// ContainerStatsOptions describes input options for getting +// stats on containers +type ContainerStatsOptions struct { + // Get all containers stats + All bool + // Operate on the latest known container. Only supported for local + // clients. + Latest bool + // Stream stats. + Stream bool + // Interval in seconds + Interval int +} + +type ContainerStatsReport = types.ContainerStatsReport + +// ContainerRenameOptions describes input options for renaming a container. +type ContainerRenameOptions struct { + // NewName is the new name that will be given to the container. + NewName string +} + +// ContainerCloneOptions contains options for cloning an existing container +type ContainerCloneOptions struct { + ID string + Destroy bool + CreateOpts ContainerCreateOptions + Image string + RawImageName string + Run bool + Force bool +} + +// ContainerUpdateOptions containers options for updating an existing containers cgroup configuration +type ContainerUpdateOptions = types.ContainerUpdateOptions diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/engine.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/engine.go new file mode 100644 index 00000000..35c8d5ef --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/engine.go @@ -0,0 +1,66 @@ +package entities + +import ( + "github.com/spf13/pflag" + "go.podman.io/common/pkg/config" +) + +// EngineMode is the connection type podman is using to access libpod +type EngineMode string + +// EngineSetup calls out whether a "normal" or specialized engine should be created +type EngineSetup string + +const ( + ABIMode = EngineMode("abi") + TunnelMode = EngineMode("tunnel") +) + +// Convert EngineMode to String +func (m EngineMode) String() string { + return string(m) +} + +// PodmanConfig combines the defaults and settings from the file system with the +// flags given in os.Args. Some runtime state is also stored here. +type PodmanConfig struct { + *pflag.FlagSet + + ContainersConf *config.Config + ContainersConfDefaultsRO *config.Config // The read-only! defaults from containers.conf. + DBBackend string // Hidden: change the database backend + DockerConfig string // Path to directory containing authentication config file + CgroupUsage string // rootless code determines Usage message + ConmonPath string // --conmon flag will set Engine.ConmonPath + CPUProfile string // Hidden: Should CPU profile be taken + EngineMode EngineMode // ABI or Tunneling mode + HooksDir []string + CdiSpecDirs []string + Identity string // ssh identity for connecting to server + TLSCertFile string // tls client cert for connecting to server + TLSKeyFile string // tls client cert private key for connection to server + TLSCAFile string // tls certificate authority to verify server connection + IsRenumber bool // Is this a system renumber command? If so, a number of checks will be relaxed + IsReset bool // Is this a system reset command? If so, a number of checks will be skipped/omitted + MaxWorks int // maximum number of parallel threads + MemoryProfile string // Hidden: Should memory profile be taken + RegistriesConf string // allows for specifying a custom registries.conf + Remote bool // Connection to Podman API Service will use RESTful API + RuntimePath string // --runtime flag will set Engine.RuntimePath + RuntimeFlags []string // global flags for the container runtime + Syslog bool // write logging information to syslog as well as the console + Trace bool // Hidden: Trace execution + URI string // URI to RESTful API Service + FarmNodeName string // Name of farm node + ConnectionError error // Error when looking up the connection in setupRemoteConnection() + + Runroot string + ImageStore string + StorageDriver string + StorageOpts []string + SSHMode string + MachineMode bool + TransientStore bool + GraphRoot string + PullOptions []string +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/engine_container.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/engine_container.go new file mode 100644 index 00000000..d81d5c13 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/engine_container.go @@ -0,0 +1,125 @@ +package entities + +import ( + "context" + "io" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/domain/entities/reports" + "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/containers/podman/v5/pkg/specgen" + netTypes "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" +) + +type ContainerCopyFunc = types.ContainerCopyFunc + +type ContainerEngine interface { //nolint:interfacebloat + AutoUpdate(ctx context.Context, options AutoUpdateOptions) ([]*AutoUpdateReport, []error) + Config(ctx context.Context) (*config.Config, error) + ContainerAttach(ctx context.Context, nameOrID string, options AttachOptions) error + ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error) + ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error) + ContainerClone(ctx context.Context, ctrClone ContainerCloneOptions) (*ContainerCreateReport, error) + ContainerCommit(ctx context.Context, nameOrID string, options CommitOptions) (*CommitReport, error) + ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options CopyOptions) (ContainerCopyFunc, error) + ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (ContainerCopyFunc, error) + ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error) + ContainerExec(ctx context.Context, nameOrID string, options ExecOptions, streams define.AttachStreams) (int, error) + ContainerExecDetached(ctx context.Context, nameOrID string, options ExecOptions) (string, error) + ContainerExists(ctx context.Context, nameOrID string, options ContainerExistsOptions) (*BoolReport, error) + ContainerExport(ctx context.Context, nameOrID string, options ContainerExportOptions) error + ContainerInit(ctx context.Context, namesOrIds []string, options ContainerInitOptions) ([]*ContainerInitReport, error) + ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, []error, error) + ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error) + ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error) + ContainerListExternal(ctx context.Context) ([]ListContainer, error) + ContainerLogs(ctx context.Context, containers []string, options ContainerLogsOptions) error + ContainerMount(ctx context.Context, nameOrIDs []string, options ContainerMountOptions) ([]*ContainerMountReport, error) + ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) + ContainerPort(ctx context.Context, nameOrID string, options ContainerPortOptions) ([]*ContainerPortReport, error) + ContainerPrune(ctx context.Context, options ContainerPruneOptions) ([]*reports.PruneReport, error) + ContainerRename(ctr context.Context, nameOrID string, options ContainerRenameOptions) error + ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error) + ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error) + ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*reports.RmReport, error) + ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error) + ContainerRunlabel(ctx context.Context, label string, image string, args []string, opts ContainerRunlabelOptions) error + ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error) + ContainerStat(ctx context.Context, nameOrDir string, path string) (*ContainerStatReport, error) + ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) (chan ContainerStatsReport, error) + ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error) + ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error) + ContainerUnmount(ctx context.Context, nameOrIDs []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error) + ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) + ContainerUpdate(ctx context.Context, options *ContainerUpdateOptions) (string, error) + ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) + Diff(ctx context.Context, namesOrIds []string, options DiffOptions) (*DiffReport, error) + Events(ctx context.Context, opts EventsOptions) error + GenerateSpec(ctx context.Context, opts *GenerateSpecOptions) (*GenerateSpecReport, error) + GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error) + GenerateKube(ctx context.Context, nameOrIDs []string, opts GenerateKubeOptions) (*GenerateKubeReport, error) + SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error) + HealthCheckRun(ctx context.Context, nameOrID string, options HealthCheckOptions) (*define.HealthCheckResults, error) + Info(ctx context.Context) (*define.Info, error) + KubeApply(ctx context.Context, body io.Reader, opts ApplyOptions) error + Locks(ctx context.Context) (*LocksReport, error) + Migrate(ctx context.Context, options SystemMigrateOptions) error + NetworkConnect(ctx context.Context, networkname string, options NetworkConnectOptions) error + NetworkCreate(ctx context.Context, network netTypes.Network, createOptions *netTypes.NetworkCreateOptions) (*netTypes.Network, error) + NetworkUpdate(ctx context.Context, networkname string, options NetworkUpdateOptions) error + NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) error + NetworkExists(ctx context.Context, networkname string) (*BoolReport, error) + NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]NetworkInspectReport, []error, error) + NetworkList(ctx context.Context, options NetworkListOptions) ([]netTypes.Network, error) + NetworkPrune(ctx context.Context, options NetworkPruneOptions) ([]*NetworkPruneReport, error) + NetworkReload(ctx context.Context, names []string, options NetworkReloadOptions) ([]*NetworkReloadReport, error) + NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error) + PlayKube(ctx context.Context, body io.Reader, opts PlayKubeOptions) (*PlayKubeReport, error) + PlayKubeDown(ctx context.Context, body io.Reader, opts PlayKubeDownOptions) (*PlayKubeReport, error) + PodCreate(ctx context.Context, specg PodSpec) (*PodCreateReport, error) + PodClone(ctx context.Context, podClone PodCloneOptions) (*PodCloneReport, error) + PodExists(ctx context.Context, nameOrID string) (*BoolReport, error) + PodInspect(ctx context.Context, namesOrID []string, options InspectOptions) ([]*PodInspectReport, []error, error) + PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error) + PodLogs(ctx context.Context, pod string, options PodLogsOptions) error + PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error) + PodPrune(ctx context.Context, options PodPruneOptions) ([]*PodPruneReport, error) + PodPs(ctx context.Context, options PodPSOptions) ([]*ListPodsReport, error) + PodRestart(ctx context.Context, namesOrIds []string, options PodRestartOptions) ([]*PodRestartReport, error) + PodRm(ctx context.Context, namesOrIds []string, options PodRmOptions) ([]*PodRmReport, error) + PodStart(ctx context.Context, namesOrIds []string, options PodStartOptions) ([]*PodStartReport, error) + PodStats(ctx context.Context, namesOrIds []string, options PodStatsOptions) ([]*PodStatsReport, error) + PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error) + PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error) + PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error) + QuadletInstall(ctx context.Context, pathsOrURLs []string, options QuadletInstallOptions) (*QuadletInstallReport, error) + QuadletList(ctx context.Context, options QuadletListOptions) ([]*ListQuadlet, error) + QuadletPrint(ctx context.Context, quadlet string) (string, error) + QuadletRemove(ctx context.Context, quadlets []string, options QuadletRemoveOptions) (*QuadletRemoveReport, error) + Renumber(ctx context.Context) error + Reset(ctx context.Context) error + SetupRootless(ctx context.Context, noMoveProcess bool, cgroupMode string) error + SecretCreate(ctx context.Context, name string, reader io.Reader, options SecretCreateOptions) (*SecretCreateReport, error) + SecretInspect(ctx context.Context, nameOrIDs []string, options SecretInspectOptions) ([]*SecretInfoReport, []error, error) + SecretList(ctx context.Context, opts SecretListRequest) ([]*SecretInfoReport, error) + SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error) + SecretExists(ctx context.Context, nameOrID string) (*BoolReport, error) + Shutdown(ctx context.Context) + SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error) + SystemCheck(ctx context.Context, options SystemCheckOptions) (*SystemCheckReport, error) + Unshare(ctx context.Context, args []string, options SystemUnshareOptions) error + Version(ctx context.Context) (*SystemVersionReport, error) + VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error) + VolumeExists(ctx context.Context, namesOrID string) (*BoolReport, error) + VolumeMounted(ctx context.Context, namesOrID string) (*BoolReport, error) + VolumeInspect(ctx context.Context, namesOrIds []string, opts InspectOptions) ([]*VolumeInspectReport, []error, error) + VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error) + VolumeMount(ctx context.Context, namesOrIds []string) ([]*VolumeMountReport, error) + VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*reports.PruneReport, error) + VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) + VolumeUnmount(ctx context.Context, namesOrIds []string) ([]*VolumeUnmountReport, error) + VolumeReload(ctx context.Context) (*VolumeReloadReport, error) + VolumeExport(ctx context.Context, nameOrID string, options VolumeExportOptions) error + VolumeImport(ctx context.Context, nameOrID string, options VolumeImportOptions) error +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/engine_image.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/engine_image.go new file mode 100644 index 00000000..0e75ddcb --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/engine_image.go @@ -0,0 +1,58 @@ +package entities + +import ( + "context" + "io" + + "github.com/containers/podman/v5/pkg/domain/entities/reports" + "go.podman.io/common/libimage/define" + "go.podman.io/common/pkg/config" +) + +type ImageEngine interface { //nolint:interfacebloat + ArtifactAdd(ctx context.Context, name string, artifactBlobs []ArtifactBlob, opts ArtifactAddOptions) (*ArtifactAddReport, error) + ArtifactExtract(ctx context.Context, name string, target string, opts ArtifactExtractOptions) error + ArtifactExtractTarStream(ctx context.Context, w io.Writer, name string, opts ArtifactExtractOptions) error + ArtifactInspect(ctx context.Context, name string, opts ArtifactInspectOptions) (*ArtifactInspectReport, error) + ArtifactList(ctx context.Context, opts ArtifactListOptions) ([]*ArtifactListReport, error) + ArtifactPull(ctx context.Context, name string, opts ArtifactPullOptions) (*ArtifactPullReport, error) + ArtifactPush(ctx context.Context, name string, opts ArtifactPushOptions) (*ArtifactPushReport, error) + ArtifactRm(ctx context.Context, opts ArtifactRemoveOptions) (*ArtifactRemoveReport, error) + Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, error) + Config(ctx context.Context) (*config.Config, error) + Exists(ctx context.Context, nameOrID string) (*BoolReport, error) + History(ctx context.Context, nameOrID string, opts ImageHistoryOptions) (*ImageHistoryReport, error) + Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error) + Inspect(ctx context.Context, namesOrIDs []string, opts InspectOptions) ([]*ImageInspectReport, []error, error) + List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error) + Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error) + Mount(ctx context.Context, images []string, options ImageMountOptions) ([]*ImageMountReport, error) + Prune(ctx context.Context, opts ImagePruneOptions) ([]*reports.PruneReport, error) + Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error) + Push(ctx context.Context, source string, destination string, opts ImagePushOptions) (*ImagePushReport, error) + Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, []error) + Save(ctx context.Context, nameOrID string, tags []string, options ImageSaveOptions) error + Scp(ctx context.Context, src, dst string, opts ImageScpOptions) (*ImageScpReport, error) + Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error) + SetTrust(ctx context.Context, args []string, options SetTrustOptions) error + ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error) + Shutdown(ctx context.Context) + Tag(ctx context.Context, nameOrID string, tags []string, options ImageTagOptions) error + Tree(ctx context.Context, nameOrID string, options ImageTreeOptions) (*ImageTreeReport, error) + Unmount(ctx context.Context, images []string, options ImageUnmountOptions) ([]*ImageUnmountReport, error) + Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error + ManifestCreate(ctx context.Context, name string, images []string, opts ManifestCreateOptions) (string, error) + ManifestExists(ctx context.Context, name string) (*BoolReport, error) + ManifestInspect(ctx context.Context, name string, opts ManifestInspectOptions) (*define.ManifestListData, error) + ManifestAdd(ctx context.Context, listName string, imageNames []string, opts ManifestAddOptions) (string, error) + ManifestAddArtifact(ctx context.Context, name string, files []string, opts ManifestAddArtifactOptions) (string, error) + ManifestAnnotate(ctx context.Context, names, image string, opts ManifestAnnotateOptions) (string, error) + ManifestRemoveDigest(ctx context.Context, names, image string) (string, error) + ManifestRm(ctx context.Context, names []string, imageRmOpts ImageRemoveOptions) (*ImageRemoveReport, []error) + ManifestPush(ctx context.Context, name, destination string, imagePushOpts ImagePushOptions) (string, error) + ManifestListClear(ctx context.Context, name string) (string, error) + Sign(ctx context.Context, names []string, options SignOptions) (*SignReport, error) + FarmNodeName(ctx context.Context) string + FarmNodeDriver(ctx context.Context) string + FarmNodeInspect(ctx context.Context) (*FarmInspectReport, error) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/events.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/events.go new file mode 100644 index 00000000..d9c76255 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/events.go @@ -0,0 +1,99 @@ +package entities + +import ( + "strconv" + "time" + + libpodEvents "github.com/containers/podman/v5/libpod/events" + types "github.com/containers/podman/v5/pkg/domain/entities/types" + dockerEvents "github.com/docker/docker/api/types/events" +) + +type Event = types.Event + +// ConvertToLibpodEvent converts an entities event to a libpod one. +func ConvertToLibpodEvent(e Event) *libpodEvents.Event { + var exitCode int + if ec, ok := e.Actor.Attributes["containerExitCode"]; ok { + var err error + exitCode, err = strconv.Atoi(ec) + if err != nil { + return nil + } + } + status, err := libpodEvents.StringToStatus(string(e.Action)) + if err != nil { + return nil + } + t, err := libpodEvents.StringToType(string(e.Type)) + if err != nil { + return nil + } + image := e.Actor.Attributes["image"] + name := e.Actor.Attributes["name"] + network := e.Actor.Attributes["network"] + podID := e.Actor.Attributes["podId"] + errorString := e.Actor.Attributes["error"] + details := e.Actor.Attributes + delete(details, "image") + delete(details, "name") + delete(details, "network") + delete(details, "podId") + delete(details, "error") + delete(details, "containerExitCode") + return &libpodEvents.Event{ + ContainerExitCode: &exitCode, + ID: e.Actor.ID, + Image: image, + Name: name, + Network: network, + Status: status, + Time: time.Unix(0, e.TimeNano), + Type: t, + HealthStatus: e.HealthStatus, + Error: errorString, + Details: libpodEvents.Details{ + PodID: podID, + Attributes: details, + }, + } +} + +// ConvertToEntitiesEvent converts a libpod event to an entities one. +func ConvertToEntitiesEvent(e libpodEvents.Event) *types.Event { + attributes := e.Details.Attributes + if attributes == nil { + attributes = make(map[string]string) + } + attributes["image"] = e.Image + attributes["name"] = e.Name + if e.ContainerExitCode != nil { + attributes["containerExitCode"] = strconv.Itoa(*e.ContainerExitCode) + } + attributes["podId"] = e.PodID + if e.Network != "" { + attributes["network"] = e.Network + } + if e.Error != "" { + attributes["error"] = e.Error + } + message := dockerEvents.Message{ + // Compatibility with clients that still look for deprecated API elements + Status: e.Status.String(), + ID: e.ID, + From: e.Image, + Type: dockerEvents.Type(e.Type.String()), + Action: dockerEvents.Action(e.Status.String()), + Actor: dockerEvents.Actor{ + ID: e.ID, + Attributes: attributes, + }, + Scope: "local", + Time: e.Time.Unix(), + TimeNano: e.Time.UnixNano(), + } + return &types.Event{ + Message: message, + HealthStatus: e.HealthStatus, + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/generate.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/generate.go new file mode 100644 index 00000000..8a54ce29 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/generate.go @@ -0,0 +1,55 @@ +package entities + +import ( + "github.com/containers/podman/v5/pkg/domain/entities/types" +) + +// GenerateSystemdOptions control the generation of systemd unit files. +type GenerateSystemdOptions struct { + Name bool + New bool + RestartPolicy *string + RestartSec *uint + StartTimeout *uint + StopTimeout *uint + ContainerPrefix string + PodPrefix string + Separator string + NoHeader bool + TemplateUnitFile bool + Wants []string + After []string + Requires []string + AdditionalEnvVariables []string +} + +// GenerateSystemdReport +type GenerateSystemdReport = types.GenerateSystemdReport + +// GenerateKubeOptions control the generation of Kubernetes YAML files. +type GenerateKubeOptions struct { + // PodmanOnly - add podman-only reserved annotations in the generated YAML file (Cannot be used by Kubernetes) + PodmanOnly bool + // Service - generate YAML for a Kubernetes _service_ object. + Service bool + // Type - the k8s kind to be generated i.e Pod or Deployment + Type string + // Replicas - the value to set in the replicas field for a Deployment + Replicas int32 + // UseLongAnnotations - don't truncate annotations to the Kubernetes maximum length of 63 characters + UseLongAnnotations bool +} + +type KubeGenerateOptions = GenerateKubeOptions + +// GenerateKubeReport +type GenerateKubeReport = types.GenerateKubeReport + +type GenerateSpecReport = types.GenerateSpecReport + +type GenerateSpecOptions struct { + ID string + FileName string + Compact bool + Name bool +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/healthcheck.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/healthcheck.go new file mode 100644 index 00000000..a880805f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/healthcheck.go @@ -0,0 +1,3 @@ +package entities + +type HealthCheckOptions struct{} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/images.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/images.go new file mode 100644 index 00000000..491f1532 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/images.go @@ -0,0 +1,354 @@ +package entities + +import ( + "io" + "net/url" + + encconfig "github.com/containers/ocicrypt/config" + entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types" + "go.podman.io/common/pkg/config" + "go.podman.io/image/v5/signature/signer" + "go.podman.io/image/v5/types" +) + +type ImageSummary = entitiesTypes.ImageSummary + +// ImageRemoveOptions can be used to alter image removal. +type ImageRemoveOptions struct { + // All will remove all images. + All bool + // Force will force image removal including containers using the images. + Force bool + // Ignore if a specified image does not exist and do not throw an error. + Ignore bool + // Confirms if given name is a manifest list and removes it, otherwise returns error. + LookupManifest bool + // NoPrune will not remove dangling images + NoPrune bool + DisableForceRemoveContainers bool +} + +// ImageRemoveReport is the response for removing one or more image(s) from storage +// and images what was untagged vs actually removed. +type ImageRemoveReport = entitiesTypes.ImageRemoveReport + +type ImageHistoryOptions struct{} + +type ( + ImageHistoryLayer = entitiesTypes.ImageHistoryLayer + ImageHistoryReport = entitiesTypes.ImageHistoryReport +) + +// ImagePullOptions are the arguments for pulling images. +type ImagePullOptions struct { + // AllTags can be specified to pull all tags of an image. Note + // that this only works if the image does not include a tag. + AllTags bool + // Authfile is the path to the authentication file. Ignored for remote + // calls. + Authfile string + // CertDir is the path to certificate directories. Ignored for remote + // calls. + CertDir string + // Username for authenticating against the registry. + Username string + // Password for authenticating against the registry. + Password string + // Arch will overwrite the local architecture for image pulls. + Arch string + // OS will overwrite the local operating system (OS) for image + // pulls. + OS string + // Variant will overwrite the local variant for image pulls. + Variant string + // Quiet can be specified to suppress pull progress when pulling. Ignored + // for remote calls. + Quiet bool + // Retry number of times to retry pull in case of failure + Retry *uint + // RetryDelay between retries in case of pull failures + RetryDelay string + // SignaturePolicy to use when pulling. Ignored for remote calls. + SignaturePolicy string + // SkipTLSVerify to skip HTTPS and certificate verification. + SkipTLSVerify types.OptionalBool + // PullPolicy whether to pull new image + PullPolicy config.PullPolicy + // Writer is used to display copy information including progress bars. + Writer io.Writer + // OciDecryptConfig contains the config that can be used to decrypt an image if it is + // encrypted if non-nil. If nil, it does not attempt to decrypt an image. + OciDecryptConfig *encconfig.DecryptConfig +} + +// ImagePullReport is the response from pulling one or more images. +type ImagePullReport = entitiesTypes.ImagePullReport + +// ImagePushOptions are the arguments for pushing images. +type ImagePushOptions struct { + // All indicates that all images referenced in a manifest list should be pushed + All bool + // Authfile is the path to the authentication file. Ignored for remote + // calls. + Authfile string + // CertDir is the path to certificate directories. Ignored for remote + // calls. + CertDir string + // Compress tarball image layers when pushing to a directory using the 'dir' + // transport. Default is same compression type as source. Ignored for remote + // calls. + Compress bool + // Username for authenticating against the registry. + Username string + // Password for authenticating against the registry. + Password string + // Format is the Manifest type (oci, v2s1, or v2s2) to use when pushing an + // image. Default is manifest type of source, with fallbacks. + // Ignored for remote calls. + Format string + // Quiet can be specified to suppress push progress when pushing. + Quiet bool + // Rm indicates whether to remove the manifest list if push succeeds + Rm bool + // RemoveSignatures, discard any pre-existing signatures in the image. + // Ignored for remote calls. + RemoveSignatures bool + // Retry number of times to retry push in case of failure + Retry *uint + // RetryDelay between retries in case of push failures + RetryDelay string + // SignaturePolicy to use when pulling. Ignored for remote calls. + SignaturePolicy string + // Signers, if non-empty, asks for signatures to be added during the copy + // using the provided signers. + // Rejected for remote calls. + Signers []*signer.Signer + // SignBy adds a signature at the destination using the specified key. + // Ignored for remote calls. + SignBy string + // SignPassphrase, if non-empty, specifies a passphrase to use when signing + // with the key ID from SignBy. + SignPassphrase string + // SignBySigstorePrivateKeyFile, if non-empty, asks for a signature to be added + // during the copy, using a sigstore private key file at the provided path. + // Ignored for remote calls. + SignBySigstorePrivateKeyFile string + // SignSigstorePrivateKeyPassphrase is the passphrase to use when signing with + // SignBySigstorePrivateKeyFile. + SignSigstorePrivateKeyPassphrase []byte + // SkipTLSVerify to skip HTTPS and certificate verification. + SkipTLSVerify types.OptionalBool + // Progress to get progress notifications + Progress chan types.ProgressProperties + // CompressionFormat is the format to use for the compression of the blobs + CompressionFormat string + // CompressionLevel is the level to use for the compression of the blobs + CompressionLevel *int + // Writer is used to display copy information including progress bars. + Writer io.Writer + // OciEncryptConfig when non-nil indicates that an image should be encrypted. + // The encryption options is derived from the construction of EncryptConfig object. + OciEncryptConfig *encconfig.EncryptConfig + // OciEncryptLayers represents the list of layers to encrypt. + // If nil, don't encrypt any layers. + // If non-nil and len==0, denotes encrypt all layers. + // integers in the slice represent 0-indexed layer indices, with support for negative + // indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer. + OciEncryptLayers *[]int + // If necessary, add clones of existing instances with requested compression algorithms to manifest list + // Note: Following option is only valid for `manifest push` + AddCompression []string + // ForceCompressionFormat ensures that the compression algorithm set in + // CompressionFormat is used exclusively, and blobs of other compression + // algorithms are not reused. + ForceCompressionFormat bool +} + +// ImagePushReport is the response from pushing an image. +type ImagePushReport struct { + // The digest of the manifest of the pushed image. + ManifestDigest string +} + +// ImagePushStream is the response from pushing an image. Only used in the +// remote API. +type ImagePushStream = entitiesTypes.ImagePushStream + +// ImageSearchOptions are the arguments for searching images. +type ImageSearchOptions struct { + // Authfile is the path to the authentication file. Ignored for remote + // calls. + Authfile string + // CertDir is the path to certificate directories. Ignored for remote + // calls. + CertDir string + // Username for authenticating against the registry. + Username string + // Password for authenticating against the registry. + Password string + // IdentityToken is used to authenticate the user and get + // an access token for the registry. + IdentityToken string + // Filters for the search results. + Filters []string + // Limit the number of results. + Limit int + // SkipTLSVerify to skip HTTPS and certificate verification. + SkipTLSVerify types.OptionalBool + // ListTags search the available tags of the repository + ListTags bool +} + +// ImageSearchReport is the response from searching images. +type ImageSearchReport = entitiesTypes.ImageSearchReport + +// Image List Options +type ImageListOptions struct { + All bool + // ExtendedAttributes is used by the libpod endpoint only to deliver extra information + // that the compat endpoint does not + ExtendedAttributes bool + Filter []string +} + +type ImagePruneOptions struct { + All bool `json:"all" schema:"all"` + External bool `json:"external" schema:"external"` + BuildCache bool `json:"buildcache" schema:"buildcache"` + Filter []string `json:"filter" schema:"filter"` +} + +type ( + ImageTagOptions struct{} + ImageUntagOptions struct{} +) + +// ImageInspectReport is the data when inspecting an image. +type ImageInspectReport = entitiesTypes.ImageInspectReport + +type ImageLoadOptions struct { + Input string + Quiet bool + SignaturePolicy string +} + +type ImageLoadReport = entitiesTypes.ImageLoadReport + +type ImageImportOptions struct { + Architecture string + Variant string + Changes []string + Message string + OS string + Quiet bool + Reference string + SignaturePolicy string + Source string + SourceIsURL bool +} + +type ImageImportReport = entitiesTypes.ImageImportReport + +// ImageSaveOptions provide options for saving images. +type ImageSaveOptions struct { + // Compress layers when saving to a directory. + Compress bool + // Format of saving the image: oci-archive, oci-dir (directory with oci + // manifest type), docker-archive, docker-dir (directory with v2s2 + // manifest type). + Format string + // MultiImageArchive denotes if the created archive shall include more + // than one image. Additional tags will be interpreted as references + // to images which are added to the archive. + MultiImageArchive bool + // Accept uncompressed layers when copying OCI images. + OciAcceptUncompressedLayers bool + // Output - write image to the specified path. + Output string + // Quiet - suppress output when copying images + Quiet bool + SignaturePolicy string +} + +// ImageScpOptions provides options for ImageEngine.Scp() +type ImageScpOptions struct { + ScpExecuteTransferOptions +} + +// ImageScpReport provides results from ImageEngine.Scp() +type ImageScpReport struct{} + +// ImageScpConnections provides the ssh related information used in remote image transfer +type ImageScpConnections struct { + // Connections holds the raw string values for connections (ssh or unix) + Connections []string + // URI contains the ssh connection URLs to be used by the client + URI []*url.URL + // Identities contains ssh identity keys to be used by the client + Identities []string +} + +// ImageTreeOptions provides options for ImageEngine.Tree() +type ImageTreeOptions struct { + WhatRequires bool // Show all child images and layers of the specified image +} + +// ImageTreeReport provides results from ImageEngine.Tree() +type ImageTreeReport = entitiesTypes.ImageTreeReport + +// ShowTrustOptions are the cli options for showing trust +type ShowTrustOptions struct { + JSON bool + PolicyPath string + Raw bool + RegistryPath string +} + +// ShowTrustReport describes the results of show trust +type ShowTrustReport = entitiesTypes.ShowTrustReport + +// SetTrustOptions describes the CLI options for setting trust +type SetTrustOptions struct { + PolicyPath string + PubKeysFile []string + Type string +} + +// SignOptions describes input options for the CLI signing +type SignOptions struct { + Directory string + SignBy string + CertDir string + Authfile string + All bool +} + +// SignReport describes the result of signing +type SignReport struct{} + +// ImageMountOptions describes the input values for mounting images +// in the CLI +type ImageMountOptions struct { + All bool + Format string +} + +// ImageUnmountOptions are the options from the cli for unmounting +type ImageUnmountOptions struct { + All bool + Force bool +} + +// ImageMountReport describes the response from image mount +type ImageMountReport = entitiesTypes.ImageMountReport + +// ImageUnmountReport describes the response from umounting an image +type ImageUnmountReport = entitiesTypes.ImageUnmountReport + +const ( + LocalFarmImageBuilderName = "(local)" + LocalFarmImageBuilderDriver = "local" +) + +// FarmInspectReport describes the response from farm inspect +type FarmInspectReport = entitiesTypes.FarmInspectReport diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/machine.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/machine.go new file mode 100644 index 00000000..1d2e45f3 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/machine.go @@ -0,0 +1,48 @@ +package entities + +import "github.com/containers/podman/v5/libpod/define" + +type ListReporter struct { + Name string + Default bool + Created string + Running bool + Starting bool + LastUp string + Stream string + VMType string + CPUs uint64 + Memory string + Swap string + DiskSize string + Port int + RemoteUsername string + IdentityPath string + UserModeNetworking bool +} + +// MachineInfo contains info on the machine host and version info +type MachineInfo struct { + Host *MachineHostInfo `json:"Host"` + Version define.Version `json:"Version"` +} + +// MachineHostInfo contains info on the machine host +type MachineHostInfo struct { + Arch string `json:"Arch"` + CurrentMachine string `json:"CurrentMachine"` + // TODO(6.0): Change `DefaultName` to `ActiveMachineConnection` to fix address + // confusion as shown in https://github.com/containers/podman/issues/23353. + // The name `DefaultMachine` can cause confusion with the user in thinking that + // they can set a default podman machine via system connections. However, + // regardless of which system connection is default, the default podman machine + // will always be podman-machine-default. + DefaultMachine string `json:"DefaultMachine"` + EventsDir string `json:"EventsDir"` + MachineConfigDir string `json:"MachineConfigDir"` + MachineImageDir string `json:"MachineImageDir"` + MachineState string `json:"MachineState"` + NumberOfMachines int `json:"NumberOfMachines"` + OS string `json:"OS"` + VMType string `json:"VMType"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/manifest.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/manifest.go new file mode 100644 index 00000000..b5e1eb24 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/manifest.go @@ -0,0 +1,132 @@ +package entities + +import ( + entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types" + "go.podman.io/image/v5/types" +) + +// ManifestCreateOptions provides model for creating manifest list or image index +type ManifestCreateOptions struct { + // True when adding lists to include all images + All bool `schema:"all"` + // Amend an extant list if there's already one with the desired name + Amend bool `schema:"amend"` + // Should TLS registry certificate be verified? + SkipTLSVerify types.OptionalBool `json:"-" schema:"-"` + // Annotations to set on the list, which forces it to be OCI format + Annotations map[string]string `json:"annotations" schema:"annotations"` +} + +// ManifestInspectOptions provides model for inspecting manifest +type ManifestInspectOptions struct { + // Path to an authentication file. + Authfile string `json:"-" schema:"-"` + // Should TLS registry certificate be verified? + SkipTLSVerify types.OptionalBool `json:"-" schema:"-"` +} + +// ManifestAddOptions provides model for adding digests to manifest list +// +// swagger:model +type ManifestAddOptions struct { + ManifestAnnotateOptions + // True when operating on a list to include all images + All bool `json:"all" schema:"all"` + // authfile to use when pushing manifest list + Authfile string `json:"-" schema:"-"` + // Home directory for certificates when pushing a manifest list + CertDir string `json:"-" schema:"-"` + // Password to authenticate to registry when pushing manifest list + Password string `json:"-" schema:"-"` + // Should TLS registry certificate be verified? + SkipTLSVerify types.OptionalBool `json:"-" schema:"-"` + // Username to authenticate to registry when pushing manifest list + Username string `json:"-" schema:"-"` + // Images is an optional list of image references to add to manifest list + Images []string `json:"images" schema:"images"` +} + +// ManifestAddArtifactOptions provides the model for creating artifact manifests +// for files and adding those manifests to a manifest list +// +// swagger:model +type ManifestAddArtifactOptions struct { + ManifestAnnotateOptions + // Note to future maintainers: keep these fields synchronized with ManifestModifyOptions! + Type *string `json:"artifact_type" schema:"artifact_type"` + LayerType string `json:"artifact_layer_type" schema:"artifact_layer_type"` + ConfigType string `json:"artifact_config_type" schema:"artifact_config_type"` + Config string `json:"artifact_config" schema:"artifact_config"` + ExcludeTitles bool `json:"artifact_exclude_titles" schema:"artifact_exclude_titles"` + Annotations map[string]string `json:"artifact_annotations" schema:"artifact_annotations"` + Subject string `json:"artifact_subject" schema:"artifact_subject"` + Files []string `json:"artifact_files" schema:"-"` +} + +// ManifestAnnotateOptions provides model for annotating manifest list +type ManifestAnnotateOptions struct { + // Annotation to add to the item in the manifest list + Annotation []string `json:"annotation" schema:"annotation"` + // Annotations to add to the item in the manifest list by a map which is preferred over Annotation + Annotations map[string]string `json:"annotations" schema:"annotations"` + // Arch overrides the architecture for the item in the manifest list + Arch string `json:"arch" schema:"arch"` + // Feature list for the item in the manifest list + Features []string `json:"features" schema:"features"` + // OS overrides the operating system for the item in the manifest list + OS string `json:"os" schema:"os"` + // OS features for the item in the manifest list + OSFeatures []string `json:"os_features" schema:"os_features"` + // OSVersion overrides the operating system for the item in the manifest list + OSVersion string `json:"os_version" schema:"os_version"` + // Variant for the item in the manifest list + Variant string `json:"variant" schema:"variant"` + // IndexAnnotation is a slice of key=value annotations to add to the manifest list itself + IndexAnnotation []string `json:"index_annotation" schema:"index_annotation"` + // IndexAnnotations is a map of key:value annotations to add to the manifest list itself, by a map which is preferred over IndexAnnotation + IndexAnnotations map[string]string `json:"index_annotations" schema:"index_annotations"` + // IndexSubject is a subject value to set in the manifest list itself + IndexSubject string `json:"subject" schema:"subject"` +} + +// ManifestModifyOptions provides the model for mutating a manifest +// +// swagger 2.0 does not support oneOf for schema validation. +// +// Operation "update" uses all fields. +// Operation "remove" uses fields: Operation and Images +// Operation "annotate" uses fields: Operation and Annotations +// +// swagger:model +type ManifestModifyOptions struct { + Operation string `json:"operation" schema:"operation"` // Valid values: update, remove, annotate + ManifestAddOptions + ManifestRemoveOptions + // The following are all of the fields from ManifestAddArtifactOptions. + // We can't just embed the whole structure because it embeds a + // ManifestAnnotateOptions, which would conflict with the one that + // ManifestAddOptions embeds. + ArtifactType *string `json:"artifact_type" schema:"artifact_type"` + ArtifactLayerType string `json:"artifact_layer_type" schema:"artifact_layer_type"` + ArtifactConfigType string `json:"artifact_config_type" schema:"artifact_config_type"` + ArtifactConfig string `json:"artifact_config" schema:"artifact_config"` + ArtifactExcludeTitles bool `json:"artifact_exclude_titles" schema:"artifact_exclude_titles"` + ArtifactAnnotations map[string]string `json:"artifact_annotations" schema:"artifact_annotations"` + ArtifactSubject string `json:"artifact_subject" schema:"artifact_subject"` + ArtifactFiles []string `json:"artifact_files" schema:"-"` +} + +// ManifestPushReport provides the model for the pushed manifest +type ManifestPushReport = entitiesTypes.ManifestPushReport + +// ManifestRemoveOptions provides the model for removing digests from a manifest +// +// swagger:model +type ManifestRemoveOptions struct { +} + +// ManifestRemoveReport provides the model for the removed manifest +type ManifestRemoveReport = entitiesTypes.ManifestRemoveReport + +// ManifestModifyReport provides the model for removed digests and changed manifest +type ManifestModifyReport = entitiesTypes.ManifestModifyReport diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/network.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/network.go new file mode 100644 index 00000000..74b6af5c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/network.go @@ -0,0 +1,86 @@ +package entities + +import ( + "net" + + entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types" +) + +// NetworkListOptions describes options for listing networks in cli +type NetworkListOptions struct { + Format string + Quiet bool + Filters map[string][]string +} + +// NetworkReloadOptions describes options for reloading container network +// configuration. +type NetworkReloadOptions struct { + All bool + Latest bool +} + +// NetworkReloadReport describes the results of reloading a container network. +type NetworkReloadReport = entitiesTypes.NetworkReloadReport + +// NetworkRmOptions describes options for removing networks +type NetworkRmOptions struct { + Force bool + Timeout *uint +} + +// NetworkRmReport describes the results of network removal +type NetworkRmReport = entitiesTypes.NetworkRmReport + +// NetworkCreateOptions describes options to create a network +type NetworkCreateOptions struct { + DisableDNS bool + Driver string + Gateways []net.IP + Internal bool + Labels map[string]string + MacVLAN string + NetworkDNSServers []string + Ranges []string + Subnets []string + Routes []string + IPv6 bool + // Mapping of driver options and values. + Options map[string]string + // IgnoreIfExists if true, do not fail if the network already exists + IgnoreIfExists bool + // InterfaceName sets the NetworkInterface in the network config + InterfaceName string +} + +// NetworkUpdateOptions describes options to update a network +type NetworkUpdateOptions struct { + AddDNSServers []string `json:"adddnsservers"` + RemoveDNSServers []string `json:"removednsservers"` +} + +// NetworkCreateReport describes a created network for the cli +type NetworkCreateReport = entitiesTypes.NetworkCreateReport + +// NetworkDisconnectOptions describes options for disconnecting +// containers from networks +type NetworkDisconnectOptions struct { + Container string + Force bool +} + +// NetworkConnectOptions describes options for connecting +// a container to a network +type NetworkConnectOptions = entitiesTypes.NetworkConnectOptions + +// NetworkPruneReport containers the name of network and an error +// associated in its pruning (removal) +type NetworkPruneReport = entitiesTypes.NetworkPruneReport + +// NetworkPruneOptions describes options for pruning unused networks +type NetworkPruneOptions struct { + Filters map[string][]string +} + +type NetworkInspectReport = entitiesTypes.NetworkInspectReport +type NetworkContainerInfo = entitiesTypes.NetworkContainerInfo diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/play.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/play.go new file mode 100644 index 00000000..9df0dbda --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/play.go @@ -0,0 +1,107 @@ +package entities + +import ( + "net" + + entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types" + "go.podman.io/image/v5/types" +) + +// PlayKubeOptions controls playing kube YAML files. +type PlayKubeOptions struct { + // Annotations - Annotations to add to Pods + Annotations map[string]string + // Authfile - path to an authentication file. + Authfile string + // Indicator to build all images with Containerfile or Dockerfile + Build types.OptionalBool + // CertDir - to a directory containing TLS certifications and keys. + CertDir string + // ContextDir - directory containing image contexts used for Build + ContextDir string + // Down indicates whether to bring contents of a yaml file "down" + // as in stop + Down bool + // ExitCodePropagation decides how the main PID of the Kube service + // should exit depending on the containers' exit codes. + ExitCodePropagation string + // Replace indicates whether to delete and recreate a yaml file + Replace bool + // Do not create /etc/hostname within the pod's containers, + // instead use the version from the image + NoHostname bool + // Do not create /etc/hosts within the pod's containers, + // instead use the version from the image + NoHosts bool + // Username for authenticating against the registry. + Username string + // Password for authenticating against the registry. + Password string + // Networks - name of the network to connect to. + Networks []string + // Quiet - suppress output when pulling images. + Quiet bool + // SignaturePolicy - path to a signature-policy file. + SignaturePolicy string + // SkipTLSVerify - skip https and certificate validation when + // contacting container registries. + SkipTLSVerify types.OptionalBool + // SeccompProfileRoot - path to a directory containing seccomp + // profiles. + SeccompProfileRoot string + // StaticIPs - Static IP address used by the pod(s). + StaticIPs []net.IP + // StaticMACs - Static MAC address used by the pod(s). + StaticMACs []net.HardwareAddr + // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs. + ConfigMaps []string + // LogDriver for the container. For example: journald + LogDriver string + // LogOptions for the log driver for the container. + LogOptions []string + // Start - don't start the pod if false + Start types.OptionalBool + // ServiceContainer - creates a service container that is started before and is stopped after all pods. + ServiceContainer bool + // UseLongAnnotations - use annotations that were not truncated to the + // Kubernetes maximum length of 63 characters + UseLongAnnotations bool + // Userns - define the user namespace to use. + Userns string + // IsRemote - was the request triggered by running podman-remote + IsRemote bool + // Force - remove volumes on --down + Force bool + // PublishPorts - configure how to expose ports configured inside the K8S YAML file + PublishPorts []string + // PublishAllPorts - whether to publish all ports defined in the K8S YAML file + // (containerPort, hostPort) otherwise only hostPort will be published + PublishAllPorts bool + // Wait - indicates whether to return after having created the pods + Wait bool + // SystemContext - used when building the image + SystemContext *types.SystemContext + // Do not prefix container name with pod name + NoPodPrefix bool +} + +// PlayKubePod represents a single pod and associated containers created by play kube +type PlayKubePod = entitiesTypes.PlayKubePod + +// PlayKubeVolume represents a single volume created by play kube. +type PlayKubeVolume entitiesTypes.PlayKubeVolume + +// PlayKubeReport contains the results of running play kube. +type PlayKubeReport = entitiesTypes.PlayKubeReport +type KubePlayReport = entitiesTypes.KubePlayReport + +// PlayKubeDownOptions are options for tearing down pods +type PlayKubeDownOptions struct { + // Force - remove volumes if passed + Force bool +} + +// PlayKubeDownReport contains the results of tearing down play kube +type PlayKubeTeardown = entitiesTypes.PlayKubeTeardown + +type PlaySecret = entitiesTypes.PlaySecret diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/pods.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/pods.go new file mode 100644 index 00000000..3c7f7c93 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/pods.go @@ -0,0 +1,482 @@ +package entities + +import ( + "errors" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/containers/podman/v5/pkg/specgen" + "github.com/containers/podman/v5/pkg/util" + "github.com/opencontainers/runtime-spec/specs-go" + commonFlag "go.podman.io/common/pkg/flag" +) + +type PodKillOptions struct { + All bool + Latest bool + Signal string +} + +type PodKillReport = types.PodKillReport + +type ListPodsReport = types.ListPodsReport + +type ListPodContainer = types.ListPodContainer + +type PodPauseOptions struct { + All bool + Latest bool +} + +type PodPauseReport = types.PodPauseReport + +type PodunpauseOptions struct { + All bool + Latest bool +} + +type PodUnpauseReport = types.PodUnpauseReport + +type PodStopOptions struct { + All bool + Ignore bool + Latest bool + Timeout int +} + +type PodStopReport = types.PodStopReport + +type PodRestartOptions struct { + All bool + Latest bool +} + +type PodRestartReport = types.PodRestartReport +type PodStartOptions struct { + All bool + Latest bool +} + +type PodStartReport = types.PodStartReport + +type PodRmOptions struct { + All bool + Force bool + Ignore bool + Latest bool + Timeout *uint +} + +type PodRmReport = types.PodRmReport + +type PodSpec = types.PodSpec + +// PodCreateOptions provides all possible options for creating a pod and its infra container. +// The JSON tags below are made to match the respective field in ContainerCreateOptions for the purpose of mapping. +// swagger:model PodCreateOptions +type PodCreateOptions struct { + CgroupParent string `json:"cgroup_parent,omitempty"` + CreateCommand []string `json:"create_command,omitempty"` + Devices []string `json:"devices,omitempty"` + DeviceReadBPs []string `json:"device_read_bps,omitempty"` + ExitPolicy string `json:"exit_policy,omitempty"` + Hostname string `json:"hostname,omitempty"` + Infra bool `json:"infra,omitempty"` + InfraImage string `json:"infra_image,omitempty"` + InfraName string `json:"container_name,omitempty"` + InfraCommand *string `json:"container_command,omitempty"` + InfraConmonPidFile string `json:"container_conmon_pidfile,omitempty"` + Ipc string `json:"ipc,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Name string `json:"name,omitempty"` + Net *NetOptions `json:"net,omitempty"` + Share []string `json:"share,omitempty"` + ShareParent *bool `json:"share_parent,omitempty"` + Restart string `json:"restart,omitempty"` + Pid string `json:"pid,omitempty"` + Cpus float64 `json:"cpus,omitempty"` + CpusetCpus string `json:"cpuset_cpus,omitempty"` + Userns specgen.Namespace `json:"-"` + Volume []string `json:"volume,omitempty"` + VolumesFrom []string `json:"volumes_from,omitempty"` + SecurityOpt []string `json:"security_opt,omitempty"` + Sysctl []string `json:"sysctl,omitempty"` + Uts string `json:"uts,omitempty"` +} + +// PodLogsOptions describes the options to extract pod logs. +type PodLogsOptions struct { + // Other fields are exactly same as ContainerLogOpts + ContainerLogsOptions + // If specified will only fetch the logs of specified container + ContainerName string + // Show different colors in the logs. + Color bool +} + +// PodCloneOptions contains options for cloning an existing pod +type PodCloneOptions struct { + ID string + Destroy bool + CreateOpts PodCreateOptions + InfraOptions ContainerCreateOptions + PerContainerOptions ContainerCreateOptions + Start bool +} + +type ContainerMode string + +const ( + InfraMode = ContainerMode("infra") + CloneMode = ContainerMode("clone") + UpdateMode = ContainerMode("update") + CreateMode = ContainerMode("create") +) + +type ContainerCreateOptions struct { + Annotation []string + Attach []string + Authfile string + BlkIOWeight string + BlkIOWeightDevice []string + CapAdd []string + CapDrop []string + CgroupNS string + CgroupsMode string + CgroupParent string `json:"cgroup_parent,omitempty"` + CIDFile string + ConmonPIDFile string `json:"container_conmon_pidfile,omitempty"` + CPUPeriod uint64 + CPUQuota int64 + CPURTPeriod uint64 + CPURTRuntime int64 + CPUShares uint64 + CPUS float64 `json:"cpus,omitempty"` + CPUSetCPUs string `json:"cpuset_cpus,omitempty"` + CPUSetMems string + Devices []string `json:"devices,omitempty"` + DeviceCgroupRule []string + DeviceReadBPs []string `json:"device_read_bps,omitempty"` + DeviceReadIOPs []string + DeviceWriteBPs []string + DeviceWriteIOPs []string + Entrypoint *string `json:"container_command,omitempty"` + Env []string + EnvHost bool + EnvFile []string + Expose []string + GIDMap []string + GPUs []string + GroupAdd []string + HealthCmd string + HealthInterval string + HealthRetries uint + HealthLogDestination string + HealthMaxLogCount uint + HealthMaxLogSize uint + HealthStartPeriod string + HealthTimeout string + HealthOnFailure string + Hostname string `json:"hostname,omitempty"` + HTTPProxy bool + HostUsers []string + ImageVolume string + Init bool + InitContainerType string + InitPath string + IntelRdtClosID string + Interactive bool + IPC string + Label []string + LabelFile []string + LogDriver string + LogOptions []string + Memory string + MemoryReservation string + MemorySwap string + MemorySwappiness int64 + Name string `json:"container_name"` + NoHealthCheck bool + OOMKillDisable bool + OOMScoreAdj *int + Arch string + OS string + Variant string + PID string `json:"pid,omitempty"` + PIDsLimit *int64 + Platform string + Pod string + PodIDFile string + Personality string + PreserveFDs uint + PreserveFD []uint + Privileged bool + PublishAll bool + Pull string + Quiet bool + ReadOnly bool + ReadWriteTmpFS bool + Restart string + Replace bool + Requires []string + Retry *uint `json:"retry,omitempty"` + RetryDelay string `json:"retry_delay,omitempty"` + Rm bool + RootFS bool + Secrets []string + SecurityOpt []string `json:"security_opt,omitempty"` + SdNotifyMode string + ShmSize string + ShmSizeSystemd string + SignaturePolicy string + StartupHCCmd string + StartupHCInterval string + StartupHCRetries uint + StartupHCSuccesses uint + StartupHCTimeout string + StopSignal string + StopTimeout uint + StorageOpts []string + SubGIDName string + SubUIDName string + Sysctl []string `json:"sysctl,omitempty"` + Systemd string + Timeout uint + TLSVerify commonFlag.OptionalBool + TmpFS []string + TTY bool + Timezone string + Umask string + EnvMerge []string + UnsetEnv []string + UnsetEnvAll bool + UIDMap []string + Ulimit []string + User string + UserNS string `json:"-"` + UTS string + Mount []string + Volume []string `json:"volume,omitempty"` + VolumesFrom []string `json:"volumes_from,omitempty"` + Workdir string + SeccompPolicy string + PidFile string + ChrootDirs []string + IsInfra bool + IsClone bool + DecryptionKeys []string + CertDir string + Creds string + Net *NetOptions `json:"net,omitempty"` + + CgroupConf []string + + GroupEntry string + PasswdEntry string +} + +func NewInfraContainerCreateOptions() ContainerCreateOptions { + options := ContainerCreateOptions{ + IsInfra: true, + ImageVolume: "anonymous", + MemorySwappiness: -1, + HealthLogDestination: define.DefaultHealthCheckLocalDestination, + HealthMaxLogCount: define.DefaultHealthMaxLogCount, + HealthMaxLogSize: define.DefaultHealthMaxLogSize, + } + return options +} + +type PodCreateReport = types.PodCreateReport + +type PodCloneReport = types.PodCloneReport + +func (p *PodCreateOptions) CPULimits() *specs.LinuxCPU { + cpu := &specs.LinuxCPU{ + Cpus: p.CpusetCpus, + } + + if p.Cpus != 0 { + period, quota := util.CoresToPeriodAndQuota(p.Cpus) + cpu.Period = &period + cpu.Quota = "a + } + + return cpu +} + +func ToPodSpecGen(s specgen.PodSpecGenerator, p *PodCreateOptions) (*specgen.PodSpecGenerator, error) { + // Basic Config + s.Name = p.Name + s.InfraName = p.InfraName + out, err := specgen.ParseNamespace(p.Pid) + if err != nil { + return nil, err + } + s.Pid = out + + out, err = specgen.ParseNamespace(p.Ipc) + if err != nil { + return nil, err + } + s.Ipc = out + + out, err = specgen.ParseNamespace(p.Uts) + if err != nil { + return nil, err + } + s.UtsNs = out + s.Hostname = p.Hostname + s.ExitPolicy = p.ExitPolicy + s.Labels = p.Labels + s.Devices = p.Devices + s.SecurityOpt = p.SecurityOpt + s.NoInfra = !p.Infra + if p.InfraCommand != nil && len(*p.InfraCommand) > 0 { + s.InfraCommand = strings.Split(*p.InfraCommand, " ") + } + if len(p.InfraConmonPidFile) > 0 { + s.InfraConmonPidFile = p.InfraConmonPidFile + } + s.InfraImage = p.InfraImage + s.SharedNamespaces = p.Share + s.ShareParent = p.ShareParent + s.PodCreateCommand = p.CreateCommand + s.VolumesFrom = p.VolumesFrom + if p.Restart != "" { + policy, retries, err := util.ParseRestartPolicy(p.Restart) + if err != nil { + return nil, err + } + s.RestartPolicy = policy + s.RestartRetries = &retries + } + + // Networking config + + if p.Net != nil { + s.NetNS = p.Net.Network + s.PortMappings = p.Net.PublishPorts + s.Networks = p.Net.Networks + s.NetworkOptions = p.Net.NetworkOptions + if p.Net.UseImageResolvConf { + s.NoManageResolvConf = true + } + s.DNSServer = p.Net.DNSServers + s.DNSSearch = p.Net.DNSSearch + s.DNSOption = p.Net.DNSOptions + s.NoManageHosts = p.Net.NoHosts + s.NoManageHostname = p.Net.NoHostname + s.HostAdd = p.Net.AddHosts + s.HostsFile = p.Net.HostsFile + } + + // Cgroup + s.CgroupParent = p.CgroupParent + + // Resource config + if s.ResourceLimits == nil { + s.ResourceLimits = &specs.LinuxResources{} + } + s.ResourceLimits.CPU = p.CPULimits() + + s.Userns = p.Userns + sysctl := map[string]string{} + if ctl := p.Sysctl; len(ctl) > 0 { + sysctl, err = util.ValidateSysctls(ctl) + if err != nil { + return nil, err + } + } + s.Sysctl = sysctl + + return &s, nil +} + +type PodPruneOptions struct { + Force bool `json:"force" schema:"force"` +} + +type PodPruneReport = types.PodPruneReport + +type PodTopOptions struct { + // CLI flags. + ListDescriptors bool + Latest bool + + // Options for the API. + Descriptors []string + NameOrID string +} + +type PodPSOptions struct { + CtrNames bool + CtrIds bool + CtrStatus bool + Filters map[string][]string + Format string + Latest bool + Namespace bool + Quiet bool + Sort string +} + +type PodInspectReport = types.PodInspectReport + +// PodStatsOptions are options for the pod stats command. +type PodStatsOptions struct { + // All - provide stats for all running pods. + All bool + // Latest - provide stats for the latest pod. + Latest bool +} + +// PodStatsReport includes pod-resource statistics data. +type PodStatsReport = types.PodStatsReport + +// ValidatePodStatsOptions validates the specified slice and options. Allows +// for sharing code in the front- and the back-end. +func ValidatePodStatsOptions(args []string, options *PodStatsOptions) error { + num := 0 + if len(args) > 0 { + num++ + } + if options.All { + num++ + } + if options.Latest { + num++ + } + switch num { + case 0: + // Podman v1 compat: if nothing's specified get all running + // pods. + options.All = true + return nil + case 1: + return nil + default: + return errors.New("--all, --latest and arguments cannot be used together") + } +} + +// PodLogsOptionsToContainerLogsOptions converts PodLogOptions to ContainerLogOptions +func PodLogsOptionsToContainerLogsOptions(options PodLogsOptions) ContainerLogsOptions { + // PodLogsOptions are similar but contains few extra fields like ctrName + // So cast other values as is so we can reuse the code + containerLogsOpts := ContainerLogsOptions{ + Details: options.Details, + Latest: options.Latest, + Follow: options.Follow, + Names: options.Names, + Since: options.Since, + Until: options.Until, + Tail: options.Tail, + Timestamps: options.Timestamps, + Colors: options.Colors, + StdoutWriter: options.StdoutWriter, + StderrWriter: options.StderrWriter, + } + return containerLogsOpts +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/quadlet.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/quadlet.go new file mode 100644 index 00000000..1306bb1e --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/quadlet.go @@ -0,0 +1,64 @@ +package entities + +// QuadletInstallOptions contains options to the `podman quadlet install` command +type QuadletInstallOptions struct { + // Whether to reload systemd after installation is completed + ReloadSystemd bool + // Replace the installation even if the quadlet already exists + Replace bool +} + +// QuadletInstallReport contains the output of the `quadlet install` command +// including what files were successfully installed (and to where), and what +// files errored (and why). +type QuadletInstallReport struct { + // InstalledQuadlets is a map of the path of the quadlet file to be installed + // to where it was installed to. + InstalledQuadlets map[string]string + // QuadletErrors is a map of the path of the quadlet file to be installed + // to the error that occurred attempting to install it + QuadletErrors map[string]error +} + +// QuadletListOptions contains options to the `podman quadlet list` command. +type QuadletListOptions struct { + // Filters contains filters that will limit what Quadlets are displayed + Filters []string +} + +// A ListQuadlet is a single Quadlet to be listed by `podman quadlet list` +type ListQuadlet struct { + // Name is the name of the Quadlet file + Name string + // UnitName is the name of the systemd unit created from the Quadlet. + // May be empty if systemd has not be reloaded since it was installed. + UnitName string + // Path to the Quadlet on disk + Path string + // What is the status of the Quadlet - if present in systemd, will be a + // systemd status, else will mention if the Quadlet has syntax errors + Status string + // If multiple quadlets were installed together they will belong + // to common App. + App string +} + +// QuadletRemoveOptions contains parameters for removing Quadlets +type QuadletRemoveOptions struct { + // Force indicates that running quadlets should be removed as well + Force bool + // All indicates all quadlets should be removed + All bool + // Ignore indicates that missing quadlets should not cause an error + Ignore bool + // ReloadSystemd determines whether systemd will be reloaded after the Quadlet is removed. + ReloadSystemd bool +} + +// QuadletRemoveReport contains the results of an operation to remove obe or more quadlets +type QuadletRemoveReport struct { + // Removed is a list of quadlets that were successfully removed + Removed []string + // Errors is a map of Quadlet name to error that occurred during removal. + Errors map[string]error +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/reports/containers.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/reports/containers.go new file mode 100644 index 00000000..87214fff --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/reports/containers.go @@ -0,0 +1,29 @@ +package reports + +type RmReport struct { + Id string `json:"Id"` + Err error `json:"Err,omitempty"` + RawInput string `json:"-"` +} + +func RmReportsIds(r []*RmReport) []string { + ids := make([]string, 0, len(r)) + for _, v := range r { + if v == nil || v.Id == "" { + continue + } + ids = append(ids, v.Id) + } + return ids +} + +func RmReportsErrs(r []*RmReport) []error { + errs := make([]error, 0, len(r)) + for _, v := range r { + if v == nil || v.Err == nil { + continue + } + errs = append(errs, v.Err) + } + return errs +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/reports/prune.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/reports/prune.go new file mode 100644 index 00000000..dfe51f74 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/reports/prune.go @@ -0,0 +1,78 @@ +package reports + +import ( + "encoding/json" + "errors" +) + +type PruneReport struct { + Id string `json:"Id"` + Err error `json:"Err,omitempty"` + Size uint64 `json:"Size"` +} + +type pruneReportHelper struct { + Id string `json:"Id"` + Err string `json:"Err,omitempty"` + Size uint64 `json:"Size"` +} + +func (pr *PruneReport) MarshalJSON() ([]byte, error) { + helper := pruneReportHelper{ + Id: pr.Id, + Size: pr.Size, + } + if pr.Err != nil { + helper.Err = pr.Err.Error() + } + return json.Marshal(helper) +} + +func (pr *PruneReport) UnmarshalJSON(data []byte) error { + var helper pruneReportHelper + if err := json.Unmarshal(data, &helper); err != nil { + return err + } + + pr.Id = helper.Id + pr.Size = helper.Size + if helper.Err != "" { + pr.Err = errors.New(helper.Err) + } else { + pr.Err = nil + } + return nil +} + +func PruneReportsIds(r []*PruneReport) []string { + ids := make([]string, 0, len(r)) + for _, v := range r { + if v == nil || v.Id == "" { + continue + } + ids = append(ids, v.Id) + } + return ids +} + +func PruneReportsErrs(r []*PruneReport) []error { + errs := make([]error, 0, len(r)) + for _, v := range r { + if v == nil || v.Err == nil { + continue + } + errs = append(errs, v.Err) + } + return errs +} + +func PruneReportsSize(r []*PruneReport) uint64 { + size := uint64(0) + for _, v := range r { + if v == nil { + continue + } + size += v.Size + } + return size +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/reports/scp.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/reports/scp.go new file mode 100644 index 00000000..93d46368 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/reports/scp.go @@ -0,0 +1,5 @@ +package reports + +type ScpReport struct { + Id string `json:"Id"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/scp.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/scp.go new file mode 100644 index 00000000..d15a7118 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/scp.go @@ -0,0 +1,97 @@ +package entities + +import ( + "net/url" + + "go.podman.io/common/pkg/ssh" +) + +// ScpTransferImageOptions provide options for securely copying images to and from a remote host +type ScpTransferImageOptions struct { + // Remote determines if this entity is operating on a remote machine + Remote bool `json:"remote,omitempty"` + // File is the input/output file for the save and load Operation + File string `json:"file,omitempty"` + // Quiet Determines if the save and load operation will be done quietly + Quiet bool `json:"quiet,omitempty"` + // Image is the image the user is providing to save and load + Image string `json:"image,omitempty"` + // User is used in conjunction with Transfer to determine if a valid user was given to save from/load into + User string `json:"user,omitempty"` + // Tag is the name to be used for the image on the destination + Tag string `json:"tag,omitempty"` +} + +type ScpLoadReport = ImageLoadReport + +type ScpExecuteTransferOptions struct { + // ParentFlags are the arguments to apply to the parent podman command when called via ssh + ParentFlags []string + // Quiet Determines if the save and load operation will be done quietly + Quiet bool + // SSHMode is the specified ssh.EngineMode which should be used + SSHMode ssh.EngineMode +} + +type ScpExecuteTransferReport struct { + // LoadReport provides results from calling podman load + LoadReport *ScpLoadReport + // Source contains data relating to the source of the image to transfer + Source *ScpTransferImageOptions + // Dest contains data relating to the destination of the image to transfer + Dest *ScpTransferImageOptions + // ParentFlags are the arguments to apply to the parent podman command when called via ssh + ParentFlags []string +} + +type ScpTransferOptions struct { + // ParentFlags are the arguments to apply to the parent podman command when called. + ParentFlags []string +} + +type ScpTransferReport struct{} + +type ScpLoadToRemoteOptions struct { + // Dest contains data relating to the destination of the image to transfer + Dest ScpTransferImageOptions + // LocalFile is a path to a local file containing saved image data to transfer + LocalFile string + // Tag is the name of the tag to be given to the loaded image (unused) + Tag string + // URL points to the remote location for loading to + URL *url.URL + // Iden is a path to an optional identity file with ssh key + Iden string + // SSHMode is the specified ssh.EngineMode which should be used + SSHMode ssh.EngineMode +} + +type ScpLoadToRemoteReport struct { + // Response contains any additional information from the executed load command + Response string + // ID is the identifier of the loaded image + ID string +} + +type ScpSaveToRemoteOptions struct { + Image string + // LocalFile is a path to a local file to copy the saved image to + LocalFile string + // Tag is the name of the tag to be given to the saved image (unused) + Tag string + // URL points to the remote location for saving from + URL *url.URL + // Iden is a path to an optional identity file with ssh key + Iden string + // SSHMode is the specified ssh.EngineMode which should be used + SSHMode ssh.EngineMode +} + +type ScpSaveToRemoteReport struct{} + +type ScpCreateCommandsOptions struct { + // ParentFlags are the arguments to apply to the parent podman command when called via ssh + ParentFlags []string + // Podman is the path to the local podman executable + Podman string +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/secrets.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/secrets.go new file mode 100644 index 00000000..f865d546 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/secrets.go @@ -0,0 +1,110 @@ +package entities + +import ( + "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/containers/podman/v5/pkg/errorhandling" +) + +type SecretCreateReport = types.SecretCreateReport + +type SecretCreateOptions struct { + Driver string + DriverOpts map[string]string + Labels map[string]string + Replace bool + Ignore bool +} + +type SecretInspectOptions struct { + ShowSecret bool +} + +type SecretListRequest struct { + Filters map[string][]string +} + +type SecretListReport = types.SecretListReport + +type SecretRmOptions struct { + All bool + Ignore bool +} + +type SecretRmReport = types.SecretRmReport + +type SecretInfoReport = types.SecretInfoReport + +type SecretInfoReportCompat = types.SecretInfoReportCompat + +type SecretVersion = types.SecretVersion + +type SecretSpec = types.SecretSpec + +type SecretDriverSpec = types.SecretDriverSpec + +// swagger:model SecretCreate +type SecretCreateRequest struct { + // User-defined name of the secret. + Name string + // Base64-url-safe-encoded (RFC 4648) data to store as secret. + Data string + // Driver represents a driver (default "file") + Driver SecretDriverSpec + // Labels are labels on the secret + Labels map[string]string +} + +// Secret create response +// swagger:response SecretCreateResponse +type SwagSecretCreateResponse struct { + // in:body + Body struct { + SecretCreateReport + } +} + +// Secret list response +// swagger:response SecretListResponse +type SwagSecretListResponse struct { + // in:body + Body []*SecretInfoReport +} + +// Secret list response +// swagger:response SecretListCompatResponse +type SwagSecretListCompatResponse struct { + // in:body + Body []*SecretInfoReportCompat +} + +// Secret inspect response +// swagger:response SecretInspectResponse +type SwagSecretInspectResponse struct { + // in:body + Body SecretInfoReport +} + +// Secret inspect compat +// swagger:response SecretInspectCompatResponse +type SwagSecretInspectCompatResponse struct { + // in:body + Body SecretInfoReportCompat +} + +// No such secret +// swagger:response NoSuchSecret +type SwagErrNoSuchSecret struct { + // in:body + Body struct { + errorhandling.ErrorModel + } +} + +// Secret in use +// swagger:response SecretInUse +type SwagErrSecretInUse struct { + // in:body + Body struct { + errorhandling.ErrorModel + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/system.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/system.go new file mode 100644 index 00000000..ab9c861a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/system.go @@ -0,0 +1,26 @@ +package entities + +import ( + "github.com/containers/podman/v5/pkg/domain/entities/types" +) + +// ServiceOptions provides the input for starting an API and sidecar pprof services +type ServiceOptions = types.ServiceOptions +type SystemPruneOptions = types.SystemPruneOptions +type SystemPruneReport = types.SystemPruneReport +type SystemMigrateOptions = types.SystemMigrateOptions +type SystemCheckOptions = types.SystemCheckOptions +type SystemCheckReport = types.SystemCheckReport +type SystemDfOptions = types.SystemDfOptions +type SystemDfReport = types.SystemDfReport +type SystemDfImageReport = types.SystemDfImageReport +type SystemDfContainerReport = types.SystemDfContainerReport +type SystemDfVolumeReport = types.SystemDfVolumeReport +type SystemVersionReport = types.SystemVersionReport +type SystemUnshareOptions = types.SystemUnshareOptions +type ComponentVersion = types.SystemComponentVersion +type ListRegistriesReport = types.ListRegistriesReport + +type AuthConfig = types.AuthConfig +type AuthReport = types.AuthReport +type LocksReport = types.LocksReport diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types.go new file mode 100644 index 00000000..455760f0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types.go @@ -0,0 +1,111 @@ +package entities + +import ( + "net" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/libpod/events" + entitiesTypes "github.com/containers/podman/v5/pkg/domain/entities/types" + "github.com/containers/podman/v5/pkg/specgen" + "go.podman.io/common/libnetwork/types" + "go.podman.io/storage/pkg/archive" +) + +type Report struct { + Id []string + Err map[string]error +} + +type PodDeleteReport struct{ Report } + +type ( + VolumeDeleteOptions struct{} + VolumeDeleteReport struct{ Report } +) + +type NetFlags struct { + AddHosts []string `json:"add-host,omitempty"` + DNS []string `json:"dns,omitempty"` + DNSOpt []string `json:"dns-opt,omitempty"` + DNDSearch []string `json:"dns-search,omitempty"` + MacAddr string `json:"mac-address,omitempty"` + Publish []string `json:"publish,omitempty"` + IP string `json:"ip,omitempty"` + NoHostname bool `json:"no-hostname,omitempty"` + NoHosts bool `json:"no-hosts,omitempty"` + Network string `json:"network,omitempty"` + NetworkAlias []string `json:"network-alias,omitempty"` +} + +// NetOptions reflect the shared network options between +// pods and containers +type NetOptions struct { + AddHosts []string `json:"hostadd,omitempty"` + Aliases []string `json:"network_alias,omitempty"` + Networks map[string]types.PerNetworkOptions `json:"networks,omitempty"` + UseImageResolvConf bool `json:"no_manage_resolv_conf,omitempty"` + DNSOptions []string `json:"dns_option,omitempty"` + DNSSearch []string `json:"dns_search,omitempty"` + DNSServers []net.IP `json:"dns_server,omitempty"` + HostsFile string `json:"hosts_file,omitempty"` + Network specgen.Namespace `json:"netns"` + NoHostname bool `json:"no_manage_hostname,omitempty"` + NoHosts bool `json:"no_manage_hosts,omitempty"` + PublishPorts []types.PortMapping `json:"portmappings,omitempty"` + // NetworkOptions are additional options for each network + NetworkOptions map[string][]string `json:"network_options,omitempty"` +} + +// InspectOptions all CLI inspect commands and inspect sub-commands use the same options +type InspectOptions struct { + // Format - change the output to JSON or a Go template. + Format string `json:",omitempty"` + // Latest - inspect the latest container Podman is aware of. + Latest bool `json:",omitempty"` + // Size (containers only) - display total file size. + Size bool `json:",omitempty"` + // Type -- return JSON for specified type. + Type string `json:",omitempty"` + // All -- inspect all + All bool `json:",omitempty"` +} + +// DiffOptions all API and CLI diff commands and diff sub-commands use the same options +type DiffOptions struct { + Format string `json:",omitempty"` // CLI only + Latest bool `json:",omitempty"` // API and CLI, only supported by containers + Type define.DiffType // Type which should be compared +} + +// DiffReport provides changes for object +type DiffReport struct { + Changes []archive.Change +} + +type EventsOptions struct { + FromStart bool + EventChan chan events.ReadResult + Filter []string + Stream bool + Since string + Until string +} + +// ContainerCreateResponse is the response struct for creating a container +type ContainerCreateResponse = entitiesTypes.ContainerCreateResponse + +// BuildOptions describe the options for building container images. +type BuildOptions = entitiesTypes.BuildOptions + +// BuildReport is the image-build report. +type BuildReport = entitiesTypes.BuildReport + +// FarmBuildOptions describes the options for building container images on farm nodes +type FarmBuildOptions = entitiesTypes.FarmBuildOptions + +type IDOrNameResponse struct { + // The Id or Name of an object + IDOrName string +} + +type IDResponse = entitiesTypes.IDResponse diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/artifacts.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/artifacts.go new file mode 100644 index 00000000..dd5a7544 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/artifacts.go @@ -0,0 +1,31 @@ +package types + +import ( + "github.com/opencontainers/go-digest" + "go.podman.io/common/pkg/libartifact" +) + +type ArtifactInspectReport struct { + *libartifact.Artifact + Digest string +} + +type ArtifactAddReport struct { + ArtifactDigest *digest.Digest +} + +type ArtifactRemoveReport struct { + ArtifactDigests []*digest.Digest +} + +type ArtifactListReport struct { + *libartifact.Artifact +} + +type ArtifactPushReport struct { + ArtifactDigest *digest.Digest +} + +type ArtifactPullReport struct { + ArtifactDigest *digest.Digest +} diff --git a/vendor/github.com/docker/cli/cli/config/types/authconfig.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/auth.go similarity index 91% rename from vendor/github.com/docker/cli/cli/config/types/authconfig.go rename to vendor/github.com/containers/podman/v5/pkg/domain/entities/types/auth.go index 056af6b8..3fa66987 100644 --- a/vendor/github.com/docker/cli/cli/config/types/authconfig.go +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/auth.go @@ -1,6 +1,8 @@ +// copied from github.com/docker/docker/api/types package types // AuthConfig contains authorization information for connecting to a Registry +// swagger:model type AuthConfig struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/container_ps.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/container_ps.go new file mode 100644 index 00000000..88892981 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/container_ps.go @@ -0,0 +1,120 @@ +package types + +import ( + "time" + + define "github.com/containers/podman/v5/pkg/ps/define" + netTypes "go.podman.io/common/libnetwork/types" +) + +// ListContainer describes a container suitable for listing +type ListContainer struct { + // AutoRemove + AutoRemove bool + // Container command + Command []string + // Container creation time + Created time.Time + // Human-readable container creation time. + CreatedAt string + // CIDFile specified at creation time. + CIDFile string + // If container has exited/stopped + Exited bool + // Time container exited + ExitedAt int64 + // If container has exited, the return code from the command + ExitCode int32 + // ExposedPorts contains the ports that are exposed but not forwarded, + // see Ports for forwarded ports. + // The key is the port number and the string slice contains the protocols, + // i.e. "tcp", "udp" and "sctp". + ExposedPorts map[uint16][]string + // The unique identifier for the container + ID string `json:"Id"` + // Container image + Image string + // Container image ID + ImageID string + // If this container is a Pod infra container + IsInfra bool + // Labels for container + Labels map[string]string + // User volume mounts + Mounts []string + // The names assigned to the container + Names []string + // Namespaces the container belongs to. Requires the + // namespace boolean to be true + Namespaces ListContainerNamespaces + // The network names assigned to the container + Networks []string + // The process id of the container + Pid int + // If the container is part of Pod, the Pod ID. Requires the pod + // boolean to be set + Pod string + // If the container is part of Pod, the Pod name. Requires the pod + // boolean to be set + PodName string + // Port mappings + Ports []netTypes.PortMapping + // Restarts is how many times the container was restarted by its + // restart policy. This is NOT incremented by normal container restarts + // (only by restart policy). + Restarts uint + // Size of the container rootfs. Requires the size boolean to be true + Size *define.ContainerSize + // Time when container started + StartedAt int64 + // State of container + State string + // Status is a human-readable approximation of a duration for json output + Status string +} + +// ListContainerNamespaces contains the identifiers of the container's Linux namespaces +type ListContainerNamespaces struct { + // Mount namespace + MNT string `json:"Mnt,omitempty"` + // Cgroup namespace + Cgroup string `json:"Cgroup,omitempty"` + // IPC namespace + IPC string `json:"Ipc,omitempty"` + // Network namespace + NET string `json:"Net,omitempty"` + // PID namespace + PIDNS string `json:"Pidns,omitempty"` + // UTS namespace + UTS string `json:"Uts,omitempty"` + // User namespace + User string `json:"User,omitempty"` +} + +func (l ListContainer) CGROUPNS() string { + return l.Namespaces.Cgroup +} + +func (l ListContainer) IPC() string { + return l.Namespaces.IPC +} + +func (l ListContainer) MNT() string { + return l.Namespaces.MNT +} + +func (l ListContainer) NET() string { + return l.Namespaces.NET +} + +func (l ListContainer) PIDNS() string { + return l.Namespaces.PIDNS +} + +func (l ListContainer) USERNS() string { + return l.Namespaces.User +} + +func (l ListContainer) UTS() string { + return l.Namespaces.UTS +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/containers.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/containers.go new file mode 100644 index 00000000..2e8441db --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/containers.go @@ -0,0 +1,91 @@ +package types + +import ( + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/specgen" + "github.com/opencontainers/runtime-spec/specs-go" +) + +type ContainerCopyFunc func() error + +type ContainerStatReport struct { + define.FileInfo +} + +type CheckpointReport struct { + Err error `json:"-"` + Id string `json:"Id"` + RawInput string `json:"-"` + RuntimeDuration int64 `json:"runtime_checkpoint_duration"` + CRIUStatistics *define.CRIUCheckpointRestoreStatistics `json:"criu_statistics"` +} + +type RestoreReport struct { + Err error `json:"-"` + Id string `json:"Id"` + RawInput string `json:"-"` + RuntimeDuration int64 `json:"runtime_restore_duration"` + CRIUStatistics *define.CRIUCheckpointRestoreStatistics `json:"criu_statistics"` +} + +// ContainerStatsReport is used for streaming container stats. +type ContainerStatsReport struct { + // Error from reading stats. + Error error + // Results, set when there is no error. + Stats []define.ContainerStats +} + +type ContainerUpdateOptions struct { + NameOrID string + // This individual items of Specgen are used to update container configuration: + // - ResourceLimits + // - RestartPolicy + // - RestartRetries + // + // Deprecated: Specgen should not be used to change the container configuration. + // Specgen is processed first, so values will be overwritten by values from other fields. + // + // To change configuration use other fields in ContainerUpdateOptions struct: + // - Resources to change resource configuration + // - DevicesLimits to Limit device + // - RestartPolicy to change restart policy + // - RestartRetries to change restart retries + // - Env to change the environment variables. + // - UntsetEnv to unset the environment variables. + Specgen *specgen.SpecGenerator + Resources *specs.LinuxResources + DevicesLimits *define.UpdateContainerDevicesLimits + ChangedHealthCheckConfiguration *define.UpdateHealthCheckConfig + RestartPolicy *string + RestartRetries *uint + Env []string + UnsetEnv []string + Latest bool +} + +func (u *ContainerUpdateOptions) ProcessSpecgen() { + if u.Specgen == nil { + return + } + + if u.Resources == nil { + u.Resources = u.Specgen.ResourceLimits + } + + if u.DevicesLimits == nil { + u.DevicesLimits = new(define.UpdateContainerDevicesLimits) + u.DevicesLimits.SetBlkIOWeightDevice(u.Specgen.WeightDevice) + u.DevicesLimits.SetDeviceReadBPs(u.Specgen.ThrottleReadBpsDevice) + u.DevicesLimits.SetDeviceWriteBPs(u.Specgen.ThrottleWriteBpsDevice) + u.DevicesLimits.SetDeviceReadIOPs(u.Specgen.ThrottleReadIOPSDevice) + u.DevicesLimits.SetDeviceWriteIOPs(u.Specgen.ThrottleWriteIOPSDevice) + } + + if u.RestartPolicy == nil { + u.RestartPolicy = &u.Specgen.RestartPolicy + } + if u.RestartRetries == nil { + u.RestartRetries = u.Specgen.RestartRetries + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/events.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/events.go new file mode 100644 index 00000000..a9e9dd6a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/events.go @@ -0,0 +1,14 @@ +package types + +import ( + dockerEvents "github.com/docker/docker/api/types/events" +) + +// Event combines various event-related data such as time, event type, status +// and more. +type Event struct { + // TODO: it would be nice to have full control over the types at some + // point and fork such Docker types. + dockerEvents.Message + HealthStatus string `json:",omitempty"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/generate.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/generate.go new file mode 100644 index 00000000..45d4241a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/generate.go @@ -0,0 +1,20 @@ +package types + +import ( + "io" +) + +type GenerateSystemdReport struct { + // Units of the generate process. key = unit name -> value = unit content + Units map[string]string +} + +type GenerateKubeReport struct { + // FIXME: Podman4.0 should change io.Reader to io.ReaderCloser + // Reader - the io.Reader to reader the generated YAML file. + Reader io.Reader +} + +type GenerateSpecReport struct { + Data []byte +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/images.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/images.go new file mode 100644 index 00000000..744cd646 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/images.go @@ -0,0 +1,157 @@ +package types + +import ( + "time" + + "github.com/containers/podman/v5/pkg/inspect" + "github.com/containers/podman/v5/pkg/trust" +) + +// swagger:model LibpodImageSummary +type ImageSummary struct { + ID string `json:"Id"` + ParentId string + RepoTags []string + RepoDigests []string + Created int64 + Size int64 + SharedSize int + VirtualSize int64 `json:",omitempty"` + Labels map[string]string + Containers int + ReadOnly bool `json:",omitempty"` + Dangling bool `json:",omitempty"` + + // Podman extensions + Arch string `json:",omitempty"` + Digest string `json:",omitempty"` + History []string `json:",omitempty"` + // IsManifestList is a ptr so we can distinguish between a true + // json empty response and false. the docker compat side needs to return + // empty; where as the libpod side needs a value of true or false + IsManifestList *bool `json:",omitempty"` + Names []string `json:",omitempty"` + Os string `json:",omitempty"` +} + +func (i *ImageSummary) Id() string { + return i.ID +} + +func (i *ImageSummary) IsReadOnly() bool { + return i.ReadOnly +} + +func (i *ImageSummary) IsDangling() bool { + return i.Dangling +} + +type ImageInspectReport struct { + *inspect.ImageData +} + +type ImageTreeReport struct { + Tree string // TODO: Refactor move presentation work out of server +} + +type ImageLoadReport struct { + Names []string +} + +type ImageImportReport struct { + Id string +} + +// ImageSearchReport is the response from searching images. +type ImageSearchReport struct { + // Index is the image index (e.g., "docker.io" or "quay.io") + Index string + // Name is the canonical name of the image (e.g., "docker.io/library/alpine"). + Name string + // Description of the image. + Description string + // Stars is the number of stars of the image. + Stars int + // Official indicates if it's an official image. + Official string + // Automated indicates if the image was created by an automated build. + Automated string + // Tag is the repository tag + Tag string +} + +// ShowTrustReport describes the results of show trust +type ShowTrustReport struct { + Raw []byte + SystemRegistriesDirPath string + JSONOutput []byte + Policies []*trust.Policy +} + +// ImageMountReport describes the response from image mount +type ImageMountReport struct { + Id string + Name string + Repositories []string + Path string +} + +// ImageUnmountReport describes the response from umounting an image +type ImageUnmountReport struct { + Err error + Id string +} + +// FarmInspectReport describes the response from farm inspect +type FarmInspectReport struct { + NativePlatforms []string + EmulatedPlatforms []string + OS string + Arch string + Variant string +} + +// ImageRemoveReport is the response for removing one or more image(s) from storage +// and images what was untagged vs actually removed. +type ImageRemoveReport struct { + // Deleted images. + Deleted []string `json:",omitempty"` + // Untagged images. Can be longer than Deleted. + Untagged []string `json:",omitempty"` + // ExitCode describes the exit codes as described in the `podman rmi` + // man page. + ExitCode int +} + +type ImageHistoryLayer struct { + ID string `json:"id"` + Created time.Time `json:"created"` + CreatedBy string `json:",omitempty"` + Tags []string `json:"tags,omitempty"` + Size int64 `json:"size"` + Comment string `json:"comment,omitempty"` +} + +type ImageHistoryReport struct { + Layers []ImageHistoryLayer +} + +type ImagePullReport struct { + // Stream used to provide output from c/image + Stream string `json:"stream,omitempty"` + // Error contains text of errors from c/image + Error string `json:"error,omitempty"` + // Images contains the ID's of the images pulled + Images []string `json:"images,omitempty"` + // ID contains image id (retained for backwards compatibility) + ID string `json:"id,omitempty"` +} + +type ImagePushStream struct { + // ManifestDigest is the digest of the manifest of the pushed image. + ManifestDigest string `json:"manifestdigest,omitempty"` + // Stream used to provide push progress + Stream string `json:"stream,omitempty"` + // Error contains text of errors from pushing + Error string `json:"error,omitempty"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/manifest.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/manifest.go new file mode 100644 index 00000000..493950bc --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/manifest.go @@ -0,0 +1,36 @@ +package types + +// swagger:model +type ManifestPushReport struct { + // ID of the pushed manifest + ID string `json:"Id"` + // Stream used to provide push progress + Stream string `json:"stream,omitempty"` + // Error contains text of errors from pushing + Error string `json:"error,omitempty"` +} + +// swagger:model +type ManifestModifyReport struct { + // Manifest List ID + ID string `json:"Id"` + // Images added to or removed from manifest list, otherwise not provided. + Images []string `json:"images,omitempty" schema:"images"` + // Files added to manifest list, otherwise not provided. + Files []string `json:"files,omitempty" schema:"files"` + // Errors associated with operation + Errors []error `json:"errors,omitempty"` +} + +// swagger:model +type ManifestRemoveReport struct { + // Deleted manifest list. + Deleted []string `json:",omitempty"` + // Untagged images. Can be longer than Deleted. + Untagged []string `json:",omitempty"` + // Errors associated with operation + Errors []string `json:",omitempty"` + // ExitCode describes the exit codes as described in the `podman rmi` + // man page. + ExitCode int +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/network.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/network.go new file mode 100644 index 00000000..2e60df30 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/network.go @@ -0,0 +1,50 @@ +package types + +import ( + commonTypes "go.podman.io/common/libnetwork/types" +) + +// NetworkPruneReport containers the name of network and an error +// associated in its pruning (removal) +// swagger:model NetworkPruneReport +type NetworkPruneReport struct { + Name string + Error error +} + +// NetworkReloadReport describes the results of reloading a container network. +type NetworkReloadReport struct { + Id string + Err error +} + +// NetworkConnectOptions describes options for connecting +// a container to a network +type NetworkConnectOptions struct { + Container string `json:"container"` + commonTypes.PerNetworkOptions +} + +// NetworkRmReport describes the results of network removal +type NetworkRmReport struct { + Name string + Err error +} + +type NetworkCreateReport struct { + Name string +} + +type NetworkInspectReport struct { + commonTypes.Network + + Containers map[string]NetworkContainerInfo `json:"containers"` +} + +type NetworkContainerInfo struct { + // Name of the container + Name string `json:"name"` + + // Interfaces configured for this container with their addresses + Interfaces map[string]commonTypes.NetInterface `json:"interfaces,omitempty"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/play.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/play.go new file mode 100644 index 00000000..7f744106 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/play.go @@ -0,0 +1,48 @@ +package types + +type PlayKubePod struct { + // ID - ID of the pod created as a result of play kube. + ID string + // Containers - the IDs of the containers running in the created pod. + Containers []string + // InitContainers - the IDs of the init containers to be run in the created pod. + InitContainers []string + // Logs - non-fatal errors and log messages while processing. + Logs []string + // ContainerErrors - any errors that occurred while starting containers + // in the pod. + ContainerErrors []string +} + +type PlayKubeVolume struct { + // Name - Name of the volume created by play kube. + Name string +} + +type PlayKubeReport struct { + // Pods - pods created by play kube. + Pods []PlayKubePod + // Volumes - volumes created by play kube. + Volumes []PlayKubeVolume + PlayKubeTeardown + // Secrets - secrets created by play kube + Secrets []PlaySecret + // ServiceContainerID - ID of the service container if one is created + ServiceContainerID string + // If set, exit with the specified exit code. + ExitCode *int32 +} + +type KubePlayReport = PlayKubeReport + +// PlayKubeDownReport contains the results of tearing down play kube +type PlayKubeTeardown struct { + StopReport []*PodStopReport + RmReport []*PodRmReport + VolumeRmReport []*VolumeRmReport + SecretRmReport []*SecretRmReport +} + +type PlaySecret struct { + CreateReport *SecretCreateReport +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/pods.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/pods.go new file mode 100644 index 00000000..8d150a89 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/pods.go @@ -0,0 +1,120 @@ +package types + +import ( + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/specgen" +) + +type PodPruneReport struct { + Err error + Id string +} + +type PodPauseReport struct { + Errs []error + Id string +} +type PodUnpauseReport struct { + Errs []error + Id string +} + +type PodStopReport struct { + Errs []error + Id string + RawInput string +} + +type PodRestartReport struct { + Errs []error + Id string +} + +type PodStartReport struct { + Errs []error + Id string + RawInput string +} + +type PodRmReport struct { + RemovedCtrs map[string]error + Err error + Id string +} + +type PodCreateReport struct { + Id string +} + +type PodCloneReport struct { + Id string +} + +// PodStatsReport includes pod-resource statistics data. +type PodStatsReport struct { + // Percentage of CPU utilized by pod + // example: 75.5% + CPU string + // Humanized Memory usage and maximum + // example: 12mb / 24mb + MemUsage string + // Memory usage and maximum in bytes + // example: 1,000,000 / 4,000,000 + MemUsageBytes string + // Percentage of Memory utilized by pod + // example: 50.5% + Mem string + // Network usage inbound + outbound + NetIO string + // Humanized disk usage read + write + BlockIO string + // Container PID + PIDS string + // Pod ID + // example: 62310217a19e + Pod string + // Container ID + // example: e43534f89a7d + CID string + // Pod Name + // example: elastic_pascal + Name string +} + +// PodSpec is an abstracted version of PodSpecGen designed to eventually accept options +// not meant to be in a specgen +type PodSpec struct { + PodSpecGen specgen.PodSpecGenerator +} + +type PodInspectReport struct { + *define.InspectPodData +} + +type PodKillReport struct { + Errs []error + Id string +} + +type ListPodsReport struct { + Cgroup string + Containers []*ListPodContainer + Created time.Time + Id string + InfraId string + Name string + Namespace string + // Network names connected to infra container + Networks []string + Status string + Labels map[string]string +} + +type ListPodContainer struct { + Id string + Names string + Status string + RestartCount uint +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/secrets.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/secrets.go new file mode 100644 index 00000000..0ba544db --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/secrets.go @@ -0,0 +1,50 @@ +package types + +import ( + "time" +) + +type SecretSpec struct { + Name string + Driver SecretDriverSpec + Labels map[string]string +} + +type SecretVersion struct { + Index int +} + +type SecretDriverSpec struct { + Name string + Options map[string]string +} + +type SecretCreateReport struct { + ID string +} + +type SecretListReport struct { + ID string + Name string + Driver string + CreatedAt string + UpdatedAt string +} + +type SecretRmReport struct { + ID string + Err error +} + +type SecretInfoReport struct { + ID string + CreatedAt time.Time + UpdatedAt time.Time + Spec SecretSpec + SecretData string `json:"SecretData,omitempty"` +} + +type SecretInfoReportCompat struct { + SecretInfoReport + Version SecretVersion +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/system.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/system.go new file mode 100644 index 00000000..b2243b46 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/system.go @@ -0,0 +1,145 @@ +package types + +import ( + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/domain/entities/reports" +) + +// ServiceOptions provides the input for starting an API and sidecar pprof services +type ServiceOptions struct { + CorsHeaders string // Cross-Origin Resource Sharing (CORS) headers + PProfAddr string // Network address to bind pprof profiles service + Timeout time.Duration // Duration of inactivity the service should wait before shutting down + URI string // Path to unix domain socket service should listen on + TLSCertFile string // Path to serving certificate PEM file + TLSKeyFile string // Path to serving certificate key PEM file + TLSClientCAFile string // Path to client certificate authority +} + +// SystemCheckOptions provides options for checking storage consistency. +type SystemCheckOptions struct { + Quick bool // skip the most time-intensive checks + Repair bool // remove damaged images + RepairLossy bool // remove damaged containers + UnreferencedLayerMaximumAge *time.Duration // maximum allowed age for unreferenced layers +} + +// SystemCheckReport provides a report of what a storage consistency check +// found, and if we removed anything that was damaged, what we removed. +type SystemCheckReport struct { + Errors bool // any errors were detected + Layers map[string][]string // layer ID → what was detected + ROLayers map[string][]string // layer ID → what was detected + RemovedLayers []string // layer ID + Images map[string][]string // image ID → what was detected + ROImages map[string][]string // image ID → what was detected + RemovedImages map[string][]string // image ID → names + Containers map[string][]string // container ID → what was detected + RemovedContainers map[string]string // container ID → name +} + +// SystemPruneOptions provides options to prune system. +type SystemPruneOptions struct { + All bool + Volume bool + Filters map[string][]string `json:"filters" schema:"filters"` + External bool + Build bool +} + +// SystemPruneReport provides report after system prune is executed. +type SystemPruneReport struct { + PodPruneReport []*PodPruneReport + ContainerPruneReports []*reports.PruneReport + ImagePruneReports []*reports.PruneReport + NetworkPruneReports []*NetworkPruneReport + VolumePruneReports []*reports.PruneReport + ReclaimedSpace uint64 +} + +// SystemMigrateOptions describes the options needed for the +// cli to migrate runtimes of containers +type SystemMigrateOptions struct { + NewRuntime string +} + +// SystemDfOptions describes the options for getting df information +type SystemDfOptions struct { + Format string + Verbose bool +} + +// SystemDfReport describes the response for df information +type SystemDfReport struct { + ImagesSize int64 + Images []*SystemDfImageReport + Containers []*SystemDfContainerReport + Volumes []*SystemDfVolumeReport +} + +// SystemDfImageReport describes an image for use with df +type SystemDfImageReport struct { + Repository string + Tag string + ImageID string + Created time.Time + Size int64 + SharedSize int64 + UniqueSize int64 + Containers int +} + +// SystemDfContainerReport describes a container for use with df +type SystemDfContainerReport struct { + ContainerID string + Image string + Command []string + LocalVolumes int + Size int64 + RWSize int64 + Created time.Time + Status string + Names string +} + +// SystemDfVolumeReport describes a volume and its size +type SystemDfVolumeReport struct { + VolumeName string + Links int + Size int64 + ReclaimableSize int64 +} + +// SystemVersionReport describes version information about the running Podman service +type SystemVersionReport struct { + // Always populated + Client *define.Version `json:",omitempty"` + // May be populated, when in tunnel mode + Server *define.Version `json:",omitempty"` +} + +// SystemUnshareOptions describes the options for the unshare command +type SystemUnshareOptions struct { + RootlessNetNS bool +} + +// ListRegistriesReport is the report when querying for a sorted list of +// registries which may be contacted during certain operations. +type ListRegistriesReport struct { + Registries []string +} + +// AuthReport describes the response for authentication check +type AuthReport struct { + IdentityToken string + Status string +} + +// LocksReport describes any conflicts in Libpod's lock allocations that could +// lead to deadlocks. +type LocksReport struct { + LockConflicts map[uint32][]string + LocksHeld []uint32 +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/types.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/types.go new file mode 100644 index 00000000..e9bbb14d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/types.go @@ -0,0 +1,87 @@ +// copied from github.com/docker/docker/api/types +package types + +import ( + "os" + + buildahDefine "github.com/containers/buildah/define" +) + +// ComponentVersion describes the version information for a specific component. +type ComponentVersion struct { + Name string + Version string + Details map[string]string `json:",omitempty"` +} + +// Version contains response of Engine API: +// GET "/version" +type Version struct { + Platform struct{ Name string } + Components []ComponentVersion `json:",omitempty"` + + // The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility + + Version string + APIVersion string `json:"ApiVersion"` + MinAPIVersion string `json:"MinAPIVersion,omitempty"` + GitCommit string + GoVersion string + Os string + Arch string + KernelVersion string `json:",omitempty"` + Experimental bool `json:",omitempty"` + BuildTime string `json:",omitempty"` +} + +// SystemComponentVersion is the type used by pkg/domain/entities +type SystemComponentVersion struct { + Version +} + +// ContainerCreateResponse is the response struct for creating a container +type ContainerCreateResponse struct { + // ID of the container created + // required: true + ID string `json:"Id"` + // Warnings during container creation + // required: true + Warnings []string `json:"Warnings"` +} + +// FarmBuildOptions describes the options for building container images on farm nodes +type FarmBuildOptions struct { + // Cleanup removes built images from farm nodes on success + Cleanup bool + // Authfile is the path to the file holding registry credentials + Authfile string + // SkipTLSVerify skips tls verification when set to true + SkipTLSVerify *bool +} + +// BuildOptions describe the options for building container images. +type BuildOptions struct { + buildahDefine.BuildOptions + ContainerFiles []string + FarmBuildOptions + // Files that need to be closed after the build + // so need to pass this to the main build functions + LogFileToClose *os.File + TmpDirToClose string +} + +// BuildReport is the image-build report. +type BuildReport struct { + // ID of the image. + ID string + // Format to save the image in + SaveFormat string +} + +// swagger:model +type IDResponse struct { + + // The id of the newly created object. + // Required: true + ID string `json:"Id"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/volumes.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/volumes.go new file mode 100644 index 00000000..06e3727f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/types/volumes.go @@ -0,0 +1,57 @@ +package types + +import ( + "github.com/containers/podman/v5/libpod/define" +) + +// swagger:model +type VolumeCreateOptions struct { + // New volume's name. Can be left blank + Name string `schema:"name"` + // Volume driver to use + Driver string `schema:"driver"` + // User-defined key/value metadata. Provided for compatibility + Label map[string]string `schema:"label"` + // User-defined key/value metadata. Preferred field, will override Label + Labels map[string]string `schema:"labels"` + // Mapping of driver options and values. + Options map[string]string `schema:"opts"` + // Ignore existing volumes + IgnoreIfExists bool `schema:"ignoreIfExist"` + // UID that the volume will be created as + UID *int `schema:"uid"` + // GID that the volume will be created as + GID *int `schema:"gid"` +} + +type VolumeRmReport struct { + Err error + Id string +} +type VolumeInspectReport struct { + *VolumeConfigResponse +} + +type VolumeListReport struct { + VolumeConfigResponse +} + +type VolumeReloadReport struct { + define.VolumeReload +} + +type VolumeMountReport struct { + Err error + Id string + Name string + Path string +} + +type VolumeUnmountReport struct { + Err error + Id string +} + +type VolumeConfigResponse struct { + define.InspectVolumeData +} diff --git a/vendor/github.com/containers/podman/v5/pkg/domain/entities/volumes.go b/vendor/github.com/containers/podman/v5/pkg/domain/entities/volumes.go new file mode 100644 index 00000000..fe310f4f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/domain/entities/volumes.go @@ -0,0 +1,56 @@ +package entities + +import ( + "io" + "net/url" + + "github.com/containers/podman/v5/pkg/domain/entities/types" +) + +// VolumeCreateOptions provides details for creating volumes +type VolumeCreateOptions = types.VolumeCreateOptions + +type VolumeConfigResponse = types.VolumeConfigResponse + +type VolumeRmOptions struct { + All bool + Force bool + Ignore bool + Timeout *uint +} + +type VolumeRmReport = types.VolumeRmReport + +type VolumeInspectReport = types.VolumeInspectReport + +// VolumePruneOptions describes the options needed +// to prune a volume from the CLI +type VolumePruneOptions struct { + Filters url.Values `json:"filters" schema:"filters"` +} + +type VolumeListOptions struct { + Filter map[string][]string +} + +type VolumeListReport = types.VolumeListReport + +// VolumeReloadReport describes the response from reload volume plugins +type VolumeReloadReport = types.VolumeReloadReport + +// VolumeMountReport describes the response from volume mount +type VolumeMountReport = types.VolumeMountReport + +// VolumeUnmountReport describes the response from umounting a volume +type VolumeUnmountReport = types.VolumeUnmountReport + +// VolumeExportOptions describes the options required to export a volume. +type VolumeExportOptions struct { + Output io.Writer +} + +// VolumeImportOptions describes the options required to import a volume +type VolumeImportOptions struct { + // Input will be closed upon being fully consumed + Input io.Reader +} diff --git a/vendor/github.com/containers/podman/v5/pkg/env/env.go b/vendor/github.com/containers/podman/v5/pkg/env/env.go new file mode 100644 index 00000000..7770d194 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/env/env.go @@ -0,0 +1,119 @@ +// Package for processing environment variables. +package env + +// TODO: we need to add tests for this package. + +import ( + "bufio" + "fmt" + "maps" + "os" + "strings" +) + +const whiteSpaces = " \t" + +// DefaultEnvVariables returns a default environment, with $PATH and $TERM set. +func DefaultEnvVariables() map[string]string { + return map[string]string{ + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "container": "podman", + } +} + +// Slice transforms the specified map of environment variables into a +// slice. If a value is non-empty, the key and value are joined with '='. +func Slice(m map[string]string) []string { + env := make([]string, 0, len(m)) + for k, v := range m { + var s string + if len(v) > 0 { + s = fmt.Sprintf("%s=%s", k, v) + } else { + s = k + } + env = append(env, s) + } + return env +} + +// Map transforms the specified slice of environment variables into a +// map. +func Map(slice []string) map[string]string { + envmap := make(map[string]string, len(slice)) + for _, line := range slice { + key, val, _ := strings.Cut(line, "=") + envmap[key] = val + } + return envmap +} + +// Join joins the two environment maps with override overriding base. +func Join(base map[string]string, override map[string]string) map[string]string { + if len(base) == 0 { + return maps.Clone(override) + } + base = maps.Clone(base) + maps.Copy(base, override) + return base +} + +// ParseFile parses the specified path for environment variables and returns them +// as a map. +func ParseFile(path string) (_ map[string]string, err error) { + env := make(map[string]string) + defer func() { + if err != nil { + err = fmt.Errorf("parsing file %q: %w", path, err) + } + }() + + fh, err := os.Open(path) + if err != nil { + return nil, err + } + defer fh.Close() + + scanner := bufio.NewScanner(fh) + for scanner.Scan() { + // trim the line from all leading whitespace first + line := strings.TrimLeft(scanner.Text(), whiteSpaces) + // line is not empty, and not starting with '#' + if len(line) > 0 && !strings.HasPrefix(line, "#") { + if err := parseEnv(env, line); err != nil { + return nil, err + } + } + } + return env, scanner.Err() +} + +func parseEnv(env map[string]string, line string) error { + key, val, hasVal := strings.Cut(line, "=") + + // catch invalid variables such as "=" or "=A" + if key == "" { + return fmt.Errorf("invalid variable: %q", line) + } + // trim the front of a variable, but nothing else + name := strings.TrimLeft(key, whiteSpaces) + if hasVal { + env[name] = val + } else { + if name, hasStar := strings.CutSuffix(name, "*"); hasStar { + for _, e := range os.Environ() { + envKey, envVal, hasEq := strings.Cut(e, "=") + if !hasEq { + continue + } + if strings.HasPrefix(envKey, name) { + env[envKey] = envVal + } + } + } else if val, ok := os.LookupEnv(name); ok { + // if only a pass-through variable is given, clean it up. + env[name] = val + } + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/env/env_unix.go b/vendor/github.com/containers/podman/v5/pkg/env/env_unix.go new file mode 100644 index 00000000..5e779cd5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/env/env_unix.go @@ -0,0 +1,15 @@ +//go:build !windows + +package env + +// ParseSlice parses the specified slice and transforms it into an environment +// map. +func ParseSlice(s []string) (map[string]string, error) { + env := make(map[string]string, len(s)) + for _, e := range s { + if err := parseEnv(env, e); err != nil { + return nil, err + } + } + return env, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/env/env_windows.go b/vendor/github.com/containers/podman/v5/pkg/env/env_windows.go new file mode 100644 index 00000000..1496dbfe --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/env/env_windows.go @@ -0,0 +1,25 @@ +package env + +// ParseSlice parses the specified slice and transforms it into an environment +// map. +func ParseSlice(s []string) (map[string]string, error) { + env := make(map[string]string, len(s)) + for _, e := range s { + if len(e) > 0 && e[0] == '=' { + // The legacy Windows CMD command interpreter uses a hack, where to emulate + // DOS semantics, it uses an illegal (by windows definition) env name for + // state storage to avoid conflicting with user defined env names. This is + // used to preserve drive letter paths. E.g., typing c: from another drive + // will remember the last CWD because CMD stores it in an env named "=C:". + // Since these are illegal, they are filtered from standard user access but + // are still available in the underlying win32 API calls. Since they have + // zero value to a container, we filter as well. + continue + } + + if err := parseEnv(env, e); err != nil { + return nil, err + } + } + return env, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/errorhandling/errorhandling.go b/vendor/github.com/containers/podman/v5/pkg/errorhandling/errorhandling.go new file mode 100644 index 00000000..a63ea091 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/errorhandling/errorhandling.go @@ -0,0 +1,134 @@ +package errorhandling + +import ( + "errors" + "os" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/sirupsen/logrus" +) + +// JoinErrors converts the error slice into a single human-readable error. +func JoinErrors(errs []error) error { + if len(errs) == 0 { + return nil + } + + // If there's just one error, return it. This prevents the "%d errors + // occurred:" header plus list from the multierror package. + if len(errs) == 1 { + return errs[0] + } + + // `multierror` appends new lines which we need to remove to prevent + // blank lines when printing the error. + var multiE *multierror.Error + multiE = multierror.Append(multiE, errs...) + + finalErr := multiE.ErrorOrNil() + if finalErr == nil { + return nil + } + return errors.New(strings.TrimSpace(finalErr.Error())) +} + +// ErrorsToString converts the slice of errors into a slice of corresponding +// error messages. +func ErrorsToStrings(errs []error) []string { + if len(errs) == 0 { + return nil + } + strErrs := make([]string, len(errs)) + for i := range errs { + strErrs[i] = errs[i].Error() + } + return strErrs +} + +// StringsToErrors converts a slice of error messages into a slice of +// corresponding errors. +func StringsToErrors(strErrs []string) []error { + if len(strErrs) == 0 { + return nil + } + errs := make([]error, len(strErrs)) + for i := range strErrs { + errs[i] = errors.New(strErrs[i]) + } + return errs +} + +// CloseQuiet closes a file and logs any error. Should only be used within +// a defer. +func CloseQuiet(f *os.File) { + if err := f.Close(); err != nil { + logrus.Errorf("Unable to close file %s: %q", f.Name(), err) + } +} + +// Contains checks if err's message contains sub's message. Contains should be +// used iff either err or sub has lost type information (e.g., due to +// marshalling). For typed errors, please use `errors.Contains(...)` or `Is()` +// in recent version of Go. +func Contains(err error, sub error) bool { + return strings.Contains(err.Error(), sub.Error()) +} + +// PodConflictErrorModel is used in remote connections with podman +type PodConflictErrorModel struct { + Errs []string + Id string +} + +// ErrorModel is used in remote connections with podman +type ErrorModel struct { + // API root cause formatted for automated parsing + // example: API root cause + Because string `json:"cause"` + // human error message, formatted for a human to read + // example: human error message + Message string `json:"message"` + // HTTP response code + // min: 400 + ResponseCode int `json:"response"` +} + +func (e ErrorModel) Error() string { + return e.Message +} + +func (e ErrorModel) Cause() error { + return errors.New(e.Because) +} + +func (e ErrorModel) Code() int { + return e.ResponseCode +} + +func (e PodConflictErrorModel) Error() string { + return strings.Join(e.Errs, ",") +} + +func (e PodConflictErrorModel) Code() int { + return 409 +} + +// Cause returns the most underlying error for the provided one. There is a +// maximum error depth of 100 to avoid endless loops. An additional error log +// message will be created if this maximum has reached. +func Cause(err error) (cause error) { + cause = err + + const maxDepth = 100 + for i := 0; i <= maxDepth; i++ { + res := errors.Unwrap(cause) + if res == nil { + return cause + } + cause = res + } + + logrus.Errorf("Max error depth of %d reached, cannot unwrap until root cause: %v", maxDepth, err) + return cause +} diff --git a/vendor/github.com/containers/podman/v5/pkg/inspect/inspect.go b/vendor/github.com/containers/podman/v5/pkg/inspect/inspect.go new file mode 100644 index 00000000..f516931f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/inspect/inspect.go @@ -0,0 +1,43 @@ +package inspect + +import ( + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "go.podman.io/image/v5/manifest" +) + +// ImageData holds the inspect information of an image. +type ImageData struct { + ID string `json:"Id"` + Digest digest.Digest `json:"Digest"` + RepoTags []string `json:"RepoTags"` + RepoDigests []string `json:"RepoDigests"` + Parent string `json:"Parent"` + Comment string `json:"Comment"` + Created *time.Time `json:"Created"` + Config *v1.ImageConfig `json:"Config"` + Version string `json:"Version"` + Author string `json:"Author"` + Architecture string `json:"Architecture"` + Os string `json:"Os"` + Size int64 `json:"Size"` + VirtualSize int64 `json:"VirtualSize"` + GraphDriver *define.DriverData `json:"GraphDriver"` + RootFS *RootFS `json:"RootFS"` + Labels map[string]string `json:"Labels"` + Annotations map[string]string `json:"Annotations"` + ManifestType string `json:"ManifestType"` + User string `json:"User"` + History []v1.History `json:"History"` + NamesHistory []string `json:"NamesHistory"` + HealthCheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"` +} + +// RootFS holds the root fs information of an image. +type RootFS struct { + Type string `json:"Type"` + Layers []digest.Digest `json:"Layers"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/LICENSE b/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/core/v1/annotation_key_constants.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/core/v1/annotation_key_constants.go new file mode 100644 index 00000000..eb9517e1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/core/v1/annotation_key_constants.go @@ -0,0 +1,151 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This file should be consistent with pkg/api/annotation_key_constants.go. + +package v1 + +const ( + // ImagePolicyFailedOpenKey is added to pods created by failing open when the image policy + // webhook backend fails. + ImagePolicyFailedOpenKey string = "alpha.image-policy.k8s.io/failed-open" + + // MirrorAnnotationKey represents the annotation key set by kubelets when creating mirror pods + MirrorPodAnnotationKey string = "kubernetes.io/config.mirror" + + // TolerationsAnnotationKey represents the key of tolerations data (json serialized) + // in the Annotations of a Pod. + TolerationsAnnotationKey string = "scheduler.alpha.kubernetes.io/tolerations" + + // TaintsAnnotationKey represents the key of taints data (json serialized) + // in the Annotations of a Node. + TaintsAnnotationKey string = "scheduler.alpha.kubernetes.io/taints" + + // SeccompPodAnnotationKey represents the key of a seccomp profile applied + // to all containers of a pod. + // Deprecated: set a pod security context `seccompProfile` field. + SeccompPodAnnotationKey string = "seccomp.security.alpha.kubernetes.io/pod" + + // SeccompContainerAnnotationKeyPrefix represents the key of a seccomp profile applied + // to one container of a pod. + // Deprecated: set a container security context `seccompProfile` field. + SeccompContainerAnnotationKeyPrefix string = "container.seccomp.security.alpha.kubernetes.io/" + + // SeccompProfileRuntimeDefault represents the default seccomp profile used by container runtime. + // Deprecated: set a pod or container security context `seccompProfile` of type "RuntimeDefault" instead. + SeccompProfileRuntimeDefault string = "runtime/default" + + // SeccompProfileNameUnconfined is the unconfined seccomp profile. + SeccompProfileNameUnconfined string = "unconfined" + + // SeccompLocalhostProfileNamePrefix is the prefix for specifying profiles loaded from the node's disk. + SeccompLocalhostProfileNamePrefix = "localhost/" + + // AppArmorBetaContainerAnnotationKeyPrefix is the prefix to an annotation key specifying a container's apparmor profile. + AppArmorBetaContainerAnnotationKeyPrefix = "container.apparmor.security.beta.kubernetes.io/" + // AppArmorBetaDefaultProfileAnnotatoinKey is the annotation key specifying the default AppArmor profile. + AppArmorBetaDefaultProfileAnnotationKey = "apparmor.security.beta.kubernetes.io/defaultProfileName" + // AppArmorBetaAllowedProfileAnnotationKey is the annotation key specifying the allowed AppArmor profiles. + AppArmorBetaAllowedProfilesAnnotationKey = "apparmor.security.beta.kubernetes.io/allowedProfileNames" + + // AppArmorBetaProfileRuntimeDefault is the profile specifying the runtime default. + AppArmorBetaProfileRuntimeDefault = "runtime/default" + + // AppArmorBetaProfileNamePrefix is the prefix for specifying profiles loaded on the node. + AppArmorBetaProfileNamePrefix = "localhost/" + + // AppArmorBetaProfileNameUnconfined is the Unconfined AppArmor profile + AppArmorBetaProfileNameUnconfined = "unconfined" + + // DeprecatedSeccompProfileDockerDefault represents the default seccomp profile used by docker. + // Deprecated: set a pod or container security context `seccompProfile` of type "RuntimeDefault" instead. + DeprecatedSeccompProfileDockerDefault string = "docker/default" + + // PreferAvoidPodsAnnotationKey represents the key of preferAvoidPods data (json serialized) + // in the Annotations of a Node. + PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods" + + // ObjectTTLAnnotations represents a suggestion for kubelet for how long it can cache + // an object (e.g. secret, config map) before fetching it again from apiserver. + // This annotation can be attached to node. + ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl" + + // annotation key prefix used to identify non-convertible json paths. + NonConvertibleAnnotationPrefix = "non-convertible.kubernetes.io" + + kubectlPrefix = "kubectl.kubernetes.io/" + + // LastAppliedConfigAnnotation is the annotation used to store the previous + // configuration of a resource for use in a three way diff by UpdateApplyAnnotation. + LastAppliedConfigAnnotation = kubectlPrefix + "last-applied-configuration" + + // AnnotationLoadBalancerSourceRangesKey is the key of the annotation on a service to set allowed ingress ranges on their LoadBalancers + // + // It should be a comma-separated list of CIDRs, e.g. `0.0.0.0/0` to + // allow full access (the default) or `18.0.0.0/8,56.0.0.0/8` to allow + // access only from the CIDRs currently allocated to MIT & the USPS. + // + // Not all cloud providers support this annotation, though AWS & GCE do. + AnnotationLoadBalancerSourceRangesKey = "service.beta.kubernetes.io/load-balancer-source-ranges" + + // EndpointsLastChangeTriggerTime is the annotation key, set for endpoints objects, that + // represents the timestamp (stored as RFC 3339 date-time string, e.g. '2018-10-22T19:32:52.1Z') + // of the last change, of some Pod or Service object, that triggered the endpoints object change. + // In other words, if a Pod / Service changed at time T0, that change was observed by endpoints + // controller at T1, and the Endpoints object was changed at T2, the + // EndpointsLastChangeTriggerTime would be set to T0. + // + // The "endpoints change trigger" here means any Pod or Service change that resulted in the + // Endpoints object change. + // + // Given the definition of the "endpoints change trigger", please note that this annotation will + // be set ONLY for endpoints object changes triggered by either Pod or Service change. If the + // Endpoints object changes due to other reasons, this annotation won't be set (or updated if it's + // already set). + // + // This annotation will be used to compute the in-cluster network programming latency SLI, see + // https://github.com/kubernetes/community/blob/master/sig-scalability/slos/network_programming_latency.md + EndpointsLastChangeTriggerTime = "endpoints.kubernetes.io/last-change-trigger-time" + + // EndpointsOverCapacity will be set on an Endpoints resource when it + // exceeds the maximum capacity of 1000 addresses. Initially the Endpoints + // controller will set this annotation with a value of "warning". In a + // future release, the controller may set this annotation with a value of + // "truncated" to indicate that any addresses exceeding the limit of 1000 + // have been truncated from the Endpoints resource. + EndpointsOverCapacity = "endpoints.kubernetes.io/over-capacity" + + // MigratedPluginsAnnotationKey is the annotation key, set for CSINode objects, that is a comma-separated + // list of in-tree plugins that will be serviced by the CSI backend on the Node represented by CSINode. + // This annotation is used by the Attach Detach Controller to determine whether to use the in-tree or + // CSI Backend for a volume plugin on a specific node. + MigratedPluginsAnnotationKey = "storage.alpha.kubernetes.io/migrated-plugins" + + // PodDeletionCost can be used to set to an int32 that represent the cost of deleting + // a pod compared to other pods belonging to the same ReplicaSet. Pods with lower + // deletion cost are preferred to be deleted before pods with higher deletion cost. + // Note that this is honored on a best-effort basis, and so it does not offer guarantees on + // pod deletion order. + // The implicit deletion cost for pods that don't set the annotation is 0, negative values are permitted. + // + // This annotation is beta-level and is only honored when PodDeletionCost feature is enabled. + PodDeletionCost = "controller.kubernetes.io/pod-deletion-cost" + + // AnnotationTopologyAwareHints can be used to enable or disable Topology + // Aware Hints for a Service. This may be set to "Auto" or "Disabled". Any + // other value is treated as "Disabled". + AnnotationTopologyAwareHints = "service.kubernetes.io/topology-aware-hints" +) diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/core/v1/resource.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/core/v1/resource.go new file mode 100644 index 00000000..fb4e8556 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/core/v1/resource.go @@ -0,0 +1,59 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource" +) + +// Returns string version of ResourceName. +func (rn ResourceName) String() string { + return string(rn) +} + +// Cpu returns the Cpu limit if specified. +func (rl *ResourceList) Cpu() *resource.Quantity { + return rl.Name(ResourceCPU, resource.DecimalSI) +} + +// Memory returns the Memory limit if specified. +func (rl *ResourceList) Memory() *resource.Quantity { + return rl.Name(ResourceMemory, resource.BinarySI) +} + +// Storage returns the Storage limit if specified. +func (rl *ResourceList) Storage() *resource.Quantity { + return rl.Name(ResourceStorage, resource.BinarySI) +} + +// Pods returns the list of pods +func (rl *ResourceList) Pods() *resource.Quantity { + return rl.Name(ResourcePods, resource.DecimalSI) +} + +// StorageEphemeral returns the list of ephemeral storage volumes, if any +func (rl *ResourceList) StorageEphemeral() *resource.Quantity { + return rl.Name(ResourceEphemeralStorage, resource.BinarySI) +} + +// Name returns the resource with name if specified, otherwise it returns a nil quantity with default format. +func (rl *ResourceList) Name(name ResourceName, defaultFormat resource.Format) *resource.Quantity { + if val, ok := (*rl)[name]; ok { + return &val + } + return &resource.Quantity{Format: defaultFormat} +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/core/v1/types.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/core/v1/types.go new file mode 100644 index 00000000..60074575 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/api/core/v1/types.go @@ -0,0 +1,5844 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource" + metav1 "github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/types" + "github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/util/intstr" +) + +// Volume represents a named volume in a pod that may be accessed by any container in the pod. +type Volume struct { + // Volume's name. + // Must be a DNS_LABEL and unique within the pod. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Name string `json:"name"` + // VolumeSource represents the location and type of the mounted volume. + // If not specified, the Volume is implied to be an EmptyDir. + // This implied behavior is deprecated and will be removed in a future version. + VolumeSource `json:",inline"` +} + +// Represents the source of a volume to mount. +// Only one of its members may be specified. +type VolumeSource struct { + // HostPath represents a pre-existing file or directory on the host + // machine that is directly exposed to the container. This is generally + // used for system agents or other privileged things that are allowed + // to see the host machine. Most containers will NOT need this. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + // --- + // TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + // mount host directories as read/write. + // +optional + HostPath *HostPathVolumeSource `json:"hostPath,omitempty"` + // PersistentVolumeClaimVolumeSource represents a reference to a + // PersistentVolumeClaim in the same namespace. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + // +optional + PersistentVolumeClaim *PersistentVolumeClaimVolumeSource `json:"persistentVolumeClaim,omitempty"` + // ConfigMap represents a configMap that should populate this volume + // +optional + ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"` + // Secret represents a secret that should be mounted as a volume + Secret *SecretVolumeSource `json:"secret,omitempty"` + // emptyDir represents a temporary directory that shares a pod's lifetime. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + // +optional + EmptyDir *EmptyDirVolumeSource `json:"emptyDir,omitempty"` + // image represents a container image pulled and mounted on the host machine. + // The volume is resolved at pod startup depending on which PullPolicy value is provided: + // + // - Always: podman always attempts to pull the reference. Container creation will fail If the pull fails. + // - Never: podman never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + // - IfNotPresent: podman pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + // + // The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + // A failure to resolve or pull the image during pod startup will block containers from starting and the pod won't be created. + // The container image gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + // The volume will be mounted read-only (ro) and non-executable files (noexec). + // Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath). + // The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + // +optional + Image *ImageVolumeSource `json:"image,omitempty"` +} + +// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. +// This volume finds the bound PV and mounts that volume for the pod. A +// PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another +// type of volume that is owned by someone else (the system). +type PersistentVolumeClaimVolumeSource struct { + // ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + ClaimName string `json:"claimName"` + // Will force the ReadOnly setting in VolumeMounts. + // Default false. + // +optional + ReadOnly bool `json:"readOnly,omitempty"` +} + +// PersistentVolumeSource is similar to VolumeSource but meant for the +// administrator who creates PVs. Exactly one of its members must be set. +type PersistentVolumeSource struct { + // HostPath represents a directory on the host. + // Provisioned by a developer or tester. + // This is useful for single-node development and testing only! + // On-host storage is not supported in any way and WILL NOT WORK in a multi-node cluster. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + // +optional + HostPath *HostPathVolumeSource `json:"hostPath,omitempty"` +} + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PersistentVolume (PV) is a storage resource provisioned by an administrator. +// It is analogous to a node. +// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes +type PersistentVolume struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Spec defines a specification of a persistent volume owned by the cluster. + // Provisioned by an administrator. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistent-volumes + // +optional + Spec PersistentVolumeSpec `json:"spec"` + + // Status represents the current information/status for the persistent volume. + // Populated by the system. + // Read-only. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistent-volumes + // +optional + Status PersistentVolumeStatus `json:"status"` +} + +// PersistentVolumeSpec is the specification of a persistent volume. +type PersistentVolumeSpec struct { + // A description of the persistent volume's resources and capacity. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#capacity + // +optional + Capacity ResourceList `json:"capacity,omitempty"` + // The actual volume backing the persistent volume. + PersistentVolumeSource `json:",inline"` + // AccessModes contains all ways the volume can be mounted. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes + // +optional + AccessModes []PersistentVolumeAccessMode `json:"accessModes,omitempty"` + // ClaimRef is part of a bi-directional binding between PersistentVolume and PersistentVolumeClaim. + // Expected to be non-nil when bound. + // claim.VolumeName is the authoritative bind between PV and PVC. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#binding + // +optional + ClaimRef *ObjectReference `json:"claimRef,omitempty"` + // What happens to a persistent volume when released from its claim. + // Valid options are Retain (default for manually created PersistentVolumes), Delete (default + // for dynamically provisioned PersistentVolumes), and Recycle (deprecated). + // Recycle must be supported by the volume plugin underlying this PersistentVolume. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#reclaiming + // +optional + PersistentVolumeReclaimPolicy PersistentVolumeReclaimPolicy `json:"persistentVolumeReclaimPolicy,omitempty"` + // Name of StorageClass to which this persistent volume belongs. Empty value + // means that this volume does not belong to any StorageClass. + // +optional + StorageClassName string `json:"storageClassName,omitempty"` + // A list of mount options, e.g. ["ro", "soft"]. Not validated - mount will + // simply fail if one is invalid. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options + // +optional + MountOptions []string `json:"mountOptions,omitempty"` + // volumeMode defines if a volume is intended to be used with a formatted filesystem + // or to remain in raw block state. Value of Filesystem is implied when not included in spec. + // +optional + VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty"` + // NodeAffinity defines constraints that limit what nodes this volume can be accessed from. + // This field influences the scheduling of pods that use this volume. + // +optional + NodeAffinity *VolumeNodeAffinity `json:"nodeAffinity,omitempty"` +} + +// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from. +type VolumeNodeAffinity struct { + // Required specifies hard node constraints that must be met. + Required *NodeSelector `json:"required,omitempty"` +} + +// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes. +type PersistentVolumeReclaimPolicy string + +const ( + // PersistentVolumeReclaimRecycle means the volume will be recycled back into the pool of unbound persistent volumes on release from its claim. + // The volume plugin must support Recycling. + PersistentVolumeReclaimRecycle PersistentVolumeReclaimPolicy = "Recycle" + // PersistentVolumeReclaimDelete means the volume will be deleted from Kubernetes on release from its claim. + // The volume plugin must support Deletion. + PersistentVolumeReclaimDelete PersistentVolumeReclaimPolicy = "Delete" + // PersistentVolumeReclaimRetain means the volume will be left in its current phase (Released) for manual reclamation by the administrator. + // The default policy is Retain. + PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain" +) + +// PersistentVolumeMode describes how a volume is intended to be consumed, either Block or Filesystem. +type PersistentVolumeMode string + +const ( + // PersistentVolumeBlock means the volume will not be formatted with a filesystem and will remain a raw block device. + PersistentVolumeBlock PersistentVolumeMode = "Block" + // PersistentVolumeFilesystem means the volume will be or is formatted with a filesystem. + PersistentVolumeFilesystem PersistentVolumeMode = "Filesystem" +) + +// PersistentVolumeStatus is the current status of a persistent volume. +type PersistentVolumeStatus struct { + // Phase indicates if a volume is available, bound to a claim, or released by a claim. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase + // +optional + Phase PersistentVolumePhase `json:"phase,omitempty"` + // A human-readable message indicating details about why the volume is in this state. + // +optional + Message string `json:"message,omitempty"` + // Reason is a brief CamelCase string that describes any failure and is meant + // for machine parsing and tidy display in the CLI. + // +optional + Reason string `json:"reason,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PersistentVolumeList is a list of PersistentVolume items. +type PersistentVolumeList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + // List of persistent volumes. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes + Items []PersistentVolume `json:"items"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PersistentVolumeClaim is a user's request for and claim to a persistent volume +type PersistentVolumeClaim struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Spec defines the desired characteristics of a volume requested by a pod author. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + // +optional + Spec PersistentVolumeClaimSpec `json:"spec"` + + // Status represents the current information/status of a persistent volume claim. + // Read-only. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + // +optional + Status PersistentVolumeClaimStatus `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PersistentVolumeClaimList is a list of PersistentVolumeClaim items. +type PersistentVolumeClaimList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + // A list of persistent volume claims. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + Items []PersistentVolumeClaim `json:"items"` +} + +// PersistentVolumeClaimSpec describes the common attributes of storage devices +// and allows a Source for provider-specific attributes +type PersistentVolumeClaimSpec struct { + // AccessModes contains the desired access modes the volume should have. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + // +optional + AccessModes []PersistentVolumeAccessMode `json:"accessModes,omitempty"` + // A label query over volumes to consider for binding. + // +optional + Selector *metav1.LabelSelector `json:"selector,omitempty"` + // Resources represents the minimum resources the volume should have. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + // +optional + Resources ResourceRequirements `json:"resources"` + // VolumeName is the binding reference to the PersistentVolume backing this claim. + // +optional + VolumeName string `json:"volumeName,omitempty"` + // Name of the StorageClass required by the claim. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + // +optional + StorageClassName *string `json:"storageClassName,omitempty"` + // volumeMode defines what type of volume is required by the claim. + // Value of Filesystem is implied when not included in claim spec. + // +optional + VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty"` + // This field can be used to specify either: + // * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + // * An existing PVC (PersistentVolumeClaim) + // If the provisioner or an external controller can support the specified data source, + // it will create a new volume based on the contents of the specified data source. + // If the AnyVolumeDataSource feature gate is enabled, this field will always have + // the same contents as the DataSourceRef field. + // +optional + DataSource *TypedLocalObjectReference `json:"dataSource,omitempty"` + // Specifies the object from which to populate the volume with data, if a non-empty + // volume is desired. This may be any local object from a non-empty API group (non + // core object) or a PersistentVolumeClaim object. + // When this field is specified, volume binding will only succeed if the type of + // the specified object matches some installed volume populator or dynamic + // provisioner. + // This field will replace the functionality of the DataSource field and as such + // if both fields are non-empty, they must have the same value. For backwards + // compatibility, both fields (DataSource and DataSourceRef) will be set to the same + // value automatically if one of them is empty and the other is non-empty. + // There are two important differences between DataSource and DataSourceRef: + // * While DataSource only allows two specific types of objects, DataSourceRef + // allows any non-core object, as well as PersistentVolumeClaim objects. + // * While DataSource ignores disallowed values (dropping them), DataSourceRef + // preserves all values, and generates an error if a disallowed value is + // specified. + // (Alpha) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + // +optional + DataSourceRef *TypedLocalObjectReference `json:"dataSourceRef,omitempty"` +} + +// PersistentVolumeClaimConditionType is a valid value of PersistentVolumeClaimCondition.Type +type PersistentVolumeClaimConditionType string + +const ( + // PersistentVolumeClaimResizing - a user trigger resize of pvc has been started + PersistentVolumeClaimResizing PersistentVolumeClaimConditionType = "Resizing" + // PersistentVolumeClaimFileSystemResizePending - controller resize is finished and a file system resize is pending on node + PersistentVolumeClaimFileSystemResizePending PersistentVolumeClaimConditionType = "FileSystemResizePending" +) + +// PersistentVolumeClaimCondition contains details about state of pvc +type PersistentVolumeClaimCondition struct { + Type PersistentVolumeClaimConditionType `json:"type"` + Status ConditionStatus `json:"status"` + // Last time we probed the condition. + // +optional + LastProbeTime metav1.Time `json:"lastProbeTime"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime"` + // Unique, this should be a short, machine understandable string that gives the reason + // for condition's last transition. If it reports "ResizeStarted" that means the underlying + // persistent volume is being resized. + // +optional + Reason string `json:"reason,omitempty"` + // Human-readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty"` +} + +// PersistentVolumeClaimStatus is the current status of a persistent volume claim. +type PersistentVolumeClaimStatus struct { + // Phase represents the current phase of PersistentVolumeClaim. + // +optional + Phase PersistentVolumeClaimPhase `json:"phase,omitempty"` + // AccessModes contains the actual access modes the volume backing the PVC has. + // More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + // +optional + AccessModes []PersistentVolumeAccessMode `json:"accessModes,omitempty"` + // Represents the actual resources of the underlying volume. + // +optional + Capacity ResourceList `json:"capacity,omitempty"` + // Current Condition of persistent volume claim. If underlying persistent volume is being + // resized then the Condition will be set to 'ResizeStarted'. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []PersistentVolumeClaimCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +type PersistentVolumeAccessMode string + +const ( + // can be mounted in read/write mode to exactly 1 host + ReadWriteOnce PersistentVolumeAccessMode = "ReadWriteOnce" + // can be mounted in read-only mode to many hosts + ReadOnlyMany PersistentVolumeAccessMode = "ReadOnlyMany" + // can be mounted in read/write mode to many hosts + ReadWriteMany PersistentVolumeAccessMode = "ReadWriteMany" + // can be mounted in read/write mode to exactly 1 pod + // cannot be used in combination with other access modes + ReadWriteOncePod PersistentVolumeAccessMode = "ReadWriteOncePod" +) + +type PersistentVolumePhase string + +const ( + // used for PersistentVolumes that are not available + VolumePending PersistentVolumePhase = "Pending" + // used for PersistentVolumes that are not yet bound + // Available volumes are held by the binder and matched to PersistentVolumeClaims + VolumeAvailable PersistentVolumePhase = "Available" + // used for PersistentVolumes that are bound + VolumeBound PersistentVolumePhase = "Bound" + // used for PersistentVolumes where the bound PersistentVolumeClaim was deleted + // released volumes must be recycled before becoming available again + // this phase is used by the persistent volume claim binder to signal to another process to reclaim the resource + VolumeReleased PersistentVolumePhase = "Released" + // used for PersistentVolumes that failed to be correctly recycled or deleted after being released from a claim + VolumeFailed PersistentVolumePhase = "Failed" +) + +type PersistentVolumeClaimPhase string + +const ( + // used for PersistentVolumeClaims that are not yet bound + ClaimPending PersistentVolumeClaimPhase = "Pending" + // used for PersistentVolumeClaims that are bound + ClaimBound PersistentVolumeClaimPhase = "Bound" + // used for PersistentVolumeClaims that lost their underlying + // PersistentVolume. The claim was bound to a PersistentVolume and this + // volume does not exist any longer and all data on it was lost. + ClaimLost PersistentVolumeClaimPhase = "Lost" +) + +type HostPathType string + +const ( + // For backwards compatible, leave it empty if unset + HostPathUnset HostPathType = "" + // If nothing exists at the given path, an empty directory will be created there + // as needed with file mode 0755, having the same group and ownership with Kubelet. + HostPathDirectoryOrCreate HostPathType = "DirectoryOrCreate" + // A directory must exist at the given path + HostPathDirectory HostPathType = "Directory" + // If nothing exists at the given path, an empty file will be created there + // as needed with file mode 0644, having the same group and ownership with Kubelet. + HostPathFileOrCreate HostPathType = "FileOrCreate" + // A file must exist at the given path + HostPathFile HostPathType = "File" + // A UNIX socket must exist at the given path + HostPathSocket HostPathType = "Socket" + // A character device must exist at the given path + HostPathCharDev HostPathType = "CharDevice" + // A block device must exist at the given path + HostPathBlockDev HostPathType = "BlockDevice" +) + +// Represents a host path mapped into a pod. +// Host path volumes do not support ownership management or SELinux relabeling. +type HostPathVolumeSource struct { + // Path of the directory on the host. + // If the path is a symlink, it will follow the link to the real path. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + Path string `json:"path"` + // Type for HostPath Volume + // Defaults to "" + // More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + // +optional + Type *HostPathType `json:"type,omitempty"` +} + +// Represents an empty directory for a pod. +// Empty directory volumes support ownership management and SELinux relabeling. +type EmptyDirVolumeSource struct { + // What type of storage medium should back this directory. + // The default is "" which means to use the node's default medium. + // Must be an empty string (default) or Memory. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + // +optional + Medium StorageMedium `json:"medium,omitempty"` + // Total amount of local storage required for this EmptyDir volume. + // The size limit is also applicable for memory medium. + // The maximum usage on memory medium EmptyDir would be the minimum value between + // the SizeLimit specified here and the sum of memory limits of all containers in a pod. + // The default is nil which means that the limit is undefined. + // More info: http://kubernetes.io/docs/user-guide/volumes#emptydir + // +optional + SizeLimit *resource.Quantity `json:"sizeLimit,omitempty"` +} + +// ImageVolumeSource represents a image volume resource. +type ImageVolumeSource struct { + // Required: Container image reference to be used. + // Behaves in the same way as pod.spec.containers[*].image. + // This field is optional to allow higher level config management to default or override + // container images in workload controllers like Deployments and StatefulSets. + // +optional + Reference string `json:"reference,omitempty"` + + // Policy for pulling OCI objects. Possible values are: + // Always: podman always attempts to pull the reference. Container creation will fail If the pull fails. + // Never: podman never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + // IfNotPresent: podman pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + // Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + // +optional + PullPolicy PullPolicy `json:"pullPolicy,omitempty"` +} + +// SecretReference represents a Secret Reference. It has enough information to retrieve secret +// in any namespace +// +structType=atomic +type SecretReference struct { + // Name is unique within a namespace to reference a secret resource. + // +optional + Name string `json:"name,omitempty"` + // Namespace defines the space within which the secret name must be unique. + // +optional + Namespace string `json:"namespace,omitempty"` +} + +// StorageMedium defines ways that storage can be allocated to a volume. +type StorageMedium string + +const ( + StorageMediumDefault StorageMedium = "" // use whatever the default is for the node, assume anything we don't explicitly handle is this + StorageMediumMemory StorageMedium = "Memory" // use memory (e.g. tmpfs on linux) + StorageMediumHugePages StorageMedium = "HugePages" // use hugepages + StorageMediumHugePagesPrefix StorageMedium = "HugePages-" // prefix for full medium notation HugePages- +) + +// Protocol defines network protocols supported for things like container ports. +type Protocol string + +const ( + // ProtocolTCP is the TCP protocol. + ProtocolTCP Protocol = "TCP" + // ProtocolUDP is the UDP protocol. + ProtocolUDP Protocol = "UDP" + // ProtocolSCTP is the SCTP protocol. + ProtocolSCTP Protocol = "SCTP" +) + +// Adapts a Secret into a volume. +// +// The contents of the target Secret's Data field will be presented in a volume +// as files using the keys in the Data field as the file names. +// Secret volumes support ownership management and SELinux relabeling. +type SecretVolumeSource struct { + // Name of the secret in the pod's namespace to use. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + // +optional + SecretName string `json:"secretName,omitempty"` + // If unspecified, each key-value pair in the Data field of the referenced + // Secret will be projected into the volume as a file whose name is the + // key and content is the value. If specified, the listed keys will be + // projected into the specified paths, and unlisted keys will not be + // present. If a key is specified which is not present in the Secret, + // the volume setup will error unless it is marked optional. Paths must be + // relative and may not contain the '..' path or start with '..'. + // +optional + Items []KeyToPath `json:"items,omitempty"` + // Optional: mode bits used to set permissions on created files by default. + // Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + // YAML accepts both octal and decimal values, JSON requires decimal values + // for mode bits. Defaults to 0644. + // Directories within the path are not affected by this setting. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + DefaultMode *int32 `json:"defaultMode,omitempty"` + // Specify whether the Secret or its keys must be defined + // +optional + Optional *bool `json:"optional,omitempty"` +} + +const ( + SecretVolumeSourceDefaultMode int32 = 0644 +) + +// Adapts a secret into a projected volume. +// +// The contents of the target Secret's Data field will be presented in a +// projected volume as files using the keys in the Data field as the file names. +// Note that this is identical to a secret volume source without the default +// mode. +type SecretProjection struct { + LocalObjectReference `json:",inline"` + // If unspecified, each key-value pair in the Data field of the referenced + // Secret will be projected into the volume as a file whose name is the + // key and content is the value. If specified, the listed keys will be + // projected into the specified paths, and unlisted keys will not be + // present. If a key is specified which is not present in the Secret, + // the volume setup will error unless it is marked optional. Paths must be + // relative and may not contain the '..' path or start with '..'. + // +optional + Items []KeyToPath `json:"items,omitempty"` + // Specify whether the Secret or its key must be defined + // +optional + Optional *bool `json:"optional,omitempty"` +} + +// Adapts a ConfigMap into a volume. +// +// The contents of the target ConfigMap's Data field will be presented in a +// volume as files using the keys in the Data field as the file names, unless +// the items element is populated with specific mappings of keys to paths. +// ConfigMap volumes support ownership management and SELinux relabeling. +type ConfigMapVolumeSource struct { + LocalObjectReference `json:",inline"` + // If unspecified, each key-value pair in the Data field of the referenced + // ConfigMap will be projected into the volume as a file whose name is the + // key and content is the value. If specified, the listed keys will be + // projected into the specified paths, and unlisted keys will not be + // present. If a key is specified which is not present in the ConfigMap, + // the volume setup will error unless it is marked optional. Paths must be + // relative and may not contain the '..' path or start with '..'. + // +optional + Items []KeyToPath `json:"items,omitempty"` + // Optional: mode bits used to set permissions on created files by default. + // Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + // YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + // Defaults to 0644. + // Directories within the path are not affected by this setting. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + DefaultMode *int32 `json:"defaultMode,omitempty"` + // Specify whether the ConfigMap or its keys must be defined + // +optional + Optional *bool `json:"optional,omitempty"` +} + +const ( + ConfigMapVolumeSourceDefaultMode int32 = 0644 +) + +// Adapts a ConfigMap into a projected volume. +// +// The contents of the target ConfigMap's Data field will be presented in a +// projected volume as files using the keys in the Data field as the file names, +// unless the items element is populated with specific mappings of keys to paths. +// Note that this is identical to a configmap volume source without the default +// mode. +type ConfigMapProjection struct { + LocalObjectReference `json:",inline"` + // If unspecified, each key-value pair in the Data field of the referenced + // ConfigMap will be projected into the volume as a file whose name is the + // key and content is the value. If specified, the listed keys will be + // projected into the specified paths, and unlisted keys will not be + // present. If a key is specified which is not present in the ConfigMap, + // the volume setup will error unless it is marked optional. Paths must be + // relative and may not contain the '..' path or start with '..'. + // +optional + Items []KeyToPath `json:"items,omitempty"` + // Specify whether the ConfigMap or its keys must be defined + // +optional + Optional *bool `json:"optional,omitempty"` +} + +// ServiceAccountTokenProjection represents a projected service account token +// volume. This projection can be used to insert a service account token into +// the pods runtime filesystem for use against APIs (Kubernetes API Server or +// otherwise). +type ServiceAccountTokenProjection struct { + // Audience is the intended audience of the token. A recipient of a token + // must identify itself with an identifier specified in the audience of the + // token, and otherwise should reject the token. The audience defaults to the + // identifier of the apiserver. + //+optional + Audience string `json:"audience,omitempty"` + // ExpirationSeconds is the requested duration of validity of the service + // account token. As the token approaches expiration, the kubelet volume + // plugin will proactively rotate the service account token. The kubelet will + // start trying to rotate the token if the token is older than 80 percent of + // its time to live or if the token is older than 24 hours.Defaults to 1 hour + // and must be at least 10 minutes. + //+optional + ExpirationSeconds *int64 `json:"expirationSeconds,omitempty"` + // Path is the path relative to the mount point of the file to project the + // token into. + Path string `json:"path"` +} + +// Represents a projected volume source +type ProjectedVolumeSource struct { + // list of volume projections + // +optional + Sources []VolumeProjection `json:"sources"` + // Mode bits used to set permissions on created files by default. + // Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + // YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + // Directories within the path are not affected by this setting. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + DefaultMode *int32 `json:"defaultMode,omitempty"` +} + +// Projection that may be projected along with other supported volume types +type VolumeProjection struct { + // all types below are the supported types for projection into the same volume + + // information about the secret data to project + // +optional + Secret *SecretProjection `json:"secret,omitempty"` + // information about the downwardAPI data to project + // +optional + DownwardAPI *DownwardAPIProjection `json:"downwardAPI,omitempty"` + // information about the configMap data to project + // +optional + ConfigMap *ConfigMapProjection `json:"configMap,omitempty"` + // information about the serviceAccountToken data to project + // +optional + ServiceAccountToken *ServiceAccountTokenProjection `json:"serviceAccountToken,omitempty"` +} + +const ( + ProjectedVolumeSourceDefaultMode int32 = 0644 +) + +// Maps a string key to a path within a volume. +type KeyToPath struct { + // The key to project. + Key string `json:"key"` + + // The relative path of the file to map the key to. + // May not be an absolute path. + // May not contain the path element '..'. + // May not start with the string '..'. + Path string `json:"path"` + // Optional: mode bits used to set permissions on this file. + // Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + // YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + // If not specified, the volume defaultMode will be used. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + Mode *int32 `json:"mode,omitempty"` +} + +// PersistentVolumeClaimTemplate is used to produce +// PersistentVolumeClaim objects as part of an EphemeralVolumeSource. +type PersistentVolumeClaimTemplate struct { + // May contain labels and annotations that will be copied into the PVC + // when creating it. No other fields are allowed and will be rejected during + // validation. + // + // +optional + metav1.ObjectMeta `json:"metadata"` + + // The specification for the PersistentVolumeClaim. The entire content is + // copied unchanged into the PVC that gets created from this + // template. The same fields as in a PersistentVolumeClaim + // are also valid here. + Spec PersistentVolumeClaimSpec `json:"spec"` +} + +// ContainerPort represents a network port in a single container. +type ContainerPort struct { + // If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + // named port in a pod must have a unique name. Name for the port that can be + // referred to by services. + // +optional + Name string `json:"name,omitempty"` + // Number of port to expose on the host. + // If specified, this must be a valid port number, 0 < x < 65536. + // If HostNetwork is specified, this must match ContainerPort. + // Most containers do not need this. + // +optional + HostPort int32 `json:"hostPort,omitempty"` + // Number of port to expose on the pod's IP address. + // This must be a valid port number, 0 < x < 65536. + ContainerPort int32 `json:"containerPort"` + // Protocol for port. Must be UDP, TCP, or SCTP. + // Defaults to "TCP". + // +optional + // +default="TCP" + Protocol Protocol `json:"protocol,omitempty"` + // What host IP to bind the external port to. + // +optional + HostIP string `json:"hostIP,omitempty"` +} + +// VolumeMount describes a mounting of a Volume within a container. +type VolumeMount struct { + // This must match the Name of a Volume. + Name string `json:"name"` + // Mounted read-only if true, read-write otherwise (false or unspecified). + // Defaults to false. + // +optional + ReadOnly bool `json:"readOnly,omitempty"` + // Path within the container at which the volume should be mounted. Must + // not contain ':'. + MountPath string `json:"mountPath"` + // Path within the volume from which the container's volume should be mounted. + // Defaults to "" (volume's root). + // +optional + SubPath string `json:"subPath,omitempty"` + // mountPropagation determines how mounts are propagated from the host + // to container and the other way around. + // When not set, MountPropagationNone is used. + // This field is beta in 1.10. + // +optional + MountPropagation *MountPropagationMode `json:"mountPropagation,omitempty"` + // Expanded path within the volume from which the container's volume should be mounted. + // Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + // Defaults to "" (volume's root). + // SubPathExpr and SubPath are mutually exclusive. + // +optional + SubPathExpr string `json:"subPathExpr,omitempty"` +} + +// MountPropagationMode describes mount propagation. +type MountPropagationMode string + +const ( + // MountPropagationNone means that the volume in a container will + // not receive new mounts from the host or other containers, and filesystems + // mounted inside the container won't be propagated to the host or other + // containers. + // Note that this mode corresponds to "private" in Linux terminology. + MountPropagationNone MountPropagationMode = "None" + // MountPropagationHostToContainer means that the volume in a container will + // receive new mounts from the host or other containers, but filesystems + // mounted inside the container won't be propagated to the host or other + // containers. + // Note that this mode is recursively applied to all mounts in the volume + // ("rslave" in Linux terminology). + MountPropagationHostToContainer MountPropagationMode = "HostToContainer" + // MountPropagationBidirectional means that the volume in a container will + // receive new mounts from the host or other containers, and its own mounts + // will be propagated from the container to the host or other containers. + // Note that this mode is recursively applied to all mounts in the volume + // ("rshared" in Linux terminology). + MountPropagationBidirectional MountPropagationMode = "Bidirectional" +) + +// volumeDevice describes a mapping of a raw block device within a container. +type VolumeDevice struct { + // name must match the name of a persistentVolumeClaim in the pod + Name string `json:"name"` + // devicePath is the path inside of the container that the device will be mapped to. + DevicePath string `json:"devicePath"` +} + +// EnvVar represents an environment variable present in a Container. +type EnvVar struct { + // Name of the environment variable. Must be a C_IDENTIFIER. + Name string `json:"name"` + + // Optional: no more than one of the following may be specified. + + // Variable references $(VAR_NAME) are expanded + // using the previously defined environment variables in the container and + // any service environment variables. If a variable cannot be resolved, + // the reference in the input string will be unchanged. Double $$ are reduced + // to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + // "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + // Escaped references will never be expanded, regardless of whether the variable + // exists or not. + // Defaults to "". + // +optional + Value string `json:"value,omitempty"` + // Source for the environment variable's value. Cannot be used if value is not empty. + // +optional + ValueFrom *EnvVarSource `json:"valueFrom,omitempty"` +} + +// EnvVarSource represents a source for the value of an EnvVar. +type EnvVarSource struct { + // Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + // spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + // +optional + FieldRef *ObjectFieldSelector `json:"fieldRef,omitempty"` + // Selects a resource of the container: only resources limits and requests + // (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + // +optional + ResourceFieldRef *ResourceFieldSelector `json:"resourceFieldRef,omitempty"` + // Selects a key of a ConfigMap. + // +optional + ConfigMapKeyRef *ConfigMapKeySelector `json:"configMapKeyRef,omitempty"` + // Selects a key of a secret in the pod's namespace + // +optional + SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty"` +} + +// ObjectFieldSelector selects an APIVersioned field of an object. +// +structType=atomic +type ObjectFieldSelector struct { + // Version of the schema the FieldPath is written in terms of, defaults to "v1". + // +optional + APIVersion string `json:"apiVersion,omitempty"` + // Path of the field to select in the specified API version. + FieldPath string `json:"fieldPath"` +} + +// ResourceFieldSelector represents container resources (cpu, memory) and their output format +// +structType=atomic +type ResourceFieldSelector struct { + // Container name: required for volumes, optional for env vars + // +optional + ContainerName string `json:"containerName,omitempty"` + // Required: resource to select + Resource string `json:"resource"` + // Specifies the output format of the exposed resources, defaults to "1" + // +optional + Divisor resource.Quantity `json:"divisor"` +} + +// Selects a key from a ConfigMap. +// +structType=atomic +type ConfigMapKeySelector struct { + // The ConfigMap to select from. + LocalObjectReference `json:",inline"` + // The key to select. + Key string `json:"key"` + // Specify whether the ConfigMap or its key must be defined + // +optional + Optional *bool `json:"optional,omitempty"` +} + +// SecretKeySelector selects a key of a Secret. +// +structType=atomic +type SecretKeySelector struct { + // The name of the secret in the pod's namespace to select from. + LocalObjectReference `json:",inline"` + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` + // Specify whether the Secret or its key must be defined + // +optional + Optional *bool `json:"optional,omitempty"` +} + +// EnvFromSource represents the source of a set of ConfigMaps +type EnvFromSource struct { + // An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + // +optional + Prefix string `json:"prefix,omitempty"` + // The ConfigMap to select from + // +optional + ConfigMapRef *ConfigMapEnvSource `json:"configMapRef,omitempty"` + // The Secret to select from + // +optional + SecretRef *SecretEnvSource `json:"secretRef,omitempty"` +} + +// ConfigMapEnvSource selects a ConfigMap to populate the environment +// variables with. +// +// The contents of the target ConfigMap's Data field will represent the +// key-value pairs as environment variables. +type ConfigMapEnvSource struct { + // The ConfigMap to select from. + LocalObjectReference `json:",inline"` + // Specify whether the ConfigMap must be defined + // +optional + Optional *bool `json:"optional,omitempty"` +} + +// SecretEnvSource selects a Secret to populate the environment +// variables with. +// +// The contents of the target Secret's Data field will represent the +// key-value pairs as environment variables. +type SecretEnvSource struct { + // The Secret to select from. + LocalObjectReference `json:",inline"` + // Specify whether the Secret must be defined + // +optional + Optional *bool `json:"optional,omitempty"` +} + +// HTTPHeader describes a custom header to be used in HTTP probes +type HTTPHeader struct { + // The header field name + Name string `json:"name"` + // The header field value + Value string `json:"value"` +} + +// HTTPGetAction describes an action based on HTTP Get requests. +type HTTPGetAction struct { + // Path to access on the HTTP server. Defaults to /. + // +optional + Path string `json:"path,omitempty"` + // Name or number of the port to access on the container. + // Number must be in the range 1 to 65535. + // Name must be an IANA_SVC_NAME. + Port intstr.IntOrString `json:"port"` + // Host name to connect to. You probably want to set "Host" in httpHeaders instead. + // Defaults to the pod IP in Kubernetes, in case of Podman to localhost. + // +optional + Host string `json:"host,omitempty"` + // Scheme to use for connecting to the host. + // Defaults to HTTP. + // +optional + Scheme URIScheme `json:"scheme,omitempty"` + // Custom headers to set in the request. HTTP allows repeated headers. + // +optional + HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty"` +} + +// URIScheme identifies the scheme used for connection to a host for Get actions +type URIScheme string + +const ( + // URISchemeHTTP means that the scheme used will be http:// + URISchemeHTTP URIScheme = "http" + // URISchemeHTTPS means that the scheme used will be https:// + URISchemeHTTPS URIScheme = "https" +) + +// TCPSocketAction describes an action based on opening a socket +type TCPSocketAction struct { + // Number or name of the port to access on the container. + // Number must be in the range 1 to 65535. + // Name must be an IANA_SVC_NAME. + Port intstr.IntOrString `json:"port"` + // Optional: Host name to connect to, defaults to the pod IP. + // +optional + Host string `json:"host,omitempty"` +} + +// ExecAction describes a "run in container" action. +type ExecAction struct { + // Command is the command line to execute inside the container, the working directory for the + // command is root ('/') in the container's filesystem. The command is simply exec'd, it is + // not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + // a shell, you need to explicitly call out to that shell. + // Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + // +optional + Command []string `json:"command,omitempty"` +} + +// Probe describes a health check to be performed against a container to determine whether it is +// alive or ready to receive traffic. +type Probe struct { + // The action taken to determine the health of a container + Handler `json:",inline"` + // Number of seconds after the container has started before liveness probes are initiated. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty"` + // Number of seconds after which the probe times out. + // Defaults to 1 second. Minimum value is 1. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"` + // How often (in seconds) to perform the probe. + // Default to 10 seconds. Minimum value is 1. + // +optional + PeriodSeconds int32 `json:"periodSeconds,omitempty"` + // Minimum consecutive successes for the probe to be considered successful after having failed. + // Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + // +optional + SuccessThreshold int32 `json:"successThreshold,omitempty"` + // Minimum consecutive failures for the probe to be considered failed after having succeeded. + // Defaults to 3. Minimum value is 1. + // +optional + FailureThreshold int32 `json:"failureThreshold,omitempty"` + // Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + // The grace period is the duration in seconds after the processes running in the pod are sent + // a termination signal and the time when the processes are forcibly halted with a kill signal. + // Set this value longer than the expected cleanup time for your process. + // If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + // value overrides the value provided by the pod spec. + // Value must be non-negative integer. The value zero indicates stop immediately via + // the kill signal (no opportunity to shut down). + // This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + // Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + // +optional + TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"` +} + +// PullPolicy describes a policy for if/when to pull a container image +type PullPolicy string + +const ( + // PullAlways means that kubelet always attempts to pull the latest image. Container will fail If the pull fails. + PullAlways PullPolicy = "Always" + // PullNever means that kubelet never pulls an image, but only uses a local image. Container will fail if the image isn't present + PullNever PullPolicy = "Never" + // PullIfNotPresent means that kubelet pulls if the image isn't present on disk. Container will fail if the image isn't present and the pull fails. + PullIfNotPresent PullPolicy = "IfNotPresent" +) + +// PreemptionPolicy describes a policy for if/when to preempt a pod. +type PreemptionPolicy string + +const ( + // PreemptLowerPriority means that pod can preempt other pods with lower priority. + PreemptLowerPriority PreemptionPolicy = "PreemptLowerPriority" + // PreemptNever means that pod never preempts other pods with lower priority. + PreemptNever PreemptionPolicy = "Never" +) + +// TerminationMessagePolicy describes how termination messages are retrieved from a container. +type TerminationMessagePolicy string + +const ( + // TerminationMessageReadFile is the default behavior and will set the container status message to + // the contents of the container's terminationMessagePath when the container exits. + TerminationMessageReadFile TerminationMessagePolicy = "File" + // TerminationMessageFallbackToLogsOnError will read the most recent contents of the container logs + // for the container status message when the container exits with an error and the + // terminationMessagePath has no contents. + TerminationMessageFallbackToLogsOnError TerminationMessagePolicy = "FallbackToLogsOnError" +) + +// Capability represent POSIX capabilities type +type Capability string + +// Adds and removes POSIX capabilities from running containers. +type Capabilities struct { + // Added capabilities + // +optional + Add []Capability `json:"add,omitempty"` + // Removed capabilities + // +optional + Drop []Capability `json:"drop,omitempty"` +} + +// ResourceRequirements describes the compute resource requirements. +type ResourceRequirements struct { + // Limits describes the maximum amount of compute resources allowed. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + // +optional + Limits ResourceList `json:"limits,omitempty"` + // Requests describes the minimum amount of compute resources required. + // If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + // otherwise to an implementation-defined value. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + // +optional + Requests ResourceList `json:"requests,omitempty"` +} + +const ( + // TerminationMessagePathDefault means the default path to capture the application termination message running in a container + TerminationMessagePathDefault string = "/dev/termination-log" +) + +// A single application container that you want to run within a pod. +type Container struct { + // Name of the container specified as a DNS_LABEL. + // Each container in a pod must have a unique name (DNS_LABEL). + // Cannot be updated. + Name string `json:"name"` + // Docker image name. + // More info: https://kubernetes.io/docs/concepts/containers/images + // This field is optional to allow higher level config management to default or override + // container images in workload controllers like Deployments and StatefulSets. + // +optional + Image string `json:"image,omitempty"` + // Entrypoint array. Not executed within a shell. + // The docker image's ENTRYPOINT is used if this is not provided. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + // to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + // produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + // of whether the variable exists or not. Cannot be updated. + // More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + // +optional + Command []string `json:"command,omitempty"` + // Arguments to the entrypoint. + // The docker image's CMD is used if this is not provided. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + // to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + // produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + // of whether the variable exists or not. Cannot be updated. + // More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + // +optional + Args []string `json:"args,omitempty"` + // Container's working directory. + // If not specified, the container runtime's default will be used, which + // might be configured in the container image. + // Cannot be updated. + // +optional + WorkingDir string `json:"workingDir,omitempty"` + // List of ports to expose from the container. Exposing a port here gives + // the system additional information about the network connections a + // container uses, but is primarily informational. Not specifying a port here + // DOES NOT prevent that port from being exposed. Any port which is + // listening on the default "0.0.0.0" address inside a container will be + // accessible from the network. + // Cannot be updated. + // +optional + // +patchMergeKey=containerPort + // +patchStrategy=merge + // +listType=map + // +listMapKey=containerPort + // +listMapKey=protocol + Ports []ContainerPort `json:"ports,omitempty" patchStrategy:"merge" patchMergeKey:"containerPort"` + // List of sources to populate environment variables in the container. + // The keys defined within a source must be a C_IDENTIFIER. All invalid keys + // will be reported as an event when the container is starting. When a key exists in multiple + // sources, the value associated with the last source will take precedence. + // Values defined by an Env with a duplicate key will take precedence. + // Cannot be updated. + // +optional + EnvFrom []EnvFromSource `json:"envFrom,omitempty"` + // List of environment variables to set in the container. + // Cannot be updated. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + Env []EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + // Compute Resources required by this container. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + // +optional + Resources ResourceRequirements `json:"resources"` + // Pod volumes to mount into the container's filesystem. + // Cannot be updated. + // +optional + // +patchMergeKey=mountPath + // +patchStrategy=merge + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath"` + // volumeDevices is the list of block devices to be used by the container. + // +patchMergeKey=devicePath + // +patchStrategy=merge + // +optional + VolumeDevices []VolumeDevice `json:"volumeDevices,omitempty" patchStrategy:"merge" patchMergeKey:"devicePath"` + // Periodic probe of container liveness. + // Container will be restarted if the probe fails. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + LivenessProbe *Probe `json:"livenessProbe,omitempty"` + // Periodic probe of container service readiness. + // Container will be removed from service endpoints if the probe fails. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + ReadinessProbe *Probe `json:"readinessProbe,omitempty"` + // StartupProbe indicates that the Pod has successfully initialized. + // If specified, no other probes are executed until this completes successfully. + // If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + // This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + // when it might take a long time to load data or warm a cache, than during steady-state operation. + // This cannot be updated. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + StartupProbe *Probe `json:"startupProbe,omitempty"` + // Actions that the management system should take in response to container lifecycle events. + // Cannot be updated. + // +optional + Lifecycle *Lifecycle `json:"lifecycle,omitempty"` + // Optional: Path at which the file to which the container's termination message + // will be written is mounted into the container's filesystem. + // Message written is intended to be brief final status, such as an assertion failure message. + // Will be truncated by the node if greater than 4096 bytes. The total message length across + // all containers will be limited to 12kb. + // Defaults to /dev/termination-log. + // Cannot be updated. + // +optional + TerminationMessagePath string `json:"terminationMessagePath,omitempty"` + // Indicate how the termination message should be populated. File will use the contents of + // terminationMessagePath to populate the container status message on both success and failure. + // FallbackToLogsOnError will use the last chunk of container log output if the termination + // message file is empty and the container exited with an error. + // The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + // Defaults to File. + // Cannot be updated. + // +optional + TerminationMessagePolicy TerminationMessagePolicy `json:"terminationMessagePolicy,omitempty"` + // Image pull policy. + // One of Always, Never, IfNotPresent. + // Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + // +optional + ImagePullPolicy PullPolicy `json:"imagePullPolicy,omitempty"` + // SecurityContext defines the security options the container should be run with. + // If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + // +optional + SecurityContext *SecurityContext `json:"securityContext,omitempty"` + + // Variables for interactive containers, these have very specialized use-cases (e.g. debugging) + // and shouldn't be used for general purpose containers. + + // Whether this container should allocate a buffer for stdin in the container runtime. If this + // is not set, reads from stdin in the container will always result in EOF. + // Default is false. + // +optional + Stdin bool `json:"stdin,omitempty"` + // Whether the container runtime should close the stdin channel after it has been opened by + // a single attach. When stdin is true the stdin stream will remain open across multiple attach + // sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the + // first client attaches to stdin, and then remains open and accepts data until the client disconnects, + // at which time stdin is closed and remains closed until the container is restarted. If this + // flag is false, a container process that reads from stdin will never receive an EOF. + // Default is false + // +optional + StdinOnce bool `json:"stdinOnce,omitempty"` + // Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. + // Default is false. + // +optional + TTY bool `json:"tty,omitempty"` +} + +// Handler defines a specific action that should be taken +// TODO: pass structured data to these actions, and document that data here. +type Handler struct { + // One and only one of the following should be specified. + // Exec specifies the action to take. + // +optional + Exec *ExecAction `json:"exec,omitempty"` + // HTTPGet specifies the http request to perform. + // +optional + HTTPGet *HTTPGetAction `json:"httpGet,omitempty"` + // TCPSocket specifies an action involving a TCP port. + // TCP hooks not yet supported + // TODO: implement a realistic TCP lifecycle hook + // +optional + TCPSocket *TCPSocketAction `json:"tcpSocket,omitempty"` +} + +// Lifecycle describes actions that the management system should take in response to container lifecycle +// events. For the PostStart and PreStop lifecycle handlers, management of the container blocks +// until the action is complete, unless the container process fails, in which case the handler is aborted. +type Lifecycle struct { + // PostStart is called immediately after a container is created. If the handler fails, + // the container is terminated and restarted according to its restart policy. + // Other management of the container blocks until the hook completes. + // More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + // +optional + PostStart *Handler `json:"postStart,omitempty"` + // PreStop is called immediately before a container is terminated due to an + // API request or management event such as liveness/startup probe failure, + // preemption, resource contention, etc. The handler is not called if the + // container crashes or exits. The reason for termination is passed to the + // handler. The Pod's termination grace period countdown begins before the + // PreStop hooked is executed. Regardless of the outcome of the handler, the + // container will eventually terminate within the Pod's termination grace + // period. Other management of the container blocks until the hook completes + // or until the termination grace period is reached. + // More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + // +optional + PreStop *Handler `json:"preStop,omitempty"` + // StopSignal defines the signal to be sent to the container when stopping. + // This value is configured via the container's Lifecycle and overrides any + // stop signal defined in the container image. If no StopSignal is specified, + // the default signal (SIGTERM) will be used. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#defining-custom-stop-signals + // +optional + StopSignal *string `json:"stopSignal,omitempty"` +} + +type ConditionStatus string + +// These are valid condition statuses. "ConditionTrue" means a resource is in the condition. +// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes +// can't decide if a resource is in the condition or not. In the future, we could add other +// intermediate conditions, e.g. ConditionDegraded. +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" + ConditionUnknown ConditionStatus = "Unknown" +) + +// ContainerStateWaiting is a waiting state of a container. +type ContainerStateWaiting struct { + // (brief) reason the container is not yet running. + // +optional + Reason string `json:"reason,omitempty"` + // Message regarding why the container is not yet running. + // +optional + Message string `json:"message,omitempty"` +} + +// ContainerStateRunning is a running state of a container. +type ContainerStateRunning struct { + // Time at which the container was last (re-)started + // +optional + StartedAt metav1.Time `json:"startedAt"` +} + +// ContainerStateTerminated is a terminated state of a container. +type ContainerStateTerminated struct { + // Exit status from the last termination of the container + ExitCode int32 `json:"exitCode"` + // Signal from the last termination of the container + // +optional + Signal int32 `json:"signal,omitempty"` + // (brief) reason from the last termination of the container + // +optional + Reason string `json:"reason,omitempty"` + // Message regarding the last termination of the container + // +optional + Message string `json:"message,omitempty"` + // Time at which previous execution of the container started + // +optional + StartedAt metav1.Time `json:"startedAt"` + // Time at which the container last terminated + // +optional + FinishedAt metav1.Time `json:"finishedAt"` + // Container's ID in the format 'docker://' + // +optional + ContainerID string `json:"containerID,omitempty"` +} + +// ContainerState holds a possible state of container. +// Only one of its members may be specified. +// If none of them is specified, the default one is ContainerStateWaiting. +type ContainerState struct { + // Details about a waiting container + // +optional + Waiting *ContainerStateWaiting `json:"waiting,omitempty"` + // Details about a running container + // +optional + Running *ContainerStateRunning `json:"running,omitempty"` + // Details about a terminated container + // +optional + Terminated *ContainerStateTerminated `json:"terminated,omitempty"` +} + +// ContainerStatus contains details for the current status of this container. +type ContainerStatus struct { + // This must be a DNS_LABEL. Each container in a pod must have a unique name. + // Cannot be updated. + Name string `json:"name"` + // Details about the container's current condition. + // +optional + State ContainerState `json:"state"` + // Details about the container's last termination condition. + // +optional + LastTerminationState ContainerState `json:"lastState"` + // Specifies whether the container has passed its readiness probe. + Ready bool `json:"ready"` + // The number of times the container has been restarted, currently based on + // the number of dead containers that have not yet been removed. + // Note that this is calculated from dead containers. But those containers are subject to + // garbage collection. This value will get capped at 5 by GC. + RestartCount int32 `json:"restartCount"` + // The image the container is running. + // More info: https://kubernetes.io/docs/concepts/containers/images + // TODO(dchen1107): Which image the container is running with? + Image string `json:"image"` + // ImageID of the container's image. + ImageID string `json:"imageID"` + // Container's ID in the format 'docker://'. + // +optional + ContainerID string `json:"containerID,omitempty"` + // Specifies whether the container has passed its startup probe. + // Initialized as false, becomes true after startupProbe is considered successful. + // Resets to false when the container is restarted, or if kubelet loses state temporarily. + // Is always true when no startupProbe is defined. + // +optional + Started *bool `json:"started,omitempty"` +} + +// PodPhase is a label for the condition of a pod at the current time. +type PodPhase string + +// These are the valid statuses of pods. +const ( + // PodPending means the pod has been accepted by the system, but one or more of the containers + // has not been started. This includes time before being bound to a node, as well as time spent + // pulling images onto the host. + PodPending PodPhase = "Pending" + // PodRunning means the pod has been bound to a node and all of the containers have been started. + // At least one container is still running or is in the process of being restarted. + PodRunning PodPhase = "Running" + // PodSucceeded means that all containers in the pod have voluntarily terminated + // with a container exit code of 0, and the system is not going to restart any of these containers. + PodSucceeded PodPhase = "Succeeded" + // PodFailed means that all containers in the pod have terminated, and at least one container has + // terminated in a failure (exited with a non-zero exit code or was stopped by the system). + PodFailed PodPhase = "Failed" + // PodUnknown means that for some reason the state of the pod could not be obtained, typically due + // to an error in communicating with the host of the pod. + // Deprecated: It isn't being set since 2015 (74da3b14b0c0f658b3bb8d2def5094686d0e9095) + PodUnknown PodPhase = "Unknown" +) + +// PodConditionType is a valid value for PodCondition.Type +type PodConditionType string + +// These are valid conditions of pod. +const ( + // ContainersReady indicates whether all containers in the pod are ready. + ContainersReady PodConditionType = "ContainersReady" + // PodInitialized means that all init containers in the pod have started successfully. + PodInitialized PodConditionType = "Initialized" + // PodReady means the pod is able to service requests and should be added to the + // load balancing pools of all matching services. + PodReady PodConditionType = "Ready" + // PodScheduled represents status of the scheduling process for this pod. + PodScheduled PodConditionType = "PodScheduled" +) + +// These are reasons for a pod's transition to a condition. +const ( + // PodReasonUnschedulable reason in PodScheduled PodCondition means that the scheduler + // can't schedule the pod right now, for example due to insufficient resources in the cluster. + PodReasonUnschedulable = "Unschedulable" +) + +// PodCondition contains details for the current condition of this pod. +type PodCondition struct { + // Type is the type of the condition. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + Type PodConditionType `json:"type"` + // Status is the status of the condition. + // Can be True, False, Unknown. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + Status ConditionStatus `json:"status"` + // Last time we probed the condition. + // +optional + LastProbeTime metav1.Time `json:"lastProbeTime"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime"` + // Unique, one-word, CamelCase reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` + // Human-readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty"` +} + +// RestartPolicy describes how the container should be restarted. +// Only one of the following restart policies may be specified. +// If none of the following policies is specified, the default one +// is RestartPolicyAlways. +type RestartPolicy string + +const ( + RestartPolicyAlways RestartPolicy = "Always" + RestartPolicyOnFailure RestartPolicy = "OnFailure" + RestartPolicyNever RestartPolicy = "Never" +) + +// DNSPolicy defines how a pod's DNS will be configured. +type DNSPolicy string + +const ( + // DNSClusterFirstWithHostNet indicates that the pod should use cluster DNS + // first, if it is available, then fall back on the default + // (as determined by kubelet) DNS settings. + DNSClusterFirstWithHostNet DNSPolicy = "ClusterFirstWithHostNet" + + // DNSClusterFirst indicates that the pod should use cluster DNS + // first unless hostNetwork is true, if it is available, then + // fall back on the default (as determined by kubelet) DNS settings. + DNSClusterFirst DNSPolicy = "ClusterFirst" + + // DNSDefault indicates that the pod should use the default (as + // determined by kubelet) DNS settings. + DNSDefault DNSPolicy = "Default" + + // DNSNone indicates that the pod should use empty DNS settings. DNS + // parameters such as nameservers and search paths should be defined via + // DNSConfig. + DNSNone DNSPolicy = "None" +) + +const ( + // DefaultTerminationGracePeriodSeconds indicates the default duration in + // seconds a pod needs to terminate gracefully. + DefaultTerminationGracePeriodSeconds = 30 +) + +// A node selector represents the union of the results of one or more label queries +// over a set of nodes; that is, it represents the OR of the selectors represented +// by the node selector terms. +// +structType=atomic +type NodeSelector struct { + // Required. A list of node selector terms. The terms are ORed. + NodeSelectorTerms []NodeSelectorTerm `json:"nodeSelectorTerms"` +} + +// A null or empty node selector term matches no objects. The requirements of +// them are ANDed. +// The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. +// +structType=atomic +type NodeSelectorTerm struct { + // A list of node selector requirements by node's labels. + // +optional + MatchExpressions []NodeSelectorRequirement `json:"matchExpressions,omitempty"` + // A list of node selector requirements by node's fields. + // +optional + MatchFields []NodeSelectorRequirement `json:"matchFields,omitempty"` +} + +// A node selector requirement is a selector that contains values, a key, and an operator +// that relates the key and values. +type NodeSelectorRequirement struct { + // The label key that the selector applies to. + Key string `json:"key"` + // Represents a key's relationship to a set of values. + // Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + Operator NodeSelectorOperator `json:"operator"` + // An array of string values. If the operator is In or NotIn, + // the values array must be non-empty. If the operator is Exists or DoesNotExist, + // the values array must be empty. If the operator is Gt or Lt, the values + // array must have a single element, which will be interpreted as an integer. + // This array is replaced during a strategic merge patch. + // +optional + Values []string `json:"values,omitempty"` +} + +// A node selector operator is the set of operators that can be used in +// a node selector requirement. +type NodeSelectorOperator string + +const ( + NodeSelectorOpIn NodeSelectorOperator = "In" + NodeSelectorOpNotIn NodeSelectorOperator = "NotIn" + NodeSelectorOpExists NodeSelectorOperator = "Exists" + NodeSelectorOpDoesNotExist NodeSelectorOperator = "DoesNotExist" + NodeSelectorOpGt NodeSelectorOperator = "Gt" + NodeSelectorOpLt NodeSelectorOperator = "Lt" +) + +// A topology selector term represents the result of label queries. +// A null or empty topology selector term matches no objects. +// The requirements of them are ANDed. +// It provides a subset of functionality as NodeSelectorTerm. +// This is an alpha feature and may change in the future. +// +structType=atomic +type TopologySelectorTerm struct { + // Usage: Fields of type []TopologySelectorTerm must be listType=atomic. + + // A list of topology selector requirements by labels. + // +optional + MatchLabelExpressions []TopologySelectorLabelRequirement `json:"matchLabelExpressions,omitempty"` +} + +// A topology selector requirement is a selector that matches given label. +// This is an alpha feature and may change in the future. +type TopologySelectorLabelRequirement struct { + // The label key that the selector applies to. + Key string `json:"key"` + // An array of string values. One value must match the label to be selected. + // Each entry in Values is ORed. + Values []string `json:"values"` +} + +// Affinity is a group of affinity scheduling rules. +type Affinity struct { + // Describes node affinity scheduling rules for the pod. + // +optional + NodeAffinity *NodeAffinity `json:"nodeAffinity,omitempty"` + // Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + // +optional + PodAffinity *PodAffinity `json:"podAffinity,omitempty"` + // Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + // +optional + PodAntiAffinity *PodAntiAffinity `json:"podAntiAffinity,omitempty"` +} + +// Pod affinity is a group of inter pod affinity scheduling rules. +type PodAffinity struct { + // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. + // If the affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to a pod label update), the + // system will try to eventually evict the pod from its node. + // When there are multiple elements, the lists of nodes corresponding to each + // podAffinityTerm are intersected, i.e. all terms must be satisfied. + // +optional + // RequiredDuringSchedulingRequiredDuringExecution []PodAffinityTerm `json:"requiredDuringSchedulingRequiredDuringExecution,omitempty"` + + // If the affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to a pod label update), the + // system may or may not try to eventually evict the pod from its node. + // When there are multiple elements, the lists of nodes corresponding to each + // podAffinityTerm are intersected, i.e. all terms must be satisfied. + // +optional + RequiredDuringSchedulingIgnoredDuringExecution []PodAffinityTerm `json:"requiredDuringSchedulingIgnoredDuringExecution,omitempty"` + // The scheduler will prefer to schedule pods to nodes that satisfy + // the affinity expressions specified by this field, but it may choose + // a node that violates one or more of the expressions. The node that is + // most preferred is the one with the greatest sum of weights, i.e. + // for each node that meets all of the scheduling requirements (resource + // request, requiredDuringScheduling affinity expressions, etc.), + // compute a sum by iterating through the elements of this field and adding + // "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + // node(s) with the highest sum are the most preferred. + // +optional + PreferredDuringSchedulingIgnoredDuringExecution []WeightedPodAffinityTerm `json:"preferredDuringSchedulingIgnoredDuringExecution,omitempty"` +} + +// Pod anti affinity is a group of inter pod anti affinity scheduling rules. +type PodAntiAffinity struct { + // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. + // If the anti-affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the anti-affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to a pod label update), the + // system will try to eventually evict the pod from its node. + // When there are multiple elements, the lists of nodes corresponding to each + // podAffinityTerm are intersected, i.e. all terms must be satisfied. + // +optional + // RequiredDuringSchedulingRequiredDuringExecution []PodAffinityTerm `json:"requiredDuringSchedulingRequiredDuringExecution,omitempty"` + + // If the anti-affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the anti-affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to a pod label update), the + // system may or may not try to eventually evict the pod from its node. + // When there are multiple elements, the lists of nodes corresponding to each + // podAffinityTerm are intersected, i.e. all terms must be satisfied. + // +optional + RequiredDuringSchedulingIgnoredDuringExecution []PodAffinityTerm `json:"requiredDuringSchedulingIgnoredDuringExecution,omitempty"` + // The scheduler will prefer to schedule pods to nodes that satisfy + // the anti-affinity expressions specified by this field, but it may choose + // a node that violates one or more of the expressions. The node that is + // most preferred is the one with the greatest sum of weights, i.e. + // for each node that meets all of the scheduling requirements (resource + // request, requiredDuringScheduling anti-affinity expressions, etc.), + // compute a sum by iterating through the elements of this field and adding + // "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + // node(s) with the highest sum are the most preferred. + // +optional + PreferredDuringSchedulingIgnoredDuringExecution []WeightedPodAffinityTerm `json:"preferredDuringSchedulingIgnoredDuringExecution,omitempty"` +} + +// The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) +type WeightedPodAffinityTerm struct { + // weight associated with matching the corresponding podAffinityTerm, + // in the range 1-100. + Weight int32 `json:"weight"` + // Required. A pod affinity term, associated with the corresponding weight. + PodAffinityTerm PodAffinityTerm `json:"podAffinityTerm"` +} + +// Defines a set of pods (namely those matching the labelSelector +// relative to the given namespace(s)) that this pod should be +// co-located (affinity) or not co-located (anti-affinity) with, +// where co-located is defined as running on a node whose value of +// the label with key matches that of any node on which +// a pod of the set of pods is running +type PodAffinityTerm struct { + // A label query over a set of resources, in this case pods. + // +optional + LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"` + // namespaces specifies a static list of namespace names that the term applies to. + // The term is applied to the union of the namespaces listed in this field + // and the ones selected by namespaceSelector. + // null or empty namespaces list and null namespaceSelector means "this pod's namespace" + // +optional + Namespaces []string `json:"namespaces,omitempty"` + // This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + // the labelSelector in the specified namespaces, where co-located is defined as running on a node + // whose value of the label with key topologyKey matches that of any node on which any of the + // selected pods is running. + // Empty topologyKey is not allowed. + TopologyKey string `json:"topologyKey"` + // A label query over the set of namespaces that the term applies to. + // The term is applied to the union of the namespaces selected by this field + // and the ones listed in the namespaces field. + // null selector and null or empty namespaces list means "this pod's namespace". + // An empty selector ({}) matches all namespaces. + // This field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled. + // +optional + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"` +} + +// Node affinity is a group of node affinity scheduling rules. +type NodeAffinity struct { + // NOT YET IMPLEMENTED. TODO: Uncomment field once it is implemented. + // If the affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to an update), the system + // will try to eventually evict the pod from its node. + // +optional + // RequiredDuringSchedulingRequiredDuringExecution *NodeSelector `json:"requiredDuringSchedulingRequiredDuringExecution,omitempty"` + + // If the affinity requirements specified by this field are not met at + // scheduling time, the pod will not be scheduled onto the node. + // If the affinity requirements specified by this field cease to be met + // at some point during pod execution (e.g. due to an update), the system + // may or may not try to eventually evict the pod from its node. + // +optional + RequiredDuringSchedulingIgnoredDuringExecution *NodeSelector `json:"requiredDuringSchedulingIgnoredDuringExecution,omitempty"` + // The scheduler will prefer to schedule pods to nodes that satisfy + // the affinity expressions specified by this field, but it may choose + // a node that violates one or more of the expressions. The node that is + // most preferred is the one with the greatest sum of weights, i.e. + // for each node that meets all of the scheduling requirements (resource + // request, requiredDuringScheduling affinity expressions, etc.), + // compute a sum by iterating through the elements of this field and adding + // "weight" to the sum if the node matches the corresponding matchExpressions; the + // node(s) with the highest sum are the most preferred. + // +optional + PreferredDuringSchedulingIgnoredDuringExecution []PreferredSchedulingTerm `json:"preferredDuringSchedulingIgnoredDuringExecution,omitempty"` +} + +// An empty preferred scheduling term matches all objects with implicit weight 0 +// (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). +type PreferredSchedulingTerm struct { + // Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + Weight int32 `json:"weight"` + // A node selector term, associated with the corresponding weight. + Preference NodeSelectorTerm `json:"preference"` +} + +// PodReadinessGate contains the reference to a pod condition +type PodReadinessGate struct { + // ConditionType refers to a condition in the pod's condition list with matching type. + ConditionType PodConditionType `json:"conditionType"` +} + +// PodSpec is a description of a pod. +type PodSpec struct { + // List of volumes that can be mounted by containers belonging to the pod. + // More info: https://kubernetes.io/docs/concepts/storage/volumes + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + Volumes []Volume `json:"volumes,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name"` + // List of initialization containers belonging to the pod. + // Init containers are executed in order prior to containers being started. If any + // init container fails, the pod is considered to have failed and is handled according + // to its restartPolicy. The name for an init container or normal container must be + // unique among all containers. + // Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. + // The resourceRequirements of an init container are taken into account during scheduling + // by finding the highest request/limit for each resource type, and then using the max of + // of that value or the sum of the normal containers. Limits are applied to init containers + // in a similar fashion. + // Init containers cannot currently be added or removed. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + // +patchMergeKey=name + // +patchStrategy=merge + InitContainers []Container `json:"initContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + // List of containers belonging to the pod. + // Containers cannot currently be added or removed. + // There must be at least one container in a Pod. + // Cannot be updated. + // +patchMergeKey=name + // +patchStrategy=merge + Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name"` + // List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing + // pod to perform user-initiated actions such as debugging. This list cannot be specified when + // creating a pod, and it cannot be modified by updating the pod spec. In order to add an + // ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource. + // This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + EphemeralContainers []EphemeralContainer `json:"ephemeralContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + // Restart policy for all containers within the pod. + // One of Always, OnFailure, Never. + // Default to Always. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy + // +optional + RestartPolicy RestartPolicy `json:"restartPolicy,omitempty"` + // Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + // Value must be non-negative integer. The value zero indicates stop immediately via + // the kill signal (no opportunity to shut down). + // If this value is nil, the default grace period will be used instead. + // The grace period is the duration in seconds after the processes running in the pod are sent + // a termination signal and the time when the processes are forcibly halted with a kill signal. + // Set this value longer than the expected cleanup time for your process. + // Defaults to 30 seconds. + // +optional + TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"` + // Optional duration in seconds the pod may be active on the node relative to + // StartTime before the system will actively try to mark it failed and kill associated containers. + // Value must be a positive integer. + // +optional + ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"` + // Set DNS policy for the pod. + // Defaults to "ClusterFirst". + // Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. + // DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. + // To have DNS options set along with hostNetwork, you have to specify DNS policy + // explicitly to 'ClusterFirstWithHostNet'. + // +optional + DNSPolicy DNSPolicy `json:"dnsPolicy,omitempty"` + // NodeSelector is a selector which must be true for the pod to fit on a node. + // Selector which must match a node's labels for the pod to be scheduled on that node. + // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + // +optional + // +mapType=atomic + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + + // ServiceAccountName is the name of the ServiceAccount to use to run this pod. + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` + // DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. + // Deprecated: Use serviceAccountName instead. + // +k8s:conversion-gen=false + // +optional + DeprecatedServiceAccount string `json:"serviceAccount,omitempty"` + // AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + // +optional + AutomountServiceAccountToken *bool `json:"automountServiceAccountToken,omitempty"` + + // NodeName is a request to schedule this pod onto a specific node. If it is non-empty, + // the scheduler simply schedules this pod onto that node, assuming that it fits resource + // requirements. + // +optional + NodeName string `json:"nodeName,omitempty"` + // Host networking requested for this pod. Use the host's network namespace. + // If this option is set, the ports that will be used must be specified. + // Default to false. + // +k8s:conversion-gen=false + // +optional + HostNetwork bool `json:"hostNetwork,omitempty"` + // Use the host's pid namespace. + // Optional: Default to false. + // +k8s:conversion-gen=false + // +optional + HostPID bool `json:"hostPID,omitempty"` + // Use the host's ipc namespace. + // Optional: Default to false. + // +k8s:conversion-gen=false + // +optional + HostIPC bool `json:"hostIPC,omitempty"` + // Share a single process namespace between all of the containers in a pod. + // When this is set containers will be able to view and signal processes from other containers + // in the same pod, and the first process in each container will not be assigned PID 1. + // HostPID and ShareProcessNamespace cannot both be set. + // Optional: Default to false. + // +k8s:conversion-gen=false + // +optional + ShareProcessNamespace *bool `json:"shareProcessNamespace,omitempty"` + // SecurityContext holds pod-level security attributes and common container settings. + // Optional: Defaults to empty. See type description for default values of each field. + // +optional + SecurityContext *PodSecurityContext `json:"securityContext,omitempty"` + // ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. + // If specified, these secrets will be passed to individual puller implementations for them to use. For example, + // in the case of docker, only DockerConfig type secrets are honored. + // More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + ImagePullSecrets []LocalObjectReference `json:"imagePullSecrets,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + // Specifies the hostname of the Pod + // If not specified, the pod's hostname will be set to a system-defined value. + // +optional + Hostname string `json:"hostname,omitempty"` + // If specified, the fully qualified Pod hostname will be "...svc.". + // If not specified, the pod will not have a domainname at all. + // +optional + Subdomain string `json:"subdomain,omitempty"` + // If specified, the pod's scheduling constraints + // +optional + Affinity *Affinity `json:"affinity,omitempty"` + // If specified, the pod will be dispatched by specified scheduler. + // If not specified, the pod will be dispatched by default scheduler. + // +optional + SchedulerName string `json:"schedulerName,omitempty"` + // HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts + // file if specified. This is only valid for non-hostNetwork pods. + // +optional + // +patchMergeKey=ip + // +patchStrategy=merge + HostAliases []HostAlias `json:"hostAliases,omitempty" patchStrategy:"merge" patchMergeKey:"ip"` + // If specified, indicates the pod's priority. "system-node-critical" and + // "system-cluster-critical" are two special keywords which indicate the + // highest priorities with the former being the highest priority. Any other + // name must be defined by creating a PriorityClass object with that name. + // If not specified, the pod priority will be default or zero if there is no + // default. + // +optional + PriorityClassName string `json:"priorityClassName,omitempty"` + // The priority value. Various system components use this field to find the + // priority of the pod. When Priority Admission Controller is enabled, it + // prevents users from setting this field. The admission controller populates + // this field from PriorityClassName. + // The higher the value, the higher the priority. + // +optional + Priority *int32 `json:"priority,omitempty"` + // Specifies the DNS parameters of a pod. + // Parameters specified here will be merged to the generated DNS + // configuration based on DNSPolicy. + // +optional + DNSConfig *PodDNSConfig `json:"dnsConfig,omitempty"` + // If specified, all readiness gates will be evaluated for pod readiness. + // A pod is ready when all its containers are ready AND + // all conditions specified in the readiness gates have status equal to "True" + // More info: https://git.k8s.io/enhancements/keps/sig-network/580-pod-readiness-gates + // +optional + ReadinessGates []PodReadinessGate `json:"readinessGates,omitempty"` + // RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used + // to run this pod. If no RuntimeClass resource matches the named class, the pod will not be run. + // If unset or empty, the "legacy" RuntimeClass will be used, which is an implicit class with an + // empty definition that uses the default runtime handler. + // More info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class + // This is a beta feature as of Kubernetes v1.14. + // +optional + RuntimeClassName *string `json:"runtimeClassName,omitempty"` + // EnableServiceLinks indicates whether information about services should be injected into pod's + // environment variables, matching the syntax of Docker links. + // Optional: Defaults to true. + // +optional + EnableServiceLinks *bool `json:"enableServiceLinks,omitempty"` + // PreemptionPolicy is the Policy for preempting pods with lower priority. + // One of Never, PreemptLowerPriority. + // Defaults to PreemptLowerPriority if unset. + // This field is beta-level, gated by the NonPreemptingPriority feature-gate. + // +optional + PreemptionPolicy *PreemptionPolicy `json:"preemptionPolicy,omitempty"` + // Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. + // This field will be autopopulated at admission time by the RuntimeClass admission controller. If + // the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. + // The RuntimeClass admission controller will reject Pod create requests which have the overhead already + // set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value + // defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. + // More info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead/README.md + // This field is beta-level as of Kubernetes v1.18, and is only honored by servers that enable the PodOverhead feature. + // +optional + Overhead ResourceList `json:"overhead,omitempty"` + // TopologySpreadConstraints describes how a group of pods ought to spread across topology + // domains. Scheduler will schedule pods in a way which abides by the constraints. + // All topologySpreadConstraints are ANDed. + // +optional + // +patchMergeKey=topologyKey + // +patchStrategy=merge + // +listType=map + // +listMapKey=topologyKey + // +listMapKey=whenUnsatisfiable + TopologySpreadConstraints []TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty" patchStrategy:"merge" patchMergeKey:"topologyKey"` + // If true the pod's hostname will be configured as the pod's FQDN, rather than the leaf name (the default). + // In Linux containers, this means setting the FQDN in the hostname field of the kernel (the nodename field of struct utsname). + // In Windows containers, this means setting the registry value of hostname for the registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters to FQDN. + // If a pod does not have FQDN, this has no effect. + // Default to false. + // +optional + SetHostnameAsFQDN *bool `json:"setHostnameAsFQDN,omitempty"` + // Use the host's user namespace. + // Optional: Default to true. + // If set to true or not present, the pod will be run in the host user namespace, useful + // for when the pod needs a feature only available to the host user namespace, such as + // loading a kernel module with CAP_SYS_MODULE. + // When set to false, a new userns is created for the pod. Setting false is useful for + // mitigating container breakout vulnerabilities even allowing users to run their + // containers as root without actually having root privileges on the host. + // This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature. + // +k8s:conversion-gen=false + // +optional + HostUsers *bool `json:"hostUsers,omitempty"` +} + +type UnsatisfiableConstraintAction string + +const ( + // DoNotSchedule instructs the scheduler not to schedule the pod + // when constraints are not satisfied. + DoNotSchedule UnsatisfiableConstraintAction = "DoNotSchedule" + // ScheduleAnyway instructs the scheduler to schedule the pod + // even if constraints are not satisfied. + ScheduleAnyway UnsatisfiableConstraintAction = "ScheduleAnyway" +) + +// TopologySpreadConstraint specifies how to spread matching pods among the given topology. +type TopologySpreadConstraint struct { + // MaxSkew describes the degree to which pods may be unevenly distributed. + // When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + // between the number of matching pods in the target topology and the global minimum. + // For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + // labelSelector spread as 1/1/0: + // +-------+-------+-------+ + // | zone1 | zone2 | zone3 | + // +-------+-------+-------+ + // | P | P | | + // +-------+-------+-------+ + // - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 1/1/1; + // scheduling it onto zone1(zone2) would make the ActualSkew(2-0) on zone1(zone2) + // violate MaxSkew(1). + // - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + // When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + // to topologies that satisfy it. + // It's a required field. Default value is 1 and 0 is not allowed. + MaxSkew int32 `json:"maxSkew"` + // TopologyKey is the key of node labels. Nodes that have a label with this key + // and identical values are considered to be in the same topology. + // We consider each as a "bucket", and try to put balanced number + // of pods into each bucket. + // It's a required field. + TopologyKey string `json:"topologyKey"` + // WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + // the spread constraint. + // - DoNotSchedule (default) tells the scheduler not to schedule it. + // - ScheduleAnyway tells the scheduler to schedule the pod in any location, + // but giving higher precedence to topologies that would help reduce the + // skew. + // A constraint is considered "Unsatisfiable" for an incoming pod + // if and only if every possible node assignment for that pod would violate + // "MaxSkew" on some topology. + // For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + // labelSelector spread as 3/1/1: + // +-------+-------+-------+ + // | zone1 | zone2 | zone3 | + // +-------+-------+-------+ + // | P P P | P | P | + // +-------+-------+-------+ + // If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + // to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + // MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + // won't make it *more* imbalanced. + // It's a required field. + WhenUnsatisfiable UnsatisfiableConstraintAction `json:"whenUnsatisfiable"` + // LabelSelector is used to find matching pods. + // Pods that match this label selector are counted to determine the number of pods + // in their corresponding topology domain. + // +optional + LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"` +} + +const ( + // The default value for enableServiceLinks attribute. + DefaultEnableServiceLinks = true +) + +// HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the +// pod's hosts file. +type HostAlias struct { + // IP address of the host file entry. + IP string `json:"ip,omitempty"` + // Hostnames for the above IP address. + Hostnames []string `json:"hostnames,omitempty"` +} + +// PodFSGroupChangePolicy holds policies that will be used for applying fsGroup to a volume +// when volume is mounted. +type PodFSGroupChangePolicy string + +const ( + // FSGroupChangeOnRootMismatch indicates that volume's ownership and permissions will be changed + // only when permission and ownership of root directory does not match with expected + // permissions on the volume. This can help shorten the time it takes to change + // ownership and permissions of a volume. + FSGroupChangeOnRootMismatch PodFSGroupChangePolicy = "OnRootMismatch" + // FSGroupChangeAlways indicates that volume's ownership and permissions + // should always be changed whenever volume is mounted inside a Pod. This the default + // behavior. + FSGroupChangeAlways PodFSGroupChangePolicy = "Always" +) + +// PodSecurityContext holds pod-level security attributes and common container settings. +// Some fields are also present in container.securityContext. Field values of +// container.securityContext take precedence over field values of PodSecurityContext. +type PodSecurityContext struct { + // The SELinux context to be applied to all containers. + // If unspecified, the container runtime will allocate a random SELinux context for each + // container. May also be set in SecurityContext. If set in + // both SecurityContext and PodSecurityContext, the value specified in SecurityContext + // takes precedence for that container. + // +optional + SELinuxOptions *SELinuxOptions `json:"seLinuxOptions,omitempty"` + // The UID to run the entrypoint of the container process. + // Defaults to user specified in image metadata if unspecified. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence + // for that container. + // +optional + RunAsUser *int64 `json:"runAsUser,omitempty"` + // The GID to run the entrypoint of the container process. + // Uses runtime default if unset. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence + // for that container. + // +optional + RunAsGroup *int64 `json:"runAsGroup,omitempty"` + // Indicates that the container must run as a non-root user. + // If true, the Kubelet will validate the image at runtime to ensure that it + // does not run as UID 0 (root) and fail to start the container if it does. + // If unset or false, no such validation will be performed. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"` + // A list of groups applied to the first process run in each container, in addition + // to the container's primary GID. If unspecified, no groups will be added to + // any container. + // +optional + SupplementalGroups []int64 `json:"supplementalGroups,omitempty"` + // A special supplemental group that applies to all containers in a pod. + // Some volume types allow the Kubelet to change the ownership of that volume + // to be owned by the pod: + // + // 1. The owning GID will be the FSGroup + // 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + // 3. The permission bits are OR'd with rw-rw---- + // + // If unset, the Kubelet will not modify the ownership and permissions of any volume. + // +optional + FSGroup *int64 `json:"fsGroup,omitempty"` + // Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + // sysctls (by the container runtime) might fail to launch. + // +optional + Sysctls []Sysctl `json:"sysctls,omitempty"` + // fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + // before being exposed inside Pod. This field will only apply to + // volume types which support fsGroup based ownership(and permissions). + // It will have no effect on ephemeral volume types such as: secret, configmaps + // and emptydir. + // Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + // +optional + FSGroupChangePolicy *PodFSGroupChangePolicy `json:"fsGroupChangePolicy,omitempty"` + // The seccomp options to use by the containers in this pod. + // +optional + SeccompProfile *SeccompProfile `json:"seccompProfile,omitempty"` +} + +// SeccompProfile defines a pod/container's seccomp profile settings. +// Only one profile source may be set. +// +union +type SeccompProfile struct { + // type indicates which kind of seccomp profile will be applied. + // Valid options are: + // + // Localhost - a profile defined in a file on the node should be used. + // RuntimeDefault - the container runtime default profile should be used. + // Unconfined - no profile should be applied. + // +unionDiscriminator + Type SeccompProfileType `json:"type"` + // localhostProfile indicates a profile defined in a file on the node should be used. + // The profile must be preconfigured on the node to work. + // Must be a descending path, relative to the kubelet's configured seccomp profile location. + // Must only be set if type is "Localhost". + // +optional + LocalhostProfile *string `json:"localhostProfile,omitempty"` +} + +// SeccompProfileType defines the supported seccomp profile types. +type SeccompProfileType string + +const ( + // SeccompProfileTypeUnconfined indicates no seccomp profile is applied (A.K.A. unconfined). + SeccompProfileTypeUnconfined SeccompProfileType = "Unconfined" + // SeccompProfileTypeRuntimeDefault represents the default container runtime seccomp profile. + SeccompProfileTypeRuntimeDefault SeccompProfileType = "RuntimeDefault" + // SeccompProfileTypeLocalhost indicates a profile defined in a file on the node should be used. + // The file's location is based off the kubelet's deprecated flag --seccomp-profile-root. + // Once the flag support is removed the location will be /seccomp. + SeccompProfileTypeLocalhost SeccompProfileType = "Localhost" +) + +// PodQOSClass defines the supported qos classes of Pods. +type PodQOSClass string + +const ( + // PodQOSGuaranteed is the Guaranteed qos class. + PodQOSGuaranteed PodQOSClass = "Guaranteed" + // PodQOSBurstable is the Burstable qos class. + PodQOSBurstable PodQOSClass = "Burstable" + // PodQOSBestEffort is the BestEffort qos class. + PodQOSBestEffort PodQOSClass = "BestEffort" +) + +// PodDNSConfig defines the DNS parameters of a pod in addition to +// those generated from DNSPolicy. +type PodDNSConfig struct { + // A list of DNS name server IP addresses. + // This will be appended to the base nameservers generated from DNSPolicy. + // Duplicated nameservers will be removed. + // +optional + Nameservers []string `json:"nameservers,omitempty"` + // A list of DNS search domains for host-name lookup. + // This will be appended to the base search paths generated from DNSPolicy. + // Duplicated search paths will be removed. + // +optional + Searches []string `json:"searches,omitempty"` + // A list of DNS resolver options. + // This will be merged with the base options generated from DNSPolicy. + // Duplicated entries will be removed. Resolution options given in Options + // will override those that appear in the base DNSPolicy. + // +optional + Options []PodDNSConfigOption `json:"options,omitempty"` +} + +// PodDNSConfigOption defines DNS resolver options of a pod. +type PodDNSConfigOption struct { + // Required. + Name string `json:"name,omitempty"` + // +optional + Value *string `json:"value,omitempty"` +} + +// IP address information for entries in the (plural) PodIPs field. +// Each entry includes: +// +// IP: An IP address allocated to the pod. Routable at least within the cluster. +type PodIP struct { + // ip is an IP address (IPv4 or IPv6) assigned to the pod + IP string `json:"ip,omitempty"` +} + +// EphemeralContainerCommon is a copy of all fields in Container to be inlined in +// EphemeralContainer. This separate type allows easy conversion from EphemeralContainer +// to Container and allows separate documentation for the fields of EphemeralContainer. +// When a new field is added to Container it must be added here as well. +type EphemeralContainerCommon struct { + // Name of the ephemeral container specified as a DNS_LABEL. + // This name must be unique among all containers, init containers and ephemeral containers. + Name string `json:"name"` + // Docker image name. + // More info: https://kubernetes.io/docs/concepts/containers/images + Image string `json:"image,omitempty"` + // Entrypoint array. Not executed within a shell. + // The docker image's ENTRYPOINT is used if this is not provided. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + // to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + // produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + // of whether the variable exists or not. Cannot be updated. + // More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + // +optional + Command []string `json:"command,omitempty"` + // Arguments to the entrypoint. + // The docker image's CMD is used if this is not provided. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + // to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + // produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + // of whether the variable exists or not. Cannot be updated. + // More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + // +optional + Args []string `json:"args,omitempty"` + // Container's working directory. + // If not specified, the container runtime's default will be used, which + // might be configured in the container image. + // Cannot be updated. + // +optional + WorkingDir string `json:"workingDir,omitempty"` + // Ports are not allowed for ephemeral containers. + Ports []ContainerPort `json:"ports,omitempty"` + // List of sources to populate environment variables in the container. + // The keys defined within a source must be a C_IDENTIFIER. All invalid keys + // will be reported as an event when the container is starting. When a key exists in multiple + // sources, the value associated with the last source will take precedence. + // Values defined by an Env with a duplicate key will take precedence. + // Cannot be updated. + // +optional + EnvFrom []EnvFromSource `json:"envFrom,omitempty"` + // List of environment variables to set in the container. + // Cannot be updated. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + Env []EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + // Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources + // already allocated to the pod. + // +optional + Resources ResourceRequirements `json:"resources"` + // Pod volumes to mount into the container's filesystem. + // Cannot be updated. + // +optional + // +patchMergeKey=mountPath + // +patchStrategy=merge + VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath"` + // volumeDevices is the list of block devices to be used by the container. + // +patchMergeKey=devicePath + // +patchStrategy=merge + // +optional + VolumeDevices []VolumeDevice `json:"volumeDevices,omitempty" patchStrategy:"merge" patchMergeKey:"devicePath"` + // Probes are not allowed for ephemeral containers. + // +optional + LivenessProbe *Probe `json:"livenessProbe,omitempty"` + // Probes are not allowed for ephemeral containers. + // +optional + ReadinessProbe *Probe `json:"readinessProbe,omitempty"` + // Probes are not allowed for ephemeral containers. + // +optional + StartupProbe *Probe `json:"startupProbe,omitempty"` + // Lifecycle is not allowed for ephemeral containers. + // +optional + Lifecycle *Lifecycle `json:"lifecycle,omitempty"` + // Optional: Path at which the file to which the container's termination message + // will be written is mounted into the container's filesystem. + // Message written is intended to be brief final status, such as an assertion failure message. + // Will be truncated by the node if greater than 4096 bytes. The total message length across + // all containers will be limited to 12kb. + // Defaults to /dev/termination-log. + // Cannot be updated. + // +optional + TerminationMessagePath string `json:"terminationMessagePath,omitempty"` + // Indicate how the termination message should be populated. File will use the contents of + // terminationMessagePath to populate the container status message on both success and failure. + // FallbackToLogsOnError will use the last chunk of container log output if the termination + // message file is empty and the container exited with an error. + // The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + // Defaults to File. + // Cannot be updated. + // +optional + TerminationMessagePolicy TerminationMessagePolicy `json:"terminationMessagePolicy,omitempty"` + // Image pull policy. + // One of Always, Never, IfNotPresent. + // Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + // +optional + ImagePullPolicy PullPolicy `json:"imagePullPolicy,omitempty"` + // Optional: SecurityContext defines the security options the ephemeral container should be run with. + // If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + // +optional + SecurityContext *SecurityContext `json:"securityContext,omitempty"` + + // Variables for interactive containers, these have very specialized use-cases (e.g. debugging) + // and shouldn't be used for general purpose containers. + + // Whether this container should allocate a buffer for stdin in the container runtime. If this + // is not set, reads from stdin in the container will always result in EOF. + // Default is false. + // +optional + Stdin bool `json:"stdin,omitempty"` + // Whether the container runtime should close the stdin channel after it has been opened by + // a single attach. When stdin is true the stdin stream will remain open across multiple attach + // sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the + // first client attaches to stdin, and then remains open and accepts data until the client disconnects, + // at which time stdin is closed and remains closed until the container is restarted. If this + // flag is false, a container process that reads from stdin will never receive an EOF. + // Default is false + // +optional + StdinOnce bool `json:"stdinOnce,omitempty"` + // Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. + // Default is false. + // +optional + TTY bool `json:"tty,omitempty"` +} + +// EphemeralContainerCommon converts to Container. All fields must be kept in sync between +// these two types. +var _ = Container(EphemeralContainerCommon{}) + +// An EphemeralContainer is a container that may be added temporarily to an existing pod for +// user-initiated activities such as debugging. Ephemeral containers have no resource or +// scheduling guarantees, and they will not be restarted when they exit or when a pod is +// removed or restarted. If an ephemeral container causes a pod to exceed its resource +// allocation, the pod may be evicted. +// Ephemeral containers may not be added by directly updating the pod spec. They must be added +// via the pod's ephemeralcontainers subresource, and they will appear in the pod spec +// once added. +// This is an alpha feature enabled by the EphemeralContainers feature flag. +type EphemeralContainer struct { + // Ephemeral containers have all of the fields of Container, plus additional fields + // specific to ephemeral containers. Fields in common with Container are in the + // following inlined struct so that an EphemeralContainer may easily be converted + // to a Container. + EphemeralContainerCommon `json:",inline"` + + // If set, the name of the container from PodSpec that this ephemeral container targets. + // The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container. + // If not set then the ephemeral container is run in whatever namespaces are shared + // for the pod. Note that the container runtime must support this feature. + // +optional + TargetContainerName string `json:"targetContainerName,omitempty"` +} + +// PodStatus represents information about the status of a pod. Status may trail the actual +// state of a system, especially if the node that hosts the pod cannot contact the control +// plane. +type PodStatus struct { + // The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. + // The conditions array, the reason and message fields, and the individual container status + // arrays contain more detail about the pod's status. + // There are five possible phase values: + // + // Pending: The pod has been accepted by the Kubernetes system, but one or more of the + // container images has not been created. This includes time before being scheduled as + // well as time spent downloading images over the network, which could take a while. + // Running: The pod has been bound to a node, and all of the containers have been created. + // At least one container is still running, or is in the process of starting or restarting. + // Succeeded: All containers in the pod have terminated in success, and will not be restarted. + // Failed: All containers in the pod have terminated, and at least one container has + // terminated in failure. The container either exited with non-zero status or was terminated + // by the system. + // Unknown: For some reason the state of the pod could not be obtained, typically due to an + // error in communicating with the host of the pod. + // + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-phase + // +optional + Phase PodPhase `json:"phase,omitempty"` + // Current service state of pod. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []PodCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // A human readable message indicating details about why the pod is in this condition. + // +optional + Message string `json:"message,omitempty"` + // A brief CamelCase message indicating details about why the pod is in this state. + // e.g. 'Evicted' + // +optional + Reason string `json:"reason,omitempty"` + // nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be + // scheduled right away as preemption victims receive their graceful termination periods. + // This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide + // to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to + // give the resources on this node to a higher priority pod that is created after preemption. + // As a result, this field may be different than PodSpec.nodeName when the pod is + // scheduled. + // +optional + NominatedNodeName string `json:"nominatedNodeName,omitempty"` + + // IP address of the host to which the pod is assigned. Empty if not yet scheduled. + // +optional + HostIP string `json:"hostIP,omitempty"` + // IP address allocated to the pod. Routable at least within the cluster. + // Empty if not yet allocated. + // +optional + PodIP string `json:"podIP,omitempty"` + + // podIPs holds the IP addresses allocated to the pod. If this field is specified, the 0th entry must + // match the podIP field. Pods may be allocated at most 1 value for each of IPv4 and IPv6. This list + // is empty if no IPs have been allocated yet. + // +optional + // +patchStrategy=merge + // +patchMergeKey=ip + PodIPs []PodIP `json:"podIPs,omitempty"` + + // RFC 3339 date and time at which the object was acknowledged by the Kubelet. + // This is before the Kubelet pulled the container image(s) for the pod. + // +optional + StartTime *metav1.Time `json:"startTime,omitempty"` + + // The list has one entry per init container in the manifest. The most recent successful + // init container will have ready = true, the most recently started container will have + // startTime set. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status + InitContainerStatuses []ContainerStatus `json:"initContainerStatuses,omitempty"` + + // The list has one entry per container in the manifest. Each entry is currently the output + // of `docker inspect`. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status + // +optional + ContainerStatuses []ContainerStatus `json:"containerStatuses,omitempty"` + // The Quality of Service (QOS) classification assigned to the pod based on resource requirements + // See PodQOSClass type for available QOS classes + // More info: https://git.k8s.io/community/contributors/design-proposals/node/resource-qos.md + // +optional + QOSClass PodQOSClass `json:"qosClass,omitempty"` + // Status for any ephemeral containers that have run in this pod. + // This field is alpha-level and is only populated by servers that enable the EphemeralContainers feature. + // +optional + EphemeralContainerStatuses []ContainerStatus `json:"ephemeralContainerStatuses,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodStatusResult is a wrapper for PodStatus returned by kubelet that can be encode/decoded +type PodStatusResult struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + // Most recently observed status of the pod. + // This data may not be up to date. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status PodStatus `json:"status"` +} + +// +genclient +// +genclient:method=UpdateEphemeralContainers,verb=update,subresource=ephemeralcontainers +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Pod is a collection of containers that can run on a host. This resource is created +// by clients and scheduled onto hosts. +type Pod struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Specification of the desired behavior of the pod. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec PodSpec `json:"spec"` + + // Most recently observed status of the pod. + // This data may not be up to date. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status PodStatus `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodList is a list of Pods. +type PodList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // List of pods. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md + Items []Pod `json:"items"` +} + +// PodTemplateSpec describes the data a pod should have when created from a template +type PodTemplateSpec struct { + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Specification of the desired behavior of the pod. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec PodSpec `json:"spec"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodTemplate describes a template for creating copies of a predefined pod. +type PodTemplate struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Template defines the pods that will be created from this pod template. + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Template PodTemplateSpec `json:"template"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodTemplateList is a list of PodTemplates. +type PodTemplateList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // List of pod templates + Items []PodTemplate `json:"items"` +} + +// ReplicationControllerSpec is the specification of a replication controller. +type ReplicationControllerSpec struct { + // Replicas is the number of desired replicas. + // This is a pointer to distinguish between explicit zero and unspecified. + // Defaults to 1. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller + // +optional + Replicas *int32 `json:"replicas,omitempty"` + + // Minimum number of seconds for which a newly created pod should be ready + // without any of its container crashing, for it to be considered available. + // Defaults to 0 (pod will be considered available as soon as it is ready) + // +optional + MinReadySeconds int32 `json:"minReadySeconds,omitempty"` + + // Selector is a label query over pods that should match the Replicas count. + // If Selector is empty, it is defaulted to the labels present on the Pod template. + // Label keys and values that must match in order to be controlled by this replication + // controller, if empty defaulted to labels on Pod template. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + // +optional + // +mapType=atomic + Selector map[string]string `json:"selector,omitempty"` + + // TemplateRef is a reference to an object that describes the pod that will be created if + // insufficient replicas are detected. + // Reference to an object that describes the pod that will be created if insufficient replicas are detected. + // +optional + // TemplateRef *ObjectReference `json:"templateRef,omitempty"` + + // Template is the object that describes the pod that will be created if + // insufficient replicas are detected. This takes precedence over a TemplateRef. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + // +optional + Template *PodTemplateSpec `json:"template,omitempty"` +} + +// ReplicationControllerStatus represents the current status of a replication +// controller. +type ReplicationControllerStatus struct { + // Replicas is the most recently observed number of replicas. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller + Replicas int32 `json:"replicas"` + + // The number of pods that have labels matching the labels of the pod template of the replication controller. + // +optional + FullyLabeledReplicas int32 `json:"fullyLabeledReplicas,omitempty"` + + // The number of ready replicas for this replication controller. + // +optional + ReadyReplicas int32 `json:"readyReplicas,omitempty"` + + // The number of available replicas (ready for at least minReadySeconds) for this replication controller. + // +optional + AvailableReplicas int32 `json:"availableReplicas,omitempty"` + + // ObservedGeneration reflects the generation of the most recently observed replication controller. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Represents the latest available observations of a replication controller's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []ReplicationControllerCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +type ReplicationControllerConditionType string + +// These are valid conditions of a replication controller. +const ( + // ReplicationControllerReplicaFailure is added in a replication controller when one of its pods + // fails to be created due to insufficient quota, limit ranges, pod security policy, node selectors, + // etc. or deleted due to kubelet being down or finalizers are failing. + ReplicationControllerReplicaFailure ReplicationControllerConditionType = "ReplicaFailure" +) + +// ReplicationControllerCondition describes the state of a replication controller at a certain point. +type ReplicationControllerCondition struct { + // Type of replication controller condition. + Type ReplicationControllerConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status ConditionStatus `json:"status"` + // The last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty"` +} + +// +genclient +// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/api/autoscaling/v1.Scale +// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicationController represents the configuration of a replication controller. +type ReplicationController struct { + metav1.TypeMeta `json:",inline"` + + // If the Labels of a ReplicationController are empty, they are defaulted to + // be the same as the Pod(s) that the replication controller manages. + // Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Spec defines the specification of the desired behavior of the replication controller. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec ReplicationControllerSpec `json:"spec"` + + // Status is the most recently observed status of the replication controller. + // This data may be out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status ReplicationControllerStatus `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicationControllerList is a collection of replication controllers. +type ReplicationControllerList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // List of replication controllers. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller + Items []ReplicationController `json:"items"` +} + +// Session Affinity Type string +type ServiceAffinity string + +const ( + // ServiceAffinityClientIP is the Client IP based. + ServiceAffinityClientIP ServiceAffinity = "ClientIP" + + // ServiceAffinityNone - no session affinity. + ServiceAffinityNone ServiceAffinity = "None" +) + +const DefaultClientIPServiceAffinitySeconds int32 = 10800 + +// SessionAffinityConfig represents the configurations of session affinity. +type SessionAffinityConfig struct { + // clientIP contains the configurations of Client IP based session affinity. + // +optional + ClientIP *ClientIPConfig `json:"clientIP,omitempty"` +} + +// ClientIPConfig represents the configurations of Client IP based session affinity. +type ClientIPConfig struct { + // timeoutSeconds specifies the seconds of ClientIP type session sticky time. + // The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + // Default value is 10800(for 3 hours). + // +optional + TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` +} + +// Service Type string describes ingress methods for a service +type ServiceType string + +const ( + // ServiceTypeClusterIP means a service will only be accessible inside the + // cluster, via the cluster IP. + ServiceTypeClusterIP ServiceType = "ClusterIP" + + // ServiceTypeNodePort means a service will be exposed on one port of + // every node, in addition to 'ClusterIP' type. + ServiceTypeNodePort ServiceType = "NodePort" + + // ServiceTypeLoadBalancer means a service will be exposed via an + // external load balancer (if the cloud provider supports it), in addition + // to 'NodePort' type. + ServiceTypeLoadBalancer ServiceType = "LoadBalancer" + + // ServiceTypeExternalName means a service consists of only a reference to + // an external name that kubedns or equivalent will return as a CNAME + // record, with no exposing or proxying of any pods involved. + ServiceTypeExternalName ServiceType = "ExternalName" +) + +// ServiceInternalTrafficPolicyType describes the type of traffic routing for +// internal traffic +type ServiceInternalTrafficPolicyType string + +const ( + // ServiceInternalTrafficPolicyCluster routes traffic to all endpoints + ServiceInternalTrafficPolicyCluster ServiceInternalTrafficPolicyType = "Cluster" + + // ServiceInternalTrafficPolicyLocal only routes to node-local + // endpoints, otherwise drops the traffic + ServiceInternalTrafficPolicyLocal ServiceInternalTrafficPolicyType = "Local" +) + +// Service External Traffic Policy Type string +type ServiceExternalTrafficPolicyType string + +const ( + // ServiceExternalTrafficPolicyTypeLocal specifies node-local endpoints behavior. + ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local" + // ServiceExternalTrafficPolicyTypeCluster specifies node-global (legacy) behavior. + ServiceExternalTrafficPolicyTypeCluster ServiceExternalTrafficPolicyType = "Cluster" +) + +// These are the valid conditions of a service. +const ( + // LoadBalancerPortsError represents the condition of the requested ports + // on the cloud load balancer instance. + LoadBalancerPortsError = "LoadBalancerPortsError" +) + +// ServiceStatus represents the current status of a service. +type ServiceStatus struct { + // LoadBalancer contains the current status of the load-balancer, + // if one is present. + // +optional + LoadBalancer LoadBalancerStatus `json:"loadBalancer"` + // Current service state + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +// LoadBalancerStatus represents the status of a load-balancer. +type LoadBalancerStatus struct { + // Ingress is a list containing ingress points for the load-balancer. + // Traffic intended for the service should be sent to these ingress points. + // +optional + Ingress []LoadBalancerIngress `json:"ingress,omitempty"` +} + +// LoadBalancerIngress represents the status of a load-balancer ingress point: +// traffic intended for the service should be sent to an ingress point. +type LoadBalancerIngress struct { + // IP is set for load-balancer ingress points that are IP based + // (typically GCE or OpenStack load-balancers) + // +optional + IP string `json:"ip,omitempty"` + + // Hostname is set for load-balancer ingress points that are DNS based + // (typically AWS load-balancers) + // +optional + Hostname string `json:"hostname,omitempty"` + + // Ports is a list of records of service ports + // If used, every port defined in the service should have an entry in it + // +listType=atomic + // +optional + Ports []PortStatus `json:"ports,omitempty"` +} + +// IPFamily represents the IP Family (IPv4 or IPv6). This type is used +// to express the family of an IP expressed by a type (e.g. service.spec.ipFamilies). +type IPFamily string + +const ( + // IPv4Protocol indicates that this IP is IPv4 protocol + IPv4Protocol IPFamily = "IPv4" + // IPv6Protocol indicates that this IP is IPv6 protocol + IPv6Protocol IPFamily = "IPv6" +) + +// IPFamilyPolicyType represents the dual-stack-ness requested or required by a Service +type IPFamilyPolicyType string + +const ( + // IPFamilyPolicySingleStack indicates that this service is required to have a single IPFamily. + // The IPFamily assigned is based on the default IPFamily used by the cluster + // or as identified by service.spec.ipFamilies field + IPFamilyPolicySingleStack IPFamilyPolicyType = "SingleStack" + // IPFamilyPolicyPreferDualStack indicates that this service prefers dual-stack when + // the cluster is configured for dual-stack. If the cluster is not configured + // for dual-stack the service will be assigned a single IPFamily. If the IPFamily is not + // set in service.spec.ipFamilies then the service will be assigned the default IPFamily + // configured on the cluster + IPFamilyPolicyPreferDualStack IPFamilyPolicyType = "PreferDualStack" + // IPFamilyPolicyRequireDualStack indicates that this service requires dual-stack. Using + // IPFamilyPolicyRequireDualStack on a single stack cluster will result in validation errors. The + // IPFamilies (and their order) assigned to this service is based on service.spec.ipFamilies. If + // service.spec.ipFamilies was not provided then it will be assigned according to how they are + // configured on the cluster. If service.spec.ipFamilies has only one entry then the alternative + // IPFamily will be added by apiserver + IPFamilyPolicyRequireDualStack IPFamilyPolicyType = "RequireDualStack" +) + +// ServiceSpec describes the attributes that a user creates on a service. +type ServiceSpec struct { + // The list of ports that are exposed by this service. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + // +patchMergeKey=port + // +patchStrategy=merge + // +listType=map + // +listMapKey=port + // +listMapKey=protocol + Ports []ServicePort `json:"ports,omitempty" patchStrategy:"merge" patchMergeKey:"port"` + + // Route service traffic to pods with label keys and values matching this + // selector. If empty or not present, the service is assumed to have an + // external process managing its endpoints, which Kubernetes will not + // modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. + // Ignored if type is ExternalName. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/ + // +optional + // +mapType=atomic + Selector map[string]string `json:"selector,omitempty"` + + // clusterIP is the IP address of the service and is usually assigned + // randomly. If an address is specified manually, is in-range (as per + // system configuration), and is not in use, it will be allocated to the + // service; otherwise creation of the service will fail. This field may not + // be changed through updates unless the type field is also being changed + // to ExternalName (which requires this field to be blank) or the type + // field is being changed from ExternalName (in which case this field may + // optionally be specified, as describe above). Valid values are "None", + // empty string (""), or a valid IP address. Setting this to "None" makes a + // "headless service" (no virtual IP), which is useful when direct endpoint + // connections are preferred and proxying is not required. Only applies to + // types ClusterIP, NodePort, and LoadBalancer. If this field is specified + // when creating a Service of type ExternalName, creation will fail. This + // field will be wiped when updating a Service to type ExternalName. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + // +optional + ClusterIP string `json:"clusterIP,omitempty"` + + // ClusterIPs is a list of IP addresses assigned to this service, and are + // usually assigned randomly. If an address is specified manually, is + // in-range (as per system configuration), and is not in use, it will be + // allocated to the service; otherwise creation of the service will fail. + // This field may not be changed through updates unless the type field is + // also being changed to ExternalName (which requires this field to be + // empty) or the type field is being changed from ExternalName (in which + // case this field may optionally be specified, as describe above). Valid + // values are "None", empty string (""), or a valid IP address. Setting + // this to "None" makes a "headless service" (no virtual IP), which is + // useful when direct endpoint connections are preferred and proxying is + // not required. Only applies to types ClusterIP, NodePort, and + // LoadBalancer. If this field is specified when creating a Service of type + // ExternalName, creation will fail. This field will be wiped when updating + // a Service to type ExternalName. If this field is not specified, it will + // be initialized from the clusterIP field. If this field is specified, + // clients must ensure that clusterIPs[0] and clusterIP have the same + // value. + // + // Unless the "IPv6DualStack" feature gate is enabled, this field is + // limited to one value, which must be the same as the clusterIP field. If + // the feature gate is enabled, this field may hold a maximum of two + // entries (dual-stack IPs, in either order). These IPs must correspond to + // the values of the ipFamilies field. Both clusterIPs and ipFamilies are + // governed by the ipFamilyPolicy field. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + // +listType=atomic + // +optional + ClusterIPs []string `json:"clusterIPs,omitempty"` + + // type determines how the Service is exposed. Defaults to ClusterIP. Valid + // options are ExternalName, ClusterIP, NodePort, and LoadBalancer. + // "ClusterIP" allocates a cluster-internal IP address for load-balancing + // to endpoints. Endpoints are determined by the selector or if that is not + // specified, by manual construction of an Endpoints object or + // EndpointSlice objects. If clusterIP is "None", no virtual IP is + // allocated and the endpoints are published as a set of endpoints rather + // than a virtual IP. + // "NodePort" builds on ClusterIP and allocates a port on every node which + // routes to the same endpoints as the clusterIP. + // "LoadBalancer" builds on NodePort and creates an external load-balancer + // (if supported in the current cloud) which routes to the same endpoints + // as the clusterIP. + // "ExternalName" aliases this service to the specified externalName. + // Several other fields do not apply to ExternalName services. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + // +optional + Type ServiceType `json:"type,omitempty"` + + // externalIPs is a list of IP addresses for which nodes in the cluster + // will also accept traffic for this service. These IPs are not managed by + // Kubernetes. The user is responsible for ensuring that traffic arrives + // at a node with this IP. A common example is external load-balancers + // that are not part of the Kubernetes system. + // +optional + ExternalIPs []string `json:"externalIPs,omitempty"` + + // Supports "ClientIP" and "None". Used to maintain session affinity. + // Enable client IP based session affinity. + // Must be ClientIP or None. + // Defaults to None. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + // +optional + SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"` + + // Only applies to Service Type: LoadBalancer + // LoadBalancer will get created with the IP specified in this field. + // This feature depends on whether the underlying cloud-provider supports specifying + // the loadBalancerIP when a load balancer is created. + // This field will be ignored if the cloud-provider does not support the feature. + // +optional + LoadBalancerIP string `json:"loadBalancerIP,omitempty"` + + // If specified and supported by the platform, this will restrict traffic through the cloud-provider + // load-balancer will be restricted to the specified client IPs. This field will be ignored if the + // cloud-provider does not support the feature." + // More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/ + // +optional + LoadBalancerSourceRanges []string `json:"loadBalancerSourceRanges,omitempty"` + + // externalName is the external reference that discovery mechanisms will + // return as an alias for this service (e.g. a DNS CNAME record). No + // proxying will be involved. Must be a lowercase RFC-1123 hostname + // (https://tools.ietf.org/html/rfc1123) and requires `type` to be "ExternalName". + // +optional + ExternalName string `json:"externalName,omitempty"` + + // externalTrafficPolicy denotes if this Service desires to route external + // traffic to node-local or cluster-wide endpoints. "Local" preserves the + // client source IP and avoids a second hop for LoadBalancer and Nodeport + // type services, but risks potentially imbalanced traffic spreading. + // "Cluster" obscures the client source IP and may cause a second hop to + // another node, but should have good overall load-spreading. + // +optional + ExternalTrafficPolicy ServiceExternalTrafficPolicyType `json:"externalTrafficPolicy,omitempty"` + + // healthCheckNodePort specifies the healthcheck nodePort for the service. + // This only applies when type is set to LoadBalancer and + // externalTrafficPolicy is set to Local. If a value is specified, is + // in-range, and is not in use, it will be used. If not specified, a value + // will be automatically allocated. External systems (e.g. load-balancers) + // can use this port to determine if a given node holds endpoints for this + // service or not. If this field is specified when creating a Service + // which does not need it, creation will fail. This field will be wiped + // when updating a Service to no longer need it (e.g. changing type). + // +optional + HealthCheckNodePort int32 `json:"healthCheckNodePort,omitempty"` + + // publishNotReadyAddresses indicates that any agent which deals with endpoints for this + // Service should disregard any indications of ready/not-ready. + // The primary use case for setting this field is for a StatefulSet's Headless Service to + // propagate SRV DNS records for its Pods for the purpose of peer discovery. + // The Kubernetes controllers that generate Endpoints and EndpointSlice resources for + // Services interpret this to mean that all endpoints are considered "ready" even if the + // Pods themselves are not. Agents which consume only Kubernetes generated endpoints + // through the Endpoints or EndpointSlice resources can safely assume this behavior. + // +optional + PublishNotReadyAddresses bool `json:"publishNotReadyAddresses,omitempty"` + + // sessionAffinityConfig contains the configurations of session affinity. + // +optional + SessionAffinityConfig *SessionAffinityConfig `json:"sessionAffinityConfig,omitempty"` + + // TopologyKeys is tombstoned to show why 16 is reserved protobuf tag. + // TopologyKeys []string `json:"topologyKeys,omitempty"` + + // IPFamily is tombstoned to show why 15 is a reserved protobuf tag. + // IPFamily *IPFamily `json:"ipFamily,omitempty"` + + // IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this + // service, and is gated by the "IPv6DualStack" feature gate. This field + // is usually assigned automatically based on cluster configuration and the + // ipFamilyPolicy field. If this field is specified manually, the requested + // family is available in the cluster, and ipFamilyPolicy allows it, it + // will be used; otherwise creation of the service will fail. This field + // is conditionally mutable: it allows for adding or removing a secondary + // IP family, but it does not allow changing the primary IP family of the + // Service. Valid values are "IPv4" and "IPv6". This field only applies + // to Services of types ClusterIP, NodePort, and LoadBalancer, and does + // apply to "headless" services. This field will be wiped when updating a + // Service to type ExternalName. + // + // This field may hold a maximum of two entries (dual-stack families, in + // either order). These families must correspond to the values of the + // clusterIPs field, if specified. Both clusterIPs and ipFamilies are + // governed by the ipFamilyPolicy field. + // +listType=atomic + // +optional + IPFamilies []IPFamily `json:"ipFamilies,omitempty"` + + // IPFamilyPolicy represents the dual-stack-ness requested or required by + // this Service, and is gated by the "IPv6DualStack" feature gate. If + // there is no value provided, then this field will be set to SingleStack. + // Services can be "SingleStack" (a single IP family), "PreferDualStack" + // (two IP families on dual-stack configured clusters or a single IP family + // on single-stack clusters), or "RequireDualStack" (two IP families on + // dual-stack configured clusters, otherwise fail). The ipFamilies and + // clusterIPs fields depend on the value of this field. This field will be + // wiped when updating a service to type ExternalName. + // +optional + IPFamilyPolicy *IPFamilyPolicyType `json:"ipFamilyPolicy,omitempty"` + + // allocateLoadBalancerNodePorts defines if NodePorts will be automatically + // allocated for services with type LoadBalancer. Default is "true". It + // may be set to "false" if the cluster load-balancer does not rely on + // NodePorts. If the caller requests specific NodePorts (by specifying a + // value), those requests will be respected, regardless of this field. + // This field may only be set for services with type LoadBalancer and will + // be cleared if the type is changed to any other type. + // This field is beta-level and is only honored by servers that enable the ServiceLBNodePortControl feature. + // +featureGate=ServiceLBNodePortControl + // +optional + AllocateLoadBalancerNodePorts *bool `json:"allocateLoadBalancerNodePorts,omitempty"` + + // loadBalancerClass is the class of the load balancer implementation this Service belongs to. + // If specified, the value of this field must be a label-style identifier, with an optional prefix, + // e.g. "internal-vip" or "example.com/internal-vip". Unprefixed names are reserved for end-users. + // This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load + // balancer implementation is used, today this is typically done through the cloud provider integration, + // but should apply for any default implementation. If set, it is assumed that a load balancer + // implementation is watching for Services with a matching class. Any default load balancer + // implementation (e.g. cloud providers) should ignore Services that set this field. + // This field can only be set when creating or updating a Service to type 'LoadBalancer'. + // Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type. + // +featureGate=LoadBalancerClass + // +optional + LoadBalancerClass *string `json:"loadBalancerClass,omitempty"` + + // InternalTrafficPolicy specifies if the cluster internal traffic + // should be routed to all endpoints or node-local endpoints only. + // "Cluster" routes internal traffic to a Service to all endpoints. + // "Local" routes traffic to node-local endpoints only, traffic is + // dropped if no node-local endpoints are ready. + // The default value is "Cluster". + // +featureGate=ServiceInternalTrafficPolicy + // +optional + InternalTrafficPolicy *ServiceInternalTrafficPolicyType `json:"internalTrafficPolicy,omitempty"` +} + +// ServicePort contains information on service's port. +type ServicePort struct { + // The name of this port within the service. This must be a DNS_LABEL. + // All ports within a ServiceSpec must have unique names. When considering + // the endpoints for a Service, this must match the 'name' field in the + // EndpointPort. + // Optional if only one ServicePort is defined on this service. + // +optional + Name string `json:"name,omitempty"` + + // The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + // Default is TCP. + // +default="TCP" + // +optional + Protocol Protocol `json:"protocol,omitempty"` + + // The application protocol for this port. + // This field follows standard Kubernetes label syntax. + // Un-prefixed names are reserved for IANA standard service names (as per + // RFC-6335 and http://www.iana.org/assignments/service-names). + // Non-standard protocols should use prefixed names such as + // mycompany.com/my-custom-protocol. + // +optional + AppProtocol *string `json:"appProtocol,omitempty"` + + // The port that will be exposed by this service. + Port int32 `json:"port"` + + // Number or name of the port to access on the pods targeted by the service. + // Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + // If this is a string, it will be looked up as a named port in the + // target Pod's container ports. If this is not specified, the value + // of the 'port' field is used (an identity map). + // This field is ignored for services with clusterIP=None, and should be + // omitted or set equal to the 'port' field. + // More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + // +optional + TargetPort intstr.IntOrString `json:"targetPort"` + + // The port on each node on which this service is exposed when type is + // NodePort or LoadBalancer. Usually assigned by the system. If a value is + // specified, in-range, and not in use it will be used, otherwise the + // operation will fail. If not specified, a port will be allocated if this + // Service requires one. If this field is specified when creating a + // Service which does not need it, creation will fail. This field will be + // wiped when updating a Service to no longer need it (e.g. changing type + // from NodePort to ClusterIP). + // More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + // +optional + NodePort int32 `json:"nodePort,omitempty"` +} + +// +genclient +// +genclient:skipVerbs=deleteCollection +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Service is a named abstraction of software service (for example, mysql) consisting of local port +// (for example 3306) that the proxy listens on, and the selector that determines which pods +// will answer requests sent through the proxy. +type Service struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Spec defines the behavior of a service. + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec ServiceSpec `json:"spec"` + + // Most recently observed status of the service. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status ServiceStatus `json:"status"` +} + +const ( + // ClusterIPNone - do not assign a cluster IP + // no proxying required and no environment variables should be created for pods + ClusterIPNone = "None" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServiceList holds a list of services. +type ServiceList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // List of services + Items []Service `json:"items"` +} + +// +genclient +// +genclient:method=CreateToken,verb=create,subresource=token,input=k8s.io/api/authentication/v1.TokenRequest,result=k8s.io/api/authentication/v1.TokenRequest +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServiceAccount binds together: +// * a name, understood by users, and perhaps by peripheral systems, for an identity +// * a principal that can be authenticated and authorized +// * a set of secrets +type ServiceAccount struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Secrets is the list of secrets allowed to be used by pods running using this ServiceAccount. + // More info: https://kubernetes.io/docs/concepts/configuration/secret + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + Secrets []ObjectReference `json:"secrets,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + + // ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images + // in pods that reference this ServiceAccount. ImagePullSecrets are distinct from Secrets because Secrets + // can be mounted in the pod, but ImagePullSecrets are only accessed by the kubelet. + // More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + // +optional + ImagePullSecrets []LocalObjectReference `json:"imagePullSecrets,omitempty"` + + // AutomountServiceAccountToken indicates whether pods running as this service account should have an API token automatically mounted. + // Can be overridden at the pod level. + // +optional + AutomountServiceAccountToken *bool `json:"automountServiceAccountToken,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServiceAccountList is a list of ServiceAccount objects +type ServiceAccountList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // List of ServiceAccounts. + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + Items []ServiceAccount `json:"items"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Endpoints is a collection of endpoints that implement the actual service. Example: +// +// Name: "mysvc", +// Subsets: [ +// { +// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], +// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] +// }, +// { +// Addresses: [{"ip": "10.10.3.3"}], +// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}] +// }, +// ] +type Endpoints struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // The set of all endpoints is the union of all subsets. Addresses are placed into + // subsets according to the IPs they share. A single address with multiple ports, + // some of which are ready and some of which are not (because they come from + // different containers) will result in the address being displayed in different + // subsets for the different ports. No address will appear in both Addresses and + // NotReadyAddresses in the same subset. + // Sets of addresses and ports that comprise a service. + // +optional + Subsets []EndpointSubset `json:"subsets,omitempty"` +} + +// EndpointSubset is a group of addresses with a common set of ports. The +// expanded set of endpoints is the Cartesian product of Addresses x Ports. +// For example, given: +// +// { +// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], +// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] +// } +// +// The resulting set of endpoints can be viewed as: +// +// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ], +// b: [ 10.10.1.1:309, 10.10.2.2:309 ] +type EndpointSubset struct { + // IP addresses which offer the related ports that are marked as ready. These endpoints + // should be considered safe for load balancers and clients to utilize. + // +optional + Addresses []EndpointAddress `json:"addresses,omitempty"` + // IP addresses which offer the related ports but are not currently marked as ready + // because they have not yet finished starting, have recently failed a readiness check, + // or have recently failed a liveness check. + // +optional + NotReadyAddresses []EndpointAddress `json:"notReadyAddresses,omitempty"` + // Port numbers available on the related IP addresses. + // +optional + Ports []EndpointPort `json:"ports,omitempty"` +} + +// EndpointAddress is a tuple that describes single IP address. +// +structType=atomic +type EndpointAddress struct { + // The IP of this endpoint. + // May not be loopback (127.0.0.0/8), link-local (169.254.0.0/16), + // or link-local multicast ((224.0.0.0/24). + // IPv6 is also accepted but not fully supported on all platforms. Also, certain + // kubernetes components, like kube-proxy, are not IPv6 ready. + // TODO: This should allow hostname or IP, See #4447. + IP string `json:"ip"` + // The Hostname of this endpoint + // +optional + Hostname string `json:"hostname,omitempty"` + // Optional: Node hosting this endpoint. This can be used to determine endpoints local to a node. + // +optional + NodeName *string `json:"nodeName,omitempty"` + // Reference to object providing the endpoint. + // +optional + TargetRef *ObjectReference `json:"targetRef,omitempty"` +} + +// EndpointPort is a tuple that describes a single port. +// +structType=atomic +type EndpointPort struct { + // The name of this port. This must match the 'name' field in the + // corresponding ServicePort. + // Must be a DNS_LABEL. + // Optional only if one port is defined. + // +optional + Name string `json:"name,omitempty"` + + // The port number of the endpoint. + Port int32 `json:"port"` + + // The IP protocol for this port. + // Must be UDP, TCP, or SCTP. + // Default is TCP. + // +optional + Protocol Protocol `json:"protocol,omitempty"` + + // The application protocol for this port. + // This field follows standard Kubernetes label syntax. + // Un-prefixed names are reserved for IANA standard service names (as per + // RFC-6335 and http://www.iana.org/assignments/service-names). + // Non-standard protocols should use prefixed names such as + // mycompany.com/my-custom-protocol. + // +optional + AppProtocol *string `json:"appProtocol,omitempty"` +} + +// ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node. +// This API is deprecated since 1.22: https://git.k8s.io/enhancements/keps/sig-node/281-dynamic-kubelet-configuration +type ConfigMapNodeConfigSource struct { + // Namespace is the metadata.namespace of the referenced ConfigMap. + // This field is required in all cases. + Namespace string `json:"namespace"` + + // Name is the metadata.name of the referenced ConfigMap. + // This field is required in all cases. + Name string `json:"name"` + + // UID is the metadata.UID of the referenced ConfigMap. + // This field is forbidden in Node.Spec, and required in Node.Status. + // +optional + UID types.UID `json:"uid,omitempty"` + + // ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. + // This field is forbidden in Node.Spec, and required in Node.Status. + // +optional + ResourceVersion string `json:"resourceVersion,omitempty"` + + // KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure + // This field is required in all cases. + KubeletConfigKey string `json:"kubeletConfigKey"` +} + +// Describe a container image +type ContainerImage struct { + // Names by which this image is known. + // e.g. ["registry.k8s.io/hyperkube:v1.0.7", "dockerhub.io/google_containers/hyperkube:v1.0.7"] + // +optional + Names []string `json:"names"` + // The size of the image in bytes. + // +optional + SizeBytes int64 `json:"sizeBytes,omitempty"` +} + +// ResourceName is the name identifying various resources in a ResourceList. +type ResourceName string + +// Resource names must be not more than 63 characters, consisting of upper- or lower-case alphanumeric characters, +// with the -, _, and . characters allowed anywhere, except the first or last character. +// The default convention, matching that for annotations, is to use lower-case names, with dashes, rather than +// camel case, separating compound words. +// Fully-qualified resource typenames are constructed from a DNS-style subdomain, followed by a slash `/` and a name. +const ( + // CPU, in cores. (500m = .5 cores) + ResourceCPU ResourceName = "cpu" + // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceMemory ResourceName = "memory" + // Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024) + ResourceStorage ResourceName = "storage" + // Local ephemeral storage, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + // The resource name for ResourceEphemeralStorage is alpha and it can change across releases. + ResourceEphemeralStorage ResourceName = "ephemeral-storage" +) + +const ( + // Default namespace prefix. + ResourceDefaultNamespacePrefix = "kubernetes.io/" + // Name prefix for huge page resources (alpha). + ResourceHugePagesPrefix = "hugepages-" + // Name prefix for storage resource limits + ResourceAttachableVolumesPrefix = "attachable-volumes-" +) + +// ResourceList is a set of (resource name, quantity) pairs. +type ResourceList map[ResourceName]resource.Quantity + +// PodLogOptions is the query options for a Pod's logs REST call. +type PodLogOptions struct { + metav1.TypeMeta `json:",inline"` + + // The container for which to stream logs. Defaults to only container if there is one container in the pod. + // +optional + Container string `json:"container,omitempty"` + // Follow the log stream of the pod. Defaults to false. + // +optional + Follow bool `json:"follow,omitempty"` + // Return previous terminated container logs. Defaults to false. + // +optional + Previous bool `json:"previous,omitempty"` + // A relative time in seconds before the current time from which to show logs. If this value + // precedes the time a pod was started, only logs since the pod start will be returned. + // If this value is in the future, no logs will be returned. + // Only one of sinceSeconds or sinceTime may be specified. + // +optional + SinceSeconds *int64 `json:"sinceSeconds,omitempty"` + // An RFC3339 timestamp from which to show logs. If this value + // precedes the time a pod was started, only logs since the pod start will be returned. + // If this value is in the future, no logs will be returned. + // Only one of sinceSeconds or sinceTime may be specified. + // +optional + SinceTime *metav1.Time `json:"sinceTime,omitempty"` + // If true, add an RFC3339 or RFC3339Nano timestamp at the beginning of every line + // of log output. Defaults to false. + // +optional + Timestamps bool `json:"timestamps,omitempty"` + // If set, the number of lines from the end of the logs to show. If not specified, + // logs are shown from the creation of the container or sinceSeconds or sinceTime + // +optional + TailLines *int64 `json:"tailLines,omitempty"` + // If set, the number of bytes to read from the server before terminating the + // log output. This may not display a complete final line of logging, and may return + // slightly more or slightly less than the specified limit. + // +optional + LimitBytes *int64 `json:"limitBytes,omitempty"` + + // insecureSkipTLSVerifyBackend indicates that the apiserver should not confirm the validity of the + // serving certificate of the backend it is connecting to. This will make the HTTPS connection between the apiserver + // and the backend insecure. This means the apiserver cannot verify the log data it is receiving came from the real + // kubelet. If the kubelet is configured to verify the apiserver's TLS credentials, it does not mean the + // connection to the real kubelet is vulnerable to a man in the middle attack (e.g. an attacker could not intercept + // the actual log data coming from the real kubelet). + // +optional + InsecureSkipTLSVerifyBackend bool `json:"insecureSkipTLSVerifyBackend,omitempty"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodAttachOptions is the query options to a Pod's remote attach call. +// --- +// TODO: merge w/ PodExecOptions below for stdin, stdout, etc +// and also when we cut V2, we should export a "StreamOptions" or somesuch that contains Stdin, Stdout, Stder and TTY +type PodAttachOptions struct { + metav1.TypeMeta `json:",inline"` + + // Stdin if true, redirects the standard input stream of the pod for this call. + // Defaults to false. + // +optional + Stdin bool `json:"stdin,omitempty"` + + // Stdout if true indicates that stdout is to be redirected for the attach call. + // Defaults to true. + // +optional + Stdout bool `json:"stdout,omitempty"` + + // Stderr if true indicates that stderr is to be redirected for the attach call. + // Defaults to true. + // +optional + Stderr bool `json:"stderr,omitempty"` + + // TTY if true indicates that a tty will be allocated for the attach call. + // This is passed through the container runtime so the tty + // is allocated on the worker node by the container runtime. + // Defaults to false. + // +optional + TTY bool `json:"tty,omitempty"` + + // The container in which to execute the command. + // Defaults to only container if there is only one container in the pod. + // +optional + Container string `json:"container,omitempty"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodExecOptions is the query options to a Pod's remote exec call. +// --- +// TODO: This is largely identical to PodAttachOptions above, make sure they stay in sync and see about merging +// and also when we cut V2, we should export a "StreamOptions" or somesuch that contains Stdin, Stdout, Stder and TTY +type PodExecOptions struct { + metav1.TypeMeta `json:",inline"` + + // Redirect the standard input stream of the pod for this call. + // Defaults to false. + // +optional + Stdin bool `json:"stdin,omitempty"` + + // Redirect the standard output stream of the pod for this call. + // Defaults to true. + // +optional + Stdout bool `json:"stdout,omitempty"` + + // Redirect the standard error stream of the pod for this call. + // Defaults to true. + // +optional + Stderr bool `json:"stderr,omitempty"` + + // TTY if true indicates that a tty will be allocated for the exec call. + // Defaults to false. + // +optional + TTY bool `json:"tty,omitempty"` + + // Container in which to execute the command. + // Defaults to only container if there is only one container in the pod. + // +optional + Container string `json:"container,omitempty"` + + // Command is the remote command to execute. argv array. Not executed within a shell. + Command []string `json:"command"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodPortForwardOptions is the query options to a Pod's port forward call +// when using WebSockets. +// The `port` query parameter must specify the port or +// ports (comma separated) to forward over. +// Port forwarding over SPDY does not use these options. It requires the port +// to be passed in the `port` header as part of request. +type PodPortForwardOptions struct { + metav1.TypeMeta `json:",inline"` + + // List of ports to forward + // Required when using WebSockets + // +optional + Ports []int32 `json:"ports,omitempty"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PodProxyOptions is the query options to a Pod's proxy call. +type PodProxyOptions struct { + metav1.TypeMeta `json:",inline"` + + // Path is the URL path to use for the current proxy request to pod. + // +optional + Path string `json:"path,omitempty"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// NodeProxyOptions is the query options to a Node's proxy call. +type NodeProxyOptions struct { + metav1.TypeMeta `json:",inline"` + + // Path is the URL path to use for the current proxy request to node. + // +optional + Path string `json:"path,omitempty"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServiceProxyOptions is the query options to a Service's proxy call. +type ServiceProxyOptions struct { + metav1.TypeMeta `json:",inline"` + + // Path is the part of URLs that include service endpoints, suffixes, + // and parameters to use for the current proxy request to service. + // For example, the whole request URL is + // http://localhost/api/v1/namespaces/kube-system/services/elasticsearch-logging/_search?q=user:kimchy. + // Path is _search?q=user:kimchy. + // +optional + Path string `json:"path,omitempty"` +} + +// ObjectReference contains enough information to let you inspect or modify the referred object. +// --- +// New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. +// 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. +// 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular +// restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". +// Those cannot be well described when embedded. +// 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. +// 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity +// during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple +// and the version of the actual struct is irrelevant. +// 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type +// will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. +// +// Instead of using this type, create a locally provided and used type that is well-focused on your reference. +// For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 . +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +structType=atomic +type ObjectReference struct { + // Kind of the referent. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + Kind string `json:"kind,omitempty"` + // Namespace of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + // +optional + Namespace string `json:"namespace,omitempty"` + // Name of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + // +optional + Name string `json:"name,omitempty"` + // UID of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + // +optional + UID types.UID `json:"uid,omitempty"` + // API version of the referent. + // +optional + APIVersion string `json:"apiVersion,omitempty"` + // Specific resourceVersion to which this reference is made, if any. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + // +optional + ResourceVersion string `json:"resourceVersion,omitempty"` + + // If referring to a piece of an object instead of an entire object, this string + // should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + // For example, if the object reference is to a container within a pod, this would take on a value like: + // "spec.containers{name}" (where "name" refers to the name of the container that triggered + // the event) or if no container name is specified "spec.containers[2]" (container with + // index 2 in this pod). This syntax is chosen only to have some well-defined way of + // referencing a part of an object. + // TODO: this design is not final and this field is subject to change in the future. + // +optional + FieldPath string `json:"fieldPath,omitempty"` +} + +// LocalObjectReference contains enough information to let you locate the +// referenced object inside the same namespace. +// +structType=atomic +type LocalObjectReference struct { + // Name of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + // TODO: Add other useful fields. apiVersion, kind, uid? + // +optional + Name string `json:"name,omitempty"` +} + +// TypedLocalObjectReference contains enough information to let you locate the +// typed referenced object inside the same namespace. +// +structType=atomic +type TypedLocalObjectReference struct { + // APIGroup is the group for the resource being referenced. + // If APIGroup is not specified, the specified Kind must be in the core API group. + // For any other third-party types, APIGroup is required. + // +optional + APIGroup *string `json:"apiGroup"` + // Kind is the type of resource being referenced + Kind string `json:"kind"` + // Name is the name of resource being referenced + Name string `json:"name"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SerializedReference is a reference to serialized object. +type SerializedReference struct { + metav1.TypeMeta `json:",inline"` + // The reference to an object in the system. + // +optional + Reference ObjectReference `json:"reference"` +} + +// EventSource contains information for an event. +type EventSource struct { + // Component from which the event is generated. + // +optional + Component string `json:"component,omitempty"` + // Node name on which the event is generated. + // +optional + Host string `json:"host,omitempty"` +} + +// Valid values for event types (new types could be added in future) +const ( + // Information only and will not cause any problems + EventTypeNormal string = "Normal" + // These events are to warn that something might go wrong + EventTypeWarning string = "Warning" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Event is a report of an event somewhere in the cluster. Events +// have a limited retention time and triggers and messages may evolve +// with time. Event consumers should not rely on the timing of an event +// with a given Reason reflecting a consistent underlying trigger, or the +// continued existence of events with that Reason. Events should be +// treated as informative, best-effort, supplemental data. +type Event struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + metav1.ObjectMeta `json:"metadata"` + + // The object that this event is about. + InvolvedObject ObjectReference `json:"involvedObject"` + + // This should be a short, machine understandable string that gives the reason + // for the transition into the object's current status. + // TODO: provide exact specification for format. + // +optional + Reason string `json:"reason,omitempty"` + + // A human-readable description of the status of this operation. + // TODO: decide on maximum length. + // +optional + Message string `json:"message,omitempty"` + + // The component reporting this event. Should be a short machine understandable string. + // +optional + Source EventSource `json:"source"` + + // The time at which the event was first recorded. (Time of server receipt is in TypeMeta.) + // +optional + FirstTimestamp metav1.Time `json:"firstTimestamp"` + + // The time at which the most recent occurrence of this event was recorded. + // +optional + LastTimestamp metav1.Time `json:"lastTimestamp"` + + // The number of times this event has occurred. + // +optional + Count int32 `json:"count,omitempty"` + + // Type of this event (Normal, Warning), new types could be added in the future + // +optional + Type string `json:"type,omitempty"` + + // Time when this Event was first observed. + // +optional + EventTime metav1.MicroTime `json:"eventTime"` + + // Data about the Event series this event represents or nil if it's a singleton Event. + // +optional + Series *EventSeries `json:"series,omitempty"` + + // What action was taken/failed regarding to the Regarding object. + // +optional + Action string `json:"action,omitempty"` + + // Optional secondary object for more complex actions. + // +optional + Related *ObjectReference `json:"related,omitempty"` + + // Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`. + // +optional + ReportingController string `json:"reportingComponent"` + + // ID of the controller instance, e.g. `kubelet-xyzf`. + // +optional + ReportingInstance string `json:"reportingInstance"` +} + +// EventSeries contain information on series of events, i.e. thing that was/is happening +// continuously for some time. +type EventSeries struct { + // Number of occurrences in this series up to the last heartbeat time + Count int32 `json:"count,omitempty"` + // Time of the last occurrence observed + LastObservedTime metav1.MicroTime `json:"lastObservedTime"` + + // +k8s:deprecated=state,protobuf=3 +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// EventList is a list of events. +type EventList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // List of events + Items []Event `json:"items"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LimitType is a type of object that is limited +type LimitType string + +const ( + // Limit that applies to all pods in a namespace + LimitTypePod LimitType = "Pod" + // Limit that applies to all containers in a namespace + LimitTypeContainer LimitType = "Container" + // Limit that applies to all persistent volume claims in a namespace + LimitTypePersistentVolumeClaim LimitType = "PersistentVolumeClaim" +) + +// LimitRangeItem defines a min/max usage limit for any resource that matches on kind. +type LimitRangeItem struct { + // Type of resource that this limit applies to. + Type LimitType `json:"type"` + // Max usage constraints on this kind by resource name. + // +optional + Max ResourceList `json:"max,omitempty"` + // Min usage constraints on this kind by resource name. + // +optional + Min ResourceList `json:"min,omitempty"` + // Default resource requirement limit value by resource name if resource limit is omitted. + // +optional + Default ResourceList `json:"default,omitempty"` + // DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + // +optional + DefaultRequest ResourceList `json:"defaultRequest,omitempty"` + // MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + // +optional + MaxLimitRequestRatio ResourceList `json:"maxLimitRequestRatio,omitempty"` +} + +// LimitRangeSpec defines a min/max usage limit for resources that match on kind. +type LimitRangeSpec struct { + // Limits is the list of LimitRangeItem objects that are enforced. + Limits []LimitRangeItem `json:"limits"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LimitRange sets resource usage limits for each kind of resource in a Namespace. +type LimitRange struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Spec defines the limits enforced. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec LimitRangeSpec `json:"spec"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LimitRangeList is a list of LimitRange items. +type LimitRangeList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // Items is a list of LimitRange objects. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + Items []LimitRange `json:"items"` +} + +// The following identify resource constants for Kubernetes object types +const ( + // Pods, number + ResourcePods ResourceName = "pods" + // Services, number + ResourceServices ResourceName = "services" + // ReplicationControllers, number + ResourceReplicationControllers ResourceName = "replicationcontrollers" + // ResourceQuotas, number + ResourceQuotas ResourceName = "resourcequotas" + // ResourceSecrets, number + ResourceSecrets ResourceName = "secrets" + // ResourceConfigMaps, number + ResourceConfigMaps ResourceName = "configmaps" + // ResourcePersistentVolumeClaims, number + ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims" + // ResourceServicesNodePorts, number + ResourceServicesNodePorts ResourceName = "services.nodeports" + // ResourceServicesLoadBalancers, number + ResourceServicesLoadBalancers ResourceName = "services.loadbalancers" + // CPU request, in cores. (500m = .5 cores) + ResourceRequestsCPU ResourceName = "requests.cpu" + // Memory request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceRequestsMemory ResourceName = "requests.memory" + // Storage request, in bytes + ResourceRequestsStorage ResourceName = "requests.storage" + // Local ephemeral storage request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceRequestsEphemeralStorage ResourceName = "requests.ephemeral-storage" + // CPU limit, in cores. (500m = .5 cores) + ResourceLimitsCPU ResourceName = "limits.cpu" + // Memory limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceLimitsMemory ResourceName = "limits.memory" + // Local ephemeral storage limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceLimitsEphemeralStorage ResourceName = "limits.ephemeral-storage" +) + +// The following identify resource prefix for Kubernetes object types +const ( + // HugePages request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + // As burst is not supported for HugePages, we would only quota its request, and ignore the limit. + ResourceRequestsHugePagesPrefix = "requests.hugepages-" + // Default resource requests prefix + DefaultResourceRequestsPrefix = "requests." +) + +// A ResourceQuotaScope defines a filter that must match each object tracked by a quota +type ResourceQuotaScope string + +const ( + // Match all pod objects where spec.activeDeadlineSeconds >=0 + ResourceQuotaScopeTerminating ResourceQuotaScope = "Terminating" + // Match all pod objects where spec.activeDeadlineSeconds is nil + ResourceQuotaScopeNotTerminating ResourceQuotaScope = "NotTerminating" + // Match all pod objects that have best effort quality of service + ResourceQuotaScopeBestEffort ResourceQuotaScope = "BestEffort" + // Match all pod objects that do not have best effort quality of service + ResourceQuotaScopeNotBestEffort ResourceQuotaScope = "NotBestEffort" + // Match all pod objects that have priority class mentioned + ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass" + // Match all pod objects that have cross-namespace pod (anti)affinity mentioned. + // This is a beta feature enabled by the PodAffinityNamespaceSelector feature flag. + ResourceQuotaScopeCrossNamespacePodAffinity ResourceQuotaScope = "CrossNamespacePodAffinity" +) + +// ResourceQuotaSpec defines the desired hard limits to enforce for Quota. +type ResourceQuotaSpec struct { + // hard is the set of desired hard limits for each named resource. + // More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/ + // +optional + Hard ResourceList `json:"hard,omitempty"` + // A collection of filters that must match each object tracked by a quota. + // If not specified, the quota matches all objects. + // +optional + Scopes []ResourceQuotaScope `json:"scopes,omitempty"` + // scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota + // but expressed using ScopeSelectorOperator in combination with possible values. + // For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + // +optional + ScopeSelector *ScopeSelector `json:"scopeSelector,omitempty"` +} + +// A scope selector represents the AND of the selectors represented +// by the scoped-resource selector requirements. +// +structType=atomic +type ScopeSelector struct { + // A list of scope selector requirements by scope of the resources. + // +optional + MatchExpressions []ScopedResourceSelectorRequirement `json:"matchExpressions,omitempty"` +} + +// A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator +// that relates the scope name and values. +type ScopedResourceSelectorRequirement struct { + // The name of the scope that the selector applies to. + ScopeName ResourceQuotaScope `json:"scopeName"` + // Represents a scope's relationship to a set of values. + // Valid operators are In, NotIn, Exists, DoesNotExist. + Operator ScopeSelectorOperator `json:"operator"` + // An array of string values. If the operator is In or NotIn, + // the values array must be non-empty. If the operator is Exists or DoesNotExist, + // the values array must be empty. + // This array is replaced during a strategic merge patch. + // +optional + Values []string `json:"values,omitempty"` +} + +// A scope selector operator is the set of operators that can be used in +// a scope selector requirement. +type ScopeSelectorOperator string + +const ( + ScopeSelectorOpIn ScopeSelectorOperator = "In" + ScopeSelectorOpNotIn ScopeSelectorOperator = "NotIn" + ScopeSelectorOpExists ScopeSelectorOperator = "Exists" + ScopeSelectorOpDoesNotExist ScopeSelectorOperator = "DoesNotExist" +) + +// ResourceQuotaStatus defines the enforced hard limits and observed use. +type ResourceQuotaStatus struct { + // Hard is the set of enforced hard limits for each named resource. + // More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/ + // +optional + Hard ResourceList `json:"hard,omitempty"` + // Used is the current observed total usage of the resource in the namespace. + // +optional + Used ResourceList `json:"used,omitempty"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ResourceQuota sets aggregate quota restrictions enforced per namespace +type ResourceQuota struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Spec defines the desired quota. + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec ResourceQuotaSpec `json:"spec"` + + // Status defines the actual enforced quota and its current usage. + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status ResourceQuotaStatus `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ResourceQuotaList is a list of ResourceQuota items. +type ResourceQuotaList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // Items is a list of ResourceQuota objects. + // More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/ + Items []ResourceQuota `json:"items"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Secret holds secret data of a certain type. The total bytes of the values in +// the Data field must be less than MaxSecretSize bytes. +type Secret struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Immutable, if set to true, ensures that data stored in the Secret cannot + // be updated (only object metadata can be modified). + // If not set to true, the field can be modified at any time. + // Defaulted to nil. + // +optional + Immutable *bool `json:"immutable,omitempty"` + + // Data contains the secret data. Each key must consist of alphanumeric + // characters, '-', '_' or '.'. The serialized form of the secret data is a + // base64 encoded string, representing the arbitrary (possibly non-string) + // data value here. Described in https://tools.ietf.org/html/rfc4648#section-4 + // +optional + Data map[string][]byte `json:"data,omitempty"` + + // stringData allows specifying non-binary secret data in string form. + // It is provided as a write-only input field for convenience. + // All keys and values are merged into the data field on write, overwriting any existing values. + // The stringData field is never output when reading from the API. + // +k8s:conversion-gen=false + // +optional + StringData map[string]string `json:"stringData,omitempty"` + + // Used to facilitate programmatic handling of secret data. + // +optional + Type SecretType `json:"type,omitempty"` +} + +type SecretType string + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// SecretList is a list of Secret. +type SecretList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // Items is a list of secret objects. + // More info: https://kubernetes.io/docs/concepts/configuration/secret + Items []Secret `json:"items"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ConfigMap holds configuration data for pods to consume. +type ConfigMap struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Immutable, if set to true, ensures that data stored in the ConfigMap cannot + // be updated (only object metadata can be modified). + // If not set to true, the field can be modified at any time. + // Defaulted to nil. + // +optional + Immutable *bool `json:"immutable,omitempty"` + + // Data contains the configuration data. + // Each key must consist of alphanumeric characters, '-', '_' or '.'. + // Values with non-UTF-8 byte sequences must use the BinaryData field. + // The keys stored in Data must not overlap with the keys in + // the BinaryData field, this is enforced during validation process. + // +optional + Data map[string]string `json:"data,omitempty"` + + // BinaryData contains the binary data. + // Each key must consist of alphanumeric characters, '-', '_' or '.'. + // BinaryData can contain byte sequences that are not in the UTF-8 range. + // The keys stored in BinaryData must not overlap with the ones in + // the Data field, this is enforced during validation process. + // Using this field will require 1.10+ apiserver and + // kubelet. + // +optional + BinaryData map[string][]byte `json:"binaryData,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ConfigMapList is a resource containing a list of ConfigMap objects. +type ConfigMapList struct { + metav1.TypeMeta `json:",inline"` + + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata"` + + // Items is the list of ConfigMaps. + Items []ConfigMap `json:"items"` +} + +// Type and constants for component health validation. +type ComponentConditionType string + +// These are the valid conditions for the component. +const ( + ComponentHealthy ComponentConditionType = "Healthy" +) + +// Information about the condition of a component. +type ComponentCondition struct { + // Type of condition for a component. + // Valid value: "Healthy" + Type ComponentConditionType `json:"type"` + // Status of the condition for a component. + // Valid values for "Healthy": "True", "False", or "Unknown". + Status ConditionStatus `json:"status"` + // Message about the condition for a component. + // For example, information about a health check. + // +optional + Message string `json:"message,omitempty"` + // Condition error code for a component. + // For example, a health check error code. + // +optional + Error string `json:"error,omitempty"` +} + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ComponentStatus (and ComponentStatusList) holds the cluster validation info. +// Deprecated: This API is deprecated in v1.19+ +type ComponentStatus struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata"` + + // List of component conditions observed + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []ComponentCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Status of all the conditions for the component as a list of ComponentStatus objects. +// Deprecated: This API is deprecated in v1.19+ +type ComponentStatusList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata"` + + // List of ComponentStatus objects. + Items []ComponentStatus `json:"items"` +} + +// DownwardAPIVolumeSource represents a volume containing downward API info. +// Downward API volumes support ownership management and SELinux relabeling. +type DownwardAPIVolumeSource struct { + // Items is a list of downward API volume file + // +optional + Items []DownwardAPIVolumeFile `json:"items,omitempty"` + // Optional: mode bits to use on created files by default. Must be a + // Optional: mode bits used to set permissions on created files by default. + // Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + // YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + // Defaults to 0644. + // Directories within the path are not affected by this setting. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + DefaultMode *int32 `json:"defaultMode,omitempty"` +} + +const ( + DownwardAPIVolumeSourceDefaultMode int32 = 0644 +) + +// DownwardAPIVolumeFile represents information to create the file containing the pod field +type DownwardAPIVolumeFile struct { + // Required: Path is the relative path name of the file to be created. Must not be absolute or contain the '..' path. Must be utf-8 encoded. The first item of the relative path must not start with '..' + Path string `json:"path"` + // Required: Selects a field of the pod: only annotations, labels, name and namespace are supported. + // +optional + FieldRef *ObjectFieldSelector `json:"fieldRef,omitempty"` + // Selects a resource of the container: only resources limits and requests + // (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + // +optional + ResourceFieldRef *ResourceFieldSelector `json:"resourceFieldRef,omitempty"` + // Optional: mode bits used to set permissions on this file, must be an octal value + // between 0000 and 0777 or a decimal value between 0 and 511. + // YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + // If not specified, the volume defaultMode will be used. + // This might be in conflict with other options that affect the file + // mode, like fsGroup, and the result can be other mode bits set. + // +optional + Mode *int32 `json:"mode,omitempty"` +} + +// Represents downward API info for projecting into a projected volume. +// Note that this is identical to a downwardAPI volume source without the default +// mode. +type DownwardAPIProjection struct { + // Items is a list of DownwardAPIVolume file + // +optional + Items []DownwardAPIVolumeFile `json:"items,omitempty"` +} + +// SecurityContext holds security configuration that will be applied to a container. +// Some fields are present in both SecurityContext and PodSecurityContext. When both +// are set, the values in SecurityContext take precedence. +type SecurityContext struct { + // The capabilities to add/drop when running containers. + // Defaults to the default set of capabilities granted by the container runtime. + // +optional + Capabilities *Capabilities `json:"capabilities,omitempty"` + // Run container in privileged mode. + // Processes in privileged containers are essentially equivalent to root on the host. + // Defaults to false. + // +optional + Privileged *bool `json:"privileged,omitempty"` + // The SELinux context to be applied to the container. + // If unspecified, the container runtime will allocate a random SELinux context for each + // container. May also be set in PodSecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + SELinuxOptions *SELinuxOptions `json:"seLinuxOptions,omitempty"` + // The UID to run the entrypoint of the container process. + // Defaults to user specified in image metadata if unspecified. + // May also be set in PodSecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + RunAsUser *int64 `json:"runAsUser,omitempty"` + // The GID to run the entrypoint of the container process. + // Uses runtime default if unset. + // May also be set in PodSecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + RunAsGroup *int64 `json:"runAsGroup,omitempty"` + // Indicates that the container must run as a non-root user. + // If true, the Kubelet will validate the image at runtime to ensure that it + // does not run as UID 0 (root) and fail to start the container if it does. + // If unset or false, no such validation will be performed. + // May also be set in PodSecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"` + // Whether this container has a read-only root filesystem. + // Default is false. + // +optional + ReadOnlyRootFilesystem *bool `json:"readOnlyRootFilesystem,omitempty"` + // AllowPrivilegeEscalation controls whether a process can gain more + // privileges than its parent process. This bool directly controls if + // the no_new_privs flag will be set on the container process. + // AllowPrivilegeEscalation is true always when the container is: + // 1) run as Privileged + // 2) has CAP_SYS_ADMIN + // +optional + AllowPrivilegeEscalation *bool `json:"allowPrivilegeEscalation,omitempty"` + // procMount denotes the type of proc mount to use for the containers. + // The default is DefaultProcMount which uses the container runtime defaults for + // readonly paths and masked paths. + // This requires the ProcMountType feature flag to be enabled. + // +optional + ProcMount *ProcMountType `json:"procMount,omitempty"` + // The seccomp options to use by this container. If seccomp options are + // provided at both the pod & container level, the container options + // override the pod options. + // +optional + SeccompProfile *SeccompProfile `json:"seccompProfile,omitempty"` +} + +type ProcMountType string + +const ( + // DefaultProcMount uses the container runtime defaults for readonly and masked + // paths for /proc. Most container runtimes mask certain paths in /proc to avoid + // accidental security exposure of special devices or information. + DefaultProcMount ProcMountType = "Default" + + // UnmaskedProcMount bypasses the default masking behavior of the container + // runtime and ensures the newly created /proc the container stays in tact with + // no modifications. + UnmaskedProcMount ProcMountType = "Unmasked" +) + +// SELinuxOptions are the labels to be applied to the container +type SELinuxOptions struct { + // User is a SELinux user label that applies to the container. + // +optional + User string `json:"user,omitempty"` + // Role is a SELinux role label that applies to the container. + // +optional + Role string `json:"role,omitempty"` + // Type is a SELinux type label that applies to the container. + // +optional + Type string `json:"type,omitempty"` + // FileType is a SELinux file type label that applies to the container. + // +optional + FileType string `json:"filetype,omitempty"` + // Level is SELinux level label that applies to the container. + // +optional + Level string `json:"level,omitempty"` +} + +const ( + // DefaultSchedulerName defines the name of default scheduler. + DefaultSchedulerName = "default-scheduler" + + // RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule + // corresponding to every RequiredDuringScheduling affinity rule. + // When the --hard-pod-affinity-weight scheduler flag is not specified, + // DefaultHardPodAffinityWeight defines the weight of the implicit PreferredDuringScheduling affinity rule. + DefaultHardPodAffinitySymmetricWeight int32 = 1 +) + +// Sysctl defines a kernel parameter to be set +type Sysctl struct { + // Name of a property to set + Name string `json:"name"` + // Value of a property to set + Value string `json:"value"` +} + +// NodeResources is an object for conveying resource information about a node. +// see https://kubernetes.io/docs/concepts/architecture/nodes/#capacity for more details. +type NodeResources struct { + // Capacity represents the available resources of a node + Capacity ResourceList +} + +const ( + // Enable stdin for remote command execution + ExecStdinParam = "input" + // Enable stdout for remote command execution + ExecStdoutParam = "output" + // Enable stderr for remote command execution + ExecStderrParam = "error" + // Enable TTY for remote command execution + ExecTTYParam = "tty" + // Command to run for remote command execution + ExecCommandParam = "command" + + // Name of header that specifies stream type + StreamType = "streamType" + // Value for streamType header for stdin stream + StreamTypeStdin = "stdin" + // Value for streamType header for stdout stream + StreamTypeStdout = "stdout" + // Value for streamType header for stderr stream + StreamTypeStderr = "stderr" + // Value for streamType header for data stream + StreamTypeData = "data" + // Value for streamType header for error stream + StreamTypeError = "error" + // Value for streamType header for terminal resize stream + StreamTypeResize = "resize" + + // Name of header that specifies the port being forwarded + PortHeader = "port" + // Name of header that specifies a request ID used to associate the error + // and data streams for a single forwarded connection + PortForwardRequestIDHeader = "requestID" +) + +// PortStatus represents the error condition of a service port + +type PortStatus struct { + // Port is the port number of the service port of which status is recorded here + Port int32 `json:"port"` + // Protocol is the protocol of the service port of which status is recorded here + // The supported values are: "TCP", "UDP", "SCTP" + Protocol Protocol `json:"protocol"` + // Error is to record the problem with the service port + // The format of the error shall comply with the following rules: + // - built-in error values shall be specified in this file and those shall use + // CamelCase names + // - cloud provider specific error values must have names that comply with the + // format foo.example.com/CamelCase. + // --- + // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + // +optional + // +kubebuilder:validation:Required + // +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$` + // +kubebuilder:validation:MaxLength=316 + Error *string `json:"error,omitempty"` +} + +// The following has been copied from https://github.com/kubernetes/client-go/blob/master/tools/clientcmd/api/v1/types.go +// It holds the struct information for a kubeconfig that let's us unmarshal a given kubeconfig so that we can deploy workloads +// to the cluster with podman generate kube. + +// Config holds the information needed to build connect to remote kubernetes clusters as a given user +type Config struct { + // Legacy field from pkg/api/types.go TypeMeta. + // TODO(jlowdermilk): remove this after eliminating downstream dependencies. + // +k8s:conversion-gen=false + // +optional + Kind string `json:"kind,omitempty"` + // Legacy field from pkg/api/types.go TypeMeta. + // TODO(jlowdermilk): remove this after eliminating downstream dependencies. + // +k8s:conversion-gen=false + // +optional + APIVersion string `json:"apiVersion,omitempty"` + // Preferences holds general information to be use for cli interactions + Preferences Preferences `json:"preferences"` + // Clusters is a map of referenceable names to cluster configs + Clusters []NamedCluster `json:"clusters"` + // AuthInfos is a map of referenceable names to user configs + AuthInfos []NamedAuthInfo `json:"users"` + // Contexts is a map of referenceable names to context configs + Contexts []NamedContext `json:"contexts"` + // CurrentContext is the name of the context that you would like to use by default + CurrentContext string `json:"current-context"` + // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields + // +optional + Extensions []NamedExtension `json:"extensions,omitempty"` +} + +type Preferences struct { + // +optional + Colors bool `json:"colors,omitempty"` + // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields + // +optional + Extensions []NamedExtension `json:"extensions,omitempty"` +} + +// Cluster contains information about how to communicate with a kubernetes cluster +type Cluster struct { + // Server is the address of the kubernetes cluster (https://hostname:port). + Server string `json:"server"` + // TLSServerName is used to check server certificate. If TLSServerName is empty, the hostname used to contact the server is used. + // +optional + TLSServerName string `json:"tls-server-name,omitempty"` + // InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure. + // +optional + InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"` + // CertificateAuthority is the path to a cert file for the certificate authority. + // +optional + CertificateAuthority string `json:"certificate-authority,omitempty"` + // CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority + // +optional + CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"` + // ProxyURL is the URL to the proxy to be used for all requests made by this + // client. URLs with "http", "https", and "socks5" schemes are supported. If + // this configuration is not provided or the empty string, the client + // attempts to construct a proxy configuration from http_proxy and + // https_proxy environment variables. If these environment variables are not + // set, the client does not attempt to proxy requests. + // + // socks5 proxying does not currently support spdy streaming endpoints (exec, + // attach, port forward). + // +optional + ProxyURL string `json:"proxy-url,omitempty"` + // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields + // +optional + Extensions []NamedExtension `json:"extensions,omitempty"` +} + +// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are. +type AuthInfo struct { + // ClientCertificate is the path to a client cert file for TLS. + // +optional + ClientCertificate string `json:"client-certificate,omitempty"` + // ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate + // +optional + ClientCertificateData []byte `json:"client-certificate-data,omitempty"` + // ClientKey is the path to a client key file for TLS. + // +optional + ClientKey string `json:"client-key,omitempty"` + // ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey + // +optional + ClientKeyData []byte `json:"client-key-data,omitempty" datapolicy:"security-key"` + // Token is the bearer token for authentication to the kubernetes cluster. + // +optional + Token string `json:"token,omitempty" datapolicy:"token"` + // TokenFile is a pointer to a file that contains a bearer token (as described above). If both Token and TokenFile are present, Token takes precedence. + // +optional + TokenFile string `json:"tokenFile,omitempty"` + // Impersonate is the username to impersonate. The name matches the flag. + // +optional + Impersonate string `json:"as,omitempty"` + // ImpersonateUID is the uid to impersonate. + // +optional + ImpersonateUID string `json:"as-uid,omitempty"` + // ImpersonateGroups is the groups to impersonate. + // +optional + ImpersonateGroups []string `json:"as-groups,omitempty"` + // ImpersonateUserExtra contains additional information for impersonated user. + // +optional + ImpersonateUserExtra map[string][]string `json:"as-user-extra,omitempty"` + // Username is the username for basic authentication to the kubernetes cluster. + // +optional + Username string `json:"username,omitempty"` + // Password is the password for basic authentication to the kubernetes cluster. + // +optional + Password string `json:"password,omitempty" datapolicy:"password"` + // AuthProvider specifies a custom authentication plugin for the kubernetes cluster. + // +optional + AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"` + // Exec specifies a custom exec-based authentication plugin for the kubernetes cluster. + // +optional + Exec *ExecConfig `json:"exec,omitempty"` + // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields + // +optional + Extensions []NamedExtension `json:"extensions,omitempty"` +} + +// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with) +type Context struct { + // Cluster is the name of the cluster for this context + Cluster string `json:"cluster"` + // AuthInfo is the name of the authInfo for this context + AuthInfo string `json:"user"` + // Namespace is the default namespace to use on unspecified requests + // +optional + Namespace string `json:"namespace,omitempty"` + // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields + // +optional + Extensions []NamedExtension `json:"extensions,omitempty"` +} + +// NamedCluster relates nicknames to cluster information +type NamedCluster struct { + // Name is the nickname for this Cluster + Name string `json:"name"` + // Cluster holds the cluster information + Cluster Cluster `json:"cluster"` +} + +// NamedContext relates nicknames to context information +type NamedContext struct { + // Name is the nickname for this Context + Name string `json:"name"` + // Context holds the context information + Context Context `json:"context"` +} + +// NamedAuthInfo relates nicknames to auth information +type NamedAuthInfo struct { + // Name is the nickname for this AuthInfo + Name string `json:"name"` + // AuthInfo holds the auth information + AuthInfo AuthInfo `json:"user"` +} + +// NamedExtension relates nicknames to extension information +type NamedExtension struct { + // Name is the nickname for this Extension + Name string `json:"name"` + // Extension holds the extension information + Extension any `json:"extension"` +} + +// AuthProviderConfig holds the configuration for a specified auth provider. +type AuthProviderConfig struct { + Name string `json:"name"` + Config map[string]string `json:"config"` +} + +// ExecConfig specifies a command to provide client credentials. The command is exec'd +// and outputs structured stdout holding credentials. +// +// See the client.authentication.k8s.io API group for specifications of the exact input +// and output format +type ExecConfig struct { + // Command to execute. + Command string `json:"command"` + // Arguments to pass to the command when executing it. + // +optional + Args []string `json:"args"` + // Env defines additional environment variables to expose to the process. These + // are unioned with the host's environment, as well as variables client-go uses + // to pass argument to the plugin. + // +optional + Env []ExecEnvVar `json:"env"` + + // Preferred input version of the ExecInfo. The returned ExecCredentials MUST use + // the same encoding version as the input. + APIVersion string `json:"apiVersion,omitempty"` + + // This text is shown to the user when the executable doesn't seem to be + // present. For example, `brew install foo-cli` might be a good InstallHint for + // foo-cli on Mac OS systems. + InstallHint string `json:"installHint,omitempty"` + + // ProvideClusterInfo determines whether or not to provide cluster information, + // which could potentially contain very large CA data, to this exec plugin as a + // part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set + // to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for + // reading this environment variable. + ProvideClusterInfo bool `json:"provideClusterInfo"` + + // InteractiveMode determines this plugin's relationship with standard input. Valid + // values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this + // exec plugin wants to use standard input if it is available), or "Always" (this exec + // plugin requires standard input to function). See ExecInteractiveMode values for more + // details. + // + // If APIVersion is client.authentication.k8s.io/v1alpha1 or + // client.authentication.k8s.io/v1beta1, then this field is optional and defaults + // to "IfAvailable" when unset. Otherwise, this field is required. + //+optional + InteractiveMode ExecInteractiveMode `json:"interactiveMode,omitempty"` +} + +// ExecEnvVar is used for setting environment variables when executing an exec-based +// credential plugin. +type ExecEnvVar struct { + Name string `json:"name"` + Value string `json:"value"` +} + +// ExecInteractiveMode is a string that describes an exec plugin's relationship with standard input. +type ExecInteractiveMode string + +const ( + // NeverExecInteractiveMode declares that this exec plugin never needs to use standard + // input, and therefore the exec plugin will be run regardless of whether standard input is + // available for user input. + NeverExecInteractiveMode ExecInteractiveMode = "Never" + // IfAvailableExecInteractiveMode declares that this exec plugin would like to use standard input + // if it is available, but can still operate if standard input is not available. Therefore, the + // exec plugin will be run regardless of whether stdin is available for user input. If standard + // input is available for user input, then it will be provided to this exec plugin. + IfAvailableExecInteractiveMode ExecInteractiveMode = "IfAvailable" + // AlwaysExecInteractiveMode declares that this exec plugin requires standard input in order to + // run, and therefore the exec plugin will only be run if standard input is available for user + // input. If standard input is not available for user input, then the exec plugin will not be run + // and an error will be returned by the exec plugin runner. + AlwaysExecInteractiveMode ExecInteractiveMode = "Always" +) + +// +genclient +// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/api/autoscaling/v1.Scale +// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale +// +genclient:method=ApplyScale,verb=apply,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Deployment enables declarative updates for Pods and ReplicaSets. +type Deployment struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + + // Specification of the desired behavior of the Deployment. + // +optional + Spec DeploymentSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + + // Most recently observed status of the Deployment. + // +optional + Status DeploymentStatus `json:"status" protobuf:"bytes,3,opt,name=status"` +} + +// DeploymentSpec is the specification of the desired behavior of the Deployment. +type DeploymentSpec struct { + // Number of desired pods. This is a pointer to distinguish between explicit + // zero and not specified. Defaults to 1. + // +optional + Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` + + // Label selector for pods. Existing ReplicaSets whose pods are + // selected by this will be the ones affected by this deployment. + // It must match the pod template's labels. + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,2,opt,name=selector"` + + // Template describes the pods that will be created. + // The only allowed template.spec.restartPolicy value is "Always". + Template PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"` + + // The deployment strategy to use to replace existing pods with new ones. + // +optional + // +patchStrategy=retainKeys + Strategy DeploymentStrategy `json:"strategy" patchStrategy:"retainKeys" protobuf:"bytes,4,opt,name=strategy"` + + // Minimum number of seconds for which a newly created pod should be ready + // without any of its container crashing, for it to be considered available. + // Defaults to 0 (pod will be considered available as soon as it is ready) + // +optional + MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,5,opt,name=minReadySeconds"` + + // The number of old ReplicaSets to retain to allow rollback. + // This is a pointer to distinguish between explicit zero and not specified. + // Defaults to 10. + // +optional + RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"` + + // Indicates that the deployment is paused. + // +optional + Paused bool `json:"paused,omitempty" protobuf:"varint,7,opt,name=paused"` + + // The maximum time in seconds for a deployment to make progress before it + // is considered to be failed. The deployment controller will continue to + // process failed deployments and a condition with a ProgressDeadlineExceeded + // reason will be surfaced in the deployment status. Note that progress will + // not be estimated during the time a deployment is paused. Defaults to 600s. + ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty" protobuf:"varint,9,opt,name=progressDeadlineSeconds"` +} + +const ( + // DefaultDeploymentUniqueLabelKey is the default key of the selector that is added + // to existing ReplicaSets (and label key that is added to its pods) to prevent the existing ReplicaSets + // to select new pods (and old pods being select by new ReplicaSet). + DefaultDeploymentUniqueLabelKey string = "pod-template-hash" +) + +// DeploymentStrategy describes how to replace existing pods with new ones. +type DeploymentStrategy struct { + // Type of deployment. Can be "Recreate" or "RollingUpdate". Default is RollingUpdate. + // +optional + Type DeploymentStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type,casttype=DeploymentStrategyType"` + + // Rolling update config params. Present only if DeploymentStrategyType = + // RollingUpdate. + //--- + // TODO: Update this to follow our convention for oneOf, whatever we decide it + // to be. + // +optional + RollingUpdate *RollingUpdateDeployment `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"` +} + +// +enum +type DeploymentStrategyType string + +const ( + // Kill all existing pods before creating new ones. + RecreateDeploymentStrategyType DeploymentStrategyType = "Recreate" + + // Replace the old ReplicaSets by new one using rolling update i.e gradually scale down the old ReplicaSets and scale up the new one. + RollingUpdateDeploymentStrategyType DeploymentStrategyType = "RollingUpdate" +) + +// Spec to control the desired behavior of rolling update. +type RollingUpdateDeployment struct { + // The maximum number of pods that can be unavailable during the update. + // Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + // Absolute number is calculated from percentage by rounding down. + // This can not be 0 if MaxSurge is 0. + // Defaults to 25%. + // Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + // immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + // can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + // that the total number of pods available at all times during the update is at + // least 70% of desired pods. + // +optional + MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,1,opt,name=maxUnavailable"` + + // The maximum number of pods that can be scheduled above the desired number of + // pods. + // Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + // This can not be 0 if MaxUnavailable is 0. + // Absolute number is calculated from percentage by rounding up. + // Defaults to 25%. + // Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + // the rolling update starts, such that the total number of old and new pods do not exceed + // 130% of desired pods. Once old pods have been killed, + // new ReplicaSet can be scaled up further, ensuring that total number of pods running + // at any time during the update is at most 130% of desired pods. + // +optional + MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty" protobuf:"bytes,2,opt,name=maxSurge"` +} + +// DeploymentStatus is the most recently observed status of the Deployment. +type DeploymentStatus struct { + // The generation observed by the deployment controller. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"` + + // Total number of non-terminated pods targeted by this deployment (their labels match the selector). + // +optional + Replicas int32 `json:"replicas,omitempty" protobuf:"varint,2,opt,name=replicas"` + + // Total number of non-terminated pods targeted by this deployment that have the desired template spec. + // +optional + UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,3,opt,name=updatedReplicas"` + + // readyReplicas is the number of pods targeted by this Deployment with a Ready Condition. + // +optional + ReadyReplicas int32 `json:"readyReplicas,omitempty" protobuf:"varint,7,opt,name=readyReplicas"` + + // Total number of available pods (ready for at least minReadySeconds) targeted by this deployment. + // +optional + AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,4,opt,name=availableReplicas"` + + // Total number of unavailable pods targeted by this deployment. This is the total number of + // pods that are still required for the deployment to have 100% available capacity. They may + // either be pods that are running but not yet available or pods that still have not been created. + // +optional + UnavailableReplicas int32 `json:"unavailableReplicas,omitempty" protobuf:"varint,5,opt,name=unavailableReplicas"` + + // Represents the latest available observations of a deployment's current state. + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []DeploymentCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,6,rep,name=conditions"` + + // Count of hash collisions for the Deployment. The Deployment controller uses this + // field as a collision avoidance mechanism when it needs to create the name for the + // newest ReplicaSet. + // +optional + CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,8,opt,name=collisionCount"` +} + +type DeploymentConditionType string + +// These are valid conditions of a deployment. +const ( + // Available means the deployment is available, ie. at least the minimum available + // replicas required are up and running for at least minReadySeconds. + DeploymentAvailable DeploymentConditionType = "Available" + // Progressing means the deployment is progressing. Progress for a deployment is + // considered when a new replica set is created or adopted, and when new pods scale + // up or old pods scale down. Progress is not estimated for paused deployments or + // when progressDeadlineSeconds is not specified. + DeploymentProgressing DeploymentConditionType = "Progressing" + // ReplicaFailure is added in a deployment when one of its pods fails to be created + // or deleted. + DeploymentReplicaFailure DeploymentConditionType = "ReplicaFailure" +) + +// DeploymentCondition describes the state of a deployment at a certain point. +type DeploymentCondition struct { + // Type of deployment condition. + Type DeploymentConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=DeploymentConditionType"` + // Status of the condition, one of True, False, Unknown. + Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // The last time this condition was updated. + LastUpdateTime metav1.Time `json:"lastUpdateTime" protobuf:"bytes,6,opt,name=lastUpdateTime"` + // Last time the condition transitioned from one status to another. + LastTransitionTime metav1.Time `json:"lastTransitionTime" protobuf:"bytes,7,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// DeploymentList is a list of Deployments. +type DeploymentList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // +optional + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + + // Items is the list of Deployments. + Items []Deployment `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// DaemonSetUpdateStrategy is a struct used to control the update strategy for a DaemonSet. +type DaemonSetUpdateStrategy struct { + // Type of daemon set update. Can be "RollingUpdate" or "OnDelete". Default is RollingUpdate. + // +optional + Type DaemonSetUpdateStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type"` + + // Rolling update config params. Present only if type = "RollingUpdate". + //--- + // TODO: Update this to follow our convention for oneOf, whatever we decide it + // to be. Same as Deployment `strategy.rollingUpdate`. + // See https://github.com/kubernetes/kubernetes/issues/35345 + // +optional + RollingUpdate *RollingUpdateDaemonSet `json:"rollingUpdate,omitempty" protobuf:"bytes,2,opt,name=rollingUpdate"` +} + +type DaemonSetUpdateStrategyType string + +const ( + // Replace the old daemons by new ones using rolling update i.e replace them on each node one after the other. + RollingUpdateDaemonSetStrategyType DaemonSetUpdateStrategyType = "RollingUpdate" + + // Replace the old daemons only when it's killed + OnDeleteDaemonSetStrategyType DaemonSetUpdateStrategyType = "OnDelete" +) + +// Spec to control the desired behavior of daemon set rolling update. +type RollingUpdateDaemonSet struct { + // The maximum number of DaemonSet pods that can be unavailable during the + // update. Value can be an absolute number (ex: 5) or a percentage of total + // number of DaemonSet pods at the start of the update (ex: 10%). Absolute + // number is calculated from percentage by rounding up. + // This cannot be 0 if MaxSurge is 0 + // Default value is 1. + // Example: when this is set to 30%, at most 30% of the total number of nodes + // that should be running the daemon pod (i.e. status.desiredNumberScheduled) + // can have their pods stopped for an update at any given time. The update + // starts by stopping at most 30% of those DaemonSet pods and then brings + // up new DaemonSet pods in their place. Once the new pods are available, + // it then proceeds onto other DaemonSet pods, thus ensuring that at least + // 70% of original number of DaemonSet pods are available at all times during + // the update. + // +optional + MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,1,opt,name=maxUnavailable"` + + // The maximum number of nodes with an existing available DaemonSet pod that + // can have an updated DaemonSet pod during during an update. + // Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + // This can not be 0 if MaxUnavailable is 0. + // Absolute number is calculated from percentage by rounding up to a minimum of 1. + // Default value is 0. + // Example: when this is set to 30%, at most 30% of the total number of nodes + // that should be running the daemon pod (i.e. status.desiredNumberScheduled) + // can have their a new pod created before the old pod is marked as deleted. + // The update starts by launching new pods on 30% of nodes. Once an updated + // pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + // on that node is marked deleted. If the old pod becomes unavailable for any + // reason (Ready transitions to false, is evicted, or is drained) an updated + // pod is immediately created on that node without considering surge limits. + // Allowing surge implies the possibility that the resources consumed by the + // daemonset on any given node can double if the readiness check fails, and + // so resource intensive daemonsets should take into account that they may + // cause evictions during disruption. + // This is beta field and enabled/disabled by DaemonSetUpdateSurge feature gate. + // +optional + MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty" protobuf:"bytes,2,opt,name=maxSurge"` +} + +// DaemonSetSpec is the specification of a daemon set. +type DaemonSetSpec struct { + // A label query over pods that are managed by the daemon set. + // Must match in order to be controlled. + // It must match the pod template's labels. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + Selector *metav1.LabelSelector `json:"selector" protobuf:"bytes,1,opt,name=selector"` + + // An object that describes the pod that will be created. + // The DaemonSet will create exactly one copy of this pod on every node + // that matches the template's node selector (or on every node if no node + // selector is specified). + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + Template PodTemplateSpec `json:"template" protobuf:"bytes,2,opt,name=template"` + + // An update strategy to replace existing DaemonSet pods with new pods. + // +optional + UpdateStrategy DaemonSetUpdateStrategy `json:"updateStrategy" protobuf:"bytes,3,opt,name=updateStrategy"` + + // The minimum number of seconds for which a newly created DaemonSet pod should + // be ready without any of its container crashing, for it to be considered + // available. Defaults to 0 (pod will be considered available as soon as it + // is ready). + // +optional + MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,4,opt,name=minReadySeconds"` + + // The number of old history to retain to allow rollback. + // This is a pointer to distinguish between explicit zero and not specified. + // Defaults to 10. + // +optional + RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"` +} + +// DaemonSetStatus represents the current status of a daemon set. +type DaemonSetStatus struct { + // The number of nodes that are running at least 1 + // daemon pod and are supposed to run the daemon pod. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/ + CurrentNumberScheduled int32 `json:"currentNumberScheduled" protobuf:"varint,1,opt,name=currentNumberScheduled"` + + // The number of nodes that are running the daemon pod, but are + // not supposed to run the daemon pod. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/ + NumberMisscheduled int32 `json:"numberMisscheduled" protobuf:"varint,2,opt,name=numberMisscheduled"` + + // The total number of nodes that should be running the daemon + // pod (including nodes correctly running the daemon pod). + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/ + DesiredNumberScheduled int32 `json:"desiredNumberScheduled" protobuf:"varint,3,opt,name=desiredNumberScheduled"` + + // The number of nodes that should be running the daemon pod and have one + // or more of the daemon pod running and ready. + NumberReady int32 `json:"numberReady" protobuf:"varint,4,opt,name=numberReady"` + + // The most recent generation observed by the daemon set controller. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,5,opt,name=observedGeneration"` + + // The total number of nodes that are running updated daemon pod + // +optional + UpdatedNumberScheduled int32 `json:"updatedNumberScheduled,omitempty" protobuf:"varint,6,opt,name=updatedNumberScheduled"` + + // The number of nodes that should be running the + // daemon pod and have one or more of the daemon pod running and + // available (ready for at least spec.minReadySeconds) + // +optional + NumberAvailable int32 `json:"numberAvailable,omitempty" protobuf:"varint,7,opt,name=numberAvailable"` + + // The number of nodes that should be running the + // daemon pod and have none of the daemon pod running and available + // (ready for at least spec.minReadySeconds) + // +optional + NumberUnavailable int32 `json:"numberUnavailable,omitempty" protobuf:"varint,8,opt,name=numberUnavailable"` + + // Count of hash collisions for the DaemonSet. The DaemonSet controller + // uses this field as a collision avoidance mechanism when it needs to + // create the name for the newest ControllerRevision. + // +optional + CollisionCount *int32 `json:"collisionCount,omitempty" protobuf:"varint,9,opt,name=collisionCount"` + + // Represents the latest available observations of a DaemonSet's current state. + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []DaemonSetCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,10,rep,name=conditions"` +} + +type DaemonSetConditionType string + +// TODO: Add valid condition types of a DaemonSet. + +// DaemonSetCondition describes the state of a DaemonSet at a certain point. +type DaemonSetCondition struct { + // Type of DaemonSet condition. + Type DaemonSetConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=DaemonSetConditionType"` + // Status of the condition, one of True, False, Unknown. + Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime" protobuf:"bytes,3,opt,name=lastTransitionTime"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// DaemonSet represents the configuration of a daemon set. +type DaemonSet struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + + // The desired behavior of this daemon set. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec DaemonSetSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + + // The current status of this daemon set. This data may be + // out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status DaemonSetStatus `json:"status" protobuf:"bytes,3,opt,name=status"` +} + +const ( + // DefaultDaemonSetUniqueLabelKey is the default label key that is added + // to existing DaemonSet pods to distinguish between old and new + // DaemonSet pods during DaemonSet template updates. + DefaultDaemonSetUniqueLabelKey = "pod-template-has" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// DaemonSetList is a collection of daemon sets. +type DaemonSetList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + + // A list of daemon sets. + Items []DaemonSet `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Job represents the configuration of a single job. +type Job struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + + // Specification of the desired behavior of a job. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec JobSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + + // Current status of a job. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status JobStatus `json:"status" protobuf:"bytes,3,opt,name=status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// JobList is a collection of jobs. +type JobList struct { + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + + // items is the list of Jobs. + Items []Job `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// CompletionMode specifies how Pod completions of a Job are tracked. +// +enum +type CompletionMode string + +const ( + // NonIndexedCompletion is a Job completion mode. In this mode, the Job is + // considered complete when there have been .spec.completions + // successfully completed Pods. Pod completions are homologous to each other. + NonIndexedCompletion CompletionMode = "NonIndexed" + + // IndexedCompletion is a Job completion mode. In this mode, the Pods of a + // Job get an associated completion index from 0 to (.spec.completions - 1). + // The Job is considered complete when a Pod completes for each completion + // index. + IndexedCompletion CompletionMode = "Indexed" +) + +// PodFailurePolicyAction specifies how a Pod failure is handled. +// +enum +type PodFailurePolicyAction string + +const ( + // This is an action which might be taken on a pod failure - mark the + // pod's job as Failed and terminate all running pods. + PodFailurePolicyActionFailJob PodFailurePolicyAction = "FailJob" + + // This is an action which might be taken on a pod failure - mark the + // Job's index as failed to avoid restarts within this index. This action + // can only be used when backoffLimitPerIndex is set. + // This value is beta-level. + PodFailurePolicyActionFailIndex PodFailurePolicyAction = "FailIndex" + + // This is an action which might be taken on a pod failure - the counter towards + // .backoffLimit, represented by the job's .status.failed field, is not + // incremented and a replacement pod is created. + PodFailurePolicyActionIgnore PodFailurePolicyAction = "Ignore" + + // This is an action which might be taken on a pod failure - the pod failure + // is handled in the default way - the counter towards .backoffLimit, + // represented by the job's .status.failed field, is incremented. + PodFailurePolicyActionCount PodFailurePolicyAction = "Count" +) + +// +enum +type PodFailurePolicyOnExitCodesOperator string + +const ( + PodFailurePolicyOnExitCodesOpIn PodFailurePolicyOnExitCodesOperator = "In" + PodFailurePolicyOnExitCodesOpNotIn PodFailurePolicyOnExitCodesOperator = "NotIn" +) + +// PodReplacementPolicy specifies the policy for creating pod replacements. +// +enum +type PodReplacementPolicy string + +const ( + // TerminatingOrFailed means that we recreate pods + // when they are terminating (has a metadata.deletionTimestamp) or failed. + TerminatingOrFailed PodReplacementPolicy = "TerminatingOrFailed" + // Failed means to wait until a previously created Pod is fully terminated (has phase + // Failed or Succeeded) before creating a replacement Pod. + Failed PodReplacementPolicy = "Failed" +) + +// PodFailurePolicyOnExitCodesRequirement describes the requirement for handling +// a failed pod based on its container exit codes. In particular, it lookups the +// .state.terminated.exitCode for each app container and init container status, +// represented by the .status.containerStatuses and .status.initContainerStatuses +// fields in the Pod status, respectively. Containers completed with success +// (exit code 0) are excluded from the requirement check. +type PodFailurePolicyOnExitCodesRequirement struct { + // Restricts the check for exit codes to the container with the + // specified name. When null, the rule applies to all containers. + // When specified, it should match one the container or initContainer + // names in the pod template. + // +optional + ContainerName *string `json:"containerName,omitempty" protobuf:"bytes,1,opt,name=containerName"` + + // Represents the relationship between the container exit code(s) and the + // specified values. Containers completed with success (exit code 0) are + // excluded from the requirement check. Possible values are: + // + // - In: the requirement is satisfied if at least one container exit code + // (might be multiple if there are multiple containers not restricted + // by the 'containerName' field) is in the set of specified values. + // - NotIn: the requirement is satisfied if at least one container exit code + // (might be multiple if there are multiple containers not restricted + // by the 'containerName' field) is not in the set of specified values. + // Additional values are considered to be added in the future. Clients should + // react to an unknown operator by assuming the requirement is not satisfied. + Operator PodFailurePolicyOnExitCodesOperator `json:"operator" protobuf:"bytes,2,req,name=operator"` + + // Specifies the set of values. Each returned container exit code (might be + // multiple in case of multiple containers) is checked against this set of + // values with respect to the operator. The list of values must be ordered + // and must not contain duplicates. Value '0' cannot be used for the In operator. + // At least one element is required. At most 255 elements are allowed. + // +listType=set + Values []int32 `json:"values" protobuf:"varint,3,rep,name=values"` +} + +// PodFailurePolicyOnPodConditionsPattern describes a pattern for matching +// an actual pod condition type. +type PodFailurePolicyOnPodConditionsPattern struct { + // Specifies the required Pod condition type. To match a pod condition + // it is required that specified type equals the pod condition type. + Type PodConditionType `json:"type" protobuf:"bytes,1,req,name=type"` + + // Specifies the required Pod condition status. To match a pod condition + // it is required that the specified status equals the pod condition status. + // Defaults to True. + Status ConditionStatus `json:"status" protobuf:"bytes,2,req,name=status"` +} + +// PodFailurePolicyRule describes how a pod failure is handled when the requirements are met. +// One of onExitCodes and onPodConditions, but not both, can be used in each rule. +type PodFailurePolicyRule struct { + // Specifies the action taken on a pod failure when the requirements are satisfied. + // Possible values are: + // + // - FailJob: indicates that the pod's job is marked as Failed and all + // running pods are terminated. + // - FailIndex: indicates that the pod's index is marked as Failed and will + // not be restarted. + // This value is beta-level. It can be used when the + // `JobBackoffLimitPerIndex` feature gate is enabled (enabled by default). + // - Ignore: indicates that the counter towards the .backoffLimit is not + // incremented and a replacement pod is created. + // - Count: indicates that the pod is handled in the default way - the + // counter towards the .backoffLimit is incremented. + // Additional values are considered to be added in the future. Clients should + // react to an unknown action by skipping the rule. + Action PodFailurePolicyAction `json:"action" protobuf:"bytes,1,req,name=action"` + + // Represents the requirement on the container exit codes. + // +optional + OnExitCodes *PodFailurePolicyOnExitCodesRequirement `json:"onExitCodes,omitempty" protobuf:"bytes,2,opt,name=onExitCodes"` + + // Represents the requirement on the pod conditions. The requirement is represented + // as a list of pod condition patterns. The requirement is satisfied if at + // least one pattern matches an actual pod condition. At most 20 elements are allowed. + // +listType=atomic + // +optional + OnPodConditions []PodFailurePolicyOnPodConditionsPattern `json:"onPodConditions,omitempty" protobuf:"bytes,3,opt,name=onPodConditions"` +} + +// PodFailurePolicy describes how failed pods influence the backoffLimit. +type PodFailurePolicy struct { + // A list of pod failure policy rules. The rules are evaluated in order. + // Once a rule matches a Pod failure, the remaining of the rules are ignored. + // When no rule matches the Pod failure, the default handling applies - the + // counter of pod failures is incremented and it is checked against + // the backoffLimit. At most 20 elements are allowed. + // +listType=atomic + Rules []PodFailurePolicyRule `json:"rules" protobuf:"bytes,1,opt,name=rules"` +} + +// SuccessPolicy describes when a Job can be declared as succeeded based on the success of some indexes. +type SuccessPolicy struct { + // rules represents the list of alternative rules for the declaring the Jobs + // as successful before `.status.succeeded >= .spec.completions`. Once any of the rules are met, + // the "SucceededCriteriaMet" condition is added, and the lingering pods are removed. + // The terminal state for such a Job has the "Complete" condition. + // Additionally, these rules are evaluated in order; Once the Job meets one of the rules, + // other rules are ignored. At most 20 elements are allowed. + // +listType=atomic + Rules []SuccessPolicyRule `json:"rules" protobuf:"bytes,1,opt,name=rules"` +} + +// SuccessPolicyRule describes rule for declaring a Job as succeeded. +// Each rule must have at least one of the "succeededIndexes" or "succeededCount" specified. +type SuccessPolicyRule struct { + // succeededIndexes specifies the set of indexes + // which need to be contained in the actual set of the succeeded indexes for the Job. + // The list of indexes must be within 0 to ".spec.completions-1" and + // must not contain duplicates. At least one element is required. + // The indexes are represented as intervals separated by commas. + // The intervals can be a decimal integer or a pair of decimal integers separated by a hyphen. + // The number are listed in represented by the first and last element of the series, + // separated by a hyphen. + // For example, if the completed indexes are 1, 3, 4, 5 and 7, they are + // represented as "1,3-5,7". + // When this field is null, this field doesn't default to any value + // and is never evaluated at any time. + // + // +optional + SucceededIndexes *string `json:"succeededIndexes,omitempty" protobuf:"bytes,1,opt,name=succeededIndexes"` + + // succeededCount specifies the minimal required size of the actual set of the succeeded indexes + // for the Job. When succeededCount is used along with succeededIndexes, the check is + // constrained only to the set of indexes specified by succeededIndexes. + // For example, given that succeededIndexes is "1-4", succeededCount is "3", + // and completed indexes are "1", "3", and "5", the Job isn't declared as succeeded + // because only "1" and "3" indexes are considered in that rules. + // When this field is null, this doesn't default to any value and + // is never evaluated at any time. + // When specified it needs to be a positive integer. + // + // +optional + SucceededCount *int32 `json:"succeededCount,omitempty" protobuf:"varint,2,opt,name=succeededCount"` +} + +// JobSpec describes how the job execution will look like. +type JobSpec struct { + + // Specifies the maximum desired number of pods the job should + // run at any given time. The actual number of pods running in steady state will + // be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), + // i.e. when the work left to do is less than max parallelism. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/ + // +optional + Parallelism *int32 `json:"parallelism,omitempty" protobuf:"varint,1,opt,name=parallelism"` + + // Specifies the desired number of successfully finished pods the + // job should be run with. Setting to null means that the success of any + // pod signals the success of all pods, and allows parallelism to have any positive + // value. Setting to 1 means that parallelism is limited to 1 and the success of that + // pod signals the success of the job. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/ + // +optional + Completions *int32 `json:"completions,omitempty" protobuf:"varint,2,opt,name=completions"` + + // Specifies the duration in seconds relative to the startTime that the job + // may be continuously active before the system tries to terminate it; value + // must be positive integer. If a Job is suspended (at creation or through an + // update), this timer will effectively be stopped and reset when the Job is + // resumed again. + // +optional + ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty" protobuf:"varint,3,opt,name=activeDeadlineSeconds"` + + // Specifies the policy of handling failed pods. In particular, it allows to + // specify the set of actions and conditions which need to be + // satisfied to take the associated action. + // If empty, the default behaviour applies - the counter of failed pods, + // represented by the jobs's .status.failed field, is incremented and it is + // checked against the backoffLimit. This field cannot be used in combination + // with restartPolicy=OnFailure. + // + // +optional + PodFailurePolicy *PodFailurePolicy `json:"podFailurePolicy,omitempty" protobuf:"bytes,11,opt,name=podFailurePolicy"` + + // successPolicy specifies the policy when the Job can be declared as succeeded. + // If empty, the default behavior applies - the Job is declared as succeeded + // only when the number of succeeded pods equals to the completions. + // When the field is specified, it must be immutable and works only for the Indexed Jobs. + // Once the Job meets the SuccessPolicy, the lingering pods are terminated. + // + // This field is beta-level. To use this field, you must enable the + // `JobSuccessPolicy` feature gate (enabled by default). + // +optional + SuccessPolicy *SuccessPolicy `json:"successPolicy,omitempty" protobuf:"bytes,16,opt,name=successPolicy"` + + // Specifies the number of retries before marking this job failed. + // Defaults to 6 + // +optional + BackoffLimit *int32 `json:"backoffLimit,omitempty" protobuf:"varint,7,opt,name=backoffLimit"` + + // Specifies the limit for the number of retries within an + // index before marking this index as failed. When enabled the number of + // failures per index is kept in the pod's + // batch.kubernetes.io/job-index-failure-count annotation. It can only + // be set when Job's completionMode=Indexed, and the Pod's restart + // policy is Never. The field is immutable. + // This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` + // feature gate is enabled (enabled by default). + // +optional + BackoffLimitPerIndex *int32 `json:"backoffLimitPerIndex,omitempty" protobuf:"varint,12,opt,name=backoffLimitPerIndex"` + + // Specifies the maximal number of failed indexes before marking the Job as + // failed, when backoffLimitPerIndex is set. Once the number of failed + // indexes exceeds this number the entire Job is marked as Failed and its + // execution is terminated. When left as null the job continues execution of + // all of its indexes and is marked with the `Complete` Job condition. + // It can only be specified when backoffLimitPerIndex is set. + // It can be null or up to completions. It is required and must be + // less than or equal to 10^4 when is completions greater than 10^5. + // This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` + // feature gate is enabled (enabled by default). + // +optional + MaxFailedIndexes *int32 `json:"maxFailedIndexes,omitempty" protobuf:"varint,13,opt,name=maxFailedIndexes"` + + // TODO enabled it when https://github.com/kubernetes/kubernetes/issues/28486 has been fixed + // Optional number of failed pods to retain. + // +optional + // FailedPodsLimit *int32 `json:"failedPodsLimit,omitempty" protobuf:"varint,9,opt,name=failedPodsLimit"` + + // A label query over pods that should match the pod count. + // Normally, the system sets this field for you. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + // +optional + Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,4,opt,name=selector"` + + // manualSelector controls generation of pod labels and pod selectors. + // Leave `manualSelector` unset unless you are certain what you are doing. + // When false or unset, the system pick labels unique to this job + // and appends those labels to the pod template. When true, + // the user is responsible for picking unique labels and specifying + // the selector. Failure to pick a unique label may cause this + // and other jobs to not function correctly. However, You may see + // `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` + // API. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector + // +optional + ManualSelector *bool `json:"manualSelector,omitempty" protobuf:"varint,5,opt,name=manualSelector"` + + // Describes the pod that will be created when executing a job. + // The only allowed template.spec.restartPolicy values are "Never" or "OnFailure". + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/ + Template PodTemplateSpec `json:"template" protobuf:"bytes,6,opt,name=template"` + + // ttlSecondsAfterFinished limits the lifetime of a Job that has finished + // execution (either Complete or Failed). If this field is set, + // ttlSecondsAfterFinished after the Job finishes, it is eligible to be + // automatically deleted. When the Job is being deleted, its lifecycle + // guarantees (e.g. finalizers) will be honored. If this field is unset, + // the Job won't be automatically deleted. If this field is set to zero, + // the Job becomes eligible to be deleted immediately after it finishes. + // +optional + TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty" protobuf:"varint,8,opt,name=ttlSecondsAfterFinished"` + + // completionMode specifies how Pod completions are tracked. It can be + // `NonIndexed` (default) or `Indexed`. + // + // `NonIndexed` means that the Job is considered complete when there have + // been .spec.completions successfully completed Pods. Each Pod completion is + // homologous to each other. + // + // `Indexed` means that the Pods of a + // Job get an associated completion index from 0 to (.spec.completions - 1), + // available in the annotation batch.kubernetes.io/job-completion-index. + // The Job is considered complete when there is one successfully completed Pod + // for each index. + // When value is `Indexed`, .spec.completions must be specified and + // `.spec.parallelism` must be less than or equal to 10^5. + // In addition, The Pod name takes the form + // `$(job-name)-$(index)-$(random-string)`, + // the Pod hostname takes the form `$(job-name)-$(index)`. + // + // More completion modes can be added in the future. + // If the Job controller observes a mode that it doesn't recognize, which + // is possible during upgrades due to version skew, the controller + // skips updates for the Job. + // +optional + CompletionMode *CompletionMode `json:"completionMode,omitempty" protobuf:"bytes,9,opt,name=completionMode,casttype=CompletionMode"` + + // suspend specifies whether the Job controller should create Pods or not. If + // a Job is created with suspend set to true, no Pods are created by the Job + // controller. If a Job is suspended after creation (i.e. the flag goes from + // false to true), the Job controller will delete all active Pods associated + // with this Job. Users must design their workload to gracefully handle this. + // Suspending a Job will reset the StartTime field of the Job, effectively + // resetting the ActiveDeadlineSeconds timer too. Defaults to false. + // + // +optional + Suspend *bool `json:"suspend,omitempty" protobuf:"varint,10,opt,name=suspend"` + + // podReplacementPolicy specifies when to create replacement Pods. + // Possible values are: + // - TerminatingOrFailed means that we recreate pods + // when they are terminating (has a metadata.deletionTimestamp) or failed. + // - Failed means to wait until a previously created Pod is fully terminated (has phase + // Failed or Succeeded) before creating a replacement Pod. + // + // When using podFailurePolicy, Failed is the the only allowed value. + // TerminatingOrFailed and Failed are allowed values when podFailurePolicy is not in use. + // This is an beta field. To use this, enable the JobPodReplacementPolicy feature toggle. + // This is on by default. + // +optional + PodReplacementPolicy *PodReplacementPolicy `json:"podReplacementPolicy,omitempty" protobuf:"bytes,14,opt,name=podReplacementPolicy,casttype=podReplacementPolicy"` + + // ManagedBy field indicates the controller that manages a Job. The k8s Job + // controller reconciles jobs which don't have this field at all or the field + // value is the reserved string `kubernetes.io/job-controller`, but skips + // reconciling Jobs with a custom value for this field. + // The value must be a valid domain-prefixed path (e.g. acme.io/foo) - + // all characters before the first "/" must be a valid subdomain as defined + // by RFC 1123. All characters trailing the first "/" must be valid HTTP Path + // characters as defined by RFC 3986. The value cannot exceed 63 characters. + // This field is immutable. + // + // This field is alpha-level. The job controller accepts setting the field + // when the feature gate JobManagedBy is enabled (disabled by default). + // +optional + ManagedBy *string `json:"managedBy,omitempty" protobuf:"bytes,15,opt,name=managedBy"` +} + +// JobStatus represents the current state of a Job. +type JobStatus struct { + // The latest available observations of an object's current state. When a Job + // fails, one of the conditions will have type "Failed" and status true. When + // a Job is suspended, one of the conditions will have type "Suspended" and + // status true; when the Job is resumed, the status of this condition will + // become false. When a Job is completed, one of the conditions will have + // type "Complete" and status true. + // + // A job is considered finished when it is in a terminal condition, either + // "Complete" or "Failed". A Job cannot have both the "Complete" and "Failed" conditions. + // Additionally, it cannot be in the "Complete" and "FailureTarget" conditions. + // The "Complete", "Failed" and "FailureTarget" conditions cannot be disabled. + // + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/ + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=atomic + Conditions []JobCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + + // Represents time when the job controller started processing a job. When a + // Job is created in the suspended state, this field is not set until the + // first time it is resumed. This field is reset every time a Job is resumed + // from suspension. It is represented in RFC3339 form and is in UTC. + // + // Once set, the field can only be removed when the job is suspended. + // The field cannot be modified while the job is unsuspended or finished. + // + // +optional + StartTime *metav1.Time `json:"startTime,omitempty" protobuf:"bytes,2,opt,name=startTime"` + + // Represents time when the job was completed. It is not guaranteed to + // be set in happens-before order across separate operations. + // It is represented in RFC3339 form and is in UTC. + // The completion time is set when the job finishes successfully, and only then. + // The value cannot be updated or removed. The value indicates the same or + // later point in time as the startTime field. + // +optional + CompletionTime *metav1.Time `json:"completionTime,omitempty" protobuf:"bytes,3,opt,name=completionTime"` + + // The number of pending and running pods which are not terminating (without + // a deletionTimestamp). + // The value is zero for finished jobs. + // +optional + Active int32 `json:"active,omitempty" protobuf:"varint,4,opt,name=active"` + + // The number of pods which reached phase Succeeded. + // The value increases monotonically for a given spec. However, it may + // decrease in reaction to scale down of elastic indexed jobs. + // +optional + Succeeded int32 `json:"succeeded,omitempty" protobuf:"varint,5,opt,name=succeeded"` + + // The number of pods which reached phase Failed. + // The value increases monotonically. + // +optional + Failed int32 `json:"failed,omitempty" protobuf:"varint,6,opt,name=failed"` + + // The number of pods which are terminating (in phase Pending or Running + // and have a deletionTimestamp). + // + // This field is beta-level. The job controller populates the field when + // the feature gate JobPodReplacementPolicy is enabled (enabled by default). + // +optional + Terminating *int32 `json:"terminating,omitempty" protobuf:"varint,11,opt,name=terminating"` + + // completedIndexes holds the completed indexes when .spec.completionMode = + // "Indexed" in a text format. The indexes are represented as decimal integers + // separated by commas. The numbers are listed in increasing order. Three or + // more consecutive numbers are compressed and represented by the first and + // last element of the series, separated by a hyphen. + // For example, if the completed indexes are 1, 3, 4, 5 and 7, they are + // represented as "1,3-5,7". + // +optional + CompletedIndexes string `json:"completedIndexes,omitempty" protobuf:"bytes,7,opt,name=completedIndexes"` + + // FailedIndexes holds the failed indexes when spec.backoffLimitPerIndex is set. + // The indexes are represented in the text format analogous as for the + // `completedIndexes` field, ie. they are kept as decimal integers + // separated by commas. The numbers are listed in increasing order. Three or + // more consecutive numbers are compressed and represented by the first and + // last element of the series, separated by a hyphen. + // For example, if the failed indexes are 1, 3, 4, 5 and 7, they are + // represented as "1,3-5,7". + // The set of failed indexes cannot overlap with the set of completed indexes. + // + // This field is beta-level. It can be used when the `JobBackoffLimitPerIndex` + // feature gate is enabled (enabled by default). + // +optional + FailedIndexes *string `json:"failedIndexes,omitempty" protobuf:"bytes,10,opt,name=failedIndexes"` + + // uncountedTerminatedPods holds the UIDs of Pods that have terminated but + // the job controller hasn't yet accounted for in the status counters. + // + // The job controller creates pods with a finalizer. When a pod terminates + // (succeeded or failed), the controller does three steps to account for it + // in the job status: + // + // 1. Add the pod UID to the arrays in this field. + // 2. Remove the pod finalizer. + // 3. Remove the pod UID from the arrays while increasing the corresponding + // counter. + // + // Old jobs might not be tracked using this field, in which case the field + // remains null. + // The structure is empty for finished jobs. + // +optional + UncountedTerminatedPods *UncountedTerminatedPods `json:"uncountedTerminatedPods,omitempty" protobuf:"bytes,8,opt,name=uncountedTerminatedPods"` + + // The number of active pods which have a Ready condition and are not + // terminating (without a deletionTimestamp). + Ready *int32 `json:"ready,omitempty" protobuf:"varint,9,opt,name=ready"` +} + +// UncountedTerminatedPods holds UIDs of Pods that have terminated but haven't +// been accounted in Job status counters. +type UncountedTerminatedPods struct { + // succeeded holds UIDs of succeeded Pods. + // +listType=set + // +optional + Succeeded []types.UID `json:"succeeded,omitempty" protobuf:"bytes,1,rep,name=succeeded,casttype=k8s.io/apimachinery/pkg/types.UID"` + + // failed holds UIDs of failed Pods. + // +listType=set + // +optional + Failed []types.UID `json:"failed,omitempty" protobuf:"bytes,2,rep,name=failed,casttype=k8s.io/apimachinery/pkg/types.UID"` +} + +type JobConditionType string + +// These are built-in conditions of a job. +const ( + // JobSuspended means the job has been suspended. + JobSuspended JobConditionType = "Suspended" + // JobComplete means the job has completed its execution. + JobComplete JobConditionType = "Complete" + // JobFailed means the job has failed its execution. + JobFailed JobConditionType = "Failed" + // FailureTarget means the job is about to fail its execution. + JobFailureTarget JobConditionType = "FailureTarget" + // JobSuccessCriteriaMet means the Job has been succeeded. + JobSuccessCriteriaMet JobConditionType = "SuccessCriteriaMet" +) + +const ( + // JobReasonPodFailurePolicy reason indicates a job failure condition is added due to + // a failed pod matching a pod failure policy rule + // https://kep.k8s.io/3329 + JobReasonPodFailurePolicy string = "PodFailurePolicy" + // JobReasonBackOffLimitExceeded reason indicates that pods within a job have failed a number of + // times higher than backOffLimit times. + JobReasonBackoffLimitExceeded string = "BackoffLimitExceeded" + // JobReasponDeadlineExceeded means job duration is past ActiveDeadline + JobReasonDeadlineExceeded string = "DeadlineExceeded" + // JobReasonMaxFailedIndexesExceeded indicates that an indexed of a job failed + // This const is used in beta-level feature: https://kep.k8s.io/3850. + JobReasonMaxFailedIndexesExceeded string = "MaxFailedIndexesExceeded" + // JobReasonFailedIndexes means Job has failed indexes. + // This const is used in beta-level feature: https://kep.k8s.io/3850. + JobReasonFailedIndexes string = "FailedIndexes" + // JobReasonSuccessPolicy reason indicates a SuccessCriteriaMet condition is added due to + // a Job met successPolicy. + // https://kep.k8s.io/3998 + // This is currently a beta field. + JobReasonSuccessPolicy string = "SuccessPolicy" + // JobReasonCompletionsReached reason indicates a SuccessCriteriaMet condition is added due to + // a number of succeeded Job pods met completions. + // - https://kep.k8s.io/3998 + // This is currently a beta field. + JobReasonCompletionsReached string = "CompletionsReached" +) + +// JobCondition describes current state of a job. +type JobCondition struct { + // Type of job condition, Complete or Failed. + Type JobConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=JobConditionType"` + // Status of the condition, one of True, False, Unknown. + Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + // Last time the condition was checked. + // +optional + LastProbeTime metav1.Time `json:"lastProbeTime" protobuf:"bytes,3,opt,name=lastProbeTime"` + // Last time the condition transit from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime" protobuf:"bytes,4,opt,name=lastTransitionTime"` + // (brief) reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"` + // Human readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` +} + +// JobTemplateSpec describes the data a Job should have when created from a template +type JobTemplateSpec struct { + // Standard object's metadata of the jobs created from this template. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + + // Specification of the desired behavior of the job. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Spec JobSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/LICENSE b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/amount.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/amount.go new file mode 100644 index 00000000..fbe162b1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/amount.go @@ -0,0 +1,299 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "math/big" + "strconv" + + "gopkg.in/inf.v0" +) + +// Scale is used for getting and setting the base-10 scaled value. +// Base-2 scales are omitted for mathematical simplicity. +// See Quantity.ScaledValue for more details. +type Scale int32 + +// infScale adapts a Scale value to an inf.Scale value. +func (s Scale) infScale() inf.Scale { + return inf.Scale(-s) // inf.Scale is upside-down +} + +const ( + Nano Scale = -9 + Micro Scale = -6 + Milli Scale = -3 + Kilo Scale = 3 + Mega Scale = 6 + Giga Scale = 9 + Tera Scale = 12 + Peta Scale = 15 + Exa Scale = 18 +) + +var ( + Zero = int64Amount{} + + // Used by quantity strings - treat as read-only + zeroBytes = []byte("0") +) + +// int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster +// than operations on inf.Dec for values that can be represented as int64. +// +k8s:openapi-gen=true +type int64Amount struct { + value int64 + scale Scale +} + +// Sign returns 0 if the value is zero, -1 if it is less than 0, or 1 if it is greater than 0. +func (a int64Amount) Sign() int { + switch { + case a.value == 0: + return 0 + case a.value > 0: + return 1 + default: + return -1 + } +} + +// AsInt64 returns the current amount as an int64 at scale 0, or false if the value cannot be +// represented in an int64 OR would result in a loss of precision. This method is intended as +// an optimization to avoid calling AsDec. +func (a int64Amount) AsInt64() (int64, bool) { + if a.scale == 0 { + return a.value, true + } + if a.scale < 0 { + // TODO: attempt to reduce factors, although it is assumed that factors are reduced prior + // to the int64Amount being created. + return 0, false + } + return positiveScaleInt64(a.value, a.scale) +} + +// AsScaledInt64 returns an int64 representing the value of this amount at the specified scale, +// rounding up, or false if that would result in overflow. (1e20).AsScaledInt64(1) would result +// in overflow because 1e19 is not representable as an int64. Note that setting a scale larger +// than the current value may result in loss of precision - i.e. (1e-6).AsScaledInt64(0) would +// return 1, because 0.000001 is rounded up to 1. +func (a int64Amount) AsScaledInt64(scale Scale) (result int64, ok bool) { + if a.scale < scale { + result, _ = negativeScaleInt64(a.value, scale-a.scale) + return result, true + } + return positiveScaleInt64(a.value, a.scale-scale) +} + +// AsDec returns an inf.Dec representation of this value. +func (a int64Amount) AsDec() *inf.Dec { + var base inf.Dec + base.SetUnscaled(a.value) + base.SetScale(inf.Scale(-a.scale)) + return &base +} + +// Cmp returns 0 if a and b are equal, 1 if a is greater than b, or -1 if a is less than b. +func (a int64Amount) Cmp(b int64Amount) int { + switch { + case a.scale == b.scale: + // compare only the unscaled portion + case a.scale > b.scale: + result, remainder, exact := divideByScaleInt64(b.value, a.scale-b.scale) + if !exact { + return a.AsDec().Cmp(b.AsDec()) + } + if result == a.value { + switch { + case remainder == 0: + return 0 + case remainder > 0: + return -1 + default: + return 1 + } + } + b.value = result + default: + result, remainder, exact := divideByScaleInt64(a.value, b.scale-a.scale) + if !exact { + return a.AsDec().Cmp(b.AsDec()) + } + if result == b.value { + switch { + case remainder == 0: + return 0 + case remainder > 0: + return 1 + default: + return -1 + } + } + a.value = result + } + + switch { + case a.value == b.value: + return 0 + case a.value < b.value: + return -1 + default: + return 1 + } +} + +// Add adds two int64Amounts together, matching scales. It will return false and not mutate +// a if overflow or underflow would result. +func (a *int64Amount) Add(b int64Amount) bool { + switch { + case b.value == 0: + return true + case a.value == 0: + a.value = b.value + a.scale = b.scale + return true + case a.scale == b.scale: + c, ok := int64Add(a.value, b.value) + if !ok { + return false + } + a.value = c + case a.scale > b.scale: + c, ok := positiveScaleInt64(a.value, a.scale-b.scale) + if !ok { + return false + } + c, ok = int64Add(c, b.value) + if !ok { + return false + } + a.scale = b.scale + a.value = c + default: + c, ok := positiveScaleInt64(b.value, b.scale-a.scale) + if !ok { + return false + } + c, ok = int64Add(a.value, c) + if !ok { + return false + } + a.value = c + } + return true +} + +// Sub removes the value of b from the current amount, or returns false if underflow would result. +func (a *int64Amount) Sub(b int64Amount) bool { + return a.Add(int64Amount{value: -b.value, scale: b.scale}) +} + +// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision +// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. +func (a int64Amount) AsScale(scale Scale) (int64Amount, bool) { + if a.scale >= scale { + return a, true + } + result, exact := negativeScaleInt64(a.value, scale-a.scale) + return int64Amount{value: result, scale: scale}, exact +} + +// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted +// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. +func (a int64Amount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + mantissa := a.value + exponent = int32(a.scale) + + amount, times := removeInt64Factors(mantissa, 10) + exponent += times + + // make sure exponent is a multiple of 3 + var ok bool + switch exponent % 3 { + case 1, -2: + amount, ok = int64MultiplyScale10(amount) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) + } + exponent-- + case 2, -1: + amount, ok = int64MultiplyScale100(amount) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBytes(out) + } + exponent -= 2 + } + return strconv.AppendInt(out, amount, 10), exponent +} + +// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would +// return []byte("2048"), 1. +func (a int64Amount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { + value, ok := a.AsScaledInt64(0) + if !ok { + return infDecAmount{a.AsDec()}.AsCanonicalBase1024Bytes(out) + } + amount, exponent := removeInt64Factors(value, 1024) + return strconv.AppendInt(out, amount, 10), exponent +} + +// infDecAmount implements common operations over an inf.Dec that are specific to the quantity +// representation. +type infDecAmount struct { + *inf.Dec +} + +// AsScale adjusts this amount to set a minimum scale, rounding up, and returns true iff no precision +// was lost. (1.1e5).AsScale(5) would return 1.1e5, but (1.1e5).AsScale(6) would return 1e6. +func (a infDecAmount) AsScale(scale Scale) (infDecAmount, bool) { + tmp := &inf.Dec{} + tmp.Round(a.Dec, scale.infScale(), inf.RoundUp) + return infDecAmount{tmp}, tmp.Cmp(a.Dec) == 0 +} + +// AsCanonicalBytes accepts a buffer to write the base-10 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. The value is adjusted +// until the exponent is a multiple of 3 - i.e. 1.1e5 would return "110", 3. +func (a infDecAmount) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + mantissa := a.Dec.UnscaledBig() + exponent = int32(-a.Dec.Scale()) + amount := big.NewInt(0).Set(mantissa) + // move all factors of 10 into the exponent for easy reasoning + amount, times := removeBigIntFactors(amount, bigTen) + exponent += times + + // make sure exponent is a multiple of 3 + for exponent%3 != 0 { + amount.Mul(amount, bigTen) + exponent-- + } + + return append(out, amount.String()...), exponent +} + +// AsCanonicalBase1024Bytes accepts a buffer to write the base-1024 string value of this field to, and returns +// either that buffer or a larger buffer and the current exponent of the value. 2048 is 2 * 1024 ^ 1 and would +// return []byte("2048"), 1. +func (a infDecAmount) AsCanonicalBase1024Bytes(out []byte) (result []byte, exponent int32) { + tmp := &inf.Dec{} + tmp.Round(a.Dec, 0, inf.RoundUp) + amount, exponent := removeBigIntFactors(tmp.UnscaledBig(), big1024) + return append(out, amount.String()...), exponent +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/math.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/math.go new file mode 100644 index 00000000..5df189a1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/math.go @@ -0,0 +1,304 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "math/big" + + "gopkg.in/inf.v0" +) + +const ( + // maxInt64Factors is the highest value that will be checked when removing factors of 10 from an int64. + // It is also the maximum decimal digits that can be represented with an int64. + maxInt64Factors = 18 +) + +var ( + // Commonly needed big.Int values-- treat as read-only! + bigTen = big.NewInt(10) + bigZero = big.NewInt(0) + bigOne = big.NewInt(1) + big1024 = big.NewInt(1024) + + // Commonly needed inf.Dec values-- treat as read-only! + decZero = inf.NewDec(0, 0) + decOne = inf.NewDec(1, 0) + + // Largest (in magnitude) number allowed. + maxAllowed = infDecAmount{inf.NewDec((1<<63)-1, 0)} // == max int64 +) + +const mostNegative = -(mostPositive + 1) +const mostPositive = 1<<63 - 1 + +// int64Add returns a+b, or false if that would overflow int64. +func int64Add(a, b int64) (int64, bool) { + c := a + b + switch { + case a > 0 && b > 0: + if c < 0 { + return 0, false + } + case a < 0 && b < 0: + if c > 0 { + return 0, false + } + if a == mostNegative && b == mostNegative { + return 0, false + } + } + return c, true +} + +// int64Multiply returns a*b, or false if that would overflow or underflow int64. +func int64Multiply(a, b int64) (int64, bool) { + if a == 0 || b == 0 || a == 1 || b == 1 { + return a * b, true + } + if a == mostNegative || b == mostNegative { + return 0, false + } + c := a * b + return c, c/b == a +} + +// int64MultiplyScale returns a*b, assuming b is greater than one, or false if that would overflow or underflow int64. +// Use when b is known to be greater than one. +func int64MultiplyScale(a int64, b int64) (int64, bool) { + if a == 0 || a == 1 { + return a * b, true + } + if a == mostNegative && b != 1 { + return 0, false + } + c := a * b + return c, c/b == a +} + +// int64MultiplyScale10 multiplies a by 10, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 10) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale10(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 10, true + } + if a == mostNegative { + return 0, false + } + c := a * 10 + return c, c/10 == a +} + +// int64MultiplyScale100 multiplies a by 100, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 100) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale100(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 100, true + } + if a == mostNegative { + return 0, false + } + c := a * 100 + return c, c/100 == a +} + +// int64MultiplyScale1000 multiplies a by 1000, or returns false if that would overflow. This method is faster than +// int64Multiply(a, 1000) because the compiler can optimize constant factor multiplication. +func int64MultiplyScale1000(a int64) (int64, bool) { + if a == 0 || a == 1 { + return a * 1000, true + } + if a == mostNegative { + return 0, false + } + c := a * 1000 + return c, c/1000 == a +} + +// positiveScaleInt64 multiplies base by 10^scale, returning false if the +// value overflows. Passing a negative scale is undefined. +func positiveScaleInt64(base int64, scale Scale) (int64, bool) { + switch scale { + case 0: + return base, true + case 1: + return int64MultiplyScale10(base) + case 2: + return int64MultiplyScale100(base) + case 3: + return int64MultiplyScale1000(base) + case 6: + return int64MultiplyScale(base, 1000000) + case 9: + return int64MultiplyScale(base, 1000000000) + default: + value := base + var ok bool + for range scale { + if value, ok = int64MultiplyScale(value, 10); !ok { + return 0, false + } + } + return value, true + } +} + +// negativeScaleInt64 reduces base by the provided scale, rounding up, until the +// value is zero or the scale is reached. Passing a negative scale is undefined. +// The value returned, if not exact, is rounded away from zero. +func negativeScaleInt64(base int64, scale Scale) (result int64, exact bool) { + if scale == 0 { + return base, true + } + + value := base + var fraction bool + for range scale { + if !fraction && value%10 != 0 { + fraction = true + } + value /= 10 + if value == 0 { + if fraction { + if base > 0 { + return 1, false + } + return -1, false + } + return 0, true + } + } + if fraction { + if base > 0 { + value++ + } else { + value-- + } + } + return value, !fraction +} + +func pow10Int64(b int64) int64 { + switch b { + case 0: + return 1 + case 1: + return 10 + case 2: + return 100 + case 3: + return 1000 + case 4: + return 10000 + case 5: + return 100000 + case 6: + return 1000000 + case 7: + return 10000000 + case 8: + return 100000000 + case 9: + return 1000000000 + case 10: + return 10000000000 + case 11: + return 100000000000 + case 12: + return 1000000000000 + case 13: + return 10000000000000 + case 14: + return 100000000000000 + case 15: + return 1000000000000000 + case 16: + return 10000000000000000 + case 17: + return 100000000000000000 + case 18: + return 1000000000000000000 + default: + return 0 + } +} + +// negativeScaleInt64 returns the result of dividing base by scale * 10 and the remainder, or +// false if no such division is possible. Dividing by negative scales is undefined. +func divideByScaleInt64(base int64, scale Scale) (result, remainder int64, exact bool) { + if scale == 0 { + return base, 0, true + } + // the max scale representable in base 10 in an int64 is 18 decimal places + if scale >= 18 { + return 0, base, false + } + divisor := pow10Int64(int64(scale)) + return base / divisor, base % divisor, true +} + +// removeInt64Factors divides in a loop; the return values have the property that +// value == result * base ^ scale +func removeInt64Factors(value int64, base int64) (result int64, times int32) { + times = 0 + result = value + negative := result < 0 + if negative { + result = -result + } + switch base { + // allow the compiler to optimize the common cases + case 10: + for result >= 10 && result%10 == 0 { + times++ + result /= 10 + } + // allow the compiler to optimize the common cases + case 1024: + for result >= 1024 && result%1024 == 0 { + times++ + result /= 1024 + } + default: + for result >= base && result%base == 0 { + times++ + result /= base + } + } + if negative { + result = -result + } + return result, times +} + +// removeBigIntFactors divides in a loop; the return values have the property that +// d == result * factor ^ times +// d may be modified in place. +// If d == 0, then the return values will be (0, 0) +func removeBigIntFactors(d, factor *big.Int) (result *big.Int, times int32) { + q := big.NewInt(0) + m := big.NewInt(0) + for d.Cmp(bigZero) != 0 { + q.DivMod(d, factor, m) + if m.Cmp(bigZero) != 0 { + break + } + times++ + d, q = q, d + } + return d, times +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/quantity.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/quantity.go new file mode 100644 index 00000000..6e7f0c47 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/quantity.go @@ -0,0 +1,702 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "bytes" + "errors" + "fmt" + "math" + "math/big" + "strconv" + "strings" + + "gopkg.in/inf.v0" +) + +// Quantity is a fixed-point representation of a number. +// It provides convenient marshalling/unmarshalling in JSON and YAML, +// in addition to String() and AsInt64() accessors. +// +// The serialization format is: +// +// ::= +// +// (Note that may be empty, from the "" case in .) +// +// ::= 0 | 1 | ... | 9 +// ::= | +// ::= | . | . | . +// ::= "+" | "-" +// ::= | +// ::= | | +// ::= Ki | Mi | Gi | Ti | Pi | Ei +// +// (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html) +// +// ::= m | "" | k | M | G | T | P | E +// +// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.) +// +// ::= "e" | "E" +// +// No matter which of the three exponent forms is used, no quantity may represent +// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal +// places. Numbers larger or more precise will be capped or rounded up. +// (E.g.: 0.1m will rounded up to 1m.) +// This may be extended in the future if we require larger or smaller quantities. +// +// When a Quantity is parsed from a string, it will remember the type of suffix +// it had, and will use the same type again when it is serialized. +// +// Before serializing, Quantity will be put in "canonical form". +// This means that Exponent/suffix will be adjusted up or down (with a +// corresponding increase or decrease in Mantissa) such that: +// +// a. No precision is lost +// b. No fractional digits will be emitted +// c. The exponent (or suffix) is as large as possible. +// +// The sign will be omitted unless the number is negative. +// +// Examples: +// +// 1.5 will be serialized as "1500m" +// 1.5Gi will be serialized as "1536Mi" +// +// Note that the quantity will NEVER be internally represented by a +// floating point number. That is the whole point of this exercise. +// +// Non-canonical values will still parse as long as they are well formed, +// but will be re-emitted in their canonical form. (So always use canonical +// form, or don't diff.) +// +// This format is intended to make it difficult to use these numbers without +// writing some sort of special handling code in the hopes that that will +// cause implementers to also use a fixed point implementation. +// +// +protobuf=true +// +protobuf.embed=string +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Quantity struct { + // i is the quantity in int64 scaled form, if d.Dec == nil + i int64Amount + // d is the quantity in inf.Dec form if d.Dec != nil + d infDecAmount + // s is the generated value of this quantity to avoid recalculation + s string + + // Change Format at will. See the comment for Canonicalize for + // more details. + Format +} + +// CanonicalValue allows a quantity amount to be converted to a string. +type CanonicalValue interface { + // AsCanonicalBytes returns a byte array representing the string representation + // of the value mantissa and an int32 representing its exponent in base-10. Callers may + // pass a byte slice to the method to avoid allocations. + AsCanonicalBytes(out []byte) ([]byte, int32) + // AsCanonicalBase1024Bytes returns a byte array representing the string representation + // of the value mantissa and an int32 representing its exponent in base-1024. Callers + // may pass a byte slice to the method to avoid allocations. + AsCanonicalBase1024Bytes(out []byte) ([]byte, int32) +} + +// Format lists the three possible formats of a quantity. +type Format string + +const ( + DecimalExponent = Format("DecimalExponent") // e.g., 12e6 + BinarySI = Format("BinarySI") // e.g., 12Mi (12 * 2^20) + DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6) +) + +// MustParse turns the given string into a quantity or panics; for tests +// or other cases where you know the string is valid. +func MustParse(str string) Quantity { + q, err := ParseQuantity(str) + if err != nil { + panic(fmt.Errorf("cannot parse '%v': %v", str, err)) + } + return q +} + +const ( + // splitREString is used to separate a number from its suffix; as such, + // this is overly permissive, but that's OK-- it will be checked later. + splitREString = "^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$" +) + +var ( + // Errors that could happen while parsing a string. + ErrFormatWrong = errors.New("quantities must match the regular expression '" + splitREString + "'") + ErrNumeric = errors.New("unable to parse numeric part of quantity") + ErrSuffix = errors.New("unable to parse quantity's suffix") +) + +// parseQuantityString is a fast scanner for quantity values. +func parseQuantityString(str string) (positive bool, value, num, denom, suffix string, err error) { + positive = true + pos := 0 + end := len(str) + + // handle leading sign + if pos < end { + switch str[0] { + case '-': + positive = false + pos++ + case '+': + pos++ + } + } + + // strip leading zeros +Zeroes: + for i := pos; ; i++ { + if i >= end { + num = "0" + value = num + return + } + switch str[i] { + case '0': + pos++ + default: + break Zeroes + } + } + + // extract the numerator +Num: + for i := pos; ; i++ { + if i >= end { + num = str[pos:end] + value = str[0:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + num = str[pos:i] + pos = i + break Num + } + } + + // if we stripped all numerator positions, always return 0 + if len(num) == 0 { + num = "0" + } + + // handle a denominator + if pos < end && str[pos] == '.' { + pos++ + Denom: + for i := pos; ; i++ { + if i >= end { + denom = str[pos:end] + value = str[0:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + denom = str[pos:i] + pos = i + break Denom + } + } + // TODO: we currently allow 1.G, but we may not want to in the future. + // if len(denom) == 0 { + // err = ErrFormatWrong + // return + // } + } + value = str[0:pos] + + // grab the elements of the suffix + suffixStart := pos + for i := pos; ; i++ { + if i >= end { + suffix = str[suffixStart:end] + return + } + if !strings.ContainsAny(str[i:i+1], "eEinumkKMGTP") { + pos = i + break + } + } + if pos < end { + switch str[pos] { + case '-', '+': + pos++ + } + } +Suffix: + for i := pos; ; i++ { + if i >= end { + suffix = str[suffixStart:end] + return + } + switch str[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + break Suffix + } + } + // we encountered a non decimal in the Suffix loop, but the last character + // was not a valid exponent + err = ErrFormatWrong + //nolint:nakedret + return +} + +// ParseQuantity turns str into a Quantity, or returns an error. +func ParseQuantity(str string) (Quantity, error) { + if len(str) == 0 { + return Quantity{}, ErrFormatWrong + } + if str == "0" { + return Quantity{Format: DecimalSI, s: str}, nil + } + + positive, value, num, denom, suf, err := parseQuantityString(str) + if err != nil { + return Quantity{}, err + } + + base, exponent, format, ok := quantitySuffixer.interpret(suffix(suf)) + if !ok { + return Quantity{}, ErrSuffix + } + + precision := int32(0) + scale := int32(0) + mantissa := int64(1) + switch format { + case DecimalExponent, DecimalSI: + scale = exponent + precision = maxInt64Factors - int32(len(num)+len(denom)) + case BinarySI: + scale = 0 + switch { + case exponent >= 0 && len(denom) == 0: + // only handle positive binary numbers with the fast path + mantissa <<= uint64(exponent) + // 1Mi (2^20) has ~6 digits of decimal precision, so exponent*3/10 -1 is roughly the precision + precision = 15 - int32(len(num)) - int32(float32(exponent)*3/10) - 1 + default: + precision = -1 + } + } + + if precision >= 0 { + // if we have a denominator, shift the entire value to the left by the number of places in the + // denominator + scale -= int32(len(denom)) + if scale >= int32(Nano) { + shifted := num + denom + + var value int64 + value, err := strconv.ParseInt(shifted, 10, 64) + if err != nil { + return Quantity{}, ErrNumeric + } + if result, ok := int64Multiply(value, mantissa); ok { + if !positive { + result = -result + } + // if the number is in canonical form, reuse the string + switch format { + case BinarySI: + if exponent%10 == 0 && (value&0x07 != 0) { + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil + } + default: + if scale%3 == 0 && !strings.HasSuffix(shifted, "000") && shifted[0] != '0' { + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil + } + } + return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format}, nil + } + } + } + + amount := new(inf.Dec) + if _, ok := amount.SetString(value); !ok { + return Quantity{}, ErrNumeric + } + + // So that no one but us has to think about suffixes, remove it. + switch base { + case 10: + amount.SetScale(amount.Scale() + Scale(exponent).infScale()) + case 2: + // numericSuffix = 2 ** exponent + numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent)) + ub := amount.UnscaledBig() + amount.SetUnscaledBig(ub.Mul(ub, numericSuffix)) + } + + // Cap at min/max bounds. + sign := amount.Sign() + if sign == -1 { + amount.Neg(amount) + } + + // This rounds non-zero values up to the minimum representable value, under the theory that + // if you want some resources, you should get some resources, even if you asked for way too small + // of an amount. Arguably, this should be inf.RoundHalfUp (normal rounding), but that would have + // the side effect of rounding values < .5n to zero. + if v, ok := amount.Unscaled(); v != int64(0) || !ok { + amount.Round(amount, Nano.infScale(), inf.RoundUp) + } + + // The max is just a simple cap. + // TODO: this prevents accumulating quantities greater than int64, for instance quota across a cluster + if format == BinarySI && amount.Cmp(maxAllowed.Dec) > 0 { + amount.Set(maxAllowed.Dec) + } + + if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 { + // This avoids rounding and hopefully confusion, too. + format = DecimalSI + } + if sign == -1 { + amount.Neg(amount) + } + + return Quantity{d: infDecAmount{amount}, Format: format}, nil +} + +// DeepCopy returns a deep-copy of the Quantity value. Note that the method +// receiver is a value, so we can mutate it in-place and return it. +func (q Quantity) DeepCopy() Quantity { + if q.d.Dec != nil { + tmp := &inf.Dec{} + q.d.Dec = tmp.Set(q.d.Dec) + } + return q +} + +// CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity). +// +// Note about BinarySI: +// - If q.Format is set to BinarySI and q.Amount represents a non-zero value between +// -1 and +1, it will be emitted as if q.Format were DecimalSI. +// - Otherwise, if q.Format is set to BinarySI, fractional parts of q.Amount will be +// rounded up. (1.1i becomes 2i.) +func (q *Quantity) CanonicalizeBytes(out []byte) (result, suffix []byte) { + if q.IsZero() { + return zeroBytes, nil + } + + var rounded CanonicalValue + format := q.Format + switch format { + case DecimalExponent, DecimalSI: + case BinarySI: + if q.CmpInt64(-1024) > 0 && q.CmpInt64(1024) < 0 { + // This avoids rounding and hopefully confusion, too. + format = DecimalSI + } else { + var exact bool + if rounded, exact = q.AsScale(0); !exact { + // Don't lose precision-- show as DecimalSI + format = DecimalSI + } + } + default: + format = DecimalExponent + } + + // TODO: If BinarySI formatting is requested but would cause rounding, upgrade to + // one of the other formats. + switch format { + case DecimalExponent, DecimalSI: + number, exponent := q.AsCanonicalBytes(out) + suffix, _ := quantitySuffixer.constructBytes(10, exponent, format) + return number, suffix + default: + // format must be BinarySI + number, exponent := rounded.AsCanonicalBase1024Bytes(out) + suffix, _ := quantitySuffixer.constructBytes(2, exponent*10, format) + return number, suffix + } +} + +// AsApproximateFloat64 returns a float64 representation of the quantity which may +// lose precision. If the value of the quantity is outside the range of a float64 +// +Inf/-Inf will be returned. +func (q *Quantity) AsApproximateFloat64() float64 { + var base float64 + var exponent int + if q.d.Dec != nil { + base, _ = big.NewFloat(0).SetInt(q.d.Dec.UnscaledBig()).Float64() + exponent = int(-q.d.Dec.Scale()) + } else { + base = float64(q.i.value) + exponent = int(q.i.scale) + } + if exponent == 0 { + return base + } + + // multiply by the appropriate exponential scale + switch q.Format { + case DecimalExponent, DecimalSI: + return base * math.Pow10(exponent) + default: + // fast path for exponents that can fit in 64 bits + if exponent > 0 && exponent < 7 { + return base * float64(int64(1)<<(exponent*10)) + } + return base * math.Pow(2, float64(exponent*10)) + } +} + +// AsInt64 returns a representation of the current value as an int64 if a fast conversion +// is possible. If false is returned, callers must use the inf.Dec form of this quantity. +func (q *Quantity) AsInt64() (int64, bool) { + if q.d.Dec != nil { + return 0, false + } + return q.i.AsInt64() +} + +// ToDec promotes the quantity in place to use an inf.Dec representation and returns itself. +func (q *Quantity) ToDec() *Quantity { + if q.d.Dec == nil { + q.d.Dec = q.i.AsDec() + q.i = int64Amount{} + } + return q +} + +// AsDec returns the quantity as represented by a scaled inf.Dec. +func (q *Quantity) AsDec() *inf.Dec { + if q.d.Dec != nil { + return q.d.Dec + } + q.d.Dec = q.i.AsDec() + q.i = int64Amount{} + return q.d.Dec +} + +// AsCanonicalBytes returns the canonical byte representation of this quantity as a mantissa +// and base 10 exponent. The out byte slice may be passed to the method to avoid an extra +// allocation. +func (q *Quantity) AsCanonicalBytes(out []byte) (result []byte, exponent int32) { + if q.d.Dec != nil { + return q.d.AsCanonicalBytes(out) + } + return q.i.AsCanonicalBytes(out) +} + +// IsZero returns true if the quantity is equal to zero. +func (q *Quantity) IsZero() bool { + if q.d.Dec != nil { + return q.d.Dec.Sign() == 0 + } + return q.i.value == 0 +} + +// AsScale returns the current value, rounded up to the provided scale, and returns +// false if the scale resulted in a loss of precision. +func (q *Quantity) AsScale(scale Scale) (CanonicalValue, bool) { + if q.d.Dec != nil { + return q.d.AsScale(scale) + } + return q.i.AsScale(scale) +} + +// Cmp returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the +// quantity is greater than y. +func (q *Quantity) Cmp(y Quantity) int { + if q.d.Dec == nil && y.d.Dec == nil { + return q.i.Cmp(y.i) + } + return q.AsDec().Cmp(y.AsDec()) +} + +// CmpInt64 returns 0 if the quantity is equal to y, -1 if the quantity is less than y, or 1 if the +// quantity is greater than y. +func (q *Quantity) CmpInt64(y int64) int { + if q.d.Dec != nil { + return q.d.Dec.Cmp(inf.NewDec(y, inf.Scale(0))) + } + return q.i.Cmp(int64Amount{value: y}) +} + +// int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation +// of most Quantity values. +const int64QuantityExpectedBytes = 18 + +// String formats the Quantity as a string, caching the result if not calculated. +// String is an expensive operation and caching this result significantly reduces the cost of +// normal parse / marshal operations on Quantity. +func (q *Quantity) String() string { + if q == nil { + return "" + } + if len(q.s) == 0 { + result := make([]byte, 0, int64QuantityExpectedBytes) + number, suffix := q.CanonicalizeBytes(result) + number = append(number, suffix...) + q.s = string(number) + } + return q.s +} + +// MarshalJSON implements the json.Marshaller interface. +func (q Quantity) MarshalJSON() ([]byte, error) { + if len(q.s) > 0 { + out := make([]byte, len(q.s)+2) + out[0], out[len(out)-1] = '"', '"' + copy(out[1:], q.s) + return out, nil + } + result := make([]byte, int64QuantityExpectedBytes) + result[0] = '"' + number, suffix := q.CanonicalizeBytes(result[1:1]) + // if the same slice was returned to us that we passed in, avoid another allocation by copying number into + // the source slice and returning that + if len(number) > 0 && &number[0] == &result[1] && (len(number)+len(suffix)+2) <= int64QuantityExpectedBytes { + number = append(number, suffix...) + number = append(number, '"') + return result[:1+len(number)], nil + } + // if CanonicalizeBytes needed more space than our slice provided, we may need to allocate again so use + // append + result = result[:1] + result = append(result, number...) //nolint: makezero + result = append(result, suffix...) //nolint: makezero + result = append(result, '"') //nolint: makezero + return result, nil +} + +// ToUnstructured implements the value.UnstructuredConverter interface. +func (q Quantity) ToUnstructured() any { + return q.String() +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +// TODO: Remove support for leading/trailing whitespace +func (q *Quantity) UnmarshalJSON(value []byte) error { + l := len(value) + if l == 4 && bytes.Equal(value, []byte("null")) { + q.d.Dec = nil + q.i = int64Amount{} + return nil + } + if l >= 2 && value[0] == '"' && value[l-1] == '"' { + value = value[1 : l-1] + } + + parsed, err := ParseQuantity(strings.TrimSpace(string(value))) + if err != nil { + return err + } + + // This copy is safe because parsed will not be referred to again. + *q = parsed + return nil +} + +// NewDecimalQuantity returns a new Quantity representing the given +// value in the given format. +func NewDecimalQuantity(b inf.Dec, format Format) *Quantity { + return &Quantity{ + d: infDecAmount{&b}, + Format: format, + } +} + +// NewQuantity returns a new Quantity representing the given +// value in the given format. +func NewQuantity(value int64, format Format) *Quantity { + return &Quantity{ + i: int64Amount{value: value}, + Format: format, + } +} + +// NewMilliQuantity returns a new Quantity representing the given +// value * 1/1000 in the given format. Note that BinarySI formatting +// will round fractional values, and will be changed to DecimalSI for +// values x where (-1 < x < 1) && (x != 0). +func NewMilliQuantity(value int64, format Format) *Quantity { + return &Quantity{ + i: int64Amount{value: value, scale: -3}, + Format: format, + } +} + +// NewScaledQuantity returns a new Quantity representing the given +// value * 10^scale in DecimalSI format. +func NewScaledQuantity(value int64, scale Scale) *Quantity { + return &Quantity{ + i: int64Amount{value: value, scale: scale}, + Format: DecimalSI, + } +} + +// Value returns the unscaled value of q rounded up to the nearest integer away from 0. +func (q *Quantity) Value() int64 { + return q.ScaledValue(0) +} + +// MilliValue returns the value of ceil(q * 1000); this could overflow an int64; +// if that's a concern, call Value() first to verify the number is small enough. +func (q *Quantity) MilliValue() int64 { + return q.ScaledValue(Milli) +} + +// ScaledValue returns the value of ceil(q / 10^scale). +// For example, NewQuantity(1, DecimalSI).ScaledValue(Milli) returns 1000. +// This could overflow an int64. +// To detect overflow, call Value() first and verify the expected magnitude. +func (q *Quantity) ScaledValue(scale Scale) int64 { + if q.d.Dec == nil { + i, _ := q.i.AsScaledInt64(scale) + return i + } + dec := q.d.Dec + return scaledValue(dec.UnscaledBig(), int(dec.Scale()), int(scale.infScale())) +} + +// Set sets q's value to be value. +func (q *Quantity) Set(value int64) { + q.SetScaled(value, 0) +} + +// SetMilli sets q's value to be value * 1/1000. +func (q *Quantity) SetMilli(value int64) { + q.SetScaled(value, Milli) +} + +// SetScaled sets q's value to be value * 10^scale +func (q *Quantity) SetScaled(value int64, scale Scale) { + q.s = "" + q.d.Dec = nil + q.i = int64Amount{value: value, scale: scale} +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/scale_int.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/scale_int.go new file mode 100644 index 00000000..0d722490 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/scale_int.go @@ -0,0 +1,95 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "math" + "math/big" + "sync" +) + +var ( + // A sync pool to reduce allocation. + intPool sync.Pool + maxInt64 = big.NewInt(math.MaxInt64) +) + +func init() { + intPool.New = func() any { + return &big.Int{} + } +} + +// scaledValue scales given unscaled value from scale to new Scale and returns +// an int64. It ALWAYS rounds up the result when scale down. The final result might +// overflow. +// +// scale, newScale represents the scale of the unscaled decimal. +// The mathematical value of the decimal is unscaled * 10**(-scale). +func scaledValue(unscaled *big.Int, scale, newScale int) int64 { + dif := scale - newScale + if dif == 0 { + return unscaled.Int64() + } + + // Handle scale up + // This is an easy case, we do not need to care about rounding and overflow. + // If any intermediate operation causes overflow, the result will overflow. + if dif < 0 { + return unscaled.Int64() * int64(math.Pow10(-dif)) + } + + // Handle scale down + // We have to be careful about the intermediate operations. + + // fast path when unscaled < max.Int64 and exp(10,dif) < max.Int64 + const log10MaxInt64 = 19 + if unscaled.Cmp(maxInt64) < 0 && dif < log10MaxInt64 { + divide := int64(math.Pow10(dif)) + result := unscaled.Int64() / divide + mod := unscaled.Int64() % divide + if mod != 0 { + return result + 1 + } + return result + } + + // We should only convert back to int64 when getting the result. + divisor := intPool.Get().(*big.Int) + exp := intPool.Get().(*big.Int) + result := intPool.Get().(*big.Int) + defer func() { + intPool.Put(divisor) + intPool.Put(exp) + intPool.Put(result) + }() + + // divisor = 10^(dif) + // TODO: create loop up table if exp costs too much. + divisor.Exp(bigTen, exp.SetInt64(int64(dif)), nil) + // reuse exp + remainder := exp + + // result = unscaled / divisor + // remainder = unscaled % divisor + result.DivMod(unscaled, divisor, remainder) + if remainder.Sign() != 0 { + return result.Int64() + 1 + } + + return result.Int64() +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/suffix.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/suffix.go new file mode 100644 index 00000000..88fc9fd9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/api/resource/suffix.go @@ -0,0 +1,198 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "strconv" +) + +type suffix string + +// suffixer can interpret and construct suffixes. +type suffixer interface { + interpret(suf suffix) (base, exponent int32, fmt Format, ok bool) + construct(base, exponent int32, fmt Format) (s suffix, ok bool) + constructBytes(base, exponent int32, fmt Format) (s []byte, ok bool) +} + +// quantitySuffixer handles suffixes for all three formats that quantity +// can handle. +var quantitySuffixer = newSuffixer() + +type bePair struct { + base, exponent int32 +} + +type listSuffixer struct { + suffixToBE map[suffix]bePair + beToSuffix map[bePair]suffix + beToSuffixBytes map[bePair][]byte +} + +func (ls *listSuffixer) addSuffix(s suffix, pair bePair) { + if ls.suffixToBE == nil { + ls.suffixToBE = map[suffix]bePair{} + } + if ls.beToSuffix == nil { + ls.beToSuffix = map[bePair]suffix{} + } + if ls.beToSuffixBytes == nil { + ls.beToSuffixBytes = map[bePair][]byte{} + } + ls.suffixToBE[s] = pair + ls.beToSuffix[pair] = s + ls.beToSuffixBytes[pair] = []byte(s) +} + +func (ls *listSuffixer) lookup(s suffix) (base, exponent int32, ok bool) { + pair, ok := ls.suffixToBE[s] + if !ok { + return 0, 0, false + } + return pair.base, pair.exponent, true +} + +func (ls *listSuffixer) construct(base, exponent int32) (s suffix, ok bool) { + s, ok = ls.beToSuffix[bePair{base, exponent}] + return +} + +func (ls *listSuffixer) constructBytes(base, exponent int32) (s []byte, ok bool) { + s, ok = ls.beToSuffixBytes[bePair{base, exponent}] + return +} + +type suffixHandler struct { + decSuffixes listSuffixer + binSuffixes listSuffixer +} + +type fastLookup struct { + *suffixHandler +} + +func (l fastLookup) interpret(s suffix) (base, exponent int32, format Format, ok bool) { + switch s { + case "": + return 10, 0, DecimalSI, true + case "n": + return 10, -9, DecimalSI, true + case "u": + return 10, -6, DecimalSI, true + case "m": + return 10, -3, DecimalSI, true + case "k": + return 10, 3, DecimalSI, true + case "M": + return 10, 6, DecimalSI, true + case "G": + return 10, 9, DecimalSI, true + } + return l.suffixHandler.interpret(s) +} + +func newSuffixer() suffixer { + sh := &suffixHandler{} + + // IMPORTANT: if you change this section you must change fastLookup + + sh.binSuffixes.addSuffix("Ki", bePair{2, 10}) + sh.binSuffixes.addSuffix("Mi", bePair{2, 20}) + sh.binSuffixes.addSuffix("Gi", bePair{2, 30}) + sh.binSuffixes.addSuffix("Ti", bePair{2, 40}) + sh.binSuffixes.addSuffix("Pi", bePair{2, 50}) + sh.binSuffixes.addSuffix("Ei", bePair{2, 60}) + // Don't emit an error when trying to produce + // a suffix for 2^0. + sh.decSuffixes.addSuffix("", bePair{2, 0}) + + sh.decSuffixes.addSuffix("n", bePair{10, -9}) + sh.decSuffixes.addSuffix("u", bePair{10, -6}) + sh.decSuffixes.addSuffix("m", bePair{10, -3}) + sh.decSuffixes.addSuffix("", bePair{10, 0}) + sh.decSuffixes.addSuffix("k", bePair{10, 3}) + sh.decSuffixes.addSuffix("M", bePair{10, 6}) + sh.decSuffixes.addSuffix("G", bePair{10, 9}) + sh.decSuffixes.addSuffix("T", bePair{10, 12}) + sh.decSuffixes.addSuffix("P", bePair{10, 15}) + sh.decSuffixes.addSuffix("E", bePair{10, 18}) + + return fastLookup{sh} +} + +func (sh *suffixHandler) construct(base, exponent int32, fmt Format) (s suffix, ok bool) { + switch fmt { + case DecimalSI: + return sh.decSuffixes.construct(base, exponent) + case BinarySI: + return sh.binSuffixes.construct(base, exponent) + case DecimalExponent: + if base != 10 { + return "", false + } + if exponent == 0 { + return "", true + } + return suffix("e" + strconv.FormatInt(int64(exponent), 10)), true + } + return "", false +} + +func (sh *suffixHandler) constructBytes(base, exponent int32, format Format) (s []byte, ok bool) { + switch format { + case DecimalSI: + return sh.decSuffixes.constructBytes(base, exponent) + case BinarySI: + return sh.binSuffixes.constructBytes(base, exponent) + case DecimalExponent: + if base != 10 { + return nil, false + } + if exponent == 0 { + return nil, true + } + result := make([]byte, 8) + result[0] = 'e' + number := strconv.AppendInt(result[1:1], int64(exponent), 10) + if &result[1] == &number[0] { + return result[:1+len(number)], true + } + result = append(result[:1], number...) + return result, true + } + return nil, false +} + +func (sh *suffixHandler) interpret(suffix suffix) (base, exponent int32, fmt Format, ok bool) { + // Try lookup tables first + if b, e, ok := sh.decSuffixes.lookup(suffix); ok { + return b, e, DecimalSI, true + } + if b, e, ok := sh.binSuffixes.lookup(suffix); ok { + return b, e, BinarySI, true + } + + if len(suffix) > 1 && (suffix[0] == 'E' || suffix[0] == 'e') { + parsed, err := strconv.ParseInt(string(suffix[1:]), 10, 64) + if err != nil { + return 0, 0, DecimalExponent, false + } + return 10, int32(parsed), DecimalExponent, true + } + + return 0, 0, DecimalExponent, false +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/duration.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/duration.go new file mode 100644 index 00000000..2b195efc --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/duration.go @@ -0,0 +1,55 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "encoding/json" + "time" +) + +// Duration is a wrapper around time.Duration which supports correct +// marshalling to YAML and JSON. In particular, it marshals into strings, which +// can be used as map keys in json. +type Duration struct { + time.Duration +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (d *Duration) UnmarshalJSON(b []byte) error { + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + pd, err := time.ParseDuration(str) + if err != nil { + return err + } + d.Duration = pd + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.Duration.String()) +} + +// ToUnstructured implements the value.UnstructuredConverter interface. +func (d Duration) ToUnstructured() any { + return d.Duration.String() +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go new file mode 100644 index 00000000..a6dbd7cb --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time.go @@ -0,0 +1,171 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "encoding/json" + "time" +) + +const RFC3339Micro = "2006-01-02T15:04:05.000000Z07:00" + +// MicroTime is version of Time with microsecond level precision. +// +// +protobuf.options.marshal=false +// +protobuf.as=Timestamp +// +protobuf.options.(gogoproto.goproto_stringer)=false +type MicroTime struct { + time.Time +} + +// DeepCopy returns a deep-copy of the MicroTime value. The underlying time.Time +// type is effectively immutable in the time API, so it is safe to +// copy-by-assign, despite the presence of (unexported) Pointer fields. +func (t *MicroTime) DeepCopyInto(out *MicroTime) { + *out = *t +} + +// NewMicroTime returns a wrapped instance of the provided time +func NewMicroTime(time time.Time) MicroTime { + return MicroTime{time} +} + +// DateMicro returns the MicroTime corresponding to the supplied parameters +// by wrapping time.Date. +func DateMicro(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) MicroTime { + return MicroTime{time.Date(year, month, day, hour, min, sec, nsec, loc)} +} + +// NowMicro returns the current local time. +func NowMicro() MicroTime { + return MicroTime{time.Now()} +} + +// IsZero returns true if the value is nil or time is zero. +func (t *MicroTime) IsZero() bool { + if t == nil { + return true + } + return t.Time.IsZero() +} + +// Before reports whether the time instant t is before u. +func (t *MicroTime) Before(u *MicroTime) bool { + if t != nil && u != nil { + return t.Time.Before(u.Time) + } + return false +} + +// Equal reports whether the time instant t is equal to u. +func (t *MicroTime) Equal(u *MicroTime) bool { + if t == nil && u == nil { + return true + } + if t != nil && u != nil { + return t.Time.Equal(u.Time) + } + return false +} + +// BeforeTime reports whether the time instant t is before second-lever precision u. +func (t *MicroTime) BeforeTime(u *Time) bool { + if t != nil && u != nil { + return t.Time.Before(u.Time) + } + return false +} + +// EqualTime reports whether the time instant t is equal to second-lever precision u. +func (t *MicroTime) EqualTime(u *Time) bool { + if t == nil && u == nil { + return true + } + if t != nil && u != nil { + return t.Time.Equal(u.Time) + } + return false +} + +// UnixMicro returns the local time corresponding to the given Unix time +// by wrapping time.Unix. +func UnixMicro(sec int64, nsec int64) MicroTime { + return MicroTime{time.Unix(sec, nsec)} +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (t *MicroTime) UnmarshalJSON(b []byte) error { + if len(b) == 4 && string(b) == "null" { + t.Time = time.Time{} + return nil + } + + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + pt, err := time.Parse(RFC3339Micro, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// UnmarshalQueryParameter converts from a URL query parameter value to an object +func (t *MicroTime) UnmarshalQueryParameter(str string) error { + if len(str) == 0 { + t.Time = time.Time{} + return nil + } + // Tolerate requests from older clients that used JSON serialization to build query params + if len(str) == 4 && str == "null" { + t.Time = time.Time{} + return nil + } + + pt, err := time.Parse(RFC3339Micro, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (t MicroTime) MarshalJSON() ([]byte, error) { + if t.IsZero() { + // Encode unset/nil objects as JSON's "null". + return []byte("null"), nil + } + + return json.Marshal(t.UTC().Format(RFC3339Micro)) +} + +// MarshalQueryParameter converts to a URL query parameter value +func (t MicroTime) MarshalQueryParameter() (string, error) { + if t.IsZero() { + // Encode unset/nil objects as an empty string + return "", nil + } + + return t.UTC().Format(RFC3339Micro), nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/time.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/time.go new file mode 100644 index 00000000..9ccedd82 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/time.go @@ -0,0 +1,172 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "encoding/json" + "time" +) + +// Time is a wrapper around time.Time which supports correct +// marshalling to YAML and JSON. Wrappers are provided for many +// of the factory methods that the time package offers. +// +// +protobuf.options.marshal=false +// +protobuf.as=Timestamp +// +protobuf.options.(gogoproto.goproto_stringer)=false +type Time struct { + time.Time +} + +// DeepCopyInto creates a deep-copy of the Time value. The underlying time.Time +// type is effectively immutable in the time API, so it is safe to +// copy-by-assign, despite the presence of (unexported) Pointer fields. +func (t *Time) DeepCopyInto(out *Time) { + *out = *t +} + +// NewTime returns a wrapped instance of the provided time +func NewTime(time time.Time) Time { + return Time{time} +} + +// Date returns the Time corresponding to the supplied parameters +// by wrapping time.Date. +func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { + return Time{time.Date(year, month, day, hour, min, sec, nsec, loc)} +} + +// Now returns the current local time. +func Now() Time { + return Time{time.Now()} +} + +// IsZero returns true if the value is nil or time is zero. +func (t *Time) IsZero() bool { + if t == nil { + return true + } + return t.Time.IsZero() +} + +// Before reports whether the time instant t is before u. +func (t *Time) Before(u *Time) bool { + if t != nil && u != nil { + return t.Time.Before(u.Time) + } + return false +} + +// Equal reports whether the time instant t is equal to u. +func (t *Time) Equal(u *Time) bool { + if t == nil && u == nil { + return true + } + if t != nil && u != nil { + return t.Time.Equal(u.Time) + } + return false +} + +// Unix returns the local time corresponding to the given Unix time +// by wrapping time.Unix. +func Unix(sec int64, nsec int64) Time { + return Time{time.Unix(sec, nsec)} +} + +// Rfc3339Copy returns a copy of the Time at second-level precision. +func (t Time) Rfc3339Copy() Time { + copied, _ := time.Parse(time.RFC3339, t.Format(time.RFC3339)) + return Time{copied} +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (t *Time) UnmarshalJSON(b []byte) error { + if len(b) == 4 && string(b) == "null" { + t.Time = time.Time{} + return nil + } + + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + pt, err := time.Parse(time.RFC3339, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// UnmarshalQueryParameter converts from a URL query parameter value to an object +func (t *Time) UnmarshalQueryParameter(str string) error { + if len(str) == 0 { + t.Time = time.Time{} + return nil + } + // Tolerate requests from older clients that used JSON serialization to build query params + if len(str) == 4 && str == "null" { + t.Time = time.Time{} + return nil + } + + pt, err := time.Parse(time.RFC3339, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (t Time) MarshalJSON() ([]byte, error) { + if t.IsZero() { + // Encode unset/nil objects as JSON's "null". + return []byte("null"), nil + } + buf := make([]byte, 0, len(time.RFC3339)+2) + buf = append(buf, '"') + // time cannot contain non escapable JSON characters + buf = t.UTC().AppendFormat(buf, time.RFC3339) + buf = append(buf, '"') + return buf, nil +} + +// ToUnstructured implements the value.UnstructuredConverter interface. +func (t Time) ToUnstructured() any { + if t.IsZero() { + return nil + } + buf := make([]byte, 0, len(time.RFC3339)) + buf = t.UTC().AppendFormat(buf, time.RFC3339) + return string(buf) +} + +// MarshalQueryParameter converts to a URL query parameter value +func (t Time) MarshalQueryParameter() (string, error) { + if t.IsZero() { + // Encode unset/nil objects as an empty string + return "", nil + } + + return t.UTC().Format(time.RFC3339), nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/types.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/types.go new file mode 100644 index 00000000..921a36d1 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/apis/meta/v1/types.go @@ -0,0 +1,1346 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1 contains API types that are common to all versions. +// +// The package contains two categories of types: +// - external (serialized) types that lack their own version (e.g TypeMeta) +// - internal (never-serialized) types that are needed by several different +// api groups, and so live here, to avoid duplication and/or import loops +// (e.g. LabelSelector). +// +// In the future, we will probably move these categories of objects into +// separate packages. +package v1 + +import ( + "fmt" + "strings" + + "github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/types" +) + +// TypeMeta describes an individual object in an API response or request +// with strings representing the type of the object and its API schema version. +// Structures that are versioned or persisted should inline TypeMeta. +// +// +k8s:deepcopy-gen=false +type TypeMeta struct { + // Kind is a string value representing the REST resource this object represents. + // Servers may infer this from the endpoint the client submits requests to. + // Cannot be updated. + // In CamelCase. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + Kind string `json:"kind,omitempty"` + + // APIVersion defines the versioned schema of this representation of an object. + // Servers should convert recognized schemas to the latest internal value, and + // may reject unrecognized values. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + // +optional + APIVersion string `json:"apiVersion,omitempty"` +} + +// ListMeta describes metadata that synthetic resources must have, including lists and +// various status objects. A resource may have only one of {ObjectMeta, ListMeta}. +type ListMeta struct { + // selfLink is a URL representing this object. + // Populated by the system. + // Read-only. + // + // DEPRECATED + // Kubernetes will stop propagating this field in 1.20 release and the field is planned + // to be removed in 1.21 release. + // +optional + SelfLink string `json:"selfLink,omitempty"` + + // String that identifies the server's internal version of this object that + // can be used by clients to determine when objects have changed. + // Value must be treated as opaque by clients and passed unmodified back to the server. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + // +optional + ResourceVersion string `json:"resourceVersion,omitempty"` + + // continue may be set if the user set a limit on the number of items returned, and indicates that + // the server has more data available. The value is opaque and may be used to issue another request + // to the endpoint that served this list to retrieve the next set of available objects. Continuing a + // consistent list may not be possible if the server configuration has changed or more than a few + // minutes have passed. The resourceVersion field returned when using this continue value will be + // identical to the value in the first response, unless you have received this token from an error + // message. + Continue string `json:"continue,omitempty"` + + // remainingItemCount is the number of subsequent items in the list which are not included in this + // list response. If the list request contained label or field selectors, then the number of + // remaining items is unknown and the field will be left unset and omitted during serialization. + // If the list is complete (either because it is not chunking or because this is the last chunk), + // then there are no more remaining items and this field will be left unset and omitted during + // serialization. + // Servers older than v1.15 do not set this field. + // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients + // should not rely on the remainingItemCount to be set or to be exact. + // +optional + RemainingItemCount *int64 `json:"remainingItemCount,omitempty"` +} + +// Field path constants that are specific to the internal API +// representation. +const ( + ObjectNameField = "metadata.name" +) + +// These are internal finalizer values for Kubernetes-like APIs, must be qualified name unless defined here +const ( + FinalizerOrphanDependents = "orphan" + FinalizerDeleteDependents = "foregroundDeletion" +) + +// ObjectMeta is metadata that all persisted resources must have, which includes all objects +// users must create. +type ObjectMeta struct { + // Name must be unique within a namespace. Is required when creating resources, although + // some resources may allow a client to request the generation of an appropriate name + // automatically. Name is primarily intended for creation idempotence and configuration + // definition. + // Cannot be updated. + // More info: http://kubernetes.io/docs/user-guide/identifiers#names + // +optional + Name string `json:"name,omitempty"` + + // GenerateName is an optional prefix, used by the server, to generate a unique + // name ONLY IF the Name field has not been provided. + // If this field is used, the name returned to the client will be different + // than the name passed. This value will also be combined with a unique suffix. + // The provided value has the same validation rules as the Name field, + // and may be truncated by the length of the suffix required to make the value + // unique on the server. + // + // If this field is specified and the generated name exists, the server will + // NOT return a 409 - instead, it will either return 201 Created or 500 with Reason + // ServerTimeout indicating a unique name could not be found in the time allotted, and the client + // should retry (optionally after the time indicated in the Retry-After header). + // + // Applied only if Name is not specified. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency + // +optional + GenerateName string `json:"generateName,omitempty"` + + // Namespace defines the space within which each name must be unique. An empty namespace is + // equivalent to the "default" namespace, but "default" is the canonical representation. + // Not all objects are required to be scoped to a namespace - the value of this field for + // those objects will be empty. + // + // Must be a DNS_LABEL. + // Cannot be updated. + // More info: http://kubernetes.io/docs/user-guide/namespaces + // +optional + Namespace string `json:"namespace,omitempty"` + + // SelfLink is a URL representing this object. + // Populated by the system. + // Read-only. + // + // DEPRECATED + // Kubernetes will stop propagating this field in 1.20 release and the field is planned + // to be removed in 1.21 release. + // +optional + SelfLink string `json:"selfLink,omitempty"` + + // UID is the unique in time and space value for this object. It is typically generated by + // the server on successful creation of a resource and is not allowed to change on PUT + // operations. + // + // Populated by the system. + // Read-only. + // More info: http://kubernetes.io/docs/user-guide/identifiers#uids + // +optional + UID types.UID `json:"uid,omitempty"` + + // An opaque value that represents the internal version of this object that can + // be used by clients to determine when objects have changed. May be used for optimistic + // concurrency, change detection, and the watch operation on a resource or set of resources. + // Clients must treat these values as opaque and passed unmodified back to the server. + // They may only be valid for a particular resource or set of resources. + // + // Populated by the system. + // Read-only. + // Value must be treated as opaque by clients and . + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + // +optional + ResourceVersion string `json:"resourceVersion,omitempty"` + + // A sequence number representing a specific generation of the desired state. + // Populated by the system. Read-only. + // +optional + Generation int64 `json:"generation,omitempty"` + + // CreationTimestamp is a timestamp representing the server time when this object was + // created. It is not guaranteed to be set in happens-before order across separate operations. + // Clients may not set this value. It is represented in RFC3339 form and is in UTC. + // + // Populated by the system. + // Read-only. + // Null for lists. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + CreationTimestamp Time `json:"creationTimestamp"` + + // DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This + // field is set by the server when a graceful deletion is requested by the user, and is not + // directly settable by a client. The resource is expected to be deleted (no longer visible + // from resource lists, and not reachable by name) after the time in this field, once the + // finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. + // Once the deletionTimestamp is set, this value may not be unset or be set further into the + // future, although it may be shortened or the resource may be deleted prior to this time. + // For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react + // by sending a graceful termination signal to the containers in the pod. After that 30 seconds, + // the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, + // remove the pod from the API. In the presence of network partitions, this object may still + // exist after this timestamp, until an administrator or automated process can determine the + // resource is fully terminated. + // If not set, graceful deletion of the object has not been requested. + // + // Populated by the system when a graceful deletion is requested. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + DeletionTimestamp *Time `json:"deletionTimestamp,omitempty"` + + // Number of seconds allowed for this object to gracefully terminate before + // it will be removed from the system. Only set when deletionTimestamp is also set. + // May only be shortened. + // Read-only. + // +optional + DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty"` + + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. May match selectors of replication controllers + // and services. + // More info: http://kubernetes.io/docs/user-guide/labels + // +optional + Labels map[string]string `json:"labels,omitempty"` + + // Annotations is an unstructured key value map stored with a resource that may be + // set by external tools to store and retrieve arbitrary metadata. They are not + // queryable and should be preserved when modifying objects. + // More info: http://kubernetes.io/docs/user-guide/annotations + // +optional + Annotations map[string]string `json:"annotations,omitempty"` + + // List of objects depended by this object. If ALL objects in the list have + // been deleted, this object will be garbage collected. If this object is managed by a controller, + // then an entry in this list will point to this controller, with the controller field set to true. + // There cannot be more than one managing controller. + // +optional + // +patchMergeKey=uid + // +patchStrategy=merge + OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid"` + + // Must be empty before the object is deleted from the registry. Each entry + // is an identifier for the responsible component that will remove the entry + // from the list. If the deletionTimestamp of the object is non-nil, entries + // in this list can only be removed. + // Finalizers may be processed and removed in any order. Order is NOT enforced + // because it introduces significant risk of stuck finalizers. + // finalizers is a shared field, any actor with permission can reorder it. + // If the finalizer list is processed in order, then this can lead to a situation + // in which the component responsible for the first finalizer in the list is + // waiting for a signal (field value, external system, or other) produced by a + // component responsible for a finalizer later in the list, resulting in a deadlock. + // Without enforced ordering finalizers are free to order amongst themselves and + // are not vulnerable to ordering changes in the list. + // +optional + // +patchStrategy=merge + Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge"` + + // The name of the cluster which the object belongs to. + // This is used to distinguish resources with same name and namespace in different clusters. + // This field is not set anywhere right now and apiserver is going to ignore it if set in create or update request. + // +optional + ClusterName string `json:"clusterName,omitempty"` + + // ManagedFields maps workflow-id and version to the set of fields + // that are managed by that workflow. This is mostly for internal + // housekeeping, and users typically shouldn't need to set or + // understand this field. A workflow can be the user's name, a + // controller's name, or the name of a specific apply path like + // "ci-cd". The set of fields is always in the version that the + // workflow used when modifying the object. + // + // +optional + ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty"` +} + +const ( + // NamespaceDefault means the object is in the default namespace which is applied when not specified by clients + NamespaceDefault = "default" + // NamespaceAll is the default argument to specify on a context when you want to list or filter resources across all namespaces + NamespaceAll = "" + // NamespaceNone is the argument for a context when there is no namespace. + NamespaceNone = "" + // NamespaceSystem is the system namespace where we place system components. + NamespaceSystem = "kube-system" + // NamespacePublic is the namespace where we place public info (ConfigMaps) + NamespacePublic = "kube-public" +) + +// OwnerReference contains enough information to let you identify an owning +// object. An owning object must be in the same namespace as the dependent, or +// be cluster-scoped, so there is no namespace field. +// +structType=atomic +type OwnerReference struct { + // API version of the referent. + APIVersion string `json:"apiVersion"` + // Kind of the referent. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind"` + // Name of the referent. + // More info: http://kubernetes.io/docs/user-guide/identifiers#names + Name string `json:"name"` + // UID of the referent. + // More info: http://kubernetes.io/docs/user-guide/identifiers#uids + UID types.UID `json:"uid"` + // If true, this reference points to the managing controller. + // +optional + Controller *bool `json:"controller,omitempty"` + // If true, AND if the owner has the "foregroundDeletion" finalizer, then + // the owner cannot be deleted from the key-value store until this + // reference is removed. + // Defaults to false. + // To set this field, a user needs "delete" permission of the owner, + // otherwise 422 (Unprocessable Entity) will be returned. + // +optional + BlockOwnerDeletion *bool `json:"blockOwnerDeletion,omitempty"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ListOptions is the query options to a standard REST list call. +type ListOptions struct { + TypeMeta `json:",inline"` + + // A selector to restrict the list of returned objects by their labels. + // Defaults to everything. + // +optional + LabelSelector string `json:"labelSelector,omitempty"` + // A selector to restrict the list of returned objects by their fields. + // Defaults to everything. + // +optional + FieldSelector string `json:"fieldSelector,omitempty"` + + // +k8s:deprecated=includeUninitialized,protobuf=6 + + // Watch for changes to the described resources and return them as a stream of + // add, update, and remove notifications. Specify resourceVersion. + // +optional + Watch bool `json:"watch,omitempty"` + // allowWatchBookmarks requests watch events with type "BOOKMARK". + // Servers that do not implement bookmarks may ignore this flag and + // bookmarks are sent at the server's discretion. Clients should not + // assume bookmarks are returned at any specific interval, nor may they + // assume the server will send any BOOKMARK event during a session. + // If this is not a watch, this field is ignored. + // +optional + AllowWatchBookmarks bool `json:"allowWatchBookmarks,omitempty"` + + // resourceVersion sets a constraint on what resource versions a request may be served from. + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + // + // Defaults to unset + // +optional + ResourceVersion string `json:"resourceVersion,omitempty"` + + // resourceVersionMatch determines how resourceVersion is applied to list calls. + // It is highly recommended that resourceVersionMatch be set for list calls where + // resourceVersion is set + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + // + // Defaults to unset + // +optional + ResourceVersionMatch ResourceVersionMatch `json:"resourceVersionMatch,omitempty"` + // Timeout for the list/watch call. + // This limits the duration of the call, regardless of any activity or inactivity. + // +optional + TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` + + // limit is a maximum number of responses to return for a list call. If more items exist, the + // server will set the `continue` field on the list metadata to a value that can be used with the + // same initial query to retrieve the next set of results. Setting a limit may return fewer than + // the requested amount of items (up to zero items) in the event all requested objects are + // filtered out and clients should only use the presence of the continue field to determine whether + // more results are available. Servers may choose not to support the limit argument and will return + // all of the available results. If limit is specified and the continue field is empty, clients may + // assume that no more results are available. This field is not supported if watch is true. + // + // The server guarantees that the objects returned when using continue will be identical to issuing + // a single list call without a limit - that is, no objects created, modified, or deleted after the + // first request is issued will be included in any subsequent continued requests. This is sometimes + // referred to as a consistent snapshot, and ensures that a client that is using limit to receive + // smaller chunks of a very large result can ensure they see all possible objects. If objects are + // updated during a chunked list the version of the object that was present at the time the first list + // result was calculated is returned. + Limit int64 `json:"limit,omitempty"` + // The continue option should be set when retrieving more results from the server. Since this value is + // server defined, clients may only use the continue value from a previous query result with identical + // query parameters (except for the value of continue) and the server may reject a continue value it + // does not recognize. If the specified continue value is no longer valid whether due to expiration + // (generally five to fifteen minutes) or a configuration change on the server, the server will + // respond with a 410 ResourceExpired error together with a continue token. If the client needs a + // consistent list, it must restart their list without the continue field. Otherwise, the client may + // send another list request with the token received with the 410 error, the server will respond with + // a list starting from the next key, but from the latest snapshot, which is inconsistent from the + // previous list results - objects that are created, modified, or deleted after the first list request + // will be included in the response, as long as their keys are after the "next key". + // + // This field is not supported when watch is true. Clients may start a watch from the last + // resourceVersion value returned by the server and not miss any modifications. + Continue string `json:"continue,omitempty"` +} + +// resourceVersionMatch specifies how the resourceVersion parameter is applied. resourceVersionMatch +// may only be set if resourceVersion is also set. +// +// "NotOlderThan" matches data at least as new as the provided resourceVersion. +// "Exact" matches data at the exact resourceVersion provided. +// +// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for +// details. +type ResourceVersionMatch string + +const ( + // ResourceVersionMatchNotOlderThan matches data at least as new as the provided + // resourceVersion. + ResourceVersionMatchNotOlderThan ResourceVersionMatch = "NotOlderThan" + // ResourceVersionMatchExact matches data at the exact resourceVersion + // provided. + ResourceVersionMatchExact ResourceVersionMatch = "Exact" +) + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// GetOptions is the standard query options to the standard REST get call. +type GetOptions struct { + TypeMeta `json:",inline"` + // resourceVersion sets a constraint on what resource versions a request may be served from. + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + // + // Defaults to unset + // +optional + ResourceVersion string `json:"resourceVersion,omitempty"` + // +k8s:deprecated=includeUninitialized,protobuf=2 +} + +// DeletionPropagation decides if a deletion will propagate to the dependents of +// the object, and how the garbage collector will handle the propagation. +type DeletionPropagation string + +const ( + // Orphans the dependents. + DeletePropagationOrphan DeletionPropagation = "Orphan" + // Deletes the object from the key-value store, the garbage collector will + // delete the dependents in the background. + DeletePropagationBackground DeletionPropagation = "Background" + // The object exists in the key-value store until the garbage collector + // deletes all the dependents whose ownerReference.blockOwnerDeletion=true + // from the key-value store. API sever will put the "foregroundDeletion" + // finalizer on the object, and sets its deletionTimestamp. This policy is + // cascading, i.e., the dependents will be deleted with Foreground. + DeletePropagationForeground DeletionPropagation = "Foreground" +) + +const ( + // DryRunAll means to complete all processing stages, but don't + // persist changes to storage. + DryRunAll = "All" +) + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// DeleteOptions may be provided when deleting an API object. +type DeleteOptions struct { + TypeMeta `json:",inline"` + + // The duration in seconds before the object should be deleted. Value must be non-negative integer. + // The value zero indicates delete immediately. If this value is nil, the default grace period for the + // specified type will be used. + // Defaults to a per object value if not specified. zero means delete immediately. + // +optional + GracePeriodSeconds *int64 `json:"gracePeriodSeconds,omitempty"` + + // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be + // returned. + // +k8s:conversion-gen=false + // +optional + Preconditions *Preconditions `json:"preconditions,omitempty"` + + // Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. + // Should the dependent objects be orphaned. If true/false, the "orphan" + // finalizer will be added to/removed from the object's finalizers list. + // Either this field or PropagationPolicy may be set, but not both. + // +optional + OrphanDependents *bool `json:"orphanDependents,omitempty"` + + // Whether and how garbage collection will be performed. + // Either this field or OrphanDependents may be set, but not both. + // The default policy is decided by the existing finalizer set in the + // metadata.finalizers and the resource-specific default policy. + // Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - + // allow the garbage collector to delete the dependents in the background; + // 'Foreground' - a cascading policy that deletes all dependents in the + // foreground. + // +optional + PropagationPolicy *DeletionPropagation `json:"propagationPolicy,omitempty"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CreateOptions may be provided when creating an API object. +type CreateOptions struct { + TypeMeta `json:",inline"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty"` + // +k8s:deprecated=includeUninitialized,protobuf=2 + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. + // +optional + FieldManager string `json:"fieldManager,omitempty"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PatchOptions may be provided when patching an API object. +// PatchOptions is meant to be a superset of UpdateOptions. +type PatchOptions struct { + TypeMeta `json:",inline"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty"` + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. Force + // flag must be unset for non-apply patch requests. + // +optional + Force *bool `json:"force,omitempty"` + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. This + // field is required for apply requests + // (application/apply-patch) but optional for non-apply patch + // types (JsonPatch, MergePatch, StrategicMergePatch). + // +optional + FieldManager string `json:"fieldManager,omitempty"` +} + +// ApplyOptions may be provided when applying an API object. +// FieldManager is required for apply requests. +// ApplyOptions is equivalent to PatchOptions. It is provided as a convenience with documentation +// that speaks specifically to how the options fields relate to apply. +type ApplyOptions struct { + TypeMeta `json:",inline"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty"` + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. + Force bool `json:"force"` + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. This + // field is required. + FieldManager string `json:"fieldManager"` +} + +func (o ApplyOptions) ToPatchOptions() PatchOptions { + return PatchOptions{DryRun: o.DryRun, Force: &o.Force, FieldManager: o.FieldManager} +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// UpdateOptions may be provided when updating an API object. +// All fields in UpdateOptions should also be present in PatchOptions. +type UpdateOptions struct { + TypeMeta `json:",inline"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty"` + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. + // +optional + FieldManager string `json:"fieldManager,omitempty"` +} + +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +type Preconditions struct { + // Specifies the target UID. + // +optional + UID *types.UID `json:"uid,omitempty"` + // Specifies the target ResourceVersion + // +optional + ResourceVersion *string `json:"resourceVersion,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Status is a return value for calls that don't return other objects. +type Status struct { + TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + ListMeta `json:"metadata"` + + // Status of the operation. + // One of: "Success" or "Failure". + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status string `json:"status,omitempty"` + // A human-readable description of the status of this operation. + // +optional + Message string `json:"message,omitempty"` + // A machine-readable description of why this operation is in the + // "Failure" status. If this value is empty there + // is no information available. A Reason clarifies an HTTP status + // code but does not override it. + // +optional + Reason StatusReason `json:"reason,omitempty"` + // Extended data associated with the reason. Each reason may define its + // own extended details. This field is optional and the data returned + // is not guaranteed to conform to any schema except that defined by + // the reason type. + // +optional + Details *StatusDetails `json:"details,omitempty"` + // Suggested HTTP return code for this status, 0 if not set. + // +optional + Code int32 `json:"code,omitempty"` +} + +// StatusDetails is a set of additional properties that MAY be set by the +// server to provide additional information about a response. The Reason +// field of a Status object defines what attributes will be set. Clients +// must ignore fields that do not match the defined type of each attribute, +// and should assume that any attribute may be empty, invalid, or under +// defined. +type StatusDetails struct { + // The name attribute of the resource associated with the status StatusReason + // (when there is a single name which can be described). + // +optional + Name string `json:"name,omitempty"` + // The group attribute of the resource associated with the status StatusReason. + // +optional + Group string `json:"group,omitempty"` + // The kind attribute of the resource associated with the status StatusReason. + // On some operations may differ from the requested resource Kind. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + Kind string `json:"kind,omitempty"` + // UID of the resource. + // (when there is a single resource which can be described). + // More info: http://kubernetes.io/docs/user-guide/identifiers#uids + // +optional + UID types.UID `json:"uid,omitempty"` + // The Causes array includes more details associated with the StatusReason + // failure. Not all StatusReasons may provide detailed causes. + // +optional + Causes []StatusCause `json:"causes,omitempty"` + // If specified, the time in seconds before the operation should be retried. Some errors may indicate + // the client must take an alternate action - for those errors this field may indicate how long to wait + // before taking the alternate action. + // +optional + RetryAfterSeconds int32 `json:"retryAfterSeconds,omitempty"` +} + +// Values of Status.Status +const ( + StatusSuccess = "Success" + StatusFailure = "Failure" +) + +// StatusReason is an enumeration of possible failure causes. Each StatusReason +// must map to a single HTTP status code, but multiple reasons may map +// to the same HTTP status code. +// TODO: move to apiserver +type StatusReason string + +const ( + // StatusReasonUnknown means the server has declined to indicate a specific reason. + // The details field may contain other information about this error. + // Status code 500. + StatusReasonUnknown StatusReason = "" + + // StatusReasonUnauthorized means the server can be reached and understood the request, but requires + // the user to present appropriate authorization credentials (identified by the WWW-Authenticate header) + // in order for the action to be completed. If the user has specified credentials on the request, the + // server considers them insufficient. + // Status code 401 + StatusReasonUnauthorized StatusReason = "Unauthorized" + + // StatusReasonForbidden means the server can be reached and understood the request, but refuses + // to take any further action. It is the result of the server being configured to deny access for some reason + // to the requested resource by the client. + // Details (optional): + // "kind" string - the kind attribute of the forbidden resource + // on some operations may differ from the requested + // resource. + // "id" string - the identifier of the forbidden resource + // Status code 403 + StatusReasonForbidden StatusReason = "Forbidden" + + // StatusReasonNotFound means one or more resources required for this operation + // could not be found. + // Details (optional): + // "kind" string - the kind attribute of the missing resource + // on some operations may differ from the requested + // resource. + // "id" string - the identifier of the missing resource + // Status code 404 + StatusReasonNotFound StatusReason = "NotFound" + + // StatusReasonAlreadyExists means the resource you are creating already exists. + // Details (optional): + // "kind" string - the kind attribute of the conflicting resource + // "id" string - the identifier of the conflicting resource + // Status code 409 + StatusReasonAlreadyExists StatusReason = "AlreadyExists" + + // StatusReasonConflict means the requested operation cannot be completed + // due to a conflict in the operation. The client may need to alter the + // request. Each resource may define custom details that indicate the + // nature of the conflict. + // Status code 409 + StatusReasonConflict StatusReason = "Conflict" + + // StatusReasonGone means the item is no longer available at the server and no + // forwarding address is known. + // Status code 410 + StatusReasonGone StatusReason = "Gone" + + // StatusReasonInvalid means the requested create or update operation cannot be + // completed due to invalid data provided as part of the request. The client may + // need to alter the request. When set, the client may use the StatusDetails + // message field as a summary of the issues encountered. + // Details (optional): + // "kind" string - the kind attribute of the invalid resource + // "id" string - the identifier of the invalid resource + // "causes" - one or more StatusCause entries indicating the data in the + // provided resource that was invalid. The code, message, and + // field attributes will be set. + // Status code 422 + StatusReasonInvalid StatusReason = "Invalid" + + // StatusReasonServerTimeout means the server can be reached and understood the request, + // but cannot complete the action in a reasonable time. The client should retry the request. + // This is may be due to temporary server load or a transient communication issue with + // another server. Status code 500 is used because the HTTP spec provides no suitable + // server-requested client retry and the 5xx class represents actionable errors. + // Details (optional): + // "kind" string - the kind attribute of the resource being acted on. + // "id" string - the operation that is being attempted. + // "retryAfterSeconds" int32 - the number of seconds before the operation should be retried + // Status code 500 + StatusReasonServerTimeout StatusReason = "ServerTimeout" + + // StatusReasonTimeout means that the request could not be completed within the given time. + // Clients can get this response only when they specified a timeout param in the request, + // or if the server cannot complete the operation within a reasonable amount of time. + // The request might succeed with an increased value of timeout param. The client *should* + // wait at least the number of seconds specified by the retryAfterSeconds field. + // Details (optional): + // "retryAfterSeconds" int32 - the number of seconds before the operation should be retried + // Status code 504 + StatusReasonTimeout StatusReason = "Timeout" + + // StatusReasonTooManyRequests means the server experienced too many requests within a + // given window and that the client must wait to perform the action again. A client may + // always retry the request that led to this error, although the client should wait at least + // the number of seconds specified by the retryAfterSeconds field. + // Details (optional): + // "retryAfterSeconds" int32 - the number of seconds before the operation should be retried + // Status code 429 + StatusReasonTooManyRequests StatusReason = "TooManyRequests" + + // StatusReasonBadRequest means that the request itself was invalid, because the request + // doesn't make any sense, for example deleting a read-only object. This is different than + // StatusReasonInvalid above which indicates that the API call could possibly succeed, but the + // data was invalid. API calls that return BadRequest can never succeed. + // Status code 400 + StatusReasonBadRequest StatusReason = "BadRequest" + + // StatusReasonMethodNotAllowed means that the action the client attempted to perform on the + // resource was not supported by the code - for instance, attempting to delete a resource that + // can only be created. API calls that return MethodNotAllowed can never succeed. + // Status code 405 + StatusReasonMethodNotAllowed StatusReason = "MethodNotAllowed" + + // StatusReasonNotAcceptable means that the accept types indicated by the client were not acceptable + // to the server - for instance, attempting to receive protobuf for a resource that supports only json and yaml. + // API calls that return NotAcceptable can never succeed. + // Status code 406 + StatusReasonNotAcceptable StatusReason = "NotAcceptable" + + // StatusReasonRequestEntityTooLarge means that the request entity is too large. + // Status code 413 + StatusReasonRequestEntityTooLarge StatusReason = "RequestEntityTooLarge" + + // StatusReasonUnsupportedMediaType means that the content type sent by the client is not acceptable + // to the server - for instance, attempting to send protobuf for a resource that supports only json and yaml. + // API calls that return UnsupportedMediaType can never succeed. + // Status code 415 + StatusReasonUnsupportedMediaType StatusReason = "UnsupportedMediaType" + + // StatusReasonInternalError indicates that an internal error occurred, it is unexpected + // and the outcome of the call is unknown. + // Details (optional): + // "causes" - The original error + // Status code 500 + StatusReasonInternalError StatusReason = "InternalError" + + // StatusReasonExpired indicates that the request is invalid because the content you are requesting + // has expired and is no longer available. It is typically associated with watches that can't be + // serviced. + // Status code 410 (gone) + StatusReasonExpired StatusReason = "Expired" + + // StatusReasonServiceUnavailable means that the request itself was valid, + // but the requested service is unavailable at this time. + // Retrying the request after some time might succeed. + // Status code 503 + StatusReasonServiceUnavailable StatusReason = "ServiceUnavailable" +) + +// StatusCause provides more information about an api.Status failure, including +// cases when multiple errors are encountered. +type StatusCause struct { + // A machine-readable description of the cause of the error. If this value is + // empty there is no information available. + // +optional + Type CauseType `json:"reason,omitempty"` + // A human-readable description of the cause of the error. This field may be + // presented as-is to a reader. + // +optional + Message string `json:"message,omitempty"` + // The field of the resource that has caused this error, as named by its JSON + // serialization. May include dot and postfix notation for nested attributes. + // Arrays are zero-indexed. Fields may appear more than once in an array of + // causes due to fields having multiple errors. + // Optional. + // + // Examples: + // "name" - the field "name" on the current resource + // "items[0].name" - the field "name" on the first array entry in "items" + // +optional + Field string `json:"field,omitempty"` +} + +// CauseType is a machine readable value providing more detail about what +// occurred in a status response. An operation may have multiple causes for a +// status (whether Failure or Success). +type CauseType string + +const ( + // CauseTypeFieldValueNotFound is used to report failure to find a requested value + // (e.g. looking up an ID). + CauseTypeFieldValueNotFound CauseType = "FieldValueNotFound" + // CauseTypeFieldValueRequired is used to report required values that are not + // provided (e.g. empty strings, null values, or empty arrays). + CauseTypeFieldValueRequired CauseType = "FieldValueRequired" + // CauseTypeFieldValueDuplicate is used to report collisions of values that must be + // unique (e.g. unique IDs). + CauseTypeFieldValueDuplicate CauseType = "FieldValueDuplicate" + // CauseTypeFieldValueInvalid is used to report malformed values (e.g. failed regex + // match). + CauseTypeFieldValueInvalid CauseType = "FieldValueInvalid" + // CauseTypeFieldValueNotSupported is used to report valid (as per formatting rules) + // values that can not be handled (e.g. an enumerated string). + CauseTypeFieldValueNotSupported CauseType = "FieldValueNotSupported" + // CauseTypeUnexpectedServerResponse is used to report when the server responded to the client + // without the expected return type. The presence of this cause indicates the error may be + // due to an intervening proxy or the server software malfunctioning. + CauseTypeUnexpectedServerResponse CauseType = "UnexpectedServerResponse" + // FieldManagerConflict is used to report when another client claims to manage this field, + // It should only be returned for a request using server-side apply. + CauseTypeFieldManagerConflict CauseType = "FieldManagerConflict" + // CauseTypeResourceVersionTooLarge is used to report that the requested resource version + // is newer than the data observed by the API server, so the request cannot be served. + CauseTypeResourceVersionTooLarge CauseType = "ResourceVersionTooLarge" +) + +// List holds a list of objects, which may not be known by the server. +type List struct { + TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + ListMeta `json:"metadata"` + + // List of objects + Items []any `json:"items"` +} + +// APIVersions lists the versions that are available, to allow clients to +// discover the API at /api, which is the root path of the legacy v1 API. +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type APIVersions struct { + TypeMeta `json:",inline"` + // versions are the api versions that are available. + Versions []string `json:"versions"` + // a map of client CIDR to server address that is serving this group. + // This is to help clients reach servers in the most network-efficient way possible. + // Clients can use the appropriate server address as per the CIDR that they match. + // In case of multiple matches, clients should use the longest matching CIDR. + // The server returns only those CIDRs that it thinks that the client can match. + // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. + // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. + ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// APIGroupList is a list of APIGroup, to allow clients to discover the API at +// /apis. +type APIGroupList struct { + TypeMeta `json:",inline"` + // groups is a list of APIGroup. + Groups []APIGroup `json:"groups"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// APIGroup contains the name, the supported versions, and the preferred version +// of a group. +type APIGroup struct { + TypeMeta `json:",inline"` + // name is the name of the group. + Name string `json:"name"` + // versions are the versions supported in this group. + Versions []GroupVersionForDiscovery `json:"versions"` + // preferredVersion is the version preferred by the API server, which + // probably is the storage version. + // +optional + PreferredVersion GroupVersionForDiscovery `json:"preferredVersion"` + // a map of client CIDR to server address that is serving this group. + // This is to help clients reach servers in the most network-efficient way possible. + // Clients can use the appropriate server address as per the CIDR that they match. + // In case of multiple matches, clients should use the longest matching CIDR. + // The server returns only those CIDRs that it thinks that the client can match. + // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. + // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. + // +optional + ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs,omitempty"` +} + +// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match. +type ServerAddressByClientCIDR struct { + // The CIDR with which clients can match their IP to figure out the server address that they should use. + ClientCIDR string `json:"clientCIDR"` + // Address of this server, suitable for a client that matches the above CIDR. + // This can be a hostname, hostname:port, IP or IP:port. + ServerAddress string `json:"serverAddress"` +} + +// GroupVersion contains the "group/version" and "version" string of a version. +// It is made a struct to keep extensibility. +type GroupVersionForDiscovery struct { + // groupVersion specifies the API group and version in the form "group/version" + GroupVersion string `json:"groupVersion"` + // version specifies the version in the form of "version". This is to save + // the clients the trouble of splitting the GroupVersion. + Version string `json:"version"` +} + +// APIResource specifies the name of a resource and whether it is namespaced. +type APIResource struct { + // name is the plural name of the resource. + Name string `json:"name"` + // singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. + // The singularName is more correct for reporting status on a single item and both singular and plural are allowed + // from the kubectl CLI interface. + SingularName string `json:"singularName"` + // namespaced indicates if a resource is namespaced or not. + Namespaced bool `json:"namespaced"` + // group is the preferred group of the resource. Empty implies the group of the containing resource list. + // For subresources, this may have a different value, for example: Scale". + Group string `json:"group,omitempty"` + // version is the preferred version of the resource. Empty implies the version of the containing resource list + // For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". + Version string `json:"version,omitempty"` + // kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') + Kind string `json:"kind"` + // verbs is a list of supported kube verbs (this includes get, list, watch, create, + // update, patch, delete, deletecollection, and proxy) + Verbs Verbs `json:"verbs"` + // shortNames is a list of suggested short names of the resource. + ShortNames []string `json:"shortNames,omitempty"` + // categories is a list of the grouped resources this resource belongs to (e.g. 'all') + Categories []string `json:"categories,omitempty"` + // The hash value of the storage version, the version this resource is + // converted to when written to the data store. Value must be treated + // as opaque by clients. Only equality comparison on the value is valid. + // This is an alpha feature and may change or be removed in the future. + // The field is populated by the apiserver only if the + // StorageVersionHash feature gate is enabled. + // This field will remain optional even if it graduates. + // +optional + StorageVersionHash string `json:"storageVersionHash,omitempty"` +} + +// Verbs masks the value so protobuf can generate +// +// +protobuf.nullable=true +// +protobuf.options.(gogoproto.goproto_stringer)=false +type Verbs []string + +func (vs Verbs) String() string { + return fmt.Sprintf("%v", []string(vs)) +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// APIResourceList is a list of APIResource, it is used to expose the name of the +// resources supported in a specific group and version, and if the resource +// is namespaced. +type APIResourceList struct { + TypeMeta `json:",inline"` + // groupVersion is the group and version this APIResourceList is for. + GroupVersion string `json:"groupVersion"` + // resources contains the name of the resources and if they are namespaced. + APIResources []APIResource `json:"resources"` +} + +// RootPaths lists the paths available at root. +// For example: "/healthz", "/apis". +type RootPaths struct { + // paths are the paths available at root. + Paths []string `json:"paths"` +} + +// TODO: remove me when watch is refactored +func LabelSelectorQueryParam(_ string) string { + return "labelSelector" +} + +// TODO: remove me when watch is refactored +func FieldSelectorQueryParam(_ string) string { + return "fieldSelector" +} + +// String returns available api versions as a human-friendly version string. +func (apiVersions APIVersions) String() string { + return strings.Join(apiVersions.Versions, ",") +} + +func (apiVersions APIVersions) GoString() string { + return apiVersions.String() +} + +// Patch is provided to give a concrete name and type to the Kubernetes PATCH request body. +type Patch struct{} + +// Note: +// There are two different styles of label selectors used in versioned types: +// an older style which is represented as just a string in versioned types, and a +// newer style that is structured. LabelSelector is an internal representation for the +// latter style. + +// A label selector is a label query over a set of resources. The result of matchLabels and +// matchExpressions are ANDed. An empty label selector matches all objects. A null +// label selector matches no objects. +// +structType=atomic +type LabelSelector struct { + // matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + // map is equivalent to an element of matchExpressions, whose key field is "key", the + // operator is "In", and the values array contains only "value". The requirements are ANDed. + // +optional + MatchLabels map[string]string `json:"matchLabels,omitempty"` + // matchExpressions is a list of label selector requirements. The requirements are ANDed. + // +optional + MatchExpressions []LabelSelectorRequirement `json:"matchExpressions,omitempty"` +} + +// A label selector requirement is a selector that contains values, a key, and an operator that +// relates the key and values. +type LabelSelectorRequirement struct { + // key is the label key that the selector applies to. + // +patchMergeKey=key + // +patchStrategy=merge + Key string `json:"key" patchStrategy:"merge" patchMergeKey:"key"` + // operator represents a key's relationship to a set of values. + // Valid operators are In, NotIn, Exists and DoesNotExist. + Operator LabelSelectorOperator `json:"operator"` + // values is an array of string values. If the operator is In or NotIn, + // the values array must be non-empty. If the operator is Exists or DoesNotExist, + // the values array must be empty. This array is replaced during a strategic + // merge patch. + // +optional + Values []string `json:"values,omitempty"` +} + +// A label selector operator is the set of operators that can be used in a selector requirement. +type LabelSelectorOperator string + +const ( + LabelSelectorOpIn LabelSelectorOperator = "In" + LabelSelectorOpNotIn LabelSelectorOperator = "NotIn" + LabelSelectorOpExists LabelSelectorOperator = "Exists" + LabelSelectorOpDoesNotExist LabelSelectorOperator = "DoesNotExist" +) + +// ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource +// that the fieldset applies to. +type ManagedFieldsEntry struct { + // Manager is an identifier of the workflow managing these fields. + Manager string `json:"manager,omitempty"` + // Operation is the type of operation which lead to this ManagedFieldsEntry being created. + // The only valid values for this field are 'Apply' and 'Update'. + Operation ManagedFieldsOperationType `json:"operation,omitempty"` + // APIVersion defines the version of this resource that this field set + // applies to. The format is "group/version" just like the top-level + // APIVersion field. It is necessary to track the version of a field + // set because it cannot be automatically converted. + APIVersion string `json:"apiVersion,omitempty"` + // Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply' + // +optional + Time *Time `json:"time,omitempty"` + + // Fields is tombstoned to show why 5 is a reserved protobuf tag. + // Fields *Fields `json:"fields,omitempty"` + + // FieldsType is the discriminator for the different fields format and version. + // There is currently only one possible value: "FieldsV1" + FieldsType string `json:"fieldsType,omitempty"` + // FieldsV1 holds the first JSON version format as described in the "FieldsV1" type. + // +optional + FieldsV1 *FieldsV1 `json:"fieldsV1,omitempty"` + + // Subresource is the name of the subresource used to update that object, or + // empty string if the object was updated through the main resource. The + // value of this field is used to distinguish between managers, even if they + // share the same name. For example, a status update will be distinct from a + // regular update using the same manager name. + // Note that the APIVersion field is not related to the Subresource field and + // it always corresponds to the version of the main resource. + Subresource string `json:"subresource,omitempty"` +} + +// ManagedFieldsOperationType is the type of operation which lead to a ManagedFieldsEntry being created. +type ManagedFieldsOperationType string + +const ( + ManagedFieldsOperationApply ManagedFieldsOperationType = "Apply" + ManagedFieldsOperationUpdate ManagedFieldsOperationType = "Update" +) + +// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format. +// +// Each key is either a '.' representing the field itself, and will always map to an empty set, +// or a string representing a sub-field or item. The string will follow one of these four formats: +// 'f:', where is the name of a field in a struct, or key in a map +// 'v:', where is the exact json formatted value of a list item +// 'i:', where is position of an item in a list +// 'k:', where is a map of a list item's key fields to their unique values +// If a key maps to an empty Fields value, the field that key represents is part of the set. +// +// The exact format is defined in sigs.k8s.io/structured-merge-diff +// +protobuf.options.(gogoproto.goproto_stringer)=false +type FieldsV1 struct { + // Raw is the underlying serialization of this object. + Raw []byte `json:"-"` +} + +func (f FieldsV1) String() string { + return string(f.Raw) +} + +type ConditionStatus string + +// These are valid condition statuses. "ConditionTrue" means a resource is in the condition. +// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes +// can't decide if a resource is in the condition or not. In the future, we could add other +// intermediate conditions, e.g. ConditionDegraded. +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" + ConditionUnknown ConditionStatus = "Unknown" +) + +// IncludeObjectPolicy controls which portion of the object is returned with a Table. +type IncludeObjectPolicy string + +const ( + // IncludeNone returns no object. + IncludeNone IncludeObjectPolicy = "None" + // IncludeMetadata serializes the object containing only its metadata field. + IncludeMetadata IncludeObjectPolicy = "Metadata" + // IncludeObject contains the full object. + IncludeObject IncludeObjectPolicy = "Object" +) + +// TableOptions are used when a Table is requested by the caller. +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type TableOptions struct { + TypeMeta `json:",inline"` + + // NoHeaders is only exposed for internal callers. It is not included in our OpenAPI definitions + // and may be removed as a field in a future release. + NoHeaders bool `json:"-"` + + // includeObject decides whether to include each object along with its columnar information. + // Specifying "None" will return no object, specifying "Object" will return the full object contents, and + // specifying "Metadata" (the default) will return the object's metadata in the PartialObjectMetadata kind + // in version v1beta1 of the meta.k8s.io API group. + IncludeObject IncludeObjectPolicy `json:"includeObject,omitempty"` +} + +// PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients +// to get access to a particular ObjectMeta schema without knowing the details of the version. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type PartialObjectMetadata struct { + TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + ObjectMeta `json:"metadata"` +} + +// PartialObjectMetadataList contains a list of objects containing only their metadata +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type PartialObjectMetadataList struct { + TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + ListMeta `json:"metadata"` + + // items contains each of the included items. + Items []PartialObjectMetadata `json:"items"` +} + +// Condition contains details for one aspect of the current state of this API Resource. +// --- +// This struct is intended for direct use as an array at the field path .status.conditions. For example, +// +// type FooStatus struct{ +// // Represents the observations of a foo's current state. +// // Known .status.conditions.type are: "Available", "Progressing", and "Degraded" +// // +patchMergeKey=type +// // +patchStrategy=merge +// // +listType=map +// // +listMapKey=type +// Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +// +// // other fields +// } +type Condition struct { + // type of condition in CamelCase or in foo.example.com/CamelCase. + // --- + // Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + // useful (see .node.status.conditions), the ability to deconflict is important. + // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$` + // +kubebuilder:validation:MaxLength=316 + Type string `json:"type"` + // status of the condition, one of True, False, Unknown. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum=True;False;Unknown + Status ConditionStatus `json:"status"` + // observedGeneration represents the .metadata.generation that the condition was set based upon. + // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + // with respect to the current state of the instance. + // +optional + // +kubebuilder:validation:Minimum=0 + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // lastTransitionTime is the last time the condition transitioned from one status to another. + // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=date-time + LastTransitionTime Time `json:"lastTransitionTime"` + // reason contains a programmatic identifier indicating the reason for the condition's last transition. + // Producers of specific condition types may define expected values and meanings for this field, + // and whether the values are considered a guaranteed API. + // The value should be a CamelCase string. + // This field may not be empty. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=1024 + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` + Reason string `json:"reason"` + // message is a human readable message indicating details about the transition. + // This may be an empty string. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=32768 + Message string `json:"message"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/types/uid.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/types/uid.go new file mode 100644 index 00000000..86933922 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/types/uid.go @@ -0,0 +1,22 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +// UID is a type that holds unique ID values, including UUIDs. Because we +// don't ONLY use UUIDs, this is an alias to string. Being a type captures +// intent and helps make sure that UIDs and names do not get conflated. +type UID string diff --git a/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/util/intstr/intstr.go b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/util/intstr/intstr.go new file mode 100644 index 00000000..29872c22 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/k8s.io/apimachinery/pkg/util/intstr/intstr.go @@ -0,0 +1,113 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package intstr + +import ( + "encoding/json" + "fmt" + "strconv" +) + +// IntOrString is a type that can hold an int32 or a string. When used in +// JSON or YAML marshalling and unmarshalling, it produces or consumes the +// inner type. This allows you to have, for example, a JSON field that can +// accept a name or number. +// TODO: Rename to Int32OrString +// +// +protobuf=true +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:openapi-gen=true +type IntOrString struct { + Type Type + IntVal int32 + StrVal string +} + +// Type represents the stored type of IntOrString. +type Type int64 + +const ( + Int Type = iota // The IntOrString holds an int. + String // The IntOrString holds a string. +) + +// FromInt creates an IntOrString object with an int32 value. It is +// your responsibility not to call this method with a value greater +// than int32. +// TODO: convert to (val int32) +func FromInt(val int) IntOrString { + return IntOrString{Type: Int, IntVal: int32(val)} +} + +// FromString creates an IntOrString object with a string value. +func FromString(val string) IntOrString { + return IntOrString{Type: String, StrVal: val} +} + +// Parse the given string and try to convert it to an integer before +// setting it as a string value. +func Parse(val string) IntOrString { + i, err := strconv.Atoi(val) + if err != nil { + return FromString(val) + } + return FromInt(i) +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (intstr *IntOrString) UnmarshalJSON(value []byte) error { + if value[0] == '"' { + intstr.Type = String + return json.Unmarshal(value, &intstr.StrVal) + } + intstr.Type = Int + return json.Unmarshal(value, &intstr.IntVal) +} + +// String returns the string value, or the Itoa of the int value. +func (intstr *IntOrString) String() string { + if intstr == nil { + return "" + } + if intstr.Type == String { + return intstr.StrVal + } + return strconv.Itoa(intstr.IntValue()) +} + +// IntValue returns the IntVal if type Int, or if +// it is a String, will attempt a conversion to int, +// returning 0 if a parsing error occurs. +func (intstr *IntOrString) IntValue() int { + if intstr.Type == String { + i, _ := strconv.Atoi(intstr.StrVal) + return i + } + return int(intstr.IntVal) +} + +// MarshalJSON implements the json.Marshaller interface. +func (intstr IntOrString) MarshalJSON() ([]byte, error) { + switch intstr.Type { + case Int: + return json.Marshal(intstr.IntVal) + case String: + return json.Marshal(intstr.StrVal) + default: + return []byte{}, fmt.Errorf("impossible IntOrString.Type") + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/lookup/lookup.go b/vendor/github.com/containers/podman/v5/pkg/lookup/lookup.go new file mode 100644 index 00000000..4444182a --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/lookup/lookup.go @@ -0,0 +1,171 @@ +package lookup + +import ( + "os" + "strconv" + + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/moby/sys/user" + "github.com/sirupsen/logrus" +) + +const ( + etcpasswd = "/etc/passwd" + etcgroup = "/etc/group" +) + +// Overrides allows you to override defaults in GetUserGroupInfo. +type Overrides struct { + DefaultUser *user.ExecUser + ContainerEtcPasswdPath string + ContainerEtcGroupPath string +} + +// GetUserGroupInfo takes string forms of the container's mount path and the container user and +// returns an ExecUser with uid, gid, sgids, and home. And override can be provided for defaults. +func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) (*user.ExecUser, error) { + var ( + passwdDest, groupDest string + defaultExecUser *user.ExecUser + err error + ) + + if override != nil { + // Check for an override /etc/passwd path + if override.ContainerEtcPasswdPath != "" { + passwdDest = override.ContainerEtcPasswdPath + } + // Check for an override for /etc/group path + if override.ContainerEtcGroupPath != "" { + groupDest = override.ContainerEtcGroupPath + } + } + + if passwdDest == "" { + // Make sure the /etc/passwd destination is not a symlink to something naughty + if passwdDest, err = securejoin.SecureJoin(containerMount, etcpasswd); err != nil { + logrus.Debug(err) + return nil, err + } + } + if groupDest == "" { + // Make sure the /etc/group destination is not a symlink to something naughty + if groupDest, err = securejoin.SecureJoin(containerMount, etcgroup); err != nil { + logrus.Debug(err) + return nil, err + } + } + + // Check for an override default user + if override != nil && override.DefaultUser != nil { + defaultExecUser = override.DefaultUser + } else { + // Define a default container user + // defaultExecUser = &user.ExecUser{ + // Uid: 0, + // Gid: 0, + // Home: "/", + defaultExecUser = nil + } + + return user.GetExecUserPath(containerUser, defaultExecUser, passwdDest, groupDest) +} + +// GetContainerGroups uses securejoin to get a list of numerical groupids from a container. Per the runc +// function it calls: If a group name cannot be found, an error will be returned. If a group id cannot be found, +// or the given group data is nil, the id will be returned as-is provided it is in the legal range. +func GetContainerGroups(groups []string, containerMount string, override *Overrides) ([]uint32, error) { + var ( + groupDest string + err error + ) + + groupPath := etcgroup + if override != nil && override.ContainerEtcGroupPath != "" { + groupPath = override.ContainerEtcGroupPath + } + + if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { + logrus.Debug(err) + return nil, err + } + + gids, err := user.GetAdditionalGroupsPath(groups, groupDest) + if err != nil { + return nil, err + } + uintgids := make([]uint32, 0, len(gids)) + // For libpod, we want []uint32s + for _, gid := range gids { + uintgids = append(uintgids, uint32(gid)) + } + return uintgids, nil +} + +// GetUser takes a containermount path and user name or ID and returns +// a matching User structure from /etc/passwd. If it cannot locate a user +// with the provided information, an ErrNoPasswdEntries is returned. +// When the provided user name was an ID, a User structure with Uid +// set is returned along with ErrNoPasswdEntries. +func GetUser(containerMount, userIDorName string) (*user.User, error) { + var inputIsName bool + uid, err := strconv.Atoi(userIDorName) + if err != nil { + inputIsName = true + } + passwdDest, err := securejoin.SecureJoin(containerMount, etcpasswd) + if err != nil { + return nil, err + } + users, err := user.ParsePasswdFileFilter(passwdDest, func(u user.User) bool { + if inputIsName { + return u.Name == userIDorName + } + return u.Uid == uid + }) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + if len(users) > 0 { + return &users[0], nil + } + if !inputIsName { + return &user.User{Uid: uid}, user.ErrNoPasswdEntries + } + return nil, user.ErrNoPasswdEntries +} + +// GetGroup takes a containermount path and a group name or ID and returns +// a match Group struct from /etc/group. If it cannot locate a group, +// an ErrNoGroupEntries error is returned. When the provided group name +// was an ID, a Group structure with Gid set is returned along with +// ErrNoGroupEntries. +func GetGroup(containerMount, groupIDorName string) (*user.Group, error) { + var inputIsName bool + gid, err := strconv.Atoi(groupIDorName) + if err != nil { + inputIsName = true + } + + groupDest, err := securejoin.SecureJoin(containerMount, etcgroup) + if err != nil { + return nil, err + } + + groups, err := user.ParseGroupFileFilter(groupDest, func(g user.Group) bool { + if inputIsName { + return g.Name == groupIDorName + } + return g.Gid == gid + }) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + if len(groups) > 0 { + return &groups[0], nil + } + if !inputIsName { + return &user.Group{Gid: gid}, user.ErrNoGroupEntries + } + return nil, user.ErrNoGroupEntries +} diff --git a/vendor/github.com/containers/podman/v5/pkg/namespaces/namespaces.go b/vendor/github.com/containers/podman/v5/pkg/namespaces/namespaces.go new file mode 100644 index 00000000..ca2c8ed3 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/namespaces/namespaces.go @@ -0,0 +1,234 @@ +package namespaces + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + bridgeType = "bridge" + containerType = "container" + defaultType = "default" + hostType = "host" + noneType = "none" + nsType = "ns" + podType = "pod" + privateType = "private" + shareableType = "shareable" + slirpType = "slirp4netns" + pastaType = "pasta" +) + +// KeepIDUserNsOptions defines how to create a user namespace using keep-id. +type KeepIDUserNsOptions struct { + // UID is the target uid in the user namespace. + UID *uint32 + // GID is the target uid in the user namespace. + GID *uint32 + // MaxSize is the maximum size of the user namespace. + MaxSize *uint32 +} + +// UsernsMode represents userns mode in the container. +type UsernsMode string + +// IsHost indicates whether the container uses the host's userns. +func (n UsernsMode) IsHost() bool { + return n == hostType +} + +// IsKeepID indicates whether container uses a mapping where the (uid, gid) on the host is kept inside of the namespace. +func (n UsernsMode) IsKeepID() bool { + parts := strings.Split(string(n), ":") + return parts[0] == "keep-id" +} + +// IsNoMap indicates whether container uses a mapping where the (uid, gid) on the host is not present in the namespace. +func (n UsernsMode) IsNoMap() bool { + return n == "nomap" +} + +// IsAuto indicates whether container uses the "auto" userns mode. +func (n UsernsMode) IsAuto() bool { + parts := strings.Split(string(n), ":") + return parts[0] == "auto" +} + +// IsDefaultValue indicates whether the user namespace has the default value. +func (n UsernsMode) IsDefaultValue() bool { + return n == "" || n == defaultType +} + +// GetKeepIDOptions returns a KeepIDUserNsOptions with the settings to keepIDmatically set up +// a user namespace. +func (n UsernsMode) GetKeepIDOptions() (*KeepIDUserNsOptions, error) { + nsmode, nsopts, hasOpts := strings.Cut(string(n), ":") + if nsmode != "keep-id" { + return nil, fmt.Errorf("wrong user namespace mode") + } + options := KeepIDUserNsOptions{} + if !hasOpts { + return &options, nil + } + for o := range strings.SplitSeq(nsopts, ",") { + opt, val, hasVal := strings.Cut(o, "=") + if !hasVal { + return nil, fmt.Errorf("invalid option specified: %q", o) + } + switch opt { + case "uid": + s, err := strconv.ParseUint(val, 10, 32) + if err != nil { + return nil, err + } + v := uint32(s) + options.UID = &v + case "gid": + s, err := strconv.ParseUint(val, 10, 32) + if err != nil { + return nil, err + } + v := uint32(s) + options.GID = &v + case "size": + s, err := strconv.ParseUint(val, 10, 32) + if err != nil { + return nil, err + } + v := uint32(s) + options.MaxSize = &v + default: + return nil, fmt.Errorf("unknown option specified: %q", opt) + } + } + return &options, nil +} + +// IsPrivate indicates whether the container uses the a private userns. +func (n UsernsMode) IsPrivate() bool { + return !n.IsHost() && !n.IsContainer() +} + +// Valid indicates whether the userns is valid. +func (n UsernsMode) Valid() bool { + parts := strings.Split(string(n), ":") + switch mode := parts[0]; mode { + case "", privateType, hostType, "keep-id", nsType, "auto", "nomap": + case containerType: + if len(parts) != 2 || parts[1] == "" { + return false + } + default: + return false + } + return true +} + +// IsNS indicates a userns namespace passed in by path (ns:) +func (n UsernsMode) IsNS() bool { + return strings.HasPrefix(string(n), "ns:") +} + +// NS gets the path associated with a ns: userns ns +func (n UsernsMode) NS() string { + _, path, _ := strings.Cut(string(n), ":") + return path +} + +// IsContainer indicates whether container uses a container userns. +func (n UsernsMode) IsContainer() bool { + typ, _, hasName := strings.Cut(string(n), ":") + return hasName && typ == containerType +} + +// Container is the id of the container which network this container is connected to. +func (n UsernsMode) Container() string { + typ, name, hasName := strings.Cut(string(n), ":") + if hasName && typ == containerType { + return name + } + return "" +} + +// NetworkMode represents the container network stack. +type NetworkMode string + +// IsNone indicates whether container isn't using a network stack. +func (n NetworkMode) IsNone() bool { + return n == noneType +} + +// IsHost indicates whether the container uses the host's network stack. +func (n NetworkMode) IsHost() bool { + return n == hostType +} + +// IsDefault indicates whether container uses the default network stack. +func (n NetworkMode) IsDefault() bool { + return n == defaultType +} + +// IsPrivate indicates whether container uses its private network stack. +func (n NetworkMode) IsPrivate() bool { + return !n.IsHost() && !n.IsContainer() +} + +// IsContainer indicates whether container uses a container network stack. +func (n NetworkMode) IsContainer() bool { + typ, _, hasName := strings.Cut(string(n), ":") + return hasName && typ == containerType +} + +// Container is the id of the container which network this container is connected to. +func (n NetworkMode) Container() string { + typ, name, hasName := strings.Cut(string(n), ":") + if hasName && typ == containerType { + return name + } + return "" +} + +// UserDefined indicates user-created network +func (n NetworkMode) UserDefined() string { + if n.IsUserDefined() { + return string(n) + } + return "" +} + +// IsBridge indicates whether container uses the bridge network stack +func (n NetworkMode) IsBridge() bool { + return n == bridgeType +} + +// IsSlirp4netns indicates if we are running a rootless network stack +func (n NetworkMode) IsSlirp4netns() bool { + return n == slirpType || strings.HasPrefix(string(n), slirpType+":") +} + +// IsPasta indicates if we are running a rootless network stack using pasta +func (n NetworkMode) IsPasta() bool { + return n == pastaType || strings.HasPrefix(string(n), pastaType+":") +} + +// IsNS indicates a network namespace passed in by path (ns:) +func (n NetworkMode) IsNS() bool { + return strings.HasPrefix(string(n), nsType) +} + +// NS gets the path associated with a ns: network ns +func (n NetworkMode) NS() string { + _, path, _ := strings.Cut(string(n), ":") + return path +} + +// IsPod returns whether the network refers to pod networking +func (n NetworkMode) IsPod() bool { + return n == podType +} + +// IsUserDefined indicates user-created network +func (n NetworkMode) IsUserDefined() bool { + return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() && !n.IsSlirp4netns() && !n.IsPasta() && !n.IsNS() +} diff --git a/vendor/github.com/containers/podman/v5/pkg/parallel/parallel.go b/vendor/github.com/containers/podman/v5/pkg/parallel/parallel.go new file mode 100644 index 00000000..64dc77ef --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/parallel/parallel.go @@ -0,0 +1,64 @@ +package parallel + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/sirupsen/logrus" + "golang.org/x/sync/semaphore" +) + +var ( + // Semaphore to control thread creation and ensure numThreads is + // respected. + jobControl *semaphore.Weighted + // Lock to control changing the semaphore - we don't want to do it + // while anyone is using it. + jobControlLock sync.RWMutex +) + +// SetMaxThreads sets the number of threads that will be used for parallel jobs. +func SetMaxThreads(threads uint) error { + if threads == 0 { + return errors.New("must give a non-zero number of threads to execute with") + } + + jobControlLock.Lock() + defer jobControlLock.Unlock() + + jobControl = semaphore.NewWeighted(int64(threads)) + logrus.Infof("Setting parallel job count to %d", threads) + + return nil +} + +// Enqueue adds a single function to the parallel jobs queue. This function will +// be run when an unused thread is available. +// Returns a receive-only error channel that will return the error (if any) from +// the provided function fn when fn has finished executing. The channel will be +// closed after this. +func Enqueue(ctx context.Context, fn func() error) <-chan error { + retChan := make(chan error) + + go func() { + jobControlLock.RLock() + defer jobControlLock.RUnlock() + + defer close(retChan) + + if err := jobControl.Acquire(ctx, 1); err != nil { + retChan <- fmt.Errorf("acquiring job control semaphore: %w", err) + return + } + + err := fn() + + jobControl.Release(1) + + retChan <- err + }() + + return retChan +} diff --git a/vendor/github.com/containers/podman/v5/pkg/pidhandle/pidhandle.go b/vendor/github.com/containers/podman/v5/pkg/pidhandle/pidhandle.go new file mode 100644 index 00000000..db93dcc6 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/pidhandle/pidhandle.go @@ -0,0 +1,140 @@ +//go:build !windows + +// Package for handling processes and PIDs. +package pidhandle + +import ( + "strconv" + "strings" + + "github.com/shirou/gopsutil/v4/process" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// PIDHandle defines an interface for working with operating system processes +// in a reliable way. OS-specific implementations include additional logic +// to try to ensure that operations (e.g., sending signals) are performed +// on the exact same process that was originally referenced when the PIDHandle +// was created via NewPIDHandle or NewPIDHandleFromString. +// +// This prevents accidental interaction with a different process in scenarios +// where the original process has exited and its PID has been reused by +// the system for an unrelated process. +type PIDHandle interface { + // Returns the PID associated with this PIDHandle. + PID() int + // Releases the PIDHandle resources. + Close() error + // Sends the signal to process. + Kill(signal unix.Signal) error + // Returns true in case the process is still alive. + IsAlive() (bool, error) + // Returns a serialized representation of the PIDHandle. + // This string can be passed to NewPIDHandleFromString to recreate + // a PIDHandle that reliably refers to the same process as the original. + String() (string, error) +} + +// The pidData value used when no process with this PID exists when creating +// the PIDHandle. +const noSuchProcessID = "no-proc" + +// The pidData prefix used when only the process start time (creation time) +// is supported when creating the PIDHandle to uniquely identify the process. +const startTimePrefix = "start-time:" + +type pidHandle struct { + pid int + pidData string +} + +// Returns the PID. +func (h *pidHandle) PID() int { + return h.pid +} + +// Close releases the PIDHandle resource. +func (h *pidHandle) Close() error { + // No resources for the default PIDHandle implementation. + return nil +} + +// Sends the signal to process. +func (h *pidHandle) Kill(signal unix.Signal) error { + if h.pidData == noSuchProcessID { + // The process did not exist when we created the PIDHandle, so return + // ESRCH error. + return unix.ESRCH + } + + // Get the start-time of the process and check if it is the same as + // the one we store in pidData. If it is not, we know that the PID + // has been recycled and return ESRCH error. + startTime, found := strings.CutPrefix(h.pidData, startTimePrefix) + if found { + p, err := process.NewProcess(int32(h.pid)) + if err != nil { + if err == process.ErrorProcessNotRunning { + return unix.ESRCH + } + return err + } + + ctime, err := p.CreateTime() + if err != nil { + return err + } + + if strconv.FormatInt(ctime, 10) != startTime { + return unix.ESRCH + } + } + + return unix.Kill(h.pid, signal) +} + +// Returns true in case the process is still alive. +func (h *pidHandle) IsAlive() (bool, error) { + err := h.Kill(0) + if err != nil { + if err == unix.ESRCH { + return false, nil + } + return false, err + } + return true, nil +} + +// Returns a serialized representation of the PIDHandle. +// This string can be passed to NewPIDHandleFromString to recreate +// a PIDHandle that reliably refers to the same process as the original. +func (h *pidHandle) String() (string, error) { + if len(h.pidData) != 0 { + return h.pidData, nil + } + + // Get the start-time of the process and return it as string. + p, err := process.NewProcess(int32(h.pid)) + if err != nil { + if err == process.ErrorProcessNotRunning { + return noSuchProcessID, nil + } + return "", err + } + + ctime, err := p.CreateTime() + if err != nil { + // The process existed, but we cannot get its start-time. There is + // either an issue with getting it, or the process terminated in the + // mean-time. We have no way to find out what actually happened, so + // in this case, we just fallback to an empty string. This will mean + // that Kill or IsAlive might kill wrong process in rare situation + // when CreateTime() failed for different reason than the process + // terminated... + logrus.Debugf("Getting CreateTime for process (%d) failed: %v", h.pid, err) + return "", nil + } + + return startTimePrefix + strconv.FormatInt(ctime, 10), nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/pidhandle/pidhandle_linux.go b/vendor/github.com/containers/podman/v5/pkg/pidhandle/pidhandle_linux.go new file mode 100644 index 00000000..2420ed86 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/pidhandle/pidhandle_linux.go @@ -0,0 +1,201 @@ +//go:build linux + +// Package for handling processes and PIDs. +package pidhandle + +import ( + "encoding/hex" + "fmt" + "os" + "strconv" + "strings" + + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +type pidfdHandle struct { + pidfd int + normalHandle pidHandle +} + +// Store the "unix." methods in variables so we can mock them +// in the unit-tests and test out different return value. +var ( + pidfdOpen = unix.PidfdOpen + newFileHandle = unix.NewFileHandle + openByHandleAt = unix.OpenByHandleAt + nameToHandleAt = unix.NameToHandleAt + pidfdSendSignal = unix.PidfdSendSignal +) + +// The pidData prefix used when the pidfd and name_to_handle is supported +// when creating the PIDHandle to uniquely identify the process. +const nameToHandlePrefix = "name-to-handle:" + +// Creates new PIDHandle for a given process pid. +// +// Note that there still can be a race condition if the process terminates +// *before* the PIDHandle is created. It is a caller's responsibility +// to ensure that this either cannot happen or accept this risk. +func NewPIDHandle(pid int) (PIDHandle, error) { + // Use the pidfd to obtain the file-descriptor pointing to the process. + pidData := "" + pidfd, err := pidfdOpen(pid, 0) + if err != nil { + switch err { + case unix.ENOSYS: + // Do not fail if PidFdOpen is not supported, we will + // fallback to process start-time later. + + case unix.ESRCH: + // The process does not exist, so any future call of Kill + // or IsAlive should return unix.ESRCH, even if the pid is + // recycled in the future. Let's note it in the pidData. + pidData = noSuchProcessID + + case unix.EINVAL: + // The PidfdOpen returns EINVAL if pid is invalid or if it refers + // to a thread and not to process. This is not a valid PID for + // PIDHandle and it most likely means the pid has been recycled + // (or there is a programming error). We therefore store + // noSuchProcessID into pidData to return unix.ESRCH in + // the future Kill or IsAlive calls. + pidData = noSuchProcessID + + default: + return nil, fmt.Errorf("pidfdOpen failed: %w", err) + } + } + + h := pidfdHandle{ + pidfd: pidfd, + normalHandle: pidHandle{pid: pid, pidData: pidData}, + } + + pidData, err = h.String() + if err != nil { + return nil, err + } + h.normalHandle.pidData = pidData + return &h, nil +} + +// Creates new PIDHandle for a given process pid using the pidData +// originally obtained from PIDHandle.String(). +func NewPIDHandleFromString(pid int, pidData string) (PIDHandle, error) { + h := pidfdHandle{ + pidfd: -1, + normalHandle: pidHandle{pid: pid, pidData: pidData}, + } + + // Open the pidfd encoded in pidData. + data, found := strings.CutPrefix(pidData, nameToHandlePrefix) + if found { + // Split the data. + parts := strings.SplitN(data, " ", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid format, expected 2 parts") + } + + // Parse fhType. + fhTypeInt, err := strconv.Atoi(parts[0]) + if err != nil { + return nil, err + } + fhType := int32(fhTypeInt) + + // Decode hex string to bytes. + bytes, err := hex.DecodeString(parts[1]) + if err != nil { + return nil, err + } + + // Create FileHandle and open it. + fh := newFileHandle(fhType, bytes) + fd, err := pidfdOpen(os.Getpid(), 0) + if err != nil { + return nil, err + } + defer unix.Close(fd) + pidfd, err := openByHandleAt(fd, fh, 0) + if err != nil { + if err == unix.ESTALE { + h.normalHandle.pidData = noSuchProcessID + return &h, nil + } + return nil, fmt.Errorf("openByHandleAt failed: %w", err) + } + h.pidfd = pidfd + return &h, nil + } + + return &h, nil +} + +// Returns the PID associated with this PIDHandle. +func (h *pidfdHandle) PID() int { + return h.normalHandle.PID() +} + +// Close releases the pidfd resource. +func (h *pidfdHandle) Close() error { + if h.pidfd != 0 { + err := unix.Close(h.pidfd) + if err != nil { + return fmt.Errorf("failed to close pidfd: %w", err) + } + h.pidfd = 0 + } + return h.normalHandle.Close() +} + +// Sends the signal to process. +func (h *pidfdHandle) Kill(signal unix.Signal) error { + if h.pidfd > -1 { + return pidfdSendSignal(h.pidfd, signal, nil, 0) + } + + return h.normalHandle.Kill(signal) +} + +// Returns true in case the process is still alive. +func (h *pidfdHandle) IsAlive() (bool, error) { + err := h.Kill(0) + if err != nil { + if err == unix.ESRCH { + return false, nil + } + return false, err + } + return true, nil +} + +// Returns a serialized representation of the PIDHandle. +// This string can be passed to NewPIDHandleFromString to recreate +// a PIDHandle that reliably refers to the same process as the original. +func (h *pidfdHandle) String() (string, error) { + if len(h.normalHandle.pidData) != 0 { + return h.normalHandle.pidData, nil + } + + // Serialize the pidfd to string if possible. + if h.pidfd > -1 { + fh, _, err := nameToHandleAt(h.pidfd, "", unix.AT_EMPTY_PATH) + if err != nil { + // Do not fail if NameToHandleAt is not supported, we will + // fallback to process start-time later. + if err == unix.ENOTSUP { + logrus.Debugf("NameToHandleAt(%d) failed: %v", h.pidfd, err) + } else { + return "", err + } + } else { + hexStr := hex.EncodeToString(fh.Bytes()) + return nameToHandlePrefix + strconv.Itoa(int(fh.Type())) + " " + hexStr, nil + } + } + + // Fallback to default String(). + return h.normalHandle.String() +} diff --git a/vendor/github.com/containers/podman/v5/pkg/pidhandle/pidhandle_unix.go b/vendor/github.com/containers/podman/v5/pkg/pidhandle/pidhandle_unix.go new file mode 100644 index 00000000..da9f79cf --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/pidhandle/pidhandle_unix.go @@ -0,0 +1,24 @@ +//go:build !linux && !windows + +// Package for handling processes and PIDs. +package pidhandle + +// Creates new PIDHandle for a given process pid. +func NewPIDHandle(pid int) (PIDHandle, error) { + h := pidHandle{pid: pid} + pidData, err := h.String() + if err != nil { + return nil, err + } + h.pidData = pidData + return &h, nil +} + +// Creates new PIDHandle for a given process pid using the pidData +// originally obtained from PIDHandle.String(). +func NewPIDHandleFromString(pid int, pidData string) (PIDHandle, error) { + return &pidHandle{ + pid: pid, + pidData: pidData, + }, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/ps/define/types.go b/vendor/github.com/containers/podman/v5/pkg/ps/define/types.go new file mode 100644 index 00000000..878653c3 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/ps/define/types.go @@ -0,0 +1,8 @@ +package define + +// ContainerSize holds the size of the container's root filesystem and top +// read-write layer. +type ContainerSize struct { + RootFsSize int64 `json:"rootFsSize"` + RwSize int64 `json:"rwSize"` +} diff --git a/vendor/github.com/containers/podman/v5/pkg/rctl/rctl_freebsd.go b/vendor/github.com/containers/podman/v5/pkg/rctl/rctl_freebsd.go new file mode 100644 index 00000000..e7e36719 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/rctl/rctl_freebsd.go @@ -0,0 +1,39 @@ +package rctl + +import ( + "bytes" + "fmt" + "strconv" + "strings" + "syscall" + "unsafe" + + "github.com/sirupsen/logrus" +) + +func GetRacct(filter string) (map[string]uint64, error) { + bp, err := syscall.ByteSliceFromString(filter) + if err != nil { + return nil, err + } + var buf [1024]byte + _, _, errno := syscall.Syscall6(syscall.SYS_RCTL_GET_RACCT, + uintptr(unsafe.Pointer(&bp[0])), + uintptr(len(bp)), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(len(buf)), 0, 0) + if errno != 0 { + return nil, fmt.Errorf("error calling rctl_get_racct with filter %s: %v", filter, errno) + } + len := bytes.IndexByte(buf[:], byte(0)) + res := make(map[string]uint64) + for entry := range strings.SplitSeq(string(buf[:len]), ",") { + key, valstr, _ := strings.Cut(entry, "=") + val, err := strconv.ParseUint(valstr, 10, 0) + if err != nil { + logrus.Warnf("unexpected rctl entry, ignoring: %s", entry) + } + res[key] = val + } + return res, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/rootless/rootless.go b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless.go new file mode 100644 index 00000000..6f1f0f39 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless.go @@ -0,0 +1,194 @@ +package rootless + +import ( + "errors" + "fmt" + "os" + "sort" + "sync" + + "github.com/moby/sys/user" + spec "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/lockfile" +) + +// TryJoinPauseProcess attempts to join the namespaces of the pause PID via +// TryJoinFromFilePaths. If joining fails, it attempts to delete the specified +// file. +func TryJoinPauseProcess(pausePidPath string) (bool, int, error) { + if err := fileutils.Exists(pausePidPath); err != nil { + if errors.Is(err, os.ErrNotExist) { + return false, -1, nil + } + return false, -1, err + } + + became, ret, err := TryJoinFromFilePaths("", []string{pausePidPath}) + if err == nil { + return became, ret, nil + } + + // It could not join the pause process, let's lock the file before trying to delete it. + pidFileLock, err := lockfile.GetLockFile(pausePidPath) + if err != nil { + // The file was deleted by another process. + if os.IsNotExist(err) { + return false, -1, nil + } + return false, -1, fmt.Errorf("acquiring lock on %s: %w", pausePidPath, err) + } + + pidFileLock.Lock() + defer func() { + pidFileLock.Unlock() + }() + + // Now the pause PID file is locked. Try to join once again in case it changed while it was not locked. + became, ret, err = TryJoinFromFilePaths("", []string{pausePidPath}) + if err != nil { + // It is still failing. We can safely remove it. + os.Remove(pausePidPath) + return false, -1, nil + } + return became, ret, err +} + +var ( + uidMap []user.IDMap + uidMapError error + uidMapOnce sync.Once + + gidMap []user.IDMap + gidMapError error + gidMapOnce sync.Once +) + +// GetAvailableUIDMap returns the UID mappings in the +// current user namespace. +func GetAvailableUIDMap() ([]user.IDMap, error) { + uidMapOnce.Do(func() { + var err error + uidMap, err = user.ParseIDMapFile("/proc/self/uid_map") + if err != nil { + uidMapError = err + return + } + }) + return uidMap, uidMapError +} + +// GetAvailableGIDMap returns the GID mappings in the +// current user namespace. +func GetAvailableGIDMap() ([]user.IDMap, error) { + gidMapOnce.Do(func() { + var err error + gidMap, err = user.ParseIDMapFile("/proc/self/gid_map") + if err != nil { + gidMapError = err + return + } + }) + return gidMap, gidMapError +} + +// GetAvailableIDMaps returns the UID and GID mappings in the +// current user namespace. +func GetAvailableIDMaps() ([]user.IDMap, []user.IDMap, error) { + u, err := GetAvailableUIDMap() + if err != nil { + return nil, nil, err + } + g, err := GetAvailableGIDMap() + if err != nil { + return nil, nil, err + } + return u, g, nil +} + +func countAvailableIDs(mappings []user.IDMap) int64 { + availableUids := int64(0) + for _, r := range mappings { + availableUids += r.Count + } + return availableUids +} + +// GetAvailableGids returns how many GIDs are available in the +// current user namespace. +func GetAvailableGids() (int64, error) { + gids, err := GetAvailableGIDMap() + if err != nil { + return -1, err + } + + return countAvailableIDs(gids), nil +} + +// findIDInMappings find the mapping that contains the specified ID. +// It assumes availableMappings is sorted by ID. +func findIDInMappings(id int64, availableMappings []user.IDMap) *user.IDMap { + i := sort.Search(len(availableMappings), func(i int) bool { + return availableMappings[i].ID <= id + }) + if i < 0 || i >= len(availableMappings) { + return nil + } + r := &availableMappings[i] + if id >= r.ID && id < r.ID+r.Count { + return r + } + return nil +} + +// MaybeSplitMappings checks whether the specified OCI mappings are possible +// in the current user namespace or the specified ranges must be split. +func MaybeSplitMappings(mappings []spec.LinuxIDMapping, availableMappings []user.IDMap) []spec.LinuxIDMapping { + var ret []spec.LinuxIDMapping + var overflow spec.LinuxIDMapping + overflow.Size = 0 + consumed := 0 + sort.Slice(availableMappings, func(i, j int) bool { + return availableMappings[i].ID > availableMappings[j].ID + }) + for { + cur := overflow + // if there is no overflow left from the previous request, get the next one + if cur.Size == 0 { + if consumed == len(mappings) { + // all done + return ret + } + cur = mappings[consumed] + consumed++ + } + + // Find the range where the first specified ID is present + r := findIDInMappings(int64(cur.HostID), availableMappings) + if r == nil { + // The requested range is not available. Just return the original request + // and let other layers deal with it. + return mappings + } + + offsetInRange := cur.HostID - uint32(r.ID) + + usableIDs := uint32(r.Count) - offsetInRange + + // the current range can satisfy the whole request + if usableIDs >= cur.Size { + // reset the overflow + overflow.Size = 0 + } else { + // the current range can satisfy the request partially + // so move the rest to overflow + overflow.Size = cur.Size - usableIDs + overflow.ContainerID = cur.ContainerID + usableIDs + overflow.HostID = cur.HostID + usableIDs + + // and cap to the usableIDs count + cur.Size = usableIDs + } + ret = append(ret, cur) + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_freebsd.c b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_freebsd.c new file mode 100644 index 00000000..f8f0ba4b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_freebsd.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include + +static int open_files_max_fd; +static fd_set *open_files_set; + +int +is_fd_inherited(int fd) +{ + if (open_files_set == NULL || fd > open_files_max_fd || fd < 0) + return 0; + + return FD_ISSET(fd % FD_SETSIZE, &(open_files_set[fd / FD_SETSIZE])) ? 1 : 0; +} + +static void __attribute__((constructor)) init() +{ + /* Store how many FDs were open before the Go runtime kicked in. */ + DIR* d = opendir ("/dev/fd"); + if (d) + { + struct dirent *ent; + size_t size = 0; + + for (ent = readdir (d); ent; ent = readdir (d)) + { + int fd; + + if (ent->d_name[0] == '.') + continue; + + fd = atoi (ent->d_name); + if (fd == dirfd (d)) { + continue; + } + + if (fd >= size * FD_SETSIZE) + { + int i; + size_t new_size; + + new_size = (fd / FD_SETSIZE) + 1; + open_files_set = realloc (open_files_set, new_size * sizeof (fd_set)); + if (open_files_set == NULL) + _exit (EXIT_FAILURE); + + for (i = size; i < new_size; i++) + FD_ZERO (&(open_files_set[i])); + + size = new_size; + } + + if (fd > open_files_max_fd) { + open_files_max_fd = fd; + } + + FD_SET (fd % FD_SETSIZE, &(open_files_set[fd / FD_SETSIZE])); + } + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_freebsd.go b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_freebsd.go new file mode 100644 index 00000000..6b0edeef --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_freebsd.go @@ -0,0 +1,59 @@ +//go:build freebsd && cgo + +package rootless + +import ( + "errors" + + "go.podman.io/storage/pkg/idtools" +) + +// extern int is_fd_inherited(int fd); +import "C" + +// IsRootless returns whether the user is rootless +func IsRootless() bool { + return false +} + +// BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed +// into a new user namespace and the return code from the re-executed podman process. +// If podman was re-executed the caller needs to propagate the error code returned by the child +// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration. +func BecomeRootInUserNS(_ string) (bool, int, error) { + return false, -1, errors.New("rootless mode is not supported on FreeBSD - run podman as root") +} + +// GetRootlessUID returns the UID of the user in the parent userNS +func GetRootlessUID() int { + return -1 +} + +// GetRootlessGID returns the GID of the user in the parent userNS +func GetRootlessGID() int { + return -1 +} + +// TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths. +// This is useful when there are already running containers and we +// don't have a pause process yet. We can use the paths to the conmon +// processes to attempt joining their namespaces. +func TryJoinFromFilePaths(_ string, _ []string) (bool, int, error) { + return false, -1, errors.New("this function is not supported on this os") +} + +// ConfigurationMatches checks whether the additional uids/gids configured for the user +// match the current user namespace. +func ConfigurationMatches() (bool, error) { + return true, nil +} + +// GetConfiguredMappings returns the additional IDs configured for the current user. +func GetConfiguredMappings(_ bool) ([]idtools.IDMap, []idtools.IDMap, error) { + return nil, nil, errors.New("this function is not supported on this os") +} + +// IsFdInherited checks whether the fd is opened and valid to use +func IsFdInherited(fd int) bool { + return int(C.is_fd_inherited(C.int(fd))) > 0 +} diff --git a/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_linux.c b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_linux.c new file mode 100644 index 00000000..644c8ef9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_linux.c @@ -0,0 +1,1103 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ETC_PREEXEC_HOOKS "/etc/containers/pre-exec-hooks" +#define LIBEXECPODMAN "/usr/libexec/podman" + +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(expression) \ + (__extension__ \ + ({ long int __result; \ + do __result = (long int) (expression); \ + while (__result == -1L && errno == EINTR); \ + __result; })) +#endif + +#define cleanup_free __attribute__ ((cleanup (cleanup_freep))) +#define cleanup_close __attribute__ ((cleanup (cleanup_closep))) +#define cleanup_dir __attribute__ ((cleanup (cleanup_dirp))) + +static inline void +cleanup_freep (void *p) +{ + void **pp = (void **) p; + free (*pp); +} + +static inline void +cleanup_closep (void *p) +{ + int *pp = p; + if (*pp >= 0) + TEMP_FAILURE_RETRY (close (*pp)); +} + +static inline void +cleanup_dirp (DIR **p) +{ + DIR *dir = *p; + if (dir) + closedir (dir); +} + +int rename_noreplace (int olddirfd, const char *oldpath, int newdirfd, const char *newpath) +{ + int ret; + +# ifdef SYS_renameat2 +# ifndef RENAME_NOREPLACE +# define RENAME_NOREPLACE (1 << 0) +# endif + + ret = (int) syscall (SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE); + if (ret == 0 || errno != EINVAL) + return ret; + + /* Fallback in case of errno==EINVAL. */ +# endif + + /* This might be an issue if another process is trying to read the file while it is empty. */ + ret = open (newpath, O_EXCL|O_CREAT, 0700); + if (ret < 0) + return ret; + close (ret); + + /* We are sure we created the file, let's overwrite it. */ + return rename (oldpath, newpath); +} + +static const char *_max_user_namespaces = "/proc/sys/user/max_user_namespaces"; +static const char *_unprivileged_user_namespaces = "/proc/sys/kernel/unprivileged_userns_clone"; + +static int open_files_max_fd; +static fd_set *open_files_set; +static uid_t rootless_uid_init; +static gid_t rootless_gid_init; +static bool do_socket_activation = false; +static char *saved_systemd_listen_fds; +static char *saved_systemd_listen_pid; +static char *saved_systemd_listen_fdnames; + +static int +syscall_setresuid (uid_t ruid, uid_t euid, uid_t suid) +{ + return (int) syscall (__NR_setresuid, ruid, euid, suid); +} + +static int +syscall_setresgid (gid_t rgid, gid_t egid, gid_t sgid) +{ + return (int) syscall (__NR_setresgid, rgid, egid, sgid); +} + +uid_t +rootless_uid () +{ + return rootless_uid_init; +} + +uid_t +rootless_gid () +{ + return rootless_gid_init; +} + +/* exec the specified executable and exit if it fails. */ +static void +exec_binary (const char *path, char **argv, int argc) +{ + int r, status = 0; + pid_t pid; + + pid = fork (); + if (pid < 0) + { + fprintf (stderr, "fork: %m\n"); + exit (EXIT_FAILURE); + } + if (pid == 0) + { + size_t i; + char **newargv = malloc ((argc + 2) * sizeof(char *)); + if (!newargv) + { + fprintf (stderr, "malloc: %m\n"); + exit (EXIT_FAILURE); + } + newargv[0] = (char*) path; + for (i = 0; i < argc; i++) + newargv[i+1] = argv[i]; + + newargv[i+1] = NULL; + errno = 0; + execv (path, newargv); + /* If the file was deleted in the meanwhile, return success. */ + if (errno == ENOENT) + exit (EXIT_SUCCESS); + exit (EXIT_FAILURE); + } + + r = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)); + if (r < 0) + { + fprintf (stderr, "waitpid: %m\n"); + exit (EXIT_FAILURE); + } + if (WIFEXITED(status) && WEXITSTATUS (status)) + exit (WEXITSTATUS(status)); + if (WIFSIGNALED (status)) + exit (127+WTERMSIG (status)); + if (WIFSTOPPED (status)) + exit (EXIT_FAILURE); +} + +static void +do_preexec_hooks_dir (const char *dir, char **argv, int argc) +{ + cleanup_free char *buffer = NULL; + cleanup_dir DIR *d = NULL; + size_t i, nfiles = 0; + struct dirent *de; + + /* Store how many FDs were open before the Go runtime kicked in. */ + d = opendir (dir); + if (!d) + { + if (errno != ENOENT) + { + fprintf (stderr, "opendir %s: %m\n", dir); + exit (EXIT_FAILURE); + } + return; + } + + errno = 0; + + for (de = readdir (d); de; de = readdir (d)) + { + buffer = realloc (buffer, (nfiles + 1) * (NAME_MAX + 1)); + if (buffer == NULL) + { + fprintf (stderr, "realloc buffer: %m\n"); + exit (EXIT_FAILURE); + } + + if (de->d_type != DT_REG) + continue; + + strncpy (buffer + nfiles * (NAME_MAX + 1), de->d_name, NAME_MAX + 1); + nfiles++; + buffer[nfiles * (NAME_MAX + 1)] = '\0'; + } + + qsort (buffer, nfiles, NAME_MAX + 1, (int (*)(const void *, const void *)) strcmp); + + for (i = 0; i < nfiles; i++) + { + const char *fname = buffer + i * (NAME_MAX + 1); + char path[PATH_MAX]; + struct stat st; + int ret; + + ret = snprintf (path, PATH_MAX, "%s/%s", dir, fname); + if (ret == PATH_MAX) + { + fprintf (stderr, "internal error: path too long\n"); + exit (EXIT_FAILURE); + } + + ret = stat (path, &st); + if (ret < 0) + { + /* Ignore the failure if the file was deleted. */ + if (errno == ENOENT) + continue; + + fprintf (stderr, "stat %s: %m\n", path); + exit (EXIT_FAILURE); + } + + /* Not an executable. */ + if ((st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) + continue; + + exec_binary (path, argv, argc); + errno = 0; + } + + if (errno) + { + fprintf (stderr, "readdir %s: %m\n", dir); + exit (EXIT_FAILURE); + } +} + +static void +do_preexec_hooks (char **argv, int argc) +{ + // Access the preexec_hooks_dir indicator file + // return without processing if the file doesn't exist + char preexec_hooks_path[] = "/etc/containers/podman_preexec_hooks.txt"; + if (access(preexec_hooks_path, F_OK) != 0) { + return; + } + + char *preexec_hooks = getenv ("PODMAN_PREEXEC_HOOKS_DIR"); + do_preexec_hooks_dir (LIBEXECPODMAN "/pre-exec-hooks", argv, argc); + do_preexec_hooks_dir (ETC_PREEXEC_HOOKS, argv, argc); + if (preexec_hooks && preexec_hooks[0]) + do_preexec_hooks_dir (preexec_hooks, argv, argc); +} + +static void +do_pause () +{ + int i; + struct sigaction act; + int const sig[] = + { + SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGPOLL, + SIGPROF, SIGVTALRM, SIGXCPU, SIGXFSZ, 0 + }; + + act.sa_handler = SIG_IGN; + + for (i = 0; sig[i]; i++) + sigaction (sig[i], &act, NULL); + + /* Attempt to execv catatonit to keep the pause process alive. */ + execl (LIBEXECPODMAN "/catatonit", "catatonit", "-P", NULL); + execl ("/usr/bin/catatonit", "catatonit", "-P", NULL); + /* and if the catatonit executable could not be found, fallback here... */ + + prctl (PR_SET_NAME, "podman pause", NULL, NULL, NULL); + while (1) + pause (); +} + +static char ** +get_cmd_line_args (int *argc_out) +{ + cleanup_free char *buffer = NULL; + cleanup_close int fd = -1; + size_t allocated; + size_t used = 0; + int ret; + int i, argc = 0; + char **argv; + + fd = open ("/proc/self/cmdline", O_RDONLY); + if (fd < 0) + return NULL; + + allocated = 512; + buffer = malloc (allocated); + if (buffer == NULL) + return NULL; + for (;;) + { + ret = TEMP_FAILURE_RETRY (read (fd, buffer + used, allocated - used)); + if (ret < 0) + return NULL; + + if (ret == 0) + break; + + used += ret; + if (allocated == used) + { + allocated += 512; + char *tmp = realloc (buffer, allocated); + if (tmp == NULL) + return NULL; + buffer = tmp; + } + } + + for (i = 0; i < used; i++) + if (buffer[i] == '\0') + argc++; + if (argc == 0) + return NULL; + + argv = malloc (sizeof (char *) * (argc + 1)); + if (argv == NULL) + return NULL; + + argc = 0; + + argv[argc++] = buffer; + for (i = 0; i < used - 1; i++) + if (buffer[i] == '\0') + argv[argc++] = buffer + i + 1; + + argv[argc] = NULL; + + /* Move ownership. */ + buffer = NULL; + + if (argc_out) + *argc_out = argc; + + return argv; +} + +static bool +can_use_shortcut (char **argv) +{ + bool ret = true; + int argc; + +#ifdef DISABLE_JOIN_SHORTCUT + return false; +#endif + + if (strstr (argv[0], "podman") == NULL) + return false; + + for (argc = 0; argv[argc]; argc++) + { + if (argc == 0 || argv[argc][0] == '-') + continue; + + if (strcmp (argv[argc], "mount") == 0 + || strcmp (argv[argc], "machine") == 0 + || strcmp (argv[argc], "version") == 0 + || strcmp (argv[argc], "context") == 0 + || strcmp (argv[argc], "search") == 0 + || strcmp (argv[argc], "compose") == 0) + { + ret = false; + break; + } + + if (argv[argc+1] != NULL && (strcmp (argv[argc], "container") == 0 || + strcmp (argv[argc], "image") == 0) && + (strcmp (argv[argc+1], "mount") == 0 || strcmp (argv[argc+1], "scp") == 0)) + { + ret = false; + break; + } + } + + return ret; +} + +static int +open_namespace (int pid_to_join, const char *ns_file) +{ + char ns_path[PATH_MAX]; + int ret; + + ret = snprintf (ns_path, PATH_MAX, "/proc/%d/ns/%s", pid_to_join, ns_file); + if (ret == PATH_MAX) + { + fprintf (stderr, "internal error: namespace path too long\n"); + return -1; + } + + return open (ns_path, O_CLOEXEC | O_RDONLY); +} + +int +is_fd_inherited(int fd) +{ + if (open_files_set == NULL || fd > open_files_max_fd || fd < 0) + return 0; + + return FD_ISSET(fd % FD_SETSIZE, &(open_files_set[fd / FD_SETSIZE])) ? 1 : 0; +} + +static void __attribute__((constructor)) init() +{ + const char *xdg_runtime_dir; + const char *pause; + const char *listen_pid; + const char *listen_fds; + const char *listen_fdnames; + cleanup_free char **argv = NULL; + cleanup_free char *argv0 = NULL; + cleanup_dir DIR *d = NULL; + int argc; + + pause = getenv ("_PODMAN_PAUSE"); + if (pause && pause[0]) + { + do_pause (); + _exit (EXIT_FAILURE); + } + + /* Store how many FDs were open before the Go runtime kicked in. */ + d = opendir ("/proc/self/fd"); + if (d) + { + struct dirent *ent; + size_t size = 0; + + for (ent = readdir (d); ent; ent = readdir (d)) + { + int fd; + + if (ent->d_name[0] == '.') + continue; + + fd = atoi (ent->d_name); + if (fd == dirfd (d)) + continue; + + if (fd >= size * FD_SETSIZE) + { + int i; + size_t new_size; + + new_size = (fd / FD_SETSIZE) + 1; + open_files_set = realloc (open_files_set, new_size * sizeof (fd_set)); + if (open_files_set == NULL) + _exit (EXIT_FAILURE); + + for (i = size; i < new_size; i++) + FD_ZERO (&(open_files_set[i])); + + size = new_size; + } + + if (fd > open_files_max_fd) + open_files_max_fd = fd; + + FD_SET (fd % FD_SETSIZE, &(open_files_set[fd / FD_SETSIZE])); + } + } + + argv = get_cmd_line_args (&argc); + if (argv == NULL) + { + fprintf(stderr, "cannot retrieve cmd line"); + _exit (EXIT_FAILURE); + } + // Even if unused, this is needed to ensure we properly free the memory + argv0 = argv[0]; + + if (geteuid () != 0 || getenv ("_CONTAINERS_USERNS_CONFIGURED") == NULL) + do_preexec_hooks(argv, argc); + + listen_pid = getenv("LISTEN_PID"); + listen_fds = getenv("LISTEN_FDS"); + listen_fdnames = getenv("LISTEN_FDNAMES"); + + if (listen_pid != NULL && listen_fds != NULL && strtol(listen_pid, NULL, 10) == getpid()) + { + // save systemd socket environment for rootless child + do_socket_activation = true; + saved_systemd_listen_pid = strdup(listen_pid); + saved_systemd_listen_fds = strdup(listen_fds); + if (listen_fdnames != NULL) + saved_systemd_listen_fdnames = strdup(listen_fdnames); + if (saved_systemd_listen_pid == NULL + || saved_systemd_listen_fds == NULL) + { + fprintf (stderr, "save socket listen environments error: %m\n"); + _exit (EXIT_FAILURE); + } + } + + /* Shortcut. If we are able to join the pause pid file, do it now so we don't + need to re-exec. */ + xdg_runtime_dir = getenv ("XDG_RUNTIME_DIR"); + if (geteuid () != 0 && xdg_runtime_dir && xdg_runtime_dir[0] && can_use_shortcut (argv)) + { + cleanup_free char *cwd = NULL; + cleanup_close int userns_fd = -1; + cleanup_close int mntns_fd = -1; + cleanup_close int fd = -1; + long pid; + char buf[12]; + uid_t uid; + gid_t gid; + char path[PATH_MAX]; + const char *const suffix = "/libpod/tmp/pause.pid"; + char uid_fmt[16]; + char gid_fmt[16]; + size_t len; + int r; + + cwd = getcwd (NULL, 0); + if (cwd == NULL) + { + fprintf (stderr, "error getting current working directory: %m\n"); + _exit (EXIT_FAILURE); + } + + len = snprintf (path, PATH_MAX, "%s%s", xdg_runtime_dir, suffix); + if (len >= PATH_MAX) + { + errno = ENAMETOOLONG; + fprintf (stderr, "invalid value for XDG_RUNTIME_DIR: %m"); + exit (EXIT_FAILURE); + } + + fd = open (path, O_RDONLY); + if (fd < 0) + return; + + r = TEMP_FAILURE_RETRY (read (fd, buf, sizeof (buf) - 1)); + + if (r < 0) + return; + buf[r] = '\0'; + + pid = strtol (buf, NULL, 10); + if (pid == LONG_MAX) + return; + + uid = geteuid (); + gid = getegid (); + + userns_fd = open_namespace (pid, "user"); + if (userns_fd < 0) + return; + + mntns_fd = open_namespace (pid, "mnt"); + if (mntns_fd < 0) + return; + + if (setns (userns_fd, 0) < 0) + return; + + /* The user namespace was joined, after this point errors are + not recoverable anymore. */ + + if (setns (mntns_fd, 0) < 0) + { + fprintf (stderr, "cannot join mount namespace for %ld: %m", pid); + exit (EXIT_FAILURE); + } + + sprintf (uid_fmt, "%d", uid); + sprintf (gid_fmt, "%d", gid); + + setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1); + setenv ("_CONTAINERS_ROOTLESS_UID", uid_fmt, 1); + setenv ("_CONTAINERS_ROOTLESS_GID", gid_fmt, 1); + + if (syscall_setresgid (0, 0, 0) < 0) + { + fprintf (stderr, "cannot setresgid: %m\n"); + _exit (EXIT_FAILURE); + } + + if (syscall_setresuid (0, 0, 0) < 0) + { + fprintf (stderr, "cannot setresuid: %m\n"); + _exit (EXIT_FAILURE); + } + + if (chdir (cwd) < 0) + { + fprintf (stderr, "cannot chdir to %s: %m\n", cwd); + _exit (EXIT_FAILURE); + } + + rootless_uid_init = uid; + rootless_gid_init = gid; + } +} + +static int +syscall_clone (unsigned long flags, void *child_stack) +{ +#if defined(__s390__) || defined(__CRIS__) + return (int) syscall (__NR_clone, child_stack, flags); +#else + return (int) syscall (__NR_clone, flags, child_stack); +#endif +} + +int +reexec_in_user_namespace_wait (int pid, int options) +{ + pid_t p; + int status; + + p = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)); + if (p < 0) + return -1; + + if (WIFEXITED (status)) + return WEXITSTATUS (status); + if (WIFSIGNALED (status)) + return 128 + WTERMSIG (status); + return -1; +} + +static int +create_pause_process (const char *pause_pid_file_path, char **argv) +{ + pid_t pid; + int p[2]; + + if (pipe (p) < 0) + return -1; + + pid = syscall_clone (SIGCHLD, NULL); + if (pid < 0) + { + close (p[0]); + close (p[1]); + return -1; + } + + if (pid) + { + char b; + int r, r2; + + close (p[1]); + /* Block until we write the pid file. */ + r = TEMP_FAILURE_RETRY (read (p[0], &b, 1)); + close (p[0]); + + r2 = reexec_in_user_namespace_wait (pid, 0); + if (r2 != 0) + return -1; + + return r == 1 && b == '0' ? 0 : -1; + } + else + { + int r, fd; + + close (p[0]); + + setsid (); + pid = syscall_clone (SIGCHLD, NULL); + if (pid < 0) + _exit (EXIT_FAILURE); + + if (pid) + { + char pid_str[12]; + char *tmp_file_path = NULL; + + sprintf (pid_str, "%d", pid); + + if (asprintf (&tmp_file_path, "%s.XXXXXX", pause_pid_file_path) < 0) + { + fprintf (stderr, "unable to print to string\n"); + kill (pid, SIGKILL); + _exit (EXIT_FAILURE); + } + + if (tmp_file_path == NULL) + { + fprintf (stderr, "temporary file path is NULL\n"); + kill (pid, SIGKILL); + _exit (EXIT_FAILURE); + } + + fd = mkstemp (tmp_file_path); + if (fd < 0) + { + fprintf (stderr, "error creating temporary file: %m\n"); + kill (pid, SIGKILL); + _exit (EXIT_FAILURE); + } + + r = TEMP_FAILURE_RETRY (write (fd, pid_str, strlen (pid_str))); + if (r < 0) + { + fprintf (stderr, "cannot write to file descriptor: %m\n"); + kill (pid, SIGKILL); + _exit (EXIT_FAILURE); + } + close (fd); + + /* There can be another process at this point trying to configure the user namespace and the pause + process, do not override the pid file if it already exists. */ + if (rename_noreplace (AT_FDCWD, tmp_file_path, AT_FDCWD, pause_pid_file_path) < 0) + { + unlink (tmp_file_path); + kill (pid, SIGKILL); + _exit (EXIT_FAILURE); + } + + r = TEMP_FAILURE_RETRY (write (p[1], "0", 1)); + if (r < 0) + { + fprintf (stderr, "cannot write to pipe: %m\n"); + _exit (EXIT_FAILURE); + } + close (p[1]); + + _exit (EXIT_SUCCESS); + } + else + { + int null; + + close (p[1]); + + null = open ("/dev/null", O_RDWR); + if (null >= 0) + { + dup2 (null, 0); + dup2 (null, 1); + dup2 (null, 2); + close (null); + } + + for (fd = 3; fd < open_files_max_fd + 16; fd++) + close (fd); + + setenv ("_PODMAN_PAUSE", "1", 1); + execlp (argv[0], argv[0], NULL); + + /* If the execve fails, then do the pause here. */ + do_pause (); + _exit (EXIT_FAILURE); + } + } +} + +static void +join_namespace_or_die (const char *name, int ns_fd) +{ + if (setns (ns_fd, 0) < 0) + { + fprintf (stderr, "cannot set %s namespace\n", name); + _exit (EXIT_FAILURE); + } +} + +int +reexec_userns_join (int pid_to_join, char *pause_pid_file_path) +{ + cleanup_close int userns_fd = -1; + cleanup_close int mntns_fd = -1; + cleanup_free char *cwd = NULL; + char uid[16]; + char gid[16]; + cleanup_free char *argv0 = NULL; + cleanup_free char **argv = NULL; + int pid; + sigset_t sigset, oldsigset; + + cwd = getcwd (NULL, 0); + if (cwd == NULL) + { + fprintf (stderr, "error getting current working directory: %m\n"); + _exit (EXIT_FAILURE); + } + + sprintf (uid, "%d", geteuid ()); + sprintf (gid, "%d", getegid ()); + + argv = get_cmd_line_args (NULL); + if (argv == NULL) + { + fprintf (stderr, "cannot read argv: %m\n"); + _exit (EXIT_FAILURE); + } + + argv0 = argv[0]; + + userns_fd = open_namespace (pid_to_join, "user"); + if (userns_fd < 0) + return userns_fd; + mntns_fd = open_namespace (pid_to_join, "mnt"); + if (mntns_fd < 0) + return mntns_fd; + + pid = fork (); + if (pid < 0) + fprintf (stderr, "cannot fork: %m\n"); + + if (pid) + { + int f; + + for (f = 3; f <= open_files_max_fd; f++) + if (is_fd_inherited (f)) + close (f); + if (do_socket_activation) + { + unsetenv ("LISTEN_PID"); + unsetenv ("LISTEN_FDS"); + unsetenv ("LISTEN_FDNAMES"); + } + + return pid; + } + + if (sigfillset (&sigset) < 0) + { + fprintf (stderr, "cannot fill sigset: %m\n"); + _exit (EXIT_FAILURE); + } + if (sigdelset (&sigset, SIGCHLD) < 0) + { + fprintf (stderr, "cannot sigdelset(SIGCHLD): %m\n"); + _exit (EXIT_FAILURE); + } + if (sigdelset (&sigset, SIGTERM) < 0) + { + fprintf (stderr, "cannot sigdelset(SIGTERM): %m\n"); + _exit (EXIT_FAILURE); + } + if (sigprocmask (SIG_BLOCK, &sigset, &oldsigset) < 0) + { + fprintf (stderr, "cannot block signals: %m\n"); + _exit (EXIT_FAILURE); + } + + if (do_socket_activation) + { + char s[32]; + sprintf (s, "%d", getpid()); + setenv ("LISTEN_PID", s, true); + setenv ("LISTEN_FDS", saved_systemd_listen_fds, true); + // Setting fdnames is optional for systemd_socket_activation + if (saved_systemd_listen_fdnames != NULL) + setenv ("LISTEN_FDNAMES", saved_systemd_listen_fdnames, true); + } + + setenv ("_CONTAINERS_USERNS_CONFIGURED", "done", 1); + setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1); + setenv ("_CONTAINERS_ROOTLESS_GID", gid, 1); + + if (prctl (PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) < 0) + { + fprintf (stderr, "cannot prctl(PR_SET_PDEATHSIG): %m\n"); + _exit (EXIT_FAILURE); + } + + join_namespace_or_die ("user", userns_fd); + join_namespace_or_die ("mnt", mntns_fd); + + if (syscall_setresgid (0, 0, 0) < 0) + { + fprintf (stderr, "cannot setresgid: %m\n"); + _exit (EXIT_FAILURE); + } + + if (syscall_setresuid (0, 0, 0) < 0) + { + fprintf (stderr, "cannot setresuid: %m\n"); + _exit (EXIT_FAILURE); + } + + if (chdir (cwd) < 0) + { + fprintf (stderr, "cannot chdir to %s: %m\n", cwd); + _exit (EXIT_FAILURE); + } + + if (pause_pid_file_path && pause_pid_file_path[0] != '\0') + { + /* We ignore errors here as we didn't create the namespace anyway. */ + create_pause_process (pause_pid_file_path, argv); + } + if (sigprocmask (SIG_SETMASK, &oldsigset, NULL) < 0) + { + fprintf (stderr, "cannot block signals: %m\n"); + _exit (EXIT_FAILURE); + } + + execvp ("/proc/self/exe", argv); + fprintf (stderr, "failed to reexec: %m\n"); + + _exit (EXIT_FAILURE); +} + +static void +check_proc_sys_userns_file (const char *path) +{ + FILE *fp; + fp = fopen (path, "r"); + if (fp) + { + char buf[32]; + size_t n_read = fread (buf, 1, sizeof(buf) - 1, fp); + if (n_read > 0) + { + buf[n_read] = '\0'; + if (strtol (buf, NULL, 10) == 0) + fprintf (stderr, "user namespaces are not enabled in %s\n", path); + } + fclose (fp); + } +} + +int +reexec_in_user_namespace (int ready, char *pause_pid_file_path) +{ + cleanup_free char **argv = NULL; + cleanup_free char *argv0 = NULL; + cleanup_free char *cwd = NULL; + sigset_t sigset, oldsigset; + int ret; + pid_t pid; + char b; + char uid[16]; + char gid[16]; + + cwd = getcwd (NULL, 0); + if (cwd == NULL) + { + fprintf (stderr, "error getting current working directory: %m\n"); + _exit (EXIT_FAILURE); + } + + sprintf (uid, "%d", geteuid ()); + sprintf (gid, "%d", getegid ()); + + pid = syscall_clone (CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD, NULL); + if (pid < 0) + { + fprintf (stderr, "cannot clone: %m\n"); + check_proc_sys_userns_file (_max_user_namespaces); + check_proc_sys_userns_file (_unprivileged_user_namespaces); + } + if (pid) + { + if (do_socket_activation) + { + long num_fds; + + num_fds = strtol (saved_systemd_listen_fds, NULL, 10); + if (num_fds != LONG_MIN && num_fds != LONG_MAX) + { + int f; + + for (f = 3; f < num_fds + 3; f++) + if (is_fd_inherited (f)) + close (f); + } + unsetenv ("LISTEN_PID"); + unsetenv ("LISTEN_FDS"); + unsetenv ("LISTEN_FDNAMES"); + } + return pid; + } + + if (sigfillset (&sigset) < 0) + { + fprintf (stderr, "cannot fill sigset: %m\n"); + _exit (EXIT_FAILURE); + } + if (sigdelset (&sigset, SIGCHLD) < 0) + { + fprintf (stderr, "cannot sigdelset(SIGCHLD): %m\n"); + _exit (EXIT_FAILURE); + } + if (sigdelset (&sigset, SIGTERM) < 0) + { + fprintf (stderr, "cannot sigdelset(SIGTERM): %m\n"); + _exit (EXIT_FAILURE); + } + if (sigprocmask (SIG_BLOCK, &sigset, &oldsigset) < 0) + { + fprintf (stderr, "cannot block signals: %m\n"); + _exit (EXIT_FAILURE); + } + + argv = get_cmd_line_args (NULL); + if (argv == NULL) + { + fprintf (stderr, "cannot read argv: %m\n"); + _exit (EXIT_FAILURE); + } + + argv0 = argv[0]; + + if (do_socket_activation) + { + char s[32]; + sprintf (s, "%d", getpid()); + setenv ("LISTEN_PID", s, true); + setenv ("LISTEN_FDS", saved_systemd_listen_fds, true); + // Setting fdnames is optional for systemd_socket_activation + if (saved_systemd_listen_fdnames != NULL) + setenv ("LISTEN_FDNAMES", saved_systemd_listen_fdnames, true); + } + + setenv ("_CONTAINERS_USERNS_CONFIGURED", "done", 1); + setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1); + setenv ("_CONTAINERS_ROOTLESS_GID", gid, 1); + + ret = TEMP_FAILURE_RETRY (read (ready, &b, 1)); + if (ret < 0) + { + fprintf (stderr, "cannot read from sync pipe: %m\n"); + _exit (EXIT_FAILURE); + } + if (ret != 1 || b != '0') + _exit (EXIT_FAILURE); + + if (syscall_setresgid (0, 0, 0) < 0) + { + fprintf (stderr, "cannot setresgid: %m\n"); + TEMP_FAILURE_RETRY (write (ready, "1", 1)); + _exit (EXIT_FAILURE); + } + + if (syscall_setresuid (0, 0, 0) < 0) + { + fprintf (stderr, "cannot setresuid: %m\n"); + TEMP_FAILURE_RETRY (write (ready, "1", 1)); + _exit (EXIT_FAILURE); + } + + if (chdir (cwd) < 0) + { + fprintf (stderr, "cannot chdir to %s: %m\n", cwd); + TEMP_FAILURE_RETRY (write (ready, "1", 1)); + _exit (EXIT_FAILURE); + } + + if (pause_pid_file_path && pause_pid_file_path[0] != '\0') + { + if (create_pause_process (pause_pid_file_path, argv) < 0) + { + TEMP_FAILURE_RETRY (write (ready, "2", 1)); + _exit (EXIT_FAILURE); + } + } + + ret = TEMP_FAILURE_RETRY (write (ready, "0", 1)); + if (ret < 0) + { + fprintf (stderr, "cannot write to ready pipe: %m\n"); + _exit (EXIT_FAILURE); + } + close (ready); + + if (sigprocmask (SIG_SETMASK, &oldsigset, NULL) < 0) + { + fprintf (stderr, "cannot block signals: %m\n"); + _exit (EXIT_FAILURE); + } + + execvp ("/proc/self/exe", argv); + fprintf (stderr, "failed to reexec: %m\n"); + + _exit (EXIT_FAILURE); +} diff --git a/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_linux.go b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_linux.go new file mode 100644 index 00000000..551211e2 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_linux.go @@ -0,0 +1,461 @@ +//go:build linux && cgo + +package rootless + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + gosignal "os/signal" + "os/user" + "runtime" + "strconv" + "strings" + "sync" + "unsafe" + + "github.com/containers/podman/v5/pkg/errorhandling" + "github.com/moby/sys/capability" + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/idtools" + pmount "go.podman.io/storage/pkg/mount" + "go.podman.io/storage/pkg/unshare" + "golang.org/x/sys/unix" +) + +/* +#cgo remote CFLAGS: -Wall -Werror -DDISABLE_JOIN_SHORTCUT +#include +#include +extern uid_t rootless_uid(); +extern uid_t rootless_gid(); +extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path); +extern int reexec_in_user_namespace_wait(int pid, int options); +extern int reexec_userns_join(int pid, char *pause_pid_file_path); +extern int is_fd_inherited(int fd); +*/ +import "C" + +const ( + numSig = 65 // max number of signals +) + +func init() { + rootlessUIDInit := int(C.rootless_uid()) + rootlessGIDInit := int(C.rootless_gid()) + if rootlessUIDInit != 0 { + // we need this if we joined the user+mount namespace from the C code. + if err := os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done"); err != nil { + logrus.Errorf("Failed to set environment variable %s as %s", "_CONTAINERS_USERNS_CONFIGURED", "done") + } + if err := os.Setenv("_CONTAINERS_ROOTLESS_UID", strconv.Itoa(rootlessUIDInit)); err != nil { + logrus.Errorf("Failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_UID", rootlessUIDInit) + } + if err := os.Setenv("_CONTAINERS_ROOTLESS_GID", strconv.Itoa(rootlessGIDInit)); err != nil { + logrus.Errorf("Failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_GID", rootlessGIDInit) + } + } +} + +func runInUser() error { + return os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done") +} + +var ( + isRootlessOnce sync.Once + isRootless bool +) + +// IsRootless tells us if we are running in rootless mode +func IsRootless() bool { + // unshare.IsRootless() is used to check if a user namespace is required. + // Here we need to make sure that nested podman instances act + // as if they have root privileges and pick paths on the host + // that would normally be used for root. + return unshare.IsRootless() && unshare.GetRootlessUID() > 0 +} + +// GetRootlessUID returns the UID of the user in the parent userNS +func GetRootlessUID() int { + return unshare.GetRootlessUID() +} + +// GetRootlessGID returns the GID of the user in the parent userNS +func GetRootlessGID() int { + return unshare.GetRootlessGID() +} + +func tryMappingTool(uid bool, pid int, hostID int, mappings []idtools.IDMap) error { + var tool = "newuidmap" + mode := os.ModeSetuid + cap := capability.CAP_SETUID + idtype := "setuid" + if !uid { + tool = "newgidmap" + mode = os.ModeSetgid + cap = capability.CAP_SETGID + idtype = "setgid" + } + path, err := exec.LookPath(tool) + if err != nil { + return fmt.Errorf("command required for rootless mode with multiple IDs: %w", err) + } + + appendTriplet := func(l []string, a, b, c int) []string { + return append(l, strconv.Itoa(a), strconv.Itoa(b), strconv.Itoa(c)) + } + + args := []string{path, strconv.Itoa(pid)} + args = appendTriplet(args, 0, hostID, 1) + for _, i := range mappings { + if hostID >= i.HostID && hostID < i.HostID+i.Size { + what := "UID" + where := "/etc/subuid" + if !uid { + what = "GID" + where = "/etc/subgid" + } + return fmt.Errorf("invalid configuration: the specified mapping %d:%d in %q includes the user %s", i.HostID, i.Size, where, what) + } + args = appendTriplet(args, i.ContainerID+1, i.HostID, i.Size) + } + cmd := exec.Cmd{ + Path: path, + Args: args, + } + + if output, err := cmd.CombinedOutput(); err != nil { + logrus.Errorf("running `%s`: %s", strings.Join(args, " "), output) + errorStr := fmt.Sprintf("cannot set up namespace using %q", path) + if isSet, err := unshare.IsSetID(cmd.Path, mode, cap); err != nil { + logrus.Errorf("Failed to check for %s on %s: %v", idtype, path, err) + } else if !isSet { + errorStr = fmt.Sprintf("%s: should have %s or have filecaps %s", errorStr, idtype, idtype) + } + return fmt.Errorf("%v: %w", errorStr, err) + } + return nil +} + +// joinUserAndMountNS re-exec podman in a new userNS and join the user and mount +// namespace of the specified PID without looking up its parent. Useful to join directly +// the conmon process. +func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) { + hasCapSysAdmin, err := unshare.HasCapSysAdmin() + if err != nil { + return false, 0, err + } + if (os.Geteuid() == 0 && hasCapSysAdmin) || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { + return false, 0, nil + } + + cPausePid := C.CString(pausePid) + defer C.free(unsafe.Pointer(cPausePid)) + + pidC := C.reexec_userns_join(C.int(pid), cPausePid) + if int(pidC) < 0 { + return false, -1, fmt.Errorf("cannot re-exec process to join the existing user namespace") + } + + return waitAndProxySignalsToChild(pidC) +} + +// GetConfiguredMappings returns the additional IDs configured for the current user. +func GetConfiguredMappings(quiet bool) ([]idtools.IDMap, []idtools.IDMap, error) { + var uids, gids []idtools.IDMap + username := os.Getenv("USER") + if username == "" { + var id string + if os.Geteuid() == 0 { + id = strconv.Itoa(GetRootlessUID()) + } else { + id = strconv.Itoa(os.Geteuid()) + } + userID, err := user.LookupId(id) + if err == nil { + username = userID.Username + } + } + mappings, err := idtools.NewIDMappings(username, username) + if err != nil { + logLevel := logrus.ErrorLevel + if quiet || (os.Geteuid() == 0 && GetRootlessUID() == 0) { + logLevel = logrus.DebugLevel + } + logrus.StandardLogger().Logf(logLevel, "cannot find UID/GID for user %s: %v - check rootless mode in man pages.", username, err) + } else { + uids = mappings.UIDs() + gids = mappings.GIDs() + } + return uids, gids, nil +} + +func copyMappings(from, to string) error { + // when running as non-root always go through the newuidmap/newgidmap + // configuration since this is the expectation when running on Kubernetes + if os.Geteuid() != 0 { + return errors.New("copying mappings is allowed only for root") + } + content, err := os.ReadFile(from) + if err != nil { + return err + } + // Both runc and crun check whether the current process is in a user namespace + // by looking up 4294967295 in /proc/self/uid_map. If the mappings would be + // copied as they are, the check in the OCI runtimes would fail. So just split + // it in two different ranges. + if bytes.Contains(content, []byte("4294967295")) { + content = []byte("0 0 1\n1 1 4294967294\n") + } + return os.WriteFile(to, content, 0o600) +} + +func becomeRootInUserNS(pausePid string) (_ bool, _ int, retErr error) { + hasCapSysAdmin, err := unshare.HasCapSysAdmin() + if err != nil { + return false, 0, err + } + + if (os.Geteuid() == 0 && hasCapSysAdmin) || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { + if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" { + return false, 0, runInUser() + } + return false, 0, nil + } + + if _, inContainer := os.LookupEnv("container"); !inContainer { + if mounts, err := pmount.GetMounts(); err == nil { + for _, m := range mounts { + if m.Mountpoint == "/" { + isShared := false + for o := range strings.SplitSeq(m.Optional, ",") { + if strings.HasPrefix(o, "shared:") { + isShared = true + break + } + } + if !isShared { + logrus.Warningf("%q is not a shared mount, this could cause issues or missing mounts with rootless containers", m.Mountpoint) + } + break + } + } + } + } + + cPausePid := C.CString(pausePid) + defer C.free(unsafe.Pointer(cPausePid)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) + if err != nil { + return false, -1, err + } + r, w := os.NewFile(uintptr(fds[0]), "sync host"), os.NewFile(uintptr(fds[1]), "sync child") + + var pid int + + defer errorhandling.CloseQuiet(r) + defer errorhandling.CloseQuiet(w) + defer func() { + toWrite := []byte("0") + if retErr != nil { + toWrite = []byte("1") + } + if _, err := w.Write(toWrite); err != nil { + logrus.Errorf("Failed to write byte 0: %q", err) + } + if retErr != nil && pid > 0 { + if err := unix.Kill(pid, unix.SIGKILL); err != nil { + if err != unix.ESRCH { + logrus.Errorf("Failed to clean up process %d: %v", pid, err) + } + } + C.reexec_in_user_namespace_wait(C.int(pid), 0) + } + }() + + pidC := C.reexec_in_user_namespace(C.int(r.Fd()), cPausePid) + pid = int(pidC) + if pid < 0 { + return false, -1, fmt.Errorf("cannot re-exec process") + } + + uids, gids, err := GetConfiguredMappings(false) + if err != nil { + return false, -1, err + } + + uidMap := fmt.Sprintf("/proc/%d/uid_map", pid) + gidMap := fmt.Sprintf("/proc/%d/gid_map", pid) + + uidsMapped := false + + if err := copyMappings("/proc/self/uid_map", uidMap); err == nil { + uidsMapped = true + } + + if uids != nil && !uidsMapped { + err := tryMappingTool(true, pid, os.Geteuid(), uids) + // If some mappings were specified, do not ignore the error + if err != nil && len(uids) > 0 { + return false, -1, err + } + uidsMapped = err == nil + } + if !uidsMapped { + logrus.Warnf("Using rootless single mapping into the namespace. This might break some images. Check /etc/subuid and /etc/subgid for adding sub*ids if not using a network user") + setgroups := fmt.Sprintf("/proc/%d/setgroups", pid) + err = os.WriteFile(setgroups, []byte("deny\n"), 0o666) + if err != nil { + return false, -1, fmt.Errorf("cannot write setgroups file: %w", err) + } + logrus.Debugf("write setgroups file exited with 0") + + err = os.WriteFile(uidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Geteuid())), 0o666) + if err != nil { + return false, -1, fmt.Errorf("cannot write uid_map: %w", err) + } + logrus.Debugf("write uid_map exited with 0") + } + + gidsMapped := false + if err := copyMappings("/proc/self/gid_map", gidMap); err == nil { + gidsMapped = true + } + if gids != nil && !gidsMapped { + err := tryMappingTool(false, pid, os.Getegid(), gids) + // If some mappings were specified, do not ignore the error + if err != nil && len(gids) > 0 { + return false, -1, err + } + gidsMapped = err == nil + } + if !gidsMapped { + err = os.WriteFile(gidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getegid())), 0o666) + if err != nil { + return false, -1, fmt.Errorf("cannot write gid_map: %w", err) + } + } + + _, err = w.WriteString("0") + if err != nil { + return false, -1, fmt.Errorf("write to sync pipe: %w", err) + } + + b := make([]byte, 1) + _, err = w.Read(b) + if err != nil { + return false, -1, fmt.Errorf("read from sync pipe: %w", err) + } + + if b[0] == '2' { + // We have lost the race for writing the PID file, as probably another + // process created a namespace and wrote the PID. + // Try to join it. + data, err := os.ReadFile(pausePid) + if err == nil { + var pid uint64 + pid, err = strconv.ParseUint(string(data), 10, 0) + if err == nil { + return joinUserAndMountNS(uint(pid), "") + } + } + return false, -1, fmt.Errorf("setting up the process: %w", err) + } + + if b[0] != '0' { + return false, -1, errors.New("setting up the process") + } + + return waitAndProxySignalsToChild(pidC) +} + +func waitAndProxySignalsToChild(pid C.int) (bool, int, error) { + signals := []os.Signal{} + for sig := 0; sig < numSig; sig++ { + if sig == int(unix.SIGTSTP) { + continue + } + signals = append(signals, unix.Signal(sig)) + } + + // Disable all existing signal handlers, from now forward everything to the child and let + // it deal with it. All we do is to wait and propagate the exit code from the child to our parent. + gosignal.Reset() + c := make(chan os.Signal, len(signals)) + gosignal.Notify(c, signals...) + go func() { + for s := range c { + if s == unix.SIGCHLD || s == unix.SIGPIPE { + continue + } + + if err := unix.Kill(int(pid), s.(unix.Signal)); err != nil { + if err != unix.ESRCH { + logrus.Errorf("Failed to propagate signal to child process %d: %v", int(pid), err) + } + } + } + }() + + ret := C.reexec_in_user_namespace_wait(pid, 0) + // child exited reset our signal proxy handler + gosignal.Reset() + if ret < 0 { + return false, -1, errors.New("waiting for the re-exec process") + } + + return true, int(ret), nil +} + +// BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed +// into a new user namespace and the return code from the re-executed podman process. +// If podman was re-executed the caller needs to propagate the error code returned by the child +// process. +func BecomeRootInUserNS(pausePid string) (bool, int, error) { + return becomeRootInUserNS(pausePid) +} + +// TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths. +// This is useful when there are already running containers and we +// don't have a pause process yet. We can use the paths to the conmon +// processes to attempt joining their namespaces. +func TryJoinFromFilePaths(pausePidPath string, paths []string) (bool, int, error) { + var lastErr error + + for _, path := range paths { + data, err := os.ReadFile(path) + if err != nil { + lastErr = err + continue + } + + pausePid, err := strconv.Atoi(string(data)) + if err != nil { + lastErr = fmt.Errorf("cannot parse file %q: %w", path, err) + continue + } + + if pausePid > 0 && unix.Kill(pausePid, 0) == nil { + joined, pid, err := joinUserAndMountNS(uint(pausePid), pausePidPath) + if err == nil { + return joined, pid, nil + } + lastErr = err + } + } + if lastErr != nil { + return false, 0, lastErr + } + return false, 0, fmt.Errorf("could not find any running process: %w", unix.ESRCH) +} + +// IsFdInherited checks whether the fd is opened and valid to use +func IsFdInherited(fd int) bool { + return int(C.is_fd_inherited(C.int(fd))) > 0 +} diff --git a/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_unsupported.go b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_unsupported.go new file mode 100644 index 00000000..32973435 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/rootless/rootless_unsupported.go @@ -0,0 +1,62 @@ +//go:build !(linux || freebsd) || !cgo + +package rootless + +import ( + "errors" + "os" + + "go.podman.io/storage/pkg/idtools" +) + +// IsRootless returns whether the user is rootless +func IsRootless() bool { + uid := os.Geteuid() + // os.Geteuid() on Windows returns -1 + if uid == -1 { + return false + } + return uid != 0 +} + +// BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed +// into a new user namespace and the return code from the re-executed podman process. +// If podman was re-executed the caller needs to propagate the error code returned by the child +// process. It is a convenience function for BecomeRootInUserNSWithOpts with a default configuration. +func BecomeRootInUserNS(_ string) (bool, int, error) { + return false, -1, errors.New("this function is not supported on this os") +} + +// GetRootlessUID returns the UID of the user in the parent userNS +func GetRootlessUID() int { + return -1 +} + +// GetRootlessGID returns the GID of the user in the parent userNS +func GetRootlessGID() int { + return -1 +} + +// TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths. +// This is useful when there are already running containers and we +// don't have a pause process yet. We can use the paths to the conmon +// processes to attempt joining their namespaces. +func TryJoinFromFilePaths(_ string, _ []string) (bool, int, error) { + return false, -1, errors.New("this function is not supported on this os") +} + +// ConfigurationMatches checks whether the additional uids/gids configured for the user +// match the current user namespace. +func ConfigurationMatches() (bool, error) { + return true, nil +} + +// GetConfiguredMappings returns the additional IDs configured for the current user. +func GetConfiguredMappings(_ bool) ([]idtools.IDMap, []idtools.IDMap, error) { + return nil, nil, errors.New("this function is not supported on this os") +} + +// IsFdInherited checks whether the fd is opened and valid to use +func IsFdInherited(_ int) bool { + return false +} diff --git a/vendor/github.com/containers/podman/v5/pkg/selinux/selinux.go b/vendor/github.com/containers/podman/v5/pkg/selinux/selinux.go new file mode 100644 index 00000000..6b6d065f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/selinux/selinux.go @@ -0,0 +1,40 @@ +package selinux + +import ( + "github.com/opencontainers/selinux/go-selinux" +) + +// KVMLabel returns labels for running kvm isolated containers +func KVMLabel(cLabel string) (string, error) { + if cLabel == "" { + // selinux is disabled + return "", nil + } + processLabel, _ := selinux.KVMContainerLabels() + selinux.ReleaseLabel(processLabel) + return swapSELinuxLabel(cLabel, processLabel) +} + +// InitLabel returns labels for running systemd based containers +func InitLabel(cLabel string) (string, error) { + if cLabel == "" { + // selinux is disabled + return "", nil + } + processLabel, _ := selinux.InitContainerLabels() + selinux.ReleaseLabel(processLabel) + return swapSELinuxLabel(cLabel, processLabel) +} + +func swapSELinuxLabel(cLabel, processLabel string) (string, error) { + dcon, err := selinux.NewContext(cLabel) + if err != nil { + return "", err + } + scon, err := selinux.NewContext(processLabel) + if err != nil { + return "", err + } + dcon["type"] = scon["type"] + return dcon.Get(), nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/signal/signal_common.go b/vendor/github.com/containers/podman/v5/pkg/signal/signal_common.go new file mode 100644 index 00000000..34e5b494 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/signal/signal_common.go @@ -0,0 +1,84 @@ +package signal + +import ( + "fmt" + "os" + "os/signal" + "strconv" + "strings" + "syscall" +) + +// Make sure the signal buffer is sufficiently big. +// runc is using the same value. +const SignalBufferSize = 2048 + +// ParseSignal translates a string to a valid syscall signal. +// It returns an error if the signal map doesn't include the given signal. +func ParseSignal(rawSignal string) (syscall.Signal, error) { + s, err := strconv.Atoi(rawSignal) + if err == nil { + if s == 0 { + return -1, fmt.Errorf("invalid signal: %s", rawSignal) + } + return syscall.Signal(s), nil + } + sig, ok := SignalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] + if !ok { + return -1, fmt.Errorf("invalid signal: %s", rawSignal) + } + return sig, nil +} + +// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input +// can be a name or number representation i.e. "KILL" "9". +func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) { + basename := strings.TrimPrefix(rawSignal, "-") + s, err := ParseSignal(basename) + if err == nil { + return s, nil + } + for k, v := range SignalMap { + if k == strings.ToUpper(basename) { + return v, nil + } + } + return -1, fmt.Errorf("invalid signal: %s", basename) +} + +// CatchAll catches all signals (except the ones that make no sense to handle/forward, +// see isSignalIgnoredBySigProxy()) and relays them to the specified channel. +func CatchAll(sigc chan os.Signal) { + handledSigs := make([]os.Signal, 0, len(SignalMap)) + for _, s := range SignalMap { + if !isSignalIgnoredBySigProxy(s) { + handledSigs = append(handledSigs, s) + } + } + signal.Notify(sigc, handledSigs...) +} + +// StopCatch stops catching the signals and closes the specified channel. +func StopCatch(sigc chan os.Signal) { + signal.Stop(sigc) + close(sigc) +} + +// ParseSysSignalToName translates syscall.Signal to its name in the operating system. +// For example, syscall.Signal(9) will return "KILL" on Linux system. +func ParseSysSignalToName(s syscall.Signal) (string, error) { + for k, v := range SignalMap { + if v == s { + return k, nil + } + } + return "", fmt.Errorf("unknown syscall signal: %s", s) +} + +func ToDockerFormat(s uint) string { + var signalStr, err = ParseSysSignalToName(syscall.Signal(s)) + if err != nil { + return strconv.FormatUint(uint64(s), 10) + } + return fmt.Sprintf("SIG%s", signalStr) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/signal/signal_linux.go b/vendor/github.com/containers/podman/v5/pkg/signal/signal_linux.go new file mode 100644 index 00000000..b0f38249 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/signal/signal_linux.go @@ -0,0 +1,104 @@ +//go:build linux && !mips && !mipsle && !mips64 && !mips64le + +// Signal handling for Linux only. +package signal + +// Copyright 2013-2018 Docker, Inc. + +// NOTE: this package has originally been copied from github.com/docker/docker. + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +const ( + sigrtmin = 34 + sigrtmax = 64 + + SIGWINCH = syscall.SIGWINCH // For cross-compilation with Windows +) + +// SignalMap is a map of Linux signals. +var SignalMap = map[string]syscall.Signal{ + "ABRT": unix.SIGABRT, + "ALRM": unix.SIGALRM, + "BUS": unix.SIGBUS, + "CHLD": unix.SIGCHLD, + "CLD": unix.SIGCLD, + "CONT": unix.SIGCONT, + "FPE": unix.SIGFPE, + "HUP": unix.SIGHUP, + "ILL": unix.SIGILL, + "INT": unix.SIGINT, + "IO": unix.SIGIO, + "IOT": unix.SIGIOT, + "KILL": unix.SIGKILL, + "PIPE": unix.SIGPIPE, + "POLL": unix.SIGPOLL, + "PROF": unix.SIGPROF, + "PWR": unix.SIGPWR, + "QUIT": unix.SIGQUIT, + "SEGV": unix.SIGSEGV, + "STKFLT": unix.SIGSTKFLT, + "STOP": unix.SIGSTOP, + "SYS": unix.SIGSYS, + "TERM": unix.SIGTERM, + "TRAP": unix.SIGTRAP, + "TSTP": unix.SIGTSTP, + "TTIN": unix.SIGTTIN, + "TTOU": unix.SIGTTOU, + "URG": unix.SIGURG, + "USR1": unix.SIGUSR1, + "USR2": unix.SIGUSR2, + "VTALRM": unix.SIGVTALRM, + "WINCH": unix.SIGWINCH, + "XCPU": unix.SIGXCPU, + "XFSZ": unix.SIGXFSZ, + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, +} + +// isSignalIgnoredBySigProxy determines whether sig-proxy should ignore syscall signal +func isSignalIgnoredBySigProxy(s syscall.Signal) bool { + // Ignore SIGCHLD and SIGPIPE - these are most likely intended for the podman command itself. + // SIGURG was added because of golang 1.14 and its preemptive changes causing more signals to "show up". + // https://github.com/containers/podman/issues/5483 + // SIGSTOP cannot be ignored/forwarded by userspace. + switch s { + case syscall.SIGCHLD, syscall.SIGPIPE, syscall.SIGURG, syscall.SIGSTOP: + return true + default: + return false + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/signal/signal_linux_mipsx.go b/vendor/github.com/containers/podman/v5/pkg/signal/signal_linux_mipsx.go new file mode 100644 index 00000000..77492d57 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/signal/signal_linux_mipsx.go @@ -0,0 +1,104 @@ +//go:build linux && (mips || mipsle || mips64 || mips64le) + +// Special signal handling for mips architecture +package signal + +// Copyright 2013-2018 Docker, Inc. + +// NOTE: this package has originally been copied from github.com/docker/docker. + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +const ( + sigrtmin = 34 + sigrtmax = 127 + + SIGWINCH = syscall.SIGWINCH +) + +// SignalMap is a map of Linux signals. +var SignalMap = map[string]syscall.Signal{ + "ABRT": unix.SIGABRT, + "ALRM": unix.SIGALRM, + "BUS": unix.SIGBUS, + "CHLD": unix.SIGCHLD, + "CLD": unix.SIGCLD, + "CONT": unix.SIGCONT, + "FPE": unix.SIGFPE, + "HUP": unix.SIGHUP, + "ILL": unix.SIGILL, + "INT": unix.SIGINT, + "IO": unix.SIGIO, + "IOT": unix.SIGIOT, + "KILL": unix.SIGKILL, + "PIPE": unix.SIGPIPE, + "POLL": unix.SIGPOLL, + "PROF": unix.SIGPROF, + "PWR": unix.SIGPWR, + "QUIT": unix.SIGQUIT, + "SEGV": unix.SIGSEGV, + "EMT": unix.SIGEMT, + "STOP": unix.SIGSTOP, + "SYS": unix.SIGSYS, + "TERM": unix.SIGTERM, + "TRAP": unix.SIGTRAP, + "TSTP": unix.SIGTSTP, + "TTIN": unix.SIGTTIN, + "TTOU": unix.SIGTTOU, + "URG": unix.SIGURG, + "USR1": unix.SIGUSR1, + "USR2": unix.SIGUSR2, + "VTALRM": unix.SIGVTALRM, + "WINCH": unix.SIGWINCH, + "XCPU": unix.SIGXCPU, + "XFSZ": unix.SIGXFSZ, + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, +} + +// isSignalIgnoredBySigProxy determines whether sig-proxy should ignore syscall signal +func isSignalIgnoredBySigProxy(s syscall.Signal) bool { + // Ignore SIGCHLD and SIGPIPE - these are most likely intended for the podman command itself. + // SIGURG was added because of golang 1.14 and its preemptive changes causing more signals to "show up". + // https://github.com/containers/podman/issues/5483 + // SIGSTOP cannot be ignored/forwarded by userspace. + switch s { + case syscall.SIGCHLD, syscall.SIGPIPE, syscall.SIGURG, syscall.SIGSTOP: + return true + default: + return false + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/signal/signal_unix.go b/vendor/github.com/containers/podman/v5/pkg/signal/signal_unix.go new file mode 100644 index 00000000..88b0a55c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/signal/signal_unix.go @@ -0,0 +1,102 @@ +//go:build aix || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || zos + +// Signal handling for Linux only. +package signal + +import ( + "syscall" +) + +const ( + sigrtmin = 34 + sigrtmax = 64 + + SIGWINCH = syscall.SIGWINCH +) + +// SignalMap is a map of Linux signals. +// These constants are sourced from the Linux version of golang.org/x/sys/unix +// (I don't see much risk of this changing). +// This should work as long as Podman only runs containers on Linux, which seems +// a safe assumption for now. +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.Signal(0x6), + "ALRM": syscall.Signal(0xe), + "BUS": syscall.Signal(0x7), + "CHLD": syscall.Signal(0x11), + "CLD": syscall.Signal(0x11), + "CONT": syscall.Signal(0x12), + "FPE": syscall.Signal(0x8), + "HUP": syscall.Signal(0x1), + "ILL": syscall.Signal(0x4), + "INT": syscall.Signal(0x2), + "IO": syscall.Signal(0x1d), + "IOT": syscall.Signal(0x6), + "KILL": syscall.Signal(0x9), + "PIPE": syscall.Signal(0xd), + "POLL": syscall.Signal(0x1d), + "PROF": syscall.Signal(0x1b), + "PWR": syscall.Signal(0x1e), + "QUIT": syscall.Signal(0x3), + "SEGV": syscall.Signal(0xb), + "STKFLT": syscall.Signal(0x10), + "STOP": syscall.Signal(0x13), + "SYS": syscall.Signal(0x1f), + "TERM": syscall.Signal(0xf), + "TRAP": syscall.Signal(0x5), + "TSTP": syscall.Signal(0x14), + "TTIN": syscall.Signal(0x15), + "TTOU": syscall.Signal(0x16), + "URG": syscall.Signal(0x17), + "USR1": syscall.Signal(0xa), + "USR2": syscall.Signal(0xc), + "VTALRM": syscall.Signal(0x1a), + "WINCH": syscall.Signal(0x1c), + "XCPU": syscall.Signal(0x18), + "XFSZ": syscall.Signal(0x19), + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, +} + +// isSignalIgnoredBySigProxy determines whether sig-proxy should ignore syscall signal +func isSignalIgnoredBySigProxy(s syscall.Signal) bool { + // Ignore SIGCHLD and SIGPIPE - these are most likely intended for the podman command itself. + // SIGURG was added because of golang 1.14 and its preemptive changes causing more signals to "show up". + // https://github.com/containers/podman/issues/5483 + // SIGSTOP cannot be ignored/forwarded by userspace. + switch s { + case syscall.SIGCHLD, syscall.SIGPIPE, syscall.SIGURG, syscall.SIGSTOP: + return true + default: + return false + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/signal/signal_unsupported.go b/vendor/github.com/containers/podman/v5/pkg/signal/signal_unsupported.go new file mode 100644 index 00000000..3c0e187f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/signal/signal_unsupported.go @@ -0,0 +1,94 @@ +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !zos + +// Signal handling for Linux only. +package signal + +import ( + "syscall" +) + +const ( + sigrtmin = 34 + sigrtmax = 64 + + SIGWINCH = syscall.Signal(0xff) +) + +// SignalMap is a map of Linux signals. +// These constants are sourced from the Linux version of golang.org/x/sys/unix +// (I don't see much risk of this changing). +// This should work as long as Podman only runs containers on Linux, which seems +// a safe assumption for now. +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.Signal(0x6), + "ALRM": syscall.Signal(0xe), + "BUS": syscall.Signal(0x7), + "CHLD": syscall.Signal(0x11), + "CLD": syscall.Signal(0x11), + "CONT": syscall.Signal(0x12), + "FPE": syscall.Signal(0x8), + "HUP": syscall.Signal(0x1), + "ILL": syscall.Signal(0x4), + "INT": syscall.Signal(0x2), + "IO": syscall.Signal(0x1d), + "IOT": syscall.Signal(0x6), + "KILL": syscall.Signal(0x9), + "PIPE": syscall.Signal(0xd), + "POLL": syscall.Signal(0x1d), + "PROF": syscall.Signal(0x1b), + "PWR": syscall.Signal(0x1e), + "QUIT": syscall.Signal(0x3), + "SEGV": syscall.Signal(0xb), + "STKFLT": syscall.Signal(0x10), + "STOP": syscall.Signal(0x13), + "SYS": syscall.Signal(0x1f), + "TERM": syscall.Signal(0xf), + "TRAP": syscall.Signal(0x5), + "TSTP": syscall.Signal(0x14), + "TTIN": syscall.Signal(0x15), + "TTOU": syscall.Signal(0x16), + "URG": syscall.Signal(0x17), + "USR1": syscall.Signal(0xa), + "USR2": syscall.Signal(0xc), + "VTALRM": syscall.Signal(0x1a), + "WINCH": syscall.Signal(0x1c), + "XCPU": syscall.Signal(0x18), + "XFSZ": syscall.Signal(0x19), + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, +} + +// isSignalIgnoredBySigProxy determines whether to sig-proxy should ignore syscall signal +// keep the container running or not. In unsupported OS this should not ignore any syscall signal. +func isSignalIgnoredBySigProxy(_ syscall.Signal) bool { + return false +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/container_validate.go b/vendor/github.com/containers/podman/v5/pkg/specgen/container_validate.go new file mode 100644 index 00000000..25193259 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/container_validate.go @@ -0,0 +1,159 @@ +package specgen + +import ( + "errors" + "fmt" + "slices" + "strings" + + "github.com/containers/podman/v5/libpod/define" +) + +var ( + // ErrInvalidSpecConfig describes an error that the given SpecGenerator is invalid + ErrInvalidSpecConfig = errors.New("invalid configuration") + // SystemDValues describes the only values that SystemD can be + SystemDValues = []string{"true", "false", "always"} + // ImageVolumeModeValues describes the only values that ImageVolumeMode can be + ImageVolumeModeValues = []string{"ignore", define.TypeTmpfs, "anonymous"} +) + +func exclusiveOptions(opt1, opt2 string) error { + return fmt.Errorf("%s and %s are mutually exclusive options", opt1, opt2) +} + +// Validate verifies that the given SpecGenerator is valid and satisfies required +// input for creating a container. +func (s *SpecGenerator) Validate() error { + // Containers being added to a pod cannot have certain network attributes + // associated with them because those should be on the infra container. + if len(s.Pod) > 0 && s.NetNS.NSMode == FromPod { + if len(s.Networks) > 0 { + return fmt.Errorf("networks must be defined when the pod is created: %w", define.ErrNetworkOnPodContainer) + } + if len(s.PortMappings) > 0 || (s.PublishExposedPorts != nil && *s.PublishExposedPorts) { + return fmt.Errorf("published or exposed ports must be defined when the pod is created: %w", define.ErrNetworkOnPodContainer) + } + if len(s.HostAdd) > 0 { + return fmt.Errorf("extra host entries must be specified on the pod: %w", define.ErrNetworkOnPodContainer) + } + } + + if s.NetNS.IsContainer() && len(s.HostAdd) > 0 { + return fmt.Errorf("cannot set extra host entries when the container is joined to another containers network namespace: %w", ErrInvalidSpecConfig) + } + + // + // ContainerBasicConfig + // + // Rootfs and Image cannot both populated + if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 { + return fmt.Errorf("both image and rootfs cannot be simultaneously: %w", ErrInvalidSpecConfig) + } + // Cannot set hostname and utsns + if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() { + if s.ContainerBasicConfig.UtsNS.IsPod() { + return fmt.Errorf("cannot set hostname when joining the pod UTS namespace: %w", ErrInvalidSpecConfig) + } + + return fmt.Errorf("cannot set hostname when running in the host UTS namespace: %w", ErrInvalidSpecConfig) + } + // systemd values must be true, false, or always + if len(s.ContainerBasicConfig.Systemd) > 0 && !slices.Contains(SystemDValues, strings.ToLower(s.ContainerBasicConfig.Systemd)) { + return fmt.Errorf("--systemd values must be one of %q: %w", strings.Join(SystemDValues, ", "), ErrInvalidSpecConfig) + } + + if err := define.ValidateSdNotifyMode(s.ContainerBasicConfig.SdNotifyMode); err != nil { + return err + } + + // + // ContainerStorageConfig + // + // rootfs and image cannot both be set + if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 { + return exclusiveOptions("rootfs", "image") + } + // imagevolumemode must be one of ignore, tmpfs, or anonymous if given + if len(s.ContainerStorageConfig.ImageVolumeMode) > 0 && !slices.Contains(ImageVolumeModeValues, strings.ToLower(s.ContainerStorageConfig.ImageVolumeMode)) { + return fmt.Errorf("invalid ImageVolumeMode %q, value must be one of %s", + s.ContainerStorageConfig.ImageVolumeMode, strings.Join(ImageVolumeModeValues, ",")) + } + // shmsize conflicts with IPC namespace + if s.ContainerStorageConfig.ShmSize != nil && (s.ContainerStorageConfig.IpcNS.IsHost() || s.ContainerStorageConfig.IpcNS.IsNone()) { + return fmt.Errorf("cannot set shmsize when running in the %s IPC Namespace", s.ContainerStorageConfig.IpcNS) + } + + // + // ContainerSecurityConfig + // + // userns and idmappings conflict + if s.UserNS.IsPrivate() && s.IDMappings == nil { + return fmt.Errorf("IDMappings are required when not creating a User namespace: %w", ErrInvalidSpecConfig) + } + + // + // ContainerCgroupConfig + // + // + // None for now + + // + // ContainerNetworkConfig + // + // useimageresolveconf conflicts with dnsserver, dnssearch, dnsoption + if s.UseImageResolvConf != nil && *s.UseImageResolvConf { + if len(s.DNSServers) > 0 { + return exclusiveOptions("UseImageResolvConf", "DNSServer") + } + if len(s.DNSSearch) > 0 { + return exclusiveOptions("UseImageResolvConf", "DNSSearch") + } + if len(s.DNSOptions) > 0 { + return exclusiveOptions("UseImageResolvConf", "DNSOption") + } + } + // UseImageHosts and HostAdd are exclusive + if (s.UseImageHosts != nil && *s.UseImageHosts) && len(s.HostAdd) > 0 { + return exclusiveOptions("UseImageHosts", "HostAdd") + } + + // TODO the specgen does not appear to handle this? Should it + // switch config.Cgroup.Cgroups { + // case "disabled": + // if addedResources { + // return errors.New("cannot specify resource limits when cgroups are disabled is specified") + // } + // configSpec.Linux.Resources = &spec.LinuxResources{} + // case "enabled", "no-conmon", "": + // // Do nothing + // default: + // return errors.New("unrecognized option for cgroups; supported are 'default', 'disabled', 'no-conmon'") + // } + // Namespaces + if err := s.UtsNS.validate(); err != nil { + return err + } + if err := validateIPCNS(&s.IpcNS); err != nil { + return err + } + if err := s.PidNS.validate(); err != nil { + return err + } + if err := s.CgroupNS.validate(); err != nil { + return err + } + if err := validateUserNS(&s.UserNS); err != nil { + return err + } + + if err := validateNetNS(&s.NetNS); err != nil { + return err + } + if s.NetNS.NSMode != Bridge && len(s.Networks) > 0 { + // Note that we also get the ip and mac in the networks map + return errors.New("networks and static ip/mac address can only be used with Bridge mode networking") + } + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/namespaces.go b/vendor/github.com/containers/podman/v5/pkg/specgen/namespaces.go new file mode 100644 index 00000000..d3cad60f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/namespaces.go @@ -0,0 +1,565 @@ +package specgen + +import ( + "errors" + "fmt" + "net" + "slices" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/namespaces" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/util" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/cgroups" + "go.podman.io/common/pkg/config" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/unshare" + storageTypes "go.podman.io/storage/types" +) + +type NamespaceMode string + +const ( + // Default indicates the spec generator should determine + // a sane default + Default NamespaceMode = "default" + // Host means the namespace is derived from the host + Host NamespaceMode = "host" + // Path is the path to a namespace + Path NamespaceMode = "path" + // FromContainer means namespace is derived from a + // different container + FromContainer NamespaceMode = "container" + // FromPod indicates the namespace is derived from a pod + FromPod NamespaceMode = "pod" + // Private indicates the namespace is private + Private NamespaceMode = "private" + // Shareable indicates the namespace is shareable + Shareable NamespaceMode = "shareable" + // None indicates the IPC namespace is created without mounting /dev/shm + None NamespaceMode = "none" + // NoNetwork indicates no network namespace should + // be joined. loopback should still exist. + // Only used with the network namespace, invalid otherwise. + NoNetwork NamespaceMode = "none" + // Bridge indicates that the network backend (CNI/netavark) + // should be used. + // Only used with the network namespace, invalid otherwise. + Bridge NamespaceMode = "bridge" + // Slirp indicates that a slirp4netns network stack should + // be used. + // Only used with the network namespace, invalid otherwise. + Slirp NamespaceMode = "slirp4netns" + // Pasta indicates that a pasta network stack should be used. + // Only used with the network namespace, invalid otherwise. + Pasta NamespaceMode = "pasta" + // KeepID indicates a user namespace to keep the owner uid inside + // of the namespace itself. + // Only used with the user namespace, invalid otherwise. + KeepID NamespaceMode = "keep-id" + // NoMap indicates a user namespace to keep the owner uid out + // of the namespace itself. + // Only used with the user namespace, invalid otherwise. + NoMap NamespaceMode = "no-map" + // Auto indicates to automatically create a user namespace. + // Only used with the user namespace, invalid otherwise. + Auto NamespaceMode = "auto" + + // DefaultKernelNamespaces is a comma-separated list of default kernel + // namespaces. + DefaultKernelNamespaces = "ipc,net,uts" +) + +// Namespace describes the namespace +type Namespace struct { + NSMode NamespaceMode `json:"nsmode,omitempty"` + Value string `json:"value,omitempty"` +} + +// IsDefault returns whether the namespace is set to the default setting (which +// also includes the empty string). +func (n *Namespace) IsDefault() bool { + return n.NSMode == Default || n.NSMode == "" +} + +// IsHost returns a bool if the namespace is host based +func (n *Namespace) IsHost() bool { + return n.NSMode == Host +} + +// IsNone returns a bool if the namespace is set to none +func (n *Namespace) IsNone() bool { + return n.NSMode == None +} + +// IsBridge returns a bool if the namespace is a Bridge +func (n *Namespace) IsBridge() bool { + return n.NSMode == Bridge +} + +// IsPath indicates via bool if the namespace is based on a path +func (n *Namespace) IsPath() bool { + return n.NSMode == Path +} + +// IsContainer indicates via bool if the namespace is based on a container +func (n *Namespace) IsContainer() bool { + return n.NSMode == FromContainer +} + +// IsPod indicates via bool if the namespace is based on a pod +func (n *Namespace) IsPod() bool { + return n.NSMode == FromPod +} + +// IsPrivate indicates the namespace is private +func (n *Namespace) IsPrivate() bool { + return n.NSMode == Private +} + +// IsAuto indicates the namespace is auto +func (n *Namespace) IsAuto() bool { + return n.NSMode == Auto +} + +// IsKeepID indicates the namespace is KeepID +func (n *Namespace) IsKeepID() bool { + return n.NSMode == KeepID +} + +// IsNoMap indicates the namespace is NoMap +func (n *Namespace) IsNoMap() bool { + return n.NSMode == NoMap +} + +func (n *Namespace) String() string { + if n.Value != "" { + return fmt.Sprintf("%s:%s", n.NSMode, n.Value) + } + return string(n.NSMode) +} + +func validateUserNS(n *Namespace) error { + if n == nil { + return nil + } + switch n.NSMode { + case Auto, KeepID, NoMap: + return nil + } + return n.validate() +} + +func validateNetNS(n *Namespace) error { + if n == nil { + return nil + } + switch n.NSMode { + case Slirp: + break + case Pasta: + // Check if we run rootless/in a userns. Do not use rootless.IsRootless() here. + // Pasta switches to nobody when running as root which causes it to fail while + // opening the netns owned by root. However when pasta is already in a userns + // it doesn't switch to nobody so it works there. + // https://github.com/containers/podman/issues/17840 + if unshare.IsRootless() { + break + } + return fmt.Errorf("pasta networking is only supported for rootless mode or when inside a nested userns") + case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge: + break + default: + return fmt.Errorf("invalid network %q", n.NSMode) + } + + // Path and From Container MUST have a string value set + if n.NSMode == Path || n.NSMode == FromContainer { + if len(n.Value) < 1 { + return fmt.Errorf("namespace mode %s requires a value", n.NSMode) + } + } else if n.NSMode != Slirp { + // All others except must NOT set a string value + if len(n.Value) > 0 { + return fmt.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode) + } + } + + return nil +} + +func validateIPCNS(n *Namespace) error { + if n == nil { + return nil + } + switch n.NSMode { + case Shareable, None: + return nil + } + return n.validate() +} + +// Validate perform simple validation on the namespace to make sure it is not +// invalid from the get-go +func (n *Namespace) validate() error { + if n == nil { + return nil + } + switch n.NSMode { + case "", Default, Host, Path, FromContainer, FromPod, Private: + // Valid, do nothing + case NoNetwork, Bridge, Slirp, Pasta: + return errors.New("cannot use network modes with non-network namespace") + default: + return fmt.Errorf("invalid namespace type %s specified", n.NSMode) + } + + // Path and From Container MUST have a string value set + if n.NSMode == Path || n.NSMode == FromContainer { + if len(n.Value) < 1 { + return fmt.Errorf("namespace mode %s requires a value", n.NSMode) + } + } else { + // All others must NOT set a string value + if len(n.Value) > 0 { + return fmt.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode) + } + } + return nil +} + +// ParseNamespace parses a namespace in string form. +// This is not intended for the network namespace, which has a separate +// function. +func ParseNamespace(ns string) (Namespace, error) { + toReturn := Namespace{} + switch ns { + case "pod": + toReturn.NSMode = FromPod + case "host": + toReturn.NSMode = Host + case "private", "": + toReturn.NSMode = Private + default: + if value, ok := strings.CutPrefix(ns, "ns:"); ok { + toReturn.NSMode = Path + toReturn.Value = value + } else if value, ok := strings.CutPrefix(ns, "container:"); ok { + toReturn.NSMode = FromContainer + toReturn.Value = value + } else { + return toReturn, fmt.Errorf("unrecognized namespace mode %s passed", ns) + } + } + + return toReturn, nil +} + +// ParseCgroupNamespace parses a cgroup namespace specification in string +// form. +func ParseCgroupNamespace(ns string) (Namespace, error) { + toReturn := Namespace{} + // Cgroup is host for v1, private for v2. + // We can't trust c/common for this, as it only assumes private. + cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return toReturn, err + } + if cgroupsv2 { + switch ns { + case "host": + toReturn.NSMode = Host + case "private", "": + toReturn.NSMode = Private + default: + return toReturn, fmt.Errorf("unrecognized cgroup namespace mode %s passed", ns) + } + } else { + toReturn.NSMode = Host + } + return toReturn, nil +} + +// ParseIPCNamespace parses an ipc namespace specification in string +// form. +func ParseIPCNamespace(ns string) (Namespace, error) { + toReturn := Namespace{} + switch ns { + case "shareable", "": + toReturn.NSMode = Shareable + return toReturn, nil + case "none": + toReturn.NSMode = None + return toReturn, nil + } + return ParseNamespace(ns) +} + +// ParseUserNamespace parses a user namespace specification in string +// form. +func ParseUserNamespace(ns string) (Namespace, error) { + toReturn := Namespace{} + switch ns { + case "auto": + toReturn.NSMode = Auto + return toReturn, nil + case "keep-id": + toReturn.NSMode = KeepID + return toReturn, nil + case "nomap": + toReturn.NSMode = NoMap + return toReturn, nil + case "": + toReturn.NSMode = Host + return toReturn, nil + default: + if value, ok := strings.CutPrefix(ns, "auto:"); ok { + toReturn.NSMode = Auto + toReturn.Value = value + return toReturn, nil + } else if value, ok := strings.CutPrefix(ns, "keep-id:"); ok { + toReturn.NSMode = KeepID + toReturn.Value = value + return toReturn, nil + } else { + return ParseNamespace(ns) + } + } +} + +// ParseNetworkFlag parses a network string slice into the network options +// If the input is nil or empty it will use the default setting from containers.conf +func ParseNetworkFlag(networks []string) (Namespace, map[string]types.PerNetworkOptions, map[string][]string, error) { + var networkOptions map[string][]string + toReturn := Namespace{} + // by default we try to use the containers.conf setting + // if we get at least one value use this instead + cfg, err := config.Default() + if err != nil { + return toReturn, nil, nil, err + } + ns := cfg.Containers.NetNS + if len(networks) > 0 { + ns = networks[0] + } + + podmanNetworks := make(map[string]types.PerNetworkOptions) + + switch { + case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"): + key, options, hasOptions := strings.Cut(ns, ":") + if hasOptions { + networkOptions = make(map[string][]string) + networkOptions[key] = strings.Split(options, ",") + } + toReturn.NSMode = Slirp + case ns == string(FromPod): + toReturn.NSMode = FromPod + case ns == "" || ns == string(Default) || ns == string(Private): + toReturn.NSMode = Private + case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"): + toReturn.NSMode = Bridge + _, options, hasOptions := strings.Cut(ns, ":") + netOpts := types.PerNetworkOptions{} + if hasOptions { + var err error + netOpts, err = parseBridgeNetworkOptions(options) + if err != nil { + return toReturn, nil, nil, err + } + } + // we have to set the special default network name here + podmanNetworks["default"] = netOpts + + case ns == string(NoNetwork): + toReturn.NSMode = NoNetwork + case ns == string(Host): + toReturn.NSMode = Host + case strings.HasPrefix(ns, "ns:"): + _, value, _ := strings.Cut(ns, ":") + toReturn.NSMode = Path + toReturn.Value = value + case strings.HasPrefix(ns, string(FromContainer)+":"): + _, value, _ := strings.Cut(ns, ":") + toReturn.NSMode = FromContainer + toReturn.Value = value + case ns == string(Pasta), strings.HasPrefix(ns, string(Pasta)+":"): + key, options, hasOptions := strings.Cut(ns, ":") + if hasOptions { + networkOptions = make(map[string][]string) + networkOptions[key] = strings.Split(options, ",") + } + toReturn.NSMode = Pasta + default: + // we should have a normal network + name, options, hasOptions := strings.Cut(ns, ":") + if hasOptions { + if name == "" { + return toReturn, nil, nil, errors.New("network name cannot be empty") + } + netOpts, err := parseBridgeNetworkOptions(options) + if err != nil { + return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", name, err) + } + podmanNetworks[name] = netOpts + } else { + // Assume we have been given a comma separated list of networks for backwards compat. + networkList := strings.SplitSeq(ns, ",") + for net := range networkList { + podmanNetworks[net] = types.PerNetworkOptions{} + } + } + + // networks need bridge mode + toReturn.NSMode = Bridge + } + + if len(networks) > 1 { + if !toReturn.IsBridge() { + return toReturn, nil, nil, fmt.Errorf("cannot set multiple networks without bridge network mode, selected mode %s: %w", toReturn.NSMode, define.ErrInvalidArg) + } + + for _, network := range networks[1:] { + name, options, hasOptions := strings.Cut(network, ":") + if name == "" { + return toReturn, nil, nil, fmt.Errorf("network name cannot be empty: %w", define.ErrInvalidArg) + } + if slices.Contains([]string{string(Bridge), string(Slirp), string(Pasta), string(FromPod), string(NoNetwork), + string(Default), string(Private), string(Path), string(FromContainer), string(Host)}, name) { + return toReturn, nil, nil, fmt.Errorf("can only set extra network names, selected mode %s conflicts with bridge: %w", name, define.ErrInvalidArg) + } + netOpts := types.PerNetworkOptions{} + if hasOptions { + var err error + netOpts, err = parseBridgeNetworkOptions(options) + if err != nil { + return toReturn, nil, nil, fmt.Errorf("invalid option for network %s: %w", name, err) + } + } + podmanNetworks[name] = netOpts + } + } + + return toReturn, podmanNetworks, networkOptions, nil +} + +func parseBridgeNetworkOptions(opts string) (types.PerNetworkOptions, error) { + netOpts := types.PerNetworkOptions{} + if len(opts) == 0 { + return netOpts, nil + } + allopts := strings.SplitSeq(opts, ",") + for opt := range allopts { + name, value, _ := strings.Cut(opt, "=") + switch name { + case "ip", "ip6": + ip := net.ParseIP(value) + if ip == nil { + return netOpts, fmt.Errorf("invalid ip address %q", value) + } + netOpts.StaticIPs = append(netOpts.StaticIPs, ip) + + case "mac": + mac, err := net.ParseMAC(value) + if err != nil { + return netOpts, err + } + netOpts.StaticMAC = types.HardwareAddr(mac) + + case "alias": + if value == "" { + return netOpts, errors.New("alias cannot be empty") + } + netOpts.Aliases = append(netOpts.Aliases, value) + + case "interface_name": + if value == "" { + return netOpts, errors.New("interface_name cannot be empty") + } + netOpts.InterfaceName = value + + default: + if netOpts.Options == nil { + netOpts.Options = make(map[string]string) + } + netOpts.Options[name] = value + } + } + return netOpts, nil +} + +func SetupUserNS(idmappings *storageTypes.IDMappingOptions, userns Namespace, g *generate.Generator) (string, error) { + // User + var user string + switch userns.NSMode { + case Path: + if err := fileutils.Exists(userns.Value); err != nil { + return user, fmt.Errorf("cannot find specified user namespace path: %w", err) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), userns.Value); err != nil { + return user, err + } + case Host: + if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil { + return user, err + } + case KeepID: + opts, err := namespaces.UsernsMode(userns.String()).GetKeepIDOptions() + if err != nil { + return user, err + } + if opts.MaxSize != nil && !rootless.IsRootless() { + return user, fmt.Errorf("cannot set max size for user namespace when not running rootless") + } + mappings, uid, gid, err := util.GetKeepIDMapping(opts) + if err != nil { + return user, err + } + idmappings = mappings + g.SetProcessUID(uint32(uid)) + g.SetProcessGID(uint32(gid)) + g.AddProcessAdditionalGid(uint32(gid)) + user = fmt.Sprintf("%d:%d", uid, gid) + if err := privateUserNamespace(idmappings, g); err != nil { + return user, err + } + case NoMap: + mappings, uid, gid, err := util.GetNoMapMapping() + if err != nil { + return user, err + } + idmappings = mappings + g.SetProcessUID(uint32(uid)) + g.SetProcessGID(uint32(gid)) + g.AddProcessAdditionalGid(uint32(gid)) + user = fmt.Sprintf("%d:%d", uid, gid) + if err := privateUserNamespace(idmappings, g); err != nil { + return user, err + } + case Private: + if err := privateUserNamespace(idmappings, g); err != nil { + return user, err + } + } + return user, nil +} + +func privateUserNamespace(idmappings *storageTypes.IDMappingOptions, g *generate.Generator) error { + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + if idmappings == nil || (len(idmappings.UIDMap) == 0 && len(idmappings.GIDMap) == 0) { + return errors.New("must provide at least one UID or GID mapping to configure a user namespace") + } + for _, uidmap := range idmappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + for _, gidmap := range idmappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/pod_validate.go b/vendor/github.com/containers/podman/v5/pkg/specgen/pod_validate.go new file mode 100644 index 00000000..a2732eeb --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/pod_validate.go @@ -0,0 +1,95 @@ +package specgen + +import ( + "errors" + "fmt" +) + +var ( + // ErrInvalidPodSpecConfig describes an error given when the podspecgenerator is invalid + ErrInvalidPodSpecConfig = errors.New("invalid pod spec") + // containerConfig has the default configurations defined in containers.conf +) + +func exclusivePodOptions(opt1, opt2 string) error { + return fmt.Errorf("%s and %s are mutually exclusive pod options: %w", opt1, opt2, ErrInvalidPodSpecConfig) +} + +// Validate verifies the input is valid +func (p *PodSpecGenerator) Validate() error { + // PodBasicConfig + if p.NoInfra { + if len(p.InfraCommand) > 0 { + return exclusivePodOptions("NoInfra", "InfraCommand") + } + if len(p.InfraImage) > 0 { + return exclusivePodOptions("NoInfra", "InfraImage") + } + if len(p.InfraName) > 0 { + return exclusivePodOptions("NoInfra", "InfraName") + } + if len(p.SharedNamespaces) > 0 { + return exclusivePodOptions("NoInfra", "SharedNamespaces") + } + } + + // PodNetworkConfig + if err := validateNetNS(&p.NetNS); err != nil { + return err + } + if p.NoInfra { + if p.NetNS.NSMode != Default && p.NetNS.NSMode != "" { + return errors.New("NoInfra and network modes cannot be used together") + } + // Note that networks might be set when --ip or --mac was set + // so we need to check that no networks are set without the infra + if len(p.Networks) > 0 { + return errors.New("cannot set networks options without infra container") + } + if len(p.DNSOption) > 0 { + return exclusivePodOptions("NoInfra", "DNSOption") + } + if len(p.DNSSearch) > 0 { + return exclusivePodOptions("NoInfo", "DNSSearch") + } + if len(p.DNSServer) > 0 { + return exclusivePodOptions("NoInfra", "DNSServer") + } + if len(p.HostAdd) > 0 { + return exclusivePodOptions("NoInfra", "HostAdd") + } + if len(p.HostsFile) > 0 { + return exclusivePodOptions("NoInfra", "HostsFile") + } + if p.NoManageResolvConf { + return exclusivePodOptions("NoInfra", "NoManageResolvConf") + } + } + if p.NetNS.NSMode != "" && p.NetNS.NSMode != Bridge && p.NetNS.NSMode != Slirp && p.NetNS.NSMode != Pasta && p.NetNS.NSMode != Default { + if len(p.PortMappings) > 0 { + return errors.New("PortMappings can only be used with Bridge, slirp4netns, or pasta networking") + } + } + + if p.NoManageResolvConf { + if len(p.DNSServer) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSServer") + } + if len(p.DNSSearch) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSSearch") + } + if len(p.DNSOption) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSOption") + } + } + if p.NoManageHosts { + if len(p.HostAdd) > 0 { + return exclusivePodOptions("NoManageHosts", "HostAdd") + } + if len(p.HostsFile) > 0 { + return exclusivePodOptions("NoManageHosts", "HostsFile") + } + } + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/podspecgen.go b/vendor/github.com/containers/podman/v5/pkg/specgen/podspecgen.go new file mode 100644 index 00000000..ad1f6267 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/podspecgen.go @@ -0,0 +1,264 @@ +package specgen + +import ( + "net" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/common/libnetwork/types" + storageTypes "go.podman.io/storage/types" +) + +// PodBasicConfig contains basic configuration options for pods. +type PodBasicConfig struct { + // Name is the name of the pod. + // If not provided, a name will be generated when the pod is created. + // Optional. + Name string `json:"name,omitempty"` + // Hostname is the pod's hostname. If not set, the name of the pod will + // be used (if a name was not provided here, the name auto-generated for + // the pod will be used). This will be used by the infra container and + // all containers in the pod as long as the UTS namespace is shared. + // Optional. + Hostname string `json:"hostname,omitempty"` + // ExitPolicy determines the pod's exit and stop behaviour. + ExitPolicy string `json:"exit_policy,omitempty"` + // Labels are key-value pairs that are used to add metadata to pods. + // Optional. + Labels map[string]string `json:"labels,omitempty"` + // NoInfra tells the pod not to create an infra container. If this is + // done, many networking-related options will become unavailable. + // Conflicts with setting any options in PodNetworkConfig, and the + // InfraCommand and InfraImages in this struct. + // Optional. + NoInfra bool `json:"no_infra,omitempty"` + // InfraConmonPidFile is a custom path to store the infra container's + // conmon PID. + InfraConmonPidFile string `json:"infra_conmon_pid_file,omitempty"` + // InfraCommand sets the command that will be used to start the infra + // container. + // If not set, the default set in the Libpod configuration file will be + // used. + // Conflicts with NoInfra=true. + // Optional. + InfraCommand []string `json:"infra_command,omitempty"` + // InfraImage is the image that will be used for the infra container. + // If not set, the default set in the Libpod configuration file will be + // used. + // Conflicts with NoInfra=true. + // Optional. + InfraImage string `json:"infra_image,omitempty"` + // InfraName is the name that will be used for the infra container. + // If not set, the default set in the Libpod configuration file will be + // used. + // Conflicts with NoInfra=true. + // Optional. + InfraName string `json:"infra_name,omitempty"` + // Ipc sets the IPC namespace of the pod, set to private by default. + // This configuration will then be shared with the entire pod if PID namespace sharing is enabled via --share + Ipc Namespace `json:"ipcns"` + // SharedNamespaces instructs the pod to share a set of namespaces. + // Shared namespaces will be joined (by default) by every container + // which joins the pod. + // If not set and NoInfra is false, the pod will set a default set of + // namespaces to share. + // Conflicts with NoInfra=true. + // Optional. + SharedNamespaces []string `json:"shared_namespaces,omitempty"` + // RestartPolicy is the pod's restart policy - an action which + // will be taken when one or all the containers in the pod exits. + // If not given, the default policy will be set to Always, which + // restarts the containers in the pod when they exit indefinitely. + // Optional. + RestartPolicy string `json:"restart_policy,omitempty"` + // RestartRetries is the number of attempts that will be made to restart + // the container. + // Only available when RestartPolicy is set to "on-failure". + // Optional. + RestartRetries *uint `json:"restart_tries,omitempty"` + // PodCreateCommand is the command used to create this pod. + // This will be shown in the output of Inspect() on the pod, and may + // also be used by some tools that wish to recreate the pod + // (e.g. `podman generate systemd --new`). + // Optional. + // ShareParent determines if all containers in the pod will share the pod's cgroup as the cgroup parent + ShareParent *bool `json:"share_parent,omitempty"` + PodCreateCommand []string `json:"pod_create_command,omitempty"` + // Pid sets the process id namespace of the pod + // Optional (defaults to private if unset). This sets the PID namespace of the infra container + // This configuration will then be shared with the entire pod if PID namespace sharing is enabled via --share + Pid Namespace `json:"pidns"` + // Userns is used to indicate which kind of Usernamespace to enter. + // Any containers created within the pod will inherit the pod's userns settings. + // Optional + Userns Namespace `json:"userns"` + // UtsNs is used to indicate the UTS mode the pod is in + UtsNs Namespace `json:"utsns"` + // Devices contains user specified Devices to be added to the Pod + Devices []string `json:"pod_devices,omitempty"` + // Sysctl sets kernel parameters for the pod + Sysctl map[string]string `json:"sysctl,omitempty"` +} + +// PodNetworkConfig contains networking configuration for a pod. +type PodNetworkConfig struct { + // NetNS is the configuration to use for the infra container's network + // namespace. This network will, by default, be shared with all + // containers in the pod. + // Cannot be set to FromContainer and FromPod. + // Setting this to anything except default conflicts with NoInfra=true. + // Defaults to Bridge as root and Slirp as rootless. + // Mandatory. + NetNS Namespace `json:"netns"` + // PortMappings is a set of ports to map into the infra container. + // As, by default, containers share their network with the infra + // container, this will forward the ports to the entire pod. + // Only available if NetNS is set to Bridge, Slirp, or Pasta. + // Optional. + PortMappings []types.PortMapping `json:"portmappings,omitempty"` + // Map of networks names to ids the container should join to. + // You can request additional settings for each network, you can + // set network aliases, static ips, static mac address and the + // network interface name for this container on the specific network. + // If the map is empty and the bridge network mode is set the container + // will be joined to the default network. + Networks map[string]types.PerNetworkOptions + // CNINetworks is a list of CNI networks to join the container to. + // If this list is empty, the default CNI network will be joined + // instead. If at least one entry is present, we will not join the + // default network (unless it is part of this list). + // Only available if NetNS is set to bridge. + // Optional. + // Deprecated: as of podman 4.0 use "Networks" instead. + CNINetworks []string `json:"cni_networks,omitempty"` + // NoManageResolvConf indicates that /etc/resolv.conf should not be + // managed by the pod. Instead, each container will create and manage a + // separate resolv.conf as if they had not joined a pod. + // Conflicts with NoInfra=true and DNSServer, DNSSearch, DNSOption. + // Optional. + NoManageResolvConf bool `json:"no_manage_resolv_conf,omitempty"` + // DNSServer is a set of DNS servers that will be used in the infra + // container's resolv.conf, which will, by default, be shared with all + // containers in the pod. + // If not provided, the host's DNS servers will be used, unless the only + // server set is a localhost address. As the container cannot connect to + // the host's localhost, a default server will instead be set. + // Conflicts with NoInfra=true. + // Optional. + DNSServer []net.IP `json:"dns_server,omitempty"` + // DNSSearch is a set of DNS search domains that will be used in the + // infra container's resolv.conf, which will, by default, be shared with + // all containers in the pod. + // If not provided, DNS search domains from the host's resolv.conf will + // be used. + // Conflicts with NoInfra=true. + // Optional. + DNSSearch []string `json:"dns_search,omitempty"` + // DNSOption is a set of DNS options that will be used in the infra + // container's resolv.conf, which will, by default, be shared with all + // containers in the pod. + // Conflicts with NoInfra=true. + // Optional. + DNSOption []string `json:"dns_option,omitempty"` + // NoManageHostname indicates that /etc/hostname should not be managed + // by the pod. Instead, each container will create a separate + // /etc/hostname as they would if not in a pod. + NoManageHostname bool `json:"no_manage_hostname,omitempty"` + // NoManageHosts indicates that /etc/hosts should not be managed by the + // pod. Instead, each container will create a separate /etc/hosts as + // they would if not in a pod. + // Conflicts with HostAdd. + NoManageHosts bool `json:"no_manage_hosts,omitempty"` + // HostAdd is a set of hosts that will be added to the infra container's + // /etc/hosts that will, by default, be shared with all containers in + // the pod. + // Conflicts with NoInfra=true and NoManageHosts. + // Optional. + HostAdd []string `json:"hostadd,omitempty"` + // HostsFile is the base file to create the `/etc/hosts` file inside the infra container. + // This must either be an absolute path to a file on the host system, or one of the + // special flags `image` or `none`. + // If it is empty it defaults to the base_hosts_file configuration in containers.conf. + // Conflicts with NoInfra=true and NoManageHosts. + // Optional. + HostsFile string `json:"hostsFile,omitempty"` + // NetworkOptions are additional options for each network + // Optional. + NetworkOptions map[string][]string `json:"network_options,omitempty"` +} + +// PodStorageConfig contains all of the storage related options for the pod and its infra container. +type PodStorageConfig struct { + // Mounts are mounts that will be added to the pod. + // These will supersede Image Volumes and VolumesFrom volumes where + // there are conflicts. + // Optional. + Mounts []spec.Mount `json:"mounts,omitempty"` + // Volumes are named volumes that will be added to the pod. + // These will supersede Image Volumes and VolumesFrom volumes where + // there are conflicts. + // Optional. + Volumes []*NamedVolume `json:"volumes,omitempty"` + // Overlay volumes are named volumes that will be added to the pod. + // Optional. + OverlayVolumes []*OverlayVolume `json:"overlay_volumes,omitempty"` + // Image volumes bind-mount a container-image mount into the pod's infra container. + // Optional. + ImageVolumes []*ImageVolume `json:"image_volumes,omitempty"` + // VolumesFrom is a set of containers whose volumes will be added to + // this pod. The name or ID of the container must be provided, and + // may optionally be followed by a : and then one or more + // comma-separated options. Valid options are 'ro', 'rw', and 'z'. + // Options will be used for all volumes sourced from the container. + VolumesFrom []string `json:"volumes_from,omitempty"` + // ShmSize is the size of the tmpfs to mount in at /dev/shm, in bytes. + // Conflicts with ShmSize if IpcNS is not private. + // Optional. + ShmSize *int64 `json:"shm_size,omitempty"` + // ShmSizeSystemd is the size of systemd-specific tmpfs mounts + // specifically /run, /run/lock, /var/log/journal and /tmp. + // Optional + ShmSizeSystemd *int64 `json:"shm_size_systemd,omitempty"` +} + +// PodCgroupConfig contains configuration options about a pod's cgroups. +// This will be expanded in future updates to pods. +type PodCgroupConfig struct { + // CgroupParent is the parent for the Cgroup that the pod will create. + // This pod cgroup will, in turn, be the default cgroup parent for all + // containers in the pod. + // Optional. + CgroupParent string `json:"cgroup_parent,omitempty"` +} + +// PodSpecGenerator describes options to create a pod +// swagger:model PodSpecGenerator +type PodSpecGenerator struct { + PodBasicConfig + PodNetworkConfig + PodCgroupConfig + PodResourceConfig + PodStorageConfig + PodSecurityConfig + InfraContainerSpec *SpecGenerator `json:"-"` + + // The ID of the pod's service container. + ServiceContainerID string `json:"serviceContainerID,omitempty"` +} + +type PodResourceConfig struct { + // ResourceLimits contains linux specific CPU data for the pod + ResourceLimits *spec.LinuxResources `json:"resource_limits,omitempty"` +} + +type PodSecurityConfig struct { + SecurityOpt []string `json:"security_opt,omitempty"` + // IDMappings are UID and GID mappings that will be used by user + // namespaces. + // Required if UserNS is private. + IDMappings *storageTypes.IDMappingOptions `json:"idmappings,omitempty"` +} + +// NewPodSpecGenerator creates a new pod spec +func NewPodSpecGenerator() *PodSpecGenerator { + return &PodSpecGenerator{} +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/resources_freebsd.go b/vendor/github.com/containers/podman/v5/pkg/specgen/resources_freebsd.go new file mode 100644 index 00000000..0ec73ae9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/resources_freebsd.go @@ -0,0 +1,8 @@ +package specgen + +import ( + "go.podman.io/common/pkg/config" +) + +func (s *SpecGenerator) InitResourceLimits(_ *config.Config) { +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/resources_linux.go b/vendor/github.com/containers/podman/v5/pkg/specgen/resources_linux.go new file mode 100644 index 00000000..4b329d34 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/resources_linux.go @@ -0,0 +1,22 @@ +package specgen + +import ( + spec "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/common/pkg/config" +) + +func (s *SpecGenerator) InitResourceLimits(rtc *config.Config) { + if s.ResourceLimits == nil || s.ResourceLimits.Pids == nil { + if s.CgroupsMode != "disabled" { + limit := rtc.PidsLimit() + if limit != 0 { + if s.ResourceLimits == nil { + s.ResourceLimits = &spec.LinuxResources{} + } + s.ResourceLimits.Pids = &spec.LinuxPids{ + Limit: limit, + } + } + } + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/specgen.go b/vendor/github.com/containers/podman/v5/pkg/specgen/specgen.go new file mode 100644 index 00000000..a7437ef0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/specgen.go @@ -0,0 +1,720 @@ +package specgen + +import ( + "errors" + "net" + "strings" + "syscall" + + "github.com/containers/podman/v5/libpod/define" + spec "github.com/opencontainers/runtime-spec/specs-go" + nettypes "go.podman.io/common/libnetwork/types" + "go.podman.io/image/v5/manifest" + "go.podman.io/storage/types" +) + +// LogConfig describes the logging characteristics for a container +// swagger:model LogConfigLibpod +type LogConfig struct { + // LogDriver is the container's log driver. + // Optional. + Driver string `json:"driver,omitempty"` + // LogPath is the path the container's logs will be stored at. + // Only available if LogDriver is set to "json-file" or "k8s-file". + // Optional. + Path string `json:"path,omitempty"` + // Size is the maximum size of the log file + // Optional. + Size int64 `json:"size,omitempty"` + // A set of options to accompany the log driver. + // Optional. + Options map[string]string `json:"options,omitempty"` +} + +// ContainerBasicConfig contains the basic parts of a container. +type ContainerBasicConfig struct { + // Name is the name the container will be given. + // If no name is provided, one will be randomly generated. + // Optional. + Name string `json:"name,omitempty"` + // Pod is the ID of the pod the container will join. + // Optional. + Pod string `json:"pod,omitempty"` + // Entrypoint is the container's entrypoint. + // If not given and Image is specified, this will be populated by the + // image's configuration. + // Optional. + Entrypoint []string `json:"entrypoint,omitempty"` + // Command is the container's command. + // If not given and Image is specified, this will be populated by the + // image's configuration. + // Optional. + Command []string `json:"command,omitempty"` + // EnvHost indicates that the host environment should be added to container + // Optional. + EnvHost *bool `json:"env_host,omitempty"` + // EnvHTTPProxy indicates that the http host proxy environment variables + // should be added to container + // Optional. + HTTPProxy *bool `json:"httpproxy,omitempty"` + // Env is a set of environment variables that will be set in the + // container. + // Optional. + Env map[string]string `json:"env,omitempty"` + // Terminal is whether the container will create a PTY. + // Optional. + Terminal *bool `json:"terminal,omitempty"` + // Stdin is whether the container will keep its STDIN open. + // Optional. + Stdin *bool `json:"stdin,omitempty"` + // Labels are key-value pairs that are used to add metadata to + // containers. + // Optional. + Labels map[string]string `json:"labels,omitempty"` + // Annotations are key-value options passed into the container runtime + // that can be used to trigger special behavior. + // Optional. + Annotations map[string]string `json:"annotations,omitempty"` + // StopSignal is the signal that will be used to stop the container. + // Must be a non-zero integer below SIGRTMAX. + // If not provided, the default, SIGTERM, will be used. + // Will conflict with Systemd if Systemd is set to "true" or "always". + // Optional. + StopSignal *syscall.Signal `json:"stop_signal,omitempty"` + // StopTimeout is a timeout between the container's stop signal being + // sent and SIGKILL being sent. + // If not provided, the default will be used. + // If 0 is used, stop signal will not be sent, and SIGKILL will be sent + // instead. + // Optional. + StopTimeout *uint `json:"stop_timeout,omitempty"` + // Timeout is a maximum time in seconds the container will run before + // main process is sent SIGKILL. + // If 0 is used, signal will not be sent. Container can run indefinitely + // if they do not stop after the default termination signal. + // Optional. + Timeout uint `json:"timeout,omitempty"` + // LogConfiguration describes the logging for a container including + // driver, path, and options. + // Optional + LogConfiguration *LogConfig `json:"log_configuration,omitempty"` + // ConmonPidFile is a path at which a PID file for Conmon will be + // placed. + // If not given, a default location will be used. + // Optional. + ConmonPidFile string `json:"conmon_pid_file,omitempty"` + // RestartPolicy is the container's restart policy - an action which + // will be taken when the container exits. + // If not given, the default policy, which does nothing, will be used. + // Optional. + RestartPolicy string `json:"restart_policy,omitempty"` + // RestartRetries is the number of attempts that will be made to restart + // the container. + // Only available when RestartPolicy is set to "on-failure". + // Optional. + RestartRetries *uint `json:"restart_tries,omitempty"` + // OCIRuntime is the name of the OCI runtime that will be used to create + // the container. + // If not specified, the default will be used. + // Optional. + OCIRuntime string `json:"oci_runtime,omitempty"` + // Systemd is whether the container will be started in systemd mode. + // Valid options are "true", "false", and "always". + // "true" enables this mode only if the binary run in the container is + // /sbin/init or systemd. "always" unconditionally enables systemd mode. + // "false" unconditionally disables systemd mode. + // If enabled, mounts and stop signal will be modified. + // If set to "always" or set to "true" and conditionally triggered, + // conflicts with StopSignal. + // If not specified, "false" will be assumed. + // Optional. + Systemd string `json:"systemd,omitempty"` + // Determine how to handle the NOTIFY_SOCKET - do we participate or pass it through + // "container" - let the OCI runtime deal with it, advertise conmon's MAINPID + // "conmon-only" - advertise conmon's MAINPID, send READY when started, don't pass to OCI + // "ignore" - unset NOTIFY_SOCKET + // Optional. + SdNotifyMode string `json:"sdnotifyMode,omitempty"` + // PidNS is the container's PID namespace. + // It defaults to private. + // Mandatory. + PidNS Namespace `json:"pidns"` + // UtsNS is the container's UTS namespace. + // It defaults to private. + // Must be set to Private to set Hostname. + // Mandatory. + UtsNS Namespace `json:"utsns"` + // Hostname is the container's hostname. If not set, the hostname will + // not be modified (if UtsNS is not private) or will be set to the + // container ID (if UtsNS is private). + // Conflicts with UtsNS if UtsNS is not set to private. + // Optional. + Hostname string `json:"hostname,omitempty"` + // HostUsers is a list of host usernames or UIDs to add to the container + // /etc/passwd file + HostUsers []string `json:"hostusers,omitempty"` + // Sysctl sets kernel parameters for the container + Sysctl map[string]string `json:"sysctl,omitempty"` + // Remove indicates if the container should be removed once it has been started + // and exits. + // Optional. + Remove *bool `json:"remove,omitempty"` + // RemoveImage indicates that the container should remove the image it + // was created from after it exits. + // Only allowed if Remove is set to true and Image, not Rootfs, is in + // use. + // Optional. + RemoveImage *bool `json:"removeImage,omitempty"` + // ContainerCreateCommand is the command that was used to create this + // container. + // This will be shown in the output of Inspect() on the container, and + // may also be used by some tools that wish to recreate the container + // (e.g. `podman generate systemd --new`). + // Optional. + ContainerCreateCommand []string `json:"containerCreateCommand,omitempty"` + // PreserveFDs is a number of additional file descriptors (in addition + // to 0, 1, 2) that will be passed to the executed process. The total FDs + // passed will be 3 + PreserveFDs. + // set tags as `json:"-"` for not supported remote + // Optional. + PreserveFDs uint `json:"-"` + // PreserveFD is a list of additional file descriptors (in addition + // to 0, 1, 2) that will be passed to the executed process. + // set tags as `json:"-"` for not supported remote + // Optional. + PreserveFD []uint `json:"-"` + // Timezone is the timezone inside the container. + // Local means it has the same timezone as the host machine + // Optional. + Timezone string `json:"timezone,omitempty"` + // DependencyContainers is an array of containers this container + // depends on. Dependency containers must be started before this + // container. Dependencies can be specified by name or full/partial ID. + // Optional. + DependencyContainers []string `json:"dependencyContainers,omitempty"` + // PidFile is the file that saves container's PID. + // Not supported for remote clients, so not serialized in specgen JSON. + // Optional. + PidFile string `json:"-"` + // EnvSecrets are secrets that will be set as environment variables + // Optional. + EnvSecrets map[string]string `json:"secret_env,omitempty"` + // InitContainerType describes if this container is an init container + // and if so, what type: always or once. + // Optional. + InitContainerType string `json:"init_container_type"` + // Personality allows users to configure different execution domains. + // Execution domains tell Linux how to map signal numbers into signal actions. + // The execution domain system allows Linux to provide limited support + // for binaries compiled under other UNIX-like operating systems. + // Optional. + Personality *spec.LinuxPersonality `json:"personality,omitempty"` + // EnvMerge takes the specified environment variables from image and preprocess them before injecting them into the + // container. + // Optional. + EnvMerge []string `json:"envmerge,omitempty"` + // UnsetEnv unsets the specified default environment variables from the image or from built-in or containers.conf + // Optional. + UnsetEnv []string `json:"unsetenv,omitempty"` + // UnsetEnvAll unsetall default environment variables from the image or from built-in or containers.conf + // UnsetEnvAll unsets all default environment variables from the image or from built-in + // Optional. + UnsetEnvAll *bool `json:"unsetenvall,omitempty"` + // Passwd is a container run option that determines if we are validating users/groups before running the container + Passwd *bool `json:"manage_password,omitempty"` + // PasswdEntry specifies an arbitrary string to append to the container's /etc/passwd file. + // Optional. + PasswdEntry string `json:"passwd_entry,omitempty"` + // GroupEntry specifies an arbitrary string to append to the container's /etc/group file. + // Optional. + GroupEntry string `json:"group_entry,omitempty"` +} + +// ContainerStorageConfig contains information on the storage configuration of a +// container. +type ContainerStorageConfig struct { + // Image is the image the container will be based on. The image will be + // used as the container's root filesystem, and its environment vars, + // volumes, and other configuration will be applied to the container. + // Conflicts with Rootfs. + // At least one of Image or Rootfs must be specified. + Image string `json:"image"` + // RawImageName is the user-specified and unprocessed input referring + // to a local or a remote image. + // Optional, but strongly encouraged to be set if Image is set. + RawImageName string `json:"raw_image_name,omitempty"` + // ImageOS is the user-specified OS of the image. + // Used to select a different variant from a manifest list. + // Optional. + ImageOS string `json:"image_os,omitempty"` + // ImageArch is the user-specified image architecture. + // Used to select a different variant from a manifest list. + // Optional. + ImageArch string `json:"image_arch,omitempty"` + // ImageVariant is the user-specified image variant. + // Used to select a different variant from a manifest list. + // Optional. + ImageVariant string `json:"image_variant,omitempty"` + // Rootfs is the path to a directory that will be used as the + // container's root filesystem. No modification will be made to the + // directory, it will be directly mounted into the container as root. + // Conflicts with Image. + // At least one of Image or Rootfs must be specified. + Rootfs string `json:"rootfs,omitempty"` + // RootfsOverlay tells if rootfs is actually an overlay on top of base path. + // Optional. + RootfsOverlay *bool `json:"rootfs_overlay,omitempty"` + // RootfsMapping specifies if there are UID/GID mappings to apply to the rootfs. + // Optional. + RootfsMapping *string `json:"rootfs_mapping,omitempty"` + // ImageVolumeMode indicates how image volumes will be created. + // Supported modes are "ignore" (do not create), "tmpfs" (create as + // tmpfs), and "anonymous" (create as anonymous volumes). + // The default if unset is anonymous. + // Optional. + ImageVolumeMode string `json:"image_volume_mode,omitempty"` + // VolumesFrom is a set of containers whose volumes will be added to + // this container. The name or ID of the container must be provided, and + // may optionally be followed by a : and then one or more + // comma-separated options. Valid options are 'ro', 'rw', and 'z'. + // Options will be used for all volumes sourced from the container. + // Optional. + VolumesFrom []string `json:"volumes_from,omitempty"` + // Init specifies that an init binary will be mounted into the + // container, and will be used as PID1. + // Optional. + Init *bool `json:"init,omitempty"` + // InitPath specifies the path to the init binary that will be added if + // Init is specified above. If not specified, the default set in the + // Libpod config will be used. Ignored if Init above is not set. + // Optional. + InitPath string `json:"init_path,omitempty"` + // Mounts are mounts that will be added to the container. + // These will supersede Image Volumes and VolumesFrom volumes where + // there are conflicts. + // Optional. + Mounts []spec.Mount `json:"mounts,omitempty"` + // Volumes are named volumes that will be added to the container. + // These will supersede Image Volumes and VolumesFrom volumes where + // there are conflicts. + // Optional. + Volumes []*NamedVolume `json:"volumes,omitempty"` + // Overlay volumes are named volumes that will be added to the container. + // Optional. + OverlayVolumes []*OverlayVolume `json:"overlay_volumes,omitempty"` + // Image volumes bind-mount a container-image mount into the container. + // Optional. + ImageVolumes []*ImageVolume `json:"image_volumes,omitempty"` + // ArtifactVolumes volumes based on an existing artifact. + ArtifactVolumes []*ArtifactVolume `json:"artifact_volumes,omitempty"` + // Devices are devices that will be added to the container. + // Optional. + Devices []spec.LinuxDevice `json:"devices,omitempty"` + // DeviceCgroupRule are device cgroup rules that allow containers + // to use additional types of devices. + DeviceCgroupRule []spec.LinuxDeviceCgroup `json:"device_cgroup_rule,omitempty"` + // DevicesFrom specifies that this container will mount the device(s) from other container(s). + // Optional. + DevicesFrom []string `json:"devices_from,omitempty"` + // HostDeviceList is used to recreate the mounted device on inherited containers + HostDeviceList []spec.LinuxDevice `json:"host_device_list,omitempty"` + // IpcNS is the container's IPC namespace. + // Default is private. + // Conflicts with ShmSize if not set to private. + // Mandatory. + IpcNS Namespace `json:"ipcns"` + // ShmSize is the size of the tmpfs to mount in at /dev/shm, in bytes. + // Conflicts with ShmSize if IpcNS is not private. + // Optional. + ShmSize *int64 `json:"shm_size,omitempty"` + // ShmSizeSystemd is the size of systemd-specific tmpfs mounts + // specifically /run, /run/lock, /var/log/journal and /tmp. + // Optional + ShmSizeSystemd *int64 `json:"shm_size_systemd,omitempty"` + // WorkDir is the container's working directory. + // If unset, the default, /, will be used. + // Optional. + WorkDir string `json:"work_dir,omitempty"` + // Create the working directory if it doesn't exist. + // If unset, it doesn't create it. + // Optional. + CreateWorkingDir *bool `json:"create_working_dir,omitempty"` + // StorageOpts is the container's storage options + // Optional. + StorageOpts map[string]string `json:"storage_opts,omitempty"` + // RootfsPropagation is the rootfs propagation mode for the container. + // If not set, the default of rslave will be used. + // Optional. + RootfsPropagation string `json:"rootfs_propagation,omitempty"` + // Secrets are the secrets that will be added to the container + // Optional. + Secrets []Secret `json:"secrets,omitempty"` + // Volatile specifies whether the container storage can be optimized + // at the cost of not syncing all the dirty files in memory. + // Optional. + Volatile *bool `json:"volatile,omitempty"` + // ChrootDirs is an additional set of directories that need to be + // treated as root directories. Standard bind mounts will be mounted + // into paths relative to these directories. + // Optional. + ChrootDirs []string `json:"chroot_directories,omitempty"` +} + +// ContainerSecurityConfig is a container's security features, including +// SELinux, Apparmor, and Seccomp. +type ContainerSecurityConfig struct { + // Privileged is whether the container is privileged. + // Privileged does the following: + // - Adds all devices on the system to the container. + // - Adds all capabilities to the container. + // - Disables Seccomp, SELinux, and Apparmor confinement. + // (Though SELinux can be manually re-enabled). + // TODO: this conflicts with things. + // TODO: this does more. + // Optional. + Privileged *bool `json:"privileged,omitempty"` + // User is the user the container will be run as. + // Can be given as a UID or a username; if a username, it will be + // resolved within the container, using the container's /etc/passwd. + // If unset, the container will be run as root. + // Optional. + User string `json:"user,omitempty"` + // Groups are a list of supplemental groups the container's user will + // be granted access to. + // Optional. + Groups []string `json:"groups,omitempty"` + // CapAdd are capabilities which will be added to the container. + // Conflicts with Privileged. + // Optional. + CapAdd []string `json:"cap_add,omitempty"` + // CapDrop are capabilities which will be removed from the container. + // Conflicts with Privileged. + // Optional. + CapDrop []string `json:"cap_drop,omitempty"` + // SelinuxProcessLabel is the process label the container will use. + // If SELinux is enabled and this is not specified, a label will be + // automatically generated if not specified. + // Optional. + SelinuxOpts []string `json:"selinux_opts,omitempty"` + // ApparmorProfile is the name of the Apparmor profile the container + // will use. + // Optional. + ApparmorProfile string `json:"apparmor_profile,omitempty"` + // SeccompPolicy determines which seccomp profile gets applied + // the container. valid values: empty,default,image + SeccompPolicy string `json:"seccomp_policy,omitempty"` + // SeccompProfilePath is the path to a JSON file containing the + // container's Seccomp profile. + // If not specified, no Seccomp profile will be used. + // Optional. + SeccompProfilePath string `json:"seccomp_profile_path,omitempty"` + // NoNewPrivileges is whether the container will set the no new + // privileges flag on create, which disables gaining additional + // privileges (e.g. via setuid) in the container. + // Optional. + NoNewPrivileges *bool `json:"no_new_privileges,omitempty"` + // UserNS is the container's user namespace. + // It defaults to host, indicating that no user namespace will be + // created. + // If set to private, IDMappings must be set. + // Mandatory. + UserNS Namespace `json:"userns"` + // IDMappings are UID and GID mappings that will be used by user + // namespaces. + // Required if UserNS is private. + IDMappings *types.IDMappingOptions `json:"idmappings,omitempty"` + // ReadOnlyFilesystem indicates that everything will be mounted + // as read-only. + // Optional. + ReadOnlyFilesystem *bool `json:"read_only_filesystem,omitempty"` + // ReadWriteTmpfs indicates that when running with a ReadOnlyFilesystem + // mount temporary file systems. + // Optional. + ReadWriteTmpfs *bool `json:"read_write_tmpfs,omitempty"` + + // LabelNested indicates whether or not the container is allowed to + // run fully nested containers including SELinux labelling. + // Optional. + LabelNested *bool `json:"label_nested,omitempty"` + + // Umask is the umask the init process of the container will be run with. + Umask string `json:"umask,omitempty"` + // ProcOpts are the options used for the proc mount. + ProcOpts []string `json:"procfs_opts,omitempty"` + // Mask is the path we want to mask in the container. This masks the paths + // given in addition to the default list. + // Optional + Mask []string `json:"mask,omitempty"` + // Unmask a path in the container. Some paths are masked by default, + // preventing them from being accessed within the container; this undoes + // that masking. If ALL is passed, all paths will be unmasked. + // Optional. + Unmask []string `json:"unmask,omitempty"` +} + +// ContainerCgroupConfig contains configuration information about a container's +// cgroups. +type ContainerCgroupConfig struct { + // CgroupNS is the container's cgroup namespace. + // It defaults to private. + // Mandatory. + CgroupNS Namespace `json:"cgroupns"` + // CgroupsMode sets a policy for how cgroups will be created for the + // container, including the ability to disable creation entirely. + // Optional. + CgroupsMode string `json:"cgroups_mode,omitempty"` + // CgroupParent is the container's Cgroup parent. + // If not set, the default for the current cgroup driver will be used. + // Optional. + CgroupParent string `json:"cgroup_parent,omitempty"` +} + +// ContainerNetworkConfig contains information on a container's network +// configuration. +type ContainerNetworkConfig struct { + // NetNS is the configuration to use for the container's network + // namespace. + // Mandatory. + NetNS Namespace `json:"netns"` + // PortBindings is a set of ports to map into the container. + // Only available if NetNS is set to bridge, slirp, or pasta. + // Optional. + PortMappings []nettypes.PortMapping `json:"portmappings,omitempty"` + // PublishExposedPorts will publish ports specified in the image to + // random unused ports (guaranteed to be above 1024) on the host. + // This is based on ports set in Expose below, and any ports specified + // by the Image (if one is given). + // Only available if NetNS is set to Bridge or Slirp. + // Optional. + PublishExposedPorts *bool `json:"publish_image_ports,omitempty"` + // Expose is a number of ports that will be forwarded to the container + // if PublishExposedPorts is set. + // Expose is a map of uint16 (port number) to a string representing + // protocol i.e map[uint16]string. Allowed protocols are "tcp", "udp", and "sctp", or some + // combination of the three separated by commas. + // If protocol is set to "" we will assume TCP. + // Only available if NetNS is set to Bridge or Slirp, and + // PublishExposedPorts is set. + // Optional. + Expose map[uint16]string `json:"expose,omitempty"` + // Map of networks names or ids that the container should join. + // You can request additional settings for each network, you can + // set network aliases, static ips, static mac address and the + // network interface name for this container on the specific network. + // If the map is empty and the bridge network mode is set the container + // will be joined to the default network. + // Optional. + Networks map[string]nettypes.PerNetworkOptions + // CNINetworks is a list of CNI networks to join the container to. + // If this list is empty, the default CNI network will be joined + // instead. If at least one entry is present, we will not join the + // default network (unless it is part of this list). + // Only available if NetNS is set to bridge. + // Optional. + // Deprecated: as of podman 4.0 use "Networks" instead. + CNINetworks []string `json:"cni_networks,omitempty"` + // UseImageResolvConf indicates that resolv.conf should not be managed + // by Podman, but instead sourced from the image. + // Conflicts with DNSServer, DNSSearch, DNSOption. + // Optional. + UseImageResolvConf *bool `json:"use_image_resolve_conf,omitempty"` + // DNSServers is a set of DNS servers that will be used in the + // container's resolv.conf, replacing the host's DNS Servers which are + // used by default. + // Conflicts with UseImageResolvConf. + // Optional. + DNSServers []net.IP `json:"dns_server,omitempty"` + // DNSSearch is a set of DNS search domains that will be used in the + // container's resolv.conf, replacing the host's DNS search domains + // which are used by default. + // Conflicts with UseImageResolvConf. + // Optional. + DNSSearch []string `json:"dns_search,omitempty"` + // DNSOptions is a set of DNS options that will be used in the + // container's resolv.conf, replacing the host's DNS options which are + // used by default. + // Conflicts with UseImageResolvConf. + // Optional. + DNSOptions []string `json:"dns_option,omitempty"` + // UseImageHostname indicates that /etc/hostname should not be managed by + // Podman, and instead sourced from the image. + // Optional. + UseImageHostname *bool `json:"use_image_hostname,omitempty"` + // UseImageHosts indicates that /etc/hosts should not be managed by + // Podman, and instead sourced from the image. + // Conflicts with HostAdd. + // Optional. + UseImageHosts *bool `json:"use_image_hosts,omitempty"` + // BaseHostsFile is the base file to create the `/etc/hosts` file inside the container. + // This must either be an absolute path to a file on the host system, or one of the + // special flags `image` or `none`. + // If it is empty it defaults to the base_hosts_file configuration in containers.conf. + // Optional. + BaseHostsFile string `json:"base_hosts_file,omitempty"` + // HostAdd is a set of hosts which will be added to the container's + // /etc/hosts file. + // Conflicts with UseImageHosts. + // Optional. + HostAdd []string `json:"hostadd,omitempty"` + // NetworkOptions are additional options for each network + // Optional. + NetworkOptions map[string][]string `json:"network_options,omitempty"` +} + +// ContainerResourceConfig contains information on container resource limits. +type ContainerResourceConfig struct { + // IntelRdt defines the Intel RDT CAT Class of Service (COS) that all processes + // of the container should run in. + // Optional. + IntelRdt *spec.LinuxIntelRdt `json:"intelRdt,omitempty"` + // ResourceLimits are resource limits to apply to the container., + // Can only be set as root on cgroups v1 systems, but can be set as + // rootless as well for cgroups v2. + // Optional. + ResourceLimits *spec.LinuxResources `json:"resource_limits,omitempty"` + // Rlimits are POSIX rlimits to apply to the container. + // Optional. + Rlimits []spec.POSIXRlimit `json:"r_limits,omitempty"` + // OOMScoreAdj adjusts the score used by the OOM killer to determine + // processes to kill for the container's process. + // Optional. + OOMScoreAdj *int `json:"oom_score_adj,omitempty"` + // Weight per cgroup per device, can override BlkioWeight + WeightDevice map[string]spec.LinuxWeightDevice `json:"weightDevice,omitempty"` + // IO read rate limit per cgroup per device, bytes per second + ThrottleReadBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadBpsDevice,omitempty"` + // IO write rate limit per cgroup per device, bytes per second + ThrottleWriteBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleWriteBpsDevice,omitempty"` + // IO read rate limit per cgroup per device, IO per second + ThrottleReadIOPSDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadIOPSDevice,omitempty"` + // IO write rate limit per cgroup per device, IO per second + ThrottleWriteIOPSDevice map[string]spec.LinuxThrottleDevice `json:"throttleWriteIOPSDevice,omitempty"` + // CgroupConf are key-value options passed into the container runtime + // that are used to configure cgroup v2. + // Optional. + CgroupConf map[string]string `json:"unified,omitempty"` +} + +// ContainerHealthCheckConfig describes a container healthcheck with attributes +// like command, retries, interval, start period, and timeout. +type ContainerHealthCheckConfig struct { + HealthConfig *manifest.Schema2HealthConfig `json:"healthconfig,omitempty"` + HealthCheckOnFailureAction define.HealthCheckOnFailureAction `json:"health_check_on_failure_action,omitempty"` + // Startup healthcheck for a container. + // Requires that HealthConfig be set. + // Optional. + StartupHealthConfig *define.StartupHealthCheck `json:"startupHealthConfig,omitempty"` + // HealthLogDestination defines the destination where the log is stored. + // TODO (6.0): In next major release convert it to pointer and use omitempty + HealthLogDestination string `json:"healthLogDestination"` + // HealthMaxLogCount is maximum number of attempts in the HealthCheck log file. + // ('0' value means an infinite number of attempts in the log file). + // TODO (6.0): In next major release convert it to pointer and use omitempty + HealthMaxLogCount uint `json:"healthMaxLogCount"` + // HealthMaxLogSize is the maximum length in characters of stored HealthCheck log + // ("0" value means an infinite log length). + // TODO (6.0): In next major release convert it to pointer and use omitempty + HealthMaxLogSize uint `json:"healthMaxLogSize"` +} + +// SpecGenerator creates an OCI spec and Libpod configuration options to create +// a container based on the given configuration. +// swagger:model SpecGenerator +type SpecGenerator struct { + ContainerBasicConfig + ContainerStorageConfig + ContainerSecurityConfig + ContainerCgroupConfig + ContainerNetworkConfig + ContainerResourceConfig + ContainerHealthCheckConfig + + //nolint:nolintlint,unused // "unused" complains when remote build tag is used, "nolintlint" complains otherwise. + cacheLibImage +} + +func (s *SpecGenerator) IsPrivileged() bool { + if s.Privileged != nil { + return *s.Privileged + } + return false +} + +func (s *SpecGenerator) IsInitContainer() bool { + return len(s.InitContainerType) != 0 +} + +type Secret struct { + Source string + Target string + UID uint32 + GID uint32 + Mode uint32 +} + +var ( + // ErrNoStaticIPRootless is used when a rootless user requests to assign a static IP address + // to a pod or container + ErrNoStaticIPRootless = errors.New("rootless containers and pods cannot be assigned static IP addresses") + // ErrNoStaticMACRootless is used when a rootless user requests to assign a static MAC address + // to a pod or container + ErrNoStaticMACRootless = errors.New("rootless containers and pods cannot be assigned static MAC addresses") + // Multiple volume mounts to the same destination is not allowed + ErrDuplicateDest = errors.New("duplicate mount destination") +) + +// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs +func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator { + csc := ContainerStorageConfig{} + if rootfs { + csc.Rootfs = arg + // check if rootfs should use overlay + lastColonIndex := strings.LastIndex(csc.Rootfs, ":") + if lastColonIndex != -1 { + lastPart := csc.Rootfs[lastColonIndex+1:] + if lastPart == "O" { + localTrue := true + csc.RootfsOverlay = &localTrue + csc.Rootfs = csc.Rootfs[:lastColonIndex] + } else if lastPart == "idmap" || strings.HasPrefix(lastPart, "idmap=") { + csc.RootfsMapping = &lastPart + csc.Rootfs = csc.Rootfs[:lastColonIndex] + } + } + } else { + csc.Image = arg + } + return &SpecGenerator{ + ContainerStorageConfig: csc, + ContainerHealthCheckConfig: ContainerHealthCheckConfig{ + HealthLogDestination: define.DefaultHealthCheckLocalDestination, + HealthMaxLogCount: define.DefaultHealthMaxLogCount, + HealthMaxLogSize: define.DefaultHealthMaxLogSize, + }, + } +} + +// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs +func NewSpecGeneratorWithRootfs(rootfs string) *SpecGenerator { + csc := ContainerStorageConfig{Rootfs: rootfs} + return &SpecGenerator{ + ContainerStorageConfig: csc, + ContainerHealthCheckConfig: ContainerHealthCheckConfig{ + HealthLogDestination: define.DefaultHealthCheckLocalDestination, + HealthMaxLogCount: define.DefaultHealthMaxLogCount, + HealthMaxLogSize: define.DefaultHealthMaxLogSize, + }, + } +} + +func StringSlicesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/specgen_local.go b/vendor/github.com/containers/podman/v5/pkg/specgen/specgen_local.go new file mode 100644 index 00000000..8f424c2f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/specgen_local.go @@ -0,0 +1,22 @@ +//go:build !remote + +package specgen + +import "go.podman.io/common/libimage" + +type cacheLibImage struct { + image *libimage.Image `json:"-"` + resolvedImageName string `json:"-"` +} + +// SetImage sets the associated for the generator. +func (s *SpecGenerator) SetImage(image *libimage.Image, resolvedImageName string) { + s.image = image + s.resolvedImageName = resolvedImageName +} + +// Image returns the associated image for the generator. +// May be nil if no image has been set yet. +func (s *SpecGenerator) GetImage() (*libimage.Image, string) { + return s.image, s.resolvedImageName +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/specgen_remote.go b/vendor/github.com/containers/podman/v5/pkg/specgen/specgen_remote.go new file mode 100644 index 00000000..7bb83da2 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/specgen_remote.go @@ -0,0 +1,9 @@ +//go:build remote + +package specgen + +// Empty stub we do not use any libimage on the remote client, +// this drastically decreases binary size for the remote client. +// +//nolint:unused // this is needed for the local client +type cacheLibImage struct{} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/utils.go b/vendor/github.com/containers/podman/v5/pkg/specgen/utils.go new file mode 100644 index 00000000..d6bb9eae --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/utils.go @@ -0,0 +1,13 @@ +//go:build !linux + +package specgen + +// FinishThrottleDevices cannot be called on non-linux OS' due to importing unix functions +func FinishThrottleDevices(_ *SpecGenerator) error { + return nil +} + +// WeightDevices cannot be called on non-linux OS' due to importing unix functions +func WeightDevices(_ *SpecGenerator) error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/utils_linux.go b/vendor/github.com/containers/podman/v5/pkg/specgen/utils_linux.go new file mode 100644 index 00000000..0a5f7949 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/utils_linux.go @@ -0,0 +1,113 @@ +//go:build linux + +package specgen + +import ( + "fmt" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "golang.org/x/sys/unix" +) + +// statBlkDev returns path's major and minor, or an error, if path is not a block device. +func statBlkDev(path string) (int64, int64, error) { + var stat unix.Stat_t + + if err := unix.Stat(path, &stat); err != nil { + return 0, 0, fmt.Errorf("could not parse device %s: %w", path, err) + } + if stat.Mode&unix.S_IFMT != unix.S_IFBLK { + return 0, 0, fmt.Errorf("%s: not a block device", path) + } + rdev := uint64(stat.Rdev) //nolint:unconvert // Some architectures have different type. + return int64(unix.Major(rdev)), int64(unix.Minor(rdev)), nil +} + +// fillThrottleDev fills in dev.Major and dev.Minor fields based on path to a block device. +func fillThrottleDev(path string, dev *spec.LinuxThrottleDevice) error { + major, minor, err := statBlkDev(path) + if err != nil { + return err + } + + dev.Major, dev.Minor = major, minor + + return nil +} + +// FinishThrottleDevices takes the temporary representation of the throttle +// devices in the specgen, fills in major and minor numbers, and amends the +// specgen with the specified devices. It returns an error if any device +// specified doesn't exist or is not a block device. +func FinishThrottleDevices(s *SpecGenerator) error { + if len(s.ThrottleReadBpsDevice)+len(s.ThrottleWriteBpsDevice)+len(s.ThrottleReadIOPSDevice)+len(s.ThrottleWriteIOPSDevice) == 0 { + return nil + } + + if s.ResourceLimits == nil { + s.ResourceLimits = &spec.LinuxResources{} + } + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + + for k, v := range s.ThrottleReadBpsDevice { + if err := fillThrottleDev(k, &v); err != nil { + return err + } + s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v) + } + + for k, v := range s.ThrottleWriteBpsDevice { + if err := fillThrottleDev(k, &v); err != nil { + return err + } + s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v) + } + + for k, v := range s.ThrottleReadIOPSDevice { + if err := fillThrottleDev(k, &v); err != nil { + return err + } + s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v) + } + + for k, v := range s.ThrottleWriteIOPSDevice { + if err := fillThrottleDev(k, &v); err != nil { + return err + } + s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v) + } + + return nil +} + +func WeightDevices(specgen *SpecGenerator) error { + if len(specgen.WeightDevice) == 0 { + return nil + } + + if specgen.ResourceLimits == nil { + specgen.ResourceLimits = &spec.LinuxResources{} + } + if specgen.ResourceLimits.BlockIO == nil { + specgen.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + + for k, v := range specgen.WeightDevice { + major, minor, err := statBlkDev(k) + if err != nil { + return fmt.Errorf("bad --blkio-weight-device: %w", err) + } + specgen.ResourceLimits.BlockIO.WeightDevice = append(specgen.ResourceLimits.BlockIO.WeightDevice, + spec.LinuxWeightDevice{ + LinuxBlockIODevice: spec.LinuxBlockIODevice{ + Major: major, + Minor: minor, + }, + Weight: v.Weight, + }) + } + + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/volumes.go b/vendor/github.com/containers/podman/v5/pkg/specgen/volumes.go new file mode 100644 index 00000000..69dbd858 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/volumes.go @@ -0,0 +1,269 @@ +package specgen + +import ( + "errors" + "fmt" + "path/filepath" + "strings" + + "github.com/containers/podman/v5/libpod/define" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/parse" +) + +// NamedVolume holds information about a named volume that will be mounted into +// the container. +type NamedVolume struct { + // Name is the name of the named volume to be mounted. May be empty. + // If empty, a new named volume with a pseudorandomly generated name + // will be mounted at the given destination. + Name string + // Destination to mount the named volume within the container. Must be + // an absolute path. Path will be created if it does not exist. + Dest string + // Options are options that the named volume will be mounted with. + Options []string + // IsAnonymous sets the named volume as anonymous even if it has a name + // This is used for emptyDir volumes from a kube yaml + IsAnonymous bool + // SubPath stores the sub directory of the named volume to be mounted in the container + SubPath string +} + +// OverlayVolume holds information about an overlay volume that will be mounted into +// the container. +type OverlayVolume struct { + // Destination is the absolute path where the mount will be placed in the container. + Destination string `json:"destination"` + // Source specifies the source path of the mount. + Source string `json:"source,omitempty"` + // Options holds overlay volume options. + Options []string `json:"options,omitempty"` +} + +// ImageVolume is a volume based on a container image. The container image is +// first mounted on the host and is then bind-mounted into the container. An +// ImageVolume is always mounted read-only. +type ImageVolume struct { + // Source is the source of the image volume. The image can be referred + // to by name and by ID. + Source string + // Destination is the absolute path of the mount in the container. + Destination string + // ReadWrite sets the volume writable. + ReadWrite bool + // SubPath mounts a particular path within the image. + // If empty, the whole image is mounted. + SubPath string `json:"subPath,omitempty"` +} + +// ArtifactVolume is a volume based on a artifact. The artifact blobs will +// be bind mounted directly as files and must always be read only. +type ArtifactVolume struct { + // Source is the name or digest of the artifact that should be mounted + Source string `json:"source"` + // Destination is the absolute path of the mount in the container. + // If path is a file in the container, then the artifact must consist of a single blob. + // Otherwise if it is a directory or does not exists all artifact blobs will be mounted + // into this path as files. As name the "org.opencontainers.image.title" will be used if + // available otherwise the digest is used as name. + Destination string `json:"destination"` + // Title can be used for multi blob artifacts to only mount the one specific blob that + // matches the "org.opencontainers.image.title" annotation. + // Optional. Conflicts with Digest. + Title string `json:"title,omitempty"` + // Digest can be used to filter a single blob from a multi blob artifact by the given digest. + // When this option is set the file name in the container defaults to the digest even when + // the title annotation exist. + // Optional. Conflicts with Title. + Digest string `json:"digest,omitempty"` + // Name is the name that should be used for the path inside the container. When a single blob + // is mounted the name is used as is. If multiple blobs are mounted then mount them as + // "-x" where x is a 0 indexed integer based on the layer order. + // Optional. + Name string `json:"name,omitempty"` +} + +// GenVolumeMounts parses user input into mounts, volumes and overlay volumes +func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*NamedVolume, map[string]*OverlayVolume, error) { + mounts := make(map[string]spec.Mount) + volumes := make(map[string]*NamedVolume) + overlayVolumes := make(map[string]*OverlayVolume) + + volumeFormatErr := errors.New("incorrect volume format, should be [host-dir:]ctr-dir[:option]") + for _, vol := range volumeFlag { + var ( + options []string + src string + dest string + err error + ) + + splitVol := SplitVolumeString(vol) + if len(splitVol) > 3 { + return nil, nil, nil, fmt.Errorf("%v: %w", vol, volumeFormatErr) + } + + src = splitVol[0] + + // Support relative paths beginning with ./ + if strings.HasPrefix(src, "./") { + path, err := filepath.EvalSymlinks(src) + if err != nil { + return nil, nil, nil, err + } + src, err = filepath.Abs(path) + if err != nil { + return nil, nil, nil, err + } + splitVol[0] = src + } + + if len(splitVol) == 1 { + // This is an anonymous named volume. Only thing given + // is destination. + // Name/source will be blank, and populated by libpod. + src = "" + dest = splitVol[0] + } else if len(splitVol) > 1 { + dest = splitVol[1] + } + if len(splitVol) > 2 { + if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { + return nil, nil, nil, err + } + } + + // Do not check source dir for anonymous volumes + if len(splitVol) > 1 { + if len(src) == 0 { + return nil, nil, nil, errors.New("host directory cannot be empty") + } + } + + if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") || IsHostWinPath(src) { + // This is not a named volume + overlayFlag := false + chownFlag := false + upperDirFlag := false + workDirFlag := false + for _, o := range options { + if o == "O" { + overlayFlag = true + + joinedOpts := strings.Join(options, "") + if strings.Contains(joinedOpts, "U") { + chownFlag = true + } + if strings.Contains(joinedOpts, "upperdir") { + upperDirFlag = true + } + if strings.Contains(joinedOpts, "workdir") { + workDirFlag = true + } + if (workDirFlag && !upperDirFlag) || (!workDirFlag && upperDirFlag) { + return nil, nil, nil, errors.New("must set both `upperdir` and `workdir`") + } + if len(options) > 2 && (len(options) != 3 || !upperDirFlag || !workDirFlag) || (len(options) == 2 && !chownFlag) { + return nil, nil, nil, errors.New("can't use 'O' with other options") + } + } + } + if overlayFlag { + // This is an overlay volume + newOverlayVol := new(OverlayVolume) + newOverlayVol.Destination = dest + // convert src to absolute path so we don't end up passing + // relative values as lowerdir for overlay mounts + source, err := filepath.Abs(src) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed while resolving absolute path for source %v for overlay mount: %w", src, err) + } + newOverlayVol.Source = source + newOverlayVol.Options = options + + if vol, ok := overlayVolumes[newOverlayVol.Destination]; ok { + if vol.Source == newOverlayVol.Source && + StringSlicesEqual(vol.Options, newOverlayVol.Options) { + continue + } + return nil, nil, nil, fmt.Errorf("%v: %w", newOverlayVol.Destination, ErrDuplicateDest) + } + overlayVolumes[newOverlayVol.Destination] = newOverlayVol + } else { + newMount := spec.Mount{ + Destination: dest, + Type: define.TypeBind, + Source: src, + Options: options, + } + if vol, ok := mounts[newMount.Destination]; ok { + if vol.Source == newMount.Source && + StringSlicesEqual(vol.Options, newMount.Options) { + continue + } + + return nil, nil, nil, fmt.Errorf("%v: %w", newMount.Destination, ErrDuplicateDest) + } + mounts[newMount.Destination] = newMount + } + } else { + // This is a named volume + newNamedVol := new(NamedVolume) + newNamedVol.Name = src + newNamedVol.Dest = dest + newNamedVol.Options = options + + if vol, ok := volumes[newNamedVol.Dest]; ok { + if vol.Name == newNamedVol.Name { + continue + } + return nil, nil, nil, fmt.Errorf("%v: %w", newNamedVol.Dest, ErrDuplicateDest) + } + volumes[newNamedVol.Dest] = newNamedVol + } + + logrus.Debugf("User mount %s:%s options %v", src, dest, options) + } + + return mounts, volumes, overlayVolumes, nil +} + +// SplitVolumeString Splits a volume string, accounting for Win drive paths +// when running as a WSL linux guest or Windows client +// Format: [[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]] +func SplitVolumeString(vol string) []string { + parts := strings.Split(vol, ":") + if !shouldResolveWinPaths() { + return parts + } + + // Skip extended marker prefix if present + n := 0 + if strings.HasPrefix(vol, `\\?\`) { + n = 4 + } + + // Determine if the last part is an absolute path (if true, it means we don't have any options such as ro, rw etc.) + lastPartIsPath := strings.HasPrefix(parts[len(parts)-1], "/") + + // Case: Volume or relative host path (e.g., "vol-name:/container" or "./hello:/container") + if lastPartIsPath && len(parts) == 2 { + return parts + } + + // Case: Volume or relative host path with options (e.g., "vol-name:/container:ro" or "./hello:/container:ro") + if !lastPartIsPath && len(parts) == 3 { + return parts + } + + // Case: Windows absolute path (e.g., "C:/Users:/mnt:ro") + if hasWinDriveScheme(vol, n) { + first := parts[0] + ":" + parts[1] + parts = parts[1:] + parts[0] = first + } + + return parts +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/winpath.go b/vendor/github.com/containers/podman/v5/pkg/specgen/winpath.go new file mode 100644 index 00000000..714bbc05 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/winpath.go @@ -0,0 +1,59 @@ +package specgen + +import ( + "errors" + "fmt" + "strings" + "unicode" +) + +func IsHostWinPath(path string) bool { + return shouldResolveWinPaths() && strings.HasPrefix(path, `\\`) || hasWinDriveScheme(path, 0) || winPathExists(path) +} + +func hasWinDriveScheme(path string, start int) bool { + if len(path) < start+2 || path[start+1] != ':' { + return false + } + + drive := rune(path[start]) + return drive < unicode.MaxASCII && unicode.IsLetter(drive) +} + +// Converts a Windows path to a WSL guest path if local env is a WSL linux guest or this is a Windows client. +func ConvertWinMountPath(path string) (string, error) { + if !shouldResolveWinPaths() { + return path, nil + } + + if strings.HasPrefix(path, "/") { + // Handle /[driveletter]/windows/path form (e.g. c:\Users\bar == /c/Users/bar) + if len(path) > 2 && path[2] == '/' && shouldResolveUnixWinVariant(path) { + drive := unicode.ToLower(rune(path[1])) + if unicode.IsLetter(drive) && drive <= unicode.MaxASCII { + return fmt.Sprintf("/mnt/%c/%s", drive, path[3:]), nil + } + } + + // unix path - pass through + return path, nil + } + + // Convert remote win client relative paths to absolute + path = resolveRelativeOnWindows(path) + + // Strip extended marker prefix if present + path = strings.TrimPrefix(path, `\\?\`) + + // Drive installed via wsl --mount + switch { + case strings.HasPrefix(path, `\\.\`): + path = "/mnt/wsl/" + path[4:] + case len(path) > 1 && path[1] == ':': + path = "/mnt/" + strings.ToLower(path[0:1]) + path[2:] + default: + return path, errors.New("unsupported UNC path") + } + + return strings.ReplaceAll(path, `\`, "/"), nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/winpath_linux.go b/vendor/github.com/containers/podman/v5/pkg/specgen/winpath_linux.go new file mode 100644 index 00000000..8244af04 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/winpath_linux.go @@ -0,0 +1,22 @@ +package specgen + +import ( + "go.podman.io/common/pkg/machine" + "go.podman.io/storage/pkg/fileutils" +) + +func shouldResolveWinPaths() bool { + return machine.HostType() == "wsl" +} + +func shouldResolveUnixWinVariant(path string) bool { + return fileutils.Exists(path) != nil +} + +func resolveRelativeOnWindows(path string) string { + return path +} + +func winPathExists(_ string) bool { + return false +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/winpath_unsupported.go b/vendor/github.com/containers/podman/v5/pkg/specgen/winpath_unsupported.go new file mode 100644 index 00000000..2a1387d7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/winpath_unsupported.go @@ -0,0 +1,19 @@ +//go:build !linux && !windows + +package specgen + +func shouldResolveWinPaths() bool { + return false +} + +func shouldResolveUnixWinVariant(_ string) bool { + return false +} + +func resolveRelativeOnWindows(path string) string { + return path +} + +func winPathExists(_ string) bool { + return false +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgen/winpath_windows.go b/vendor/github.com/containers/podman/v5/pkg/specgen/winpath_windows.go new file mode 100644 index 00000000..c6ae29a9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgen/winpath_windows.go @@ -0,0 +1,30 @@ +package specgen + +import ( + "path/filepath" + + "github.com/sirupsen/logrus" + "go.podman.io/storage/pkg/fileutils" +) + +func shouldResolveUnixWinVariant(_ string) bool { + return true +} + +func shouldResolveWinPaths() bool { + return true +} + +func resolveRelativeOnWindows(path string) string { + ret, err := filepath.Abs(path) + if err != nil { + logrus.Debugf("problem resolving possible relative path %q: %s", path, err.Error()) + return path + } + + return ret +} + +func winPathExists(path string) bool { + return fileutils.Exists(path) == nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgenutil/createparse.go b/vendor/github.com/containers/podman/v5/pkg/specgenutil/createparse.go new file mode 100644 index 00000000..091919cf --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgenutil/createparse.go @@ -0,0 +1,23 @@ +package specgenutil + +import ( + "errors" + + "github.com/containers/podman/v5/pkg/domain/entities" + "go.podman.io/common/pkg/config" +) + +// validate determines if the flags and values given by the user are valid. things checked +// by validate must not need any state information on the flag (i.e. changed) +func validate(c *entities.ContainerCreateOptions) error { + var () + if c.Rm && (c.Restart != "" && c.Restart != "no" && c.Restart != "on-failure") { + return errors.New(`the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"`) + } + + if _, err := config.ParsePullPolicy(c.Pull); err != nil { + return err + } + + return config.ValidateImageVolumeMode(c.ImageVolume) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgenutil/ports.go b/vendor/github.com/containers/podman/v5/pkg/specgenutil/ports.go new file mode 100644 index 00000000..23ee9b4d --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgenutil/ports.go @@ -0,0 +1,23 @@ +package specgenutil + +import ( + "fmt" + + "github.com/docker/go-connections/nat" +) + +func verifyExpose(expose []string) error { + // add the expose ports from the user (--expose) + // can be single or a range + for _, expose := range expose { + // support two formats for expose, original format /[] or /[] + _, port := nat.SplitProtoPort(expose) + // parse the start and end port and create a sequence of ports to expose + // if expose a port, the start and end port are the same + _, _, err := nat.ParsePortRange(port) + if err != nil { + return fmt.Errorf("invalid range format for --expose: %s: %w", expose, err) + } + } + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgenutil/specgen.go b/vendor/github.com/containers/podman/v5/pkg/specgenutil/specgen.go new file mode 100644 index 00000000..effd9acc --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgenutil/specgen.go @@ -0,0 +1,1346 @@ +package specgenutil + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/containers/podman/v5/cmd/podman/parse" + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/domain/entities" + envLib "github.com/containers/podman/v5/pkg/env" + "github.com/containers/podman/v5/pkg/namespaces" + "github.com/containers/podman/v5/pkg/specgen" + systemdDefine "github.com/containers/podman/v5/pkg/systemd/define" + "github.com/containers/podman/v5/pkg/util" + "github.com/docker/go-units" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux" + "go.podman.io/common/pkg/config" + "go.podman.io/image/v5/manifest" +) + +const ( + rlimitPrefix = "rlimit_" +) + +func getCPULimits(c *entities.ContainerCreateOptions) *specs.LinuxCPU { + cpu := &specs.LinuxCPU{} + hasLimits := false + + if c.CPUS > 0 { + period, quota := util.CoresToPeriodAndQuota(c.CPUS) + + cpu.Period = &period + cpu.Quota = "a + hasLimits = true + } + if c.CPUShares > 0 { + cpu.Shares = &c.CPUShares + hasLimits = true + } + if c.CPUPeriod > 0 { + cpu.Period = &c.CPUPeriod + hasLimits = true + } + if c.CPUSetCPUs != "" { + cpu.Cpus = c.CPUSetCPUs + hasLimits = true + } + if c.CPUSetMems != "" { + cpu.Mems = c.CPUSetMems + hasLimits = true + } + if c.CPUQuota > 0 { + cpu.Quota = &c.CPUQuota + hasLimits = true + } + if c.CPURTPeriod > 0 { + cpu.RealtimePeriod = &c.CPURTPeriod + hasLimits = true + } + if c.CPURTRuntime > 0 { + cpu.RealtimeRuntime = &c.CPURTRuntime + hasLimits = true + } + + if !hasLimits { + return nil + } + return cpu +} + +func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxBlockIO, error) { + var err error + io := &specs.LinuxBlockIO{} + if s.ResourceLimits == nil { + s.ResourceLimits = &specs.LinuxResources{} + } + hasLimits := false + if b := c.BlkIOWeight; len(b) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &specs.LinuxBlockIO{} + } + u, err := strconv.ParseUint(b, 10, 16) + if err != nil { + return nil, fmt.Errorf("invalid value for blkio-weight: %w", err) + } + nu := uint16(u) + io.Weight = &nu + s.ResourceLimits.BlockIO.Weight = &nu + hasLimits = true + } + + if len(c.BlkIOWeightDevice) > 0 { + if s.WeightDevice, err = parseWeightDevices(c.BlkIOWeightDevice); err != nil { + return nil, err + } + hasLimits = true + } + + if bps := c.DeviceReadBPs; len(bps) > 0 { + if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil { + return nil, err + } + hasLimits = true + } + + if bps := c.DeviceWriteBPs; len(bps) > 0 { + if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil { + return nil, err + } + hasLimits = true + } + + if iops := c.DeviceReadIOPs; len(iops) > 0 { + if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil { + return nil, err + } + hasLimits = true + } + + if iops := c.DeviceWriteIOPs; len(iops) > 0 { + if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil { + return nil, err + } + hasLimits = true + } + + if !hasLimits { + return nil, nil + } + return io, nil +} + +func LimitToSwap(memory *specs.LinuxMemory, swap string, ml int64) { + if ml > 0 { + memory.Limit = &ml + if swap == "" { + limit := 2 * ml + memory.Swap = &(limit) + } + } +} + +func getMemoryLimits(c *entities.ContainerCreateOptions) (*specs.LinuxMemory, error) { + var err error + memory := &specs.LinuxMemory{} + hasLimits := false + if m := c.Memory; len(m) > 0 { + ml, err := units.RAMInBytes(m) + if err != nil { + return nil, fmt.Errorf("invalid value for memory: %w", err) + } + LimitToSwap(memory, c.MemorySwap, ml) + hasLimits = true + } + if m := c.MemoryReservation; len(m) > 0 { + mr, err := units.RAMInBytes(m) + if err != nil { + return nil, fmt.Errorf("invalid value for memory: %w", err) + } + memory.Reservation = &mr + hasLimits = true + } + if m := c.MemorySwap; len(m) > 0 { + var ms int64 + // only set memory swap if it was set + // -1 indicates unlimited + if m != "-1" { + ms, err = units.RAMInBytes(m) + memory.Swap = &ms + if err != nil { + return nil, fmt.Errorf("invalid value for memory: %w", err) + } + hasLimits = true + } + } + if c.MemorySwappiness >= 0 { + swappiness := uint64(c.MemorySwappiness) + memory.Swappiness = &swappiness + hasLimits = true + } + if c.OOMKillDisable { + memory.DisableOOMKiller = &c.OOMKillDisable + hasLimits = true + } + if !hasLimits { + return nil, nil + } + return memory, nil +} + +func setNamespaces(rtc *config.Config, s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) error { + var err error + + if c.PID != "" { + s.PidNS, err = specgen.ParseNamespace(c.PID) + if err != nil { + return err + } + } + if c.IPC != "" { + s.IpcNS, err = specgen.ParseIPCNamespace(c.IPC) + if err != nil { + return err + } + } + if c.UTS != "" { + s.UtsNS, err = specgen.ParseNamespace(c.UTS) + if err != nil { + return err + } + } + if c.CgroupNS != "" { + s.CgroupNS, err = specgen.ParseNamespace(c.CgroupNS) + if err != nil { + return err + } + } + userns := c.UserNS + // caller must make sure s.Pod is set before calling this function. + if userns == "" && s.Pod == "" { + if ns, ok := os.LookupEnv("PODMAN_USERNS"); ok { + userns = ns + } else { + // TODO: This should be moved into pkg/specgen/generate so we don't use the client's containers.conf + userns = rtc.Containers.UserNS + } + } + // userns must be treated differently + if userns != "" { + s.UserNS, err = specgen.ParseUserNamespace(userns) + if err != nil { + return err + } + } + if c.Net != nil { + s.NetNS = c.Net.Network + } + + if s.IDMappings == nil { + userNS := namespaces.UsernsMode(s.UserNS.NSMode) + tempIDMap, err := util.ParseIDMapping(namespaces.UsernsMode(userns), []string{}, []string{}, "", "") + if err != nil { + return err + } + s.IDMappings, err = util.ParseIDMapping(userNS, c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName) + if err != nil { + return err + } + if len(s.IDMappings.GIDMap) == 0 { + s.IDMappings.AutoUserNsOpts.AdditionalGIDMappings = tempIDMap.AutoUserNsOpts.AdditionalGIDMappings + if s.UserNS.NSMode == specgen.NamespaceMode("auto") { + s.IDMappings.AutoUserNs = true + } + } + if len(s.IDMappings.UIDMap) == 0 { + s.IDMappings.AutoUserNsOpts.AdditionalUIDMappings = tempIDMap.AutoUserNsOpts.AdditionalUIDMappings + if s.UserNS.NSMode == specgen.NamespaceMode("auto") { + s.IDMappings.AutoUserNs = true + } + } + if tempIDMap.AutoUserNsOpts.Size != 0 { + s.IDMappings.AutoUserNsOpts.Size = tempIDMap.AutoUserNsOpts.Size + } + // If some mappings are specified, assume a private user namespace + if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) { + s.UserNS.NSMode = specgen.Private + } else { + s.UserNS.NSMode = specgen.NamespaceMode(userNS) + } + } + + return nil +} + +func GenRlimits(ulimits []string) ([]specs.POSIXRlimit, error) { + rlimits := make([]specs.POSIXRlimit, 0, len(ulimits)) + // Rlimits/Ulimits + for _, ulimit := range ulimits { + if ulimit == "host" { + rlimits = nil + break + } + // `ulimitNameMapping` from go-units uses lowercase and names + // without prefixes, e.g. `RLIMIT_NOFILE` should be converted to `nofile`. + // https://github.com/containers/podman/issues/9803 + u := strings.TrimPrefix(strings.ToLower(ulimit), rlimitPrefix) + ul, err := units.ParseUlimit(u) + if err != nil { + return nil, fmt.Errorf("ulimit option %q requires name=SOFT:HARD, failed to be parsed: %w", u, err) + } + rl := specs.POSIXRlimit{ + Type: ul.Name, + Hard: uint64(ul.Hard), + Soft: uint64(ul.Soft), + } + rlimits = append(rlimits, rl) + } + return rlimits, nil +} + +func currentLabelOpts() ([]string, error) { + label, err := selinux.CurrentLabel() + if err != nil { + return nil, err + } + if label == "" { + return nil, nil + } + con, err := selinux.NewContext(label) + if err != nil { + return nil, err + } + return []string{ + fmt.Sprintf("label=user:%s", con["user"]), + fmt.Sprintf("label=role:%s", con["role"]), + }, nil +} + +func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions, args []string) error { + rtc, err := config.Default() + if err != nil { + return err + } + + // TODO: This needs to move into pkg/specgen/generate so we aren't using containers.conf on the client. + if rtc.Containers.EnableLabeledUsers { + defSecurityOpts, err := currentLabelOpts() + if err != nil { + return err + } + + c.SecurityOpt = append(defSecurityOpts, c.SecurityOpt...) + } + + // validate flags as needed + if err := validate(c); err != nil { + return err + } + s.User = c.User + var inputCommand []string + if !c.IsInfra { + if len(args) > 1 { + inputCommand = args[1:] + } + } + + if len(c.HealthCmd) > 0 { + if c.NoHealthCheck { + return errors.New("cannot specify both --no-healthcheck and --health-cmd") + } + s.HealthConfig, err = MakeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod, false) + if err != nil { + return err + } + } else if c.NoHealthCheck { + s.HealthConfig = &manifest.Schema2HealthConfig{ + Test: []string{"NONE"}, + } + } + + onFailureAction, err := define.ParseHealthCheckOnFailureAction(c.HealthOnFailure) + if err != nil { + return err + } + s.HealthCheckOnFailureAction = onFailureAction + + s.HealthLogDestination = c.HealthLogDestination + + s.HealthMaxLogCount = c.HealthMaxLogCount + + s.HealthMaxLogSize = c.HealthMaxLogSize + + if c.StartupHCCmd != "" { + if c.NoHealthCheck { + return errors.New("cannot specify both --no-healthcheck and --health-startup-cmd") + } + // The hardcoded "1s" will be discarded, as the startup + // healthcheck does not have a period. So just hardcode + // something that parses correctly. + tmpHcConfig, err := MakeHealthCheckFromCli(c.StartupHCCmd, c.StartupHCInterval, c.StartupHCRetries, c.StartupHCTimeout, "1s", true) + if err != nil { + return err + } + s.StartupHealthConfig = new(define.StartupHealthCheck) + s.StartupHealthConfig.Test = tmpHcConfig.Test + s.StartupHealthConfig.Interval = tmpHcConfig.Interval + s.StartupHealthConfig.Timeout = tmpHcConfig.Timeout + s.StartupHealthConfig.Retries = tmpHcConfig.Retries + s.StartupHealthConfig.Successes = int(c.StartupHCSuccesses) + } + + if len(s.Pod) == 0 || len(c.Pod) > 0 { + s.Pod = c.Pod + } + + if len(c.PodIDFile) > 0 { + if len(s.Pod) > 0 { + return errors.New("cannot specify both --pod and --pod-id-file") + } + podID, err := ReadPodIDFile(c.PodIDFile) + if err != nil { + return err + } + s.Pod = podID + } + + // Important s.Pod must be set above here. + if err := setNamespaces(rtc, s, c); err != nil { + return err + } + + if s.Terminal == nil { + s.Terminal = &c.TTY + } + + if err := verifyExpose(c.Expose); err != nil { + return err + } + // We are not handling the Expose flag yet. + // s.PortsExpose = c.Expose + if c.Net != nil { + s.PortMappings = c.Net.PublishPorts + } + if s.PublishExposedPorts == nil { + s.PublishExposedPorts = &c.PublishAll + } + + expose, err := CreateExpose(c.Expose) + if err != nil { + return err + } + + if len(s.Expose) == 0 { + s.Expose = expose + } + + if sig := c.StopSignal; len(sig) > 0 { + stopSignal, err := util.ParseSignal(sig) + if err != nil { + return err + } + s.StopSignal = &stopSignal + } + + // ENVIRONMENT VARIABLES + // + // Precedence order (higher index wins): + // 1) containers.conf (EnvHost, EnvHTTP, Env) 2) image data, 3 User EnvHost/EnvHTTP, 4) env-file, 5) env + // containers.conf handled and image data handled on the server side + // user specified EnvHost and EnvHTTP handled on Server Side relative to Server + // env-file and env handled on client side + var env map[string]string + + // First transform the os env into a map. We need it for the labels later in + // any case. + osEnv := envLib.Map(os.Environ()) + + if s.EnvHost == nil { + s.EnvHost = &c.EnvHost + } + + if s.HTTPProxy == nil { + s.HTTPProxy = &c.HTTPProxy + } + + // env-file overrides any previous variables + for _, f := range c.EnvFile { + fileEnv, err := envLib.ParseFile(f) + if err != nil { + return err + } + // File env is overridden by env. + env = envLib.Join(env, fileEnv) + } + + parsedEnv, err := envLib.ParseSlice(c.Env) + if err != nil { + return err + } + + if len(s.Env) == 0 { + s.Env = envLib.Join(env, parsedEnv) + } + + // LABEL VARIABLES + labels, err := parse.GetAllLabels(c.LabelFile, c.Label) + if err != nil { + return fmt.Errorf("unable to process labels: %w", err) + } + + if systemdUnit, exists := osEnv[systemdDefine.EnvVariable]; exists { + labels[systemdDefine.EnvVariable] = systemdUnit + } + + if len(s.Labels) == 0 { + s.Labels = labels + } + + // Intel RDT CAT + if c.IntelRdtClosID != "" { + s.IntelRdt = &specs.LinuxIntelRdt{} + s.IntelRdt.ClosID = c.IntelRdtClosID + } + + // ANNOTATIONS + annotations := make(map[string]string) + + // Last, add user annotations + for _, annotation := range c.Annotation { + key, val, hasVal := strings.Cut(annotation, "=") + if !hasVal { + return errors.New("annotations must be formatted KEY=VALUE") + } + annotations[key] = val + } + if len(s.Annotations) == 0 { + s.Annotations = annotations + } + // Add the user namespace configuration to the annotations + if c.UserNS != "" { + s.Annotations[define.UserNsAnnotation] = c.UserNS + } + + if c.PIDsLimit != nil { + s.Annotations[define.PIDsLimitAnnotation] = strconv.FormatInt(*c.PIDsLimit, 10) + } + + if c.CPUSetCPUs != "" { + s.Annotations[define.CpusetAnnotation] = c.CPUSetCPUs + } + + if c.CPUSetMems != "" { + s.Annotations[define.MemoryNodesAnnotation] = c.CPUSetMems + } + + if len(c.StorageOpts) > 0 { + opts := make(map[string]string, len(c.StorageOpts)) + for _, opt := range c.StorageOpts { + key, val, hasVal := strings.Cut(opt, "=") + if !hasVal { + return errors.New("storage-opt must be formatted KEY=VALUE") + } + opts[key] = val + } + s.StorageOpts = opts + } + if len(s.WorkDir) == 0 { + s.WorkDir = c.Workdir + } + if c.Entrypoint != nil { + entrypoint := []string{} + // Check if entrypoint specified is json + if err := json.Unmarshal([]byte(*c.Entrypoint), &entrypoint); err != nil { + entrypoint = append(entrypoint, *c.Entrypoint) + } + s.Entrypoint = entrypoint + } + + if len(inputCommand) > 0 { + s.Command = inputCommand + } + + // SHM Size + if c.ShmSize != "" { + val, err := units.RAMInBytes(c.ShmSize) + + if err != nil { + return fmt.Errorf("unable to translate --shm-size: %w", err) + } + + s.ShmSize = &val + } + + // SHM Size Systemd + if c.ShmSizeSystemd != "" { + val, err := units.RAMInBytes(c.ShmSizeSystemd) + if err != nil { + return fmt.Errorf("unable to translate --shm-size-systemd: %w", err) + } + + s.ShmSizeSystemd = &val + } + + if c.Net != nil { + s.Networks = c.Net.Networks + } + + if c.Net != nil { + s.HostAdd = c.Net.AddHosts + s.BaseHostsFile = c.Net.HostsFile + s.UseImageResolvConf = &c.Net.UseImageResolvConf + s.DNSServers = c.Net.DNSServers + s.DNSSearch = c.Net.DNSSearch + s.DNSOptions = c.Net.DNSOptions + s.NetworkOptions = c.Net.NetworkOptions + s.UseImageHostname = &c.Net.NoHostname + s.UseImageHosts = &c.Net.NoHosts + } + if len(s.HostUsers) == 0 || len(c.HostUsers) != 0 { + s.HostUsers = c.HostUsers + } + if len(c.ImageVolume) != 0 { + if len(s.ImageVolumeMode) == 0 { + s.ImageVolumeMode = c.ImageVolume + } + } + if s.ImageVolumeMode == define.TypeBind { + s.ImageVolumeMode = "anonymous" + } + + if len(s.Systemd) == 0 || len(c.Systemd) != 0 { + s.Systemd = strings.ToLower(c.Systemd) + } + if len(s.SdNotifyMode) == 0 || len(c.SdNotifyMode) != 0 { + s.SdNotifyMode = c.SdNotifyMode + } + if s.ResourceLimits == nil { + s.ResourceLimits = &specs.LinuxResources{} + } + + s.ResourceLimits, err = GetResources(s, c) + if err != nil { + return err + } + + if s.LogConfiguration == nil { + s.LogConfiguration = &specgen.LogConfig{} + } + + if ld := c.LogDriver; len(ld) > 0 { + s.LogConfiguration.Driver = ld + } + if len(s.CgroupParent) == 0 || len(c.CgroupParent) != 0 { + s.CgroupParent = c.CgroupParent + } + if len(s.CgroupsMode) == 0 { + s.CgroupsMode = c.CgroupsMode + } + + if len(s.Groups) == 0 || len(c.GroupAdd) != 0 { + s.Groups = c.GroupAdd + } + + if len(s.Hostname) == 0 || len(c.Hostname) != 0 { + s.Hostname = c.Hostname + } + sysctl := map[string]string{} + if ctl := c.Sysctl; len(ctl) > 0 { + sysctl, err = util.ValidateSysctls(ctl) + if err != nil { + return err + } + } + if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 { + s.Sysctl = sysctl + } + + if len(s.CapAdd) == 0 || len(c.CapAdd) != 0 { + s.CapAdd = c.CapAdd + } + if len(s.CapDrop) == 0 || len(c.CapDrop) != 0 { + s.CapDrop = c.CapDrop + } + if s.Privileged == nil { + s.Privileged = &c.Privileged + } + if s.ReadOnlyFilesystem == nil { + s.ReadOnlyFilesystem = &c.ReadOnly + } + if len(s.ConmonPidFile) == 0 || len(c.ConmonPIDFile) != 0 { + s.ConmonPidFile = c.ConmonPIDFile + } + + if len(s.DependencyContainers) == 0 || len(c.Requires) != 0 { + s.DependencyContainers = c.Requires + } + + // Only add ReadWrite tmpfs mounts iff the container is + // being run ReadOnly and ReadWriteTmpFS is not disabled, + // (user specifying --read-only-tmpfs=false.) + localRWTmpfs := c.ReadOnly && c.ReadWriteTmpFS + s.ReadWriteTmpfs = &localRWTmpfs + + // TODO convert to map? + // check if key=value and convert + sysmap := make(map[string]string) + for _, ctl := range c.Sysctl { + key, val, hasVal := strings.Cut(ctl, "=") + if !hasVal { + return fmt.Errorf("invalid sysctl value %q", ctl) + } + sysmap[key] = val + } + if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 { + s.Sysctl = sysmap + } + + if c.CIDFile != "" { + s.Annotations[define.InspectAnnotationCIDFile] = c.CIDFile + } + + for _, opt := range c.SecurityOpt { + // Docker deprecated the ":" syntax but still supports it, + // so we need to as well + var key, val string + var hasVal bool + if strings.Contains(opt, "=") { + key, val, hasVal = strings.Cut(opt, "=") + } else { + key, val, hasVal = strings.Cut(opt, ":") + } + if !hasVal && + key != "no-new-privileges" { + return fmt.Errorf("invalid --security-opt 1: %q", opt) + } + switch key { + case "apparmor": + s.ContainerSecurityConfig.ApparmorProfile = val + s.Annotations[define.InspectAnnotationApparmor] = val + case "label": + if val == "nested" { + localTrue := true + s.ContainerSecurityConfig.LabelNested = &localTrue + continue + } + // TODO selinux opts and label opts are the same thing + s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, val) + s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=") + case "mask": + s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(val, ":")...) + case "proc-opts": + s.ProcOpts = strings.Split(val, ",") + case "seccomp": + convertedPath := val + // Do not try to convert special value "unconfined", + // https://github.com/containers/podman/issues/26855 + if val != "unconfined" { + convertedPath, err = specgen.ConvertWinMountPath(val) + if err != nil { + // If the conversion fails, use the original path + convertedPath = val + } + } + s.SeccompProfilePath = convertedPath + s.Annotations[define.InspectAnnotationSeccomp] = convertedPath + // this option is for docker compatibility, it is the same as unmask=ALL + case "systempaths": + if val == "unconfined" { + s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...) + } else { + return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", val) + } + case "unmask": + s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, strings.Split(val, ":")...) + case "no-new-privileges": + noNewPrivileges := true + if hasVal { + noNewPrivileges, err = strconv.ParseBool(val) + if err != nil { + return fmt.Errorf("invalid --security-opt 2: %q", opt) + } + } + s.ContainerSecurityConfig.NoNewPrivileges = &noNewPrivileges + default: + return fmt.Errorf("invalid --security-opt 2: %q", opt) + } + } + + if len(s.SeccompPolicy) == 0 || len(c.SeccompPolicy) != 0 { + s.SeccompPolicy = c.SeccompPolicy + } + + if len(s.VolumesFrom) == 0 || len(c.VolumesFrom) != 0 { + s.VolumesFrom = c.VolumesFrom + } + + // Only add read-only tmpfs mounts in case that we are read-only and the + // read-only tmpfs flag has been set. + containerMounts, err := parseVolumes(rtc, c.Volume, c.Mount, c.TmpFS) + if err != nil { + return err + } + if len(s.Mounts) == 0 || len(c.Mount) != 0 { + s.Mounts = containerMounts.mounts + } + if len(s.Volumes) == 0 || len(c.Volume) != 0 { + s.Volumes = containerMounts.volumes + } + + if s.LabelNested != nil && *s.LabelNested { + // Need to unmask the SELinux file system + s.Unmask = append(s.Unmask, "/sys/fs/selinux", "/proc") + s.Mounts = append(s.Mounts, specs.Mount{ + Source: "/sys/fs/selinux", + Destination: "/sys/fs/selinux", + Type: define.TypeBind, + }) + s.Annotations[define.RunOCIMountContextType] = "rootcontext" + } + // TODO make sure these work in clone + if len(s.OverlayVolumes) == 0 { + s.OverlayVolumes = containerMounts.overlayVolumes + } + if len(s.ImageVolumes) == 0 { + s.ImageVolumes = containerMounts.imageVolumes + } + if len(s.ArtifactVolumes) == 0 { + s.ArtifactVolumes = containerMounts.artifactVolumes + } + + devices := c.Devices + for _, gpu := range c.GPUs { + devices = append(devices, "nvidia.com/gpu="+gpu) + } + + for _, dev := range devices { + s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev}) + } + + for _, rule := range c.DeviceCgroupRule { + dev, err := parseLinuxResourcesDeviceAccess(rule) + if err != nil { + return err + } + s.DeviceCgroupRule = append(s.DeviceCgroupRule, dev) + } + + if s.Init == nil { + s.Init = &c.Init + } + if len(s.InitPath) == 0 || len(c.InitPath) != 0 { + s.InitPath = c.InitPath + } + if s.Stdin == nil { + s.Stdin = &c.Interactive + } + // quiet + // DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), + + // Rlimits/Ulimits + s.Rlimits, err = GenRlimits(c.Ulimit) + if err != nil { + return err + } + + if rtc.Containers.LogPath != "" { + s.LogConfiguration.Path = rtc.Containers.LogPath + } + + logOpts := make(map[string]string) + for _, o := range c.LogOptions { + key, val, hasVal := strings.Cut(o, "=") + if !hasVal { + return fmt.Errorf("invalid log option %q", o) + } + switch strings.ToLower(key) { + case "driver": + s.LogConfiguration.Driver = val + case "path": + s.LogConfiguration.Path = val + case "max-size": + logSize, err := units.FromHumanSize(val) + if err != nil { + return err + } + s.LogConfiguration.Size = logSize + default: + logOpts[key] = val + } + } + + if len(s.LogConfiguration.Options) == 0 || len(c.LogOptions) != 0 { + s.LogConfiguration.Options = logOpts + } + if len(s.Name) == 0 || len(c.Name) != 0 { + s.Name = c.Name + } + + if c.PreserveFDs != 0 && c.PreserveFD != nil { + return errors.New("cannot specify both --preserve-fds and --preserve-fd") + } + + if s.PreserveFDs == 0 || c.PreserveFDs != 0 { + s.PreserveFDs = c.PreserveFDs + } + if s.PreserveFD == nil || c.PreserveFD != nil { + s.PreserveFD = c.PreserveFD + } + + if s.OOMScoreAdj == nil || c.OOMScoreAdj != nil { + s.OOMScoreAdj = c.OOMScoreAdj + } + if c.Restart != "" { + policy, retries, err := util.ParseRestartPolicy(c.Restart) + if err != nil { + return err + } + s.RestartPolicy = policy + s.RestartRetries = &retries + } + + if len(s.Secrets) == 0 || len(c.Secrets) != 0 { + s.Secrets, s.EnvSecrets, err = parseSecrets(c.Secrets) + if err != nil { + return err + } + } + + if c.Personality != "" { + s.Personality = &specs.LinuxPersonality{} + s.Personality.Domain = specs.LinuxPersonalityDomain(c.Personality) + } + + if s.Remove == nil { + s.Remove = &c.Rm + } + if s.StopTimeout == nil || c.StopTimeout != 0 { + s.StopTimeout = &c.StopTimeout + } + if s.Timeout == 0 || c.Timeout != 0 { + s.Timeout = c.Timeout + } + if len(s.Timezone) == 0 || len(c.Timezone) != 0 { + s.Timezone = c.Timezone + } + if len(s.Umask) == 0 || len(c.Umask) != 0 { + s.Umask = c.Umask + } + if len(s.PidFile) == 0 || len(c.PidFile) != 0 { + s.PidFile = c.PidFile + } + if s.Volatile == nil { + s.Volatile = &c.Rm + } + if len(s.EnvMerge) == 0 || len(c.EnvMerge) != 0 { + s.EnvMerge = c.EnvMerge + } + if len(s.UnsetEnv) == 0 || len(c.UnsetEnv) != 0 { + s.UnsetEnv = c.UnsetEnv + } + if s.UnsetEnvAll == nil { + s.UnsetEnvAll = &c.UnsetEnvAll + } + if len(s.ChrootDirs) == 0 || len(c.ChrootDirs) != 0 { + s.ChrootDirs = c.ChrootDirs + } + + // Initcontainers + if len(s.InitContainerType) == 0 || len(c.InitContainerType) != 0 { + s.InitContainerType = c.InitContainerType + } + + t := true + if s.Passwd == nil { + s.Passwd = &t + } + + if len(s.PasswdEntry) == 0 || len(c.PasswdEntry) != 0 { + s.PasswdEntry = c.PasswdEntry + } + + if len(s.GroupEntry) == 0 || len(c.GroupEntry) != 0 { + s.GroupEntry = c.GroupEntry + } + + return nil +} + +func MakeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string, isStartup bool) (*manifest.Schema2HealthConfig, error) { + cmdArr := []string{} + isArr := true + err := json.Unmarshal([]byte(inCmd), &cmdArr) // array unmarshalling + if err != nil { + cmdArr = strings.SplitN(inCmd, " ", 2) // default for compat + isArr = false + } + // Every healthcheck requires a command + if len(cmdArr) == 0 { + return nil, errors.New("must define a healthcheck command for all healthchecks") + } + + var concat string + if strings.ToUpper(cmdArr[0]) == define.HealthConfigTestCmd || strings.ToUpper(cmdArr[0]) == define.HealthConfigTestNone { // this is for compat, we are already split properly for most compat cases + cmdArr = strings.Fields(inCmd) + } else if strings.ToUpper(cmdArr[0]) != define.HealthConfigTestCmdShell { // this is for podman side of things, won't contain the keywords + if isArr && len(cmdArr) > 1 { // an array of consecutive commands + cmdArr = append([]string{define.HealthConfigTestCmd}, cmdArr...) + } else { // one singular command + if len(cmdArr) == 1 { + concat = cmdArr[0] + } else { + concat = strings.Join(cmdArr[0:], " ") + } + cmdArr = append([]string{define.HealthConfigTestCmdShell}, concat) + } + } + + if strings.ToUpper(cmdArr[0]) == define.HealthConfigTestNone { // if specified to remove healtcheck + cmdArr = []string{define.HealthConfigTestNone} + } + + // healthcheck is by default an array, so we simply pass the user input + hc := manifest.Schema2HealthConfig{ + Test: cmdArr, + } + + if interval == "disable" { + interval = "0" + } + intervalDuration, err := time.ParseDuration(interval) + if err != nil { + return nil, fmt.Errorf("invalid healthcheck-interval: %w", err) + } + + hc.Interval = intervalDuration + + if retries < 1 && !isStartup { + return nil, errors.New("healthcheck-retries must be greater than 0") + } + hc.Retries = int(retries) + timeoutDuration, err := time.ParseDuration(timeout) + if err != nil { + return nil, fmt.Errorf("invalid healthcheck-timeout: %w", err) + } + if timeoutDuration < time.Duration(1) { + return nil, errors.New("healthcheck-timeout must be at least 1 second") + } + hc.Timeout = timeoutDuration + + startPeriodDuration, err := time.ParseDuration(startPeriod) + if err != nil { + return nil, fmt.Errorf("invalid healthcheck-start-period: %w", err) + } + if startPeriodDuration < time.Duration(0) { + return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") + } + hc.StartPeriod = startPeriodDuration + return &hc, nil +} + +func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice, error) { + wd := make(map[string]specs.LinuxWeightDevice) + for _, dev := range weightDevs { + key, val, hasVal := strings.Cut(dev, ":") + if !hasVal { + return nil, fmt.Errorf("bad format: %s", dev) + } + if !strings.HasPrefix(key, "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", dev) + } + weight, err := strconv.ParseUint(val, 10, 0) + if err != nil { + return nil, fmt.Errorf("invalid weight for device: %s", dev) + } + if weight > 0 && (weight < 10 || weight > 1000) { + return nil, fmt.Errorf("invalid weight for device: %s", dev) + } + w := uint16(weight) + wd[key] = specs.LinuxWeightDevice{ + Weight: &w, + LeafWeight: nil, + } + } + return wd, nil +} + +func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { + td := make(map[string]specs.LinuxThrottleDevice) + for _, dev := range bpsDevices { + key, val, hasVal := strings.Cut(dev, ":") + if !hasVal { + return nil, fmt.Errorf("bad format: %s", dev) + } + if !strings.HasPrefix(key, "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", dev) + } + rate, err := units.RAMInBytes(val) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", dev) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", dev) + } + td[key] = specs.LinuxThrottleDevice{Rate: uint64(rate)} + } + return td, nil +} + +func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { + td := make(map[string]specs.LinuxThrottleDevice) + for _, dev := range iopsDevices { + key, val, hasVal := strings.Cut(dev, ":") + if !hasVal { + return nil, fmt.Errorf("bad format: %s", dev) + } + if !strings.HasPrefix(key, "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", dev) + } + rate, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", dev) + } + td[key] = specs.LinuxThrottleDevice{Rate: rate} + } + return td, nil +} + +func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) { + secretParseError := errors.New("parsing secret") + var mount []specgen.Secret + envs := make(map[string]string) + for _, val := range secrets { + // mount only tells if user has set an option that can only be used with mount secret type + mountOnly := false + source := "" + secretType := "" + target := "" + var uid, gid uint32 + // default mode 444 octal = 292 decimal + var mode uint32 = 292 + split := strings.Split(val, ",") + + // --secret mysecret + if len(split) == 1 { + mountSecret := specgen.Secret{ + Source: val, + Target: target, + UID: uid, + GID: gid, + Mode: mode, + } + mount = append(mount, mountSecret) + continue + } + // --secret mysecret,opt=opt + if !strings.Contains(split[0], "=") { + source = split[0] + split = split[1:] + } + + for _, val := range split { + name, value, hasValue := strings.Cut(val, "=") + if !hasValue { + return nil, nil, fmt.Errorf("option %s must be in form option=value: %w", val, secretParseError) + } + switch name { + case "source": + source = value + case "type": + if secretType != "" { + return nil, nil, fmt.Errorf("cannot set more than one secret type: %w", secretParseError) + } + if value != "mount" && value != "env" { + return nil, nil, fmt.Errorf("type %s is invalid: %w", value, secretParseError) + } + secretType = value + case "target": + target = value + case "mode": + mountOnly = true + mode64, err := strconv.ParseUint(value, 8, 32) + if err != nil { + return nil, nil, fmt.Errorf("mode %s invalid: %w", value, secretParseError) + } + mode = uint32(mode64) + case "uid", "UID": + mountOnly = true + uid64, err := strconv.ParseUint(value, 10, 32) + if err != nil { + return nil, nil, fmt.Errorf("UID %s invalid: %w", value, secretParseError) + } + uid = uint32(uid64) + case "gid", "GID": + mountOnly = true + gid64, err := strconv.ParseUint(value, 10, 32) + if err != nil { + return nil, nil, fmt.Errorf("GID %s invalid: %w", value, secretParseError) + } + gid = uint32(gid64) + + default: + return nil, nil, fmt.Errorf("option %s invalid: %w", val, secretParseError) + } + } + + if secretType == "" { + secretType = "mount" + } + if source == "" { + return nil, nil, fmt.Errorf("no source found %s: %w", val, secretParseError) + } + if secretType == "mount" { + mountSecret := specgen.Secret{ + Source: source, + Target: target, + UID: uid, + GID: gid, + Mode: mode, + } + mount = append(mount, mountSecret) + } + if secretType == "env" { + if mountOnly { + return nil, nil, fmt.Errorf("UID, GID, Mode options cannot be set with secret type env: %w", secretParseError) + } + if target == "" { + target = source + } + envs[target] = source + } + } + return mount, envs, nil +} + +var cgroupDeviceType = map[string]bool{ + "a": true, // all + "b": true, // block device + "c": true, // character device +} + +var cgroupDeviceAccess = map[string]bool{ + "r": true, // read + "w": true, // write + "m": true, // mknod +} + +// parseLinuxResourcesDeviceAccess parses the raw string passed with the --device-access-add flag +func parseLinuxResourcesDeviceAccess(device string) (specs.LinuxDeviceCgroup, error) { + var devType, access string + var major, minor *int64 + + value := strings.Split(device, " ") + if len(value) != 3 { + return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device cgroup rule requires type, major:Minor, and access rules: %q", device) + } + + devType = value[0] + if !cgroupDeviceType[devType] { + return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device type in device-access-add: %s", devType) + } + + majorNumber, minorNumber, hasMinor := strings.Cut(value[1], ":") + if majorNumber != "*" { + i, err := strconv.ParseUint(majorNumber, 10, 64) + if err != nil { + return specs.LinuxDeviceCgroup{}, err + } + m := int64(i) + major = &m + } + if hasMinor && minorNumber != "*" { + i, err := strconv.ParseUint(minorNumber, 10, 64) + if err != nil { + return specs.LinuxDeviceCgroup{}, err + } + m := int64(i) + minor = &m + } + access = value[2] + for c := range strings.SplitSeq(access, "") { + if !cgroupDeviceAccess[c] { + return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device access in device-access-add: %s", c) + } + } + return specs.LinuxDeviceCgroup{ + Allow: true, + Type: devType, + Major: major, + Minor: minor, + Access: access, + }, nil +} + +func GetResources(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxResources, error) { + var err error + if s.ResourceLimits.Memory == nil || (len(c.Memory) != 0 || len(c.MemoryReservation) != 0 || len(c.MemorySwap) != 0 || c.MemorySwappiness != 0) { + s.ResourceLimits.Memory, err = getMemoryLimits(c) + if err != nil { + return nil, err + } + } + if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0 || len(c.DeviceReadBPs) != 0 || len(c.DeviceWriteBPs) != 0) { + s.ResourceLimits.BlockIO, err = getIOLimits(s, c) + if err != nil { + return nil, err + } + } + if c.PIDsLimit != nil { + pids := specs.LinuxPids{ + Limit: *c.PIDsLimit, + } + + s.ResourceLimits.Pids = &pids + } + + if s.ResourceLimits.CPU == nil || (c.CPUPeriod != 0 || c.CPUQuota != 0 || c.CPURTPeriod != 0 || c.CPURTRuntime != 0 || c.CPUS != 0 || len(c.CPUSetCPUs) != 0 || len(c.CPUSetMems) != 0 || c.CPUShares != 0) { + s.ResourceLimits.CPU = getCPULimits(c) + } + + unifieds := make(map[string]string) + for _, unified := range c.CgroupConf { + key, val, hasVal := strings.Cut(unified, "=") + if !hasVal { + return nil, errors.New("--cgroup-conf must be formatted KEY=VALUE") + } + unifieds[key] = val + } + if len(unifieds) > 0 { + s.ResourceLimits.Unified = unifieds + } + + if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil && s.ResourceLimits.Unified == nil { + s.ResourceLimits = nil + } + return s.ResourceLimits, nil +} + +func UpdateMajorAndMinorNumbers(resources *specs.LinuxResources, devicesLimits *define.UpdateContainerDevicesLimits) (*specs.LinuxResources, error) { + spec := specgen.SpecGenerator{} + spec.ResourceLimits = &specs.LinuxResources{} + if resources != nil { + spec.ResourceLimits = resources + } + + spec.WeightDevice = devicesLimits.GetMapOfLinuxWeightDevice() + spec.ThrottleReadBpsDevice = devicesLimits.GetMapOfDeviceReadBPs() + spec.ThrottleWriteBpsDevice = devicesLimits.GetMapOfDeviceWriteBPs() + spec.ThrottleReadIOPSDevice = devicesLimits.GetMapOfDeviceReadIOPs() + spec.ThrottleWriteIOPSDevice = devicesLimits.GetMapOfDeviceWriteIOPs() + + err := specgen.WeightDevices(&spec) + if err != nil { + return nil, err + } + err = specgen.FinishThrottleDevices(&spec) + if err != nil { + return nil, err + } + return spec.ResourceLimits, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgenutil/util.go b/vendor/github.com/containers/podman/v5/pkg/specgenutil/util.go new file mode 100644 index 00000000..638109a5 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgenutil/util.go @@ -0,0 +1,336 @@ +package specgenutil + +import ( + "errors" + "fmt" + "net" + "os" + "strconv" + "strings" + + "github.com/sirupsen/logrus" + "go.podman.io/common/libnetwork/types" + "go.podman.io/common/pkg/config" + storageTypes "go.podman.io/storage/types" +) + +// ReadPodIDFile reads the specified file and returns its content (i.e., first +// line). +func ReadPodIDFile(path string) (string, error) { + content, err := os.ReadFile(path) + if err != nil { + return "", fmt.Errorf("reading pod ID file: %w", err) + } + return strings.Split(string(content), "\n")[0], nil +} + +// ReadPodIDFiles reads the specified files and returns their content (i.e., +// first line). +func ReadPodIDFiles(files []string) ([]string, error) { + ids := []string{} + for _, file := range files { + id, err := ReadPodIDFile(file) + if err != nil { + return nil, err + } + ids = append(ids, id) + } + return ids, nil +} + +// CreateExpose parses user-provided exposed port definitions and converts them +// into SpecGen format. +// TODO: The SpecGen format should really handle ranges more sanely - we could +// be massively inflating what is sent over the wire with a large range. +func CreateExpose(expose []string) (map[uint16]string, error) { + toReturn := make(map[uint16]string) + + for _, e := range expose { + // Check for protocol + proto := "tcp" + splitProto := strings.Split(e, "/") + if len(splitProto) > 2 { + return nil, errors.New("invalid expose format - protocol can only be specified once") + } else if len(splitProto) == 2 { + proto = splitProto[1] + } + + // Check for a range + start, len, err := parseAndValidateRange(splitProto[0]) + if err != nil { + return nil, err + } + + var index uint16 + for index = range len { + portNum := start + index + protocols, ok := toReturn[portNum] + if !ok { + toReturn[portNum] = proto + } else { + newProto := strings.Join(append(strings.Split(protocols, ","), strings.Split(proto, ",")...), ",") + toReturn[portNum] = newProto + } + } + } + + return toReturn, nil +} + +// CreatePortBindings iterates ports mappings into SpecGen format. +func CreatePortBindings(ports []string) ([]types.PortMapping, error) { + // --publish is formatted as follows: + // [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol] + toReturn := make([]types.PortMapping, 0, len(ports)) + + for _, p := range ports { + var ( + ctrPort string + proto, hostIP, hostPort *string + ) + + splitProto := strings.Split(p, "/") + switch len(splitProto) { + case 1: + // No protocol was provided + case 2: + proto = &(splitProto[1]) + default: + return nil, errors.New("invalid port format - protocol can only be specified once") + } + + remainder := splitProto[0] + haveV6 := false + + // Check for an IPv6 address in brackets + splitV6 := strings.Split(remainder, "]") + switch len(splitV6) { + case 1: + // Do nothing, proceed as before + case 2: + // We potentially have an IPv6 address + haveV6 = true + if !strings.HasPrefix(splitV6[0], "[") { + return nil, errors.New("invalid port format - IPv6 addresses must be enclosed by []") + } + if !strings.HasPrefix(splitV6[1], ":") { + return nil, errors.New("invalid port format - IPv6 address must be followed by a colon (':')") + } + ipNoPrefix := strings.TrimPrefix(splitV6[0], "[") + hostIP = &ipNoPrefix + remainder = strings.TrimPrefix(splitV6[1], ":") + default: + return nil, errors.New("invalid port format - at most one IPv6 address can be specified in a --publish") + } + + splitPort := strings.Split(remainder, ":") + switch len(splitPort) { + case 1: + if haveV6 { + return nil, errors.New("invalid port format - must provide host and destination port if specifying an IP") + } + ctrPort = splitPort[0] + case 2: + hostPort = &(splitPort[0]) + ctrPort = splitPort[1] + case 3: + if haveV6 { + return nil, errors.New("invalid port format - when v6 address specified, must be [ipv6]:hostPort:ctrPort") + } + hostIP = &(splitPort[0]) + hostPort = &(splitPort[1]) + ctrPort = splitPort[2] + default: + return nil, errors.New("invalid port format - format is [[hostIP:]hostPort:]containerPort") + } + + newPort, err := parseSplitPort(hostIP, hostPort, ctrPort, proto) + if err != nil { + return nil, err + } + + toReturn = append(toReturn, newPort) + } + + return toReturn, nil +} + +// parseSplitPort parses individual components of the --publish flag to produce +// a single port mapping in SpecGen format. +func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (types.PortMapping, error) { + newPort := types.PortMapping{} + if ctrPort == "" { + return newPort, errors.New("must provide a non-empty container port to publish") + } + ctrStart, ctrLen, err := parseAndValidateRange(ctrPort) + if err != nil { + return newPort, fmt.Errorf("parsing container port: %w", err) + } + newPort.ContainerPort = ctrStart + newPort.Range = ctrLen + + if protocol != nil { + if *protocol == "" { + return newPort, errors.New("must provide a non-empty protocol to publish") + } + newPort.Protocol = *protocol + } + if hostIP != nil { + if *hostIP == "" { + return newPort, errors.New("must provide a non-empty container host IP to publish") + } else if *hostIP != "0.0.0.0" { + // If hostIP is 0.0.0.0, leave it unset - CNI treats + // 0.0.0.0 and empty differently, Docker does not. + testIP := net.ParseIP(*hostIP) + if testIP == nil { + return newPort, fmt.Errorf("cannot parse %q as an IP address", *hostIP) + } + newPort.HostIP = testIP.String() + } + } + if hostPort != nil { + if *hostPort == "" { + // Set 0 as a placeholder. The server side of Specgen + // will find a random, open, unused port to use. + newPort.HostPort = 0 + } else { + hostStart, hostLen, err := parseAndValidateRange(*hostPort) + if err != nil { + return newPort, fmt.Errorf("parsing host port: %w", err) + } + if hostLen != ctrLen { + return newPort, fmt.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen) + } + newPort.HostPort = hostStart + } + } + + hport := newPort.HostPort + logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol) + + return newPort, nil +} + +// Parse and validate a port range. +// Returns start port, length of range, error. +func parseAndValidateRange(portRange string) (uint16, uint16, error) { + splitRange := strings.Split(portRange, "-") + if len(splitRange) > 2 { + return 0, 0, errors.New("invalid port format - port ranges are formatted as startPort-stopPort") + } + + if splitRange[0] == "" { + return 0, 0, errors.New("port numbers cannot be negative") + } + + startPort, err := parseAndValidatePort(splitRange[0]) + if err != nil { + return 0, 0, err + } + + var rangeLen uint16 = 1 + if len(splitRange) == 2 { + if splitRange[1] == "" { + return 0, 0, errors.New("must provide ending number for port range") + } + endPort, err := parseAndValidatePort(splitRange[1]) + if err != nil { + return 0, 0, err + } + if endPort <= startPort { + return 0, 0, fmt.Errorf("the end port of a range must be higher than the start port - %d is not higher than %d", endPort, startPort) + } + // Our range is the total number of ports + // involved, so we need to add 1 (8080:8081 is + // 2 ports, for example, not 1) + rangeLen = endPort - startPort + 1 + } + + return startPort, rangeLen, nil +} + +// Turn a single string into a valid U16 port. +func parseAndValidatePort(port string) (uint16, error) { + num, err := strconv.Atoi(port) + if err != nil { + return 0, fmt.Errorf("invalid port number: %w", err) + } + if num < 1 || num > 65535 { + return 0, fmt.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num) + } + return uint16(num), nil +} + +func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *config.Config, syslog, rm, rmi, exec bool) ([]string, error) { + // We need a cleanup process for containers in the current model. + // But we can't assume that the caller is Podman - it could be another + // user of the API. + // As such, provide a way to specify a path to Podman, so we can + // still invoke a cleanup process. + + podmanPath, err := os.Executable() + if err != nil { + return nil, err + } + + command := []string{podmanPath, + "--root", storageConfig.GraphRoot, + "--runroot", storageConfig.RunRoot, + "--log-level", logrus.GetLevel().String(), + "--cgroup-manager", config.Engine.CgroupManager, + "--tmpdir", config.Engine.TmpDir, + "--network-config-dir", config.Network.NetworkConfigDir, + "--network-backend", config.Network.NetworkBackend, + "--volumepath", config.Engine.VolumePath, + "--db-backend", config.Engine.DBBackend, + fmt.Sprintf("--transient-store=%t", storageConfig.TransientStore), + } + for _, dir := range config.Engine.HooksDir.Get() { + command = append(command, []string{"--hooks-dir", dir}...) + } + if storageConfig.ImageStore != "" { + command = append(command, []string{"--imagestore", storageConfig.ImageStore}...) + } + if config.Engine.OCIRuntime != "" { + command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...) + } + if storageConfig.GraphDriverName != "" { + command = append(command, []string{"--storage-driver", storageConfig.GraphDriverName}...) + } + for _, opt := range storageConfig.GraphDriverOptions { + command = append(command, []string{"--storage-opt", opt}...) + } + if config.Engine.EventsLogger != "" { + command = append(command, []string{"--events-backend", config.Engine.EventsLogger}...) + } + + if syslog { + command = append(command, "--syslog") + } + + // Make sure that loaded containers.conf modules are passed down to the + // callback as well. + for _, module := range config.LoadedModules() { + command = append(command, "--module", module) + } + + // --stopped-only is used to ensure we only cleanup stopped containers and do not race + // against other processes that did a cleanup() + init() again before we had the chance to run + command = append(command, []string{"container", "cleanup", "--stopped-only"}...) + + if rm { + command = append(command, "--rm") + } + + if rmi { + command = append(command, "--rmi") + } + + // This has to be absolutely last, to ensure that the exec session ID + // will be added after it by Libpod. + if exec { + command = append(command, "--exec") + } + + return command, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgenutil/volumes.go b/vendor/github.com/containers/podman/v5/pkg/specgenutil/volumes.go new file mode 100644 index 00000000..52086c7c --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgenutil/volumes.go @@ -0,0 +1,815 @@ +package specgenutil + +import ( + "errors" + "fmt" + "path" + "path/filepath" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/specgen" + "github.com/containers/podman/v5/pkg/specgenutilexternal" + "github.com/containers/podman/v5/pkg/util" + spec "github.com/opencontainers/runtime-spec/specs-go" + "go.podman.io/common/pkg/config" + "go.podman.io/common/pkg/parse" +) + +var ( + errOptionArg = errors.New("must provide an argument for option") + errNoDest = errors.New("must set volume destination") +) + +type containerMountSlice struct { + mounts []spec.Mount + volumes []*specgen.NamedVolume + overlayVolumes []*specgen.OverlayVolume + imageVolumes []*specgen.ImageVolume + artifactVolumes []*specgen.ArtifactVolume +} + +// containerMountMap contains the container mounts with the destination path as map key +type containerMountMap struct { + mounts map[string]spec.Mount + volumes map[string]*specgen.NamedVolume + imageVolumes map[string]*specgen.ImageVolume + artifactVolumes map[string]*specgen.ArtifactVolume +} + +type universalMount struct { + mount spec.Mount + // Used only with Named Volume type mounts + subPath string +} + +// Parse all volume-related options in the create config into a set of mounts +// and named volumes to add to the container. +// Handles --volumes, --mount, and --tmpfs flags. +// Does not handle image volumes, init, and --volumes-from flags. +// Can also add tmpfs mounts from read-only tmpfs. +// TODO: handle options parsing/processing via containers/storage/pkg/mount +func parseVolumes(rtc *config.Config, volumeFlag, mountFlag, tmpfsFlag []string) (*containerMountSlice, error) { + // Get mounts from the --mounts flag. + // TODO: The runtime config part of this needs to move into pkg/specgen/generate to avoid querying containers.conf on the client. + unifiedContainerMounts, err := mounts(mountFlag, rtc.Mounts()) + if err != nil { + return nil, err + } + + // Next --volumes flag. + volumeMounts, volumeVolumes, overlayVolumes, err := specgen.GenVolumeMounts(volumeFlag) + if err != nil { + return nil, err + } + + // Next --tmpfs flag. + tmpfsMounts, err := getTmpfsMounts(tmpfsFlag) + if err != nil { + return nil, err + } + + // Unify mounts from --mount, --volume, --tmpfs. + // Start with --volume. + for dest, mount := range volumeMounts { + if vol, ok := unifiedContainerMounts.mounts[dest]; ok { + if mount.Source == vol.Source && + specgen.StringSlicesEqual(vol.Options, mount.Options) { + continue + } + return nil, fmt.Errorf("%v: %w", dest, specgen.ErrDuplicateDest) + } + unifiedContainerMounts.mounts[dest] = mount + } + for dest, volume := range volumeVolumes { + if vol, ok := unifiedContainerMounts.volumes[dest]; ok { + if volume.Name == vol.Name && + specgen.StringSlicesEqual(vol.Options, volume.Options) { + continue + } + return nil, fmt.Errorf("%v: %w", dest, specgen.ErrDuplicateDest) + } + unifiedContainerMounts.volumes[dest] = volume + } + // Now --tmpfs + for dest, tmpfs := range tmpfsMounts { + if vol, ok := unifiedContainerMounts.mounts[dest]; ok { + if vol.Type != define.TypeTmpfs { + return nil, fmt.Errorf("%v: %w", dest, specgen.ErrDuplicateDest) + } + continue + } + unifiedContainerMounts.mounts[dest] = tmpfs + } + + // Check for conflicts between named volumes, overlay & image volumes, + // and mounts + allMounts := make(map[string]bool) + testAndSet := func(dest string) error { + if _, ok := allMounts[dest]; ok { + return fmt.Errorf("%v: %w", dest, specgen.ErrDuplicateDest) + } + allMounts[dest] = true + return nil + } + for dest := range unifiedContainerMounts.mounts { + if err := testAndSet(dest); err != nil { + return nil, err + } + } + for dest := range unifiedContainerMounts.volumes { + if err := testAndSet(dest); err != nil { + return nil, err + } + } + for dest := range overlayVolumes { + if err := testAndSet(dest); err != nil { + return nil, err + } + } + for dest := range unifiedContainerMounts.imageVolumes { + if err := testAndSet(dest); err != nil { + return nil, err + } + } + for dest := range unifiedContainerMounts.artifactVolumes { + if err := testAndSet(dest); err != nil { + return nil, err + } + } + + // Final step: maps to arrays + finalMounts := make([]spec.Mount, 0, len(unifiedContainerMounts.mounts)) + for _, mount := range unifiedContainerMounts.mounts { + if mount.Type == define.TypeBind { + absSrc, err := specgen.ConvertWinMountPath(mount.Source) + if err != nil { + return nil, fmt.Errorf("getting absolute path of %s: %w", mount.Source, err) + } + mount.Source = absSrc + } + finalMounts = append(finalMounts, mount) + } + finalVolumes := make([]*specgen.NamedVolume, 0, len(unifiedContainerMounts.volumes)) + for _, volume := range unifiedContainerMounts.volumes { + finalVolumes = append(finalVolumes, volume) + } + finalOverlayVolume := make([]*specgen.OverlayVolume, 0, len(overlayVolumes)) + for _, volume := range overlayVolumes { + absSrc, err := specgen.ConvertWinMountPath(volume.Source) + if err != nil { + return nil, fmt.Errorf("getting absolute path of %s: %w", volume.Source, err) + } + volume.Source = absSrc + finalOverlayVolume = append(finalOverlayVolume, volume) + } + finalImageVolumes := make([]*specgen.ImageVolume, 0, len(unifiedContainerMounts.imageVolumes)) + for _, volume := range unifiedContainerMounts.imageVolumes { + finalImageVolumes = append(finalImageVolumes, volume) + } + finalArtifactVolumes := make([]*specgen.ArtifactVolume, 0, len(unifiedContainerMounts.artifactVolumes)) + for _, volume := range unifiedContainerMounts.artifactVolumes { + finalArtifactVolumes = append(finalArtifactVolumes, volume) + } + + return &containerMountSlice{ + mounts: finalMounts, + volumes: finalVolumes, + overlayVolumes: finalOverlayVolume, + imageVolumes: finalImageVolumes, + artifactVolumes: finalArtifactVolumes, + }, nil +} + +// mounts takes user-provided input from the --mount flag as well as mounts +// specified in containers.conf and creates OCI spec mounts and Libpod named volumes. +// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... +// podman run --mount type=tmpfs,target=/dev/shm ... +// podman run --mount type=volume,source=test-volume, ... +// podman run --mount type=artifact,source=$artifact,dest=... +func mounts(mountFlag []string, configMounts []string) (*containerMountMap, error) { + finalMounts := make(map[string]spec.Mount) + finalNamedVolumes := make(map[string]*specgen.NamedVolume) + finalImageVolumes := make(map[string]*specgen.ImageVolume) + finalArtifactVolumes := make(map[string]*specgen.ArtifactVolume) + parseMounts := func(mounts []string, ignoreDup bool) error { + for _, mount := range mounts { + // TODO: Docker defaults to "volume" if no mount type is specified. + mountType, tokens, err := specgenutilexternal.FindMountType(mount) + if err != nil { + return err + } + switch mountType { + case define.TypeBind: + mount, err := getBindMount(tokens) + if err != nil { + return err + } + if _, ok := finalMounts[mount.Destination]; ok { + if ignoreDup { + continue + } + return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest) + } + finalMounts[mount.Destination] = mount + case "glob": + mounts, err := getGlobMounts(tokens) + if err != nil { + return err + } + for _, mount := range mounts { + if _, ok := finalMounts[mount.Destination]; ok { + if ignoreDup { + continue + } + return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest) + } + finalMounts[mount.Destination] = mount + } + case define.TypeTmpfs, define.TypeRamfs: + mount, err := parseMemoryMount(tokens, mountType) + if err != nil { + return err + } + if _, ok := finalMounts[mount.Destination]; ok { + if ignoreDup { + continue + } + return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest) + } + finalMounts[mount.Destination] = mount + case define.TypeDevpts: + mount, err := getDevptsMount(tokens) + if err != nil { + return err + } + if _, ok := finalMounts[mount.Destination]; ok { + if ignoreDup { + continue + } + return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest) + } + finalMounts[mount.Destination] = mount + case "image": + volume, err := getImageVolume(tokens) + if err != nil { + return err + } + if _, ok := finalImageVolumes[volume.Destination]; ok { + if ignoreDup { + continue + } + return fmt.Errorf("%v: %w", volume.Destination, specgen.ErrDuplicateDest) + } + finalImageVolumes[volume.Destination] = volume + case "artifact": + volume, err := getArtifactVolume(tokens) + if err != nil { + return err + } + if _, ok := finalArtifactVolumes[volume.Destination]; ok { + if ignoreDup { + continue + } + return fmt.Errorf("%v: %w", volume.Destination, specgen.ErrDuplicateDest) + } + finalArtifactVolumes[volume.Destination] = volume + case "volume": + volume, err := getNamedVolume(tokens) + if err != nil { + return err + } + if _, ok := finalNamedVolumes[volume.Dest]; ok { + if ignoreDup { + continue + } + return fmt.Errorf("%v: %w", volume.Dest, specgen.ErrDuplicateDest) + } + finalNamedVolumes[volume.Dest] = volume + default: + return fmt.Errorf("invalid filesystem type %q", mountType) + } + } + return nil + } + + // Parse mounts passed in from the user + if err := parseMounts(mountFlag, false); err != nil { + return nil, err + } + + // If user specified a mount flag that conflicts with a containers.conf flag, then ignore + // the duplicate. This means that the parsing of the containers.conf configMounts should always + // happen second. + if err := parseMounts(configMounts, true); err != nil { + return nil, fmt.Errorf("parsing containers.conf mounts: %w", err) + } + + return &containerMountMap{ + mounts: finalMounts, + volumes: finalNamedVolumes, + imageVolumes: finalImageVolumes, + artifactVolumes: finalArtifactVolumes, + }, nil +} + +func parseMountOptions(mountType string, args []string) (*universalMount, error) { + var setTmpcopyup, setRORW, setSuid, setDev, setExec, setRelabel, setOwnership, setSwap bool + + mnt := new(universalMount) + for _, arg := range args { + name, value, hasValue := strings.Cut(arg, "=") + switch name { + case "bind-nonrecursive": + if mountType != define.TypeBind { + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) + } + mnt.mount.Options = append(mnt.mount.Options, define.TypeBind) + case "bind-propagation": + if mountType != define.TypeBind { + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) + } + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + switch value { + case "shared", "rshared", "private", "rprivate", "slave", "rslave", "unbindable", "runbindable": + // Do nothing, sane value + default: + return nil, fmt.Errorf("invalid value %q", arg) + } + mnt.mount.Options = append(mnt.mount.Options, value) + case "consistency": + // Often used on MACs and mistakenly on Linux platforms. + // Since Docker ignores this option so shall we. + continue + case "idmap": + if hasValue { + mnt.mount.Options = append(mnt.mount.Options, fmt.Sprintf("idmap=%s", value)) + } else { + mnt.mount.Options = append(mnt.mount.Options, "idmap") + } + case "readonly", "ro", "rw": + if setRORW { + return nil, fmt.Errorf("cannot pass 'readonly', 'ro', or 'rw' mnt.Options more than once: %w", errOptionArg) + } + setRORW = true + // Can be formatted as one of: + // readonly + // readonly=[true|false] + // ro + // ro=[true|false] + // rw + // rw=[true|false] + if name == "readonly" { + name = "ro" + } + if hasValue { + switch strings.ToLower(value) { + case "true": + mnt.mount.Options = append(mnt.mount.Options, name) + case "false": + // Set the opposite only for rw + // ro's opposite is the default + if name == "rw" { + mnt.mount.Options = append(mnt.mount.Options, "ro") + } + } + } else { + mnt.mount.Options = append(mnt.mount.Options, name) + } + case "nodev", "dev": + if setDev { + return nil, fmt.Errorf("cannot pass 'nodev' and 'dev' mnt.Options more than once: %w", errOptionArg) + } + setDev = true + mnt.mount.Options = append(mnt.mount.Options, name) + case "noexec", "exec": + if setExec { + return nil, fmt.Errorf("cannot pass 'noexec' and 'exec' mnt.Options more than once: %w", errOptionArg) + } + setExec = true + mnt.mount.Options = append(mnt.mount.Options, name) + case "nosuid", "suid": + if setSuid { + return nil, fmt.Errorf("cannot pass 'nosuid' and 'suid' mnt.Options more than once: %w", errOptionArg) + } + setSuid = true + mnt.mount.Options = append(mnt.mount.Options, name) + case "noswap": + if setSwap { + return nil, fmt.Errorf("cannot pass 'noswap' mnt.Options more than once: %w", errOptionArg) + } + if rootless.IsRootless() { + return nil, fmt.Errorf("the 'noswap' option is only allowed with rootful tmpfs mounts: %w", errOptionArg) + } + setSwap = true + mnt.mount.Options = append(mnt.mount.Options, name) + case "relabel": + if setRelabel { + return nil, fmt.Errorf("cannot pass 'relabel' option more than once: %w", errOptionArg) + } + setRelabel = true + if !hasValue { + return nil, fmt.Errorf("%s mount option must be 'private' or 'shared': %w", name, util.ErrBadMntOption) + } + switch value { + case "private": + mnt.mount.Options = append(mnt.mount.Options, "Z") + case "shared": + mnt.mount.Options = append(mnt.mount.Options, "z") + default: + return nil, fmt.Errorf("%s mount option must be 'private' or 'shared': %w", name, util.ErrBadMntOption) + } + case "shared", "rshared", "private", "rprivate", "slave", "rslave", "unbindable", "runbindable", "Z", "z", "no-dereference": + mnt.mount.Options = append(mnt.mount.Options, name) + case "src", "source": + if mountType == define.TypeTmpfs { + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) + } + if mnt.mount.Source != "" { + return nil, fmt.Errorf("cannot pass %q option more than once: %w", name, errOptionArg) + } + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + if len(value) == 0 { + return nil, fmt.Errorf("host directory cannot be empty: %w", errOptionArg) + } + mnt.mount.Source = value + case "subpath", "volume-subpath": + if mountType != define.TypeVolume { + return nil, fmt.Errorf("cannot set option %q on non-volume mounts", name) + } + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + mnt.subPath = value + case "target", "dst", "dest", "destination": + if mnt.mount.Destination != "" { + return nil, fmt.Errorf("cannot pass %q option more than once: %w", name, errOptionArg) + } + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + if err := parse.ValidateVolumeCtrDir(value); err != nil { + return nil, err + } + mnt.mount.Destination = unixPathClean(value) + case "tmpcopyup", "notmpcopyup": + if mountType != define.TypeTmpfs { + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) + } + if setTmpcopyup { + return nil, fmt.Errorf("cannot pass 'tmpcopyup' and 'notmpcopyup' mnt.Options more than once: %w", errOptionArg) + } + setTmpcopyup = true + mnt.mount.Options = append(mnt.mount.Options, name) + case "tmpfs-mode": + if mountType != define.TypeTmpfs { + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) + } + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + mnt.mount.Options = append(mnt.mount.Options, fmt.Sprintf("mode=%s", value)) + case "tmpfs-size": + if mountType != define.TypeTmpfs { + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) + } + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + mnt.mount.Options = append(mnt.mount.Options, fmt.Sprintf("size=%s", value)) + case "U", "chown": + if setOwnership { + return nil, fmt.Errorf("cannot pass 'U' or 'chown' option more than once: %w", errOptionArg) + } + ok, err := validChownFlag(value) + if err != nil { + return nil, err + } + if ok { + mnt.mount.Options = append(mnt.mount.Options, "U") + } + setOwnership = true + case "volume-label": + if mountType != define.TypeVolume { + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) + } + return nil, fmt.Errorf("the --volume-label option is not presently implemented") + case "volume-opt": + if mountType != define.TypeVolume { + return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType) + } + mnt.mount.Options = append(mnt.mount.Options, arg) + default: + return nil, fmt.Errorf("%s: %w", name, util.ErrBadMntOption) + } + } + if mountType != "glob" && len(mnt.mount.Destination) == 0 { + return nil, errNoDest + } + return mnt, nil +} + +// Parse glob mounts entry from the --mount flag. +func getGlobMounts(args []string) ([]spec.Mount, error) { + mounts := []spec.Mount{} + + uMnt, err := parseMountOptions("glob", args) + if err != nil { + return nil, err + } + mnt := uMnt.mount + + globs, err := filepath.Glob(mnt.Source) + if err != nil { + return nil, err + } + if len(globs) == 0 { + return nil, fmt.Errorf("no file paths matching glob %q", mnt.Source) + } + + options, err := parse.ValidateVolumeOpts(mnt.Options) + if err != nil { + return nil, err + } + for _, src := range globs { + var newMount spec.Mount + newMount.Type = define.TypeBind + newMount.Options = options + newMount.Source = src + if len(mnt.Destination) == 0 { + newMount.Destination = src + } else { + newMount.Destination = filepath.Join(mnt.Destination, filepath.Base(src)) + } + mounts = append(mounts, newMount) + } + + return mounts, nil +} + +// Parse a single bind mount entry from the --mount flag. +func getBindMount(args []string) (spec.Mount, error) { + newMount := spec.Mount{ + Type: define.TypeBind, + } + var err error + uMnt, err := parseMountOptions(newMount.Type, args) + if err != nil { + return newMount, err + } + mnt := uMnt.mount + + if len(mnt.Destination) == 0 { + return newMount, errNoDest + } + + if len(mnt.Source) == 0 { + mnt.Source = mnt.Destination + } + + options, err := parse.ValidateVolumeOpts(mnt.Options) + if err != nil { + return newMount, err + } + newMount.Source = mnt.Source + newMount.Destination = mnt.Destination + newMount.Options = options + return newMount, nil +} + +// Parse a single tmpfs/ramfs mount entry from the --mount flag +func parseMemoryMount(args []string, mountType string) (spec.Mount, error) { + newMount := spec.Mount{ + Type: mountType, + Source: mountType, + } + + var err error + uMnt, err := parseMountOptions(newMount.Type, args) + if err != nil { + return newMount, err + } + mnt := uMnt.mount + if len(mnt.Destination) == 0 { + return newMount, errNoDest + } + newMount.Destination = mnt.Destination + newMount.Options = mnt.Options + return newMount, nil +} + +// Parse a single devpts mount entry from the --mount flag +func getDevptsMount(args []string) (spec.Mount, error) { + newMount := spec.Mount{ + Type: define.TypeDevpts, + Source: define.TypeDevpts, + } + + var setDest bool + + for _, arg := range args { + name, value, hasValue := strings.Cut(arg, "=") + switch name { + case "uid", "gid", "mode", "ptmxmode", "newinstance", "max": + newMount.Options = append(newMount.Options, arg) + case "target", "dst", "dest", "destination": + if !hasValue { + return newMount, fmt.Errorf("%v: %w", name, errOptionArg) + } + if err := parse.ValidateVolumeCtrDir(value); err != nil { + return newMount, err + } + newMount.Destination = unixPathClean(value) + setDest = true + default: + return newMount, fmt.Errorf("%s: %w", name, util.ErrBadMntOption) + } + } + + if !setDest { + return newMount, errNoDest + } + + return newMount, nil +} + +// Parse a single volume mount entry from the --mount flag. +// Note that the volume-label option for named volumes is currently NOT supported. +// TODO: add support for --volume-label +func getNamedVolume(args []string) (*specgen.NamedVolume, error) { + newVolume := new(specgen.NamedVolume) + + mnt, err := parseMountOptions(define.TypeVolume, args) + if err != nil { + return nil, err + } + if len(mnt.mount.Destination) == 0 { + return nil, errNoDest + } + + newVolume.Options = mnt.mount.Options + newVolume.SubPath = mnt.subPath + newVolume.Name = mnt.mount.Source + newVolume.Dest = mnt.mount.Destination + return newVolume, nil +} + +// Parse the arguments into an image volume. An image volume is a volume based +// on a container image. The container image is first mounted on the host and +// is then bind-mounted into the container. An ImageVolume is always mounted +// read-only. +func getImageVolume(args []string) (*specgen.ImageVolume, error) { + newVolume := new(specgen.ImageVolume) + + for _, arg := range args { + name, value, hasValue := strings.Cut(arg, "=") + switch name { + case "src", "source": + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + newVolume.Source = value + case "target", "dst", "dest", "destination": + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + if err := parse.ValidateVolumeCtrDir(value); err != nil { + return nil, err + } + newVolume.Destination = unixPathClean(value) + case "rw", "readwrite": + switch value { + case "true": + newVolume.ReadWrite = true + case "false": + // Nothing to do. RO is default. + default: + return nil, fmt.Errorf("invalid rw value %q: %w", value, util.ErrBadMntOption) + } + case "subpath": + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + if !filepath.IsAbs(value) { + return nil, fmt.Errorf("volume subpath %q must be an absolute path", value) + } + newVolume.SubPath = value + case "consistency": + // Often used on MACs and mistakenly on Linux platforms. + // Since Docker ignores this option so shall we. + continue + default: + return nil, fmt.Errorf("%s: %w", name, util.ErrBadMntOption) + } + } + + if len(newVolume.Source)*len(newVolume.Destination) == 0 { + return nil, errors.New("must set source and destination for image volume") + } + + return newVolume, nil +} + +// Parse the arguments into an artifact volume. An artifact volume creates mounts +// based on an existing artifact in the store. +func getArtifactVolume(args []string) (*specgen.ArtifactVolume, error) { + newVolume := new(specgen.ArtifactVolume) + + for _, arg := range args { + name, value, hasValue := strings.Cut(arg, "=") + switch name { + case "src", "source": + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + newVolume.Source = value + case "target", "dst", "dest", "destination": + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + if err := parse.ValidateVolumeCtrDir(value); err != nil { + return nil, err + } + newVolume.Destination = unixPathClean(value) + case "title": + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + newVolume.Title = value + + case "digest": + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + newVolume.Digest = value + case "name": + if !hasValue { + return nil, fmt.Errorf("%v: %w", name, errOptionArg) + } + newVolume.Name = value + default: + return nil, fmt.Errorf("%s: %w", name, util.ErrBadMntOption) + } + } + + if len(newVolume.Source)*len(newVolume.Destination) == 0 { + return nil, errors.New("must set source and destination for artifact volume") + } + + return newVolume, nil +} + +// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts +func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) { + m := make(map[string]spec.Mount) + for _, i := range tmpfsFlag { + // Default options if nothing passed + var options []string + spliti := strings.Split(i, ":") + destPath := spliti[0] + if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil { + return nil, err + } + if len(spliti) > 1 { + options = strings.Split(spliti[1], ",") + } + + if vol, ok := m[destPath]; ok { + if specgen.StringSlicesEqual(vol.Options, options) { + continue + } + return nil, fmt.Errorf("%v: %w", destPath, specgen.ErrDuplicateDest) + } + mount := spec.Mount{ + Destination: unixPathClean(destPath), + Type: define.TypeTmpfs, + Options: options, + Source: define.TypeTmpfs, + } + m[destPath] = mount + } + return m, nil +} + +// validChownFlag ensures that the U or chown flag is correctly used +func validChownFlag(value string) (bool, error) { + // U=[true|false] + switch { + case strings.EqualFold(value, "true"), value == "": + return true, nil + case strings.EqualFold(value, "false"): + return false, nil + default: + return false, fmt.Errorf("'U' or 'chown' must be set to true or false, instead received %q: %w", value, errOptionArg) + } +} + +// Use path instead of filepath to preserve Unix style paths on Windows +func unixPathClean(p string) string { + return path.Clean(p) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/specgenutilexternal/mount.go b/vendor/github.com/containers/podman/v5/pkg/specgenutilexternal/mount.go new file mode 100644 index 00000000..eb48ab7f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/specgenutilexternal/mount.go @@ -0,0 +1,36 @@ +package specgenutilexternal + +import ( + "encoding/csv" + "errors" + "strings" +) + +// FindMountType parses the input and extracts the type of the mount type and +// the remaining non-type tokens. +func FindMountType(input string) (mountType string, tokens []string, err error) { + // Split by comma, iterate over the slice and look for + // "type=$mountType". Everything else is appended to tokens. + found := false + csvReader := csv.NewReader(strings.NewReader(input)) + records, err := csvReader.ReadAll() + if err != nil { + return "", nil, err + } + if len(records) != 1 { + return "", nil, errors.New("incorrect mount format: should be --mount type=,[src=,]target=[,options]") + } + for _, s := range records[0] { + kv := strings.Split(s, "=") + if found || (len(kv) != 2 || kv[0] != "type") { + tokens = append(tokens, s) + continue + } + mountType = kv[1] + found = true + } + if !found { + mountType = "volume" + } + return +} diff --git a/vendor/github.com/containers/podman/v5/pkg/syncmap/syncmap.go b/vendor/github.com/containers/podman/v5/pkg/syncmap/syncmap.go new file mode 100644 index 00000000..314da236 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/syncmap/syncmap.go @@ -0,0 +1,82 @@ +package syncmap + +import ( + "maps" + "sync" +) + +// A Map is a map of a string to a generified value which is locked for safe +// access from multiple threads. +// It is effectively a generic version of Golang's standard library sync.Map. +// Admittedly, that has optimizations for multithreading performance that we do +// not here; thus, Map should not be used in truly performance sensitive +// areas, but places where code cleanliness is more important than raw +// performance. +// Map must always be passed by reference, not by value, to ensure thread +// safety is maintained. +type Map[K comparable, V any] struct { + data map[K]V + lock sync.Mutex +} + +// New generates a new, empty Map +func New[K comparable, V any]() *Map[K, V] { + toReturn := new(Map[K, V]) + toReturn.data = make(map[K]V) + + return toReturn +} + +// Put adds an entry into the map +func (m *Map[K, V]) Put(key K, value V) { + m.lock.Lock() + defer m.lock.Unlock() + + m.data[key] = value +} + +// Get retrieves an entry from the map. +// Semantic match Golang map semantics - the bool represents whether the key +// exists, and the empty value of T will be returned if the key does not exist. +func (m *Map[K, V]) Get(key K) (V, bool) { + m.lock.Lock() + defer m.lock.Unlock() + + value, exists := m.data[key] + + return value, exists +} + +// Exists returns true if a key exists in the map. +func (m *Map[K, V]) Exists(key K) bool { + m.lock.Lock() + defer m.lock.Unlock() + + _, ok := m.data[key] + + return ok +} + +// Delete removes an entry from the map. +func (m *Map[K, V]) Delete(key K) { + m.lock.Lock() + defer m.lock.Unlock() + + delete(m.data, key) +} + +// ToMap returns a shallow copy of the underlying data of the Map. +func (m *Map[K, V]) ToMap() map[K]V { + m.lock.Lock() + defer m.lock.Unlock() + + return maps.Clone(m.data) +} + +// Underlying returns a reference to the underlying storage of the Map. +// Once Underlying has been called, the Map is NO LONGER THREAD SAFE. +// If thread safety is still required, the shallow-copy offered by ToMap() +// should be used instead. +func (m *Map[K, V]) Underlying() map[K]V { + return m.data +} diff --git a/vendor/github.com/containers/podman/v5/pkg/systemd/activation.go b/vendor/github.com/containers/podman/v5/pkg/systemd/activation.go new file mode 100644 index 00000000..9fcfed77 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/systemd/activation.go @@ -0,0 +1,29 @@ +package systemd + +import ( + "os" + "strconv" +) + +// SocketActivated determine if podman is running under the socket activation protocol +// Criteria is based on the expectations of "github.com/coreos/go-systemd/v22/activation" +func SocketActivated() bool { + pid, found := os.LookupEnv("LISTEN_PID") + if !found { + return false + } + p, err := strconv.Atoi(pid) + if err != nil || p != os.Getpid() { + return false + } + + fds, found := os.LookupEnv("LISTEN_FDS") + if !found { + return false + } + nfds, err := strconv.Atoi(fds) + if err != nil || nfds == 0 { + return false + } + return true +} diff --git a/vendor/github.com/containers/podman/v5/pkg/systemd/dbus.go b/vendor/github.com/containers/podman/v5/pkg/systemd/dbus.go new file mode 100644 index 00000000..2c38cb34 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/systemd/dbus.go @@ -0,0 +1,149 @@ +package systemd + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/containers/podman/v5/pkg/rootless" + "github.com/coreos/go-systemd/v22/dbus" + godbus "github.com/godbus/dbus/v5" + "github.com/sirupsen/logrus" +) + +// IsSystemdSessionValid checks if sessions is valid for provided rootless uid. +func IsSystemdSessionValid(uid int) bool { + var conn *godbus.Conn + var err error + var object godbus.BusObject + var seat0Path godbus.ObjectPath + dbusDest := "org.freedesktop.login1" + dbusInterface := "org.freedesktop.login1.Manager" + dbusPath := "/org/freedesktop/login1" + + if rootless.IsRootless() { + conn, err = GetLogindConnection(rootless.GetRootlessUID()) + if err != nil { + // unable to fetch systemd object for logind + logrus.Debugf("systemd-logind: %s", err) + return false + } + object = conn.Object(dbusDest, godbus.ObjectPath(dbusPath)) + if err := object.Call(dbusInterface+".GetSeat", 0, "seat0").Store(&seat0Path); err != nil { + // unable to get seat0 path. + logrus.Debugf("systemd-logind: %s", err) + return false + } + seat0Obj := conn.Object(dbusDest, seat0Path) + activeSession, err := seat0Obj.GetProperty(dbusDest + ".Seat.ActiveSession") + if err != nil { + // unable to get active sessions. + logrus.Debugf("systemd-logind: %s", err) + return false + } + activeSessionMap, ok := activeSession.Value().([]any) + if !ok || len(activeSessionMap) < 2 { + // unable to get active session map. + logrus.Debugf("systemd-logind: %s", err) + return false + } + activeSessionPath, ok := activeSessionMap[1].(godbus.ObjectPath) + if !ok { + // unable to fetch active session path. + logrus.Debugf("systemd-logind: %s", err) + return false + } + activeSessionObj := conn.Object(dbusDest, activeSessionPath) + sessionUser, err := activeSessionObj.GetProperty(dbusDest + ".Session.User") + if err != nil { + // unable to fetch session user from activeSession path. + logrus.Debugf("systemd-logind: %s", err) + return false + } + dbusUser, ok := sessionUser.Value().([]any) + if !ok { + // not a valid user. + return false + } + if len(dbusUser) < 2 { + // not a valid session user. + return false + } + activeUID, ok := dbusUser[0].(uint32) + if !ok { + return false + } + // active session found which belongs to following rootless user + if activeUID == uint32(uid) { + return true + } + return false + } + return true +} + +// GetDbusConnection returns a user connection to D-BUS +func GetLogindConnection(uid int) (*godbus.Conn, error) { + return dbusAuthConnectionLogind(uid) +} + +func dbusAuthConnectionLogind(uid int) (*godbus.Conn, error) { + var conn *godbus.Conn + var err error + conn, err = godbus.SystemBusPrivate() + if err != nil { + return nil, err + } + methods := []godbus.Auth{godbus.AuthExternal(strconv.Itoa(uid))} + if err = conn.Auth(methods); err != nil { + conn.Close() + return nil, err + } + err = conn.Hello() + if err != nil { + conn.Close() + return nil, err + } + return conn, nil +} + +func dbusAuthRootlessConnection(createBus func(opts ...godbus.ConnOption) (*godbus.Conn, error)) (*godbus.Conn, error) { + conn, err := createBus() + if err != nil { + return nil, err + } + + methods := []godbus.Auth{godbus.AuthExternal(strconv.Itoa(rootless.GetRootlessUID()))} + + err = conn.Auth(methods) + if err != nil { + conn.Close() + return nil, err + } + + return conn, nil +} + +func newRootlessConnection() (*dbus.Conn, error) { + return dbus.NewConnection(func() (*godbus.Conn, error) { + return dbusAuthRootlessConnection(func(_ ...godbus.ConnOption) (*godbus.Conn, error) { + path := filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "systemd", "private") + path, err := filepath.EvalSymlinks(path) + if err != nil { + return nil, err + } + return godbus.Dial(fmt.Sprintf("unix:path=%s", path)) + }) + }) +} + +// ConnectToDBUS returns a DBUS connection. It works both as root and non-root +// users. +func ConnectToDBUS() (*dbus.Conn, error) { + if rootless.IsRootless() { + return newRootlessConnection() + } + return dbus.NewSystemdConnectionContext(context.Background()) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/systemd/define/const.go b/vendor/github.com/containers/podman/v5/pkg/systemd/define/const.go new file mode 100644 index 00000000..6bab8b62 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/systemd/define/const.go @@ -0,0 +1,14 @@ +package define + +const ( + // Default restart policy for generated unit files. + DefaultRestartPolicy = "on-failure" + + // EnvVariable "PODMAN_SYSTEMD_UNIT" is set in all generated systemd units and + // is set to the unit's (unique) name. + EnvVariable = "PODMAN_SYSTEMD_UNIT" +) + +// RestartPolicies includes all valid restart policies to be used in a unit +// file. +var RestartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"} diff --git a/vendor/github.com/containers/podman/v5/pkg/systemd/notifyproxy/notifyproxy.go b/vendor/github.com/containers/podman/v5/pkg/systemd/notifyproxy/notifyproxy.go new file mode 100644 index 00000000..069e5fdd --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/systemd/notifyproxy/notifyproxy.go @@ -0,0 +1,254 @@ +//go:build !windows + +package notifyproxy + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "os" + "strings" + "syscall" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/coreos/go-systemd/v22/daemon" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +const ( + // All constants below are defined by systemd. + _notifyRcvbufSize = 8 * 1024 * 1024 + _notifyBufferMax = 4096 + _notifyFdMax = 768 + _notifyBarrierMsg = "BARRIER=1" + _notifyRdyMsg = daemon.SdNotifyReady +) + +// SendMessage sends the specified message to the specified socket. +// No message is sent if no socketPath is provided and the NOTIFY_SOCKET +// variable is not set either. +func SendMessage(socketPath string, message string) error { + if socketPath == "" { + socketPath, _ = os.LookupEnv("NOTIFY_SOCKET") + if socketPath == "" { + return nil + } + } + socketAddr := &net.UnixAddr{ + Name: socketPath, + Net: "unixgram", + } + conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr) + if err != nil { + return err + } + defer conn.Close() + + _, err = conn.Write([]byte(message)) + return err +} + +// NotifyProxy can be used to proxy notify messages. +type NotifyProxy struct { + connection *net.UnixConn + socketPath string + container Container // optional + + // Channels for synchronizing the goroutine waiting for the READY + // message and the one checking if the optional container is still + // running. + errorChan chan error + readyChan chan bool +} + +// New creates a NotifyProxy that starts listening immediately. The specified +// temp directory can be left empty. +func New(tmpDir string) (*NotifyProxy, error) { + tempFile, err := os.CreateTemp(tmpDir, "-podman-notify-proxy.sock") + if err != nil { + return nil, err + } + defer tempFile.Close() + + socketPath := tempFile.Name() + if err := syscall.Unlink(socketPath); err != nil { // Unlink the socket so we can bind it + return nil, err + } + + socketAddr := &net.UnixAddr{ + Name: socketPath, + Net: "unixgram", + } + conn, err := net.ListenUnixgram(socketAddr.Net, socketAddr) + if err != nil { + return nil, err + } + + if err := conn.SetReadBuffer(_notifyRcvbufSize); err != nil { + return nil, fmt.Errorf("setting read buffer: %w", err) + } + + errorChan := make(chan error, 1) + readyChan := make(chan bool, 1) + + proxy := &NotifyProxy{ + connection: conn, + socketPath: socketPath, + errorChan: errorChan, + readyChan: readyChan, + } + + // Start waiting for the READY message in the background. This way, + // the proxy can be created prior to starting the container and + // circumvents a race condition on writing/reading on the socket. + proxy.listen() + + return proxy, nil +} + +// listen waits for the READY message in the background, and process file +// descriptors and barriers send over the NOTIFY_SOCKET. The goroutine returns +// when the socket is closed. +func (p *NotifyProxy) listen() { + go func() { + // See https://github.com/containers/podman/issues/16515 for a description of the protocol. + fdSize := unix.CmsgSpace(4) + buffer := make([]byte, _notifyBufferMax) + oob := make([]byte, _notifyFdMax*fdSize) + sBuilder := strings.Builder{} + for { + n, oobn, flags, _, err := p.connection.ReadMsgUnix(buffer, oob) + if err != nil { + if !errors.Is(err, io.EOF) { + p.errorChan <- err + return + } + logrus.Errorf("Error reading unix message on socket %q: %v", p.socketPath, err) + continue + } + + if n > _notifyBufferMax || oobn > _notifyFdMax*fdSize { + logrus.Errorf("Ignoring unix message on socket %q: incorrect number of bytes read (n=%d, oobn=%d)", p.socketPath, n, oobn) + continue + } + + if flags&unix.MSG_CTRUNC != 0 { + logrus.Errorf("Ignoring unix message on socket %q: message truncated", p.socketPath) + continue + } + + sBuilder.Reset() + sBuilder.Write(buffer[:n]) + var isBarrier, isReady bool + + for line := range strings.SplitSeq(sBuilder.String(), "\n") { + switch line { + case _notifyRdyMsg: + isReady = true + case _notifyBarrierMsg: + isBarrier = true + } + } + + if isBarrier { + scms, err := unix.ParseSocketControlMessage(oob) + if err != nil { + logrus.Errorf("parsing control message on socket %q: %v", p.socketPath, err) + } + for _, scm := range scms { + fds, err := unix.ParseUnixRights(&scm) + if err != nil { + logrus.Errorf("parsing unix rights of control message on socket %q: %v", p.socketPath, err) + continue + } + for _, fd := range fds { + if err := unix.Close(fd); err != nil { + logrus.Errorf("closing fd passed on socket %q: %v", fd, err) + continue + } + } + } + continue + } + + if isReady { + p.readyChan <- true + } + } + }() +} + +// SocketPath returns the path of the socket the proxy is listening on. +func (p *NotifyProxy) SocketPath() string { + return p.socketPath +} + +// Close closes the listener and removes the socket. +func (p *NotifyProxy) Close() error { + defer os.Remove(p.socketPath) + return p.connection.Close() +} + +// AddContainer associates a container with the proxy. +func (p *NotifyProxy) AddContainer(container Container) { + p.container = container +} + +// ErrNoReadyMessage is returned when we are waiting for the READY message of a +// container that is not in the running state anymore. +var ErrNoReadyMessage = errors.New("container stopped running before READY message was received") + +// Container avoids a circular dependency among this package and libpod. +type Container interface { + State() (define.ContainerStatus, error) + ID() string +} + +// Wait waits until receiving the `READY` notify message. Note that the +// this function must only be executed inside a systemd service which will kill +// the process after a given timeout. If the (optional) container stopped +// running before the `READY` is received, the waiting gets canceled and +// ErrNoReadyMessage is returned. +func (p *NotifyProxy) Wait() error { + // If the proxy has a container we need to watch it as it may exit + // without sending a READY message. The goroutine below returns when + // the container exits OR when the function returns (see deferred the + // cancel()) in which case we either we've either received the READY + // message or encountered an error reading from the socket. + if p.container != nil { + // Create a cancellable context to make sure the goroutine + // below terminates on function return. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + for { + select { + case <-ctx.Done(): + return + case <-time.After(time.Second): + state, err := p.container.State() + if err != nil { + p.errorChan <- err + return + } + if state != define.ContainerStateRunning { + p.errorChan <- fmt.Errorf("%w: %s", ErrNoReadyMessage, p.container.ID()) + return + } + } + } + }() + } + + // Wait for the ready/error channel. + select { + case <-p.readyChan: + return nil + case err := <-p.errorChan: + return err + } +} diff --git a/vendor/github.com/containers/podman/v5/pkg/trust/policy.go b/vendor/github.com/containers/podman/v5/pkg/trust/policy.go new file mode 100644 index 00000000..dfdc75cb --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/trust/policy.go @@ -0,0 +1,262 @@ +package trust + +import ( + "bufio" + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/sirupsen/logrus" + "go.podman.io/common/pkg/config" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/homedir" +) + +// policyContent is the overall structure of a policy.json file (= c/image/v5/signature.Policy) +type policyContent struct { + Default []repoContent `json:"default"` + Transports transportsContent `json:"transports,omitempty"` +} + +// transportsContent contains policies for individual transports (= c/image/v5/signature.Policy.Transports) +type transportsContent map[string]repoMap + +// repoMap maps a scope name to requirements that apply to that scope (= c/image/v5/signature.PolicyTransportScopes) +type repoMap map[string][]repoContent + +// repoContent is a single policy requirement (one of possibly several for a scope), representing all of the individual alternatives in a single merged struct +// (= c/image/v5/signature.{PolicyRequirement,pr*}) +type repoContent struct { + Type string `json:"type"` + KeyType string `json:"keyType,omitempty"` + KeyPath string `json:"keyPath,omitempty"` + KeyPaths []string `json:"keyPaths,omitempty"` + KeyData string `json:"keyData,omitempty"` + SignedIdentity json.RawMessage `json:"signedIdentity,omitempty"` +} + +// genericPolicyContent is the overall structure of a policy.json file (= c/image/v5/signature.Policy), using generic data for individual requirements. +type genericPolicyContent struct { + Default json.RawMessage `json:"default"` + Transports genericTransportsContent `json:"transports,omitempty"` +} + +// genericTransportsContent contains policies for individual transports (= c/image/v5/signature.Policy.Transports), using generic data for individual requirements. +type genericTransportsContent map[string]genericRepoMap + +// genericRepoMap maps a scope name to requirements that apply to that scope (= c/image/v5/signature.PolicyTransportScopes) +type genericRepoMap map[string]json.RawMessage + +// DefaultPolicyPath returns a path to the default policy of the system. +func DefaultPolicyPath(sys *types.SystemContext) string { + if sys != nil && sys.SignaturePolicyPath != "" { + return sys.SignaturePolicyPath + } + + userPolicyFilePath := filepath.Join(homedir.Get(), filepath.FromSlash(".config/containers/policy.json")) + err := fileutils.Exists(userPolicyFilePath) + if err == nil { + return userPolicyFilePath + } + if !errors.Is(err, fs.ErrNotExist) { + logrus.Warnf("Error trying to read local config file: %s", err.Error()) + } + + systemDefaultPolicyPath := config.DefaultSignaturePolicyPath + if sys != nil && sys.RootForImplicitAbsolutePaths != "" { + return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath) + } + return systemDefaultPolicyPath +} + +// gpgIDReader returns GPG key IDs of keys stored at the provided path. +// It exists only for tests, production code should always use getGPGIdFromKeyPath. +type gpgIDReader func(string) []string + +// createTmpFile creates a temp file under dir and writes the content into it +func createTmpFile(dir, pattern string, content []byte) (string, error) { + tmpfile, err := os.CreateTemp(dir, pattern) + if err != nil { + return "", err + } + defer tmpfile.Close() + + if _, err := tmpfile.Write(content); err != nil { + return "", err + } + return tmpfile.Name(), nil +} + +// getGPGIdFromKeyPath returns GPG key IDs of keys stored at the provided path. +func getGPGIdFromKeyPath(path string) []string { + cmd := exec.Command("gpg2", "--with-colons", path) + results, err := cmd.Output() + if err != nil { + logrus.Errorf("Getting key identity: %s", err) + return nil + } + return parseUids(results) +} + +// getGPGIdFromKeyData returns GPG key IDs of keys in the provided keyring. +func getGPGIdFromKeyData(idReader gpgIDReader, key string) []string { + decodeKey, err := base64.StdEncoding.DecodeString(key) + if err != nil { + logrus.Errorf("%s, error decoding key data", err) + return nil + } + tmpfileName, err := createTmpFile("", "", decodeKey) + if err != nil { + logrus.Errorf("Creating key date temp file %s", err) + } + defer os.Remove(tmpfileName) + return idReader(tmpfileName) +} + +func parseUids(colonDelimitKeys []byte) []string { + var parseduids []string + scanner := bufio.NewScanner(bytes.NewReader(colonDelimitKeys)) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") { + uid := strings.Split(line, ":")[9] + if uid == "" { + continue + } + parseduid := uid + if ltidx := strings.Index(uid, "<"); ltidx != -1 { + subuid := parseduid[ltidx+1:] + if gtidx := strings.Index(subuid, ">"); gtidx != -1 { + parseduid = subuid[:gtidx] + } + } + parseduids = append(parseduids, parseduid) + } + } + return parseduids +} + +// getPolicy parses policy.json into policyContent. +func getPolicy(policyPath string) (policyContent, error) { + var policyContentStruct policyContent + policyContent, err := os.ReadFile(policyPath) + if err != nil { + return policyContentStruct, fmt.Errorf("unable to read policy file: %w", err) + } + if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { + return policyContentStruct, fmt.Errorf("could not parse trust policies from %s: %w", policyPath, err) + } + return policyContentStruct, nil +} + +var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "sigstoreSigned": "sigstoreSigned", "reject": "reject"} + +func trustTypeDescription(trustType string) string { + trustDescription, exist := typeDescription[trustType] + if !exist { + logrus.Warnf("Invalid trust type %s", trustType) + } + return trustDescription +} + +// AddPolicyEntriesInput collects some parameters to AddPolicyEntries, +// primarily so that the callers use named values instead of just strings in a sequence. +type AddPolicyEntriesInput struct { + Scope string // "default" or a docker/atomic scope name + Type string + PubKeyFiles []string // For signature enforcement types, paths to public keys files (where the image needs to be signed by at least one key from _each_ of the files). File format depends on Type. +} + +// AddPolicyEntries adds one or more policy entries necessary to implement AddPolicyEntriesInput. +func AddPolicyEntries(policyPath string, input AddPolicyEntriesInput) error { + var ( + policyContentStruct genericPolicyContent + newReposContent []repoContent + ) + trustType := input.Type + if trustType == "accept" { + trustType = "insecureAcceptAnything" + } + pubkeysfile := input.PubKeyFiles + + // The error messages in validation failures use input.Type instead of trustType to match the user’s input. + switch trustType { + case "insecureAcceptAnything", "reject": + if len(pubkeysfile) != 0 { + return fmt.Errorf("%d public keys unexpectedly provided for trust type %v", len(pubkeysfile), input.Type) + } + newReposContent = append(newReposContent, repoContent{Type: trustType}) + + case "signedBy": + if len(pubkeysfile) == 0 { + return errors.New("at least one public key must be defined for type 'signedBy'") + } + for _, filepath := range pubkeysfile { + newReposContent = append(newReposContent, repoContent{Type: trustType, KeyType: "GPGKeys", KeyPath: filepath}) + } + + case "sigstoreSigned": + if len(pubkeysfile) == 0 { + return errors.New("at least one public key must be defined for type 'sigstoreSigned'") + } + for _, filepath := range pubkeysfile { + newReposContent = append(newReposContent, repoContent{Type: trustType, KeyPath: filepath}) + } + + default: + return fmt.Errorf("unknown trust type %q", input.Type) + } + newReposJSON, err := json.Marshal(newReposContent) + if err != nil { + return err + } + + err = fileutils.Exists(policyPath) + if !os.IsNotExist(err) { + policyContent, err := os.ReadFile(policyPath) + if err != nil { + return err + } + if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { + return errors.New("could not read trust policies") + } + } + if input.Scope == "default" { + policyContentStruct.Default = json.RawMessage(newReposJSON) + } else { + if len(policyContentStruct.Default) == 0 { + return errors.New("default trust policy must be set") + } + registryExists := false + for transport, transportval := range policyContentStruct.Transports { + _, registryExists = transportval[input.Scope] + if registryExists { + policyContentStruct.Transports[transport][input.Scope] = json.RawMessage(newReposJSON) + break + } + } + if !registryExists { + if policyContentStruct.Transports == nil { + policyContentStruct.Transports = make(map[string]genericRepoMap) + } + if policyContentStruct.Transports["docker"] == nil { + policyContentStruct.Transports["docker"] = make(map[string]json.RawMessage) + } + policyContentStruct.Transports["docker"][input.Scope] = json.RawMessage(newReposJSON) + } + } + + data, err := json.MarshalIndent(policyContentStruct, "", " ") + if err != nil { + return fmt.Errorf("setting trust policy: %w", err) + } + return os.WriteFile(policyPath, data, 0o644) +} diff --git a/vendor/github.com/containers/podman/v5/pkg/trust/registries.go b/vendor/github.com/containers/podman/v5/pkg/trust/registries.go new file mode 100644 index 00000000..371eee81 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/trust/registries.go @@ -0,0 +1,126 @@ +package trust + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/homedir" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/fileutils" + "sigs.k8s.io/yaml" +) + +// registryConfiguration is one of the files in registriesDirPath configuring lookaside locations, or the result of merging them all. +// NOTE: Keep this in sync with docs/registries.d.md! +type registryConfiguration struct { + DefaultDocker *registryNamespace `json:"default-docker"` + // The key is a namespace, using fully-expanded Docker reference format or parent namespaces (per dockerReference.PolicyConfiguration*), + Docker map[string]registryNamespace `json:"docker"` +} + +// registryNamespace defines lookaside locations for a single namespace. +type registryNamespace struct { + Lookaside string `json:"lookaside"` // For reading, and if LookasideStaging is not present, for writing. + LookasideStaging string `json:"lookaside-staging"` // For writing only. + SigStore string `json:"sigstore"` // For reading, and if SigStoreStaging is not present, for writing. + SigStoreStaging string `json:"sigstore-staging"` // For writing only. +} + +// systemRegistriesDirPath is the path to registries.d. +const systemRegistriesDirPath = "/etc/containers/registries.d" + +// userRegistriesDir is the path to the per user registries.d. +var userRegistriesDir = filepath.FromSlash(".config/containers/registries.d") + +// RegistriesDirPath returns a path to registries.d +func RegistriesDirPath(sys *types.SystemContext) string { + if sys != nil && sys.RegistriesDirPath != "" { + return sys.RegistriesDirPath + } + userRegistriesDirPath := filepath.Join(homedir.Get(), userRegistriesDir) + if err := fileutils.Exists(userRegistriesDirPath); err == nil { + return userRegistriesDirPath + } + if sys != nil && sys.RootForImplicitAbsolutePaths != "" { + return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath) + } + + return systemRegistriesDirPath +} + +// loadAndMergeConfig loads registries.d configuration files in dirPath +func loadAndMergeConfig(dirPath string) (*registryConfiguration, error) { + mergedConfig := registryConfiguration{Docker: map[string]registryNamespace{}} + dockerDefaultMergedFrom := "" + nsMergedFrom := map[string]string{} + + dir, err := os.Open(dirPath) + if err != nil { + if os.IsNotExist(err) { + return &mergedConfig, nil + } + return nil, err + } + configNames, err := dir.Readdirnames(0) + if err != nil { + return nil, err + } + for _, configName := range configNames { + if !strings.HasSuffix(configName, ".yaml") { + continue + } + configPath := filepath.Join(dirPath, configName) + configBytes, err := os.ReadFile(configPath) + if err != nil { + return nil, err + } + var config registryConfiguration + err = yaml.Unmarshal(configBytes, &config) + if err != nil { + return nil, fmt.Errorf("parsing %s: %w", configPath, err) + } + if config.DefaultDocker != nil { + if mergedConfig.DefaultDocker != nil { + return nil, fmt.Errorf(`error parsing signature storage configuration: "default-docker" defined both in "%s" and "%s"`, + dockerDefaultMergedFrom, configPath) + } + mergedConfig.DefaultDocker = config.DefaultDocker + dockerDefaultMergedFrom = configPath + } + for nsName, nsConfig := range config.Docker { // includes config.Docker == nil + if _, ok := mergedConfig.Docker[nsName]; ok { + return nil, fmt.Errorf(`error parsing signature storage configuration: "docker" namespace "%s" defined both in "%s" and "%s"`, + nsName, nsMergedFrom[nsName], configPath) + } + mergedConfig.Docker[nsName] = nsConfig + nsMergedFrom[nsName] = configPath + } + } + return &mergedConfig, nil +} + +// registriesDConfigurationForScope returns registries.d configuration for the provided scope. +// scope can be "" to return only the global default configuration entry. +func registriesDConfigurationForScope(registryConfigs *registryConfiguration, scope string) *registryNamespace { + searchScope := scope + if searchScope != "" { + if !strings.Contains(searchScope, "/") { + val, exists := registryConfigs.Docker[searchScope] + if exists { + return &val + } + } + for range strings.SplitSeq(scope, "/") { + val, exists := registryConfigs.Docker[searchScope] + if exists { + return &val + } + if strings.Contains(searchScope, "/") { + searchScope = searchScope[:strings.LastIndex(searchScope, "/")] + } + } + } + return registryConfigs.DefaultDocker +} diff --git a/vendor/github.com/containers/podman/v5/pkg/trust/trust.go b/vendor/github.com/containers/podman/v5/pkg/trust/trust.go new file mode 100644 index 00000000..ae9945aa --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/trust/trust.go @@ -0,0 +1,121 @@ +package trust + +import ( + "fmt" + "maps" + "slices" + "sort" + "strings" +) + +// Policy describes a basic trust policy configuration +type Policy struct { + Transport string `json:"transport"` + Name string `json:"name,omitempty"` + RepoName string `json:"repo_name,omitempty"` + Keys []string `json:"keys,omitempty"` + SignatureStore string `json:"sigstore,omitempty"` + Type string `json:"type"` + GPGId string `json:"gpg_id,omitempty"` +} + +// PolicyDescription returns an user-focused description of the policy in policyPath and registries.d data from registriesDirPath. +func PolicyDescription(policyPath, registriesDirPath string) ([]*Policy, error) { + return policyDescriptionWithGPGIDReader(policyPath, registriesDirPath, getGPGIdFromKeyPath) +} + +// policyDescriptionWithGPGIDReader is PolicyDescription with a gpgIDReader parameter. It exists only to make testing easier. +func policyDescriptionWithGPGIDReader(policyPath, registriesDirPath string, idReader gpgIDReader) ([]*Policy, error) { + policyContentStruct, err := getPolicy(policyPath) + if err != nil { + return nil, fmt.Errorf("could not read trust policies: %w", err) + } + res, err := getPolicyShowOutput(policyContentStruct, registriesDirPath, idReader) + if err != nil { + return nil, fmt.Errorf("could not show trust policies: %w", err) + } + return res, nil +} + +func getPolicyShowOutput(policyContentStruct policyContent, systemRegistriesDirPath string, idReader gpgIDReader) ([]*Policy, error) { + var output []*Policy + + registryConfigs, err := loadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return nil, err + } + + if len(policyContentStruct.Default) > 0 { + template := Policy{ + Transport: "all", + Name: "* (default)", + RepoName: "default", + } + output = append(output, descriptionsOfPolicyRequirements(policyContentStruct.Default, template, registryConfigs, "", idReader)...) + } + transports := slices.Collect(maps.Keys(policyContentStruct.Transports)) + sort.Strings(transports) + for _, transport := range transports { + transval := policyContentStruct.Transports[transport] + if transport == "docker" { + transport = "repository" + } + + scopes := slices.Collect(maps.Keys(transval)) + sort.Strings(scopes) + for _, repo := range scopes { + repoval := transval[repo] + template := Policy{ + Transport: transport, + Name: repo, + RepoName: repo, + } + output = append(output, descriptionsOfPolicyRequirements(repoval, template, registryConfigs, repo, idReader)...) + } + } + return output, nil +} + +// descriptionsOfPolicyRequirements turns reqs into user-readable policy entries, with Transport/Name/Reponame coming from template, potentially looking up scope (which may be "") in registryConfigs. +func descriptionsOfPolicyRequirements(reqs []repoContent, template Policy, registryConfigs *registryConfiguration, scope string, idReader gpgIDReader) []*Policy { + res := []*Policy{} + + var lookasidePath string + registryNamespace := registriesDConfigurationForScope(registryConfigs, scope) + if registryNamespace != nil { + if registryNamespace.Lookaside != "" { + lookasidePath = registryNamespace.Lookaside + } else { // incl. registryNamespace.SigStore == "" + lookasidePath = registryNamespace.SigStore + } + } + + for _, repoele := range reqs { + entry := template + entry.Type = trustTypeDescription(repoele.Type) + + var gpgIDString string + switch repoele.Type { + case "signedBy": + uids := []string{} + if len(repoele.KeyPath) > 0 { + uids = append(uids, idReader(repoele.KeyPath)...) + } + for _, path := range repoele.KeyPaths { + uids = append(uids, idReader(path)...) + } + if len(repoele.KeyData) > 0 { + uids = append(uids, getGPGIdFromKeyData(idReader, repoele.KeyData)...) + } + gpgIDString = strings.Join(uids, ", ") + + case "sigstoreSigned": + gpgIDString = "N/A" // We could potentially return key fingerprints here, but they would not be _GPG_ fingerprints. + } + entry.GPGId = gpgIDString + entry.SignatureStore = lookasidePath // We do this even for sigstoreSigned and things like type: reject, to show that the sigstore is being read. + res = append(res, &entry) + } + + return res +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/changes.go b/vendor/github.com/containers/podman/v5/pkg/util/changes.go new file mode 100644 index 00000000..df6b13bf --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/changes.go @@ -0,0 +1,34 @@ +package util + +import ( + "strings" + "unicode" +) + +// DecodeChanges reads one or more changes from a slice and cleans them up, +// since what we've advertised as being acceptable in the past isn't really. +func DecodeChanges(changes []string) []string { + result := make([]string, 0, len(changes)) + for _, possiblyMultilineChange := range changes { + for change := range strings.SplitSeq(possiblyMultilineChange, "\n") { + // In particular, we document that we accept values + // like "CMD=/bin/sh", which is not valid Dockerfile + // syntax, so we can't just pass such a value directly + // to a parser that's going to rightfully reject it. + // If we trim the string of whitespace at both ends, + // and the first occurrence of "=" is before the first + // whitespace, replace that "=" with whitespace. + change = strings.TrimSpace(change) + if change == "" { + continue + } + firstEqualIndex := strings.Index(change, "=") + firstSpaceIndex := strings.IndexFunc(change, unicode.IsSpace) + if firstEqualIndex != -1 && (firstSpaceIndex == -1 || firstEqualIndex < firstSpaceIndex) { + change = change[:firstEqualIndex] + " " + change[firstEqualIndex+1:] + } + result = append(result, change) + } + } + return result +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/filters.go b/vendor/github.com/containers/podman/v5/pkg/util/filters.go new file mode 100644 index 00000000..d1e33b10 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/filters.go @@ -0,0 +1,74 @@ +package util + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" +) + +// filtersFromRequests extracts the "filters" parameter from the specified +// http.Request. The parameter can either be a `map[string][]string` as done +// in new versions of Docker and libpod, or a `map[string]map[string]bool` as +// done in older versions of Docker. We have to do a bit of Yoga to support +// both - just as Docker does as well. +// +// Please refer to https://github.com/containers/podman/issues/6899 for some +// background. +func FiltersFromRequest(r *http.Request) ([]string, error) { + var ( + compatFilters map[string]map[string]bool + filters map[string][]string + libpodFilters []string + raw []byte + ) + + if _, found := r.URL.Query()["filters"]; found { + raw = []byte(r.Form.Get("filters")) + } else if _, found := r.URL.Query()["Filters"]; found { + raw = []byte(r.Form.Get("Filters")) + } else { + return []string{}, nil + } + + // Backwards compat with older versions of Docker. + if err := json.Unmarshal(raw, &compatFilters); err == nil { + for filterKey, filterMap := range compatFilters { + for filterValue, toAdd := range filterMap { + if toAdd { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) + } + } + } + return libpodFilters, nil + } + + if err := json.Unmarshal(raw, &filters); err != nil { + return nil, err + } + + for filterKey, filterSlice := range filters { + for _, filterValue := range filterSlice { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) + } + } + + return libpodFilters, nil +} + +// PrepareFilters prepares a *map[string][]string of filters to be later searched +// in lipod and compat API to get desired filters +func PrepareFilters(r *http.Request) (*map[string][]string, error) { + filtersList, err := FiltersFromRequest(r) + if err != nil { + return nil, err + } + filterMap := map[string][]string{} + for _, filter := range filtersList { + fname, filter, hasFilter := strings.Cut(filter, "=") + if hasFilter { + filterMap[fname] = append(filterMap[fname], filter) + } + } + return &filterMap, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/kube.go b/vendor/github.com/containers/podman/v5/pkg/util/kube.go new file mode 100644 index 00000000..7159fa74 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/kube.go @@ -0,0 +1,20 @@ +package util + +const ( + // Kube annotation for podman volume driver. + VolumeDriverAnnotation = "volume.podman.io/driver" + // Kube annotation for podman volume type. + VolumeTypeAnnotation = "volume.podman.io/type" + // Kube annotation for podman volume device. + VolumeDeviceAnnotation = "volume.podman.io/device" + // Kube annotation for podman volume UID. + VolumeUIDAnnotation = "volume.podman.io/uid" + // Kube annotation for podman volume GID. + VolumeGIDAnnotation = "volume.podman.io/gid" + // Kube annotation for podman volume mount options. + VolumeMountOptsAnnotation = "volume.podman.io/mount-options" + // Kube annotation for podman volume import source. + VolumeImportSourceAnnotation = "volume.podman.io/import-source" + // Kube annotation for podman volume image. + VolumeImageAnnotation = "volume.podman.io/image" +) diff --git a/vendor/github.com/containers/podman/v5/pkg/util/mount_opts.go b/vendor/github.com/containers/podman/v5/pkg/util/mount_opts.go new file mode 100644 index 00000000..e48ed1cc --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/mount_opts.go @@ -0,0 +1,238 @@ +package util + +import ( + "errors" + "fmt" + "strings" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" +) + +var ( + // ErrBadMntOption indicates that an invalid mount option was passed. + ErrBadMntOption = errors.New("invalid mount option") + // ErrDupeMntOption indicates that a duplicate mount option was passed. + ErrDupeMntOption = errors.New("duplicate mount option passed") +) + +type defaultMountOptions struct { + noexec bool + nosuid bool + nodev bool +} + +type getDefaultMountOptionsFn func(path string) (defaultMountOptions, error) + +// ProcessOptions parses the options for a bind or tmpfs mount and ensures that +// they are sensible and follow convention. The isTmpfs variable controls +// whether extra, tmpfs-specific options will be allowed. +// The sourcePath variable, if not empty, contains a bind mount source. +func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string, error) { + return processOptionsInternal(options, isTmpfs, sourcePath, getDefaultMountOptions) +} + +func processOptionsInternal(options []string, isTmpfs bool, sourcePath string, getDefaultMountOptions getDefaultMountOptionsFn) ([]string, error) { + var ( + foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap, foundCopy, foundNoSwap, foundNoDereference bool + ) + + recursiveBind := true + + newOptions := make([]string, 0, len(options)) + for _, opt := range options { + // Some options have parameters - size, mode + key, _, _ := strings.Cut(opt, "=") + + // add advanced options such as upperdir=/path and workdir=/path, when overlay is specified + if foundOverlay { + if strings.Contains(opt, "upperdir") { + newOptions = append(newOptions, opt) + continue + } + if strings.Contains(opt, "workdir") { + newOptions = append(newOptions, opt) + continue + } + } + if strings.HasPrefix(key, "subpath") { + newOptions = append(newOptions, opt) + continue + } + if strings.HasPrefix(key, "idmap") { + if foundIdmap { + return nil, fmt.Errorf("the 'idmap' option can only be set once: %w", ErrDupeMntOption) + } + foundIdmap = true + newOptions = append(newOptions, opt) + continue + } + + switch key { + case "copy", "nocopy": + if foundCopy { + return nil, fmt.Errorf("only one of 'nocopy' and 'copy' can be used: %w", ErrDupeMntOption) + } + foundCopy = true + case "O": + foundOverlay = true + case "volume-opt": + // Volume-opt should be relayed and processed by driver. + newOptions = append(newOptions, opt) + case "exec", "noexec": + if foundExec { + return nil, fmt.Errorf("only one of 'noexec' and 'exec' can be used: %w", ErrDupeMntOption) + } + foundExec = true + case "suid", "nosuid": + if foundSuid { + return nil, fmt.Errorf("only one of 'nosuid' and 'suid' can be used: %w", ErrDupeMntOption) + } + foundSuid = true + case "nodev", "dev": + if foundDev { + return nil, fmt.Errorf("only one of 'nodev' and 'dev' can be used: %w", ErrDupeMntOption) + } + foundDev = true + case "rw", "ro": + if foundWrite { + return nil, fmt.Errorf("only one of 'rw' and 'ro' can be used: %w", ErrDupeMntOption) + } + foundWrite = true + case "private", "rprivate", "slave", "rslave", "shared", "rshared", "unbindable", "runbindable": + if foundProp { + return nil, fmt.Errorf("only one root propagation mode can be used: %w", ErrDupeMntOption) + } + foundProp = true + case "size": + if !isTmpfs { + return nil, fmt.Errorf("the 'size' option is only allowed with tmpfs mounts: %w", ErrBadMntOption) + } + if foundSize { + return nil, fmt.Errorf("only one tmpfs size can be specified: %w", ErrDupeMntOption) + } + foundSize = true + case "mode": + if !isTmpfs { + return nil, fmt.Errorf("the 'mode' option is only allowed with tmpfs mounts: %w", ErrBadMntOption) + } + if foundMode { + return nil, fmt.Errorf("only one tmpfs mode can be specified: %w", ErrDupeMntOption) + } + foundMode = true + case "tmpcopyup": + if !isTmpfs { + return nil, fmt.Errorf("the 'tmpcopyup' option is only allowed with tmpfs mounts: %w", ErrBadMntOption) + } + if foundCopyUp { + return nil, fmt.Errorf("the 'tmpcopyup' or 'notmpcopyup' option can only be set once: %w", ErrDupeMntOption) + } + foundCopyUp = true + case "consistency": + // Often used on MACs and mistakenly on Linux platforms. + // Since Docker ignores this option so shall we. + continue + case "notmpcopyup": + if !isTmpfs { + return nil, fmt.Errorf("the 'notmpcopyup' option is only allowed with tmpfs mounts: %w", ErrBadMntOption) + } + if foundCopyUp { + return nil, fmt.Errorf("the 'tmpcopyup' or 'notmpcopyup' option can only be set once: %w", ErrDupeMntOption) + } + foundCopyUp = true + // do not propagate notmpcopyup to the OCI runtime + continue + case "noswap": + + if !isTmpfs { + return nil, fmt.Errorf("the 'noswap' option is only allowed with tmpfs mounts: %w", ErrBadMntOption) + } + if rootless.IsRootless() { + return nil, fmt.Errorf("the 'noswap' option is only allowed with rootful tmpfs mounts: %w", ErrBadMntOption) + } + if foundNoSwap { + return nil, fmt.Errorf("the 'tmpswap' option can only be set once: %w", ErrDupeMntOption) + } + foundNoSwap = true + newOptions = append(newOptions, opt) + continue + case "no-dereference": + if foundNoDereference { + return nil, fmt.Errorf("the 'no-dereference' option can only be set once: %w", ErrDupeMntOption) + } + foundNoDereference = true + case define.TypeBind: + recursiveBind = false + fallthrough + case "rbind": + if isTmpfs { + return nil, fmt.Errorf("the 'bind' and 'rbind' options are not allowed with tmpfs mounts: %w", ErrBadMntOption) + } + if foundBind { + return nil, fmt.Errorf("only one of 'rbind' and 'bind' can be used: %w", ErrDupeMntOption) + } + foundBind = true + case "z", "Z": + if isTmpfs { + return nil, fmt.Errorf("the 'z' and 'Z' options are not allowed with tmpfs mounts: %w", ErrBadMntOption) + } + if foundZ { + return nil, fmt.Errorf("only one of 'z' and 'Z' can be used: %w", ErrDupeMntOption) + } + foundZ = true + case "U": + if foundU { + return nil, fmt.Errorf("the 'U' option can only be set once: %w", ErrDupeMntOption) + } + foundU = true + case "noatime": + if !isTmpfs { + return nil, fmt.Errorf("the 'noatime' option is only allowed with tmpfs mounts: %w", ErrBadMntOption) + } + default: + return nil, fmt.Errorf("unknown mount option %q: %w", opt, ErrBadMntOption) + } + newOptions = append(newOptions, opt) + } + + if !foundProp { + if recursiveBind { + newOptions = append(newOptions, "rprivate") + } else { + newOptions = append(newOptions, "private") + } + } + defaults, err := getDefaultMountOptions(sourcePath) + if err != nil { + return nil, err + } + if !foundExec && defaults.noexec { + newOptions = append(newOptions, "noexec") + } + if !foundSuid && defaults.nosuid { + newOptions = append(newOptions, "nosuid") + } + if !foundDev && defaults.nodev { + newOptions = append(newOptions, "nodev") + } + if isTmpfs && !foundCopyUp { + newOptions = append(newOptions, "tmpcopyup") + } + if !isTmpfs && !foundBind { + newOptions = append(newOptions, "rbind") + } + + return newOptions, nil +} + +func ParseDriverOpts(option string) (string, string, error) { + _, val, hasVal := strings.Cut(option, "=") + if !hasVal { + return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption) + } + optKey, optVal, hasOptVal := strings.Cut(val, "=") + if !hasOptVal { + return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption) + } + return optKey, optVal, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/mount_opts_linux.go b/vendor/github.com/containers/podman/v5/pkg/util/mount_opts_linux.go new file mode 100644 index 00000000..bc7c675f --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/mount_opts_linux.go @@ -0,0 +1,23 @@ +package util + +import ( + "os" + + "golang.org/x/sys/unix" +) + +func getDefaultMountOptions(path string) (defaultMountOptions, error) { + opts := defaultMountOptions{false, true, true} + if path == "" { + return opts, nil + } + var statfs unix.Statfs_t + if e := unix.Statfs(path, &statfs); e != nil { + return opts, &os.PathError{Op: "statfs", Path: path, Err: e} + } + opts.nodev = (statfs.Flags&unix.MS_NODEV == unix.MS_NODEV) + opts.noexec = (statfs.Flags&unix.MS_NOEXEC == unix.MS_NOEXEC) + opts.nosuid = (statfs.Flags&unix.MS_NOSUID == unix.MS_NOSUID) + + return opts, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/mount_opts_other.go b/vendor/github.com/containers/podman/v5/pkg/util/mount_opts_other.go new file mode 100644 index 00000000..18a4c0e4 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/mount_opts_other.go @@ -0,0 +1,7 @@ +//go:build !linux + +package util + +func getDefaultMountOptions(_ string) (opts defaultMountOptions, err error) { + return +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/tlsutil/tls.go b/vendor/github.com/containers/podman/v5/pkg/util/tlsutil/tls.go new file mode 100644 index 00000000..2e9552c2 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/tlsutil/tls.go @@ -0,0 +1,32 @@ +package tlsutil + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "os" +) + +func ReadCertBundle(path string) (*x509.CertPool, error) { + pool := x509.NewCertPool() + caPEM, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading cert bundle %s: %w", path, err) + } + for ix := 0; len(caPEM) != 0; ix++ { + var caDER *pem.Block + caDER, caPEM = pem.Decode(caPEM) + if caDER == nil { + return nil, fmt.Errorf("reading cert bundle %s: non-PEM data found", path) + } + if caDER.Type != "CERTIFICATE" { + return nil, fmt.Errorf("reading cert bundle %s: non-certificate type `%s` PEM data found", path, caDER.Type) + } + caCert, err := x509.ParseCertificate(caDER.Bytes) + if err != nil { + return nil, fmt.Errorf("reading cert bundle %s: parsing item %d: %w", path, ix, err) + } + pool.AddCert(caCert) + } + return pool, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/utils.go b/vendor/github.com/containers/podman/v5/pkg/util/utils.go new file mode 100644 index 00000000..5d94f0ef --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/utils.go @@ -0,0 +1,1266 @@ +package util + +import ( + "errors" + "fmt" + "io/fs" + "math" + "math/bits" + "os" + "os/user" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + "syscall" + "time" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/namespaces" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/podman/v5/pkg/signal" + securejoin "github.com/cyphar/filepath-securejoin" + ruser "github.com/moby/sys/user" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "go.podman.io/image/v5/types" + "go.podman.io/storage/pkg/directory" + "go.podman.io/storage/pkg/fileutils" + "go.podman.io/storage/pkg/idtools" + "go.podman.io/storage/pkg/unshare" + stypes "go.podman.io/storage/types" + "golang.org/x/term" +) + +// The flags that an [ug]id mapping can have +type idMapFlags struct { + Extends bool // The "+" flag + UserMap bool // The "u" flag + GroupMap bool // The "g" flag +} + +// Helper function to determine the username/password passed +// in the creds string. It could be either or both. +func parseCreds(creds string) (string, string) { + username, password, _ := strings.Cut(creds, ":") + return username, password +} + +// Takes build context and validates `.containerignore` or `.dockerignore` +// if they are symlink outside of buildcontext. Returns list of files to be +// excluded and resolved path to the ignore files inside build context or error +func ParseDockerignore(containerfiles []string, root string) ([]string, string, error) { + ignoreFile := "" + path, err := securejoin.SecureJoin(root, ".containerignore") + if err != nil { + return nil, ignoreFile, err + } + // set resolved ignore file so imagebuildah + // does not attempts to re-resolve it + ignoreFile = path + ignore, err := os.ReadFile(path) + if err != nil { + var dockerIgnoreErr error + path, symlinkErr := securejoin.SecureJoin(root, ".dockerignore") + if symlinkErr != nil { + return nil, ignoreFile, symlinkErr + } + // set resolved ignore file so imagebuildah + // does not attempts to re-resolve it + ignoreFile = path + ignore, dockerIgnoreErr = os.ReadFile(path) + if errors.Is(dockerIgnoreErr, fs.ErrNotExist) { + // In this case either ignorefile was not found + // or it is a symlink to unexpected file in such + // case manually set ignorefile to `/dev/null` so + // internally imagebuildah does not attempts to re-resolve + // this invalid symlink and instead reads a blank file. + ignoreFile = "/dev/null" + } + // after https://github.com/containers/buildah/pull/4239 build supports + // .containerignore or .dockerignore as ignore file + // so remote must support parsing that. + if dockerIgnoreErr != nil { + for _, containerfile := range containerfiles { + containerfile = strings.TrimPrefix(containerfile, root) + if err := fileutils.Exists(filepath.Join(root, containerfile+".containerignore")); err == nil { + path, symlinkErr = securejoin.SecureJoin(root, containerfile+".containerignore") + if symlinkErr == nil { + ignoreFile = path + ignore, dockerIgnoreErr = os.ReadFile(path) + } + } + if err := fileutils.Exists(filepath.Join(root, containerfile+".dockerignore")); err == nil { + path, symlinkErr = securejoin.SecureJoin(root, containerfile+".dockerignore") + if symlinkErr == nil { + ignoreFile = path + ignore, dockerIgnoreErr = os.ReadFile(path) + } + } + if dockerIgnoreErr == nil { + break + } + } + } + if dockerIgnoreErr != nil && !os.IsNotExist(dockerIgnoreErr) { + return nil, ignoreFile, err + } + } + rawexcludes := strings.Split(string(ignore), "\n") + excludes := make([]string, 0, len(rawexcludes)) + for _, e := range rawexcludes { + if len(e) == 0 || e[0] == '#' { + continue + } + excludes = append(excludes, e) + } + return excludes, ignoreFile, nil +} + +// ParseRegistryCreds takes a credentials string in the form USERNAME:PASSWORD +// and returns a DockerAuthConfig +func ParseRegistryCreds(creds string) (*types.DockerAuthConfig, error) { + username, password := parseCreds(creds) + if username == "" { + fmt.Print("Username: ") + _, err := fmt.Scanln(&username) + if err != nil { + return nil, fmt.Errorf("could not read username: %w", err) + } + } + if password == "" { + fmt.Print("Password: ") + termPassword, err := term.ReadPassword(0) + if err != nil { + return nil, fmt.Errorf("could not read password from terminal: %w", err) + } + password = string(termPassword) + } + + return &types.DockerAuthConfig{ + Username: username, + Password: password, + }, nil +} + +// StringMatchRegexSlice determines if a given string matches one of the given regexes, returns bool +func StringMatchRegexSlice(s string, re []string) bool { + for _, r := range re { + m, err := regexp.MatchString(r, s) + if err == nil && m { + return true + } + } + return false +} + +// ParseSignal parses and validates a signal name or number. +func ParseSignal(rawSignal string) (syscall.Signal, error) { + // Strip off leading dash, to allow -1 or -HUP + basename := strings.TrimPrefix(rawSignal, "-") + + sig, err := signal.ParseSignal(basename) + if err != nil { + return -1, err + } + // 64 is SIGRTMAX; wish we could get this from a standard Go library + if sig < 1 || sig > 64 { + return -1, errors.New("valid signals are 1 through 64") + } + return sig, nil +} + +func getRootlessKeepIDMapping(uid, gid int, uids, gids []idtools.IDMap, maxSize int) (*stypes.IDMappingOptions, int, int, error) { + options := stypes.IDMappingOptions{ + HostUIDMapping: false, + HostGIDMapping: false, + } + maxUID, maxGID := 0, 0 + for _, u := range uids { + maxUID += u.Size + } + for _, g := range gids { + maxGID += g.Size + } + if maxSize > 0 { + // If maxSize is set, we need to ensure that the mappings are within the available range + maxUID = min(maxUID, maxSize-1) + maxGID = min(maxGID, maxSize-1) + } + + options.UIDMap, options.GIDMap = nil, nil + + if len(uids) > 0 && uid != 0 { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) + } + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) + if maxUID > uid { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) + } + + if len(gids) > 0 && gid != 0 { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) + } + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) + if maxGID > gid { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) + } + + return &options, uid, gid, nil +} + +// GetKeepIDMapping returns the mappings and the user to use when keep-id is used +func GetKeepIDMapping(opts *namespaces.KeepIDUserNsOptions) (*stypes.IDMappingOptions, int, int, error) { + if !rootless.IsRootless() { + options := stypes.IDMappingOptions{ + HostUIDMapping: false, + HostGIDMapping: false, + } + uids, gids, err := unshare.GetHostIDMappings("") + if err != nil { + return nil, 0, 0, err + } + options.UIDMap = RuntimeSpecToIDtools(uids) + options.GIDMap = RuntimeSpecToIDtools(gids) + + uid, gid := 0, 0 + if opts.UID != nil { + uid = int(*opts.UID) + } + if opts.GID != nil { + gid = int(*opts.GID) + } + + return &options, uid, gid, nil + } + + uid := rootless.GetRootlessUID() + gid := rootless.GetRootlessGID() + if opts.UID != nil { + uid = int(*opts.UID) + } + if opts.GID != nil { + gid = int(*opts.GID) + } + maxSize := 0 + if opts.MaxSize != nil { + maxSize = int(*opts.MaxSize) + } + + uids, gids, err := rootless.GetConfiguredMappings(true) + if err != nil { + return nil, -1, -1, fmt.Errorf("cannot read mappings: %w", err) + } + + return getRootlessKeepIDMapping(uid, gid, uids, gids, maxSize) +} + +// GetNoMapMapping returns the mappings and the user to use when nomap is used +func GetNoMapMapping() (*stypes.IDMappingOptions, int, int, error) { + if !rootless.IsRootless() { + return nil, -1, -1, errors.New("nomap is only supported in rootless mode") + } + options := stypes.IDMappingOptions{ + HostUIDMapping: false, + HostGIDMapping: false, + } + uids, gids, err := rootless.GetConfiguredMappings(false) + if err != nil { + return nil, -1, -1, fmt.Errorf("cannot read mappings: %w", err) + } + if len(uids) == 0 || len(gids) == 0 { + return nil, -1, -1, fmt.Errorf("nomap requires additional UIDs or GIDs defined in /etc/subuid and /etc/subgid to function correctly: %w", err) + } + options.UIDMap, options.GIDMap = nil, nil + uid, gid := 0, 0 + for _, u := range uids { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: uid + 1, Size: u.Size}) + uid += u.Size + } + for _, g := range gids { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: gid + 1, Size: g.Size}) + gid += g.Size + } + return &options, 0, 0, nil +} + +// Map a given ID to the Parent/Host ID of a given mapping, and return +// its corresponding ID/ContainerID. +// Returns an error if the given ID is not found on the mapping parents +func mapIDwithMapping(id uint64, mapping []ruser.IDMap, mapSetting string) (mappedid uint64, err error) { + for _, v := range mapping { + if v.Count == 0 { + continue + } + if id >= uint64(v.ParentID) && id < uint64(v.ParentID+v.Count) { + offset := id - uint64(v.ParentID) + return uint64(v.ID) + offset, nil + } + } + return uint64(0), fmt.Errorf("parent ID %s %d is not mapped/delegated", mapSetting, id) +} + +// Parse flags from spec +// The `u` and `g` flags can be used to enforce that the mapping applies +// exclusively to UIDs or GIDs. +// +// The `+` flag is interpreted as if the mapping replaces previous mappings +// removing any conflicting mapping from those before adding this one. +func parseFlags(spec []string) (flags idMapFlags, read int, err error) { + flags.Extends = false + flags.UserMap = false + flags.GroupMap = false + for read, char := range spec[0] { + switch { + case '0' <= char && char <= '9': + return flags, read, nil + case char == '+': + flags.Extends = true + case char == 'u': + flags.UserMap = true + case char == 'g': + flags.GroupMap = true + case true: + return flags, 0, fmt.Errorf("invalid mapping: %v. Unknown flag %v", spec, char) + } + } + return flags, read, fmt.Errorf("invalid mapping: %v, parsing flags", spec) +} + +// Extension of idTools.parseTriple that parses idmap triples. +// The triple should be a length 3 string array, containing: +// - Flags and ContainerID +// - HostID +// - Size +// +// parseTriple returns the parsed mapping, the mapping flags and +// any possible error. If the error is not-nil, the mapping and flags +// are not well-defined. +// +// idTools.parseTriple is extended here with the following enhancements: +// +// HostID @ syntax: +// ================= +// HostID may use the "@" syntax: The "101001:@1001:1" mapping +// means "take the 1001 id from the parent namespace and map it to 101001" +// +// Flags: +// ====== +// Flags can be used to tell the caller how should the mapping be interpreted +func parseTriple(spec []string, parentMapping []ruser.IDMap, mapSetting string) (mappings []idtools.IDMap, flags idMapFlags, err error) { + if len(spec[0]) == 0 { + return mappings, flags, fmt.Errorf("invalid empty container id at %s map: %v", mapSetting, spec) + } + var cids, hids, sizes []uint64 + var cid, hid uint64 + var hidIsParent bool + flags, i, err := parseFlags(spec) + if err != nil { + return mappings, flags, err + } + // If no "u" nor "g" flag is given, assume the mapping applies to both + if !flags.UserMap && !flags.GroupMap { + flags.UserMap = true + flags.GroupMap = true + } + // Parse the container ID, which must be an integer: + cid, err = strconv.ParseUint(spec[0][i:], 10, 32) + if err != nil { + return mappings, flags, fmt.Errorf("parsing id map value %q: %w", spec[0], err) + } + // Parse the host id, which may be integer or @ + if len(spec[1]) == 0 { + return mappings, flags, fmt.Errorf("invalid empty host id at %s map: %v", mapSetting, spec) + } + if spec[1][0] != '@' { + hidIsParent = false + hid, err = strconv.ParseUint(spec[1], 10, 32) + } else { + // Parse @, where is an integer corresponding to the parent mapping + hidIsParent = true + hid, err = strconv.ParseUint(spec[1][1:], 10, 32) + } + if err != nil { + return mappings, flags, fmt.Errorf("parsing id map value %q: %w", spec[1], err) + } + // Parse the size of the mapping, which must be an integer + sz, err := strconv.ParseUint(spec[2], 10, 32) + if err != nil { + return mappings, flags, fmt.Errorf("parsing id map value %q: %w", spec[2], err) + } + + if hidIsParent { + if (mapSetting == "UID" && flags.UserMap) || (mapSetting == "GID" && flags.GroupMap) { + for i := range sz { + cids = append(cids, cid+i) + mappedID, err := mapIDwithMapping(hid+i, parentMapping, mapSetting) + if err != nil { + return mappings, flags, err + } + hids = append(hids, mappedID) + sizes = append(sizes, 1) + } + } + } else { + cids = []uint64{cid} + hids = []uint64{hid} + sizes = []uint64{sz} + } + + // Avoid possible integer overflow on 32bit builds + if bits.UintSize == 32 { + for i := range cids { + if cids[i] > math.MaxInt32 || hids[i] > math.MaxInt32 || sizes[i] > math.MaxInt32 { + return mappings, flags, fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"[+ug]uint32:[@]uint32[:uint32]\"] : %q", mapSetting, spec) + } + } + } + for i := range cids { + mappings = append(mappings, idtools.IDMap{ + ContainerID: int(cids[i]), + HostID: int(hids[i]), + Size: int(sizes[i]), + }) + } + return mappings, flags, nil +} + +// Remove any conflicting mapping from mapping present in extension, so +// extension can be appended to mapping without conflicts. +// Returns the resulting mapping, with extension appended to it. +func breakInsert(mapping []idtools.IDMap, extension idtools.IDMap) (result []idtools.IDMap) { + // Two steps: + // 1. Remove extension regions from mapping + // For each element in mapping, remove those parts of the mapping + // that overlap with the extension, both in the container range + // or in the host range. + // 2. Add extension to mapping + // Step 1: Remove extension regions from mapping + for _, mapPiece := range mapping { + // Make container and host ranges comparable, by computing their + // extension relative to the start of the mapPiece: + range1Start := extension.ContainerID - mapPiece.ContainerID + range2Start := extension.HostID - mapPiece.HostID + + // Range end relative to mapPiece range + range1End := range1Start + extension.Size + range2End := range2Start + extension.Size + + // mapPiece range: + mapPieceStart := 0 + mapPieceEnd := mapPiece.Size + + if range1End < mapPieceStart || range1Start >= mapPieceEnd { + // out of range, forget about it + range1End = -1 + range1Start = -1 + } else { + // clip limits removal to mapPiece + range1End = min(range1End, mapPieceEnd) + range1Start = max(range1Start, mapPieceStart) + } + + if range2End < mapPieceStart || range2Start >= mapPieceEnd { + // out of range, forget about it + range2End = -1 + range2Start = -1 + } else { + // clip limits removal to mapPiece + range2End = min(range2End, mapPieceEnd) + range2Start = max(range2Start, mapPieceStart) + } + + // If there is nothing to remove, append the original and continue: + if range1Start == -1 && range2Start == -1 { + result = append(result, mapPiece) + continue + } + + // If there is one range to remove, save it at range1: + if range1Start == -1 && range2Start != -1 { + range1Start = range2Start + range1End = range2End + range2Start = -1 + range2End = -1 + } + + // If we have two valid ranges, merge them into range1 if possible + if range2Start != -1 { + // Swap ranges so always range1Start is <= range2Start + if range2Start < range1Start { + range1Start, range2Start = range2Start, range1Start + range1End, range2End = range2End, range1End + } + // If there is overlap, merge them: + if range1End >= range2Start { + range1End = max(range1End, range2End) + range2Start = -1 + range2End = -1 + } + } + + if range1Start > 0 { + // Append everything before range1Start + result = append(result, idtools.IDMap{ + ContainerID: mapPiece.ContainerID, + HostID: mapPiece.HostID, + Size: range1Start, + }) + } + if range2Start == -1 { + // Append everything after range1 + if mapPiece.Size-range1End > 0 { + result = append(result, idtools.IDMap{ + ContainerID: mapPiece.ContainerID + range1End, + HostID: mapPiece.HostID + range1End, + Size: mapPiece.Size - range1End, + }) + } + } else { + // Append everything between range1 and range2 + result = append(result, idtools.IDMap{ + ContainerID: mapPiece.ContainerID + range1End, + HostID: mapPiece.HostID + range1End, + Size: range2Start - range1End, + }) + // Append everything after range2 + if mapPiece.Size-range2End > 0 { + result = append(result, idtools.IDMap{ + ContainerID: mapPiece.ContainerID + range2End, + HostID: mapPiece.HostID + range2End, + Size: mapPiece.Size - range2End, + }) + } + } + } + // Step 2. Add extension to mapping + result = append(result, extension) + return result +} + +// A multirange is a list of [start,end) ranges and is expressed as +// an array of length-2 integers. +// +// This function computes availableRanges = fullRanges - usedRanges, +// where all variables are multiranges. +// The subtraction operation is defined as "return the multirange +// containing all integers found in fullRanges and not found in usedRanges. +func getAvailableIDRanges(fullRanges, usedRanges [][2]int) (availableRanges [][2]int) { + // Sort them + sort.Slice(fullRanges, func(i, j int) bool { + return fullRanges[i][0] < fullRanges[j][0] + }) + + if len(usedRanges) == 0 { + return fullRanges + } + + sort.Slice(usedRanges, func(i, j int) bool { + return usedRanges[i][0] < usedRanges[j][0] + }) + + // To traverse usedRanges + i := 0 + nextUsedID := usedRanges[i][0] + nextUsedIDEnd := usedRanges[i][1] + + for _, fullRange := range fullRanges { + currentIDToProcess := fullRange[0] + for currentIDToProcess < fullRange[1] { + switch { + case nextUsedID == -1: + // No further used ids, append all the remaining ranges + availableRanges = append(availableRanges, [2]int{currentIDToProcess, fullRange[1]}) + currentIDToProcess = fullRange[1] + case currentIDToProcess < nextUsedID: + // currentIDToProcess is not used, append: + if fullRange[1] <= nextUsedID { + availableRanges = append(availableRanges, [2]int{currentIDToProcess, fullRange[1]}) + currentIDToProcess = fullRange[1] + } else { + availableRanges = append(availableRanges, [2]int{currentIDToProcess, nextUsedID}) + currentIDToProcess = nextUsedID + } + case currentIDToProcess == nextUsedID: + // currentIDToProcess and all ids until nextUsedIDEnd are used + // Advance currentIDToProcess + currentIDToProcess = min(fullRange[1], nextUsedIDEnd) + default: // currentIDToProcess > nextUsedID + // Increment nextUsedID so it is >= currentIDToProcess + // Go to next used block if this one is all behind: + if currentIDToProcess >= nextUsedIDEnd { + i += 1 + if i == len(usedRanges) { + // No more used ranges + nextUsedID = -1 + } else { + nextUsedID = usedRanges[i][0] + nextUsedIDEnd = usedRanges[i][1] + } + continue + } else { // currentIDToProcess < nextUsedIDEnd + currentIDToProcess = min(fullRange[1], nextUsedIDEnd) + } + } + } + } + return availableRanges +} + +// Gets the multirange of subordinated ids from parentMapping and the +// multirange of already assigned ids from idmap, and returns the +// multirange of unassigned subordinated ids. +func getAvailableIDRangesFromMappings(idmap []idtools.IDMap, parentMapping []ruser.IDMap) (availableRanges [][2]int) { + // Get all subordinated ids from parentMapping: + fullRanges := [][2]int{} // {Multirange: [start, end), [start, end), ...} + for _, mapPiece := range parentMapping { + fullRanges = append(fullRanges, [2]int{int(mapPiece.ID), int(mapPiece.ID + mapPiece.Count)}) + } + + // Get the ids already mapped: + usedRanges := [][2]int{} + for _, mapPiece := range idmap { + usedRanges = append(usedRanges, [2]int{mapPiece.HostID, mapPiece.HostID + mapPiece.Size}) + } + + // availableRanges = fullRanges - usedRanges + availableRanges = getAvailableIDRanges(fullRanges, usedRanges) + return availableRanges +} + +// Fills unassigned idmap ContainerIDs, starting from zero with all +// the available ids given by availableRanges. +// Returns the filled idmap. +func fillIDMap(idmap []idtools.IDMap, availableRanges [][2]int) (output []idtools.IDMap) { + idmapByCid := append([]idtools.IDMap{}, idmap...) + sort.Slice(idmapByCid, func(i, j int) bool { + return idmapByCid[i].ContainerID < idmapByCid[j].ContainerID + }) + + if len(availableRanges) == 0 { + return idmapByCid + } + + i := 0 // to iterate through availableRanges + nextCid := 0 + nextAvailHid := availableRanges[i][0] + + for _, mapPiece := range idmapByCid { + // While there are available IDs to map and unassigned + // container ids, map the available ids: + for nextCid < mapPiece.ContainerID && nextAvailHid != -1 { + size := min(mapPiece.ContainerID-nextCid, availableRanges[i][1]-nextAvailHid) + output = append(output, idtools.IDMap{ + ContainerID: nextCid, + HostID: nextAvailHid, + Size: size, + }) + nextCid += size + if nextAvailHid+size < availableRanges[i][1] { + nextAvailHid += size + } else { + i += 1 + if i == len(availableRanges) { + nextAvailHid = -1 + continue + } + nextAvailHid = availableRanges[i][0] + } + } + // The given mapping does not change + output = append(output, mapPiece) + nextCid += mapPiece.Size + } + // After the last given mapping is mapped, we use all the remaining + // ids to map the rest of the space + for nextAvailHid != -1 { + size := availableRanges[i][1] - nextAvailHid + output = append(output, idtools.IDMap{ + ContainerID: nextCid, + HostID: nextAvailHid, + Size: size, + }) + nextCid += size + i += 1 + if i == len(availableRanges) { + nextAvailHid = -1 + continue + } + nextAvailHid = availableRanges[i][0] + } + return output +} + +func addOneMapping(idmap []idtools.IDMap, fillMap bool, mapping idtools.IDMap, flags idMapFlags, mapSetting string) ([]idtools.IDMap, bool) { + // If we are mapping uids and the spec doesn't have the usermap flag, ignore it + if mapSetting == "UID" && !flags.UserMap { + return idmap, fillMap + } + // If we are mapping gids and the spec doesn't have the groupmap flag, ignore it + if mapSetting == "GID" && !flags.GroupMap { + return idmap, fillMap + } + + // Zero-size mapping is ignored + if mapping.Size == 0 { + return idmap, fillMap + } + + // Not extending, just append: + if !flags.Extends { + idmap = append(idmap, mapping) + return idmap, fillMap + } + // Break and extend the last mapping: + + // Extending without any mapping, if rootless, we will fill + // the space with the remaining IDs: + if len(idmap) == 0 && rootless.IsRootless() { + fillMap = true + } + + idmap = breakInsert(idmap, mapping) + return idmap, fillMap +} + +// Extension of idTools.ParseIDMap that parses idmap triples from string. +// This extension accepts additional flags that control how the mapping is done +func ParseIDMap(mapSpec []string, mapSetting string, parentMapping []ruser.IDMap) (idmap []idtools.IDMap, err error) { + stdErr := fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"[+ug]uint32:[@]uint32[:uint32]\"] : %q", mapSetting, mapSpec) + // When fillMap is true, the given mapping will be filled with the remaining subordinate available ids + fillMap := false + for _, idMapSpec := range mapSpec { + if idMapSpec == "" { + continue + } + idSpec := strings.Split(idMapSpec, ":") + // if it's a length-2 list assume the size is 1: + if len(idSpec) == 2 { + idSpec = append(idSpec, "1") + } + if len(idSpec)%3 != 0 { + return nil, stdErr + } + for i := range idSpec { + if i%3 != 0 { + continue + } + if len(idSpec[i]) == 0 { + return nil, stdErr + } + // Parse this mapping: + mappings, flags, err := parseTriple(idSpec[i:i+3], parentMapping, mapSetting) + if err != nil { + return nil, err + } + for _, mapping := range mappings { + idmap, fillMap = addOneMapping(idmap, fillMap, mapping, flags, mapSetting) + } + } + } + if fillMap { + availableRanges := getAvailableIDRangesFromMappings(idmap, parentMapping) + idmap = fillIDMap(idmap, availableRanges) + } + + if len(idmap) == 0 { + return idmap, nil + } + idmap = sortAndMergeConsecutiveMappings(idmap) + return idmap, nil +} + +// Given a mapping, sort all entries by their ContainerID then and merge +// entries that are consecutive. +func sortAndMergeConsecutiveMappings(idmap []idtools.IDMap) (finalIDMap []idtools.IDMap) { + idmapByCid := append([]idtools.IDMap{}, idmap...) + sort.Slice(idmapByCid, func(i, j int) bool { + return idmapByCid[i].ContainerID < idmapByCid[j].ContainerID + }) + for i, mapPiece := range idmapByCid { + if i == 0 { + finalIDMap = append(finalIDMap, mapPiece) + continue + } + lastMap := finalIDMap[len(finalIDMap)-1] + containersMatch := lastMap.ContainerID+lastMap.Size == mapPiece.ContainerID + hostsMatch := lastMap.HostID+lastMap.Size == mapPiece.HostID + if containersMatch && hostsMatch { + finalIDMap[len(finalIDMap)-1].Size += mapPiece.Size + } else { + finalIDMap = append(finalIDMap, mapPiece) + } + } + return finalIDMap +} + +// Extension of idTools.parseAutoTriple that parses idmap triples. +// The triple should be a length 3 string array, containing: +// - Flags and ContainerID +// - HostID +// - Size +// +// parseAutoTriple returns the parsed mapping and any possible error. +// If the error is not-nil, the mapping is not well-defined. +// +// idTools.parseAutoTriple is extended here with the following enhancements: +// +// HostID @ syntax: +// ================= +// HostID may use the "@" syntax: The "101001:@1001:1" mapping +// means "take the 1001 id from the parent namespace and map it to 101001" +func parseAutoTriple(spec []string, parentMapping []ruser.IDMap, mapSetting string) (mappings []idtools.IDMap, err error) { + if len(spec[0]) == 0 { + return mappings, fmt.Errorf("invalid empty container id at %s map: %v", mapSetting, spec) + } + var cids, hids, sizes []uint64 + var cid, hid uint64 + var hidIsParent bool + // Parse the container ID, which must be an integer: + cid, err = strconv.ParseUint(spec[0][0:], 10, 32) + if err != nil { + return mappings, fmt.Errorf("parsing id map value %q: %w", spec[0], err) + } + // Parse the host id, which may be integer or @ + if len(spec[1]) == 0 { + return mappings, fmt.Errorf("invalid empty host id at %s map: %v", mapSetting, spec) + } + if spec[1][0] != '@' { + hidIsParent = false + hid, err = strconv.ParseUint(spec[1], 10, 32) + } else { + // Parse @, where is an integer corresponding to the parent mapping + hidIsParent = true + hid, err = strconv.ParseUint(spec[1][1:], 10, 32) + } + if err != nil { + return mappings, fmt.Errorf("parsing id map value %q: %w", spec[1], err) + } + // Parse the size of the mapping, which must be an integer + sz, err := strconv.ParseUint(spec[2], 10, 32) + if err != nil { + return mappings, fmt.Errorf("parsing id map value %q: %w", spec[2], err) + } + + if hidIsParent { + for i := range sz { + cids = append(cids, cid+i) + mappedID, err := mapIDwithMapping(hid+i, parentMapping, mapSetting) + if err != nil { + return mappings, err + } + hids = append(hids, mappedID) + sizes = append(sizes, 1) + } + } else { + cids = []uint64{cid} + hids = []uint64{hid} + sizes = []uint64{sz} + } + + // Avoid possible integer overflow on 32bit builds + if bits.UintSize == 32 { + for i := range cids { + if cids[i] > math.MaxInt32 || hids[i] > math.MaxInt32 || sizes[i] > math.MaxInt32 { + return mappings, fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"[+ug]uint32:[@]uint32[:uint32]\"] : %q", mapSetting, spec) + } + } + } + for i := range cids { + mappings = append(mappings, idtools.IDMap{ + ContainerID: int(cids[i]), + HostID: int(hids[i]), + Size: int(sizes[i]), + }) + } + return mappings, nil +} + +// Extension of idTools.ParseIDMap that parses idmap triples from string. +// This extension accepts additional flags that control how the mapping is done +func parseAutoIDMap(mapSpec string, mapSetting string, parentMapping []ruser.IDMap) (idmap []idtools.IDMap, err error) { + stdErr := fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"uint32:[@]uint32[:uint32]\"] : %q", mapSetting, mapSpec) + idSpec := strings.Split(mapSpec, ":") + // if it's a length-2 list assume the size is 1: + if len(idSpec) == 2 { + idSpec = append(idSpec, "1") + } + if len(idSpec) != 3 { + return nil, stdErr + } + // Parse this mapping: + mappings, err := parseAutoTriple(idSpec, parentMapping, mapSetting) + if err != nil { + return nil, err + } + idmap = sortAndMergeConsecutiveMappings(mappings) + return idmap, nil +} + +// GetAutoOptions returns an AutoUserNsOptions with the settings to automatically set up +// a user namespace. +func GetAutoOptions(n namespaces.UsernsMode) (*stypes.AutoUserNsOptions, error) { + mode, opts, hasOpts := strings.Cut(string(n), ":") + if mode != "auto" { + return nil, fmt.Errorf("wrong user namespace mode") + } + options := stypes.AutoUserNsOptions{} + if !hasOpts { + return &options, nil + } + + parentUIDMap, parentGIDMap, err := rootless.GetAvailableIDMaps() + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // The kernel-provided files only exist if user namespaces are supported + logrus.Debugf("User or group ID mappings not available: %s", err) + } else { + return nil, err + } + } + + for o := range strings.SplitSeq(opts, ",") { + key, val, hasVal := strings.Cut(o, "=") + if !hasVal { + return nil, fmt.Errorf("invalid option specified: %q", o) + } + switch key { + case "size": + s, err := strconv.ParseUint(val, 10, 32) + if err != nil { + return nil, err + } + options.Size = uint32(s) + case "uidmapping": + mapping, err := parseAutoIDMap(val, "UID", parentUIDMap) + if err != nil { + return nil, err + } + options.AdditionalUIDMappings = append(options.AdditionalUIDMappings, mapping...) + case "gidmapping": + mapping, err := parseAutoIDMap(val, "GID", parentGIDMap) + if err != nil { + return nil, err + } + options.AdditionalGIDMappings = append(options.AdditionalGIDMappings, mapping...) + default: + return nil, fmt.Errorf("unknown option specified: %q", key) + } + } + return &options, nil +} + +// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping +func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*stypes.IDMappingOptions, error) { + options := stypes.IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + } + + if mode.IsAuto() { + var err error + options.HostUIDMapping = false + options.HostGIDMapping = false + options.AutoUserNs = true + opts, err := GetAutoOptions(mode) + if err != nil { + return nil, err + } + options.AutoUserNsOpts = *opts + return &options, nil + } + if mode.IsKeepID() || mode.IsNoMap() { + options.HostUIDMapping = false + options.HostGIDMapping = false + return &options, nil + } + + if subGIDMap == "" && subUIDMap != "" { + subGIDMap = subUIDMap + } + if subUIDMap == "" && subGIDMap != "" { + subUIDMap = subGIDMap + } + if len(gidMapSlice) == 0 && len(uidMapSlice) != 0 { + gidMapSlice = uidMapSlice + } + if len(uidMapSlice) == 0 && len(gidMapSlice) != 0 { + uidMapSlice = gidMapSlice + } + + if subUIDMap != "" && subGIDMap != "" { + mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap) + if err != nil { + return nil, err + } + options.UIDMap = mappings.UIDs() + options.GIDMap = mappings.GIDs() + } + + parentUIDMap, parentGIDMap, err := rootless.GetAvailableIDMaps() + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // The kernel-provided files only exist if user namespaces are supported + logrus.Debugf("User or group ID mappings not available: %s", err) + } else { + return nil, err + } + } + + parsedUIDMap, err := ParseIDMap(uidMapSlice, "UID", parentUIDMap) + if err != nil { + return nil, err + } + parsedGIDMap, err := ParseIDMap(gidMapSlice, "GID", parentGIDMap) + if err != nil { + return nil, err + } + + // When running rootless, if one of UID/GID mappings is provided, fill the other one: + if rootless.IsRootless() { + switch { + case len(parsedUIDMap) != 0 && len(parsedGIDMap) == 0: + availableRanges := getAvailableIDRangesFromMappings(parsedGIDMap, parentGIDMap) + parsedGIDMap = fillIDMap(parsedGIDMap, availableRanges) + case len(parsedUIDMap) == 0 && len(parsedGIDMap) != 0: + availableRanges := getAvailableIDRangesFromMappings(parsedUIDMap, parentUIDMap) + parsedUIDMap = fillIDMap(parsedUIDMap, availableRanges) + } + } + + options.UIDMap = append(options.UIDMap, parsedUIDMap...) + options.GIDMap = append(options.GIDMap, parsedGIDMap...) + if len(options.UIDMap) > 0 { + options.HostUIDMapping = false + } + if len(options.GIDMap) > 0 { + options.HostGIDMapping = false + } + return &options, nil +} + +// ParseInputTime takes the users input and to determine if it is valid and +// returns a time format and error. The input is compared to known time formats +// or a duration which implies no-duration +func ParseInputTime(inputTime string, since bool) (time.Time, error) { + timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999", + "2006-01-02Z07:00", "2006-01-02"} + // iterate the supported time formats + for _, tf := range timeFormats { + t, err := time.Parse(tf, inputTime) + if err == nil { + return t, nil + } + } + + unixTimestamp, err := strconv.ParseFloat(inputTime, 64) + if err == nil { + iPart, fPart := math.Modf(unixTimestamp) + return time.Unix(int64(iPart), int64(fPart*1_000_000_000)).UTC(), nil + } + + // input might be a duration + duration, err := time.ParseDuration(inputTime) + if err != nil { + return time.Time{}, errors.New("unable to interpret time value") + } + if since { + return time.Now().Add(-duration), nil + } + return time.Now().Add(duration), nil +} + +func Tmpdir() string { + tmpdir := os.Getenv("TMPDIR") + if tmpdir == "" { + tmpdir = "/var/tmp" + } + + return tmpdir +} + +// ValidateSysctls validates a list of sysctl and returns it. +func ValidateSysctls(strSlice []string) (map[string]string, error) { + sysctl := make(map[string]string) + validSysctlMap := map[string]bool{ + "kernel.msgmax": true, + "kernel.msgmnb": true, + "kernel.msgmni": true, + "kernel.sem": true, + "kernel.shmall": true, + "kernel.shmmax": true, + "kernel.shmmni": true, + "kernel.shm_rmid_forced": true, + } + validSysctlPrefixes := []string{ + "net.", + "fs.mqueue.", + } + + for _, val := range strSlice { + foundMatch := false + arr := strings.Split(val, "=") + if len(arr) < 2 { + return nil, fmt.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val) + } + + trimmed := fmt.Sprintf("%s=%s", strings.TrimSpace(arr[0]), strings.TrimSpace(arr[1])) + if trimmed != val { + return nil, fmt.Errorf("'%s' is invalid, extra spaces found", val) + } + + if validSysctlMap[arr[0]] { + sysctl[arr[0]] = arr[1] + continue + } + + for _, prefix := range validSysctlPrefixes { + if strings.HasPrefix(arr[0], prefix) { + sysctl[arr[0]] = arr[1] + foundMatch = true + break + } + } + if !foundMatch { + return nil, fmt.Errorf("sysctl '%s' is not allowed", arr[0]) + } + } + return sysctl, nil +} + +func CreateIDFile(path string, id string) error { + idFile, err := os.Create(path) + if err != nil { + return fmt.Errorf("creating idfile: %w", err) + } + defer idFile.Close() + if _, err = idFile.WriteString(id); err != nil { + return fmt.Errorf("writing idfile: %w", err) + } + return nil +} + +// DefaultCPUPeriod is the default CPU period (100ms) in microseconds, which is +// the same default as Kubernetes. +const DefaultCPUPeriod uint64 = 100000 + +// CoresToPeriodAndQuota converts a fraction of cores to the equivalent +// Completely Fair Scheduler (CFS) parameters period and quota. +// +// Cores is a fraction of the CFS period that a container may use. Period and +// Quota are in microseconds. +func CoresToPeriodAndQuota(cores float64) (uint64, int64) { + return DefaultCPUPeriod, int64(cores * float64(DefaultCPUPeriod)) +} + +// PeriodAndQuotaToCores takes the CFS parameters period and quota and returns +// a fraction that represents the limit to the number of cores that can be +// utilized over the scheduling period. +// +// Cores is a fraction of the CFS period that a container may use. Period and +// Quota are in microseconds. +func PeriodAndQuotaToCores(period uint64, quota int64) float64 { + return float64(quota) / float64(period) +} + +// IDtoolsToRuntimeSpec converts idtools ID mapping to the one of the runtime spec. +func IDtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) { + for _, idmap := range idMaps { + tempIDMap := specs.LinuxIDMapping{ + ContainerID: uint32(idmap.ContainerID), + HostID: uint32(idmap.HostID), + Size: uint32(idmap.Size), + } + convertedIDMap = append(convertedIDMap, tempIDMap) + } + return convertedIDMap +} + +// RuntimeSpecToIDtoolsTo converts runtime spec to the one of the idtools ID mapping +func RuntimeSpecToIDtools(idMaps []specs.LinuxIDMapping) (convertedIDMap []idtools.IDMap) { + for _, idmap := range idMaps { + tempIDMap := idtools.IDMap{ + ContainerID: int(idmap.ContainerID), + HostID: int(idmap.HostID), + Size: int(idmap.Size), + } + convertedIDMap = append(convertedIDMap, tempIDMap) + } + return convertedIDMap +} + +func LookupUser(name string) (*user.User, error) { + // Assume UID lookup first, if it fails look up by username + if u, err := user.LookupId(name); err == nil { + return u, nil + } + return user.Lookup(name) +} + +// SizeOfPath determines the file usage of a given path. it was called volumeSize in v1 +// and now is made to be generic and take a path instead of a libpod volume +// Deprecated: use github.com/containers/storage/pkg/directory.Size() instead. +func SizeOfPath(path string) (uint64, error) { + size, err := directory.Size(path) + return uint64(size), err +} + +// ParseRestartPolicy parses the value given to the --restart flag and returns the policy +// and restart retries value +func ParseRestartPolicy(policy string) (string, uint, error) { + var ( + retriesUint uint + policyType string + ) + splitRestart := strings.Split(policy, ":") + switch len(splitRestart) { + case 1: + // No retries specified + policyType = splitRestart[0] + if strings.ToLower(splitRestart[0]) == "never" { + policyType = define.RestartPolicyNo + } + case 2: + if strings.ToLower(splitRestart[0]) != "on-failure" { + return "", 0, errors.New("restart policy retries can only be specified with on-failure restart policy") + } + retries, err := strconv.Atoi(splitRestart[1]) + if err != nil { + return "", 0, fmt.Errorf("parsing restart policy retry count: %w", err) + } + if retries < 0 { + return "", 0, errors.New("must specify restart policy retry count as a number greater than 0") + } + retriesUint = uint(retries) + policyType = splitRestart[0] + default: + return "", 0, errors.New("invalid restart policy: may specify retries at most once") + } + return policyType, retriesUint, nil +} + +// ConvertTimeout converts negative timeout to MaxUint32, which indicates approximately infinity, waiting to stop containers +func ConvertTimeout(timeout int) uint { + if timeout < 0 { + return math.MaxUint32 + } + return uint(timeout) +} + +// ExecAddTERM when container does not have a TERM environment variable and +// caller wants a tty, then leak the existing TERM environment into +// the container. +func ExecAddTERM(existingEnv []string, execEnvs map[string]string) { + if _, ok := execEnvs["TERM"]; ok { + return + } + + for _, val := range existingEnv { + if strings.HasPrefix(val, "TERM=") { + return + } + } + + execEnvs["TERM"] = "xterm" +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/utils_darwin.go b/vendor/github.com/containers/podman/v5/pkg/util/utils_darwin.go new file mode 100644 index 00000000..adefa703 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/utils_darwin.go @@ -0,0 +1,11 @@ +//go:build darwin + +package util + +import ( + "errors" +) + +func GetContainerPidInformationDescriptors() ([]string, error) { + return []string{}, errors.New("this function is not supported on darwin") +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/utils_freebsd.go b/vendor/github.com/containers/podman/v5/pkg/util/utils_freebsd.go new file mode 100644 index 00000000..dcfc8c8b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/utils_freebsd.go @@ -0,0 +1,33 @@ +//go:build freebsd + +package util + +import ( + "github.com/opencontainers/runtime-tools/generate" +) + +func GetContainerPidInformationDescriptors() ([]string, error) { + // These are chosen to match the set of AIX format descriptors + // supported in Linux - FreeBSD ps does support (many) others. + return []string{ + "args", + "comm", + "etime", + "group", + "nice", + "pcpu", + "pgid", + "pid", + "ppid", + "rgroup", + "ruser", + "time", + "tty", + "user", + "vsz", + }, nil +} + +func AddPrivilegedDevices(_ *generate.Generator, _ bool) error { + return nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/utils_linux.go b/vendor/github.com/containers/podman/v5/pkg/util/utils_linux.go new file mode 100644 index 00000000..258451d0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/utils_linux.go @@ -0,0 +1,267 @@ +package util + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + + "github.com/containers/podman/v5/libpod/define" + "github.com/containers/podman/v5/pkg/rootless" + "github.com/containers/psgo" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +var ( + errNotADevice = errors.New("not a device node") +) + +// GetContainerPidInformationDescriptors returns a string slice of all supported +// format descriptors of GetContainerPidInformation. +func GetContainerPidInformationDescriptors() ([]string, error) { + return psgo.ListDescriptors(), nil +} + +// FindDeviceNodes parses /dev/ into a set of major:minor -> path, where +// [major:minor] is the device's major and minor numbers formatted as, for +// example, 2:0 and path is the path to the device node. +// Symlinks to nodes are ignored. +// If onlyBlockDevices is specified, character devices are ignored. +func FindDeviceNodes(onlyBlockDevices bool) (map[string]string, error) { + nodes := make(map[string]string) + err := filepath.WalkDir("/dev", func(path string, d fs.DirEntry, err error) error { + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + logrus.Warnf("Error descending into path %s: %v", path, err) + } + return filepath.SkipDir + } + + // If we aren't a device node, do nothing. + if d.Type()&os.ModeDevice == 0 { + return nil + } + + // Ignore character devices, because it is not possible to set limits on them. + // os.ModeCharDevice is usable only when os.ModeDevice is set. + if onlyBlockDevices && d.Type()&os.ModeCharDevice != 0 { + return nil + } + + info, err := d.Info() + if err != nil { + // Info() can return ErrNotExist if the file was deleted between the readdir and stat call. + // This race can happen and is no reason to log an ugly error. If this is a container device + // that is used the code later will print a proper error in such case. + // There also seem to be cases were ErrNotExist is always returned likely due a weird device + // state, e.g. removing a device forcefully. This can happen with iSCSI devices. + if !errors.Is(err, fs.ErrNotExist) { + logrus.Errorf("Failed to get device information for %s: %v", path, err) + } + // return nil here as we want to continue looking for more device and not stop the WalkDir() + return nil + } + // We are a device node. Get major/minor. + sysstat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return errors.New("could not convert stat output for use") + } + // We must typeconvert sysstat.Rdev from uint64->int to avoid constant overflow + rdev := int(sysstat.Rdev) + major := ((rdev >> 8) & 0xfff) | ((rdev >> 32) & ^0xfff) + minor := (rdev & 0xff) | ((rdev >> 12) & ^0xff) + + nodes[fmt.Sprintf("%d:%d", major, minor)] = path + + return nil + }) + if err != nil { + return nil, err + } + + return nodes, nil +} + +// isVirtualConsoleDevice returns true if path is a virtual console device +// (/dev/tty\d+). +// The passed path must be clean (filepath.Clean). +func isVirtualConsoleDevice(path string) bool { + /* + Virtual consoles are of the form `/dev/tty\d+`, any other device such as + /dev/tty, ttyUSB0, or ttyACM0 should not be matched. + See `man 4 console` for more information. + */ + suffix := strings.TrimPrefix(path, "/dev/tty") + if suffix == path || suffix == "" { + return false + } + + // 16bit because, max. supported TTY devices is 512 in Linux 6.1.5. + _, err := strconv.ParseUint(suffix, 10, 16) + return err == nil +} + +func AddPrivilegedDevices(g *generate.Generator, systemdMode bool) error { + hostDevices, err := getDevices("/dev") + if err != nil { + return err + } + + if rootless.IsRootless() { + mounts := make(map[string]any) + for _, m := range g.Mounts() { + mounts[m.Destination] = true + } + newMounts := []spec.Mount{} + for _, d := range hostDevices { + devMnt := spec.Mount{ + Destination: d.Path, + Type: define.TypeBind, + Source: d.Path, + Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, + } + + /* The following devices should not be mounted in rootless containers: + * + * /dev/ptmx: The host-provided /dev/ptmx should not be shared to + * the rootless containers for security reasons, and + * the container runtime will create it for us + * anyway (ln -s /dev/pts/ptmx /dev/ptmx); + * /dev/tty and + * /dev/tty[0-9]+: Prevent the container from taking over the host's + * virtual consoles, even when not in systemd mode + * for backwards compatibility. + */ + if d.Path == "/dev/ptmx" || d.Path == "/dev/tty" || isVirtualConsoleDevice(d.Path) { + continue + } + if _, found := mounts[d.Path]; found { + continue + } + newMounts = append(newMounts, devMnt) + } + g.Config.Mounts = append(newMounts, g.Config.Mounts...) + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } + } else { + for _, d := range hostDevices { + /* Restrict access to the virtual consoles *only* when running + * in systemd mode to improve backwards compatibility. See + * https://github.com/containers/podman/issues/15878. + * + * NOTE: May need revisiting in the future to drop the systemd + * condition if more use cases end up breaking the virtual terminals + * of people who specifically disable the systemd mode. It would + * also provide a more consistent behaviour between rootless and + * rootfull containers. + */ + if systemdMode && isVirtualConsoleDevice(d.Path) { + continue + } + g.AddDevice(d) + } + // Add resources device - need to clear the existing one first. + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } + g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") + } + + return nil +} + +// based on getDevices from runc (libcontainer/devices/devices.go) +func getDevices(path string) ([]spec.LinuxDevice, error) { + files, err := os.ReadDir(path) + if err != nil { + if rootless.IsRootless() && os.IsPermission(err) { + return nil, nil + } + return nil, err + } + out := []spec.LinuxDevice{} + for _, f := range files { + switch { + case f.IsDir(): + switch f.Name() { + // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 + case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": + continue + default: + sub, err := getDevices(filepath.Join(path, f.Name())) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + continue + } + return nil, err + } + if sub != nil { + out = append(out, sub...) + } + continue + } + case f.Name() == "console": + continue + case f.Type()&os.ModeSymlink != 0: + continue + } + + device, err := DeviceFromPath(filepath.Join(path, f.Name())) + if err != nil { + if err == errNotADevice { + continue + } + if errors.Is(err, fs.ErrNotExist) { + continue + } + return nil, err + } + out = append(out, *device) + } + return out, nil +} + +// Copied from github.com/opencontainers/runc/libcontainer/devices +// Given the path to a device look up the information about a linux device +func DeviceFromPath(path string) (*spec.LinuxDevice, error) { + var stat unix.Stat_t + err := unix.Lstat(path, &stat) + if err != nil { + return nil, err + } + var ( + devType string + mode = stat.Mode + devNumber = uint64(stat.Rdev) //nolint: unconvert + m = os.FileMode(mode) + ) + + switch { + case mode&unix.S_IFBLK == unix.S_IFBLK: + devType = "b" + case mode&unix.S_IFCHR == unix.S_IFCHR: + devType = "c" + case mode&unix.S_IFIFO == unix.S_IFIFO: + devType = "p" + default: + return nil, errNotADevice + } + + return &spec.LinuxDevice{ + Type: devType, + Path: path, + FileMode: &m, + UID: &stat.Uid, + GID: &stat.Gid, + Major: int64(unix.Major(devNumber)), + Minor: int64(unix.Minor(devNumber)), + }, nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/utils_supported.go b/vendor/github.com/containers/podman/v5/pkg/util/utils_supported.go new file mode 100644 index 00000000..3f8dca5b --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/utils_supported.go @@ -0,0 +1,39 @@ +//go:build !windows + +package util + +// TODO once rootless function is consolidated under libpod, we +// should work to take darwin from this + +import ( + "path/filepath" + + "github.com/containers/podman/v5/pkg/rootless" + "go.podman.io/storage/pkg/homedir" +) + +// GetRootlessRuntimeDir returns the runtime directory when running as non root +func GetRootlessRuntimeDir() (string, error) { + if !rootless.IsRootless() { + return "", nil + } + return homedir.GetRuntimeDir() +} + +// GetRootlessConfigHomeDir returns the config home directory when running as non root +func GetRootlessConfigHomeDir() (string, error) { + return homedir.GetConfigHome() +} + +// GetRootlessPauseProcessPidPath returns the path to the file that holds the pid for +// the pause process. +func GetRootlessPauseProcessPidPath() (string, error) { + runtimeDir, err := homedir.GetRuntimeDir() + if err != nil { + return "", err + } + // Note this path must be kept in sync with pkg/rootless/rootless_linux.go + // We only want a single pause process per user, so we do not want to use + // the tmpdir which can be changed via --tmpdir. + return filepath.Join(runtimeDir, "libpod", "tmp", "pause.pid"), nil +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/utils_unsupported.go b/vendor/github.com/containers/podman/v5/pkg/util/utils_unsupported.go new file mode 100644 index 00000000..fb3329a9 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/utils_unsupported.go @@ -0,0 +1,10 @@ +//go:build darwin || windows || freebsd + +package util + +import "errors" + +// FindDeviceNodes is not implemented anywhere except Linux. +func FindDeviceNodes(_ bool) (map[string]string, error) { + return nil, errors.New("not supported on non-Linux OSes") +} diff --git a/vendor/github.com/containers/podman/v5/pkg/util/utils_windows.go b/vendor/github.com/containers/podman/v5/pkg/util/utils_windows.go new file mode 100644 index 00000000..be490827 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/pkg/util/utils_windows.go @@ -0,0 +1,45 @@ +//go:build windows + +package util + +import ( + "errors" + "fmt" + "path/filepath" + + "go.podman.io/storage/pkg/homedir" +) + +var errNotImplemented = errors.New("not yet implemented") + +// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 unified mode. +func IsCgroup2UnifiedMode() (bool, error) { + return false, fmt.Errorf("IsCgroup2Unified: %w", errNotImplemented) +} + +// GetContainerPidInformationDescriptors returns a string slice of all supported +// format descriptors of GetContainerPidInformation. +func GetContainerPidInformationDescriptors() ([]string, error) { + return nil, fmt.Errorf("GetContainerPidInformationDescriptors: %w", errNotImplemented) +} + +// GetRootlessPauseProcessPidPath returns the path to the file that holds the pid for +// the pause process +func GetRootlessPauseProcessPidPath() (string, error) { + return "", fmt.Errorf("GetRootlessPauseProcessPidPath: %w", errNotImplemented) +} + +// GetRootlessRuntimeDir returns the runtime directory +func GetRootlessRuntimeDir() (string, error) { + data, err := homedir.GetDataHome() + if err != nil { + return "", err + } + runtimeDir := filepath.Join(data, "containers", "podman") + return runtimeDir, nil +} + +// GetRootlessConfigHomeDir returns the config home directory when running as non root +func GetRootlessConfigHomeDir() (string, error) { + return "", errors.New("this function is not implemented for windows") +} diff --git a/vendor/github.com/containers/podman/v5/utils/ports.go b/vendor/github.com/containers/podman/v5/utils/ports.go new file mode 100644 index 00000000..eea06043 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/utils/ports.go @@ -0,0 +1,25 @@ +package utils + +import ( + "fmt" + "net" + "strconv" +) + +// Find a random, open port on the host. +func GetRandomPort() (int, error) { + l, err := net.Listen("tcp", ":0") + if err != nil { + return 0, fmt.Errorf("unable to get free TCP port: %w", err) + } + defer l.Close() + _, randomPort, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + return 0, fmt.Errorf("unable to determine free port: %w", err) + } + rp, err := strconv.Atoi(randomPort) + if err != nil { + return 0, fmt.Errorf("unable to convert random port to int: %w", err) + } + return rp, nil +} diff --git a/vendor/github.com/containers/podman/v5/utils/utils.go b/vendor/github.com/containers/podman/v5/utils/utils.go new file mode 100644 index 00000000..527feef8 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/utils/utils.go @@ -0,0 +1,132 @@ +package utils + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/sirupsen/logrus" + "github.com/vbauerster/mpb/v8" + "github.com/vbauerster/mpb/v8/decor" + "go.podman.io/storage/pkg/archive" + "go.podman.io/storage/pkg/chrootarchive" +) + +// ExecCmd executes a command with args and returns its output as a string along +// with an error, if any. +func ExecCmd(name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("`%v %v` failed: %v %v (%v)", name, strings.Join(args, " "), stderr.String(), stdout.String(), err) + } + + return stdout.String(), nil +} + +// ExecCmdWithStdStreams execute a command with the specified standard streams. +func ExecCmdWithStdStreams(stdin io.Reader, stdout, stderr io.Writer, env []string, name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdin = stdin + cmd.Stdout = stdout + cmd.Stderr = stderr + cmd.Env = env + + err := cmd.Run() + if err != nil { + return fmt.Errorf("`%v %v` failed: %v", name, strings.Join(args, " "), err) + } + + return nil +} + +// TarToFilesystem creates a tarball from source and writes to an os.file +// provided +func TarToFilesystem(source string, tarball *os.File) error { + tb, err := Tar(source) + if err != nil { + return err + } + defer tb.Close() + _, err = io.Copy(tarball, tb) + if err != nil { + return err + } + logrus.Debugf("wrote tarball file %s", tarball.Name()) + return nil +} + +// Tar creates a tarball from source and returns a readcloser of it +func Tar(source string) (io.ReadCloser, error) { + logrus.Debugf("creating tarball of %s", source) + return archive.Tar(source, archive.Uncompressed) +} + +// TarWithChroot creates a tarball from source and returns a readcloser of it +// while chrooted to the source. +func TarWithChroot(source string) (io.ReadCloser, error) { + logrus.Debugf("creating tarball of %s", source) + return chrootarchive.Tar(source, nil, source) +} + +// GuardedRemoveAll functions much like os.RemoveAll but +// will not delete certain catastrophic paths. +func GuardedRemoveAll(path string) error { + if path == "" || path == "/" { + return fmt.Errorf("refusing to recursively delete `%s`", path) + } + return os.RemoveAll(path) +} + +// RemoveFilesExcept removes all files in a directory except for the one specified +// by excludeFile and will not delete certain catastrophic paths. +func RemoveFilesExcept(path string, excludeFile string) error { + if path == "" || path == "/" { + return fmt.Errorf("refusing to recursively delete `%s`", path) + } + + files, err := os.ReadDir(path) + if err != nil { + return err + } + for _, file := range files { + if file.Name() != excludeFile { + if err := os.RemoveAll(filepath.Join(path, file.Name())); err != nil { + return err + } + } + } + return nil +} + +func ProgressBar(prefix string, size int64, onComplete string) (*mpb.Progress, *mpb.Bar) { + p := mpb.New( + mpb.WithWidth(80), // Do not go below 80, see bug #17718 + mpb.WithRefreshRate(180*time.Millisecond), + ) + + bar := p.AddBar(size, + mpb.BarFillerClearOnComplete(), + mpb.PrependDecorators( + decor.OnComplete(decor.Name(prefix), onComplete), + ), + mpb.AppendDecorators( + decor.OnComplete(decor.CountersKibiByte("%.1f / %.1f"), ""), + ), + ) + if size == 0 { + bar.SetTotal(0, true) + } + + return p, bar +} diff --git a/vendor/github.com/containers/podman/v5/version/rawversion/version.go b/vendor/github.com/containers/podman/v5/version/rawversion/version.go new file mode 100644 index 00000000..110afed0 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/version/rawversion/version.go @@ -0,0 +1,7 @@ +package rawversion + +// RawVersion is the raw version string. +// +// This indirection is needed to prevent semver packages from bloating +// Quadlet's binary size. +const RawVersion = "5.7.1" diff --git a/vendor/github.com/containers/podman/v5/version/version.go b/vendor/github.com/containers/podman/v5/version/version.go new file mode 100644 index 00000000..bb35b6c7 --- /dev/null +++ b/vendor/github.com/containers/podman/v5/version/version.go @@ -0,0 +1,46 @@ +package version + +import ( + "github.com/blang/semver/v4" + "github.com/containers/podman/v5/version/rawversion" +) + +type ( + // Tree determines which API endpoint tree for version + Tree int + // Level determines which API level, current or something from the past + Level int +) + +const ( + // Libpod supports Libpod endpoints + Libpod = Tree(iota) + // Compat supports Libpod endpoints + Compat + + // CurrentAPI announces what is the current API level + CurrentAPI = Level(iota) + // MinimalAPI announces what is the oldest API level supported + MinimalAPI +) + +// Version is the version of the build. +var Version = semver.MustParse(rawversion.RawVersion) + +// See https://docs.docker.com/engine/api/v1.40/ +// libpod compat handlers are expected to honor docker API versions + +// APIVersion provides the current and minimal API versions for compat and libpod endpoint trees +// Note: GET|HEAD /_ping is never versioned and provides the API-Version and Libpod-API-Version headers to allow +// +// clients to shop for the Version they wish to support +var APIVersion = map[Tree]map[Level]semver.Version{ + Libpod: { + CurrentAPI: Version, + MinimalAPI: semver.MustParse("4.0.0"), + }, + Compat: { + CurrentAPI: semver.MustParse("1.41.0"), + MinimalAPI: semver.MustParse("1.24.0"), + }, +} diff --git a/vendor/github.com/containers/psgo/.codespellrc b/vendor/github.com/containers/psgo/.codespellrc new file mode 100644 index 00000000..604bc21d --- /dev/null +++ b/vendor/github.com/containers/psgo/.codespellrc @@ -0,0 +1,2 @@ +[codespell] +skip = ./vendor,./.git diff --git a/vendor/github.com/containers/psgo/.gitignore b/vendor/github.com/containers/psgo/.gitignore new file mode 100644 index 00000000..fc8fbcad --- /dev/null +++ b/vendor/github.com/containers/psgo/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# everything in build dir +bin/* diff --git a/vendor/github.com/containers/psgo/.golangci.yml b/vendor/github.com/containers/psgo/.golangci.yml new file mode 100644 index 00000000..a098068f --- /dev/null +++ b/vendor/github.com/containers/psgo/.golangci.yml @@ -0,0 +1,6 @@ +# For documentation, see https://golangci-lint.run/usage/configuration/ + +linters: + enable: + - errorlint + - gofumpt diff --git a/vendor/github.com/containers/psgo/CODE-OF-CONDUCT.md b/vendor/github.com/containers/psgo/CODE-OF-CONDUCT.md new file mode 100644 index 00000000..1081553f --- /dev/null +++ b/vendor/github.com/containers/psgo/CODE-OF-CONDUCT.md @@ -0,0 +1,3 @@ +## The psgo Project Community Code of Conduct + +The psgo project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/master/CODE-OF-CONDUCT.md). diff --git a/vendor/github.com/containers/psgo/LICENSE b/vendor/github.com/containers/psgo/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/containers/psgo/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/psgo/Makefile b/vendor/github.com/containers/psgo/Makefile new file mode 100644 index 00000000..fb6126e7 --- /dev/null +++ b/vendor/github.com/containers/psgo/Makefile @@ -0,0 +1,55 @@ +SHELL= /bin/bash +GO ?= go +BUILD_DIR := ./bin +BIN_DIR := /usr/local/bin +NAME := psgo +BATS_TESTS := *.bats + +# Not all platforms support -buildmode=pie, plus it's incompatible with -race. +ifeq ($(shell $(GO) env GOOS),linux) + ifeq (,$(filter $(shell $(GO) env GOARCH),mips mipsle mips64 mips64le ppc64 riscv64)) + ifeq (,$(findstring -race,$(EXTRA_BUILD_FLAGS))) + GO_BUILDMODE := "-buildmode=pie" + endif + endif +endif +GO_BUILD := $(GO) build $(GO_BUILDMODE) + +all: validate build + +.PHONY: build +build: + $(GO_BUILD) $(EXTRA_BUILD_FLAGS) -o $(BUILD_DIR)/$(NAME) ./sample + +.PHONY: clean +clean: + rm -rf $(BUILD_DIR) + +.PHONY: vendor +vendor: + go mod tidy + go mod vendor + go mod verify + +.PHONY: validate +validate: + golangci-lint run + +.PHONY: test +test: test-unit test-integration + +.PHONY: test-integration +test-integration: + bats test/$(BATS_TESTS) + +.PHONY: test-unit +test-unit: + $(GO) test -v $(EXTRA_TEST_FLAGS) ./... + +.PHONY: install +install: + sudo install -D -m755 $(BUILD_DIR)/$(NAME) $(BIN_DIR) + +.PHONY: uninstall +uninstall: + sudo rm $(BIN_DIR)/$(NAME) diff --git a/vendor/github.com/containers/psgo/README.md b/vendor/github.com/containers/psgo/README.md new file mode 100644 index 00000000..aa26c170 --- /dev/null +++ b/vendor/github.com/containers/psgo/README.md @@ -0,0 +1,106 @@ +[![GoDoc](https://godoc.org/github.com/containers/psgo?status.svg)](https://godoc.org/github.com/containers/psgo) + +# psgo +A ps(1) AIX-format compatible golang library extended with various descriptors useful for displaying container-related data. + +The idea behind the library is to provide an easy to use way of extracting process-related data, just as ps(1) does. The problem when using ps(1) is that the ps format strings split columns with whitespaces, making the output nearly impossible to parse. It also adds some jitter as we have to fork and execute ps either in the container or filter the output afterwards, further limiting applicability. + +This library aims to make things a bit more comfortable, especially for container runtimes, as the API allows to join the mount namespace of a given process and will parse `/proc` and `/dev/` from there. The API consists of the following functions: + + - `psgo.ProcessInfo(descriptors []string) ([][]string, error)` + - ProcessInfo returns the process information of all processes in the current mount namespace. The input descriptors must be a slice of supported AIX format descriptors in the normal form or in the code form, if supported. If the input descriptor slice is empty, the `psgo.DefaultDescriptors` are used. The return value contains the string slice of process data, one per process. + + - `psgo.ProcessInfoByPids(pids []string, descriptors []string) ([][]string, error)` + - ProcessInfoByPids is similar to `psgo.ProcessInfo`, but limits the return value to a list of specified pids. The pids input must be a slice of PIDs for which process information should be returned. If the input descriptor slice is empty, only the format descriptor headers are returned. + + - `psgo.JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, error)` + - JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins the mount namespace of the specified pid before extracting data from /proc. This way, we can extract the `/proc` data from a container without executing any command inside the container. + + - `psgo.JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][]string, error)` + - JoinNamespaceAndProcessInfoByPids is similar to `psgo.JoinNamespaceAndProcessInfo` but takes a slice of pids as an argument. To avoid duplicate entries (e.g., when two or more containers share the same PID namespace), a given PID namespace will be joined only once. + + - `psgo.ListDescriptors() []string` + - ListDescriptors returns a sorted string slice of all supported AIX format descriptors in the normal form (e.g., "args,comm,user"). It can be useful in the context of bash-completion, help messages, etc. + +### Listing processes +We can use the [psgo](https://github.com/containers/psgo/blob/master/sample/sample.go) sample tool from this project to test the core components of this library. First, let's build `psgo` via `make build`. The binary is now located under `./bin/psgo`. By default `psgo` displays data about all running processes in the current mount namespace, similar to the output of `ps -ef`. + +``` +$ ./bin/psgo | head -n5 +USER PID PPID %CPU ELAPSED TTY TIME COMMAND +root 1 0 0.064 6h3m27.677997443s ? 13.98s systemd +root 2 0 0.000 6h3m27.678380128s ? 20ms [kthreadd] +root 4 2 0.000 6h3m27.678701852s ? 0s [kworker/0:0H] +root 6 2 0.000 6h3m27.678999508s ? 0s [mm_percpu_wq] +``` + +### Listing processes +You can use the `--pids` flag to restrict `psgo` output to a subset of processes. This option accepts a list of comma separate process IDs and will return exactly the same kind of information per process as the default output. + +``` +$ ./bin/psgo --pids 1,$(pgrep bash | tr "\n" ",") +USER PID PPID %CPU ELAPSED TTY TIME COMMAND +root 1 0 0.009 128h52m44.193475932s ? 40s systemd +root 20830 20827 0.000 105h2m44.19579679s pts/5 0s bash +root 25843 25840 0.000 102h56m4.196072027s pts/6 0s bash +``` + +### Listing processes within a container +Let's have a look at how we can use this library in the context of containers. As a simple show case, we'll start a Docker container, extract the process ID via `docker-inspect` and run the `psgo` binary to extract the data of running processes within that container. + +```shell +$ docker run -d alpine sleep 100 +473c9a05d4223b88ef7f5a9ac11e3d21e9914e012338425cc1cef853fc6c32a2 + +$ docker inspect --format '{{.State.Pid}}' 473c9 +5572 + +$ sudo ./bin/psgo -pids 5572 -join +USER PID PPID %CPU ELAPSED TTY TIME COMMAND +root 1 0 0.000 17.249905587s ? 0s sleep +``` + +### Format descriptors +The ps library is compatible with all AIX format descriptors of the ps command-line utility (see `man 1 ps` for details) but it also supports some additional descriptors that can be useful when seeking specific process-related information. + +- **capamb** + - Set of ambient capabilities. See capabilities(7) for more information. +- **capbnd** + - Set of bounding capabilities. See capabilities(7) for more information. +- **capeff** + - Set of effective capabilities. See capabilities(7) for more information. +- **capinh** + - Set of inheritable capabilities. See capabilities(7) for more information. +- **capprm** + - Set of permitted capabilities. See capabilities(7) for more information. +- **groups** + - Supplmentary groups inside the container. +- **hgroup** + - The corresponding effective group of a container process on the host. +- **hgroups** + - Supplmentary groups on the host. +- **hpid** + - The corresponding host PID of a container process. +- **huser** + - The corresponding effective user of a container process on the host. +- **huid** + - The corresponding host UID of a container process. +- **label** + - Current security attributes of the process. +- **seccomp** + - Seccomp mode of the process (i.e., disabled, strict or filter). See seccomp(2) for more information. +- **state** + - Process state codes (e.g, **R** for *running*, **S** for *sleeping*). See proc(5) for more information. +- **stime** + - Process start time (e.g, "2019-12-09 10:50:36 +0100 CET). + +We can try out different format descriptors with the psgo binary: + +```shell +$ ./bin/psgo -format "pid, user, group, seccomp" | head -n5 +PID USER GROUP SECCOMP +1 root root disabled +2 root root disabled +4 root root disabled +6 root root disabled +``` diff --git a/vendor/github.com/containers/psgo/SECURITY.md b/vendor/github.com/containers/psgo/SECURITY.md new file mode 100644 index 00000000..5d5ba254 --- /dev/null +++ b/vendor/github.com/containers/psgo/SECURITY.md @@ -0,0 +1,3 @@ +## Security and Disclosure Information Policy for the psgo Project + +The psgo Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/master/SECURITY.md) for the Containers Projects. diff --git a/vendor/github.com/containers/psgo/internal/capabilities/capabilities.go b/vendor/github.com/containers/psgo/internal/capabilities/capabilities.go new file mode 100644 index 00000000..9545ed57 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/capabilities/capabilities.go @@ -0,0 +1,92 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package capabilities provides a mapping from common kernel bit masks to the +// alphanumerical representation of kernel capabilities. See capabilities(7) +// for additional information. +package capabilities + +var ( + // capabilities are a mapping from a numerical value to the textual + // representation of a given capability. A map allows to easily check + // if a given value is included or not. + // + // NOTE: this map must be maintained and kept in sync with the + // ./include/uapi/linux/capability.h kernel header. + capabilities = map[uint]string{ + 0: "CHOWN", + 1: "DAC_OVERRIDE", + 2: "DAC_READ_SEARCH", + 3: "FOWNER", + 4: "FSETID", + 5: "KILL", + 6: "SETGID", + 7: "SETUID", + 8: "SETPCAP", + 9: "LINUX_IMMUTABLE", + 10: "NET_BIND_SERVICE", + 11: "NET_BROADCAST", + 12: "NET_ADMIN", + 13: "NET_RAW", + 14: "IPC_LOCK", + 15: "IPC_OWNER", + 16: "SYS_MODULE", + 17: "SYS_RAWIO", + 18: "SYS_CHROOT", + 19: "SYS_PTRACE", + 20: "SYS_PACCT", + 21: "SYS_ADMIN", + 22: "SYS_BOOT", + 23: "SYS_NICE", + 24: "SYS_RESOURCE", + 25: "SYS_TIME", + 26: "SYS_TTY_CONFIG", + 27: "MKNOD", + 28: "LEASE", + 29: "AUDIT_WRITE", + 30: "AUDIT_CONTROL", + 31: "SETFCAP", + 32: "MAC_OVERRIDE", + 33: "MAC_ADMIN", + 34: "SYSLOG", + 35: "WAKE_ALARM", + 36: "BLOCK_SUSPEND", + 37: "AUDIT_READ", + 38: "PERFMON", + 39: "BPF", + 40: "CHECKPOINT_RESTORE", + } + + // FullCAPs represents the value of a bitmask with a full capability + // set. + FullCAPs = uint64(0x1FFFFFFFFFF) +) + +// TranslateMask iterates over mask and returns a slice of corresponding +// capabilities. If a bit is out of range of known capabilities, it is set as +// "unknown" to catch potential regressions when new capabilities are added to +// the kernel. +func TranslateMask(mask uint64) []string { + caps := []string{} + for i := uint(0); i < 64; i++ { + if (mask>>i)&0x1 == 1 { + c, known := capabilities[i] + if !known { + c = "unknown" + } + caps = append(caps, c) + } + } + return caps +} diff --git a/vendor/github.com/containers/psgo/internal/cgroups/cgroups.go b/vendor/github.com/containers/psgo/internal/cgroups/cgroups.go new file mode 100644 index 00000000..eecaf87c --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/cgroups/cgroups.go @@ -0,0 +1,44 @@ +// Copyright 2019 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cgroups + +import ( + "sync" + "syscall" +) + +const ( + CgroupRoot = "/sys/fs/cgroup" + cgroup2SuperMagic = 0x63677270 +) + +var ( + isUnifiedOnce sync.Once + isUnified bool + isUnifiedErr error +) + +// IsCgroup2UnifiedMode returns whether we are running in cgroup or cgroupv2 mode. +func IsCgroup2UnifiedMode() (bool, error) { + isUnifiedOnce.Do(func() { + var st syscall.Statfs_t + if err := syscall.Statfs(CgroupRoot, &st); err != nil { + isUnified, isUnifiedErr = false, err + } else { + isUnified, isUnifiedErr = st.Type == cgroup2SuperMagic, nil + } + }) + return isUnified, isUnifiedErr +} diff --git a/vendor/github.com/containers/psgo/internal/dev/tty.go b/vendor/github.com/containers/psgo/internal/dev/tty.go new file mode 100644 index 00000000..863767f7 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/dev/tty.go @@ -0,0 +1,125 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev + +import ( + "os" + "strings" + "syscall" +) + +// TTY represents a tty including its minor and major device number and the +// path to the tty. +type TTY struct { + // Minor device number. + Minor uint64 + // Major device number. + Major uint64 + // Path to the tty device. + Path string +} + +// FindTTY return the corresponding TTY to the ttyNr or nil of non could be +// found. +func FindTTY(ttyNr uint64, devices *[]TTY) (*TTY, error) { + // (man 5 proc) The minor device number is contained in the combination + // of bits 31 to 20 and 7 to 0; the major device number is in bits 15 + // to 8. + maj := (ttyNr >> 8) & 0xFF + min := (ttyNr & 0xFF) | ((ttyNr >> 20) & 0xFFF) + + if devices == nil { + devs, err := TTYs() + if err != nil { + return nil, err + } + devices = devs + } + + for _, t := range *devices { + if t.Minor == min && t.Major == maj { + return &t, nil + } + } + + return nil, nil +} + +// majDevNum returns the major device number of rdev (see stat_t.Rdev). +func majDevNum(rdev uint64) uint64 { + return (rdev >> 8) & 0xfff +} + +// minDevNum returns the minor device number of rdev (see stat_t.Rdev). +func minDevNum(rdev uint64) uint64 { + return (rdev & 0xff) | ((rdev >> 12) & 0xfff00) +} + +// TTYs parses /dev for tty and pts devices. +func TTYs() (*[]TTY, error) { + devDir, err := os.Open("/dev/") + if err != nil { + return nil, err + } + defer devDir.Close() + + devices := []string{} + devTTYs, err := devDir.Readdirnames(0) + if err != nil { + return nil, err + } + for _, d := range devTTYs { + if !strings.HasPrefix(d, "tty") { + continue + } + devices = append(devices, "/dev/"+d) + } + + devPTSDir, err := os.Open("/dev/pts/") + if err != nil { + return nil, err + } + defer devPTSDir.Close() + + devPTSs, err := devPTSDir.Readdirnames(0) + if err != nil { + return nil, err + } + for _, d := range devPTSs { + devices = append(devices, "/dev/pts/"+d) + } + + ttys := []TTY{} + for _, dev := range devices { + fi, err := os.Stat(dev) + if err != nil { + if os.IsNotExist(err) { + // catch race conditions + continue + } + return nil, err + } + s := fi.Sys().(*syscall.Stat_t) + t := TTY{ + // Rdev is type uint32 on mips arch so we have to cast to uint64 + Minor: minDevNum(uint64(s.Rdev)), + Major: majDevNum(uint64(s.Rdev)), + Path: dev, + } + ttys = append(ttys, t) + } + + return &ttys, nil +} diff --git a/vendor/github.com/containers/psgo/internal/host/host.go b/vendor/github.com/containers/psgo/internal/host/host.go new file mode 100644 index 00000000..3c708a2b --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/host/host.go @@ -0,0 +1,61 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package host extracts data from the host, such as the system's boot time or +// the tick rate of the system clock. +package host + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +// BootTime parses /proc/uptime returns the boot time in seconds since the +// Epoch, 1970-01-01 00:00:00 +0000 (UTC). +func BootTime() (int64, error) { + if bootTime != nil { + return *bootTime, nil + } + + f, err := os.Open("/proc/stat") + if err != nil { + return 0, err + } + + btimeStr := "" + scanner := bufio.NewScanner(f) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) < 2 { + continue + } + if fields[0] == "btime" { + btimeStr = fields[1] + } + } + + if len(btimeStr) == 0 { + return 0, fmt.Errorf("couldn't extract boot time from /proc/stat") + } + + btimeSec, err := strconv.ParseInt(btimeStr, 10, 64) + if err != nil { + return 0, fmt.Errorf("error parsing boot time from /proc/stat: %w", err) + } + bootTime = &btimeSec + return btimeSec, nil +} diff --git a/vendor/github.com/containers/psgo/internal/host/host_cgo.go b/vendor/github.com/containers/psgo/internal/host/host_cgo.go new file mode 100644 index 00000000..eac9fe5c --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/host/host_cgo.go @@ -0,0 +1,37 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package host extracts data from the host, such as the system's boot time or +// the tick rate of the system clock. +package host + +/* +#include +*/ +import "C" + +var ( + // cache host queries to redundant calculations + clockTicks *int64 + bootTime *int64 +) + +// ClockTicks returns sysconf(SC_CLK_TCK). +func ClockTicks() (int64, error) { + if clockTicks == nil { + ticks := int64(C.sysconf(C._SC_CLK_TCK)) + clockTicks = &ticks + } + return *clockTicks, nil +} diff --git a/vendor/github.com/containers/psgo/internal/host/host_nocgo.go b/vendor/github.com/containers/psgo/internal/host/host_nocgo.go new file mode 100644 index 00000000..89e85e00 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/host/host_nocgo.go @@ -0,0 +1,85 @@ +//go:build !cgo +// +build !cgo + +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package host extracts data from the host, such as the system's boot time or +// the tick rate of the system clock. +package host + +import ( + "encoding/binary" + "fmt" + "os" + "unsafe" +) + +var ( + // cache host queries to redundant calculations + clockTicks *int64 + bootTime *int64 +) + +func getNativeEndianness() binary.ByteOrder { + var i int32 = 0x00000001 + u := unsafe.Pointer(&i) + if *((*byte)(u)) == 0x01 { + return binary.LittleEndian + } + return binary.BigEndian +} + +const ( + atClktck = 17 +) + +func getFromAuxv(what uint, whatName string) (uint, error) { + dataLen := int(unsafe.Sizeof(int(0))) + p, err := os.ReadFile("/proc/self/auxv") + if err != nil { + return 0, err + } + native := getNativeEndianness() + for i := 0; i < len(p); { + var k, v uint + + switch dataLen { + case 4: + k = uint(native.Uint32(p[i : i+dataLen])) + v = uint(native.Uint32(p[i+dataLen : i+dataLen*2])) + case 8: + k = uint(native.Uint64(p[i : i+dataLen])) + v = uint(native.Uint64(p[i+dataLen : i+dataLen*2])) + } + i += dataLen * 2 + if k == what { + return v, nil + } + } + return 0, fmt.Errorf("cannot find %s in auxv", whatName) +} + +// ClockTicks returns sysconf(SC_CLK_TCK). +func ClockTicks() (int64, error) { + if clockTicks == nil { + ret, err := getFromAuxv(atClktck, "AT_CLKTCK") + if err != nil { + return -1, err + } + ticks := int64(ret) + clockTicks = &ticks + } + return *clockTicks, nil +} diff --git a/vendor/github.com/containers/psgo/internal/proc/attr.go b/vendor/github.com/containers/psgo/internal/proc/attr.go new file mode 100644 index 00000000..c94f71cd --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/attr.go @@ -0,0 +1,40 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "errors" + "fmt" + "os" + "strings" + + "golang.org/x/sys/unix" +) + +// ParseAttrCurrent returns the contents of /proc/$pid/attr/current of "?" if +// labeling is not supported on the host. +func ParseAttrCurrent(pid string) (string, error) { + data, err := os.ReadFile(fmt.Sprintf("/proc/%s/attr/current", pid)) + if err != nil { + _, err = os.Stat(fmt.Sprintf("/proc/%s", pid)) + if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.ESRCH) { + // PID doesn't exist + return "", err + } + // PID exists but labeling seems to be unsupported + return "?", nil + } + return strings.Trim(string(data), "\n"), nil +} diff --git a/vendor/github.com/containers/psgo/internal/proc/cmdline.go b/vendor/github.com/containers/psgo/internal/proc/cmdline.go new file mode 100644 index 00000000..0143589d --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/cmdline.go @@ -0,0 +1,36 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "bytes" + "fmt" + "os" +) + +// ParseCmdLine parses a /proc/$pid/cmdline file and returns a string slice. +func ParseCmdLine(pid string) ([]string, error) { + data, err := os.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid)) + if err != nil { + return nil, err + } + + cmdLine := []string{} + for _, rawCmd := range bytes.Split(data, []byte{0}) { + cmdLine = append(cmdLine, string(rawCmd)) + } + + return cmdLine, nil +} diff --git a/vendor/github.com/containers/psgo/internal/proc/ns.go b/vendor/github.com/containers/psgo/internal/proc/ns.go new file mode 100644 index 00000000..1983512f --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/ns.go @@ -0,0 +1,73 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "bufio" + "fmt" + "io" + "os" + + "go.podman.io/storage/pkg/idtools" +) + +// ParsePIDNamespace returns the content of /proc/$pid/ns/pid. +func ParsePIDNamespace(pid string) (string, error) { + pidNS, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/pid", pid)) + if err != nil { + return "", err + } + return pidNS, nil +} + +// ParseUserNamespace returns the content of /proc/$pid/ns/user. +func ParseUserNamespace(pid string) (string, error) { + userNS, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/user", pid)) + if err != nil { + return "", err + } + return userNS, nil +} + +// ReadMappings reads the user namespace mappings at the specified path +func ReadMappings(path string) ([]idtools.IDMap, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + var mappings []idtools.IDMap + + buf := bufio.NewReader(file) + for { + line, _, err := buf.ReadLine() + if err != nil { + if err == io.EOF { //nolint:errorlint // False positive, see https://github.com/polyfloyd/go-errorlint/pull/12 + return mappings, nil + } + return nil, fmt.Errorf("cannot read line from %s: %w", path, err) + } + if line == nil { + return mappings, nil + } + + var containerID, hostID, size int + if _, err := fmt.Sscanf(string(line), "%d %d %d", &containerID, &hostID, &size); err != nil { + return nil, fmt.Errorf("cannot parse %s: %w", string(line), err) + } + mappings = append(mappings, idtools.IDMap{ContainerID: containerID, HostID: hostID, Size: size}) + } +} diff --git a/vendor/github.com/containers/psgo/internal/proc/pids.go b/vendor/github.com/containers/psgo/internal/proc/pids.go new file mode 100644 index 00000000..2687396e --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/pids.go @@ -0,0 +1,164 @@ +// Copyright 2018-2019 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/containers/psgo/internal/cgroups" +) + +// GetPIDs extracts and returns all PIDs from /proc. +func GetPIDs() ([]string, error) { + procDir, err := os.Open("/proc/") + if err != nil { + return nil, err + } + defer procDir.Close() + + // extract string slice of all directories in procDir + pidDirs, err := procDir.Readdirnames(0) + if err != nil { + return nil, err + } + + pids := []string{} + for _, pidDir := range pidDirs { + _, err := strconv.Atoi(pidDir) + if err != nil { + // skip non-numerical entries (e.g., `/proc/softirqs`) + continue + } + pids = append(pids, pidDir) + } + + return pids, nil +} + +// GetPIDsFromCgroup returns a strings slice of all pids listed in pid's pids +// cgroup. It automatically detects if we're running in unified mode or not. +func GetPIDsFromCgroup(pid string) ([]string, error) { + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, err + } + if unified { + return getPIDsFromCgroupV2(pid) + } + return getPIDsFromCgroupV1(pid) +} + +// getPIDsFromCgroupV1 returns a strings slice of all pids listed in pid's pids +// cgroup. +func getPIDsFromCgroupV1(pid string) ([]string, error) { + // First, find the corresponding path to the PID cgroup. + pidPath := fmt.Sprintf("/proc/%s/cgroup", pid) + f, err := os.Open(pidPath) + if err != nil { + return nil, err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + cgroupPath := "" + for scanner.Scan() { + fields := strings.Split(scanner.Text(), ":") + if len(fields) != 3 { + continue + } + if fields[1] == "pids" { + cgroupPath = filepath.Join(cgroups.CgroupRoot, "pids", fields[2], "cgroup.procs") + break + } + } + + if cgroupPath == "" { + return nil, fmt.Errorf("couldn't find v1 pids group for PID %s", pid) + } + + // Second, extract the PIDs inside the cgroup. + f, err = os.Open(cgroupPath) + if err != nil { + if os.IsNotExist(err) { + // OCI runtimes might mount the container cgroup at the root, breaking what it showed + // in /proc/$PID/cgroup and the path. + // Check if the PID still exists to make sure the process is still alive. + if _, errStat := os.Stat(pidPath); errStat == nil { + cgroupPath = filepath.Join(cgroups.CgroupRoot, "pids", "cgroup.procs") + f, err = os.Open(cgroupPath) + } + } + if err != nil { + return nil, err + } + } + defer f.Close() + + pids := []string{} + scanner = bufio.NewScanner(f) + for scanner.Scan() { + pids = append(pids, scanner.Text()) + } + + return pids, nil +} + +// getPIDsFromCgroupV2 returns a strings slice of all pids listed in pid's pids +// cgroup. +func getPIDsFromCgroupV2(pid string) ([]string, error) { + // First, find the corresponding path to the PID cgroup. + f, err := os.Open(fmt.Sprintf("/proc/%s/cgroup", pid)) + if err != nil { + return nil, err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + cgroupSlice := "" + for scanner.Scan() { + fields := strings.Split(scanner.Text(), ":") + if len(fields) != 3 { + continue + } + if fields[1] == "" { + cgroupSlice = fields[2] + break + } + } + + if cgroupSlice == "" { + return nil, fmt.Errorf("couldn't find v2 pids group for PID %s", pid) + } + + // Second, extract the PIDs inside the cgroup. + f, err = os.Open(filepath.Join(cgroups.CgroupRoot, cgroupSlice, "cgroup.procs")) + if err != nil { + return nil, err + } + defer f.Close() + + pids := []string{} + scanner = bufio.NewScanner(f) + for scanner.Scan() { + pids = append(pids, scanner.Text()) + } + + return pids, nil +} diff --git a/vendor/github.com/containers/psgo/internal/proc/stat.go b/vendor/github.com/containers/psgo/internal/proc/stat.go new file mode 100644 index 00000000..57bc8d62 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/stat.go @@ -0,0 +1,170 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "errors" + "fmt" + "os" + "strings" +) + +// Stat is a direct translation of a `/proc/[pid]/stat` file as described in +// the proc(5) manpage. Please note that it is not a full translation as not +// all fields are in the scope of this library and higher indices are +// Kernel-version dependent. +type Stat struct { + // (1) The process ID + Pid string + // (2) The filename of the executable, in parentheses. This is visible + // whether or not the executable is swapped out. + Comm string + // (3) The process state (e.g., running, sleeping, zombie, dead). + // Refer to proc(5) for further details. + State string + // (4) The PID of the parent of this process. + Ppid string + // (5) The process group ID of the process. + Pgrp string + // (6) The session ID of the process. + Session string + // (7) The controlling terminal of the process. (The minor device + // number is contained in the combination of bits 31 to 20 and 7 to 0; + // the major device number is in bits 15 to 8.) + TtyNr string + // (8) The ID of the foreground process group of the controlling + // terminal of the process. + Tpgid string + // (9) The kernel flags word of the process. For bit meanings, see the + // PF_* defines in the Linux kernel source file + // include/linux/sched.h. Details depend on the kernel version. + Flags string + // (10) The number of minor faults the process has made which have not + // required loading a memory page from disk. + Minflt string + // (11) The number of minor faults that the process's waited-for + // children have made. + Cminflt string + // (12) The number of major faults the process has made which have + // required loading a memory page from disk. + Majflt string + // (13) The number of major faults that the process's waited-for + // children have made. + Cmajflt string + // (14) Amount of time that this process has been scheduled in user + // mode, measured in clock ticks (divide by + // sysconf(_SC_CLK_TCK)). This includes guest time, guest_time + // (time spent running a virtual CPU, see below), so that applications + // that are not aware of the guest time field do not lose that time + // from their calculations. + Utime string + // (15) Amount of time that this process has been scheduled in kernel + // mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). + Stime string + // (16) Amount of time that this process's waited-for children have + // been scheduled in user mode, measured in clock ticks (divide by + // sysconf(_SC_CLK_TCK)). (See also times(2).) This includes guest + // time, cguest_time (time spent running a virtual CPU, see below). + Cutime string + // (17) Amount of time that this process's waited-for children have + // been scheduled in kernel mode, measured in clock ticks (divide by + // sysconf(_SC_CLK_TCK)). + Cstime string + // (18) (Explanation for Linux 2.6+) For processes running a real-time + // scheduling policy (policy below; see sched_setscheduler(2)), this is + // the negated scheduling pri- ority, minus one; that is, a number + // in the range -2 to -100, corresponding to real-time priorities 1 to + // 99. For processes running under a non-real-time scheduling + // policy, this is the raw nice value (setpriority(2)) as represented + // in the kernel. The kernel stores nice values as numbers in the + // range 0 (high) to 39 (low), corresponding to the user-visible nice + // range of -20 to 19. + Priority string + // (19) The nice value (see setpriority(2)), a value in the range 19 + // (low priority) to -20 (high priority). + Nice string + // (20) Number of threads in this process (since Linux 2.6). Before + // kernel 2.6, this field was hard coded to 0 as a placeholder for an + // earlier removed field. + NumThreads string + // (21) The time in jiffies before the next SIGALRM is sent to the + // process due to an interval timer. Since kernel 2.6.17, this + // field is no longer maintained, and is hard coded as 0. + Itrealvalue string + // (22) The time the process started after system boot. In kernels + // before Linux 2.6, this value was expressed in jiffies. Since + // Linux 2.6, the value is expressed in clock ticks (divide by + // sysconf(_SC_CLK_TCK)). + Starttime string + // (23) Virtual memory size in bytes. + Vsize string +} + +// readStat is used for mocking in unit tests. +var readStat = func(path string) (string, error) { + rawData, err := os.ReadFile(path) + if err != nil { + return "", err + } + return string(rawData), nil +} + +// ParseStat parses the /proc/$pid/stat file and returns a Stat. +func ParseStat(pid string) (*Stat, error) { + data, err := readStat(fmt.Sprintf("/proc/%s/stat", pid)) + if err != nil { + return nil, err + } + + firstParen := strings.IndexByte(data, '(') + lastParen := strings.LastIndexByte(data, ')') + if firstParen == -1 || lastParen == -1 { + return nil, errors.New("invalid format in stat") + } + pidstr := data[0 : firstParen-1] + comm := data[firstParen+1 : lastParen] + rest := strings.Fields(data[lastParen+1:]) + fields := append([]string{pidstr, comm}, rest...) + + fieldAt := func(i int) string { + return fields[i-1] + } + + return &Stat{ + Pid: fieldAt(1), + Comm: fieldAt(2), + State: fieldAt(3), + Ppid: fieldAt(4), + Pgrp: fieldAt(5), + Session: fieldAt(6), + TtyNr: fieldAt(7), + Tpgid: fieldAt(8), + Flags: fieldAt(9), + Minflt: fieldAt(10), + Cminflt: fieldAt(11), + Majflt: fieldAt(12), + Cmajflt: fieldAt(13), + Utime: fieldAt(14), + Stime: fieldAt(15), + Cutime: fieldAt(16), + Cstime: fieldAt(17), + Priority: fieldAt(18), + Nice: fieldAt(19), + NumThreads: fieldAt(20), + Itrealvalue: fieldAt(21), + Starttime: fieldAt(22), + Vsize: fieldAt(23), + }, nil +} diff --git a/vendor/github.com/containers/psgo/internal/proc/status.go b/vendor/github.com/containers/psgo/internal/proc/status.go new file mode 100644 index 00000000..59e4ae94 --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/proc/status.go @@ -0,0 +1,403 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" + "sync" + + "go.podman.io/storage/pkg/idtools" +) + +// Status is a direct translation of a `/proc/[pid]/status`, which provides much +// of the information in /proc/[pid]/stat and /proc/[pid]/statm in a format +// that's easier for humans to parse. +type Status struct { + // Name: Command run by this process. + Name string + // Umask: Process umask, expressed in octal with a leading zero; see + // umask(2). (Since Linux 4.7.) + Umask string + // State: Current state of the process. One of "R (running)", "S + // (sleeping)", "D (disk sleep)", "T (stopped)", "T (tracing stop)", "Z + // (zombie)", or "X (dead)". + State string + // Tgid: Thread group ID (i.e., Process ID). + Tgid string + // Ngid: NUMA group ID (0 if none; since Linux 3.13). + Ngid string + // Pid: Thread ID (see gettid(2)). + Pid string + // PPid: PID of parent process. + PPid string + // TracerPid: PID of process tracing this process (0 if not being traced). + TracerPid string + // Uids: Real, effective, saved set, and filesystem. + Uids []string + // Gids: Real, effective, saved set, and filesystem. + Gids []string + // FDSize: Number of file descriptor slots currently allocated. + FdSize string + // Groups: Supplementary group list. + Groups []string + // NStgid : Thread group ID (i.e., PID) in each of the PID namespaces + // of which [pid] is a member. The leftmost entry shows the value + // with respect to the PID namespace of the reading process, followed + // by the value in successively nested inner namespaces. (Since Linux + // 4.1.) + NStgid string + // NSpid: Thread ID in each of the PID namespaces of which [pid] is a + // member. The fields are ordered as for NStgid. (Since Linux 4.1.) + NSpid []string + // NSpgid: Process group ID in each of the PID namespaces of which + // [pid] is a member. The fields are ordered as for NStgid. (Since + // Linux 4.1.) + NSpgid string + // NSsid: descendant namespace session ID hierarchy Session ID in + // each of the PID names- paces of which [pid] is a member. The fields + // are ordered as for NStgid. (Since Linux 4.1.) + NSsid string + // VMPeak: Peak virtual memory size. + VMPeak string + // VMSize: Virtual memory size. + VMSize string + // VMLck: Locked memory size (see mlock(3)). + VMLCK string + // VMPin: Pinned memory size (since Linux 3.2). These are pages + // that can't be moved because something needs to directly access + // physical memory. + VMPin string + // VMHWM: Peak resident set size ("high water mark"). + VMHWM string + // VMRSS: Resident set size. Note that the value here is the sum of + // RssAnon, RssFile, and RssShmem. + VMRSS string + // RssAnon: Size of resident anonymous memory. (since Linux 4.5). + RssAnon string + // RssFile: Size of resident file mappings. (since Linux 4.5). + RssFile string + // RssShmem: Size of resident shared memory (includes System V + // shared memory, mappings from tmpfs(5), and shared anonymous + // mappings). (since Linux 4.5). + RssShmem string + // VMData: Size of data segment. + VMData string + // VMStk: Size of stack segment. + VMStk string + // VMExe: Size of text segment. + VMExe string + // VMLib: Shared library code size. + VMLib string + // VMPTE: Page table entries size (since Linux 2.6.10). + VMPTE string + // VMPMD: Size of second-level page tables (since Linux 4.0). + VMPMD string + // VMSwap: Swapped-out virtual memory size by anonymous private pages; + // shmem swap usage is not included (since Linux 2.6.34). + VMSwap string + // HugetlbPages: Size of hugetlb memory portions. (since Linux 4.4). + HugetlbPages string + // Threads: Number of threads in process containing this thread. + Threads string + // SigQ: This field contains two slash-separated numbers that relate to + // queued signals for the real user ID of this process. The first of + // these is the number of currently queued signals for this real + // user ID, and the second is the resource limit on the number of + // queued signals for this process (see the description of + // RLIMIT_SIGPENDING in getr- limit(2)). + SigQ string + // SigPnd: Number of signals pending for thread and for (see pthreads(7)). + SigPnd string + // ShdPnd: Number of signals pending for process as a whole (see + // signal(7)). + ShdPnd string + // SigBlk: Mask indicating signals being blocked (see signal(7)). + SigBlk string + // SigIgn: Mask indicating signals being ignored (see signal(7)). + SigIgn string + // SigCgt: Mask indicating signals being blocked caught (see signal(7)). + SigCgt string + // CapInh: Mask of capabilities enabled in inheritable sets (see + // capabilities(7)). + CapInh string + // CapPrm: Mask of capabilities enabled in permitted sets (see + // capabilities(7)). + CapPrm string + // CapEff: Mask of capabilities enabled in effective sets (see + // capabilities(7)). + CapEff string + // CapBnd: Capability Bounding set (since Linux 2.6.26, see + // capabilities(7)). + CapBnd string + // CapAmb: Ambient capability set (since Linux 4.3, see capabilities(7)). + CapAmb string + // NoNewPrivs: Value of the no_new_privs bit (since Linux 4.10, see + // prctl(2)). + NoNewPrivs string + // Seccomp: Seccomp mode of the process (since Linux 3.8, see + // seccomp(2)). 0 means SEC- COMP_MODE_DISABLED; 1 means + // SECCOMP_MODE_STRICT; 2 means SECCOMP_MODE_FILTER. This field is + // provided only if the kernel was built with the CONFIG_SECCOMP kernel + // configu- ration option enabled. + Seccomp string + // SeccompFilters: Amount of filters attached to the process. + // (since Linux 5.9) + SeccompFilters string + // Cpus_allowed: Mask of CPUs on which this process may run + // (since Linux 2.6.24, see cpuset(7)). + CpusAllowed string + // Cpus_allowed_list: Same as previous, but in "list format" (since + // Linux 2.6.26, see cpuset(7)). + CpusAllowedList string + // Mems_allowed: Mask of memory nodes allowed to this process + // (since Linux 2.6.24, see cpuset(7)). + MemsAllowed string + // Mems_allowed_list: Same as previous, but in "list format" (since + // Linux 2.6.26, see cpuset(7)). + MemsAllowedList string + // voluntaryCtxtSwitches: Number of voluntary context switches + // (since Linux 2.6.23). + VoluntaryCtxtSwitches string + // nonvoluntaryCtxtSwitches: Number of involuntary context switches + // (since Linux 2.6.23). + NonvoluntaryCtxtSwitches string +} + +// readStatus returns the content of /proc/pid/status as a string slice. +func readStatus(pid string) ([]string, error) { + path := fmt.Sprintf("/proc/%s/status", pid) + f, err := os.Open(path) + if err != nil { + return nil, err + } + lines := []string{} + scanner := bufio.NewScanner(f) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + return lines, nil +} + +// mapField maps a single string-typed ID field given the set of mappings. If +// no mapping exists, the overflow uid/gid is used. +func mapStatusField(field *string, mapping []idtools.IDMap, overflow string) { + hostId, err := strconv.Atoi(*field) + if err != nil { + *field = overflow + return + } + contId, err := idtools.RawToContainer(hostId, mapping) + if err != nil { + *field = overflow + return + } + *field = strconv.Itoa(contId) +} + +var ( + overflowOnce sync.Once + overflowUid = "65534" + overflowGid = "65534" +) + +func overflowIds() (string, string) { + overflowOnce.Do(func() { + if uid, err := os.ReadFile("/proc/sys/kernel/overflowuid"); err == nil { + overflowUid = strings.TrimSpace(string(uid)) + } + if gid, err := os.ReadFile("/proc/sys/kernel/overflowgid"); err == nil { + overflowGid = strings.TrimSpace(string(gid)) + } + }) + return overflowUid, overflowGid +} + +// mapStatus takes a Status struct and remaps all of the relevant fields to +// match the user namespace of the target process. +func mapStatus(pid string, status *Status) (*Status, error) { + uidMap, err := ReadMappings(fmt.Sprintf("/proc/%s/uid_map", pid)) + if err != nil { + return nil, err + } + gidMap, err := ReadMappings(fmt.Sprintf("/proc/%s/gid_map", pid)) + if err != nil { + return nil, err + } + overflowUid, overflowGid := overflowIds() + for i := range status.Uids { + mapStatusField(&status.Uids[i], uidMap, overflowUid) + } + for i := range status.Gids { + mapStatusField(&status.Gids[i], gidMap, overflowGid) + } + for i := range status.Groups { + mapStatusField(&status.Groups[i], gidMap, overflowGid) + } + return status, nil +} + +// ParseStatus parses the /proc/$pid/status file and returns a *Status. +func ParseStatus(pid string, mapUserNS bool) (*Status, error) { + lines, err := readStatus(pid) + if err != nil { + return nil, err + } + status, err := parseStatus(pid, lines) + if err != nil { + return nil, err + } + if mapUserNS { + status, err = mapStatus(pid, status) + if err != nil { + return nil, err + } + } + return status, nil +} + +// parseStatus extracts data from lines and returns a *Status. +func parseStatus(pid string, lines []string) (*Status, error) { + s := Status{} + errUnexpectedInput := fmt.Errorf("unexpected input from /proc/%s/status", pid) + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + + switch fields[0] { + case "Name:": + s.Name = fields[1] + case "Umask:": + s.Umask = fields[1] + case "State:": + s.State = fields[1] + case "Tgid:": + s.Tgid = fields[1] + case "Ngid:": + s.Ngid = fields[1] + case "Pid:": + s.Pid = fields[1] + case "PPid:": + s.PPid = fields[1] + case "TracerPid:": + s.TracerPid = fields[1] + case "Uid:": + if len(fields) != 5 { + return nil, fmt.Errorf(line+": %w", errUnexpectedInput) + } + s.Uids = []string{fields[1], fields[2], fields[3], fields[4]} + case "Gid:": + if len(fields) != 5 { + return nil, fmt.Errorf(line+": %w", errUnexpectedInput) + } + s.Gids = []string{fields[1], fields[2], fields[3], fields[4]} + case "FDSize:": + s.FdSize = fields[1] + case "Groups:": + s.Groups = fields[1:] + case "NStgid:": + s.NStgid = fields[1] + case "NSpid:": + s.NSpid = fields[1:] + case "NSpgid:": + s.NSpgid = fields[1] + case "NSsid:": + s.NSsid = fields[1] + case "VmPeak:": + s.VMPeak = fields[1] + case "VmSize:": + s.VMSize = fields[1] + case "VmLck:": + s.VMLCK = fields[1] + case "VmPin:": + s.VMPin = fields[1] + case "VmHWM:": + s.VMHWM = fields[1] + case "VmRSS:": + s.VMRSS = fields[1] + case "RssAnon:": + s.RssAnon = fields[1] + case "RssFile:": + s.RssFile = fields[1] + case "RssShmem:": + s.RssShmem = fields[1] + case "VmData:": + s.VMData = fields[1] + case "VmStk:": + s.VMStk = fields[1] + case "VmExe:": + s.VMExe = fields[1] + case "VmLib:": + s.VMLib = fields[1] + case "VmPTE:": + s.VMPTE = fields[1] + case "VmPMD:": + s.VMPMD = fields[1] + case "VmSwap:": + s.VMSwap = fields[1] + case "HugetlbPages:": + s.HugetlbPages = fields[1] + case "Threads:": + s.Threads = fields[1] + case "SigQ:": + s.SigQ = fields[1] + case "SigPnd:": + s.SigPnd = fields[1] + case "ShdPnd:": + s.ShdPnd = fields[1] + case "SigBlk:": + s.SigBlk = fields[1] + case "SigIgn:": + s.SigIgn = fields[1] + case "SigCgt:": + s.SigCgt = fields[1] + case "CapInh:": + s.CapInh = fields[1] + case "CapPrm:": + s.CapPrm = fields[1] + case "CapEff:": + s.CapEff = fields[1] + case "CapBnd:": + s.CapBnd = fields[1] + case "CapAmb:": + s.CapAmb = fields[1] + case "NoNewPrivs:": + s.NoNewPrivs = fields[1] + case "Seccomp:": + s.Seccomp = fields[1] + case "Seccomp_filters:": + s.SeccompFilters = fields[1] + case "Cpus_allowed:": + s.CpusAllowed = fields[1] + case "Cpus_allowed_list:": + s.CpusAllowedList = fields[1] + case "Mems_allowed:": + s.MemsAllowed = fields[1] + case "Mems_allowed_list:": + s.MemsAllowedList = fields[1] + case "voluntary_ctxt_switches:": + s.VoluntaryCtxtSwitches = fields[1] + case "nonvoluntary_ctxt_switches:": + s.NonvoluntaryCtxtSwitches = fields[1] + } + } + + return &s, nil +} diff --git a/vendor/github.com/containers/psgo/internal/process/process.go b/vendor/github.com/containers/psgo/internal/process/process.go new file mode 100644 index 00000000..0516466e --- /dev/null +++ b/vendor/github.com/containers/psgo/internal/process/process.go @@ -0,0 +1,236 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package process + +import ( + "errors" + "fmt" + "os" + "strconv" + "time" + + "github.com/containers/psgo/internal/host" + "github.com/containers/psgo/internal/proc" + "github.com/moby/sys/user" + "golang.org/x/sys/unix" +) + +// Process includes process-related from the /proc FS. +type Process struct { + // PID is the process ID. + Pid string + // Stat contains data from /proc/$pid/stat. + Stat proc.Stat + // Status contains data from /proc/$pid/status. + Status proc.Status + // CmdLine contains data from /proc/$pid/cmdline. + CmdLine []string + // Label containers data from /proc/$pid/attr/current. + Label string + // PidNS contains data from /proc/$pid/ns/pid. + PidNS string + // Huser is the effective host user of a container process. + Huser string + // Hgroup is the effective host group of a container process. + Hgroup string +} + +// LookupGID returns the textual group ID, if it can be obtained, or the +// decimal representation otherwise. +func LookupGID(gid string) (string, error) { + gidNum, err := strconv.Atoi(gid) + if err != nil { + return "", fmt.Errorf("error parsing group ID: %w", err) + } + g, err := user.LookupGid(gidNum) + if err != nil { + return gid, nil + } + return g.Name, nil +} + +// LookupUID return the textual user ID, if it can be obtained, or the decimal +// representation otherwise. +func LookupUID(uid string) (string, error) { + uidNum, err := strconv.Atoi(uid) + if err != nil { + return "", fmt.Errorf("error parsing user ID: %w", err) + } + u, err := user.LookupUid(uidNum) + if err != nil { + return uid, nil + } + return u.Name, nil +} + +// New returns a new Process with the specified pid and parses the relevant +// data from /proc and /dev. +func New(pid string, joinUserNS bool) (*Process, error) { + p := Process{Pid: pid} + + if err := p.parseStat(); err != nil { + return nil, err + } + if err := p.parseStatus(joinUserNS); err != nil { + return nil, err + } + if err := p.parseCmdLine(); err != nil { + return nil, err + } + if err := p.parsePIDNamespace(); err != nil { + // Ignore permission errors as those occur for some pids when + // the caller has limited permissions. + if !os.IsPermission(err) { + return nil, err + } + } + if err := p.parseLabel(); err != nil { + return nil, err + } + + return &p, nil +} + +// FromPIDs creates a new Process for each pid. +func FromPIDs(pids []string, joinUserNS bool) ([]*Process, error) { + processes := []*Process{} + for _, pid := range pids { + p, err := New(pid, joinUserNS) + if err != nil { + if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.ESRCH) { + // proc parsing is racy + // Let's ignore "does not exist" errors + continue + } + return nil, err + } + processes = append(processes, p) + } + return processes, nil +} + +// parseStat parses /proc/$pid/stat. +func (p *Process) parseStat() error { + s, err := proc.ParseStat(p.Pid) + if err != nil { + return err + } + p.Stat = *s + return nil +} + +// parseStatus parses /proc/$pid/status. +func (p *Process) parseStatus(joinUserNS bool) error { + s, err := proc.ParseStatus(p.Pid, joinUserNS) + if err != nil { + return err + } + p.Status = *s + return nil +} + +// parseCmdLine parses /proc/$pid/cmdline. +func (p *Process) parseCmdLine() error { + s, err := proc.ParseCmdLine(p.Pid) + if err != nil { + return err + } + p.CmdLine = s + return nil +} + +// parsePIDNamespace sets the PID namespace. +func (p *Process) parsePIDNamespace() error { + pidNS, err := proc.ParsePIDNamespace(p.Pid) + if err != nil { + return err + } + p.PidNS = pidNS + return nil +} + +// parseLabel parses the security label. +func (p *Process) parseLabel() error { + label, err := proc.ParseAttrCurrent(p.Pid) + if err != nil { + return err + } + p.Label = label + return nil +} + +// SetHostData sets all host-related data fields. +func (p *Process) SetHostData() error { + var err error + + p.Huser, err = LookupUID(p.Status.Uids[1]) + if err != nil { + return err + } + + p.Hgroup, err = LookupGID(p.Status.Gids[1]) + if err != nil { + return err + } + + return nil +} + +// ElapsedTime returns the time.Duration since process p was created. +func (p *Process) ElapsedTime() (time.Duration, error) { + startTime, err := p.StartTime() + if err != nil { + return 0, err + } + return time.Since(startTime), nil +} + +// StarTime returns the time.Time when process p was started. +func (p *Process) StartTime() (time.Time, error) { + sinceBoot, err := strconv.ParseInt(p.Stat.Starttime, 10, 64) + if err != nil { + return time.Time{}, err + } + clockTicks, err := host.ClockTicks() + if err != nil { + return time.Time{}, err + } + bootTime, err := host.BootTime() + if err != nil { + return time.Time{}, err + } + + sinceBoot = sinceBoot / clockTicks + return time.Unix(sinceBoot+bootTime, 0), nil +} + +// CPUTime returns the cumulative CPU time of process p as a time.Duration. +func (p *Process) CPUTime() (time.Duration, error) { + user, err := strconv.ParseInt(p.Stat.Utime, 10, 64) + if err != nil { + return 0, err + } + system, err := strconv.ParseInt(p.Stat.Stime, 10, 64) + if err != nil { + return 0, err + } + clockTicks, err := host.ClockTicks() + if err != nil { + return 0, err + } + secs := (user + system) / clockTicks + cpu := time.Unix(secs, 0) + return cpu.Sub(time.Unix(0, 0)), nil +} diff --git a/vendor/github.com/containers/psgo/psgo.go b/vendor/github.com/containers/psgo/psgo.go new file mode 100644 index 00000000..696b9f74 --- /dev/null +++ b/vendor/github.com/containers/psgo/psgo.go @@ -0,0 +1,938 @@ +// Copyright 2018 psgo authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package psgo is a ps (1) AIX-format compatible golang library extended with +// various descriptors useful for displaying container-related data. +// +// The idea behind the library is to provide an easy to use way of extracting +// process-related data, just as ps (1) does. The problem when using ps (1) is +// that the ps format strings split columns with whitespaces, making the output +// nearly impossible to parse. It also adds some jitter as we have to fork and +// execute ps either in the container or filter the output afterwards, further +// limiting applicability. +// +// Please visit https://github.com/containers/psgo for further details about +// supported format descriptors and to see some usage examples. +package psgo + +import ( + "errors" + "fmt" + "os" + "runtime" + "sort" + "strconv" + "strings" + "sync" + + "github.com/containers/psgo/internal/capabilities" + "github.com/containers/psgo/internal/dev" + "github.com/containers/psgo/internal/proc" + "github.com/containers/psgo/internal/process" + "go.podman.io/storage/pkg/idtools" + "golang.org/x/sys/unix" +) + +// JoinNamespaceOpts specifies different options for joining the specified namespaces. +type JoinNamespaceOpts struct { + // UIDMap specifies a mapping for UIDs in the container. If specified + // huser will perform the reverse mapping. + UIDMap []idtools.IDMap + // GIDMap specifies a mapping for GIDs in the container. If specified + // hgroup will perform the reverse mapping. + GIDMap []idtools.IDMap + + // FillMappings specified whether UIDMap and GIDMap must be initialized + // with the current user namespace. + FillMappings bool +} + +type psContext struct { + // Processes in the container. + containersProcesses []*process.Process + // Processes on the host. Used to map those to the ones running in the container. + hostProcesses []*process.Process + // tty and pty devices. + ttys *[]dev.TTY + // Various options + opts *JoinNamespaceOpts +} + +// processFunc is used to map a given aixFormatDescriptor to a corresponding +// function extracting the desired data from a process. +type processFunc func(*process.Process, *psContext) (string, error) + +// aixFormatDescriptor as mentioned in the ps(1) manpage. A given descriptor +// can either be specified via its code (e.g., "%C") or its normal representation +// (e.g., "pcpu") and will be printed under its corresponding header (e.g, "%CPU"). +type aixFormatDescriptor struct { + // code descriptor in the short form (e.g., "%C"). + code string + // normal descriptor in the long form (e.g., "pcpu"). + normal string + // header of the descriptor (e.g., "%CPU"). + header string + // onHost controls if data of the corresponding host processes will be + // extracted as well. + onHost bool + // procFN points to the corresponding method to extract the desired data. + procFn processFunc +} + +// findID converts the specified id to the host mapping +func findID(idStr string, mapping []idtools.IDMap, lookupFunc func(uid string) (string, error), overflowFile string) (string, error) { + if len(mapping) == 0 { + return idStr, nil + } + + id, err := strconv.ParseInt(idStr, 10, 0) + if err != nil { + return "", fmt.Errorf("cannot parse ID: %w", err) + } + for _, m := range mapping { + if int(id) >= m.ContainerID && int(id) < m.ContainerID+m.Size { + user := fmt.Sprintf("%d", m.HostID+(int(id)-m.ContainerID)) + + return lookupFunc(user) + } + } + + // User not found, read the overflow + overflow, err := os.ReadFile(overflowFile) + if err != nil { + return "", err + } + return string(overflow), nil +} + +// translateDescriptors parses the descriptors and returns a correspodning slice of +// aixFormatDescriptors. Descriptors can be specified in the normal and in the +// code form (if supported). If the descriptors slice is empty, the +// `DefaultDescriptors` is used. +func translateDescriptors(descriptors []string) ([]aixFormatDescriptor, error) { + if len(descriptors) == 0 { + descriptors = DefaultDescriptors + } + + formatDescriptors := []aixFormatDescriptor{} + for _, d := range descriptors { + d = strings.TrimSpace(d) + found := false + for _, aix := range aixFormatDescriptors { + if d == aix.code || d == aix.normal { + formatDescriptors = append(formatDescriptors, aix) + found = true + } + } + if !found { + return nil, fmt.Errorf("'%s': %w", d, ErrUnknownDescriptor) + } + } + + return formatDescriptors, nil +} + +var ( + // DefaultDescriptors is the `ps -ef` compatible default format. + DefaultDescriptors = []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "args"} + + // ErrUnknownDescriptor is returned when an unknown descriptor is parsed. + ErrUnknownDescriptor = errors.New("unknown descriptor") + + aixFormatDescriptors = []aixFormatDescriptor{ + { + code: "%C", + normal: "pcpu", + header: "%CPU", + procFn: processPCPU, + }, + { + code: "%G", + normal: "group", + header: "GROUP", + procFn: processGROUP, + }, + { + normal: "groups", + header: "GROUPS", + procFn: processGROUPS, + }, + { + code: "%P", + normal: "ppid", + header: "PPID", + procFn: processPPID, + }, + { + code: "%U", + normal: "user", + header: "USER", + procFn: processUSER, + }, + { + normal: "uid", + header: "UID", + procFn: processUID, + }, + { + code: "%a", + normal: "args", + header: "COMMAND", + procFn: processARGS, + }, + { + code: "%c", + normal: "comm", + header: "COMMAND", + procFn: processCOMM, + }, + { + code: "%g", + normal: "rgroup", + header: "RGROUP", + procFn: processRGROUP, + }, + { + code: "%n", + normal: "nice", + header: "NI", + procFn: processNICE, + }, + { + code: "%p", + normal: "pid", + header: "PID", + procFn: processPID, + }, + { + code: "%r", + normal: "pgid", + header: "PGID", + procFn: processPGID, + }, + { + code: "%t", + normal: "etime", + header: "ELAPSED", + procFn: processETIME, + }, + { + code: "%u", + normal: "ruser", + header: "RUSER", + procFn: processRUSER, + }, + { + code: "%x", + normal: "time", + header: "TIME", + procFn: processTIME, + }, + { + code: "%y", + normal: "tty", + header: "TTY", + procFn: processTTY, + }, + { + code: "%z", + normal: "vsz", + header: "VSZ", + procFn: processVSZ, + }, + { + normal: "capamb", + header: "AMBIENT CAPS", + procFn: processCAPAMB, + }, + { + normal: "capinh", + header: "INHERITED CAPS", + procFn: processCAPINH, + }, + { + normal: "capprm", + header: "PERMITTED CAPS", + procFn: processCAPPRM, + }, + { + normal: "capeff", + header: "EFFECTIVE CAPS", + procFn: processCAPEFF, + }, + { + normal: "capbnd", + header: "BOUNDING CAPS", + procFn: processCAPBND, + }, + { + normal: "seccomp", + header: "SECCOMP", + procFn: processSECCOMP, + }, + { + normal: "label", + header: "LABEL", + procFn: processLABEL, + }, + { + normal: "hpid", + header: "HPID", + onHost: true, + procFn: processHPID, + }, + { + normal: "huser", + header: "HUSER", + onHost: true, + procFn: processHUSER, + }, + { + normal: "huid", + header: "HUID", + onHost: true, + procFn: processHUID, + }, + { + normal: "hgroup", + header: "HGROUP", + onHost: true, + procFn: processHGROUP, + }, + { + normal: "hgroups", + header: "HGROUPS", + onHost: true, + procFn: processHGROUPS, + }, + { + normal: "rss", + header: "RSS", + procFn: processRSS, + }, + { + normal: "state", + header: "STATE", + procFn: processState, + }, + { + normal: "stime", + header: "STIME", + procFn: processStartTime, + }, + } +) + +// ListDescriptors returns a string slice of all supported AIX format +// descriptors in the normal form. +func ListDescriptors() (list []string) { + for _, d := range aixFormatDescriptors { + list = append(list, d.normal) + } + sort.Strings(list) + return +} + +// JoinNamespaceAndProcessInfo has the same semantics as ProcessInfo but joins +// the mount namespace of the specified pid before extracting data from `/proc`. +func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, error) { + return JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, &JoinNamespaceOpts{}) +} + +func contextFromOptions(options *JoinNamespaceOpts) (*psContext, error) { + ctx := new(psContext) + ctx.opts = options + if ctx.opts != nil && ctx.opts.FillMappings { + uidMappings, err := proc.ReadMappings("/proc/self/uid_map") + if err != nil { + return nil, err + } + + gidMappings, err := proc.ReadMappings("/proc/self/gid_map") + if err != nil { + return nil, err + } + ctx.opts.UIDMap = uidMappings + ctx.opts.GIDMap = gidMappings + + ctx.opts.FillMappings = false + } + return ctx, nil +} + +// JoinNamespaceAndProcessInfoWithOptions has the same semantics as ProcessInfo but joins +// the mount namespace of the specified pid before extracting data from `/proc`. +func JoinNamespaceAndProcessInfoWithOptions(pid string, descriptors []string, options *JoinNamespaceOpts) ([][]string, error) { + var ( + data [][]string + dataErr error + wg sync.WaitGroup + ) + + aixDescriptors, err := translateDescriptors(descriptors) + if err != nil { + return nil, err + } + + ctx, err := contextFromOptions(options) + if err != nil { + return nil, err + } + + // extract data from host processes only on-demand / when at least one + // of the specified descriptors requires host data + for _, d := range aixDescriptors { + if d.onHost { + ctx.hostProcesses, err = hostProcesses(pid) + if err != nil { + return nil, err + } + break + } + } + + wg.Add(1) + go func() { + defer wg.Done() + runtime.LockOSThread() + + // extract user namespaces prior to joining the mount namespace + currentUserNs, err := proc.ParseUserNamespace("self") + if err != nil { + dataErr = fmt.Errorf("error determining user namespace: %w", err) + return + } + + pidUserNs, err := proc.ParseUserNamespace(pid) + if err != nil { + dataErr = fmt.Errorf("error determining user namespace of PID %s: %w", pid, err) + } + + // join the mount namespace of pid + fd, err := os.Open(fmt.Sprintf("/proc/%s/ns/mnt", pid)) + if err != nil { + dataErr = err + return + } + defer fd.Close() + + // create a new mountns on the current thread + if err = unix.Unshare(unix.CLONE_NEWNS); err != nil { + dataErr = err + return + } + if err := unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS); err != nil { + dataErr = err + return + } + + // extract all pids mentioned in pid's mount namespace + pids, err := proc.GetPIDs() + if err != nil { + dataErr = err + return + } + + // join the user NS if the pid's user NS is different + // to the caller's user NS. + joinUserNS := currentUserNs != pidUserNs + + ctx.containersProcesses, err = process.FromPIDs(pids, joinUserNS) + if err != nil { + dataErr = err + return + } + + data, dataErr = processDescriptors(aixDescriptors, ctx) + }() + wg.Wait() + + return data, dataErr +} + +// JoinNamespaceAndProcessInfoByPidsWithOptions has similar semantics to +// JoinNamespaceAndProcessInfo and avoids duplicate entries by joining a giving +// PID namespace only once. +func JoinNamespaceAndProcessInfoByPidsWithOptions(pids []string, descriptors []string, options *JoinNamespaceOpts) ([][]string, error) { + // Extracting data from processes that share the same PID namespace + // would yield duplicate results. Avoid that by extracting data only + // from the first process in `pids` from a given PID namespace. + // `nsMap` is used for quick lookups if a given PID namespace is + // already covered, `pidList` is used to preserve the order which is + // not guaranteed by nondeterministic maps in golang. + nsMap := make(map[string]bool) + pidList := []string{} + for _, pid := range pids { + ns, err := proc.ParsePIDNamespace(pid) + if err != nil { + if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.ESRCH) { + // catch race conditions + continue + } + return nil, fmt.Errorf("error extracting PID namespace: %w", err) + } + if _, exists := nsMap[ns]; !exists { + nsMap[ns] = true + pidList = append(pidList, pid) + } + } + + data := [][]string{} + for i, pid := range pidList { + pidData, err := JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, options) + if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.ESRCH) { + // catch race conditions + continue + } + if err != nil { + return nil, err + } + if i == 0 { + data = append(data, pidData[0]) + } + data = append(data, pidData[1:]...) + } + + return data, nil +} + +// JoinNamespaceAndProcessInfoByPids has similar semantics to +// JoinNamespaceAndProcessInfo and avoids duplicate entries by joining a giving +// PID namespace only once. +func JoinNamespaceAndProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) { + return JoinNamespaceAndProcessInfoByPidsWithOptions(pids, descriptors, &JoinNamespaceOpts{}) +} + +// ProcessInfo returns the process information of all processes in the current +// mount namespace. The input format must be a comma-separated list of +// supported AIX format descriptors. If the input string is empty, the +// `DefaultDescriptors` is used. +// The return value is an array of tab-separated strings, to easily use the +// output for column-based formatting (e.g., with the `text/tabwriter` package). +func ProcessInfo(descriptors []string) ([][]string, error) { + pids, err := proc.GetPIDs() + if err != nil { + return nil, err + } + + return ProcessInfoByPids(pids, descriptors) +} + +// ProcessInfoByPids is like ProcessInfo, but the process information returned +// is limited to a list of user specified PIDs. +func ProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) { + aixDescriptors, err := translateDescriptors(descriptors) + if err != nil { + return nil, err + } + + ctx, err := contextFromOptions(nil) + if err != nil { + return nil, err + } + ctx.containersProcesses, err = process.FromPIDs(pids, false) + if err != nil { + return nil, err + } + + return processDescriptors(aixDescriptors, ctx) +} + +// hostProcesses returns all processes running in the current namespace. +func hostProcesses(pid string) ([]*process.Process, error) { + // get processes + pids, err := proc.GetPIDsFromCgroup(pid) + if err != nil { + return nil, err + } + + processes, err := process.FromPIDs(pids, false) + if err != nil { + return nil, err + } + + // set the additional host data + for _, p := range processes { + if err := p.SetHostData(); err != nil { + return nil, err + } + } + + return processes, nil +} + +// processDescriptors calls each `procFn` of all formatDescriptors on each +// process and returns an array of tab-separated strings. +func processDescriptors(formatDescriptors []aixFormatDescriptor, ctx *psContext) ([][]string, error) { + data := [][]string{} + // create header + header := []string{} + for _, desc := range formatDescriptors { + header = append(header, desc.header) + } + data = append(data, header) + + // dispatch all descriptor functions on each process + for _, proc := range ctx.containersProcesses { + pData := []string{} + for _, desc := range formatDescriptors { + dataStr, err := desc.procFn(proc, ctx) + if err != nil { + return nil, err + } + pData = append(pData, dataStr) + } + data = append(data, pData) + } + + return data, nil +} + +// findHostProcess returns the corresponding process from `hostProcesses` or +// nil if non is found. +func findHostProcess(p *process.Process, ctx *psContext) *process.Process { + for _, hp := range ctx.hostProcesses { + // We expect the host process to be in another namespace, so + // /proc/$pid/status.NSpid must have at least two entries. + if len(hp.Status.NSpid) < 2 { + continue + } + // The process' PID must match the one in the NS of the host + // process and both must share the same pid NS. + if p.Pid == hp.Status.NSpid[1] && p.PidNS == hp.PidNS { + return hp + } + } + return nil +} + +// processGROUP returns the effective group ID of the process. This will be +// the textual group ID, if it can be obtained, or a decimal representation +// otherwise. +func processGROUP(p *process.Process, ctx *psContext) (string, error) { + return process.LookupGID(p.Status.Gids[1]) +} + +// processGROUPS returns the supplementary groups of the process separated by +// comma. This will be the textual group ID, if it can be obtained, or a +// decimal representation otherwise. +func processGROUPS(p *process.Process, ctx *psContext) (string, error) { + var err error + groups := make([]string, len(p.Status.Groups)) + for i, g := range p.Status.Groups { + groups[i], err = process.LookupGID(g) + if err != nil { + return "", err + } + } + return strings.Join(groups, ","), nil +} + +// processRGROUP returns the real group ID of the process. This will be +// the textual group ID, if it can be obtained, or a decimal representation +// otherwise. +func processRGROUP(p *process.Process, ctx *psContext) (string, error) { + return process.LookupGID(p.Status.Gids[0]) +} + +// processPPID returns the parent process ID of process p. +func processPPID(p *process.Process, ctx *psContext) (string, error) { + return p.Status.PPid, nil +} + +// processUSER returns the effective user name of the process. This will be +// the textual user ID, if it can be obtained, or a decimal representation +// otherwise. +func processUSER(p *process.Process, ctx *psContext) (string, error) { + return process.LookupUID(p.Status.Uids[1]) +} + +// processUID returns the effective UID of the process as the decimal representation. +func processUID(p *process.Process, ctx *psContext) (string, error) { + return p.Status.Uids[1], nil +} + +// processRUSER returns the effective user name of the process. This will be +// the textual user ID, if it can be obtained, or a decimal representation +// otherwise. +func processRUSER(p *process.Process, ctx *psContext) (string, error) { + return process.LookupUID(p.Status.Uids[0]) +} + +// processName returns the name of process p in the format "[$name]". +func processName(p *process.Process, ctx *psContext) (string, error) { + return fmt.Sprintf("[%s]", p.Status.Name), nil +} + +// processARGS returns the command of p with all its arguments. +func processARGS(p *process.Process, ctx *psContext) (string, error) { + // ps (1) returns "[$name]" if command/args are empty + if p.CmdLine[0] == "" { + return processName(p, ctx) + } + return strings.Join(p.CmdLine, " "), nil +} + +// processCOMM returns the command name (i.e., executable name) of process p. +func processCOMM(p *process.Process, ctx *psContext) (string, error) { + return p.Stat.Comm, nil +} + +// processNICE returns the nice value of process p. +func processNICE(p *process.Process, ctx *psContext) (string, error) { + return p.Stat.Nice, nil +} + +// processPID returns the process ID of process p. +func processPID(p *process.Process, ctx *psContext) (string, error) { + return p.Pid, nil +} + +// processPGID returns the process group ID of process p. +func processPGID(p *process.Process, ctx *psContext) (string, error) { + return p.Stat.Pgrp, nil +} + +// processPCPU returns how many percent of the CPU time process p uses as +// a three digit float as string. +func processPCPU(p *process.Process, ctx *psContext) (string, error) { + elapsed, err := p.ElapsedTime() + if err != nil { + return "", err + } + cpu, err := p.CPUTime() + if err != nil { + return "", err + } + pcpu := 100 * cpu.Seconds() / elapsed.Seconds() + + return strconv.FormatFloat(pcpu, 'f', 3, 64), nil +} + +// processETIME returns the elapsed time since the process was started. +func processETIME(p *process.Process, ctx *psContext) (string, error) { + elapsed, err := p.ElapsedTime() + if err != nil { + return "", nil + } + return fmt.Sprintf("%v", elapsed), nil +} + +// processTIME returns the cumulative CPU time of process p. +func processTIME(p *process.Process, ctx *psContext) (string, error) { + cpu, err := p.CPUTime() + if err != nil { + return "", err + } + return fmt.Sprintf("%v", cpu), nil +} + +// processStartTime returns the start time of process p. +func processStartTime(p *process.Process, ctx *psContext) (string, error) { + sTime, err := p.StartTime() + if err != nil { + return "", err + } + return fmt.Sprintf("%v", sTime), nil +} + +// processTTY returns the controlling tty (terminal) of process p. +func processTTY(p *process.Process, ctx *psContext) (string, error) { + ttyNr, err := strconv.ParseUint(p.Stat.TtyNr, 10, 64) + if err != nil { + return "", nil + } + + tty, err := dev.FindTTY(ttyNr, ctx.ttys) + if err != nil { + return "", nil + } + + ttyS := "?" + if tty != nil { + ttyS = strings.TrimPrefix(tty.Path, "/dev/") + } + return ttyS, nil +} + +// processVSZ returns the virtual memory size of process p in KiB (1024-byte +// units). +func processVSZ(p *process.Process, ctx *psContext) (string, error) { + vmsize, err := strconv.Atoi(p.Stat.Vsize) + if err != nil { + return "", err + } + return fmt.Sprintf("%d", vmsize/1024), nil +} + +// parseCAP parses cap (a string bit mask) and returns the associated set of +// capabilities. If all capabilities are set, "full" is returned. If no +// capability is enabled, "none" is returned. +func parseCAP(cap string) (string, error) { + mask, err := strconv.ParseUint(cap, 16, 64) + if err != nil { + return "", err + } + if mask == capabilities.FullCAPs { + return "full", nil + } + caps := capabilities.TranslateMask(mask) + if len(caps) == 0 { + return "none", nil + } + sort.Strings(caps) + return strings.Join(caps, ","), nil +} + +// processCAPAMB returns the set of ambient capabilities associated with +// process p. If all capabilities are set, "full" is returned. If no +// capability is enabled, "none" is returned. +func processCAPAMB(p *process.Process, ctx *psContext) (string, error) { + return parseCAP(p.Status.CapAmb) +} + +// processCAPINH returns the set of inheritable capabilities associated with +// process p. If all capabilities are set, "full" is returned. If no +// capability is enabled, "none" is returned. +func processCAPINH(p *process.Process, ctx *psContext) (string, error) { + return parseCAP(p.Status.CapInh) +} + +// processCAPPRM returns the set of permitted capabilities associated with +// process p. If all capabilities are set, "full" is returned. If no +// capability is enabled, "none" is returned. +func processCAPPRM(p *process.Process, ctx *psContext) (string, error) { + return parseCAP(p.Status.CapPrm) +} + +// processCAPEFF returns the set of effective capabilities associated with +// process p. If all capabilities are set, "full" is returned. If no +// capability is enabled, "none" is returned. +func processCAPEFF(p *process.Process, ctx *psContext) (string, error) { + return parseCAP(p.Status.CapEff) +} + +// processCAPBND returns the set of bounding capabilities associated with +// process p. If all capabilities are set, "full" is returned. If no +// capability is enabled, "none" is returned. +func processCAPBND(p *process.Process, ctx *psContext) (string, error) { + return parseCAP(p.Status.CapBnd) +} + +// processSECCOMP returns the seccomp mode of the process (i.e., disabled, +// strict or filter) or "?" if /proc/$pid/status.seccomp has a unknown value. +func processSECCOMP(p *process.Process, ctx *psContext) (string, error) { + switch p.Status.Seccomp { + case "0": + return "disabled", nil + case "1": + return "strict", nil + case "2": + return "filter", nil + default: + return "?", nil + } +} + +// processLABEL returns the process label of process p or "?" if the system +// doesn't support labeling. +func processLABEL(p *process.Process, ctx *psContext) (string, error) { + return p.Label, nil +} + +// processHPID returns the PID of the corresponding host process of the +// (container) or "?" if no corresponding process could be found. +func processHPID(p *process.Process, ctx *psContext) (string, error) { + if hp := findHostProcess(p, ctx); hp != nil { + return hp.Pid, nil + } + return "?", nil +} + +// processHUSER returns the effective user ID of the corresponding host process +// of the (container) or "?" if no corresponding process could be found. +func processHUSER(p *process.Process, ctx *psContext) (string, error) { + if hp := findHostProcess(p, ctx); hp != nil { + if ctx.opts != nil && len(ctx.opts.UIDMap) > 0 { + return findID(hp.Status.Uids[1], ctx.opts.UIDMap, process.LookupUID, "/proc/sys/fs/overflowuid") + } + return hp.Huser, nil + } + return "?", nil +} + +// processHUID returns the effective UID of the corresponding host process +// of the (container) as the decimal representation or "?" if no corresponding +// process could be found. +func processHUID(p *process.Process, ctx *psContext) (string, error) { + if hp := findHostProcess(p, ctx); hp != nil { + if ctx.opts != nil && len(ctx.opts.UIDMap) > 0 { + // Return uid without searching its textual representation. + lookupFunc := func(uid string) (string, error) { + return uid, nil + } + return findID(hp.Status.Uids[1], ctx.opts.UIDMap, lookupFunc, "/proc/sys/fs/overflowuid") + } + return hp.Status.Uids[1], nil + } + return "?", nil +} + +// processHGROUP returns the effective group ID of the corresponding host +// process of the (container) or "?" if no corresponding process could be +// found. +func processHGROUP(p *process.Process, ctx *psContext) (string, error) { + if hp := findHostProcess(p, ctx); hp != nil { + if ctx.opts != nil && len(ctx.opts.GIDMap) > 0 { + return findID(hp.Status.Gids[1], ctx.opts.GIDMap, process.LookupGID, "/proc/sys/fs/overflowgid") + } + return hp.Hgroup, nil + } + return "?", nil +} + +// processHGROUPS returns the supplementary groups of the corresponding host +// process of the (container) or "?" if no corresponding process could be +// found. +func processHGROUPS(p *process.Process, ctx *psContext) (string, error) { + if hp := findHostProcess(p, ctx); hp != nil { + groups := hp.Status.Groups + if ctx.opts != nil && len(ctx.opts.GIDMap) > 0 { + var err error + for i, g := range groups { + groups[i], err = findID(g, ctx.opts.GIDMap, process.LookupGID, "/proc/sys/fs/overflowgid") + if err != nil { + return "", err + } + } + } + return strings.Join(groups, ","), nil + } + return "?", nil +} + +// processRSS returns the resident set size of process p in KiB (1024-byte +// units). +func processRSS(p *process.Process, ctx *psContext) (string, error) { + if p.Status.VMRSS == "" { + // probably a kernel thread + return "0", nil + } + return p.Status.VMRSS, nil +} + +// processState returns the process state of process p. +func processState(p *process.Process, ctx *psContext) (string, error) { + return p.Status.State, nil +} diff --git a/vendor/github.com/coreos/go-systemd/LICENSE b/vendor/github.com/coreos/go-systemd/LICENSE new file mode 100644 index 00000000..37ec93a1 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/coreos/go-systemd/NOTICE b/vendor/github.com/coreos/go-systemd/NOTICE new file mode 100644 index 00000000..23a0ada2 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/NOTICE @@ -0,0 +1,5 @@ +CoreOS Project +Copyright 2018 CoreOS, Inc + +This product includes software developed at CoreOS, Inc. +(http://www.coreos.com/). diff --git a/vendor/github.com/coreos/go-systemd/activation/files.go b/vendor/github.com/coreos/go-systemd/activation/files.go new file mode 100644 index 00000000..29dd18de --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/activation/files.go @@ -0,0 +1,67 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package activation implements primitives for systemd socket activation. +package activation + +import ( + "os" + "strconv" + "strings" + "syscall" +) + +const ( + // listenFdsStart corresponds to `SD_LISTEN_FDS_START`. + listenFdsStart = 3 +) + +// Files returns a slice containing a `os.File` object for each +// file descriptor passed to this process via systemd fd-passing protocol. +// +// The order of the file descriptors is preserved in the returned slice. +// `unsetEnv` is typically set to `true` in order to avoid clashes in +// fd usage and to avoid leaking environment flags to child processes. +func Files(unsetEnv bool) []*os.File { + if unsetEnv { + defer os.Unsetenv("LISTEN_PID") + defer os.Unsetenv("LISTEN_FDS") + defer os.Unsetenv("LISTEN_FDNAMES") + } + + pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) + if err != nil || pid != os.Getpid() { + return nil + } + + nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) + if err != nil || nfds == 0 { + return nil + } + + names := strings.Split(os.Getenv("LISTEN_FDNAMES"), ":") + + files := make([]*os.File, 0, nfds) + for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { + syscall.CloseOnExec(fd) + name := "LISTEN_FD_" + strconv.Itoa(fd) + offset := fd - listenFdsStart + if offset < len(names) && len(names[offset]) > 0 { + name = names[offset] + } + files = append(files, os.NewFile(uintptr(fd), name)) + } + + return files +} diff --git a/vendor/github.com/coreos/go-systemd/activation/listeners.go b/vendor/github.com/coreos/go-systemd/activation/listeners.go new file mode 100644 index 00000000..3dbe2b08 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/activation/listeners.go @@ -0,0 +1,103 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package activation + +import ( + "crypto/tls" + "net" +) + +// Listeners returns a slice containing a net.Listener for each matching socket type +// passed to this process. +// +// The order of the file descriptors is preserved in the returned slice. +// Nil values are used to fill any gaps. For example if systemd were to return file descriptors +// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener} +func Listeners() ([]net.Listener, error) { + files := Files(true) + listeners := make([]net.Listener, len(files)) + + for i, f := range files { + if pc, err := net.FileListener(f); err == nil { + listeners[i] = pc + f.Close() + } + } + return listeners, nil +} + +// ListenersWithNames maps a listener name to a set of net.Listener instances. +func ListenersWithNames() (map[string][]net.Listener, error) { + files := Files(true) + listeners := map[string][]net.Listener{} + + for _, f := range files { + if pc, err := net.FileListener(f); err == nil { + current, ok := listeners[f.Name()] + if !ok { + listeners[f.Name()] = []net.Listener{pc} + } else { + listeners[f.Name()] = append(current, pc) + } + f.Close() + } + } + return listeners, nil +} + +// TLSListeners returns a slice containing a net.listener for each matching TCP socket type +// passed to this process. +// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig. +func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) { + listeners, err := Listeners() + + if listeners == nil || err != nil { + return nil, err + } + + if tlsConfig != nil { + for i, l := range listeners { + // Activate TLS only for TCP sockets + if l.Addr().Network() == "tcp" { + listeners[i] = tls.NewListener(l, tlsConfig) + } + } + } + + return listeners, err +} + +// TLSListenersWithNames maps a listener name to a net.Listener with +// the associated TLS configuration. +func TLSListenersWithNames(tlsConfig *tls.Config) (map[string][]net.Listener, error) { + listeners, err := ListenersWithNames() + + if listeners == nil || err != nil { + return nil, err + } + + if tlsConfig != nil { + for _, ll := range listeners { + // Activate TLS only for TCP sockets + for i, l := range ll { + if l.Addr().Network() == "tcp" { + ll[i] = tls.NewListener(l, tlsConfig) + } + } + } + } + + return listeners, err +} diff --git a/vendor/github.com/coreos/go-systemd/activation/packetconns.go b/vendor/github.com/coreos/go-systemd/activation/packetconns.go new file mode 100644 index 00000000..a9720678 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/activation/packetconns.go @@ -0,0 +1,38 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package activation + +import ( + "net" +) + +// PacketConns returns a slice containing a net.PacketConn for each matching socket type +// passed to this process. +// +// The order of the file descriptors is preserved in the returned slice. +// Nil values are used to fill any gaps. For example if systemd were to return file descriptors +// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn} +func PacketConns() ([]net.PacketConn, error) { + files := Files(true) + conns := make([]net.PacketConn, len(files)) + + for i, f := range files { + if pc, err := net.FilePacketConn(f); err == nil { + conns[i] = pc + f.Close() + } + } + return conns, nil +} diff --git a/vendor/github.com/coreos/go-systemd/v22/LICENSE b/vendor/github.com/coreos/go-systemd/v22/LICENSE new file mode 100644 index 00000000..37ec93a1 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/coreos/go-systemd/v22/NOTICE b/vendor/github.com/coreos/go-systemd/v22/NOTICE new file mode 100644 index 00000000..23a0ada2 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/NOTICE @@ -0,0 +1,5 @@ +CoreOS Project +Copyright 2018 CoreOS, Inc + +This product includes software developed at CoreOS, Inc. +(http://www.coreos.com/). diff --git a/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify.go b/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify.go new file mode 100644 index 00000000..ba4ae31f --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify.go @@ -0,0 +1,84 @@ +// Copyright 2014 Docker, Inc. +// Copyright 2015-2018 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Package daemon provides a Go implementation of the sd_notify protocol. +// It can be used to inform systemd of service start-up completion, watchdog +// events, and other status changes. +// +// https://www.freedesktop.org/software/systemd/man/sd_notify.html#Description +package daemon + +import ( + "net" + "os" +) + +const ( + // SdNotifyReady tells the service manager that service startup is finished + // or the service finished loading its configuration. + SdNotifyReady = "READY=1" + + // SdNotifyStopping tells the service manager that the service is beginning + // its shutdown. + SdNotifyStopping = "STOPPING=1" + + // SdNotifyReloading tells the service manager that this service is + // reloading its configuration. Note that you must call SdNotifyReady when + // it completed reloading. + SdNotifyReloading = "RELOADING=1" + + // SdNotifyWatchdog tells the service manager to update the watchdog + // timestamp for the service. + SdNotifyWatchdog = "WATCHDOG=1" +) + +// SdNotify sends a message to the init daemon. It is common to ignore the error. +// If `unsetEnvironment` is true, the environment variable `NOTIFY_SOCKET` +// will be unconditionally unset. +// +// It returns one of the following: +// (false, nil) - notification not supported (i.e. NOTIFY_SOCKET is unset) +// (false, err) - notification supported, but failure happened (e.g. error connecting to NOTIFY_SOCKET or while sending data) +// (true, nil) - notification supported, data has been sent +func SdNotify(unsetEnvironment bool, state string) (bool, error) { + socketAddr := &net.UnixAddr{ + Name: os.Getenv("NOTIFY_SOCKET"), + Net: "unixgram", + } + + // NOTIFY_SOCKET not set + if socketAddr.Name == "" { + return false, nil + } + + if unsetEnvironment { + if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil { + return false, err + } + } + + conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr) + // Error connecting to NOTIFY_SOCKET + if err != nil { + return false, err + } + defer conn.Close() + + if _, err = conn.Write([]byte(state)); err != nil { + return false, err + } + return true, nil +} diff --git a/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go b/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go new file mode 100644 index 00000000..25d9c1aa --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go @@ -0,0 +1,73 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package daemon + +import ( + "fmt" + "os" + "strconv" + "time" +) + +// SdWatchdogEnabled returns watchdog information for a service. +// Processes should call daemon.SdNotify(false, daemon.SdNotifyWatchdog) every +// time / 2. +// If `unsetEnvironment` is true, the environment variables `WATCHDOG_USEC` and +// `WATCHDOG_PID` will be unconditionally unset. +// +// It returns one of the following: +// (0, nil) - watchdog isn't enabled or we aren't the watched PID. +// (0, err) - an error happened (e.g. error converting time). +// (time, nil) - watchdog is enabled and we can send ping. time is delay +// before inactive service will be killed. +func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) { + wusec := os.Getenv("WATCHDOG_USEC") + wpid := os.Getenv("WATCHDOG_PID") + if unsetEnvironment { + wusecErr := os.Unsetenv("WATCHDOG_USEC") + wpidErr := os.Unsetenv("WATCHDOG_PID") + if wusecErr != nil { + return 0, wusecErr + } + if wpidErr != nil { + return 0, wpidErr + } + } + + if wusec == "" { + return 0, nil + } + s, err := strconv.Atoi(wusec) + if err != nil { + return 0, fmt.Errorf("error converting WATCHDOG_USEC: %s", err) + } + if s <= 0 { + return 0, fmt.Errorf("error WATCHDOG_USEC must be a positive number") + } + interval := time.Duration(s) * time.Microsecond + + if wpid == "" { + return interval, nil + } + p, err := strconv.Atoi(wpid) + if err != nil { + return 0, fmt.Errorf("error converting WATCHDOG_PID: %s", err) + } + if os.Getpid() != p { + return 0, nil + } + + return interval, nil +} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go b/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go new file mode 100644 index 00000000..22ce8f1d --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go @@ -0,0 +1,267 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package dbus provides integration with the systemd D-Bus API. +// See http://www.freedesktop.org/wiki/Software/systemd/dbus/ +package dbus + +import ( + "context" + "encoding/hex" + "fmt" + "os" + "strconv" + "strings" + "sync" + + "github.com/godbus/dbus/v5" +) + +const ( + alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ` + num = `0123456789` + alphanum = alpha + num + signalBuffer = 100 +) + +// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped +func needsEscape(i int, b byte) bool { + // Escape everything that is not a-z-A-Z-0-9 + // Also escape 0-9 if it's the first character + return strings.IndexByte(alphanum, b) == -1 || + (i == 0 && strings.IndexByte(num, b) != -1) +} + +// PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the +// rules that systemd uses for serializing special characters. +func PathBusEscape(path string) string { + // Special case the empty string + if len(path) == 0 { + return "_" + } + n := []byte{} + for i := 0; i < len(path); i++ { + c := path[i] + if needsEscape(i, c) { + e := fmt.Sprintf("_%x", c) + n = append(n, []byte(e)...) + } else { + n = append(n, c) + } + } + return string(n) +} + +// pathBusUnescape is the inverse of PathBusEscape. +func pathBusUnescape(path string) string { + if path == "_" { + return "" + } + n := []byte{} + for i := 0; i < len(path); i++ { + c := path[i] + if c == '_' && i+2 < len(path) { + res, err := hex.DecodeString(path[i+1 : i+3]) + if err == nil { + n = append(n, res...) + } + i += 2 + } else { + n = append(n, c) + } + } + return string(n) +} + +// Conn is a connection to systemd's dbus endpoint. +type Conn struct { + // sysconn/sysobj are only used to call dbus methods + sysconn *dbus.Conn + sysobj dbus.BusObject + + // sigconn/sigobj are only used to receive dbus signals + sigconn *dbus.Conn + sigobj dbus.BusObject + + jobListener struct { + jobs map[dbus.ObjectPath]chan<- string + sync.Mutex + } + subStateSubscriber struct { + updateCh chan<- *SubStateUpdate + errCh chan<- error + sync.Mutex + ignore map[dbus.ObjectPath]int64 + cleanIgnore int64 + } + propertiesSubscriber struct { + updateCh chan<- *PropertiesUpdate + errCh chan<- error + sync.Mutex + } +} + +// Deprecated: use NewWithContext instead. +func New() (*Conn, error) { + return NewWithContext(context.Background()) +} + +// NewWithContext establishes a connection to any available bus and authenticates. +// Callers should call Close() when done with the connection. +func NewWithContext(ctx context.Context) (*Conn, error) { + conn, err := NewSystemConnectionContext(ctx) + if err != nil && os.Geteuid() == 0 { + return NewSystemdConnectionContext(ctx) + } + return conn, err +} + +// Deprecated: use NewSystemConnectionContext instead. +func NewSystemConnection() (*Conn, error) { + return NewSystemConnectionContext(context.Background()) +} + +// NewSystemConnectionContext establishes a connection to the system bus and authenticates. +// Callers should call Close() when done with the connection. +func NewSystemConnectionContext(ctx context.Context) (*Conn, error) { + return NewConnection(func() (*dbus.Conn, error) { + return dbusAuthHelloConnection(ctx, dbus.SystemBusPrivate) + }) +} + +// Deprecated: use NewUserConnectionContext instead. +func NewUserConnection() (*Conn, error) { + return NewUserConnectionContext(context.Background()) +} + +// NewUserConnectionContext establishes a connection to the session bus and +// authenticates. This can be used to connect to systemd user instances. +// Callers should call Close() when done with the connection. +func NewUserConnectionContext(ctx context.Context) (*Conn, error) { + return NewConnection(func() (*dbus.Conn, error) { + return dbusAuthHelloConnection(ctx, dbus.SessionBusPrivate) + }) +} + +// Deprecated: use NewSystemdConnectionContext instead. +func NewSystemdConnection() (*Conn, error) { + return NewSystemdConnectionContext(context.Background()) +} + +// NewSystemdConnectionContext establishes a private, direct connection to systemd. +// This can be used for communicating with systemd without a dbus daemon. +// Callers should call Close() when done with the connection. +func NewSystemdConnectionContext(ctx context.Context) (*Conn, error) { + return NewConnection(func() (*dbus.Conn, error) { + // We skip Hello when talking directly to systemd. + return dbusAuthConnection(ctx, func(opts ...dbus.ConnOption) (*dbus.Conn, error) { + return dbus.Dial("unix:path=/run/systemd/private", opts...) + }) + }) +} + +// Close closes an established connection. +func (c *Conn) Close() { + c.sysconn.Close() + c.sigconn.Close() +} + +// Connected returns whether conn is connected +func (c *Conn) Connected() bool { + return c.sysconn.Connected() && c.sigconn.Connected() +} + +// NewConnection establishes a connection to a bus using a caller-supplied function. +// This allows connecting to remote buses through a user-supplied mechanism. +// The supplied function may be called multiple times, and should return independent connections. +// The returned connection must be fully initialised: the org.freedesktop.DBus.Hello call must have succeeded, +// and any authentication should be handled by the function. +func NewConnection(dialBus func() (*dbus.Conn, error)) (*Conn, error) { + sysconn, err := dialBus() + if err != nil { + return nil, err + } + + sigconn, err := dialBus() + if err != nil { + sysconn.Close() + return nil, err + } + + c := &Conn{ + sysconn: sysconn, + sysobj: systemdObject(sysconn), + sigconn: sigconn, + sigobj: systemdObject(sigconn), + } + + c.subStateSubscriber.ignore = make(map[dbus.ObjectPath]int64) + c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string) + + // Setup the listeners on jobs so that we can get completions + c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'") + + c.dispatch() + return c, nil +} + +// GetManagerProperty returns the value of a property on the org.freedesktop.systemd1.Manager +// interface. The value is returned in its string representation, as defined at +// https://developer.gnome.org/glib/unstable/gvariant-text.html. +func (c *Conn) GetManagerProperty(prop string) (string, error) { + variant, err := c.sysobj.GetProperty("org.freedesktop.systemd1.Manager." + prop) + if err != nil { + return "", err + } + return variant.String(), nil +} + +func dbusAuthConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { + conn, err := createBus(dbus.WithContext(ctx)) + if err != nil { + return nil, err + } + + // Only use EXTERNAL method, and hardcode the uid (not username) + // to avoid a username lookup (which requires a dynamically linked + // libc) + methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} + + err = conn.Auth(methods) + if err != nil { + conn.Close() + return nil, err + } + + return conn, nil +} + +func dbusAuthHelloConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { + conn, err := dbusAuthConnection(ctx, createBus) + if err != nil { + return nil, err + } + + if err = conn.Hello(); err != nil { + conn.Close() + return nil, err + } + + return conn, nil +} + +func systemdObject(conn *dbus.Conn) dbus.BusObject { + return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1")) +} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go b/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go new file mode 100644 index 00000000..a64f0b3e --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go @@ -0,0 +1,876 @@ +// Copyright 2015, 2018 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dbus + +import ( + "context" + "errors" + "fmt" + "path" + "strconv" + + "github.com/godbus/dbus/v5" +) + +// Who specifies which process to send a signal to via the [KillUnitWithTarget]. +type Who string + +const ( + // All sends the signal to all processes in the unit. + All Who = "all" + // Main sends the signal to the main process of the unit. + Main Who = "main" + // Control sends the signal to the control process of the unit. + Control Who = "control" +) + +func (c *Conn) jobComplete(signal *dbus.Signal) { + var id uint32 + var job dbus.ObjectPath + var unit string + var result string + + _ = dbus.Store(signal.Body, &id, &job, &unit, &result) + c.jobListener.Lock() + out, ok := c.jobListener.jobs[job] + if ok { + out <- result + delete(c.jobListener.jobs, job) + } + c.jobListener.Unlock() +} + +func (c *Conn) startJob(ctx context.Context, ch chan<- string, job string, args ...any) (int, error) { + if ch != nil { + c.jobListener.Lock() + defer c.jobListener.Unlock() + } + + var p dbus.ObjectPath + err := c.sysobj.CallWithContext(ctx, job, 0, args...).Store(&p) + if err != nil { + return 0, err + } + + if ch != nil { + c.jobListener.jobs[p] = ch + } + + // ignore error since 0 is fine if conversion fails + jobID, _ := strconv.Atoi(path.Base(string(p))) + + return jobID, nil +} + +// Deprecated: use StartUnitContext instead. +func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.StartUnitContext(context.Background(), name, mode, ch) +} + +// StartUnitContext enqueues a start job and depending jobs, if any (unless otherwise +// specified by the mode string). +// +// Takes the unit to activate, plus a mode string. The mode needs to be one of +// replace, fail, isolate, ignore-dependencies, ignore-requirements. If +// "replace" the call will start the unit and its dependencies, possibly +// replacing already queued jobs that conflict with this. If "fail" the call +// will start the unit and its dependencies, but will fail if this would change +// an already queued job. If "isolate" the call will start the unit in question +// and terminate all units that aren't dependencies of it. If +// "ignore-dependencies" it will start a unit but ignore all its dependencies. +// If "ignore-requirements" it will start a unit but only ignore the +// requirement dependencies. It is not recommended to make use of the latter +// two options. +// +// If the provided channel is non-nil, a result string will be sent to it upon +// job completion: one of done, canceled, timeout, failed, dependency, skipped. +// done indicates successful execution of a job. canceled indicates that a job +// has been canceled before it finished execution. timeout indicates that the +// job timeout was reached. failed indicates that the job failed. dependency +// indicates that a job this job has been depending on failed and the job hence +// has been removed too. skipped indicates that a job was skipped because it +// didn't apply to the units current state. +// +// Important: It is the caller's responsibility to unblock the provided channel write, +// either by reading from the channel or by using a buffered channel. Until the write +// is unblocked, the Conn object cannot handle other jobs. +// +// If no error occurs, the ID of the underlying systemd job will be returned. There +// does exist the possibility for no error to be returned, but for the returned job +// ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint +// should not be considered authoritative. +// +// If an error does occur, it will be returned to the user alongside a job ID of 0. +func (c *Conn) StartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode) +} + +// Deprecated: use StopUnitContext instead. +func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) { + return c.StopUnitContext(context.Background(), name, mode, ch) +} + +// StopUnitContext is similar to StartUnitContext, but stops the specified unit +// rather than starting it. +func (c *Conn) StopUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode) +} + +// Deprecated: use ReloadUnitContext instead. +func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) { + return c.ReloadUnitContext(context.Background(), name, mode, ch) +} + +// ReloadUnitContext reloads a unit. Reloading is done only if the unit +// is already running, and fails otherwise. +func (c *Conn) ReloadUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) +} + +// Deprecated: use RestartUnitContext instead. +func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.RestartUnitContext(context.Background(), name, mode, ch) +} + +// RestartUnitContext restarts a service. If a service is restarted that isn't +// running it will be started. +func (c *Conn) RestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode) +} + +// Deprecated: use TryRestartUnitContext instead. +func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.TryRestartUnitContext(context.Background(), name, mode, ch) +} + +// TryRestartUnitContext is like RestartUnitContext, except that a service that +// isn't running is not affected by the restart. +func (c *Conn) TryRestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) +} + +// Deprecated: use ReloadOrRestartUnitContext instead. +func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.ReloadOrRestartUnitContext(context.Background(), name, mode, ch) +} + +// ReloadOrRestartUnitContext attempts a reload if the unit supports it and use +// a restart otherwise. +func (c *Conn) ReloadOrRestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) +} + +// Deprecated: use ReloadOrTryRestartUnitContext instead. +func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.ReloadOrTryRestartUnitContext(context.Background(), name, mode, ch) +} + +// ReloadOrTryRestartUnitContext attempts a reload if the unit supports it, +// and use a "Try" flavored restart otherwise. +func (c *Conn) ReloadOrTryRestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) +} + +// Deprecated: use StartTransientUnitContext instead. +func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) { + return c.StartTransientUnitContext(context.Background(), name, mode, properties, ch) +} + +// StartTransientUnitContext may be used to create and start a transient unit, which +// will be released as soon as it is not running or referenced anymore or the +// system is rebooted. name is the unit name including suffix, and must be +// unique. mode is the same as in StartUnitContext, properties contains properties +// of the unit. +func (c *Conn) StartTransientUnitContext(ctx context.Context, name string, mode string, properties []Property, ch chan<- string) (int, error) { + return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) +} + +// Deprecated: use [KillUnitWithTarget] instead. +func (c *Conn) KillUnit(name string, signal int32) { + c.KillUnitContext(context.Background(), name, signal) +} + +// KillUnitContext takes the unit name and a UNIX signal number to send. +// All of the unit's processes are killed. +// +// Deprecated: use [KillUnitWithTarget] instead, with target argument set to [All]. +func (c *Conn) KillUnitContext(ctx context.Context, name string, signal int32) { + _ = c.KillUnitWithTarget(ctx, name, All, signal) +} + +// KillUnitWithTarget sends a signal to the specified unit. +// The target argument can be one of [All], [Main], or [Control]. +func (c *Conn) KillUnitWithTarget(ctx context.Context, name string, target Who, signal int32) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.KillUnit", 0, name, string(target), signal).Store() +} + +// Deprecated: use ResetFailedUnitContext instead. +func (c *Conn) ResetFailedUnit(name string) error { + return c.ResetFailedUnitContext(context.Background(), name) +} + +// ResetFailedUnitContext resets the "failed" state of a specific unit. +func (c *Conn) ResetFailedUnitContext(ctx context.Context, name string) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store() +} + +// Deprecated: use SystemStateContext instead. +func (c *Conn) SystemState() (*Property, error) { + return c.SystemStateContext(context.Background()) +} + +// SystemStateContext returns the systemd state. Equivalent to +// systemctl is-system-running. +func (c *Conn) SystemStateContext(ctx context.Context) (*Property, error) { + var err error + var prop dbus.Variant + + obj := c.sysconn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") + err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.systemd1.Manager", "SystemState").Store(&prop) + if err != nil { + return nil, err + } + + return &Property{Name: "SystemState", Value: prop}, nil +} + +// getProperties takes the unit path and returns all of its dbus object properties, for the given dbus interface. +func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInterface string) (map[string]any, error) { + var err error + var props map[string]dbus.Variant + + if !path.IsValid() { + return nil, fmt.Errorf("invalid unit name: %v", path) + } + + obj := c.sysconn.Object("org.freedesktop.systemd1", path) + err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props) + if err != nil { + return nil, err + } + + out := make(map[string]any, len(props)) + for k, v := range props { + out[k] = v.Value() + } + + return out, nil +} + +// Deprecated: use GetUnitPropertiesContext instead. +func (c *Conn) GetUnitProperties(unit string) (map[string]any, error) { + return c.GetUnitPropertiesContext(context.Background(), unit) +} + +// GetUnitPropertiesContext takes the (unescaped) unit name and returns all of +// its dbus object properties. +func (c *Conn) GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]any, error) { + path := unitPath(unit) + return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") +} + +// Deprecated: use GetUnitPathPropertiesContext instead. +func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]any, error) { + return c.GetUnitPathPropertiesContext(context.Background(), path) +} + +// GetUnitPathPropertiesContext takes the (escaped) unit path and returns all +// of its dbus object properties. +func (c *Conn) GetUnitPathPropertiesContext(ctx context.Context, path dbus.ObjectPath) (map[string]any, error) { + return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") +} + +// Deprecated: use GetAllPropertiesContext instead. +func (c *Conn) GetAllProperties(unit string) (map[string]any, error) { + return c.GetAllPropertiesContext(context.Background(), unit) +} + +// GetAllPropertiesContext takes the (unescaped) unit name and returns all of +// its dbus object properties. +func (c *Conn) GetAllPropertiesContext(ctx context.Context, unit string) (map[string]any, error) { + path := unitPath(unit) + return c.getProperties(ctx, path, "") +} + +func (c *Conn) getProperty(ctx context.Context, unit string, dbusInterface string, propertyName string) (*Property, error) { + var err error + var prop dbus.Variant + + path := unitPath(unit) + if !path.IsValid() { + return nil, errors.New("invalid unit name: " + unit) + } + + obj := c.sysconn.Object("org.freedesktop.systemd1", path) + err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop) + if err != nil { + return nil, err + } + + return &Property{Name: propertyName, Value: prop}, nil +} + +// Deprecated: use GetUnitPropertyContext instead. +func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) { + return c.GetUnitPropertyContext(context.Background(), unit, propertyName) +} + +// GetUnitPropertyContext takes an (unescaped) unit name, and a property name, +// and returns the property value. +func (c *Conn) GetUnitPropertyContext(ctx context.Context, unit string, propertyName string) (*Property, error) { + return c.getProperty(ctx, unit, "org.freedesktop.systemd1.Unit", propertyName) +} + +// Deprecated: use GetServicePropertyContext instead. +func (c *Conn) GetServiceProperty(service string, propertyName string) (*Property, error) { + return c.GetServicePropertyContext(context.Background(), service, propertyName) +} + +// GetServicePropertyContext returns property for given service name and property name. +func (c *Conn) GetServicePropertyContext(ctx context.Context, service string, propertyName string) (*Property, error) { + return c.getProperty(ctx, service, "org.freedesktop.systemd1.Service", propertyName) +} + +// Deprecated: use GetUnitTypePropertiesContext instead. +func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]any, error) { + return c.GetUnitTypePropertiesContext(context.Background(), unit, unitType) +} + +// GetUnitTypePropertiesContext returns the extra properties for a unit, specific to the unit type. +// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope. +// Returns "dbus.Error: Unknown interface" error if the unitType is not the correct type of the unit. +func (c *Conn) GetUnitTypePropertiesContext(ctx context.Context, unit string, unitType string) (map[string]any, error) { + path := unitPath(unit) + return c.getProperties(ctx, path, "org.freedesktop.systemd1."+unitType) +} + +// Deprecated: use SetUnitPropertiesContext instead. +func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error { + return c.SetUnitPropertiesContext(context.Background(), name, runtime, properties...) +} + +// SetUnitPropertiesContext may be used to modify certain unit properties at runtime. +// Not all properties may be changed at runtime, but many resource management +// settings (primarily those in systemd.cgroup(5)) may. The changes are applied +// instantly, and stored on disk for future boots, unless runtime is true, in which +// case the settings only apply until the next reboot. name is the name of the unit +// to modify. properties are the settings to set, encoded as an array of property +// name and value pairs. +func (c *Conn) SetUnitPropertiesContext(ctx context.Context, name string, runtime bool, properties ...Property) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store() +} + +// Deprecated: use GetUnitTypePropertyContext instead. +func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { + return c.GetUnitTypePropertyContext(context.Background(), unit, unitType, propertyName) +} + +// GetUnitTypePropertyContext takes a property name, a unit name, and a unit type, +// and returns a property value. For valid values of unitType, see GetUnitTypePropertiesContext. +func (c *Conn) GetUnitTypePropertyContext(ctx context.Context, unit string, unitType string, propertyName string) (*Property, error) { + return c.getProperty(ctx, unit, "org.freedesktop.systemd1."+unitType, propertyName) +} + +type UnitStatus struct { + Name string // The primary unit name as string + Description string // The human readable description string + LoadState string // The load state (i.e. whether the unit file has been loaded successfully) + ActiveState string // The active state (i.e. whether the unit is currently started or not) + SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not) + Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string. + Path dbus.ObjectPath // The unit object path + JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise + JobType string // The job type as string + JobPath dbus.ObjectPath // The job object path +} + +type storeFunc func(retvalues ...any) error + +func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) { + result := make([][]any, 0) + err := f(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]any, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + status := make([]UnitStatus, len(result)) + statusInterface := make([]any, len(status)) + for i := range status { + statusInterface[i] = &status[i] + } + + err = dbus.Store(resultInterface, statusInterface...) + if err != nil { + return nil, err + } + + return status, nil +} + +// GetUnitByPID returns the unit object path of the unit a process ID +// belongs to. It takes a UNIX PID and returns the object path. The PID must +// refer to an existing system process +func (c *Conn) GetUnitByPID(ctx context.Context, pid uint32) (dbus.ObjectPath, error) { + var result dbus.ObjectPath + + err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.GetUnitByPID", 0, pid).Store(&result) + + return result, err +} + +// GetUnitNameByPID returns the name of the unit a process ID belongs to. It +// takes a UNIX PID and returns the object path. The PID must refer to an +// existing system process +func (c *Conn) GetUnitNameByPID(ctx context.Context, pid uint32) (string, error) { + path, err := c.GetUnitByPID(ctx, pid) + if err != nil { + return "", err + } + + return unitName(path), nil +} + +// Deprecated: use ListUnitsContext instead. +func (c *Conn) ListUnits() ([]UnitStatus, error) { + return c.ListUnitsContext(context.Background()) +} + +// ListUnitsContext returns an array with all currently loaded units. Note that +// units may be known by multiple names at the same time, and hence there might +// be more unit names loaded than actual units behind them. +// Also note that a unit is only loaded if it is active and/or enabled. +// Units that are both disabled and inactive will thus not be returned. +func (c *Conn) ListUnitsContext(ctx context.Context) ([]UnitStatus, error) { + return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnits", 0).Store) +} + +// Deprecated: use ListUnitsFilteredContext instead. +func (c *Conn) ListUnitsFiltered(states []string) ([]UnitStatus, error) { + return c.ListUnitsFilteredContext(context.Background(), states) +} + +// ListUnitsFilteredContext returns an array with units filtered by state. +// It takes a list of units' statuses to filter. +func (c *Conn) ListUnitsFilteredContext(ctx context.Context, states []string) ([]UnitStatus, error) { + return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states).Store) +} + +// Deprecated: use ListUnitsByPatternsContext instead. +func (c *Conn) ListUnitsByPatterns(states []string, patterns []string) ([]UnitStatus, error) { + return c.ListUnitsByPatternsContext(context.Background(), states, patterns) +} + +// ListUnitsByPatternsContext returns an array with units. +// It takes a list of units' statuses and names to filter. +// Note that units may be known by multiple names at the same time, +// and hence there might be more unit names loaded than actual units behind them. +func (c *Conn) ListUnitsByPatternsContext(ctx context.Context, states []string, patterns []string) ([]UnitStatus, error) { + return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByPatterns", 0, states, patterns).Store) +} + +// Deprecated: use ListUnitsByNamesContext instead. +func (c *Conn) ListUnitsByNames(units []string) ([]UnitStatus, error) { + return c.ListUnitsByNamesContext(context.Background(), units) +} + +// ListUnitsByNamesContext returns an array with units. It takes a list of units' +// names and returns an UnitStatus array. Comparing to ListUnitsByPatternsContext +// method, this method returns statuses even for inactive or non-existing +// units. Input array should contain exact unit names, but not patterns. +// +// Requires systemd v230 or higher. +func (c *Conn) ListUnitsByNamesContext(ctx context.Context, units []string) ([]UnitStatus, error) { + return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByNames", 0, units).Store) +} + +type UnitFile struct { + Path string + Type string +} + +func (c *Conn) listUnitFilesInternal(f storeFunc) ([]UnitFile, error) { + result := make([][]any, 0) + err := f(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]any, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + files := make([]UnitFile, len(result)) + fileInterface := make([]any, len(files)) + for i := range files { + fileInterface[i] = &files[i] + } + + err = dbus.Store(resultInterface, fileInterface...) + if err != nil { + return nil, err + } + + return files, nil +} + +// Deprecated: use ListUnitFilesContext instead. +func (c *Conn) ListUnitFiles() ([]UnitFile, error) { + return c.ListUnitFilesContext(context.Background()) +} + +// ListUnitFilesContext returns an array of all available units on disk. +func (c *Conn) ListUnitFilesContext(ctx context.Context) ([]UnitFile, error) { + return c.listUnitFilesInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store) +} + +// Deprecated: use ListUnitFilesByPatternsContext instead. +func (c *Conn) ListUnitFilesByPatterns(states []string, patterns []string) ([]UnitFile, error) { + return c.ListUnitFilesByPatternsContext(context.Background(), states, patterns) +} + +// ListUnitFilesByPatternsContext returns an array of all available units on disk matched the patterns. +func (c *Conn) ListUnitFilesByPatternsContext(ctx context.Context, states []string, patterns []string) ([]UnitFile, error) { + return c.listUnitFilesInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFilesByPatterns", 0, states, patterns).Store) +} + +type LinkUnitFileChange EnableUnitFileChange + +// Deprecated: use LinkUnitFilesContext instead. +func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { + return c.LinkUnitFilesContext(context.Background(), files, runtime, force) +} + +// LinkUnitFilesContext links unit files (that are located outside of the +// usual unit search paths) into the unit search path. +// +// It takes a list of absolute paths to unit files to link and two +// booleans. +// +// The first boolean controls whether the unit shall be +// enabled for runtime only (true, /run), or persistently (false, +// /etc). +// +// The second controls whether symlinks pointing to other units shall +// be replaced if necessary. +// +// This call returns a list of the changes made. The list consists of +// structures with three strings: the type of the change (one of symlink +// or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) LinkUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { + result := make([][]any, 0) + err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]any, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]LinkUnitFileChange, len(result)) + changesInterface := make([]any, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return nil, err + } + + return changes, nil +} + +// Deprecated: use EnableUnitFilesContext instead. +func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { + return c.EnableUnitFilesContext(context.Background(), files, runtime, force) +} + +// EnableUnitFilesContext may be used to enable one or more units in the system +// (by creating symlinks to them in /etc or /run). +// +// It takes a list of unit files to enable (either just file names or full +// absolute paths if the unit files are residing outside the usual unit +// search paths), and two booleans: the first controls whether the unit shall +// be enabled for runtime only (true, /run), or persistently (false, /etc). +// The second one controls whether symlinks pointing to other units shall +// be replaced if necessary. +// +// This call returns one boolean and an array with the changes made. The +// boolean signals whether the unit files contained any enablement +// information (i.e. an [Install]) section. The changes list consists of +// structures with three strings: the type of the change (one of symlink +// or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) EnableUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { + var carries_install_info bool + + result := make([][]any, 0) + err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result) + if err != nil { + return false, nil, err + } + + resultInterface := make([]any, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]EnableUnitFileChange, len(result)) + changesInterface := make([]any, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return false, nil, err + } + + return carries_install_info, changes, nil +} + +type EnableUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// Deprecated: use DisableUnitFilesContext instead. +func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) { + return c.DisableUnitFilesContext(context.Background(), files, runtime) +} + +// DisableUnitFilesContext may be used to disable one or more units in the +// system (by removing symlinks to them from /etc or /run). +// +// It takes a list of unit files to disable (either just file names or full +// absolute paths if the unit files are residing outside the usual unit +// search paths), and one boolean: whether the unit was enabled for runtime +// only (true, /run), or persistently (false, /etc). +// +// This call returns an array with the changes made. The changes list +// consists of structures with three strings: the type of the change (one of +// symlink or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) DisableUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]DisableUnitFileChange, error) { + result := make([][]any, 0) + err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]any, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]DisableUnitFileChange, len(result)) + changesInterface := make([]any, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return nil, err + } + + return changes, nil +} + +type DisableUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// Deprecated: use MaskUnitFilesContext instead. +func (c *Conn) MaskUnitFiles(files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) { + return c.MaskUnitFilesContext(context.Background(), files, runtime, force) +} + +// MaskUnitFilesContext masks one or more units in the system. +// +// The files argument contains a list of units to mask (either just file names +// or full absolute paths if the unit files are residing outside the usual unit +// search paths). +// +// The runtime argument is used to specify whether the unit was enabled for +// runtime only (true, /run/systemd/..), or persistently (false, +// /etc/systemd/..). +func (c *Conn) MaskUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) { + result := make([][]any, 0) + err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.MaskUnitFiles", 0, files, runtime, force).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]any, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]MaskUnitFileChange, len(result)) + changesInterface := make([]any, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return nil, err + } + + return changes, nil +} + +type MaskUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// Deprecated: use UnmaskUnitFilesContext instead. +func (c *Conn) UnmaskUnitFiles(files []string, runtime bool) ([]UnmaskUnitFileChange, error) { + return c.UnmaskUnitFilesContext(context.Background(), files, runtime) +} + +// UnmaskUnitFilesContext unmasks one or more units in the system. +// +// It takes the list of unit files to mask (either just file names or full +// absolute paths if the unit files are residing outside the usual unit search +// paths), and a boolean runtime flag to specify whether the unit was enabled +// for runtime only (true, /run/systemd/..), or persistently (false, +// /etc/systemd/..). +func (c *Conn) UnmaskUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]UnmaskUnitFileChange, error) { + result := make([][]any, 0) + err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.UnmaskUnitFiles", 0, files, runtime).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]any, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]UnmaskUnitFileChange, len(result)) + changesInterface := make([]any, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return nil, err + } + + return changes, nil +} + +type UnmaskUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// Deprecated: use ReloadContext instead. +func (c *Conn) Reload() error { + return c.ReloadContext(context.Background()) +} + +// ReloadContext instructs systemd to scan for and reload unit files. This is +// an equivalent to systemctl daemon-reload. +func (c *Conn) ReloadContext(ctx context.Context) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.Reload", 0).Store() +} + +func unitPath(name string) dbus.ObjectPath { + return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name)) +} + +// unitName returns the unescaped base element of the supplied escaped path. +func unitName(dpath dbus.ObjectPath) string { + return pathBusUnescape(path.Base(string(dpath))) +} + +// JobStatus holds a currently queued job definition. +type JobStatus struct { + Id uint32 // The numeric job id + Unit string // The primary unit name for this job + JobType string // The job type as string + Status string // The job state as string + JobPath dbus.ObjectPath // The job object path + UnitPath dbus.ObjectPath // The unit object path +} + +// Deprecated: use ListJobsContext instead. +func (c *Conn) ListJobs() ([]JobStatus, error) { + return c.ListJobsContext(context.Background()) +} + +// ListJobsContext returns an array with all currently queued jobs. +func (c *Conn) ListJobsContext(ctx context.Context) ([]JobStatus, error) { + return c.listJobsInternal(ctx) +} + +func (c *Conn) listJobsInternal(ctx context.Context) ([]JobStatus, error) { + result := make([][]any, 0) + if err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListJobs", 0).Store(&result); err != nil { + return nil, err + } + + resultInterface := make([]any, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + status := make([]JobStatus, len(result)) + statusInterface := make([]any, len(status)) + for i := range status { + statusInterface[i] = &status[i] + } + + if err := dbus.Store(resultInterface, statusInterface...); err != nil { + return nil, err + } + + return status, nil +} + +// FreezeUnit freezes the cgroup associated with the unit. +// Note that FreezeUnit and [ThawUnit] are only supported on systems running with cgroup v2. +func (c *Conn) FreezeUnit(ctx context.Context, unit string) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.FreezeUnit", 0, unit).Store() +} + +// ThawUnit unfreezes the cgroup associated with the unit. +func (c *Conn) ThawUnit(ctx context.Context, unit string) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ThawUnit", 0, unit).Store() +} + +// AttachProcessesToUnit moves existing processes, identified by pids, into an existing systemd unit. +func (c *Conn) AttachProcessesToUnit(ctx context.Context, unit, subcgroup string, pids []uint32) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.AttachProcessesToUnit", 0, unit, subcgroup, pids).Store() +} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/properties.go b/vendor/github.com/coreos/go-systemd/v22/dbus/properties.go new file mode 100644 index 00000000..fb42b627 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/properties.go @@ -0,0 +1,237 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dbus + +import ( + "github.com/godbus/dbus/v5" +) + +// From the systemd docs: +// +// The properties array of StartTransientUnit() may take many of the settings +// that may also be configured in unit files. Not all parameters are currently +// accepted though, but we plan to cover more properties with future release. +// Currently you may set the Description, Slice and all dependency types of +// units, as well as RemainAfterExit, ExecStart for service units, +// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares, +// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth, +// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit, +// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map +// directly to their counterparts in unit files and as normal D-Bus object +// properties. The exception here is the PIDs field of scope units which is +// used for construction of the scope only and specifies the initial PIDs to +// add to the scope object. + +type Property struct { + Name string + Value dbus.Variant +} + +type PropertyCollection struct { + Name string + Properties []Property +} + +type execStart struct { + Path string // the binary path to execute + Args []string // an array with all arguments to pass to the executed command, starting with argument 0 + UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly +} + +// PropExecStart sets the ExecStart service property. The first argument is a +// slice with the binary path to execute followed by the arguments to pass to +// the executed command. See +// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= +func PropExecStart(command []string, uncleanIsFailure bool) Property { + execStarts := []execStart{ + { + Path: command[0], + Args: command, + UncleanIsFailure: uncleanIsFailure, + }, + } + + return Property{ + Name: "ExecStart", + Value: dbus.MakeVariant(execStarts), + } +} + +// PropRemainAfterExit sets the RemainAfterExit service property. See +// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit= +func PropRemainAfterExit(b bool) Property { + return Property{ + Name: "RemainAfterExit", + Value: dbus.MakeVariant(b), + } +} + +// PropType sets the Type service property. See +// http://www.freedesktop.org/software/systemd/man/systemd.service.html#Type= +func PropType(t string) Property { + return Property{ + Name: "Type", + Value: dbus.MakeVariant(t), + } +} + +// PropDescription sets the Description unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description= +func PropDescription(desc string) Property { + return Property{ + Name: "Description", + Value: dbus.MakeVariant(desc), + } +} + +func propDependency(name string, units []string) Property { + return Property{ + Name: name, + Value: dbus.MakeVariant(units), + } +} + +// PropRequires sets the Requires unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires= +func PropRequires(units ...string) Property { + return propDependency("Requires", units) +} + +// PropRequiresOverridable sets the RequiresOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable= +func PropRequiresOverridable(units ...string) Property { + return propDependency("RequiresOverridable", units) +} + +// PropRequisite sets the Requisite unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite= +func PropRequisite(units ...string) Property { + return propDependency("Requisite", units) +} + +// PropRequisiteOverridable sets the RequisiteOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable= +func PropRequisiteOverridable(units ...string) Property { + return propDependency("RequisiteOverridable", units) +} + +// PropWants sets the Wants unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants= +func PropWants(units ...string) Property { + return propDependency("Wants", units) +} + +// PropBindsTo sets the BindsTo unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo= +func PropBindsTo(units ...string) Property { + return propDependency("BindsTo", units) +} + +// PropRequiredBy sets the RequiredBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy= +func PropRequiredBy(units ...string) Property { + return propDependency("RequiredBy", units) +} + +// PropRequiredByOverridable sets the RequiredByOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable= +func PropRequiredByOverridable(units ...string) Property { + return propDependency("RequiredByOverridable", units) +} + +// PropWantedBy sets the WantedBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy= +func PropWantedBy(units ...string) Property { + return propDependency("WantedBy", units) +} + +// PropBoundBy sets the BoundBy unit property. See +// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy= +func PropBoundBy(units ...string) Property { + return propDependency("BoundBy", units) +} + +// PropConflicts sets the Conflicts unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts= +func PropConflicts(units ...string) Property { + return propDependency("Conflicts", units) +} + +// PropConflictedBy sets the ConflictedBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy= +func PropConflictedBy(units ...string) Property { + return propDependency("ConflictedBy", units) +} + +// PropBefore sets the Before unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before= +func PropBefore(units ...string) Property { + return propDependency("Before", units) +} + +// PropAfter sets the After unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After= +func PropAfter(units ...string) Property { + return propDependency("After", units) +} + +// PropOnFailure sets the OnFailure unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure= +func PropOnFailure(units ...string) Property { + return propDependency("OnFailure", units) +} + +// PropTriggers sets the Triggers unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers= +func PropTriggers(units ...string) Property { + return propDependency("Triggers", units) +} + +// PropTriggeredBy sets the TriggeredBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy= +func PropTriggeredBy(units ...string) Property { + return propDependency("TriggeredBy", units) +} + +// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo= +func PropPropagatesReloadTo(units ...string) Property { + return propDependency("PropagatesReloadTo", units) +} + +// PropRequiresMountsFor sets the RequiresMountsFor unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor= +func PropRequiresMountsFor(units ...string) Property { + return propDependency("RequiresMountsFor", units) +} + +// PropSlice sets the Slice unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice= +func PropSlice(slice string) Property { + return Property{ + Name: "Slice", + Value: dbus.MakeVariant(slice), + } +} + +// PropPids sets the PIDs field of scope units used in the initial construction +// of the scope only and specifies the initial PIDs to add to the scope object. +// See https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/#properties +func PropPids(pids ...uint32) Property { + return Property{ + Name: "PIDs", + Value: dbus.MakeVariant(pids), + } +} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/set.go b/vendor/github.com/coreos/go-systemd/v22/dbus/set.go new file mode 100644 index 00000000..17c5d485 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/set.go @@ -0,0 +1,47 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dbus + +type set struct { + data map[string]bool +} + +func (s *set) Add(value string) { + s.data[value] = true +} + +func (s *set) Remove(value string) { + delete(s.data, value) +} + +func (s *set) Contains(value string) (exists bool) { + _, exists = s.data[value] + return +} + +func (s *set) Length() int { + return len(s.data) +} + +func (s *set) Values() (values []string) { + for val := range s.data { + values = append(values, val) + } + return +} + +func newSet() *set { + return &set{make(map[string]bool)} +} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go new file mode 100644 index 00000000..f0f6aad9 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go @@ -0,0 +1,333 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dbus + +import ( + "errors" + "log" + "time" + + "github.com/godbus/dbus/v5" +) + +const ( + cleanIgnoreInterval = int64(10 * time.Second) + ignoreInterval = int64(30 * time.Millisecond) +) + +// Subscribe sets up this connection to subscribe to all systemd dbus events. +// This is required before calling SubscribeUnits. When the connection closes +// systemd will automatically stop sending signals so there is no need to +// explicitly call Unsubscribe(). +func (c *Conn) Subscribe() error { + c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'") + c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'") + + return c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store() +} + +// Unsubscribe this connection from systemd dbus events. +func (c *Conn) Unsubscribe() error { + return c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store() +} + +func (c *Conn) dispatch() { + ch := make(chan *dbus.Signal, signalBuffer) + + c.sigconn.Signal(ch) + + go func() { + for { + signal, ok := <-ch + if !ok { + return + } + + if signal.Name == "org.freedesktop.systemd1.Manager.JobRemoved" { + c.jobComplete(signal) + } + + if c.subStateSubscriber.updateCh == nil && + c.propertiesSubscriber.updateCh == nil { + continue + } + + var unitPath dbus.ObjectPath + switch signal.Name { + case "org.freedesktop.systemd1.Manager.JobRemoved": + unitName := signal.Body[2].(string) + _ = c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) + case "org.freedesktop.systemd1.Manager.UnitNew": + unitPath = signal.Body[1].(dbus.ObjectPath) + case "org.freedesktop.DBus.Properties.PropertiesChanged": + if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" { + unitPath = signal.Path + + if len(signal.Body) >= 2 { + if changed, ok := signal.Body[1].(map[string]dbus.Variant); ok { + c.sendPropertiesUpdate(unitPath, changed) + } + } + } + } + + if unitPath == dbus.ObjectPath("") { + continue + } + + c.sendSubStateUpdate(unitPath) + } + }() +} + +// SubscribeUnits returns two unbuffered channels which will receive all changed units every +// interval. Deleted units are sent as nil. +func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) { + return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil) +} + +// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer +// size of the channels, the comparison function for detecting changes and a filter +// function for cutting down on the noise that your channel receives. +func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) { + old := make(map[string]*UnitStatus) + statusChan := make(chan map[string]*UnitStatus, buffer) + errChan := make(chan error, buffer) + + go func() { + for { + timerChan := time.After(interval) + + units, err := c.ListUnits() + if err == nil { + cur := make(map[string]*UnitStatus) + for i := range units { + if filterUnit != nil && filterUnit(units[i].Name) { + continue + } + cur[units[i].Name] = &units[i] + } + + // add all new or changed units + changed := make(map[string]*UnitStatus) + for n, u := range cur { + if oldU, ok := old[n]; !ok || isChanged(oldU, u) { + changed[n] = u + } + delete(old, n) + } + + // add all deleted units + for oldN := range old { + changed[oldN] = nil + } + + old = cur + + if len(changed) != 0 { + statusChan <- changed + } + } else { + errChan <- err + } + + <-timerChan + } + }() + + return statusChan, errChan +} + +type SubStateUpdate struct { + UnitName string + SubState string +} + +// SetSubStateSubscriber writes to updateCh when any unit's substate changes. +// Although this writes to updateCh on every state change, the reported state +// may be more recent than the change that generated it (due to an unavoidable +// race in the systemd dbus interface). That is, this method provides a good +// way to keep a current view of all units' states, but is not guaranteed to +// show every state transition they go through. Furthermore, state changes +// will only be written to the channel with non-blocking writes. If updateCh +// is full, it attempts to write an error to errCh; if errCh is full, the error +// passes silently. +func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) { + if c == nil { + msg := "nil receiver" + select { + case errCh <- errors.New(msg): + default: + log.Printf("full error channel while reporting: %s\n", msg) + } + return + } + + c.subStateSubscriber.Lock() + defer c.subStateSubscriber.Unlock() + c.subStateSubscriber.updateCh = updateCh + c.subStateSubscriber.errCh = errCh +} + +func (c *Conn) sendSubStateUpdate(unitPath dbus.ObjectPath) { + c.subStateSubscriber.Lock() + defer c.subStateSubscriber.Unlock() + + if c.subStateSubscriber.updateCh == nil { + return + } + + isIgnored := c.shouldIgnore(unitPath) + defer c.cleanIgnore() + if isIgnored { + return + } + + info, err := c.GetUnitPathProperties(unitPath) + if err != nil { + select { + case c.subStateSubscriber.errCh <- err: + default: + log.Printf("full error channel while reporting: %s\n", err) + } + return + } + defer c.updateIgnore(unitPath, info) + + name, ok := info["Id"].(string) + if !ok { + msg := "failed to cast info.Id" + select { + case c.subStateSubscriber.errCh <- errors.New(msg): + default: + log.Printf("full error channel while reporting: %s\n", err) + } + return + } + substate, ok := info["SubState"].(string) + if !ok { + msg := "failed to cast info.SubState" + select { + case c.subStateSubscriber.errCh <- errors.New(msg): + default: + log.Printf("full error channel while reporting: %s\n", msg) + } + return + } + + update := &SubStateUpdate{name, substate} + select { + case c.subStateSubscriber.updateCh <- update: + default: + msg := "update channel is full" + select { + case c.subStateSubscriber.errCh <- errors.New(msg): + default: + log.Printf("full error channel while reporting: %s\n", msg) + } + return + } +} + +// The ignore functions work around a wart in the systemd dbus interface. +// Requesting the properties of an unloaded unit will cause systemd to send a +// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's +// properties on UnitNew (as that's the only indication of a new unit coming up +// for the first time), we would enter an infinite loop if we did not attempt +// to detect and ignore these spurious signals. The signal themselves are +// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an +// unloaded unit's signals for a short time after requesting its properties. +// This means that we will miss e.g. a transient unit being restarted +// *immediately* upon failure and also a transient unit being started +// immediately after requesting its status (with systemctl status, for example, +// because this causes a UnitNew signal to be sent which then causes us to fetch +// the properties). + +func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool { + t, ok := c.subStateSubscriber.ignore[path] + return ok && t >= time.Now().UnixNano() +} + +func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]any) { + loadState, ok := info["LoadState"].(string) + if !ok { + return + } + + // unit is unloaded - it will trigger bad systemd dbus behavior + if loadState == "not-found" { + c.subStateSubscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval + } +} + +// without this, ignore would grow unboundedly over time +func (c *Conn) cleanIgnore() { + now := time.Now().UnixNano() + if c.subStateSubscriber.cleanIgnore < now { + c.subStateSubscriber.cleanIgnore = now + cleanIgnoreInterval + + for p, t := range c.subStateSubscriber.ignore { + if t < now { + delete(c.subStateSubscriber.ignore, p) + } + } + } +} + +// PropertiesUpdate holds a map of a unit's changed properties +type PropertiesUpdate struct { + UnitName string + Changed map[string]dbus.Variant +} + +// SetPropertiesSubscriber writes to updateCh when any unit's properties +// change. Every property change reported by systemd will be sent; that is, no +// transitions will be "missed" (as they might be with SetSubStateSubscriber). +// However, state changes will only be written to the channel with non-blocking +// writes. If updateCh is full, it attempts to write an error to errCh; if +// errCh is full, the error passes silently. +func (c *Conn) SetPropertiesSubscriber(updateCh chan<- *PropertiesUpdate, errCh chan<- error) { + c.propertiesSubscriber.Lock() + defer c.propertiesSubscriber.Unlock() + c.propertiesSubscriber.updateCh = updateCh + c.propertiesSubscriber.errCh = errCh +} + +// we don't need to worry about shouldIgnore() here because +// sendPropertiesUpdate doesn't call GetProperties() +func (c *Conn) sendPropertiesUpdate(unitPath dbus.ObjectPath, changedProps map[string]dbus.Variant) { + c.propertiesSubscriber.Lock() + defer c.propertiesSubscriber.Unlock() + + if c.propertiesSubscriber.updateCh == nil { + return + } + + update := &PropertiesUpdate{unitName(unitPath), changedProps} + + select { + case c.propertiesSubscriber.updateCh <- update: + default: + msg := "update channel is full" + select { + case c.propertiesSubscriber.errCh <- errors.New(msg): + default: + log.Printf("full error channel while reporting: %s\n", msg) + } + return + } +} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go new file mode 100644 index 00000000..dbe4aa88 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go @@ -0,0 +1,57 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dbus + +import ( + "time" +) + +// SubscriptionSet returns a subscription set which is like conn.Subscribe but +// can filter to only return events for a set of units. +type SubscriptionSet struct { + *set + conn *Conn +} + +func (s *SubscriptionSet) filter(unit string) bool { + return !s.Contains(unit) +} + +// Subscribe starts listening for dbus events for all of the units in the set. +// Returns channels identical to conn.SubscribeUnits. +func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { + // TODO: Make fully evented by using systemd 209 with properties changed values + return s.conn.SubscribeUnitsCustom(time.Second, 0, + mismatchUnitStatus, + func(unit string) bool { return s.filter(unit) }, + ) +} + +// NewSubscriptionSet returns a new subscription set. +func (c *Conn) NewSubscriptionSet() *SubscriptionSet { + return &SubscriptionSet{newSet(), c} +} + +// mismatchUnitStatus returns true if the provided UnitStatus objects +// are not equivalent. false is returned if the objects are equivalent. +// Only the Name, Description and state-related fields are used in +// the comparison. +func mismatchUnitStatus(u1, u2 *UnitStatus) bool { + return u1.Name != u2.Name || + u1.Description != u2.Description || + u1.LoadState != u2.LoadState || + u1.ActiveState != u2.ActiveState || + u1.SubState != u2.SubState +} diff --git a/vendor/github.com/coreos/go-systemd/v22/internal/dlopen/dlopen.go b/vendor/github.com/coreos/go-systemd/v22/internal/dlopen/dlopen.go new file mode 100644 index 00000000..023b16bd --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/internal/dlopen/dlopen.go @@ -0,0 +1,92 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package dlopen provides some convenience functions to dlopen a library and +// get its symbols. +package dlopen + +// #cgo LDFLAGS: -ldl +// #include +// #include +import "C" + +import ( + "errors" + "fmt" + "runtime" + "unsafe" +) + +var ErrSoNotFound = errors.New("unable to open a handle to the library") + +// LibHandle represents an open handle to a library (.so) +type LibHandle struct { + Handle unsafe.Pointer + Libname string +} + +// GetHandle tries to get a handle to a library (.so), attempting to access it +// by the names specified in libs and returning the first that is successfully +// opened. Callers are responsible for closing the handler. If no library can +// be successfully opened, an error is returned. +func GetHandle(libs []string) (*LibHandle, error) { + for _, name := range libs { + libname := C.CString(name) + defer C.free(unsafe.Pointer(libname)) + handle := C.dlopen(libname, C.RTLD_LAZY) + if handle != nil { + h := &LibHandle{ + Handle: handle, + Libname: name, + } + return h, nil + } + } + return nil, ErrSoNotFound +} + +// GetSymbolPointer takes a symbol name and returns a pointer to the symbol. +func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) { + // Locking the thread is critical here as the dlerror() is thread local so + // go should not reschedule this onto another thread. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + sym := C.CString(symbol) + defer C.free(unsafe.Pointer(sym)) + + C.dlerror() + p := C.dlsym(l.Handle, sym) + e := C.dlerror() + if e != nil { + return nil, fmt.Errorf("error resolving symbol %q: %v", symbol, errors.New(C.GoString(e))) + } + + return p, nil +} + +// Close closes a LibHandle. +func (l *LibHandle) Close() error { + // Locking the thread is critical here as the dlerror() is thread local so + // go should not reschedule this onto another thread. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + C.dlerror() + C.dlclose(l.Handle) + e := C.dlerror() + if e != nil { + return fmt.Errorf("error closing %v: %v", l.Libname, errors.New(C.GoString(e))) + } + + return nil +} diff --git a/vendor/github.com/coreos/go-systemd/v22/internal/dlopen/dlopen_example.go b/vendor/github.com/coreos/go-systemd/v22/internal/dlopen/dlopen_example.go new file mode 100644 index 00000000..d50a52c9 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/internal/dlopen/dlopen_example.go @@ -0,0 +1,56 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//go:build linux + +package dlopen + +// #include +// #include +// +// int +// my_strlen(void *f, const char *s) +// { +// size_t (*strlen)(const char *); +// +// strlen = (size_t (*)(const char *))f; +// return strlen(s); +// } +import "C" + +import ( + "fmt" + "unsafe" +) + +func strlen(libs []string, s string) (int, error) { + h, err := GetHandle(libs) + if err != nil { + return -1, fmt.Errorf(`couldn't get a handle to the library: %v`, err) + } + defer h.Close() + + f := "strlen" + cs := C.CString(s) + defer C.free(unsafe.Pointer(cs)) + + strlen, err := h.GetSymbolPointer(f) + if err != nil { + return -1, fmt.Errorf(`couldn't get symbol %q: %v`, f, err) + } + + len := C.my_strlen(strlen, cs) + + return int(len), nil +} diff --git a/vendor/github.com/coreos/go-systemd/v22/journal/journal.go b/vendor/github.com/coreos/go-systemd/v22/journal/journal.go new file mode 100644 index 00000000..16c4e477 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/journal/journal.go @@ -0,0 +1,46 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package journal provides write bindings to the local systemd journal. +// It is implemented in pure Go and connects to the journal directly over its +// unix socket. +// +// To read from the journal, see the "sdjournal" package, which wraps the +// sd-journal a C API. +// +// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html +package journal + +import ( + "fmt" +) + +// Priority of a journal message +type Priority int + +const ( + PriEmerg Priority = iota + PriAlert + PriCrit + PriErr + PriWarning + PriNotice + PriInfo + PriDebug +) + +// Print prints a message to the local systemd journal using Send(). +func Print(priority Priority, format string, a ...any) error { + return Send(fmt.Sprintf(format, a...), priority, nil) +} diff --git a/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go b/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go new file mode 100644 index 00000000..6266e16e --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go @@ -0,0 +1,265 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !windows + +// Package journal provides write bindings to the local systemd journal. +// It is implemented in pure Go and connects to the journal directly over its +// unix socket. +// +// To read from the journal, see the "sdjournal" package, which wraps the +// sd-journal a C API. +// +// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html +package journal + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "os" + "strconv" + "strings" + "sync" + "sync/atomic" + "syscall" + "unsafe" +) + +var ( + // This can be overridden at build-time: + // https://github.com/golang/go/wiki/GcToolchainTricks#including-build-information-in-the-executable + journalSocket = "/run/systemd/journal/socket" + + // unixConnPtr atomically holds the local unconnected Unix-domain socket. + // Concrete safe pointer type: *net.UnixConn + unixConnPtr unsafe.Pointer + // onceConn ensures that unixConnPtr is initialized exactly once. + onceConn sync.Once +) + +// Enabled checks whether the local systemd journal is available for logging. +func Enabled() bool { + if c := getOrInitConn(); c == nil { + return false + } + + conn, err := net.Dial("unixgram", journalSocket) + if err != nil { + return false + } + defer conn.Close() + + return true +} + +// StderrIsJournalStream returns whether the process stderr is connected +// to the Journal's stream transport. +// +// This can be used for automatic protocol upgrading described in [Journal Native Protocol]. +// +// Returns true if JOURNAL_STREAM environment variable is present, +// and stderr's device and inode numbers match it. +// +// Error is returned if unexpected error occurs: e.g. if JOURNAL_STREAM environment variable +// is present, but malformed, fstat syscall fails, etc. +// +// [Journal Native Protocol]: https://systemd.io/JOURNAL_NATIVE_PROTOCOL/#automatic-protocol-upgrading +func StderrIsJournalStream() (bool, error) { + return fdIsJournalStream(syscall.Stderr) +} + +// StdoutIsJournalStream returns whether the process stdout is connected +// to the Journal's stream transport. +// +// Returns true if JOURNAL_STREAM environment variable is present, +// and stdout's device and inode numbers match it. +// +// Error is returned if unexpected error occurs: e.g. if JOURNAL_STREAM environment variable +// is present, but malformed, fstat syscall fails, etc. +// +// Most users should probably use [StderrIsJournalStream]. +func StdoutIsJournalStream() (bool, error) { + return fdIsJournalStream(syscall.Stdout) +} + +func fdIsJournalStream(fd int) (bool, error) { + journalStream := os.Getenv("JOURNAL_STREAM") + if journalStream == "" { + return false, nil + } + + var expectedStat syscall.Stat_t + _, err := fmt.Sscanf(journalStream, "%d:%d", &expectedStat.Dev, &expectedStat.Ino) + if err != nil { + return false, fmt.Errorf("failed to parse JOURNAL_STREAM=%q: %v", journalStream, err) + } + + var stat syscall.Stat_t + err = syscall.Fstat(fd, &stat) + if err != nil { + return false, err + } + + match := stat.Dev == expectedStat.Dev && stat.Ino == expectedStat.Ino + return match, nil +} + +// Send a message to the local systemd journal. vars is a map of journald +// fields to values. Fields must be composed of uppercase letters, numbers, +// and underscores, but must not start with an underscore. Within these +// restrictions, any arbitrary field name may be used. Some names have special +// significance: see the journalctl documentation +// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html) +// for more details. vars may be nil. +func Send(message string, priority Priority, vars map[string]string) error { + conn := getOrInitConn() + if conn == nil { + return errors.New("could not initialize socket to journald") + } + + socketAddr := &net.UnixAddr{ + Name: journalSocket, + Net: "unixgram", + } + + data := new(bytes.Buffer) + appendVariable(data, "PRIORITY", strconv.Itoa(int(priority))) + appendVariable(data, "MESSAGE", message) + for k, v := range vars { + appendVariable(data, k, v) + } + + _, _, err := conn.WriteMsgUnix(data.Bytes(), nil, socketAddr) + if err == nil { + return nil + } + if !isSocketSpaceError(err) { + return err + } + + // Large log entry, send it via tempfile and ancillary-fd. + file, err := tempFd() + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(file, data) + if err != nil { + return err + } + rights := syscall.UnixRights(int(file.Fd())) + _, _, err = conn.WriteMsgUnix([]byte{}, rights, socketAddr) + if err != nil { + return err + } + + return nil +} + +// getOrInitConn attempts to get the global `unixConnPtr` socket, initializing if necessary +func getOrInitConn() *net.UnixConn { + conn := (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr)) + if conn != nil { + return conn + } + onceConn.Do(initConn) + return (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr)) +} + +func appendVariable(w io.Writer, name, value string) { + if err := validVarName(name); err != nil { + fmt.Fprintf(os.Stderr, "variable name %s contains invalid character, ignoring\n", name) + } + if strings.ContainsRune(value, '\n') { + /* When the value contains a newline, we write: + * - the variable name, followed by a newline + * - the size (in 64bit little endian format) + * - the data, followed by a newline + */ + fmt.Fprintln(w, name) + _ = binary.Write(w, binary.LittleEndian, uint64(len(value))) + fmt.Fprintln(w, value) + } else { + /* just write the variable and value all on one line */ + fmt.Fprintf(w, "%s=%s\n", name, value) + } +} + +// validVarName validates a variable name to make sure journald will accept it. +// The variable name must be in uppercase and consist only of characters, +// numbers and underscores, and may not begin with an underscore: +// https://www.freedesktop.org/software/systemd/man/sd_journal_print.html +func validVarName(name string) error { + if name == "" { + return errors.New("Empty variable name") + } else if name[0] == '_' { + return errors.New("Variable name begins with an underscore") + } + + for _, c := range name { + if ('A' > c || c > 'Z') && ('0' > c || c > '9') && c != '_' { + return errors.New("Variable name contains invalid characters") + } + } + return nil +} + +// isSocketSpaceError checks whether the error is signaling +// an "overlarge message" condition. +func isSocketSpaceError(err error) bool { + opErr, ok := err.(*net.OpError) + if !ok || opErr == nil { + return false + } + + sysErr, ok := opErr.Err.(*os.SyscallError) + if !ok || sysErr == nil { + return false + } + + return sysErr.Err == syscall.EMSGSIZE || sysErr.Err == syscall.ENOBUFS +} + +// tempFd creates a temporary, unlinked file under `/dev/shm`. +func tempFd() (*os.File, error) { + file, err := os.CreateTemp("/dev/shm/", "journal.XXXXX") + if err != nil { + return nil, err + } + err = syscall.Unlink(file.Name()) + if err != nil { + return nil, err + } + return file, nil +} + +// initConn initializes the global `unixConnPtr` socket. +// It is automatically called when needed. +func initConn() { + autobind, err := net.ResolveUnixAddr("unixgram", "") + if err != nil { + return + } + + sock, err := net.ListenUnixgram("unixgram", autobind) + if err != nil { + return + } + + atomic.StorePointer(&unixConnPtr, unsafe.Pointer(sock)) +} diff --git a/vendor/github.com/coreos/go-systemd/v22/journal/journal_windows.go b/vendor/github.com/coreos/go-systemd/v22/journal/journal_windows.go new file mode 100644 index 00000000..322e41e7 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/journal/journal_windows.go @@ -0,0 +1,43 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package journal provides write bindings to the local systemd journal. +// It is implemented in pure Go and connects to the journal directly over its +// unix socket. +// +// To read from the journal, see the "sdjournal" package, which wraps the +// sd-journal a C API. +// +// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html +package journal + +import ( + "errors" +) + +func Enabled() bool { + return false +} + +func Send(message string, priority Priority, vars map[string]string) error { + return errors.New("could not initialize socket to journald") +} + +func StderrIsJournalStream() (bool, error) { + return false, nil +} + +func StdoutIsJournalStream() (bool, error) { + return false, nil +} diff --git a/vendor/github.com/coreos/go-systemd/v22/sdjournal/functions.go b/vendor/github.com/coreos/go-systemd/v22/sdjournal/functions.go new file mode 100644 index 00000000..b5c2943c --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/sdjournal/functions.go @@ -0,0 +1,67 @@ +// Copyright 2015 RedHat, Inc. +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sdjournal + +import ( + "sync" + "unsafe" + + "github.com/coreos/go-systemd/v22/internal/dlopen" +) + +var ( + // lazy initialized + libsystemdHandle *dlopen.LibHandle + + libsystemdMutex = &sync.Mutex{} + libsystemdFunctions = map[string]unsafe.Pointer{} + libsystemdNames = []string{ + // systemd < 209 + "libsystemd-journal.so.0", + "libsystemd-journal.so", + + // systemd >= 209 merged libsystemd-journal into libsystemd proper + "libsystemd.so.0", + "libsystemd.so", + } +) + +func getFunction(name string) (unsafe.Pointer, error) { + libsystemdMutex.Lock() + defer libsystemdMutex.Unlock() + + if libsystemdHandle == nil { + h, err := dlopen.GetHandle(libsystemdNames) + if err != nil { + return nil, err + } + + libsystemdHandle = h + } + + f, ok := libsystemdFunctions[name] + if !ok { + var err error + f, err = libsystemdHandle.GetSymbolPointer(name) + if err != nil { + return nil, err + } + + libsystemdFunctions[name] = f + } + + return f, nil +} diff --git a/vendor/github.com/coreos/go-systemd/v22/sdjournal/journal.go b/vendor/github.com/coreos/go-systemd/v22/sdjournal/journal.go new file mode 100644 index 00000000..973b236c --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/sdjournal/journal.go @@ -0,0 +1,1168 @@ +// Copyright 2015 RedHat, Inc. +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package sdjournal provides a low-level Go interface to the +// systemd journal wrapped around the sd-journal C API. +// +// All public read methods map closely to the sd-journal API functions. See the +// sd-journal.h documentation[1] for information about each function. +// +// To write to the journal, see the pure-Go "journal" package +// +// [1] http://www.freedesktop.org/software/systemd/man/sd-journal.html +package sdjournal + +// #include +// #include +// #include +// #include +// +// int +// my_sd_journal_open(void *f, sd_journal **ret, int flags) +// { +// int (*sd_journal_open)(sd_journal **, int); +// +// sd_journal_open = f; +// return sd_journal_open(ret, flags); +// } +// +// int +// my_sd_journal_open_directory(void *f, sd_journal **ret, const char *path, int flags) +// { +// int (*sd_journal_open_directory)(sd_journal **, const char *, int); +// +// sd_journal_open_directory = f; +// return sd_journal_open_directory(ret, path, flags); +// } +// +// int +// my_sd_journal_open_files(void *f, sd_journal **ret, const char **paths, int flags) +// { +// int (*sd_journal_open_files)(sd_journal **, const char **, int); +// +// sd_journal_open_files = f; +// return sd_journal_open_files(ret, paths, flags); +// } +// +// void +// my_sd_journal_close(void *f, sd_journal *j) +// { +// void (*sd_journal_close)(sd_journal *); +// +// sd_journal_close = f; +// sd_journal_close(j); +// } +// +// int +// my_sd_journal_get_usage(void *f, sd_journal *j, uint64_t *bytes) +// { +// int (*sd_journal_get_usage)(sd_journal *, uint64_t *); +// +// sd_journal_get_usage = f; +// return sd_journal_get_usage(j, bytes); +// } +// +// int +// my_sd_journal_add_match(void *f, sd_journal *j, const void *data, size_t size) +// { +// int (*sd_journal_add_match)(sd_journal *, const void *, size_t); +// +// sd_journal_add_match = f; +// return sd_journal_add_match(j, data, size); +// } +// +// int +// my_sd_journal_add_disjunction(void *f, sd_journal *j) +// { +// int (*sd_journal_add_disjunction)(sd_journal *); +// +// sd_journal_add_disjunction = f; +// return sd_journal_add_disjunction(j); +// } +// +// int +// my_sd_journal_add_conjunction(void *f, sd_journal *j) +// { +// int (*sd_journal_add_conjunction)(sd_journal *); +// +// sd_journal_add_conjunction = f; +// return sd_journal_add_conjunction(j); +// } +// +// void +// my_sd_journal_flush_matches(void *f, sd_journal *j) +// { +// void (*sd_journal_flush_matches)(sd_journal *); +// +// sd_journal_flush_matches = f; +// sd_journal_flush_matches(j); +// } +// +// int +// my_sd_journal_next(void *f, sd_journal *j) +// { +// int (*sd_journal_next)(sd_journal *); +// +// sd_journal_next = f; +// return sd_journal_next(j); +// } +// +// int +// my_sd_journal_next_skip(void *f, sd_journal *j, uint64_t skip) +// { +// int (*sd_journal_next_skip)(sd_journal *, uint64_t); +// +// sd_journal_next_skip = f; +// return sd_journal_next_skip(j, skip); +// } +// +// int +// my_sd_journal_previous(void *f, sd_journal *j) +// { +// int (*sd_journal_previous)(sd_journal *); +// +// sd_journal_previous = f; +// return sd_journal_previous(j); +// } +// +// int +// my_sd_journal_previous_skip(void *f, sd_journal *j, uint64_t skip) +// { +// int (*sd_journal_previous_skip)(sd_journal *, uint64_t); +// +// sd_journal_previous_skip = f; +// return sd_journal_previous_skip(j, skip); +// } +// +// int +// my_sd_journal_get_data(void *f, sd_journal *j, const char *field, const void **data, size_t *length) +// { +// int (*sd_journal_get_data)(sd_journal *, const char *, const void **, size_t *); +// +// sd_journal_get_data = f; +// return sd_journal_get_data(j, field, data, length); +// } +// +// int +// my_sd_journal_set_data_threshold(void *f, sd_journal *j, size_t sz) +// { +// int (*sd_journal_set_data_threshold)(sd_journal *, size_t); +// +// sd_journal_set_data_threshold = f; +// return sd_journal_set_data_threshold(j, sz); +// } +// +// int +// my_sd_journal_get_cursor(void *f, sd_journal *j, char **cursor) +// { +// int (*sd_journal_get_cursor)(sd_journal *, char **); +// +// sd_journal_get_cursor = f; +// return sd_journal_get_cursor(j, cursor); +// } +// +// int +// my_sd_journal_test_cursor(void *f, sd_journal *j, const char *cursor) +// { +// int (*sd_journal_test_cursor)(sd_journal *, const char *); +// +// sd_journal_test_cursor = f; +// return sd_journal_test_cursor(j, cursor); +// } +// +// int +// my_sd_journal_get_realtime_usec(void *f, sd_journal *j, uint64_t *usec) +// { +// int (*sd_journal_get_realtime_usec)(sd_journal *, uint64_t *); +// +// sd_journal_get_realtime_usec = f; +// return sd_journal_get_realtime_usec(j, usec); +// } +// +// int +// my_sd_journal_get_monotonic_usec(void *f, sd_journal *j, uint64_t *usec, sd_id128_t *boot_id) +// { +// int (*sd_journal_get_monotonic_usec)(sd_journal *, uint64_t *, sd_id128_t *); +// +// sd_journal_get_monotonic_usec = f; +// return sd_journal_get_monotonic_usec(j, usec, boot_id); +// } +// +// int +// my_sd_journal_seek_head(void *f, sd_journal *j) +// { +// int (*sd_journal_seek_head)(sd_journal *); +// +// sd_journal_seek_head = f; +// return sd_journal_seek_head(j); +// } +// +// int +// my_sd_journal_seek_tail(void *f, sd_journal *j) +// { +// int (*sd_journal_seek_tail)(sd_journal *); +// +// sd_journal_seek_tail = f; +// return sd_journal_seek_tail(j); +// } +// +// +// int +// my_sd_journal_seek_cursor(void *f, sd_journal *j, const char *cursor) +// { +// int (*sd_journal_seek_cursor)(sd_journal *, const char *); +// +// sd_journal_seek_cursor = f; +// return sd_journal_seek_cursor(j, cursor); +// } +// +// int +// my_sd_journal_seek_realtime_usec(void *f, sd_journal *j, uint64_t usec) +// { +// int (*sd_journal_seek_realtime_usec)(sd_journal *, uint64_t); +// +// sd_journal_seek_realtime_usec = f; +// return sd_journal_seek_realtime_usec(j, usec); +// } +// +// int +// my_sd_journal_wait(void *f, sd_journal *j, uint64_t timeout_usec) +// { +// int (*sd_journal_wait)(sd_journal *, uint64_t); +// +// sd_journal_wait = f; +// return sd_journal_wait(j, timeout_usec); +// } +// +// void +// my_sd_journal_restart_data(void *f, sd_journal *j) +// { +// void (*sd_journal_restart_data)(sd_journal *); +// +// sd_journal_restart_data = f; +// sd_journal_restart_data(j); +// } +// +// int +// my_sd_journal_enumerate_data(void *f, sd_journal *j, const void **data, size_t *length) +// { +// int (*sd_journal_enumerate_data)(sd_journal *, const void **, size_t *); +// +// sd_journal_enumerate_data = f; +// return sd_journal_enumerate_data(j, data, length); +// } +// +// int +// my_sd_journal_query_unique(void *f, sd_journal *j, const char *field) +// { +// int(*sd_journal_query_unique)(sd_journal *, const char *); +// +// sd_journal_query_unique = f; +// return sd_journal_query_unique(j, field); +// } +// +// int +// my_sd_journal_enumerate_unique(void *f, sd_journal *j, const void **data, size_t *length) +// { +// int(*sd_journal_enumerate_unique)(sd_journal *, const void **, size_t *); +// +// sd_journal_enumerate_unique = f; +// return sd_journal_enumerate_unique(j, data, length); +// } +// +// void +// my_sd_journal_restart_unique(void *f, sd_journal *j) +// { +// void(*sd_journal_restart_unique)(sd_journal *); +// +// sd_journal_restart_unique = f; +// sd_journal_restart_unique(j); +// } +// +// int +// my_sd_journal_get_catalog(void *f, sd_journal *j, char **ret) +// { +// int(*sd_journal_get_catalog)(sd_journal *, char **); +// +// sd_journal_get_catalog = f; +// return sd_journal_get_catalog(j, ret); +// } +// +// int +// my_sd_id128_get_boot(void *f, sd_id128_t *boot_id) +// { +// int(*sd_id128_get_boot)(sd_id128_t *); +// +// sd_id128_get_boot = f; +// return sd_id128_get_boot(boot_id); +// } +// +// char * +// my_sd_id128_to_string(void *f, sd_id128_t boot_id, char s[SD_ID128_STRING_MAX]) +// { +// char *(*sd_id128_to_string)(sd_id128_t, char *); +// +// sd_id128_to_string = f; +// return sd_id128_to_string(boot_id, s); +// } +// +import "C" + +import ( + "bytes" + "errors" + "fmt" + "strings" + "sync" + "syscall" + "time" + "unsafe" +) + +// Journal entry field strings which correspond to: +// http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html +const ( + // User Journal Fields + SD_JOURNAL_FIELD_MESSAGE = "MESSAGE" + SD_JOURNAL_FIELD_MESSAGE_ID = "MESSAGE_ID" + SD_JOURNAL_FIELD_PRIORITY = "PRIORITY" + SD_JOURNAL_FIELD_CODE_FILE = "CODE_FILE" + SD_JOURNAL_FIELD_CODE_LINE = "CODE_LINE" + SD_JOURNAL_FIELD_CODE_FUNC = "CODE_FUNC" + SD_JOURNAL_FIELD_ERRNO = "ERRNO" + SD_JOURNAL_FIELD_SYSLOG_FACILITY = "SYSLOG_FACILITY" + SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER = "SYSLOG_IDENTIFIER" + SD_JOURNAL_FIELD_SYSLOG_PID = "SYSLOG_PID" + + // Trusted Journal Fields + SD_JOURNAL_FIELD_PID = "_PID" + SD_JOURNAL_FIELD_UID = "_UID" + SD_JOURNAL_FIELD_GID = "_GID" + SD_JOURNAL_FIELD_COMM = "_COMM" + SD_JOURNAL_FIELD_EXE = "_EXE" + SD_JOURNAL_FIELD_CMDLINE = "_CMDLINE" + SD_JOURNAL_FIELD_CAP_EFFECTIVE = "_CAP_EFFECTIVE" + SD_JOURNAL_FIELD_AUDIT_SESSION = "_AUDIT_SESSION" + SD_JOURNAL_FIELD_AUDIT_LOGINUID = "_AUDIT_LOGINUID" + SD_JOURNAL_FIELD_SYSTEMD_CGROUP = "_SYSTEMD_CGROUP" + SD_JOURNAL_FIELD_SYSTEMD_SESSION = "_SYSTEMD_SESSION" + SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT" + SD_JOURNAL_FIELD_SYSTEMD_USER_UNIT = "_SYSTEMD_USER_UNIT" + SD_JOURNAL_FIELD_SYSTEMD_OWNER_UID = "_SYSTEMD_OWNER_UID" + SD_JOURNAL_FIELD_SYSTEMD_SLICE = "_SYSTEMD_SLICE" + SD_JOURNAL_FIELD_SELINUX_CONTEXT = "_SELINUX_CONTEXT" + SD_JOURNAL_FIELD_SOURCE_REALTIME_TIMESTAMP = "_SOURCE_REALTIME_TIMESTAMP" + SD_JOURNAL_FIELD_BOOT_ID = "_BOOT_ID" + SD_JOURNAL_FIELD_MACHINE_ID = "_MACHINE_ID" + SD_JOURNAL_FIELD_HOSTNAME = "_HOSTNAME" + SD_JOURNAL_FIELD_TRANSPORT = "_TRANSPORT" + + // Address Fields + SD_JOURNAL_FIELD_CURSOR = "__CURSOR" + SD_JOURNAL_FIELD_REALTIME_TIMESTAMP = "__REALTIME_TIMESTAMP" + SD_JOURNAL_FIELD_MONOTONIC_TIMESTAMP = "__MONOTONIC_TIMESTAMP" +) + +// Journal event constants +const ( + SD_JOURNAL_NOP = int(C.SD_JOURNAL_NOP) + SD_JOURNAL_APPEND = int(C.SD_JOURNAL_APPEND) + SD_JOURNAL_INVALIDATE = int(C.SD_JOURNAL_INVALIDATE) +) + +const ( + // IndefiniteWait is a sentinel value that can be passed to + // sdjournal.Wait() to signal an indefinite wait for new journal + // events. It is implemented as the maximum value for a time.Duration: + // https://github.com/golang/go/blob/e4dcf5c8c22d98ac9eac7b9b226596229624cb1d/src/time/time.go#L434 + IndefiniteWait time.Duration = 1<<63 - 1 +) + +// ErrNoTestCursor gets returned when using TestCursor function and cursor +// parameter is not the same as the current cursor position. +var ErrNoTestCursor = errors.New("Cursor parameter is not the same as current position") + +// Journal is a Go wrapper of an sd_journal structure. +type Journal struct { + cjournal *C.sd_journal + mu sync.Mutex +} + +// JournalEntry represents all fields of a journal entry plus address fields. +type JournalEntry struct { + Fields map[string]string + Cursor string + RealtimeTimestamp uint64 + MonotonicTimestamp uint64 +} + +// Match is a convenience wrapper to describe filters supplied to AddMatch. +type Match struct { + Field string + Value string +} + +// String returns a string representation of a Match suitable for use with AddMatch. +func (m *Match) String() string { + return m.Field + "=" + m.Value +} + +// NewJournal returns a new Journal instance pointing to the local journal +func NewJournal() (j *Journal, err error) { + j = &Journal{} + + sd_journal_open, err := getFunction("sd_journal_open") + if err != nil { + return nil, err + } + + r := C.my_sd_journal_open(sd_journal_open, &j.cjournal, C.SD_JOURNAL_LOCAL_ONLY) + + if r < 0 { + return nil, fmt.Errorf("failed to open journal: %s", syscall.Errno(-r).Error()) + } + + return j, nil +} + +// NewJournalFromDir returns a new Journal instance pointing to a journal residing +// in a given directory. +func NewJournalFromDir(path string) (j *Journal, err error) { + j = &Journal{} + + sd_journal_open_directory, err := getFunction("sd_journal_open_directory") + if err != nil { + return nil, err + } + + p := C.CString(path) + defer C.free(unsafe.Pointer(p)) + + r := C.my_sd_journal_open_directory(sd_journal_open_directory, &j.cjournal, p, 0) + if r < 0 { + return nil, fmt.Errorf("failed to open journal in directory %q: %s", path, syscall.Errno(-r).Error()) + } + + return j, nil +} + +// NewJournalFromFiles returns a new Journal instance pointing to a journals residing +// in a given files. +func NewJournalFromFiles(paths ...string) (j *Journal, err error) { + j = &Journal{} + + sd_journal_open_files, err := getFunction("sd_journal_open_files") + if err != nil { + return nil, err + } + + // by making the slice 1 elem too long, we guarantee it'll be null-terminated + cPaths := make([]*C.char, len(paths)+1) + for idx, path := range paths { + p := C.CString(path) + cPaths[idx] = p + defer C.free(unsafe.Pointer(p)) + } + + r := C.my_sd_journal_open_files(sd_journal_open_files, &j.cjournal, &cPaths[0], 0) + if r < 0 { + return nil, fmt.Errorf("failed to open journals in paths %q: %s", paths, syscall.Errno(-r).Error()) + } + + return j, nil +} + +// Close closes a journal opened with NewJournal. +func (j *Journal) Close() error { + sd_journal_close, err := getFunction("sd_journal_close") + if err != nil { + return err + } + + j.mu.Lock() + C.my_sd_journal_close(sd_journal_close, j.cjournal) + j.mu.Unlock() + + return nil +} + +// AddMatch adds a match by which to filter the entries of the journal. +func (j *Journal) AddMatch(match string) error { + sd_journal_add_match, err := getFunction("sd_journal_add_match") + if err != nil { + return err + } + + m := C.CString(match) + defer C.free(unsafe.Pointer(m)) + + j.mu.Lock() + r := C.my_sd_journal_add_match(sd_journal_add_match, j.cjournal, unsafe.Pointer(m), C.size_t(len(match))) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to add match: %s", syscall.Errno(-r).Error()) + } + + return nil +} + +// AddDisjunction inserts a logical OR in the match list. +func (j *Journal) AddDisjunction() error { + sd_journal_add_disjunction, err := getFunction("sd_journal_add_disjunction") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_add_disjunction(sd_journal_add_disjunction, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to add a disjunction in the match list: %s", syscall.Errno(-r).Error()) + } + + return nil +} + +// AddConjunction inserts a logical AND in the match list. +func (j *Journal) AddConjunction() error { + sd_journal_add_conjunction, err := getFunction("sd_journal_add_conjunction") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_add_conjunction(sd_journal_add_conjunction, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to add a conjunction in the match list: %s", syscall.Errno(-r).Error()) + } + + return nil +} + +// FlushMatches flushes all matches, disjunctions and conjunctions. +func (j *Journal) FlushMatches() { + sd_journal_flush_matches, err := getFunction("sd_journal_flush_matches") + if err != nil { + return + } + + j.mu.Lock() + C.my_sd_journal_flush_matches(sd_journal_flush_matches, j.cjournal) + j.mu.Unlock() +} + +// Next advances the read pointer into the journal by one entry. +func (j *Journal) Next() (uint64, error) { + sd_journal_next, err := getFunction("sd_journal_next") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_next(sd_journal_next, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to iterate journal: %s", syscall.Errno(-r).Error()) + } + + return uint64(r), nil +} + +// NextSkip advances the read pointer by multiple entries at once, +// as specified by the skip parameter. +func (j *Journal) NextSkip(skip uint64) (uint64, error) { + sd_journal_next_skip, err := getFunction("sd_journal_next_skip") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_next_skip(sd_journal_next_skip, j.cjournal, C.uint64_t(skip)) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to iterate journal: %s", syscall.Errno(-r).Error()) + } + + return uint64(r), nil +} + +// Previous sets the read pointer into the journal back by one entry. +func (j *Journal) Previous() (uint64, error) { + sd_journal_previous, err := getFunction("sd_journal_previous") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_previous(sd_journal_previous, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to iterate journal: %s", syscall.Errno(-r).Error()) + } + + return uint64(r), nil +} + +// PreviousSkip sets back the read pointer by multiple entries at once, +// as specified by the skip parameter. +func (j *Journal) PreviousSkip(skip uint64) (uint64, error) { + sd_journal_previous_skip, err := getFunction("sd_journal_previous_skip") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_previous_skip(sd_journal_previous_skip, j.cjournal, C.uint64_t(skip)) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to iterate journal: %s", syscall.Errno(-r).Error()) + } + + return uint64(r), nil +} + +func (j *Journal) getData(field string) (unsafe.Pointer, C.int, error) { + sd_journal_get_data, err := getFunction("sd_journal_get_data") + if err != nil { + return nil, 0, err + } + + f := C.CString(field) + defer C.free(unsafe.Pointer(f)) + + var d unsafe.Pointer + var l C.size_t + + j.mu.Lock() + r := C.my_sd_journal_get_data(sd_journal_get_data, j.cjournal, f, &d, &l) + j.mu.Unlock() + + if r < 0 { + return nil, 0, fmt.Errorf("failed to read message: %s", syscall.Errno(-r).Error()) + } + + return d, C.int(l), nil +} + +// GetData gets the data object associated with a specific field from the +// the journal entry referenced by the last completed Next/Previous function +// call. To call GetData, you must have first called one of these functions. +func (j *Journal) GetData(field string) (string, error) { + d, l, err := j.getData(field) + if err != nil { + return "", err + } + + return C.GoStringN((*C.char)(d), l), nil +} + +// GetDataValue gets the data object associated with a specific field from the +// journal entry referenced by the last completed Next/Previous function call, +// returning only the value of the object. To call GetDataValue, you must first +// have called one of the Next/Previous functions. +func (j *Journal) GetDataValue(field string) (string, error) { + val, err := j.GetData(field) + if err != nil { + return "", err + } + + return strings.SplitN(val, "=", 2)[1], nil +} + +// GetDataBytes gets the data object associated with a specific field from the +// journal entry referenced by the last completed Next/Previous function call. +// To call GetDataBytes, you must first have called one of these functions. +func (j *Journal) GetDataBytes(field string) ([]byte, error) { + d, l, err := j.getData(field) + if err != nil { + return nil, err + } + + return C.GoBytes(d, l), nil +} + +// GetDataValueBytes gets the data object associated with a specific field from the +// journal entry referenced by the last completed Next/Previous function call, +// returning only the value of the object. To call GetDataValueBytes, you must first +// have called one of the Next/Previous functions. +func (j *Journal) GetDataValueBytes(field string) ([]byte, error) { + val, err := j.GetDataBytes(field) + if err != nil { + return nil, err + } + + return bytes.SplitN(val, []byte("="), 2)[1], nil +} + +// GetEntry returns a full representation of the journal entry referenced by the +// last completed Next/Previous function call, with all key-value pairs of data +// as well as address fields (cursor, realtime timestamp and monotonic timestamp). +// To call GetEntry, you must first have called one of the Next/Previous functions. +func (j *Journal) GetEntry() (*JournalEntry, error) { + sd_journal_get_realtime_usec, err := getFunction("sd_journal_get_realtime_usec") + if err != nil { + return nil, err + } + + sd_journal_get_monotonic_usec, err := getFunction("sd_journal_get_monotonic_usec") + if err != nil { + return nil, err + } + + sd_journal_get_cursor, err := getFunction("sd_journal_get_cursor") + if err != nil { + return nil, err + } + + sd_journal_restart_data, err := getFunction("sd_journal_restart_data") + if err != nil { + return nil, err + } + + sd_journal_enumerate_data, err := getFunction("sd_journal_enumerate_data") + if err != nil { + return nil, err + } + + j.mu.Lock() + defer j.mu.Unlock() + + var r C.int + entry := &JournalEntry{Fields: make(map[string]string)} + + var realtimeUsec C.uint64_t + r = C.my_sd_journal_get_realtime_usec(sd_journal_get_realtime_usec, j.cjournal, &realtimeUsec) + if r < 0 { + return nil, fmt.Errorf("failed to get realtime timestamp: %s", syscall.Errno(-r).Error()) + } + + entry.RealtimeTimestamp = uint64(realtimeUsec) + + var monotonicUsec C.uint64_t + var boot_id C.sd_id128_t + + r = C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &monotonicUsec, &boot_id) + if r < 0 { + return nil, fmt.Errorf("failed to get monotonic timestamp: %s", syscall.Errno(-r).Error()) + } + + entry.MonotonicTimestamp = uint64(monotonicUsec) + + var c *C.char + // since the pointer is mutated by sd_journal_get_cursor, need to wait + // until after the call to free the memory + r = C.my_sd_journal_get_cursor(sd_journal_get_cursor, j.cjournal, &c) + defer C.free(unsafe.Pointer(c)) + if r < 0 { + return nil, fmt.Errorf("failed to get cursor: %s", syscall.Errno(-r).Error()) + } + + entry.Cursor = C.GoString(c) + + // Implements the JOURNAL_FOREACH_DATA_RETVAL macro from journal-internal.h + var d unsafe.Pointer + var l C.size_t + C.my_sd_journal_restart_data(sd_journal_restart_data, j.cjournal) + for { + r = C.my_sd_journal_enumerate_data(sd_journal_enumerate_data, j.cjournal, &d, &l) + if r == 0 { + break + } + + if r < 0 { + return nil, fmt.Errorf("failed to read message field: %s", syscall.Errno(-r).Error()) + } + + msg := C.GoStringN((*C.char)(d), C.int(l)) + kv := strings.SplitN(msg, "=", 2) + if len(kv) < 2 { + return nil, fmt.Errorf("failed to parse field") + } + + entry.Fields[kv[0]] = kv[1] + } + + return entry, nil +} + +// SetDataThreshold sets the data field size threshold for data returned by +// GetData. To retrieve the complete data fields this threshold should be +// turned off by setting it to 0, so that the library always returns the +// complete data objects. +func (j *Journal) SetDataThreshold(threshold uint64) error { + sd_journal_set_data_threshold, err := getFunction("sd_journal_set_data_threshold") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_set_data_threshold(sd_journal_set_data_threshold, j.cjournal, C.size_t(threshold)) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to set data threshold: %s", syscall.Errno(-r).Error()) + } + + return nil +} + +// GetRealtimeUsec gets the realtime (wallclock) timestamp of the journal +// entry referenced by the last completed Next/Previous function call. To +// call GetRealtimeUsec, you must first have called one of the Next/Previous +// functions. +func (j *Journal) GetRealtimeUsec() (uint64, error) { + var usec C.uint64_t + + sd_journal_get_realtime_usec, err := getFunction("sd_journal_get_realtime_usec") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_get_realtime_usec(sd_journal_get_realtime_usec, j.cjournal, &usec) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to get realtime timestamp: %s", syscall.Errno(-r).Error()) + } + + return uint64(usec), nil +} + +// GetMonotonicUsec gets the monotonic timestamp of the journal entry +// referenced by the last completed Next/Previous function call. To call +// GetMonotonicUsec, you must first have called one of the Next/Previous +// functions. +func (j *Journal) GetMonotonicUsec() (uint64, error) { + var usec C.uint64_t + var boot_id C.sd_id128_t + + sd_journal_get_monotonic_usec, err := getFunction("sd_journal_get_monotonic_usec") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &usec, &boot_id) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to get monotonic timestamp: %s", syscall.Errno(-r).Error()) + } + + return uint64(usec), nil +} + +// GetCursor gets the cursor of the last journal entry reeferenced by the +// last completed Next/Previous function call. To call GetCursor, you must +// first have called one of the Next/Previous functions. +func (j *Journal) GetCursor() (string, error) { + sd_journal_get_cursor, err := getFunction("sd_journal_get_cursor") + if err != nil { + return "", err + } + + var d *C.char + // since the pointer is mutated by sd_journal_get_cursor, need to wait + // until after the call to free the memory + + j.mu.Lock() + r := C.my_sd_journal_get_cursor(sd_journal_get_cursor, j.cjournal, &d) + j.mu.Unlock() + defer C.free(unsafe.Pointer(d)) + + if r < 0 { + return "", fmt.Errorf("failed to get cursor: %s", syscall.Errno(-r).Error()) + } + + cursor := C.GoString(d) + + return cursor, nil +} + +// TestCursor checks whether the current position in the journal matches the +// specified cursor +func (j *Journal) TestCursor(cursor string) error { + sd_journal_test_cursor, err := getFunction("sd_journal_test_cursor") + if err != nil { + return err + } + + c := C.CString(cursor) + defer C.free(unsafe.Pointer(c)) + + j.mu.Lock() + r := C.my_sd_journal_test_cursor(sd_journal_test_cursor, j.cjournal, c) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to test to cursor %q: %s", cursor, syscall.Errno(-r).Error()) + } else if r == 0 { + return ErrNoTestCursor + } + + return nil +} + +// SeekHead seeks to the beginning of the journal, i.e. the oldest available +// entry. This call must be followed by a call to Next before any call to +// Get* will return data about the first element. +func (j *Journal) SeekHead() error { + sd_journal_seek_head, err := getFunction("sd_journal_seek_head") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_seek_head(sd_journal_seek_head, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to seek to head of journal: %s", syscall.Errno(-r).Error()) + } + + return nil +} + +// SeekTail may be used to seek to the end of the journal, i.e. the most recent +// available entry. This call must be followed by a call to Previous before any +// call to Get* will return data about the last element. +func (j *Journal) SeekTail() error { + sd_journal_seek_tail, err := getFunction("sd_journal_seek_tail") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_seek_tail(sd_journal_seek_tail, j.cjournal) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to seek to tail of journal: %s", syscall.Errno(-r).Error()) + } + + return nil +} + +// SeekRealtimeUsec seeks to the entry with the specified realtime (wallclock) +// timestamp, i.e. CLOCK_REALTIME. This call must be followed by a call to +// Next/Previous before any call to Get* will return data about the sought entry. +func (j *Journal) SeekRealtimeUsec(usec uint64) error { + sd_journal_seek_realtime_usec, err := getFunction("sd_journal_seek_realtime_usec") + if err != nil { + return err + } + + j.mu.Lock() + r := C.my_sd_journal_seek_realtime_usec(sd_journal_seek_realtime_usec, j.cjournal, C.uint64_t(usec)) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to seek to %d: %s", usec, syscall.Errno(-r).Error()) + } + + return nil +} + +// SeekCursor seeks to a concrete journal cursor. This call must be +// followed by a call to Next/Previous before any call to Get* will return +// data about the sought entry. +func (j *Journal) SeekCursor(cursor string) error { + sd_journal_seek_cursor, err := getFunction("sd_journal_seek_cursor") + if err != nil { + return err + } + + c := C.CString(cursor) + defer C.free(unsafe.Pointer(c)) + + j.mu.Lock() + r := C.my_sd_journal_seek_cursor(sd_journal_seek_cursor, j.cjournal, c) + j.mu.Unlock() + + if r < 0 { + return fmt.Errorf("failed to seek to cursor %q: %s", cursor, syscall.Errno(-r).Error()) + } + + return nil +} + +// Wait will synchronously wait until the journal gets changed. The maximum time +// this call sleeps may be controlled with the timeout parameter. If +// sdjournal.IndefiniteWait is passed as the timeout parameter, Wait will +// wait indefinitely for a journal change. +func (j *Journal) Wait(timeout time.Duration) int { + var to uint64 + + sd_journal_wait, err := getFunction("sd_journal_wait") + if err != nil { + return -1 + } + + if timeout == IndefiniteWait { + // sd_journal_wait(3) calls for a (uint64_t) -1 to be passed to signify + // indefinite wait, but using a -1 overflows our C.uint64_t, so we use an + // equivalent hex value. + to = 0xffffffffffffffff + } else { + to = uint64(timeout / time.Microsecond) + } + j.mu.Lock() + r := C.my_sd_journal_wait(sd_journal_wait, j.cjournal, C.uint64_t(to)) + j.mu.Unlock() + + return int(r) +} + +// GetUsage returns the journal disk space usage, in bytes. +func (j *Journal) GetUsage() (uint64, error) { + var out C.uint64_t + + sd_journal_get_usage, err := getFunction("sd_journal_get_usage") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_get_usage(sd_journal_get_usage, j.cjournal, &out) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to get journal disk space usage: %s", syscall.Errno(-r).Error()) + } + + return uint64(out), nil +} + +// GetUniqueValues returns all unique values for a given field. +func (j *Journal) GetUniqueValues(field string) ([]string, error) { + var result []string + + sd_journal_query_unique, err := getFunction("sd_journal_query_unique") + if err != nil { + return nil, err + } + + sd_journal_enumerate_unique, err := getFunction("sd_journal_enumerate_unique") + if err != nil { + return nil, err + } + + sd_journal_restart_unique, err := getFunction("sd_journal_restart_unique") + if err != nil { + return nil, err + } + + j.mu.Lock() + defer j.mu.Unlock() + + f := C.CString(field) + defer C.free(unsafe.Pointer(f)) + + r := C.my_sd_journal_query_unique(sd_journal_query_unique, j.cjournal, f) + + if r < 0 { + return nil, fmt.Errorf("failed to query journal: %s", syscall.Errno(-r).Error()) + } + + // Implements the SD_JOURNAL_FOREACH_UNIQUE macro from sd-journal.h + var d unsafe.Pointer + var l C.size_t + C.my_sd_journal_restart_unique(sd_journal_restart_unique, j.cjournal) + for { + r = C.my_sd_journal_enumerate_unique(sd_journal_enumerate_unique, j.cjournal, &d, &l) + if r == 0 { + break + } + + if r < 0 { + return nil, fmt.Errorf("failed to read message field: %s", syscall.Errno(-r).Error()) + } + + msg := C.GoStringN((*C.char)(d), C.int(l)) + kv := strings.SplitN(msg, "=", 2) + if len(kv) < 2 { + return nil, fmt.Errorf("failed to parse field") + } + + result = append(result, kv[1]) + } + + return result, nil +} + +// GetCatalog retrieves a message catalog entry for the journal entry referenced +// by the last completed Next/Previous function call. To call GetCatalog, you +// must first have called one of these functions. +func (j *Journal) GetCatalog() (string, error) { + sd_journal_get_catalog, err := getFunction("sd_journal_get_catalog") + if err != nil { + return "", err + } + + var c *C.char + + j.mu.Lock() + r := C.my_sd_journal_get_catalog(sd_journal_get_catalog, j.cjournal, &c) + j.mu.Unlock() + defer C.free(unsafe.Pointer(c)) + + if r < 0 { + return "", fmt.Errorf("failed to retrieve catalog entry for current journal entry: %s", syscall.Errno(-r).Error()) + } + + catalog := C.GoString(c) + + return catalog, nil +} + +// GetBootID get systemd boot id +func (j *Journal) GetBootID() (string, error) { + sd_id128_get_boot, err := getFunction("sd_id128_get_boot") + if err != nil { + return "", err + } + + var boot_id C.sd_id128_t + r := C.my_sd_id128_get_boot(sd_id128_get_boot, &boot_id) + if r < 0 { + return "", fmt.Errorf("failed to get boot id: %s", syscall.Errno(-r).Error()) + } + + sd_id128_to_string, err := getFunction("sd_id128_to_string") + if err != nil { + return "", err + } + + id128StringMax := C.size_t(C.SD_ID128_STRING_MAX) + c := (*C.char)(C.malloc(id128StringMax)) + defer C.free(unsafe.Pointer(c)) + C.my_sd_id128_to_string(sd_id128_to_string, boot_id, c) + + bootID := C.GoString(c) + if len(bootID) <= 0 { + return "", fmt.Errorf("get boot id %s is not valid", bootID) + } + + return bootID, nil +} diff --git a/vendor/github.com/coreos/go-systemd/v22/sdjournal/read.go b/vendor/github.com/coreos/go-systemd/v22/sdjournal/read.go new file mode 100644 index 00000000..b543398b --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/sdjournal/read.go @@ -0,0 +1,268 @@ +// Copyright 2015 RedHat, Inc. +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sdjournal + +import ( + "errors" + "fmt" + "io" + "log" + "strings" + "sync" + "time" +) + +// ErrExpired gets returned when the Follow function runs into the +// specified timeout. +var ErrExpired = errors.New("Timeout expired") + +// JournalReaderConfig represents options to drive the behavior of a JournalReader. +type JournalReaderConfig struct { + // The Since, NumFromTail and Cursor options are mutually exclusive and + // determine where the reading begins within the journal. The order in which + // options are written is exactly the order of precedence. + Since time.Duration // start relative to a Duration from now + NumFromTail uint64 // start relative to the tail + Cursor string // start relative to the cursor + + // Show only journal entries whose fields match the supplied values. If + // the array is empty, entries will not be filtered. + Matches []Match + + // If not empty, the journal instance will point to a journal residing + // in this directory. The supplied path may be relative or absolute. + Path string + + // If not nil, Formatter will be used to translate the resulting entries + // into strings. If not set, the default format (timestamp and message field) + // will be used. If Formatter returns an error, Read will stop and return the error. + Formatter func(entry *JournalEntry) (string, error) +} + +// JournalReader is an io.ReadCloser which provides a simple interface for iterating through the +// systemd journal. A JournalReader is not safe for concurrent use by multiple goroutines. +type JournalReader struct { + journal *Journal + msgReader *strings.Reader + formatter func(entry *JournalEntry) (string, error) +} + +// NewJournalReader creates a new JournalReader with configuration options that are similar to the +// systemd journalctl tool's iteration and filtering features. +func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) { + // use simpleMessageFormatter as default formatter. + if config.Formatter == nil { + config.Formatter = simpleMessageFormatter + } + + r := &JournalReader{ + formatter: config.Formatter, + } + + // Open the journal + var err error + if config.Path != "" { + r.journal, err = NewJournalFromDir(config.Path) + } else { + r.journal, err = NewJournal() + } + if err != nil { + return nil, err + } + + // Add any supplied matches + for _, m := range config.Matches { + if err = r.journal.AddMatch(m.String()); err != nil { + return nil, err + } + } + + // Set the start position based on options + if config.Since != 0 { + // Start based on a relative time + start := time.Now().Add(config.Since) + if err := r.journal.SeekRealtimeUsec(uint64(start.UnixNano() / 1000)); err != nil { + return nil, err + } + } else if config.NumFromTail != 0 { + // Start based on a number of lines before the tail + if err := r.journal.SeekTail(); err != nil { + return nil, err + } + + // Move the read pointer into position near the tail. Go one further than + // the option so that the initial cursor advancement positions us at the + // correct starting point. + skip, err := r.journal.PreviousSkip(config.NumFromTail + 1) + if err != nil { + return nil, err + } + // If we skipped fewer lines than expected, we have reached journal start. + // Thus, we seek to head so that next invocation can read the first line. + if skip != config.NumFromTail+1 { + if err := r.journal.SeekHead(); err != nil { + return nil, err + } + } + } else if config.Cursor != "" { + // Start based on a custom cursor + if err := r.journal.SeekCursor(config.Cursor); err != nil { + return nil, err + } + } + + return r, nil +} + +// Read reads entries from the journal. Read follows the Reader interface so +// it must be able to read a specific amount of bytes. Journald on the other +// hand only allows us to read full entries of arbitrary size (without byte +// granularity). JournalReader is therefore internally buffering entries that +// don't fit in the read buffer. Callers should keep calling until 0 and/or an +// error is returned. +func (r *JournalReader) Read(b []byte) (int, error) { + if r.msgReader == nil { + // Advance the journal cursor. It has to be called at least one time + // before reading + c, err := r.journal.Next() + // An unexpected error + if err != nil { + return 0, err + } + + // EOF detection + if c == 0 { + return 0, io.EOF + } + + entry, err := r.journal.GetEntry() + if err != nil { + return 0, err + } + + // Build a message + msg, err := r.formatter(entry) + if err != nil { + return 0, err + } + r.msgReader = strings.NewReader(msg) + } + + // Copy and return the message + sz, err := r.msgReader.Read(b) + if err == io.EOF { + // The current entry has been fully read. Don't propagate this + // EOF, so the next entry can be read at the next Read() + // iteration. + r.msgReader = nil + return sz, nil + } + if err != nil { + return sz, err + } + if r.msgReader.Len() == 0 { + r.msgReader = nil + } + + return sz, nil +} + +// Close closes the JournalReader's handle to the journal. +func (r *JournalReader) Close() error { + return r.journal.Close() +} + +// Rewind attempts to rewind the JournalReader to the first entry. +func (r *JournalReader) Rewind() error { + r.msgReader = nil + return r.journal.SeekHead() +} + +// Follow synchronously follows the JournalReader, writing each new journal entry to writer. The +// follow will continue until a single time.Time is received on the until channel. +func (r *JournalReader) Follow(until <-chan time.Time, writer io.Writer) error { + // Process journal entries and events. Entries are flushed until the tail or + // timeout is reached, and then we wait for new events or the timeout. + msg := make([]byte, 64*1<<(10)) + waitCh := make(chan int, 1) + var waitGroup sync.WaitGroup + defer waitGroup.Wait() + +process: + for { + c, err := r.Read(msg) + if err != nil && err != io.EOF { + return err + } + + select { + case <-until: + return ErrExpired + default: + } + if c > 0 { + if _, err = writer.Write(msg[:c]); err != nil { + return err + } + continue process + } + + // We're at the tail, so wait for new events or time out. + // Holds journal events to process. Tightly bounded for now unless there's a + // reason to unblock the journal watch routine more quickly. + for { + waitGroup.Add(1) + go func() { + status := r.journal.Wait(100 * time.Millisecond) + waitCh <- status + waitGroup.Done() + }() + + select { + case <-until: + return ErrExpired + case e := <-waitCh: + switch e { + case SD_JOURNAL_NOP: + // the journal did not change since the last invocation + case SD_JOURNAL_APPEND, SD_JOURNAL_INVALIDATE: + continue process + default: + if e < 0 { + return fmt.Errorf("received error event: %d", e) + } + + log.Printf("received unknown event: %d\n", e) + } + } + } + } +} + +// simpleMessageFormatter is the default formatter. +// It returns a string representing the current journal entry in a simple format which +// includes the entry timestamp and MESSAGE field. +func simpleMessageFormatter(entry *JournalEntry) (string, error) { + msg, ok := entry.Fields["MESSAGE"] + if !ok { + return "", fmt.Errorf("no MESSAGE field present in journal entry") + } + + usec := entry.RealtimeTimestamp + timestamp := time.Unix(0, int64(usec)*int64(time.Microsecond)) + + return fmt.Sprintf("%s %s\n", timestamp, msg), nil +} diff --git a/vendor/github.com/cyberphone/json-canonicalization/LICENSE b/vendor/github.com/cyberphone/json-canonicalization/LICENSE new file mode 100644 index 00000000..59121159 --- /dev/null +++ b/vendor/github.com/cyberphone/json-canonicalization/LICENSE @@ -0,0 +1,13 @@ + Copyright 2018 Anders Rundgren + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer/es6numfmt.go b/vendor/github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer/es6numfmt.go new file mode 100644 index 00000000..92574a3f --- /dev/null +++ b/vendor/github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer/es6numfmt.go @@ -0,0 +1,71 @@ +// +// Copyright 2006-2019 WebPKI.org (http://webpki.org). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This package converts numbers in IEEE-754 double precision into the +// format specified for JSON in EcmaScript Version 6 and forward. +// The core application for this is canonicalization: +// https://tools.ietf.org/html/draft-rundgren-json-canonicalization-scheme-02 + +package jsoncanonicalizer + +import ( + "errors" + "math" + "strconv" + "strings" +) + +const invalidPattern uint64 = 0x7ff0000000000000 + +func NumberToJSON(ieeeF64 float64) (res string, err error) { + ieeeU64 := math.Float64bits(ieeeF64) + + // Special case: NaN and Infinity are invalid in JSON + if (ieeeU64 & invalidPattern) == invalidPattern { + return "null", errors.New("Invalid JSON number: " + strconv.FormatUint(ieeeU64, 16)) + } + + // Special case: eliminate "-0" as mandated by the ES6-JSON/JCS specifications + if ieeeF64 == 0 { // Right, this line takes both -0 and 0 + return "0", nil + } + + // Deal with the sign separately + var sign string = "" + if ieeeF64 < 0 { + ieeeF64 =-ieeeF64 + sign = "-" + } + + // ES6 has a unique "g" format + var format byte = 'e' + if ieeeF64 < 1e+21 && ieeeF64 >= 1e-6 { + format = 'f' + } + + // The following should do the trick: + es6Formatted := strconv.FormatFloat(ieeeF64, format, -1, 64) + + // Minor cleanup + exponent := strings.IndexByte(es6Formatted, 'e') + if exponent > 0 { + // Go outputs "1e+09" which must be rewritten as "1e+9" + if es6Formatted[exponent + 2] == '0' { + es6Formatted = es6Formatted[:exponent + 2] + es6Formatted[exponent + 3:] + } + } + return sign + es6Formatted, nil +} diff --git a/vendor/github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer/jsoncanonicalizer.go b/vendor/github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer/jsoncanonicalizer.go new file mode 100644 index 00000000..661f4105 --- /dev/null +++ b/vendor/github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer/jsoncanonicalizer.go @@ -0,0 +1,378 @@ +// +// Copyright 2006-2019 WebPKI.org (http://webpki.org). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This package transforms JSON data in UTF-8 according to: +// https://tools.ietf.org/html/draft-rundgren-json-canonicalization-scheme-02 + +package jsoncanonicalizer + +import ( + "errors" + "container/list" + "fmt" + "strconv" + "strings" + "unicode/utf16" +) + +type nameValueType struct { + name string + sortKey []uint16 + value string +} + +// JSON standard escapes (modulo \u) +var asciiEscapes = []byte{'\\', '"', 'b', 'f', 'n', 'r', 't'} +var binaryEscapes = []byte{'\\', '"', '\b', '\f', '\n', '\r', '\t'} + +// JSON literals +var literals = []string{"true", "false", "null"} + +func Transform(jsonData []byte) (result []byte, e error) { + + // JSON data MUST be UTF-8 encoded + var jsonDataLength int = len(jsonData) + + // Current pointer in jsonData + var index int = 0 + + // "Forward" declarations are needed for closures referring each other + var parseElement func() string + var parseSimpleType func() string + var parseQuotedString func() string + var parseObject func() string + var parseArray func() string + + var globalError error = nil + + checkError := func(e error) { + // We only honor the first reported error + if globalError == nil { + globalError = e + } + } + + setError := func(msg string) { + checkError(errors.New(msg)) + } + + isWhiteSpace := func(c byte) bool { + return c == 0x20 || c == 0x0a || c == 0x0d || c == 0x09 + } + + nextChar := func() byte { + if index < jsonDataLength { + c := jsonData[index] + if c > 0x7f { + setError("Unexpected non-ASCII character") + } + index++ + return c + } + setError("Unexpected EOF reached") + return '"' + } + + scan := func() byte { + for { + c := nextChar() + if isWhiteSpace(c) { + continue; + } + return c + } + } + + scanFor := func(expected byte) { + c := scan() + if c != expected { + setError("Expected '" + string(expected) + "' but got '" + string(c) + "'") + } + } + + getUEscape := func() rune { + start := index + nextChar() + nextChar() + nextChar() + nextChar() + if globalError != nil { + return 0 + } + u16, err := strconv.ParseUint(string(jsonData[start:index]), 16, 64) + checkError(err) + return rune(u16) + } + + testNextNonWhiteSpaceChar := func() byte { + save := index + c := scan() + index = save + return c + } + + decorateString := func(rawUTF8 string) string { + var quotedString strings.Builder + quotedString.WriteByte('"') + CoreLoop: + for _, c := range []byte(rawUTF8) { + // Is this within the JSON standard escapes? + for i, esc := range binaryEscapes { + if esc == c { + quotedString.WriteByte('\\') + quotedString.WriteByte(asciiEscapes[i]) + continue CoreLoop + } + } + if c < 0x20 { + // Other ASCII control characters must be escaped with \uhhhh + quotedString.WriteString(fmt.Sprintf("\\u%04x", c)) + } else { + quotedString.WriteByte(c) + } + } + quotedString.WriteByte('"') + return quotedString.String() + } + + parseQuotedString = func() string { + var rawString strings.Builder + CoreLoop: + for globalError == nil { + var c byte + if index < jsonDataLength { + c = jsonData[index] + index++ + } else { + nextChar() + break + } + if (c == '"') { + break; + } + if c < ' ' { + setError("Unterminated string literal") + } else if c == '\\' { + // Escape sequence + c = nextChar() + if c == 'u' { + // The \u escape + firstUTF16 := getUEscape() + if utf16.IsSurrogate(firstUTF16) { + // If the first UTF-16 code unit has a certain value there must be + // another succeeding UTF-16 code unit as well + if nextChar() != '\\' || nextChar() != 'u' { + setError("Missing surrogate") + } else { + // Output the UTF-32 code point as UTF-8 + rawString.WriteRune(utf16.DecodeRune(firstUTF16, getUEscape())) + } + } else { + // Single UTF-16 code identical to UTF-32. Output as UTF-8 + rawString.WriteRune(firstUTF16) + } + } else if c == '/' { + // Benign but useless escape + rawString.WriteByte('/') + } else { + // The JSON standard escapes + for i, esc := range asciiEscapes { + if esc == c { + rawString.WriteByte(binaryEscapes[i]) + continue CoreLoop + } + } + setError("Unexpected escape: \\" + string(c)) + } + } else { + // Just an ordinary ASCII character alternatively a UTF-8 byte + // outside of ASCII. + // Note that properly formatted UTF-8 never clashes with ASCII + // making byte per byte search for ASCII break characters work + // as expected. + rawString.WriteByte(c) + } + } + return rawString.String() + } + + parseSimpleType = func() string { + var token strings.Builder + index-- + for globalError == nil { + c := testNextNonWhiteSpaceChar() + if c == ',' || c == ']' || c == '}' { + break; + } + c = nextChar() + if isWhiteSpace(c) { + break + } + token.WriteByte(c) + } + if token.Len() == 0 { + setError("Missing argument") + } + value := token.String() + // Is it a JSON literal? + for _, literal := range literals { + if literal == value { + return literal + } + } + // Apparently not so we assume that it is a I-JSON number + ieeeF64, err := strconv.ParseFloat(value, 64) + checkError(err) + value, err = NumberToJSON(ieeeF64) + checkError(err) + return value + } + + parseElement = func() string { + switch scan() { + case '{': + return parseObject() + case '"': + return decorateString(parseQuotedString()) + case '[': + return parseArray() + default: + return parseSimpleType() + } + } + + parseArray = func() string { + var arrayData strings.Builder + arrayData.WriteByte('[') + var next bool = false + for globalError == nil && testNextNonWhiteSpaceChar() != ']' { + if next { + scanFor(',') + arrayData.WriteByte(',') + } else { + next = true + } + arrayData.WriteString(parseElement()) + } + scan() + arrayData.WriteByte(']') + return arrayData.String() + } + + lexicographicallyPrecedes := func(sortKey []uint16, e *list.Element) bool { + // Find the minimum length of the sortKeys + oldSortKey := e.Value.(nameValueType).sortKey + minLength := len(oldSortKey) + if minLength > len(sortKey) { + minLength = len(sortKey) + } + for q := 0; q < minLength; q++ { + diff := int(sortKey[q]) - int(oldSortKey[q]) + if diff < 0 { + // Smaller => Precedes + return true + } else if diff > 0 { + // Bigger => No match + return false + } + // Still equal => Continue + } + // The sortKeys compared equal up to minLength + if len(sortKey) < len(oldSortKey) { + // Shorter => Precedes + return true + } + if len(sortKey) == len(oldSortKey) { + setError("Duplicate key: " + e.Value.(nameValueType).name) + } + // Longer => No match + return false + } + + parseObject = func() string { + nameValueList := list.New() + var next bool = false + CoreLoop: + for globalError == nil && testNextNonWhiteSpaceChar() != '}' { + if next { + scanFor(',') + } + next = true + scanFor('"') + rawUTF8 := parseQuotedString() + if globalError != nil { + break; + } + // Sort keys on UTF-16 code units + // Since UTF-8 doesn't have endianess this is just a value transformation + // In the Go case the transformation is UTF-8 => UTF-32 => UTF-16 + sortKey := utf16.Encode([]rune(rawUTF8)) + scanFor(':') + nameValue := nameValueType{rawUTF8, sortKey, parseElement()} + for e := nameValueList.Front(); e != nil; e = e.Next() { + // Check if the key is smaller than a previous key + if lexicographicallyPrecedes(sortKey, e) { + // Precedes => Insert before and exit sorting + nameValueList.InsertBefore(nameValue, e) + continue CoreLoop + } + // Continue searching for a possibly succeeding sortKey + // (which is straightforward since the list is ordered) + } + // The sortKey is either the first or is succeeding all previous sortKeys + nameValueList.PushBack(nameValue) + } + // Scan away '}' + scan() + // Now everything is sorted so we can properly serialize the object + var objectData strings.Builder + objectData.WriteByte('{') + next = false + for e := nameValueList.Front(); e != nil; e = e.Next() { + if next { + objectData.WriteByte(',') + } + next = true + nameValue := e.Value.(nameValueType) + objectData.WriteString(decorateString(nameValue.name)) + objectData.WriteByte(':') + objectData.WriteString(nameValue.value) + } + objectData.WriteByte('}') + return objectData.String() + } + + ///////////////////////////////////////////////// + // This is where Transform actually begins... // + ///////////////////////////////////////////////// + var transformed string + + if testNextNonWhiteSpaceChar() == '[' { + scan() + transformed = parseArray() + } else { + scanFor('{') + transformed = parseObject() + } + for index < jsonDataLength { + if !isWhiteSpace(jsonData[index]) { + setError("Improperly terminated JSON object") + break; + } + index++ + } + return []byte(transformed), globalError +} \ No newline at end of file diff --git a/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml b/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml new file mode 100644 index 00000000..e965034e --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/.golangci.yml @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: MPL-2.0 + +# Copyright (C) 2025 Aleksa Sarai +# Copyright (C) 2025 SUSE LLC +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +version: "2" + +linters: + enable: + - asasalint + - asciicheck + - containedctx + - contextcheck + - errcheck + - errorlint + - exhaustive + - forcetypeassert + - godot + - goprintffuncname + - govet + - importas + - ineffassign + - makezero + - misspell + - musttag + - nilerr + - nilnesserr + - nilnil + - noctx + - prealloc + - revive + - staticcheck + - testifylint + - unconvert + - unparam + - unused + - usetesting + settings: + govet: + enable: + - nilness + testifylint: + enable-all: true + +formatters: + enable: + - gofumpt + - goimports + settings: + goimports: + local-prefixes: + - github.com/cyphar/filepath-securejoin diff --git a/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md new file mode 100644 index 00000000..1fc7eeb0 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md @@ -0,0 +1,418 @@ +# Changelog # +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased 0.5.z] ## + +## [0.5.2] - 2025-11-19 ## + +> "Will you walk into my parlour?" said a spider to a fly. + +### Fixed ### +- Our logic for deciding whether to use `openat2(2)` or fallback to an `O_PATH` + resolver would cache the result to avoid doing needless test runs of + `openat2(2)`. However, this causes issues when `pathrs-lite` is being used by + a program that applies new seccomp-bpf filters onto itself -- if the filter + denies `openat2(2)` then we would return that error rather than falling back + to the `O_PATH` resolver. To resolve this issue, we no longer cache the + result if `openat2(2)` was successful, only if there was an error. +- A file descriptor leak in our `openat2` wrapper (when doing the necessary + `dup` for `RESOLVE_IN_ROOT`) has been removed. + +## [0.5.1] - 2025-10-31 ## + +> Spooky scary skeletons send shivers down your spine! + +### Changed ### +- `openat2` can return `-EAGAIN` if it detects a possible attack in certain + scenarios (namely if there was a rename or mount while walking a path with a + `..` component). While this is necessary to avoid a denial-of-service in the + kernel, it does require retry loops in userspace. + + In previous versions, `pathrs-lite` would retry `openat2` 32 times before + returning an error, but we've received user reports that this limit can be + hit on systems with very heavy load. In some synthetic benchmarks (testing + the worst-case of an attacker doing renames in a tight loop on every core of + a 16-core machine) we managed to get a ~3% failure rate in runc. We have + improved this situation in two ways: + + * We have now increased this limit to 128, which should be good enough for + most use-cases without becoming a denial-of-service vector (the number of + syscalls called by the `O_PATH` resolver in a typical case is within the + same ballpark). The same benchmarks show a failure rate of ~0.12% which + (while not zero) is probably sufficient for most users. + + * In addition, we now return a `unix.EAGAIN` error that is bubbled up and can + be detected by callers. This means that callers with stricter requirements + to avoid spurious errors can choose to do their own infinite `EAGAIN` retry + loop (though we would strongly recommend users use time-based deadlines in + such retry loops to avoid potentially unbounded denials-of-service). + +## [0.5.0] - 2025-09-26 ## + +> Let the past die. Kill it if you have to. + +> **NOTE**: With this release, some parts of +> `github.com/cyphar/filepath-securejoin` are now licensed under the Mozilla +> Public License (version 2). Please see [COPYING.md][] as well as the the +> license header in each file for more details. + +[COPYING.md]: ./COPYING.md + +### Breaking ### +- The new API introduced in the [0.3.0][] release has been moved to a new + subpackage called `pathrs-lite`. This was primarily done to better indicate + the split between the new and old APIs, as well as indicate to users the + purpose of this subpackage (it is a less complete version of [libpathrs][]). + + We have added some wrappers to the top-level package to ease the transition, + but those are deprecated and will be removed in the next minor release of + filepath-securejoin. Users should update their import paths. + + This new subpackage has also been relicensed under the Mozilla Public License + (version 2), please see [COPYING.md][] for more details. + +### Added ### +- Most of the key bits the safe `procfs` API have now been exported and are + available in `github.com/cyphar/filepath-securejoin/pathrs-lite/procfs`. At + the moment this primarily consists of a new `procfs.Handle` API: + + * `OpenProcRoot` returns a new handle to `/proc`, endeavouring to make it + safe if possible (`subset=pid` to protect against mistaken write attacks + and leaks, as well as using `fsopen(2)` to avoid racing mount attacks). + + `OpenUnsafeProcRoot` returns a handle without attempting to create one + with `subset=pid`, which makes it more dangerous to leak. Most users + should use `OpenProcRoot` (even if you need to use `ProcRoot` as the base + of an operation, as filepath-securejoin will internally open a handle when + necessary). + + * The `(*procfs.Handle).Open*` family of methods lets you get a safe + `O_PATH` handle to subpaths within `/proc` for certain subpaths. + + For `OpenThreadSelf`, the returned `ProcThreadSelfCloser` needs to be + called after you completely finish using the handle (this is necessary + because Go is multi-threaded and `ProcThreadSelf` references + `/proc/thread-self` which may disappear if we do not + `runtime.LockOSThread` -- `ProcThreadSelfCloser` is currently equivalent + to `runtime.UnlockOSThread`). + + Note that you cannot open any `procfs` symlinks (most notably magic-links) + using this API. At the moment, filepath-securejoin does not support this + feature (but [libpathrs][] does). + + * `ProcSelfFdReadlink` lets you get the in-kernel path representation of a + file descriptor (think `readlink("/proc/self/fd/...")`), except that we + verify that there aren't any tricky overmounts that could fool the + process. + + Please be aware that the returned string is simply a snapshot at that + particular moment, and an attacker could move the file being pointed to. + In addition, complex namespace configurations could result in non-sensical + or confusing paths to be returned. The value received from this function + should only be used as secondary verification of some security property, + not as proof that a particular handle has a particular path. + + The procfs handle used internally by the API is the same as the rest of + `filepath-securejoin` (for privileged programs this is usually a private + in-process `procfs` instance created with `fsopen(2)`). + + As before, this is intended as a stop-gap before users migrate to + [libpathrs][], which provides a far more extensive safe `procfs` API and is + generally more robust. + +- Previously, the hardened procfs implementation (used internally within + `Reopen` and `Open(at)InRoot`) only protected against overmount attacks on + systems with `openat2(2)` (Linux 5.6) or systems with `fsopen(2)` or + `open_tree(2)` (Linux 5.2) and programs with privileges to use them (with + some caveats about locked mounts that probably affect very few users). For + other users, an attacker with the ability to create malicious mounts (on most + systems, a sysadmin) could trick you into operating on files you didn't + expect. This attack only really makes sense in the context of container + runtime implementations. + + This was considered a reasonable trade-off, as the long-term intention was to + get all users to just switch to [libpathrs][] if they wanted to use the safe + `procfs` API (which had more extensive protections, and is what these new + protections in `filepath-securejoin` are based on). However, as the API + is now being exported it seems unwise to advertise the API as "safe" if we do + not protect against known attacks. + + The procfs API is now more protected against attackers on systems lacking the + aforementioned protections. However, the most comprehensive of these + protections effectively rely on [`statx(STATX_MNT_ID)`][statx.2] (Linux 5.8). + On older kernel versions, there is no effective protection (there is some + minimal protection against non-`procfs` filesystem components but a + sufficiently clever attacker can work around those). In addition, + `STATX_MNT_ID` is vulnerable to mount ID reuse attacks by sufficiently + motivated and privileged attackers -- this problem is mitigated with + `STATX_MNT_ID_UNIQUE` (Linux 6.8) but that raises the minimum kernel version + for more protection. + + The fact that these protections are quite limited despite needing a fair bit + of extra code to handle was one of the primary reasons we did not initially + implement this in `filepath-securejoin` ([libpathrs][] supports all of this, + of course). + +### Fixed ### +- RHEL 8 kernels have backports of `fsopen(2)` but in some testing we've found + that it has very bad (and very difficult to debug) performance issues, and so + we will explicitly refuse to use `fsopen(2)` if the running kernel version is + pre-5.2 and will instead fallback to `open("/proc")`. + +[CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv +[libpathrs]: https://github.com/cyphar/libpathrs +[statx.2]: https://www.man7.org/linux/man-pages/man2/statx.2.html + +## [0.4.1] - 2025-01-28 ## + +### Fixed ### +- The restrictions added for `root` paths passed to `SecureJoin` in 0.4.0 was + found to be too strict and caused some regressions when folks tried to + update, so this restriction has been relaxed to only return an error if the + path contains a `..` component. We still recommend users use `filepath.Clean` + (and even `filepath.EvalSymlinks`) on the `root` path they are using, but at + least you will no longer be punished for "trivial" unclean paths. + +## [0.4.0] - 2025-01-13 ## + +### Breaking #### +- `SecureJoin(VFS)` will now return an error if the provided `root` is not a + `filepath.Clean`'d path. + + While it is ultimately the responsibility of the caller to ensure the root is + a safe path to use, passing a path like `/symlink/..` as a root would result + in the `SecureJoin`'d path being placed in `/` even though `/symlink/..` + might be a different directory, and so we should more strongly discourage + such usage. + + All major users of `securejoin.SecureJoin` already ensure that the paths they + provide are safe (and this is ultimately a question of user error), but + removing this foot-gun is probably a good idea. Of course, this is + necessarily a breaking API change (though we expect no real users to be + affected by it). + + Thanks to [Erik Sjölund](https://github.com/eriksjolund), who initially + reported this issue as a possible security issue. + +- `MkdirAll` and `MkdirHandle` now take an `os.FileMode`-style mode argument + instead of a raw `unix.S_*`-style mode argument, which may cause compile-time + type errors depending on how you use `filepath-securejoin`. For most users, + there will be no change in behaviour aside from the type change (as the + bottom `0o777` bits are the same in both formats, and most users are probably + only using those bits). + + However, if you were using `unix.S_ISVTX` to set the sticky bit with + `MkdirAll(Handle)` you will need to switch to `os.ModeSticky` otherwise you + will get a runtime error with this update. In addition, the error message you + will get from passing `unix.S_ISUID` and `unix.S_ISGID` will be different as + they are treated as invalid bits now (note that previously passing said bits + was also an error). + +## [0.3.6] - 2024-12-17 ## + +### Compatibility ### +- The minimum Go version requirement for `filepath-securejoin` is now Go 1.18 + (we use generics internally). + + For reference, `filepath-securejoin@v0.3.0` somewhat-arbitrarily bumped the + Go version requirement to 1.21. + + While we did make some use of Go 1.21 stdlib features (and in principle Go + versions <= 1.21 are no longer even supported by upstream anymore), some + downstreams have complained that the version bump has meant that they have to + do workarounds when backporting fixes that use the new `filepath-securejoin` + API onto old branches. This is not an ideal situation, but since using this + library is probably better for most downstreams than a hand-rolled + workaround, we now have compatibility shims that allow us to build on older + Go versions. +- Lower minimum version requirement for `golang.org/x/sys` to `v0.18.0` (we + need the wrappers for `fsconfig(2)`), which should also make backporting + patches to older branches easier. + +## [0.3.5] - 2024-12-06 ## + +### Fixed ### +- `MkdirAll` will now no longer return an `EEXIST` error if two racing + processes are creating the same directory. We will still verify that the path + is a directory, but this will avoid spurious errors when multiple threads or + programs are trying to `MkdirAll` the same path. opencontainers/runc#4543 + +## [0.3.4] - 2024-10-09 ## + +### Fixed ### +- Previously, some testing mocks we had resulted in us doing `import "testing"` + in non-`_test.go` code, which made some downstreams like Kubernetes unhappy. + This has been fixed. (#32) + +## [0.3.3] - 2024-09-30 ## + +### Fixed ### +- The mode and owner verification logic in `MkdirAll` has been removed. This + was originally intended to protect against some theoretical attacks but upon + further consideration these protections don't actually buy us anything and + they were causing spurious errors with more complicated filesystem setups. +- The "is the created directory empty" logic in `MkdirAll` has also been + removed. This was not causing us issues yet, but some pseudofilesystems (such + as `cgroup`) create non-empty directories and so this logic would've been + wrong for such cases. + +## [0.3.2] - 2024-09-13 ## + +### Changed ### +- Passing the `S_ISUID` or `S_ISGID` modes to `MkdirAllInRoot` will now return + an explicit error saying that those bits are ignored by `mkdirat(2)`. In the + past a different error was returned, but since the silent ignoring behaviour + is codified in the man pages a more explicit error seems apt. While silently + ignoring these bits would be the most compatible option, it could lead to + users thinking their code sets these bits when it doesn't. Programs that need + to deal with compatibility can mask the bits themselves. (#23, #25) + +### Fixed ### +- If a directory has `S_ISGID` set, then all child directories will have + `S_ISGID` set when created and a different gid will be used for any inode + created under the directory. Previously, the "expected owner and mode" + validation in `securejoin.MkdirAll` did not correctly handle this. We now + correctly handle this case. (#24, #25) + +## [0.3.1] - 2024-07-23 ## + +### Changed ### +- By allowing `Open(at)InRoot` to opt-out of the extra work done by `MkdirAll` + to do the necessary "partial lookups", `Open(at)InRoot` now does less work + for both implementations (resulting in a many-fold decrease in the number of + operations for `openat2`, and a modest improvement for non-`openat2`) and is + far more guaranteed to match the correct `openat2(RESOLVE_IN_ROOT)` + behaviour. +- We now use `readlinkat(fd, "")` where possible. For `Open(at)InRoot` this + effectively just means that we no longer risk getting spurious errors during + rename races. However, for our hardened procfs handler, this in theory should + prevent mount attacks from tricking us when doing magic-link readlinks (even + when using the unsafe host `/proc` handle). Unfortunately `Reopen` is still + potentially vulnerable to those kinds of somewhat-esoteric attacks. + + Technically this [will only work on post-2.6.39 kernels][linux-readlinkat-emptypath] + but it seems incredibly unlikely anyone is using `filepath-securejoin` on a + pre-2011 kernel. + +### Fixed ### +- Several improvements were made to the errors returned by `Open(at)InRoot` and + `MkdirAll` when dealing with invalid paths under the emulated (ie. + non-`openat2`) implementation. Previously, some paths would return the wrong + error (`ENOENT` when the last component was a non-directory), and other paths + would be returned as though they were acceptable (trailing-slash components + after a non-directory would be ignored by `Open(at)InRoot`). + + These changes were done to match `openat2`'s behaviour and purely is a + consistency fix (most users are going to be using `openat2` anyway). + +[linux-readlinkat-emptypath]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=65cfc6722361570bfe255698d9cd4dccaf47570d + +## [0.3.0] - 2024-07-11 ## + +### Added ### +- A new set of `*os.File`-based APIs have been added. These are adapted from + [libpathrs][] and we strongly suggest using them if possible (as they provide + far more protection against attacks than `SecureJoin`): + + - `Open(at)InRoot` resolves a path inside a rootfs and returns an `*os.File` + handle to the path. Note that the handle returned is an `O_PATH` handle, + which cannot be used for reading or writing (as well as some other + operations -- [see open(2) for more details][open.2]) + + - `Reopen` takes an `O_PATH` file handle and safely re-opens it to upgrade + it to a regular handle. This can also be used with non-`O_PATH` handles, + but `O_PATH` is the most obvious application. + + - `MkdirAll` is an implementation of `os.MkdirAll` that is safe to use to + create a directory tree within a rootfs. + + As these are new APIs, they may change in the future. However, they should be + safe to start migrating to as we have extensive tests ensuring they behave + correctly and are safe against various races and other attacks. + +[libpathrs]: https://github.com/cyphar/libpathrs +[open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html + +## [0.2.5] - 2024-05-03 ## + +### Changed ### +- Some minor changes were made to how lexical components (like `..` and `.`) + are handled during path generation in `SecureJoin`. There is no behaviour + change as a result of this fix (the resulting paths are the same). + +### Fixed ### +- The error returned when we hit a symlink loop now references the correct + path. (#10) + +## [0.2.4] - 2023-09-06 ## + +### Security ### +- This release fixes a potential security issue in filepath-securejoin when + used on Windows ([GHSA-6xv5-86q9-7xr8][], which could be used to generate + paths outside of the provided rootfs in certain cases), as well as improving + the overall behaviour of filepath-securejoin when dealing with Windows paths + that contain volume names. Thanks to Paulo Gomes for discovering and fixing + these issues. + +### Fixed ### +- Switch to GitHub Actions for CI so we can test on Windows as well as Linux + and MacOS. + +[GHSA-6xv5-86q9-7xr8]: https://github.com/advisories/GHSA-6xv5-86q9-7xr8 + +## [0.2.3] - 2021-06-04 ## + +### Changed ### +- Switch to Go 1.13-style `%w` error wrapping, letting us drop the dependency + on `github.com/pkg/errors`. + +## [0.2.2] - 2018-09-05 ## + +### Changed ### +- Use `syscall.ELOOP` as the base error for symlink loops, rather than our own + (internal) error. This allows callers to more easily use `errors.Is` to check + for this case. + +## [0.2.1] - 2018-09-05 ## + +### Fixed ### +- Use our own `IsNotExist` implementation, which lets us handle `ENOTDIR` + properly within `SecureJoin`. + +## [0.2.0] - 2017-07-19 ## + +We now have 100% test coverage! + +### Added ### +- Add a `SecureJoinVFS` API that can be used for mocking (as we do in our new + tests) or for implementing custom handling of lookup operations (such as for + rootless containers, where work is necessary to access directories with weird + modes because we don't have `CAP_DAC_READ_SEARCH` or `CAP_DAC_OVERRIDE`). + +## 0.1.0 - 2017-07-19 + +This is our first release of `github.com/cyphar/filepath-securejoin`, +containing a full implementation with a coverage of 93.5% (the only missing +cases are the error cases, which are hard to mocktest at the moment). + +[Unreleased 0.5.z]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...release-0.5 +[0.5.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.5.1 +[0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0 +[0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0 +[0.3.6]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.5...v0.3.6 +[0.3.5]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.4...v0.3.5 +[0.3.4]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.3...v0.3.4 +[0.3.3]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.2...v0.3.3 +[0.3.2]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.1...v0.3.2 +[0.3.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.5...v0.3.0 +[0.2.5]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.4...v0.2.5 +[0.2.4]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.3...v0.2.4 +[0.2.3]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.2...v0.2.3 +[0.2.2]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.1...v0.2.2 +[0.2.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.1.0...v0.2.0 diff --git a/vendor/github.com/cyphar/filepath-securejoin/COPYING.md b/vendor/github.com/cyphar/filepath-securejoin/COPYING.md new file mode 100644 index 00000000..520e822b --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/COPYING.md @@ -0,0 +1,447 @@ +## COPYING ## + +`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` + +This project is made up of code licensed under different licenses. Which code +you use will have an impact on whether only one or both licenses apply to your +usage of this library. + +Note that **each file** in this project individually has a code comment at the +start describing the license of that particular file -- this is the most +accurate license information of this project; in case there is any conflict +between this document and the comment at the start of a file, the comment shall +take precedence. The only purpose of this document is to work around [a known +technical limitation of pkg.go.dev's license checking tool when dealing with +non-trivial project licenses][go75067]. + +[go75067]: https://go.dev/issue/75067 + +### `BSD-3-Clause` ### + +At time of writing, the following files and directories are licensed under the +BSD-3-Clause license: + + * `doc.go` + * `join*.go` + * `vfs.go` + * `internal/consts/*.go` + * `pathrs-lite/internal/gocompat/*.go` + * `pathrs-lite/internal/kernelversion/*.go` + +The text of the BSD-3-Clause license used by this project is the following (the +text is also available from the [`LICENSE.BSD`](./LICENSE.BSD) file): + +``` +Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +Copyright (C) 2017-2024 SUSE LLC. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### `MPL-2.0` ### + +All other files (unless otherwise marked) are licensed under the Mozilla Public +License (version 2.0). + +The text of the Mozilla Public License (version 2.0) is the following (the text +is also available from the [`LICENSE.MPL-2.0`](./LICENSE.MPL-2.0) file): + +``` +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. +``` diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE.BSD b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.BSD new file mode 100644 index 00000000..cb1ab88d --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.BSD @@ -0,0 +1,28 @@ +Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +Copyright (C) 2017-2024 SUSE LLC. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0 b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0 new file mode 100644 index 00000000..d0a1fa14 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0 @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/cyphar/filepath-securejoin/README.md b/vendor/github.com/cyphar/filepath-securejoin/README.md new file mode 100644 index 00000000..6673abfc --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/README.md @@ -0,0 +1,184 @@ +## `filepath-securejoin` ## + +[![Go Documentation](https://pkg.go.dev/badge/github.com/cyphar/filepath-securejoin.svg)](https://pkg.go.dev/github.com/cyphar/filepath-securejoin) +[![Build Status](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml/badge.svg)](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml) + +### Old API ### + +This library was originally just an implementation of `SecureJoin` which was +[intended to be included in the Go standard library][go#20126] as a safer +`filepath.Join` that would restrict the path lookup to be inside a root +directory. + +The implementation was based on code that existed in several container +runtimes. Unfortunately, this API is **fundamentally unsafe** against attackers +that can modify path components after `SecureJoin` returns and before the +caller uses the path, allowing for some fairly trivial TOCTOU attacks. + +`SecureJoin` (and `SecureJoinVFS`) are still provided by this library to +support legacy users, but new users are strongly suggested to avoid using +`SecureJoin` and instead use the [new api](#new-api) or switch to +[libpathrs][libpathrs]. + +With the above limitations in mind, this library guarantees the following: + +* If no error is set, the resulting string **must** be a child path of + `root` and will not contain any symlink path components (they will all be + expanded). + +* When expanding symlinks, all symlink path components **must** be resolved + relative to the provided root. In particular, this can be considered a + userspace implementation of how `chroot(2)` operates on file paths. Note that + these symlinks will **not** be expanded lexically (`filepath.Clean` is not + called on the input before processing). + +* Non-existent path components are unaffected by `SecureJoin` (similar to + `filepath.EvalSymlinks`'s semantics). + +* The returned path will always be `filepath.Clean`ed and thus not contain any + `..` components. + +A (trivial) implementation of this function on GNU/Linux systems could be done +with the following (note that this requires root privileges and is far more +opaque than the implementation in this library, and also requires that +`readlink` is inside the `root` path and is trustworthy): + +```go +package securejoin + +import ( + "os/exec" + "path/filepath" +) + +func SecureJoin(root, unsafePath string) (string, error) { + unsafePath = string(filepath.Separator) + unsafePath + cmd := exec.Command("chroot", root, + "readlink", "--canonicalize-missing", "--no-newline", unsafePath) + output, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + expanded := string(output) + return filepath.Join(root, expanded), nil +} +``` + +[libpathrs]: https://github.com/openSUSE/libpathrs +[go#20126]: https://github.com/golang/go/issues/20126 + +### New API ### +[#new-api]: #new-api + +While we recommend users switch to [libpathrs][libpathrs] as soon as it has a +stable release, some methods implemented by libpathrs have been ported to this +library to ease the transition. These APIs are only supported on Linux. + +These APIs are implemented such that `filepath-securejoin` will +opportunistically use certain newer kernel APIs that make these operations far +more secure. In particular: + +* All of the lookup operations will use [`openat2`][openat2.2] on new enough + kernels (Linux 5.6 or later) to restrict lookups through magic-links and + bind-mounts (for certain operations) and to make use of `RESOLVE_IN_ROOT` to + efficiently resolve symlinks within a rootfs. + +* The APIs provide hardening against a malicious `/proc` mount to either detect + or avoid being tricked by a `/proc` that is not legitimate. This is done + using [`openat2`][openat2.2] for all users, and privileged users will also be + further protected by using [`fsopen`][fsopen.2] and [`open_tree`][open_tree.2] + (Linux 5.2 or later). + +[openat2.2]: https://www.man7.org/linux/man-pages/man2/openat2.2.html +[fsopen.2]: https://github.com/brauner/man-pages-md/blob/main/fsopen.md +[open_tree.2]: https://github.com/brauner/man-pages-md/blob/main/open_tree.md + +#### `OpenInRoot` #### + +```go +func OpenInRoot(root, unsafePath string) (*os.File, error) +func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) +func Reopen(handle *os.File, flags int) (*os.File, error) +``` + +`OpenInRoot` is a much safer version of + +```go +path, err := securejoin.SecureJoin(root, unsafePath) +file, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC) +``` + +that protects against various race attacks that could lead to serious security +issues, depending on the application. Note that the returned `*os.File` is an +`O_PATH` file descriptor, which is quite restricted. Callers will probably need +to use `Reopen` to get a more usable handle (this split is done to provide +useful features like PTY spawning and to avoid users accidentally opening bad +inodes that could cause a DoS). + +Callers need to be careful in how they use the returned `*os.File`. Usually it +is only safe to operate on the handle directly, and it is very easy to create a +security issue. [libpathrs][libpathrs] provides far more helpers to make using +these handles safer -- there is currently no plan to port them to +`filepath-securejoin`. + +`OpenatInRoot` is like `OpenInRoot` except that the root is provided using an +`*os.File`. This allows you to ensure that multiple `OpenatInRoot` (or +`MkdirAllHandle`) calls are operating on the same rootfs. + +> **NOTE**: Unlike `SecureJoin`, `OpenInRoot` will error out as soon as it hits +> a dangling symlink or non-existent path. This is in contrast to `SecureJoin` +> which treated non-existent components as though they were real directories, +> and would allow for partial resolution of dangling symlinks. These behaviours +> are at odds with how Linux treats non-existent paths and dangling symlinks, +> and so these are no longer allowed. + +#### `MkdirAll` #### + +```go +func MkdirAll(root, unsafePath string, mode int) error +func MkdirAllHandle(root *os.File, unsafePath string, mode int) (*os.File, error) +``` + +`MkdirAll` is a much safer version of + +```go +path, err := securejoin.SecureJoin(root, unsafePath) +err = os.MkdirAll(path, mode) +``` + +that protects against the same kinds of races that `OpenInRoot` protects +against. + +`MkdirAllHandle` is like `MkdirAll` except that the root is provided using an +`*os.File` (the reason for this is the same as with `OpenatInRoot`) and an +`*os.File` of the final created directory is returned (this directory is +guaranteed to be effectively identical to the directory created by +`MkdirAllHandle`, which is not possible to ensure by just using `OpenatInRoot` +after `MkdirAll`). + +> **NOTE**: Unlike `SecureJoin`, `MkdirAll` will error out as soon as it hits +> a dangling symlink or non-existent path. This is in contrast to `SecureJoin` +> which treated non-existent components as though they were real directories, +> and would allow for partial resolution of dangling symlinks. These behaviours +> are at odds with how Linux treats non-existent paths and dangling symlinks, +> and so these are no longer allowed. This means that `MkdirAll` will not +> create non-existent directories referenced by a dangling symlink. + +### License ### + +`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` + +Some of the code in this project is derived from Go, and is licensed under a +BSD 3-clause license (available in `LICENSE.BSD`). Other files (many of which +are derived from [libpathrs][libpathrs]) are licensed under the Mozilla Public +License version 2.0 (available in `LICENSE.MPL-2.0`). If you are using the +["New API" described above][#new-api], you are probably using code from files +released under this license. + +Every source file in this project has a copyright header describing its +license. Please check the license headers of each file to see what license +applies to it. + +See [COPYING.md](./COPYING.md) for some more details. + +[umoci]: https://github.com/opencontainers/umoci diff --git a/vendor/github.com/cyphar/filepath-securejoin/VERSION b/vendor/github.com/cyphar/filepath-securejoin/VERSION new file mode 100644 index 00000000..cb0c939a --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/VERSION @@ -0,0 +1 @@ +0.5.2 diff --git a/vendor/github.com/cyphar/filepath-securejoin/codecov.yml b/vendor/github.com/cyphar/filepath-securejoin/codecov.yml new file mode 100644 index 00000000..ff284dbf --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/codecov.yml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: MPL-2.0 + +# Copyright (C) 2025 Aleksa Sarai +# Copyright (C) 2025 SUSE LLC +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +comment: + layout: "condensed_header, reach, diff, components, condensed_files, condensed_footer" + require_changes: true + branches: + - main + +coverage: + range: 60..100 + status: + project: + default: + target: 85% + threshold: 0% + patch: + default: + target: auto + informational: true + +github_checks: + annotations: false diff --git a/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go b/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go new file mode 100644 index 00000000..3e427b16 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/deprecated_linux.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package securejoin + +import ( + "github.com/cyphar/filepath-securejoin/pathrs-lite" +) + +var ( + // MkdirAll is a wrapper around [pathrs.MkdirAll]. + // + // Deprecated: You should use [pathrs.MkdirAll] directly instead. This + // wrapper will be removed in filepath-securejoin v0.6. + MkdirAll = pathrs.MkdirAll + + // MkdirAllHandle is a wrapper around [pathrs.MkdirAllHandle]. + // + // Deprecated: You should use [pathrs.MkdirAllHandle] directly instead. + // This wrapper will be removed in filepath-securejoin v0.6. + MkdirAllHandle = pathrs.MkdirAllHandle + + // OpenInRoot is a wrapper around [pathrs.OpenInRoot]. + // + // Deprecated: You should use [pathrs.OpenInRoot] directly instead. This + // wrapper will be removed in filepath-securejoin v0.6. + OpenInRoot = pathrs.OpenInRoot + + // OpenatInRoot is a wrapper around [pathrs.OpenatInRoot]. + // + // Deprecated: You should use [pathrs.OpenatInRoot] directly instead. This + // wrapper will be removed in filepath-securejoin v0.6. + OpenatInRoot = pathrs.OpenatInRoot + + // Reopen is a wrapper around [pathrs.Reopen]. + // + // Deprecated: You should use [pathrs.Reopen] directly instead. This + // wrapper will be removed in filepath-securejoin v0.6. + Reopen = pathrs.Reopen +) diff --git a/vendor/github.com/cyphar/filepath-securejoin/doc.go b/vendor/github.com/cyphar/filepath-securejoin/doc.go new file mode 100644 index 00000000..1438fc9c --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/doc.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +// Copyright (C) 2017-2024 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package securejoin implements a set of helpers to make it easier to write Go +// code that is safe against symlink-related escape attacks. The primary idea +// is to let you resolve a path within a rootfs directory as if the rootfs was +// a chroot. +// +// securejoin has two APIs, a "legacy" API and a "modern" API. +// +// The legacy API is [SecureJoin] and [SecureJoinVFS]. These methods are +// **not** safe against race conditions where an attacker changes the +// filesystem after (or during) the [SecureJoin] operation. +// +// The new API is available in the [pathrs-lite] subpackage, and provide +// protections against racing attackers as well as several other key +// protections against attacks often seen by container runtimes. As the name +// suggests, [pathrs-lite] is a stripped down (pure Go) reimplementation of +// [libpathrs]. The main APIs provided are [OpenInRoot], [MkdirAll], and +// [procfs.Handle] -- other APIs are not planned to be ported. The long-term +// goal is for users to migrate to [libpathrs] which is more fully-featured. +// +// securejoin has been used by several container runtimes (Docker, runc, +// Kubernetes, etc) for quite a few years as a de-facto standard for operating +// on container filesystem paths "safely". However, most users still use the +// legacy API which is unsafe against various attacks (there is a fairly long +// history of CVEs in dependent as a result). Users should switch to the modern +// API as soon as possible (or even better, switch to libpathrs). +// +// This project was initially intended to be included in the Go standard +// library, but it was rejected (see https://go.dev/issue/20126). Much later, +// [os.Root] was added to the Go stdlib that shares some of the goals of +// filepath-securejoin. However, its design is intended to work like +// openat2(RESOLVE_BENEATH) which does not fit the usecase of container +// runtimes and most system tools. +// +// [pathrs-lite]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite +// [libpathrs]: https://github.com/openSUSE/libpathrs +// [OpenInRoot]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#OpenInRoot +// [MkdirAll]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#MkdirAll +// [procfs.Handle]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle +// [os.Root]: https:///pkg.go.dev/os#Root +package securejoin diff --git a/vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go b/vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go new file mode 100644 index 00000000..c69c4da9 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +// Copyright (C) 2017-2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package consts contains the definitions of internal constants used +// throughout filepath-securejoin. +package consts + +// MaxSymlinkLimit is the maximum number of symlinks that can be encountered +// during a single lookup before returning -ELOOP. At time of writing, Linux +// has an internal limit of 40. +const MaxSymlinkLimit = 255 diff --git a/vendor/github.com/cyphar/filepath-securejoin/join.go b/vendor/github.com/cyphar/filepath-securejoin/join.go new file mode 100644 index 00000000..199c1d83 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/join.go @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. +// Copyright (C) 2017-2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package securejoin + +import ( + "errors" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/cyphar/filepath-securejoin/internal/consts" +) + +// IsNotExist tells you if err is an error that implies that either the path +// accessed does not exist (or path components don't exist). This is +// effectively a more broad version of [os.IsNotExist]. +func IsNotExist(err error) bool { + // Check that it's not actually an ENOTDIR, which in some cases is a more + // convoluted case of ENOENT (usually involving weird paths). + return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) || errors.Is(err, syscall.ENOENT) +} + +// errUnsafeRoot is returned if the user provides SecureJoinVFS with a path +// that contains ".." components. +var errUnsafeRoot = errors.New("root path provided to SecureJoin contains '..' components") + +// stripVolume just gets rid of the Windows volume included in a path. Based on +// some godbolt tests, the Go compiler is smart enough to make this a no-op on +// Linux. +func stripVolume(path string) string { + return path[len(filepath.VolumeName(path)):] +} + +// hasDotDot checks if the path contains ".." components in a platform-agnostic +// way. +func hasDotDot(path string) bool { + // If we are on Windows, strip any volume letters. It turns out that + // C:..\foo may (or may not) be a valid pathname and we need to handle that + // leading "..". + path = stripVolume(path) + // Look for "/../" in the path, but we need to handle leading and trailing + // ".."s by adding separators. Doing this with filepath.Separator is ugly + // so just convert to Unix-style "/" first. + path = filepath.ToSlash(path) + return strings.Contains("/"+path+"/", "/../") +} + +// SecureJoinVFS joins the two given path components (similar to +// [filepath.Join]) except that the returned path is guaranteed to be scoped +// inside the provided root path (when evaluated). Any symbolic links in the +// path are evaluated with the given root treated as the root of the +// filesystem, similar to a chroot. The filesystem state is evaluated through +// the given [VFS] interface (if nil, the standard [os].* family of functions +// are used). +// +// Note that the guarantees provided by this function only apply if the path +// components in the returned string are not modified (in other words are not +// replaced with symlinks on the filesystem) after this function has returned. +// Such a symlink race is necessarily out-of-scope of SecureJoinVFS. +// +// NOTE: Due to the above limitation, Linux users are strongly encouraged to +// use [OpenInRoot] instead, which does safely protect against these kinds of +// attacks. There is no way to solve this problem with SecureJoinVFS because +// the API is fundamentally wrong (you cannot return a "safe" path string and +// guarantee it won't be modified afterwards). +// +// Volume names in unsafePath are always discarded, regardless if they are +// provided via direct input or when evaluating symlinks. Therefore: +// +// "C:\Temp" + "D:\path\to\file.txt" results in "C:\Temp\path\to\file.txt" +// +// If the provided root is not [filepath.Clean] then an error will be returned, +// as such root paths are bordering on somewhat unsafe and using such paths is +// not best practice. We also strongly suggest that any root path is first +// fully resolved using [filepath.EvalSymlinks] or otherwise constructed to +// avoid containing symlink components. Of course, the root also *must not* be +// attacker-controlled. +func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { //nolint:revive // name is part of public API + // The root path must not contain ".." components, otherwise when we join + // the subpath we will end up with a weird path. We could work around this + // in other ways but users shouldn't be giving us non-lexical root paths in + // the first place. + if hasDotDot(root) { + return "", errUnsafeRoot + } + + // Use the os.* VFS implementation if none was specified. + if vfs == nil { + vfs = osVFS{} + } + + unsafePath = filepath.FromSlash(unsafePath) + var ( + currentPath string + remainingPath = unsafePath + linksWalked int + ) + for remainingPath != "" { + // On Windows, if we managed to end up at a path referencing a volume, + // drop the volume to make sure we don't end up with broken paths or + // escaping the root volume. + remainingPath = stripVolume(remainingPath) + + // Get the next path component. + var part string + if i := strings.IndexRune(remainingPath, filepath.Separator); i == -1 { + part, remainingPath = remainingPath, "" + } else { + part, remainingPath = remainingPath[:i], remainingPath[i+1:] + } + + // Apply the component lexically to the path we are building. + // currentPath does not contain any symlinks, and we are lexically + // dealing with a single component, so it's okay to do a filepath.Clean + // here. + nextPath := filepath.Join(string(filepath.Separator), currentPath, part) + if nextPath == string(filepath.Separator) { + currentPath = "" + continue + } + fullPath := root + string(filepath.Separator) + nextPath + + // Figure out whether the path is a symlink. + fi, err := vfs.Lstat(fullPath) + if err != nil && !IsNotExist(err) { + return "", err + } + // Treat non-existent path components the same as non-symlinks (we + // can't do any better here). + if IsNotExist(err) || fi.Mode()&os.ModeSymlink == 0 { + currentPath = nextPath + continue + } + + // It's a symlink, so get its contents and expand it by prepending it + // to the yet-unparsed path. + linksWalked++ + if linksWalked > consts.MaxSymlinkLimit { + return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP} + } + + dest, err := vfs.Readlink(fullPath) + if err != nil { + return "", err + } + remainingPath = dest + string(filepath.Separator) + remainingPath + // Absolute symlinks reset any work we've already done. + if filepath.IsAbs(dest) { + currentPath = "" + } + } + + // There should be no lexical components like ".." left in the path here, + // but for safety clean up the path before joining it to the root. + finalPath := filepath.Join(string(filepath.Separator), currentPath) + return filepath.Join(root, finalPath), nil +} + +// SecureJoin is a wrapper around [SecureJoinVFS] that just uses the [os].* library +// of functions as the [VFS]. If in doubt, use this function over [SecureJoinVFS]. +func SecureJoin(root, unsafePath string) (string, error) { + return SecureJoinVFS(root, unsafePath, nil) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md new file mode 100644 index 00000000..1be727e7 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md @@ -0,0 +1,33 @@ +## `pathrs-lite` ## + +`github.com/cyphar/filepath-securejoin/pathrs-lite` provides a minimal **pure +Go** implementation of the core bits of [libpathrs][]. This is not intended to +be a complete replacement for libpathrs, instead it is mainly intended to be +useful as a transition tool for existing Go projects. + +The long-term plan for `pathrs-lite` is to provide a build tag that will cause +all `pathrs-lite` operations to call into libpathrs directly, thus removing +code duplication for projects that wish to make use of libpathrs (and providing +the ability for software packagers to opt-in to libpathrs support without +needing to patch upstream). + +[libpathrs]: https://github.com/cyphar/libpathrs + +### License ### + +Most of this subpackage is licensed under the Mozilla Public License (version +2.0). For more information, see the top-level [COPYING.md][] and +[LICENSE.MPL-2.0][] files, as well as the individual license headers for each +file. + +``` +Copyright (C) 2024-2025 Aleksa Sarai +Copyright (C) 2024-2025 SUSE LLC + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. +``` + +[COPYING.md]: ../COPYING.md +[LICENSE.MPL-2.0]: ../LICENSE.MPL-2.0 diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go new file mode 100644 index 00000000..d3d74517 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package pathrs (pathrs-lite) is a less complete pure Go implementation of +// some of the APIs provided by [libpathrs]. +package pathrs diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go new file mode 100644 index 00000000..595dfbf1 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 + +// Copyright (C) 2025 Aleksa Sarai +// Copyright (C) 2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package assert provides some basic assertion helpers for Go. +package assert + +import ( + "fmt" +) + +// Assert panics if the predicate is false with the provided argument. +func Assert(predicate bool, msg any) { + if !predicate { + panic(msg) + } +} + +// Assertf panics if the predicate is false and formats the message using the +// same formatting as [fmt.Printf]. +// +// [fmt.Printf]: https://pkg.go.dev/fmt#Printf +func Assertf(predicate bool, fmtMsg string, args ...any) { + Assert(predicate, fmt.Sprintf(fmtMsg, args...)) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go new file mode 100644 index 00000000..d0b200f4 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package internal contains unexported common code for filepath-securejoin. +package internal + +import ( + "errors" + + "golang.org/x/sys/unix" +) + +type xdevErrorish struct { + description string +} + +func (err xdevErrorish) Error() string { return err.description } +func (err xdevErrorish) Is(target error) bool { return target == unix.EXDEV } + +var ( + // ErrPossibleAttack indicates that some attack was detected. + ErrPossibleAttack error = xdevErrorish{"possible attack detected"} + + // ErrPossibleBreakout indicates that during an operation we ended up in a + // state that could be a breakout but we detected it. + ErrPossibleBreakout error = xdevErrorish{"possible breakout detected"} + + // ErrInvalidDirectory indicates an unlinked directory. + ErrInvalidDirectory = errors.New("wandered into deleted directory") + + // ErrDeletedInode indicates an unlinked file (non-directory). + ErrDeletedInode = errors.New("cannot verify path of deleted inode") +) diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go new file mode 100644 index 00000000..09105491 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package fd + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" +) + +// prepareAtWith returns -EBADF (an invalid fd) if dir is nil, otherwise using +// the dir.Fd(). We use -EBADF because in filepath-securejoin we generally +// don't want to allow relative-to-cwd paths. The returned path is an +// *informational* string that describes a reasonable pathname for the given +// *at(2) arguments. You must not use the full path for any actual filesystem +// operations. +func prepareAt(dir Fd, path string) (dirFd int, unsafeUnmaskedPath string) { + dirFd, dirPath := -int(unix.EBADF), "." + if dir != nil { + dirFd, dirPath = int(dir.Fd()), dir.Name() + } + if !filepath.IsAbs(path) { + // only prepend the dirfd path for relative paths + path = dirPath + "/" + path + } + // NOTE: If path is "." or "", the returned path won't be filepath.Clean, + // but that's okay since this path is either used for errors (in which case + // a trailing "/" or "/." is important information) or will be + // filepath.Clean'd later (in the case of fd.Openat). + return dirFd, path +} + +// Openat is an [Fd]-based wrapper around unix.Openat. +func Openat(dir Fd, path string, flags int, mode int) (*os.File, error) { //nolint:unparam // wrapper func + dirFd, fullPath := prepareAt(dir, path) + // Make sure we always set O_CLOEXEC. + flags |= unix.O_CLOEXEC + fd, err := unix.Openat(dirFd, path, flags, uint32(mode)) + if err != nil { + return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + // openat is only used with lexically-safe paths so we can use + // filepath.Clean here, and also the path itself is not going to be used + // for actual path operations. + fullPath = filepath.Clean(fullPath) + return os.NewFile(uintptr(fd), fullPath), nil +} + +// Fstatat is an [Fd]-based wrapper around unix.Fstatat. +func Fstatat(dir Fd, path string, flags int) (unix.Stat_t, error) { + dirFd, fullPath := prepareAt(dir, path) + var stat unix.Stat_t + if err := unix.Fstatat(dirFd, path, &stat, flags); err != nil { + return stat, &os.PathError{Op: "fstatat", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return stat, nil +} + +// Faccessat is an [Fd]-based wrapper around unix.Faccessat. +func Faccessat(dir Fd, path string, mode uint32, flags int) error { + dirFd, fullPath := prepareAt(dir, path) + err := unix.Faccessat(dirFd, path, mode, flags) + if err != nil { + err = &os.PathError{Op: "faccessat", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return err +} + +// Readlinkat is an [Fd]-based wrapper around unix.Readlinkat. +func Readlinkat(dir Fd, path string) (string, error) { + dirFd, fullPath := prepareAt(dir, path) + size := 4096 + for { + linkBuf := make([]byte, size) + n, err := unix.Readlinkat(dirFd, path, linkBuf) + if err != nil { + return "", &os.PathError{Op: "readlinkat", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + if n != size { + return string(linkBuf[:n]), nil + } + // Possible truncation, resize the buffer. + size *= 2 + } +} + +const ( + // STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to + // avoid bumping the requirement for a single constant we can just define it + // ourselves. + _STATX_MNT_ID_UNIQUE = 0x4000 //nolint:revive // unix.* name + + // We don't care which mount ID we get. The kernel will give us the unique + // one if it is supported. If the kernel doesn't support + // STATX_MNT_ID_UNIQUE, the bit is ignored and the returned request mask + // will only contain STATX_MNT_ID (if supported). + wantStatxMntMask = _STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID +) + +var hasStatxMountID = gocompat.SyncOnceValue(func() bool { + var stx unix.Statx_t + err := unix.Statx(-int(unix.EBADF), "/", 0, wantStatxMntMask, &stx) + return err == nil && stx.Mask&wantStatxMntMask != 0 +}) + +// GetMountID gets the mount identifier associated with the fd and path +// combination. It is effectively a wrapper around fetching +// STATX_MNT_ID{,_UNIQUE} with unix.Statx, but with a fallback to 0 if the +// kernel doesn't support the feature. +func GetMountID(dir Fd, path string) (uint64, error) { + // If we don't have statx(STATX_MNT_ID*) support, we can't do anything. + if !hasStatxMountID() { + return 0, nil + } + + dirFd, fullPath := prepareAt(dir, path) + + var stx unix.Statx_t + err := unix.Statx(dirFd, path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, wantStatxMntMask, &stx) + if stx.Mask&wantStatxMntMask == 0 { + // It's not a kernel limitation, for some reason we couldn't get a + // mount ID. Assume it's some kind of attack. + err = fmt.Errorf("could not get mount id: %w", err) + } + if err != nil { + return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return stx.Mnt_id, nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go new file mode 100644 index 00000000..d2206a38 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MPL-2.0 + +// Copyright (C) 2025 Aleksa Sarai +// Copyright (C) 2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package fd provides a drop-in interface-based replacement of [*os.File] that +// allows for things like noop-Close wrappers to be used. +// +// [*os.File]: https://pkg.go.dev/os#File +package fd + +import ( + "io" + "os" +) + +// Fd is an interface that mirrors most of the API of [*os.File], allowing you +// to create wrappers that can be used in place of [*os.File]. +// +// [*os.File]: https://pkg.go.dev/os#File +type Fd interface { + io.Closer + Name() string + Fd() uintptr +} + +// Compile-time interface checks. +var ( + _ Fd = (*os.File)(nil) + _ Fd = noClose{} +) + +type noClose struct{ inner Fd } + +func (f noClose) Name() string { return f.inner.Name() } +func (f noClose) Fd() uintptr { return f.inner.Fd() } + +func (f noClose) Close() error { return nil } + +// NopCloser returns an [*os.File]-like object where the [Close] method is now +// a no-op. +// +// Note that for [*os.File] and similar objects, the Go garbage collector will +// still call [Close] on the underlying file unless you use +// [runtime.SetFinalizer] to disable this behaviour. This is up to the caller +// to do (if necessary). +// +// [*os.File]: https://pkg.go.dev/os#File +// [Close]: https://pkg.go.dev/io#Closer +// [runtime.SetFinalizer]: https://pkg.go.dev/runtime#SetFinalizer +func NopCloser(f Fd) Fd { return noClose{inner: f} } diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go new file mode 100644 index 00000000..e1ec3c0b --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package fd + +import ( + "fmt" + "os" + "runtime" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal" +) + +// DupWithName creates a new file descriptor referencing the same underlying +// file, but with the provided name instead of fd.Name(). +func DupWithName(fd Fd, name string) (*os.File, error) { + fd2, err := unix.FcntlInt(fd.Fd(), unix.F_DUPFD_CLOEXEC, 0) + if err != nil { + return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err) + } + runtime.KeepAlive(fd) + return os.NewFile(uintptr(fd2), name), nil +} + +// Dup creates a new file description referencing the same underlying file. +func Dup(fd Fd) (*os.File, error) { + return DupWithName(fd, fd.Name()) +} + +// Fstat is an [Fd]-based wrapper around unix.Fstat. +func Fstat(fd Fd) (unix.Stat_t, error) { + var stat unix.Stat_t + if err := unix.Fstat(int(fd.Fd()), &stat); err != nil { + return stat, &os.PathError{Op: "fstat", Path: fd.Name(), Err: err} + } + runtime.KeepAlive(fd) + return stat, nil +} + +// Fstatfs is an [Fd]-based wrapper around unix.Fstatfs. +func Fstatfs(fd Fd) (unix.Statfs_t, error) { + var statfs unix.Statfs_t + if err := unix.Fstatfs(int(fd.Fd()), &statfs); err != nil { + return statfs, &os.PathError{Op: "fstatfs", Path: fd.Name(), Err: err} + } + runtime.KeepAlive(fd) + return statfs, nil +} + +// IsDeadInode detects whether the file has been unlinked from a filesystem and +// is thus a "dead inode" from the kernel's perspective. +func IsDeadInode(file Fd) error { + // If the nlink of a file drops to 0, there is an attacker deleting + // directories during our walk, which could result in weird /proc values. + // It's better to error out in this case. + stat, err := Fstat(file) + if err != nil { + return fmt.Errorf("check for dead inode: %w", err) + } + if stat.Nlink == 0 { + err := internal.ErrDeletedInode + if stat.Mode&unix.S_IFMT == unix.S_IFDIR { + err = internal.ErrInvalidDirectory + } + return fmt.Errorf("%w %q", err, file.Name()) + } + return nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go new file mode 100644 index 00000000..77549c7a --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package fd + +import ( + "os" + "runtime" + + "golang.org/x/sys/unix" +) + +// Fsopen is an [Fd]-based wrapper around unix.Fsopen. +func Fsopen(fsName string, flags int) (*os.File, error) { + // Make sure we always set O_CLOEXEC. + flags |= unix.FSOPEN_CLOEXEC + fd, err := unix.Fsopen(fsName, flags) + if err != nil { + return nil, os.NewSyscallError("fsopen "+fsName, err) + } + return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil +} + +// Fsmount is an [Fd]-based wrapper around unix.Fsmount. +func Fsmount(ctx Fd, flags, mountAttrs int) (*os.File, error) { + // Make sure we always set O_CLOEXEC. + flags |= unix.FSMOUNT_CLOEXEC + fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs) + if err != nil { + return nil, os.NewSyscallError("fsmount "+ctx.Name(), err) + } + return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil +} + +// OpenTree is an [Fd]-based wrapper around unix.OpenTree. +func OpenTree(dir Fd, path string, flags uint) (*os.File, error) { + dirFd, fullPath := prepareAt(dir, path) + // Make sure we always set O_CLOEXEC. + flags |= unix.OPEN_TREE_CLOEXEC + fd, err := unix.OpenTree(dirFd, path, flags) + if err != nil { + return nil, &os.PathError{Op: "open_tree", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return os.NewFile(uintptr(fd), fullPath), nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go new file mode 100644 index 00000000..63863647 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package fd + +import ( + "errors" + "os" + "runtime" + + "golang.org/x/sys/unix" +) + +func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool { + // RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve + // ".." while a mount or rename occurs anywhere on the system. This could + // happen spuriously, or as the result of an attacker trying to mess with + // us during lookup. + // + // In addition, scoped lookups have a "safety check" at the end of + // complete_walk which will return -EXDEV if the final path is not in the + // root. + return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 && + (errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV)) +} + +// This is a fairly arbitrary limit we have just to avoid an attacker being +// able to make us spin in an infinite retry loop -- callers can choose to +// retry on EAGAIN if they prefer. +const scopedLookupMaxRetries = 128 + +// Openat2 is an [Fd]-based wrapper around unix.Openat2, but with some retry +// logic in case of EAGAIN errors. +// +// NOTE: This is a variable so that the lookup tests can force openat2 to fail. +var Openat2 = func(dir Fd, path string, how *unix.OpenHow) (*os.File, error) { + dirFd, fullPath := prepareAt(dir, path) + // Make sure we always set O_CLOEXEC. + how.Flags |= unix.O_CLOEXEC + var tries int + for { + fd, err := unix.Openat2(dirFd, path, how) + if err != nil { + if scopedLookupShouldRetry(how, err) && tries < scopedLookupMaxRetries { + // We retry a couple of times to avoid the spurious errors, and + // if we are being attacked then returning -EAGAIN is the best + // we can do. + tries++ + continue + } + return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err} + } + runtime.KeepAlive(dir) + return os.NewFile(uintptr(fd), fullPath), nil + } +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md new file mode 100644 index 00000000..5dcb6ae0 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md @@ -0,0 +1,10 @@ +## gocompat ## + +This directory contains backports of stdlib functions from later Go versions so +the filepath-securejoin can continue to be used by projects that are stuck with +Go 1.18 support. Note that often filepath-securejoin is added in security +patches for old releases, so avoiding the need to bump Go compiler requirements +is a huge plus to downstreams. + +The source code is licensed under the same license as the Go stdlib. See the +source files for the precise license information. diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go new file mode 100644 index 00000000..4b1803f5 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build linux && go1.20 + +// Copyright (C) 2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gocompat includes compatibility shims (backported from future Go +// stdlib versions) to permit filepath-securejoin to be used with older Go +// versions (often filepath-securejoin is added in security patches for old +// releases, so avoiding the need to bump Go compiler requirements is a huge +// plus to downstreams). +package gocompat diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_go119.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_go119.go new file mode 100644 index 00000000..ac93cb04 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_go119.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && go1.19 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocompat + +import ( + "sync/atomic" +) + +// A Bool is an atomic boolean value. +// The zero value is false. +// +// Bool must not be copied after first use. +type Bool = atomic.Bool diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_unsupported.go new file mode 100644 index 00000000..21b5b29a --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_atomic_unsupported.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && !go1.19 + +// Copyright (C) 2024-2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocompat + +import ( + "sync/atomic" +) + +// noCopy may be added to structs which must not be copied +// after the first use. +// +// See https://golang.org/issues/8005#issuecomment-190753527 +// for details. +// +// Note that it must not be embedded, due to the Lock and Unlock methods. +type noCopy struct{} + +// Lock is a no-op used by -copylocks checker from `go vet`. +func (*noCopy) Lock() {} + +// b32 returns a uint32 0 or 1 representing b. +func b32(b bool) uint32 { + if b { + return 1 + } + return 0 +} + +// A Bool is an atomic boolean value. +// The zero value is false. +// +// Bool must not be copied after first use. +type Bool struct { + _ noCopy + v uint32 +} + +// Load atomically loads and returns the value stored in x. +func (x *Bool) Load() bool { return atomic.LoadUint32(&x.v) != 0 } + +// Store atomically stores val into x. +func (x *Bool) Store(val bool) { atomic.StoreUint32(&x.v, b32(val)) } diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go new file mode 100644 index 00000000..4a114bd3 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_go120.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause +//go:build linux && go1.20 + +// Copyright (C) 2024 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocompat + +import ( + "fmt" +) + +// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except +// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) +// is only guaranteed to give you baseErr. +func WrapBaseError(baseErr, extraErr error) error { + return fmt.Errorf("%w: %w", extraErr, baseErr) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go new file mode 100644 index 00000000..3061016a --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_errors_unsupported.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && !go1.20 + +// Copyright (C) 2024 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocompat + +import ( + "fmt" +) + +type wrappedError struct { + inner error + isError error +} + +func (err wrappedError) Is(target error) bool { + return err.isError == target +} + +func (err wrappedError) Unwrap() error { + return err.inner +} + +func (err wrappedError) Error() string { + return fmt.Sprintf("%v: %v", err.isError, err.inner) +} + +// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except +// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap) +// is only guaranteed to give you baseErr. +func WrapBaseError(baseErr, extraErr error) error { + return wrappedError{ + inner: baseErr, + isError: extraErr, + } +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go new file mode 100644 index 00000000..d4a93818 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_go121.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && go1.21 + +// Copyright (C) 2024-2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocompat + +import ( + "cmp" + "slices" + "sync" +) + +// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc. +func SlicesDeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S { + return slices.DeleteFunc(slice, delFn) +} + +// SlicesContains is equivalent to Go 1.21's slices.Contains. +func SlicesContains[S ~[]E, E comparable](slice S, val E) bool { + return slices.Contains(slice, val) +} + +// SlicesClone is equivalent to Go 1.21's slices.Clone. +func SlicesClone[S ~[]E, E any](slice S) S { + return slices.Clone(slice) +} + +// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue. +func SyncOnceValue[T any](f func() T) func() T { + return sync.OnceValue(f) +} + +// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues. +func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { + return sync.OnceValues(f) +} + +// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition. +type CmpOrdered = cmp.Ordered + +// CmpCompare is equivalent to Go 1.21's cmp.Compare. +func CmpCompare[T CmpOrdered](x, y T) int { + return cmp.Compare(x, y) +} + +// Max2 is equivalent to Go 1.21's max builtin (but only for two parameters). +func Max2[T CmpOrdered](x, y T) T { + return max(x, y) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go new file mode 100644 index 00000000..0ea6218a --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/gocompat_generics_unsupported.go @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: BSD-3-Clause + +//go:build linux && !go1.21 + +// Copyright (C) 2021, 2022 The Go Authors. All rights reserved. +// Copyright (C) 2024-2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.BSD file. + +package gocompat + +import ( + "sync" +) + +// These are very minimal implementations of functions that appear in Go 1.21's +// stdlib, included so that we can build on older Go versions. Most are +// borrowed directly from the stdlib, and a few are modified to be "obviously +// correct" without needing to copy too many other helpers. + +// clearSlice is equivalent to Go 1.21's builtin clear. +// Copied from the Go 1.24 stdlib implementation. +func clearSlice[S ~[]E, E any](slice S) { + var zero E + for i := range slice { + slice[i] = zero + } +} + +// slicesIndexFunc is equivalent to Go 1.21's slices.IndexFunc. +// Copied from the Go 1.24 stdlib implementation. +func slicesIndexFunc[S ~[]E, E any](s S, f func(E) bool) int { + for i := range s { + if f(s[i]) { + return i + } + } + return -1 +} + +// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc. +// Copied from the Go 1.24 stdlib implementation. +func SlicesDeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { + i := slicesIndexFunc(s, del) + if i == -1 { + return s + } + // Don't start copying elements until we find one to delete. + for j := i + 1; j < len(s); j++ { + if v := s[j]; !del(v) { + s[i] = v + i++ + } + } + clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC + return s[:i] +} + +// SlicesContains is equivalent to Go 1.21's slices.Contains. +// Similar to the stdlib slices.Contains, except that we don't have +// slices.Index so we need to use slices.IndexFunc for this non-Func helper. +func SlicesContains[S ~[]E, E comparable](s S, v E) bool { + return slicesIndexFunc(s, func(e E) bool { return e == v }) >= 0 +} + +// SlicesClone is equivalent to Go 1.21's slices.Clone. +// Copied from the Go 1.24 stdlib implementation. +func SlicesClone[S ~[]E, E any](s S) S { + // Preserve nil in case it matters. + if s == nil { + return nil + } + return append(S([]E{}), s...) +} + +// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue. +// Copied from the Go 1.25 stdlib implementation. +func SyncOnceValue[T any](f func() T) func() T { + // Use a struct so that there's a single heap allocation. + d := struct { + f func() T + once sync.Once + valid bool + p any + result T + }{ + f: f, + } + return func() T { + d.once.Do(func() { + defer func() { + d.f = nil + d.p = recover() + if !d.valid { + panic(d.p) + } + }() + d.result = d.f() + d.valid = true + }) + if !d.valid { + panic(d.p) + } + return d.result + } +} + +// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues. +// Copied from the Go 1.25 stdlib implementation. +func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { + // Use a struct so that there's a single heap allocation. + d := struct { + f func() (T1, T2) + once sync.Once + valid bool + p any + r1 T1 + r2 T2 + }{ + f: f, + } + return func() (T1, T2) { + d.once.Do(func() { + defer func() { + d.f = nil + d.p = recover() + if !d.valid { + panic(d.p) + } + }() + d.r1, d.r2 = d.f() + d.valid = true + }) + if !d.valid { + panic(d.p) + } + return d.r1, d.r2 + } +} + +// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition. +// Copied from the Go 1.25 stdlib implementation. +type CmpOrdered interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | + ~float32 | ~float64 | + ~string +} + +// isNaN reports whether x is a NaN without requiring the math package. +// This will always return false if T is not floating-point. +// Copied from the Go 1.25 stdlib implementation. +func isNaN[T CmpOrdered](x T) bool { + return x != x +} + +// CmpCompare is equivalent to Go 1.21's cmp.Compare. +// Copied from the Go 1.25 stdlib implementation. +func CmpCompare[T CmpOrdered](x, y T) int { + xNaN := isNaN(x) + yNaN := isNaN(y) + if xNaN { + if yNaN { + return 0 + } + return -1 + } + if yNaN { + return +1 + } + if x < y { + return -1 + } + if x > y { + return +1 + } + return 0 +} + +// Max2 is equivalent to Go 1.21's max builtin for two parameters. +func Max2[T CmpOrdered](x, y T) T { + m := x + if y > m { + m = y + } + return m +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go new file mode 100644 index 00000000..cb6de418 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion/kernel_linux.go @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Copyright (C) 2022 The Go Authors. All rights reserved. +// Copyright (C) 2025 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.BSD file. + +// The parsing logic is very loosely based on the Go stdlib's +// src/internal/syscall/unix/kernel_version_linux.go but with an API that looks +// a bit like runc's libcontainer/system/kernelversion. +// +// TODO(cyphar): This API has been copied around to a lot of different projects +// (Docker, containerd, runc, and now filepath-securejoin) -- maybe we should +// put it in a separate project? + +// Package kernelversion provides a simple mechanism for checking whether the +// running kernel is at least as new as some baseline kernel version. This is +// often useful when checking for features that would be too complicated to +// test support for (or in cases where we know that some kernel features in +// backport-heavy kernels are broken and need to be avoided). +package kernelversion + +import ( + "bytes" + "errors" + "fmt" + "strconv" + "strings" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" +) + +// KernelVersion is a numeric representation of the key numerical elements of a +// kernel version (for instance, "4.1.2-default-1" would be represented as +// KernelVersion{4, 1, 2}). +type KernelVersion []uint64 + +func (kver KernelVersion) String() string { + var str strings.Builder + for idx, elem := range kver { + if idx != 0 { + _, _ = str.WriteRune('.') + } + _, _ = str.WriteString(strconv.FormatUint(elem, 10)) + } + return str.String() +} + +var errInvalidKernelVersion = errors.New("invalid kernel version") + +// parseKernelVersion parses a string and creates a KernelVersion based on it. +func parseKernelVersion(kverStr string) (KernelVersion, error) { + kver := make(KernelVersion, 1, 3) + for idx, ch := range kverStr { + if '0' <= ch && ch <= '9' { + v := &kver[len(kver)-1] + *v = (*v * 10) + uint64(ch-'0') + } else { + if idx == 0 || kverStr[idx-1] < '0' || '9' < kverStr[idx-1] { + // "." must be preceded by a digit while in version section + return nil, fmt.Errorf("%w %q: kernel version has dot(s) followed by non-digit in version section", errInvalidKernelVersion, kverStr) + } + if ch != '.' { + break + } + kver = append(kver, 0) + } + } + if len(kver) < 2 { + return nil, fmt.Errorf("%w %q: kernel versions must contain at least two components", errInvalidKernelVersion, kverStr) + } + return kver, nil +} + +// getKernelVersion gets the current kernel version. +var getKernelVersion = gocompat.SyncOnceValues(func() (KernelVersion, error) { + var uts unix.Utsname + if err := unix.Uname(&uts); err != nil { + return nil, err + } + // Remove the \x00 from the release. + release := uts.Release[:] + return parseKernelVersion(string(release[:bytes.IndexByte(release, 0)])) +}) + +// GreaterEqualThan returns true if the the host kernel version is greater than +// or equal to the provided [KernelVersion]. When doing this comparison, any +// non-numerical suffixes of the host kernel version are ignored. +// +// If the number of components provided is not equal to the number of numerical +// components of the host kernel version, any missing components are treated as +// 0. This means that GreaterEqualThan(KernelVersion{4}) will be treated the +// same as GreaterEqualThan(KernelVersion{4, 0, 0, ..., 0, 0}), and that if the +// host kernel version is "4" then GreaterEqualThan(KernelVersion{4, 1}) will +// return false (because the host version will be treated as "4.0"). +func GreaterEqualThan(wantKver KernelVersion) (bool, error) { + hostKver, err := getKernelVersion() + if err != nil { + return false, err + } + + // Pad out the kernel version lengths to match one another. + cmpLen := gocompat.Max2(len(hostKver), len(wantKver)) + hostKver = append(hostKver, make(KernelVersion, cmpLen-len(hostKver))...) + wantKver = append(wantKver, make(KernelVersion, cmpLen-len(wantKver))...) + + for i := 0; i < cmpLen; i++ { + switch gocompat.CmpCompare(hostKver[i], wantKver[i]) { + case -1: + // host < want + return false, nil + case +1: + // host > want + return true, nil + case 0: + continue + } + } + // equal version values + return true, nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go new file mode 100644 index 00000000..4635714f --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/doc.go @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MPL-2.0 + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package linux returns information about what features are supported on the +// running kernel. +package linux diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go new file mode 100644 index 00000000..b29905bf --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/mount_linux.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package linux + +import ( + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/kernelversion" +) + +// HasNewMountAPI returns whether the new fsopen(2) mount API is supported on +// the running kernel. +var HasNewMountAPI = gocompat.SyncOnceValue(func() bool { + // All of the pieces of the new mount API we use (fsopen, fsconfig, + // fsmount, open_tree) were added together in Linux 5.2[1,2], so we can + // just check for one of the syscalls and the others should also be + // available. + // + // Just try to use open_tree(2) to open a file without OPEN_TREE_CLONE. + // This is equivalent to openat(2), but tells us if open_tree is + // available (and thus all of the other basic new mount API syscalls). + // open_tree(2) is most light-weight syscall to test here. + // + // [1]: merge commit 400913252d09 + // [2]: + fd, err := unix.OpenTree(-int(unix.EBADF), "/", unix.OPEN_TREE_CLOEXEC) + if err != nil { + return false + } + _ = unix.Close(fd) + + // RHEL 8 has a backport of fsopen(2) that appears to have some very + // difficult to debug performance pathology. As such, it seems prudent to + // simply reject pre-5.2 kernels. + isNotBackport, _ := kernelversion.GreaterEqualThan(kernelversion.KernelVersion{5, 2}) + return isNotBackport +}) diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go new file mode 100644 index 00000000..dc5f65ce --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux/openat2_linux.go @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package linux + +import ( + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" +) + +// sawOpenat2Error stores whether we have seen an error from HasOpenat2. This +// is a one-way toggle, so as soon as we see an error we "lock" into that mode. +// We cannot use sync.OnceValue to store the success/fail state once because it +// is possible for the program we are running in to apply a seccomp-bpf filter +// and thus disable openat2 during execution. +var sawOpenat2Error gocompat.Bool + +// HasOpenat2 returns whether openat2(2) is supported on the running kernel. +var HasOpenat2 = func() bool { + if sawOpenat2Error.Load() { + return false + } + + fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT, + }) + if err != nil { + sawOpenat2Error.Store(true) // doesn't matter if we race here + return false + } + _ = unix.Close(fd) + return true +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go new file mode 100644 index 00000000..21e0a62e --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_linux.go @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package procfs provides a safe API for operating on /proc on Linux. Note +// that this is the *internal* procfs API, mainy needed due to Go's +// restrictions on cyclic dependencies and its incredibly minimal visibility +// system without making a separate internal/ package. +package procfs + +import ( + "errors" + "fmt" + "io" + "os" + "runtime" + "strconv" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" +) + +// The kernel guarantees that the root inode of a procfs mount has an +// f_type of PROC_SUPER_MAGIC and st_ino of PROC_ROOT_INO. +const ( + procSuperMagic = 0x9fa0 // PROC_SUPER_MAGIC + procRootIno = 1 // PROC_ROOT_INO +) + +// verifyProcHandle checks that the handle is from a procfs filesystem. +// Contrast this to [verifyProcRoot], which also verifies that the handle is +// the root of a procfs mount. +func verifyProcHandle(procHandle fd.Fd) error { + if statfs, err := fd.Fstatfs(procHandle); err != nil { + return err + } else if statfs.Type != procSuperMagic { + return fmt.Errorf("%w: incorrect procfs root filesystem type 0x%x", errUnsafeProcfs, statfs.Type) + } + return nil +} + +// verifyProcRoot verifies that the handle is the root of a procfs filesystem. +// Contrast this to [verifyProcHandle], which only verifies if the handle is +// some file on procfs (regardless of what file it is). +func verifyProcRoot(procRoot fd.Fd) error { + if err := verifyProcHandle(procRoot); err != nil { + return err + } + if stat, err := fd.Fstat(procRoot); err != nil { + return err + } else if stat.Ino != procRootIno { + return fmt.Errorf("%w: incorrect procfs root inode number %d", errUnsafeProcfs, stat.Ino) + } + return nil +} + +type procfsFeatures struct { + // hasSubsetPid was added in Linux 5.8, along with hidepid=ptraceable (and + // string-based hidepid= values). Before this patchset, it was not really + // safe to try to modify procfs superblock flags because the superblock was + // shared -- so if this feature is not available, **you should not set any + // superblock flags**. + // + // 6814ef2d992a ("proc: add option to mount only a pids subset") + // fa10fed30f25 ("proc: allow to mount many instances of proc in one pid namespace") + // 24a71ce5c47f ("proc: instantiate only pids that we can ptrace on 'hidepid=4' mount option") + // 1c6c4d112e81 ("proc: use human-readable values for hidepid") + // 9ff7258575d5 ("Merge branch 'proc-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace") + hasSubsetPid bool +} + +var getProcfsFeatures = gocompat.SyncOnceValue(func() procfsFeatures { + if !linux.HasNewMountAPI() { + return procfsFeatures{} + } + procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC) + if err != nil { + return procfsFeatures{} + } + defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here + + return procfsFeatures{ + hasSubsetPid: unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") == nil, + } +}) + +func newPrivateProcMount(subset bool) (_ *Handle, Err error) { + procfsCtx, err := fd.Fsopen("proc", unix.FSOPEN_CLOEXEC) + if err != nil { + return nil, err + } + defer procfsCtx.Close() //nolint:errcheck // close failures aren't critical here + + if subset && getProcfsFeatures().hasSubsetPid { + // Try to configure hidepid=ptraceable,subset=pid if possible, but + // ignore errors. + _ = unix.FsconfigSetString(int(procfsCtx.Fd()), "hidepid", "ptraceable") + _ = unix.FsconfigSetString(int(procfsCtx.Fd()), "subset", "pid") + } + + // Get an actual handle. + if err := unix.FsconfigCreate(int(procfsCtx.Fd())); err != nil { + return nil, os.NewSyscallError("fsconfig create procfs", err) + } + // TODO: Output any information from the fscontext log to debug logs. + procRoot, err := fd.Fsmount(procfsCtx, unix.FSMOUNT_CLOEXEC, unix.MS_NODEV|unix.MS_NOEXEC|unix.MS_NOSUID) + if err != nil { + return nil, err + } + defer func() { + if Err != nil { + _ = procRoot.Close() + } + }() + return newHandle(procRoot) +} + +func clonePrivateProcMount() (_ *Handle, Err error) { + // Try to make a clone without using AT_RECURSIVE if we can. If this works, + // we can be sure there are no over-mounts and so if the root is valid then + // we're golden. Otherwise, we have to deal with over-mounts. + procRoot, err := fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE) + if err != nil || hookForcePrivateProcRootOpenTreeAtRecursive(procRoot) { + procRoot, err = fd.OpenTree(nil, "/proc", unix.OPEN_TREE_CLONE|unix.AT_RECURSIVE) + } + if err != nil { + return nil, fmt.Errorf("creating a detached procfs clone: %w", err) + } + defer func() { + if Err != nil { + _ = procRoot.Close() + } + }() + return newHandle(procRoot) +} + +func privateProcRoot(subset bool) (*Handle, error) { + if !linux.HasNewMountAPI() || hookForceGetProcRootUnsafe() { + return nil, fmt.Errorf("new mount api: %w", unix.ENOTSUP) + } + // Try to create a new procfs mount from scratch if we can. This ensures we + // can get a procfs mount even if /proc is fake (for whatever reason). + procRoot, err := newPrivateProcMount(subset) + if err != nil || hookForcePrivateProcRootOpenTree(procRoot) { + // Try to clone /proc then... + procRoot, err = clonePrivateProcMount() + } + return procRoot, err +} + +func unsafeHostProcRoot() (_ *Handle, Err error) { + procRoot, err := os.OpenFile("/proc", unix.O_PATH|unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) + if err != nil { + return nil, err + } + defer func() { + if Err != nil { + _ = procRoot.Close() + } + }() + return newHandle(procRoot) +} + +// Handle is a wrapper around an *os.File handle to "/proc", which can be used +// to do further procfs-related operations in a safe way. +type Handle struct { + Inner fd.Fd + // Does this handle have subset=pid set? + isSubset bool +} + +func newHandle(procRoot fd.Fd) (*Handle, error) { + if err := verifyProcRoot(procRoot); err != nil { + // This is only used in methods that + _ = procRoot.Close() + return nil, err + } + proc := &Handle{Inner: procRoot} + // With subset=pid we can be sure that /proc/uptime will not exist. + if err := fd.Faccessat(proc.Inner, "uptime", unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil { + proc.isSubset = errors.Is(err, os.ErrNotExist) + } + return proc, nil +} + +// Close closes the underlying file for the Handle. +func (proc *Handle) Close() error { return proc.Inner.Close() } + +var getCachedProcRoot = gocompat.SyncOnceValue(func() *Handle { + procRoot, err := getProcRoot(true) + if err != nil { + return nil // just don't cache if we see an error + } + if !procRoot.isSubset { + return nil // we only cache verified subset=pid handles + } + + // Disarm (*Handle).Close() to stop someone from accidentally closing + // the global handle. + procRoot.Inner = fd.NopCloser(procRoot.Inner) + return procRoot +}) + +// OpenProcRoot tries to open a "safer" handle to "/proc". +func OpenProcRoot() (*Handle, error) { + if proc := getCachedProcRoot(); proc != nil { + return proc, nil + } + return getProcRoot(true) +} + +// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or +// masked paths (but also without "subset=pid"). +func OpenUnsafeProcRoot() (*Handle, error) { return getProcRoot(false) } + +func getProcRoot(subset bool) (*Handle, error) { + proc, err := privateProcRoot(subset) + if err != nil { + // Fall back to using a /proc handle if making a private mount failed. + // If we have openat2, at least we can avoid some kinds of over-mount + // attacks, but without openat2 there's not much we can do. + proc, err = unsafeHostProcRoot() + } + return proc, err +} + +var hasProcThreadSelf = gocompat.SyncOnceValue(func() bool { + return unix.Access("/proc/thread-self/", unix.F_OK) == nil +}) + +var errUnsafeProcfs = errors.New("unsafe procfs detected") + +// lookup is a very minimal wrapper around [procfsLookupInRoot] which is +// intended to be called from the external API. +func (proc *Handle) lookup(subpath string) (*os.File, error) { + handle, err := procfsLookupInRoot(proc.Inner, subpath) + if err != nil { + return nil, err + } + return handle, nil +} + +// procfsBase is an enum indicating the prefix of a subpath in operations +// involving [Handle]s. +type procfsBase string + +const ( + // ProcRoot refers to the root of the procfs (i.e., "/proc/"). + ProcRoot procfsBase = "/proc" + // ProcSelf refers to the current process' subdirectory (i.e., + // "/proc/self/"). + ProcSelf procfsBase = "/proc/self" + // ProcThreadSelf refers to the current thread's subdirectory (i.e., + // "/proc/thread-self/"). In multi-threaded programs (i.e., all Go + // programs) where one thread has a different CLONE_FS, it is possible for + // "/proc/self" to point the wrong thread and so "/proc/thread-self" may be + // necessary. Note that on pre-3.17 kernels, "/proc/thread-self" doesn't + // exist and so a fallback will be used in that case. + ProcThreadSelf procfsBase = "/proc/thread-self" + // TODO: Switch to an interface setup so we can have a more type-safe + // version of ProcPid and remove the need to worry about invalid string + // values. +) + +// prefix returns a prefix that can be used with the given [Handle]. +func (base procfsBase) prefix(proc *Handle) (string, error) { + switch base { + case ProcRoot: + return ".", nil + case ProcSelf: + return "self", nil + case ProcThreadSelf: + threadSelf := "thread-self" + if !hasProcThreadSelf() || hookForceProcSelfTask() { + // Pre-3.17 kernels don't have /proc/thread-self, so do it + // manually. + threadSelf = "self/task/" + strconv.Itoa(unix.Gettid()) + if err := fd.Faccessat(proc.Inner, threadSelf, unix.F_OK, unix.AT_SYMLINK_NOFOLLOW); err != nil || hookForceProcSelf() { + // In this case, we running in a pid namespace that doesn't + // match the /proc mount we have. This can happen inside runc. + // + // Unfortunately, there is no nice way to get the correct TID + // to use here because of the age of the kernel, so we have to + // just use /proc/self and hope that it works. + threadSelf = "self" + } + } + return threadSelf, nil + } + return "", fmt.Errorf("invalid procfs base %q", base) +} + +// ProcThreadSelfCloser is a callback that needs to be called when you are done +// operating on an [os.File] fetched using [ProcThreadSelf]. +// +// [os.File]: https://pkg.go.dev/os#File +type ProcThreadSelfCloser func() + +// open is the core lookup operation for [Handle]. It returns a handle to +// "/proc//". If the returned [ProcThreadSelfCloser] is non-nil, +// you should call it after you are done interacting with the returned handle. +// +// In general you should use prefer to use the other helpers, as they remove +// the need to interact with [procfsBase] and do not return a nil +// [ProcThreadSelfCloser] for [procfsBase] values other than [ProcThreadSelf] +// where it is necessary. +func (proc *Handle) open(base procfsBase, subpath string) (_ *os.File, closer ProcThreadSelfCloser, Err error) { + prefix, err := base.prefix(proc) + if err != nil { + return nil, nil, err + } + subpath = prefix + "/" + subpath + + switch base { + case ProcRoot: + file, err := proc.lookup(subpath) + if errors.Is(err, os.ErrNotExist) { + // The Handle handle in use might be a subset=pid one, which will + // result in spurious errors. In this case, just open a temporary + // unmasked procfs handle for this operation. + proc, err2 := OpenUnsafeProcRoot() // !subset=pid + if err2 != nil { + return nil, nil, err + } + defer proc.Close() //nolint:errcheck // close failures aren't critical here + + file, err = proc.lookup(subpath) + } + return file, nil, err + + case ProcSelf: + file, err := proc.lookup(subpath) + return file, nil, err + + case ProcThreadSelf: + // We need to lock our thread until the caller is done with the handle + // because between getting the handle and using it we could get + // interrupted by the Go runtime and hit the case where the underlying + // thread is swapped out and the original thread is killed, resulting + // in pull-your-hair-out-hard-to-debug issues in the caller. + runtime.LockOSThread() + defer func() { + if Err != nil { + runtime.UnlockOSThread() + closer = nil + } + }() + + file, err := proc.lookup(subpath) + return file, runtime.UnlockOSThread, err + } + // should never be reached + return nil, nil, fmt.Errorf("[internal error] invalid procfs base %q", base) +} + +// OpenThreadSelf returns a handle to "/proc/thread-self/" (or an +// equivalent handle on older kernels where "/proc/thread-self" doesn't exist). +// Once finished with the handle, you must call the returned closer function +// (runtime.UnlockOSThread). You must not pass the returned *os.File to other +// Go threads or use the handle after calling the closer. +func (proc *Handle) OpenThreadSelf(subpath string) (_ *os.File, _ ProcThreadSelfCloser, Err error) { + return proc.open(ProcThreadSelf, subpath) +} + +// OpenSelf returns a handle to /proc/self/. +func (proc *Handle) OpenSelf(subpath string) (*os.File, error) { + file, closer, err := proc.open(ProcSelf, subpath) + assert.Assert(closer == nil, "closer for ProcSelf must be nil") + return file, err +} + +// OpenRoot returns a handle to /proc/. +func (proc *Handle) OpenRoot(subpath string) (*os.File, error) { + file, closer, err := proc.open(ProcRoot, subpath) + assert.Assert(closer == nil, "closer for ProcRoot must be nil") + return file, err +} + +// OpenPid returns a handle to /proc/$pid/ (pid can be a pid or tid). +// This is mainly intended for usage when operating on other processes. +func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) { + return proc.OpenRoot(strconv.Itoa(pid) + "/" + subpath) +} + +// checkSubpathOvermount checks if the dirfd and path combination is on the +// same mount as the given root. +func checkSubpathOvermount(root, dir fd.Fd, path string) error { + // Get the mntID of our procfs handle. + expectedMountID, err := fd.GetMountID(root, "") + if err != nil { + return fmt.Errorf("get root mount id: %w", err) + } + // Get the mntID of the target magic-link. + gotMountID, err := fd.GetMountID(dir, path) + if err != nil { + return fmt.Errorf("get subpath mount id: %w", err) + } + // As long as the directory mount is alive, even with wrapping mount IDs, + // we would expect to see a different mount ID here. (Of course, if we're + // using unsafeHostProcRoot() then an attaker could change this after we + // did this check.) + if expectedMountID != gotMountID { + return fmt.Errorf("%w: subpath %s/%s has an overmount obscuring the real path (mount ids do not match %d != %d)", + errUnsafeProcfs, dir.Name(), path, expectedMountID, gotMountID) + } + return nil +} + +// Readlink performs a readlink operation on "/proc//" in a way +// that should be free from race attacks. This is most commonly used to get the +// real path of a file by looking at "/proc/self/fd/$n", with the same safety +// protections as [Open] (as well as some additional checks against +// overmounts). +func (proc *Handle) Readlink(base procfsBase, subpath string) (string, error) { + link, closer, err := proc.open(base, subpath) + if closer != nil { + defer closer() + } + if err != nil { + return "", fmt.Errorf("get safe %s/%s handle: %w", base, subpath, err) + } + defer link.Close() //nolint:errcheck // close failures aren't critical here + + // Try to detect if there is a mount on top of the magic-link. This should + // be safe in general (a mount on top of the path afterwards would not + // affect the handle itself) and will definitely be safe if we are using + // privateProcRoot() (at least since Linux 5.12[1], when anonymous mount + // namespaces were completely isolated from external mounts including mount + // propagation events). + // + // [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts + // onto targets that reside on shared mounts"). + if err := checkSubpathOvermount(proc.Inner, link, ""); err != nil { + return "", fmt.Errorf("check safety of %s/%s magiclink: %w", base, subpath, err) + } + + // readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See Linux commit + // 65cfc6722361 ("readlinkat(), fchownat() and fstatat() with empty + // relative pathnames"). + return fd.Readlinkat(link, "") +} + +// ProcSelfFdReadlink gets the real path of the given file by looking at +// readlink(/proc/thread-self/fd/$n). +// +// This is just a wrapper around [Handle.Readlink]. +func ProcSelfFdReadlink(fd fd.Fd) (string, error) { + procRoot, err := OpenProcRoot() // subset=pid + if err != nil { + return "", err + } + defer procRoot.Close() //nolint:errcheck // close failures aren't critical here + + fdPath := "fd/" + strconv.Itoa(int(fd.Fd())) + return procRoot.Readlink(ProcThreadSelf, fdPath) +} + +// CheckProcSelfFdPath returns whether the given file handle matches the +// expected path. (This is inherently racy.) +func CheckProcSelfFdPath(path string, file fd.Fd) error { + if err := fd.IsDeadInode(file); err != nil { + return err + } + actualPath, err := ProcSelfFdReadlink(file) + if err != nil { + return fmt.Errorf("get path of handle: %w", err) + } + if actualPath != path { + return fmt.Errorf("%w: handle path %q doesn't match expected path %q", internal.ErrPossibleBreakout, actualPath, path) + } + return nil +} + +// ReopenFd takes an existing file descriptor and "re-opens" it through +// /proc/thread-self/fd/. This allows for O_PATH file descriptors to be +// upgraded to regular file descriptors, as well as changing the open mode of a +// regular file descriptor. Some filesystems have unique handling of open(2) +// which make this incredibly useful (such as /dev/ptmx). +func ReopenFd(handle fd.Fd, flags int) (*os.File, error) { + procRoot, err := OpenProcRoot() // subset=pid + if err != nil { + return nil, err + } + defer procRoot.Close() //nolint:errcheck // close failures aren't critical here + + // We can't operate on /proc/thread-self/fd/$n directly when doing a + // re-open, so we need to open /proc/thread-self/fd and then open a single + // final component. + procFdDir, closer, err := procRoot.OpenThreadSelf("fd/") + if err != nil { + return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err) + } + defer procFdDir.Close() //nolint:errcheck // close failures aren't critical here + defer closer() + + // Try to detect if there is a mount on top of the magic-link we are about + // to open. If we are using unsafeHostProcRoot(), this could change after + // we check it (and there's nothing we can do about that) but for + // privateProcRoot() this should be guaranteed to be safe (at least since + // Linux 5.12[1], when anonymous mount namespaces were completely isolated + // from external mounts including mount propagation events). + // + // [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts + // onto targets that reside on shared mounts"). + fdStr := strconv.Itoa(int(handle.Fd())) + if err := checkSubpathOvermount(procRoot.Inner, procFdDir, fdStr); err != nil { + return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err) + } + + flags |= unix.O_CLOEXEC + // Rather than just wrapping fd.Openat, open-code it so we can copy + // handle.Name(). + reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0) + if err != nil { + return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err) + } + return os.NewFile(uintptr(reopenFd), handle.Name()), nil +} + +// Test hooks used in the procfs tests to verify that the fallback logic works. +// See testing_mocks_linux_test.go and procfs_linux_test.go for more details. +var ( + hookForcePrivateProcRootOpenTree = hookDummyFile + hookForcePrivateProcRootOpenTreeAtRecursive = hookDummyFile + hookForceGetProcRootUnsafe = hookDummy + + hookForceProcSelfTask = hookDummy + hookForceProcSelf = hookDummy +) + +func hookDummy() bool { return false } +func hookDummyFile(_ io.Closer) bool { return false } diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go new file mode 100644 index 00000000..1ad1f18e --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs/procfs_lookup_linux.go @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// This code is adapted to be a minimal version of the libpathrs proc resolver +// . +// As we only need O_PATH|O_NOFOLLOW support, this is not too much to port. + +package procfs + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/internal/consts" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" +) + +// procfsLookupInRoot is a stripped down version of completeLookupInRoot, +// entirely designed to support the very small set of features necessary to +// make procfs handling work. Unlike completeLookupInRoot, we always have +// O_PATH|O_NOFOLLOW behaviour for trailing symlinks. +// +// The main restrictions are: +// +// - ".." is not supported (as it requires either os.Root-style replays, +// which is more bug-prone; or procfs verification, which is not possible +// due to re-entrancy issues). +// - Absolute symlinks for the same reason (and all absolute symlinks in +// procfs are magic-links, which we want to skip anyway). +// - If statx is supported (checkSymlinkOvermount), any mount-point crossings +// (which is the main attack of concern against /proc). +// - Partial lookups are not supported, so the symlink stack is not needed. +// - Trailing slash special handling is not necessary in most cases (if we +// operating on procfs, it's usually with programmer-controlled strings +// that will then be re-opened), so we skip it since whatever re-opens it +// can deal with it. It's a creature comfort anyway. +// +// If the system supports openat2(), this is implemented using equivalent flags +// (RESOLVE_BENEATH | RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS). +func procfsLookupInRoot(procRoot fd.Fd, unsafePath string) (Handle *os.File, _ error) { + unsafePath = filepath.ToSlash(unsafePath) // noop + + // Make sure that an empty unsafe path still returns something sane, even + // with openat2 (which doesn't have AT_EMPTY_PATH semantics yet). + if unsafePath == "" { + unsafePath = "." + } + + // This is already checked by getProcRoot, but make sure here since the + // core security of this lookup is based on this assumption. + if err := verifyProcRoot(procRoot); err != nil { + return nil, err + } + + if linux.HasOpenat2() { + // We prefer being able to use RESOLVE_NO_XDEV if we can, to be + // absolutely sure we are operating on a clean /proc handle that + // doesn't have any cheeky overmounts that could trick us (including + // symlink mounts on top of /proc/thread-self). RESOLVE_BENEATH isn't + // strictly needed, but just use it since we have it. + // + // NOTE: /proc/self is technically a magic-link (the contents of the + // symlink are generated dynamically), but it doesn't use + // nd_jump_link() so RESOLVE_NO_MAGICLINKS allows it. + // + // TODO: It would be nice to have RESOLVE_NO_DOTDOT, purely for + // self-consistency with the backup O_PATH resolver. + handle, err := fd.Openat2(procRoot, unsafePath, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_MAGICLINKS, + }) + if err != nil { + // TODO: Once we bump the minimum Go version to 1.20, we can use + // multiple %w verbs for this wrapping. For now we need to use a + // compatibility shim for older Go versions. + // err = fmt.Errorf("%w: %w", errUnsafeProcfs, err) + return nil, gocompat.WrapBaseError(err, errUnsafeProcfs) + } + return handle, nil + } + + // To mirror openat2(RESOLVE_BENEATH), we need to return an error if the + // path is absolute. + if path.IsAbs(unsafePath) { + return nil, fmt.Errorf("%w: cannot resolve absolute paths in procfs resolver", internal.ErrPossibleBreakout) + } + + currentDir, err := fd.Dup(procRoot) + if err != nil { + return nil, fmt.Errorf("clone root fd: %w", err) + } + defer func() { + // If a handle is not returned, close the internal handle. + if Handle == nil { + _ = currentDir.Close() + } + }() + + var ( + linksWalked int + currentPath string + remainingPath = unsafePath + ) + for remainingPath != "" { + // Get the next path component. + var part string + if i := strings.IndexByte(remainingPath, '/'); i == -1 { + part, remainingPath = remainingPath, "" + } else { + part, remainingPath = remainingPath[:i], remainingPath[i+1:] + } + if part == "" { + // no-op component, but treat it the same as "." + part = "." + } + if part == ".." { + // not permitted + return nil, fmt.Errorf("%w: cannot walk into '..' in procfs resolver", internal.ErrPossibleBreakout) + } + + // Apply the component lexically to the path we are building. + // currentPath does not contain any symlinks, and we are lexically + // dealing with a single component, so it's okay to do a filepath.Clean + // here. (Not to mention that ".." isn't allowed.) + nextPath := path.Join("/", currentPath, part) + // If we logically hit the root, just clone the root rather than + // opening the part and doing all of the other checks. + if nextPath == "/" { + // Jump to root. + rootClone, err := fd.Dup(procRoot) + if err != nil { + return nil, fmt.Errorf("clone root fd: %w", err) + } + _ = currentDir.Close() + currentDir = rootClone + currentPath = nextPath + continue + } + + // Try to open the next component. + nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return nil, err + } + + // Make sure we are still on procfs and haven't crossed mounts. + if err := verifyProcHandle(nextDir); err != nil { + _ = nextDir.Close() + return nil, fmt.Errorf("check %q component is on procfs: %w", part, err) + } + if err := checkSubpathOvermount(procRoot, nextDir, ""); err != nil { + _ = nextDir.Close() + return nil, fmt.Errorf("check %q component is not overmounted: %w", part, err) + } + + // We are emulating O_PATH|O_NOFOLLOW, so we only need to traverse into + // trailing symlinks if we are not the final component. Otherwise we + // can just return the currentDir. + if remainingPath != "" { + st, err := nextDir.Stat() + if err != nil { + _ = nextDir.Close() + return nil, fmt.Errorf("stat component %q: %w", part, err) + } + + if st.Mode()&os.ModeType == os.ModeSymlink { + // readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See + // Linux commit 65cfc6722361 ("readlinkat(), fchownat() and + // fstatat() with empty relative pathnames"). + linkDest, err := fd.Readlinkat(nextDir, "") + // We don't need the handle anymore. + _ = nextDir.Close() + if err != nil { + return nil, err + } + + linksWalked++ + if linksWalked > consts.MaxSymlinkLimit { + return nil, &os.PathError{Op: "securejoin.procfsLookupInRoot", Path: "/proc/" + unsafePath, Err: unix.ELOOP} + } + + // Update our logical remaining path. + remainingPath = linkDest + "/" + remainingPath + // Absolute symlinks are probably magiclinks, we reject them. + if path.IsAbs(linkDest) { + return nil, fmt.Errorf("%w: cannot jump to / in procfs resolver -- possible magiclink", internal.ErrPossibleBreakout) + } + continue + } + } + + // Walk into the next component. + _ = currentDir.Close() + currentDir = nextDir + currentPath = nextPath + } + + // One final sanity-check. + if err := verifyProcHandle(currentDir); err != nil { + return nil, fmt.Errorf("check final handle is on procfs: %w", err) + } + if err := checkSubpathOvermount(procRoot, currentDir, ""); err != nil { + return nil, fmt.Errorf("check final handle is not overmounted: %w", err) + } + return currentDir, nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go new file mode 100644 index 00000000..05d7dbcc --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/lookup_linux.go @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/internal/consts" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" +) + +type symlinkStackEntry struct { + // (dir, remainingPath) is what we would've returned if the link didn't + // exist. This matches what openat2(RESOLVE_IN_ROOT) would return in + // this case. + dir *os.File + remainingPath string + // linkUnwalked is the remaining path components from the original + // Readlink which we have yet to walk. When this slice is empty, we + // drop the link from the stack. + linkUnwalked []string +} + +func (se symlinkStackEntry) String() string { + return fmt.Sprintf("<%s>/%s [->%s]", se.dir.Name(), se.remainingPath, strings.Join(se.linkUnwalked, "/")) +} + +func (se symlinkStackEntry) Close() { + _ = se.dir.Close() +} + +type symlinkStack []*symlinkStackEntry + +func (s *symlinkStack) IsEmpty() bool { + return s == nil || len(*s) == 0 +} + +func (s *symlinkStack) Close() { + if s != nil { + for _, link := range *s { + link.Close() + } + // TODO: Switch to clear once we switch to Go 1.21. + *s = nil + } +} + +var ( + errEmptyStack = errors.New("[internal] stack is empty") + errBrokenSymlinkStack = errors.New("[internal error] broken symlink stack") +) + +func (s *symlinkStack) popPart(part string) error { + if s == nil || s.IsEmpty() { + // If there is nothing in the symlink stack, then the part was from the + // real path provided by the user, and this is a no-op. + return errEmptyStack + } + if part == "." { + // "." components are no-ops -- we drop them when doing SwapLink. + return nil + } + + tailEntry := (*s)[len(*s)-1] + + // Double-check that we are popping the component we expect. + if len(tailEntry.linkUnwalked) == 0 { + return fmt.Errorf("%w: trying to pop component %q of empty stack entry %s", errBrokenSymlinkStack, part, tailEntry) + } + headPart := tailEntry.linkUnwalked[0] + if headPart != part { + return fmt.Errorf("%w: trying to pop component %q but the last stack entry is %s (%q)", errBrokenSymlinkStack, part, tailEntry, headPart) + } + + // Drop the component, but keep the entry around in case we are dealing + // with a "tail-chained" symlink. + tailEntry.linkUnwalked = tailEntry.linkUnwalked[1:] + return nil +} + +func (s *symlinkStack) PopPart(part string) error { + if err := s.popPart(part); err != nil { + if errors.Is(err, errEmptyStack) { + // Skip empty stacks. + err = nil + } + return err + } + + // Clean up any of the trailing stack entries that are empty. + for lastGood := len(*s) - 1; lastGood >= 0; lastGood-- { + entry := (*s)[lastGood] + if len(entry.linkUnwalked) > 0 { + break + } + entry.Close() + (*s) = (*s)[:lastGood] + } + return nil +} + +func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) error { + if s == nil { + return nil + } + // Split the link target and clean up any "" parts. + linkTargetParts := gocompat.SlicesDeleteFunc( + strings.Split(linkTarget, "/"), + func(part string) bool { return part == "" || part == "." }) + + // Copy the directory so the caller doesn't close our copy. + dirCopy, err := fd.Dup(dir) + if err != nil { + return err + } + + // Add to the stack. + *s = append(*s, &symlinkStackEntry{ + dir: dirCopy, + remainingPath: remainingPath, + linkUnwalked: linkTargetParts, + }) + return nil +} + +func (s *symlinkStack) SwapLink(linkPart string, dir *os.File, remainingPath, linkTarget string) error { + // If we are currently inside a symlink resolution, remove the symlink + // component from the last symlink entry, but don't remove the entry even + // if it's empty. If we are a "tail-chained" symlink (a trailing symlink we + // hit during a symlink resolution) we need to keep the old symlink until + // we finish the resolution. + if err := s.popPart(linkPart); err != nil { + if !errors.Is(err, errEmptyStack) { + return err + } + // Push the component regardless of whether the stack was empty. + } + return s.push(dir, remainingPath, linkTarget) +} + +func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) { + if s == nil || s.IsEmpty() { + return nil, "", false + } + tailEntry := (*s)[0] + *s = (*s)[1:] + return tailEntry.dir, tailEntry.remainingPath, true +} + +// partialLookupInRoot tries to lookup as much of the request path as possible +// within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing +// component of the requested path, returning a file handle to the final +// existing component and a string containing the remaining path components. +func partialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) { + return lookupInRoot(root, unsafePath, true) +} + +func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) { + handle, remainingPath, err := lookupInRoot(root, unsafePath, false) + if remainingPath != "" && err == nil { + // should never happen + err = fmt.Errorf("[bug] non-empty remaining path when doing a non-partial lookup: %q", remainingPath) + } + // lookupInRoot(partial=false) will always close the handle if an error is + // returned, so no need to double-check here. + return handle, err +} + +func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) { + unsafePath = filepath.ToSlash(unsafePath) // noop + + // This is very similar to SecureJoin, except that we operate on the + // components using file descriptors. We then return the last component we + // managed open, along with the remaining path components not opened. + + // Try to use openat2 if possible. + // + // NOTE: If openat2(2) works normally but fails for this lookup, it is + // probably not a good idea to fall-back to the O_PATH resolver. An + // attacker could find a bug in the O_PATH resolver and uncontionally + // falling back to the O_PATH resolver would form a downgrade attack. + if handle, remainingPath, err := lookupOpenat2(root, unsafePath, partial); err == nil || linux.HasOpenat2() { + return handle, remainingPath, err + } + + // Get the "actual" root path from /proc/self/fd. This is necessary if the + // root is some magic-link like /proc/$pid/root, in which case we want to + // make sure when we do procfs.CheckProcSelfFdPath that we are using the + // correct root path. + logicalRootPath, err := procfs.ProcSelfFdReadlink(root) + if err != nil { + return nil, "", fmt.Errorf("get real root path: %w", err) + } + + currentDir, err := fd.Dup(root) + if err != nil { + return nil, "", fmt.Errorf("clone root fd: %w", err) + } + defer func() { + // If a handle is not returned, close the internal handle. + if Handle == nil { + _ = currentDir.Close() + } + }() + + // symlinkStack is used to emulate how openat2(RESOLVE_IN_ROOT) treats + // dangling symlinks. If we hit a non-existent path while resolving a + // symlink, we need to return the (dir, remainingPath) that we had when we + // hit the symlink (treating the symlink as though it were a regular file). + // The set of (dir, remainingPath) sets is stored within the symlinkStack + // and we add and remove parts when we hit symlink and non-symlink + // components respectively. We need a stack because of recursive symlinks + // (symlinks that contain symlink components in their target). + // + // Note that the stack is ONLY used for book-keeping. All of the actual + // path walking logic is still based on currentPath/remainingPath and + // currentDir (as in SecureJoin). + var symStack *symlinkStack + if partial { + symStack = new(symlinkStack) + defer symStack.Close() + } + + var ( + linksWalked int + currentPath string + remainingPath = unsafePath + ) + for remainingPath != "" { + // Save the current remaining path so if the part is not real we can + // return the path including the component. + oldRemainingPath := remainingPath + + // Get the next path component. + var part string + if i := strings.IndexByte(remainingPath, '/'); i == -1 { + part, remainingPath = remainingPath, "" + } else { + part, remainingPath = remainingPath[:i], remainingPath[i+1:] + } + // If we hit an empty component, we need to treat it as though it is + // "." so that trailing "/" and "//" components on a non-directory + // correctly return the right error code. + if part == "" { + part = "." + } + + // Apply the component lexically to the path we are building. + // currentPath does not contain any symlinks, and we are lexically + // dealing with a single component, so it's okay to do a filepath.Clean + // here. + nextPath := path.Join("/", currentPath, part) + // If we logically hit the root, just clone the root rather than + // opening the part and doing all of the other checks. + if nextPath == "/" { + if err := symStack.PopPart(part); err != nil { + return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err) + } + // Jump to root. + rootClone, err := fd.Dup(root) + if err != nil { + return nil, "", fmt.Errorf("clone root fd: %w", err) + } + _ = currentDir.Close() + currentDir = rootClone + currentPath = nextPath + continue + } + + // Try to open the next component. + nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + switch err { + case nil: + st, err := nextDir.Stat() + if err != nil { + _ = nextDir.Close() + return nil, "", fmt.Errorf("stat component %q: %w", part, err) + } + + switch st.Mode() & os.ModeType { //nolint:exhaustive // just a glorified if statement + case os.ModeSymlink: + // readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See + // Linux commit 65cfc6722361 ("readlinkat(), fchownat() and + // fstatat() with empty relative pathnames"). + linkDest, err := fd.Readlinkat(nextDir, "") + // We don't need the handle anymore. + _ = nextDir.Close() + if err != nil { + return nil, "", err + } + + linksWalked++ + if linksWalked > consts.MaxSymlinkLimit { + return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP} + } + + // Swap out the symlink's component for the link entry itself. + if err := symStack.SwapLink(part, currentDir, oldRemainingPath, linkDest); err != nil { + return nil, "", fmt.Errorf("walking into symlink %q failed: push symlink: %w", part, err) + } + + // Update our logical remaining path. + remainingPath = linkDest + "/" + remainingPath + // Absolute symlinks reset any work we've already done. + if path.IsAbs(linkDest) { + // Jump to root. + rootClone, err := fd.Dup(root) + if err != nil { + return nil, "", fmt.Errorf("clone root fd: %w", err) + } + _ = currentDir.Close() + currentDir = rootClone + currentPath = "/" + } + + default: + // If we are dealing with a directory, simply walk into it. + _ = currentDir.Close() + currentDir = nextDir + currentPath = nextPath + + // The part was real, so drop it from the symlink stack. + if err := symStack.PopPart(part); err != nil { + return nil, "", fmt.Errorf("walking into directory %q failed: %w", part, err) + } + + // If we are operating on a .., make sure we haven't escaped. + // We only have to check for ".." here because walking down + // into a regular component component cannot cause you to + // escape. This mirrors the logic in RESOLVE_IN_ROOT, except we + // have to check every ".." rather than only checking after a + // rename or mount on the system. + if part == ".." { + // Make sure the root hasn't moved. + if err := procfs.CheckProcSelfFdPath(logicalRootPath, root); err != nil { + return nil, "", fmt.Errorf("root path moved during lookup: %w", err) + } + // Make sure the path is what we expect. + fullPath := logicalRootPath + nextPath + if err := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil { + return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err) + } + } + } + + default: + if !partial { + return nil, "", err + } + // If there are any remaining components in the symlink stack, we + // are still within a symlink resolution and thus we hit a dangling + // symlink. So pretend that the first symlink in the stack we hit + // was an ENOENT (to match openat2). + if oldDir, remainingPath, ok := symStack.PopTopSymlink(); ok { + _ = currentDir.Close() + return oldDir, remainingPath, err + } + // We have hit a final component that doesn't exist, so we have our + // partial open result. Note that we have to use the OLD remaining + // path, since the lookup failed. + return currentDir, oldRemainingPath, err + } + } + + // If the unsafePath had a trailing slash, we need to make sure we try to + // do a relative "." open so that we will correctly return an error when + // the final component is a non-directory (to match openat2). In the + // context of openat2, a trailing slash and a trailing "/." are completely + // equivalent. + if strings.HasSuffix(unsafePath, "/") { + nextDir, err := fd.Openat(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + if !partial { + _ = currentDir.Close() + currentDir = nil + } + return currentDir, "", err + } + _ = currentDir.Close() + currentDir = nextDir + } + + // All of the components existed! + return currentDir, "", nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go new file mode 100644 index 00000000..f3c62b0d --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/mkdir_linux.go @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat" + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux" +) + +var errInvalidMode = errors.New("invalid permission mode") + +// modePermExt is like os.ModePerm except that it also includes the set[ug]id +// and sticky bits. +const modePermExt = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky + +//nolint:cyclop // this function needs to handle a lot of cases +func toUnixMode(mode os.FileMode) (uint32, error) { + sysMode := uint32(mode.Perm()) + if mode&os.ModeSetuid != 0 { + sysMode |= unix.S_ISUID + } + if mode&os.ModeSetgid != 0 { + sysMode |= unix.S_ISGID + } + if mode&os.ModeSticky != 0 { + sysMode |= unix.S_ISVTX + } + // We don't allow file type bits. + if mode&os.ModeType != 0 { + return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", errInvalidMode, mode, mode) + } + // We don't allow other unknown modes. + if mode&^modePermExt != 0 || sysMode&unix.S_IFMT != 0 { + return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", errInvalidMode, mode, mode) + } + return sysMode, nil +} + +// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use +// in two respects: +// +// - The caller provides the root directory as an *[os.File] (preferably O_PATH) +// handle. This means that the caller can be sure which root directory is +// being used. Note that this can be emulated by using /proc/self/fd/... as +// the root path with [os.MkdirAll]. +// +// - Once all of the directories have been created, an *[os.File] O_PATH handle +// to the directory at unsafePath is returned to the caller. This is done in +// an effectively-race-free way (an attacker would only be able to swap the +// final directory component), which is not possible to emulate with +// [MkdirAll]. +// +// In addition, the returned handle is obtained far more efficiently than doing +// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after +// doing [MkdirAll]. If you intend to open the directory after creating it, you +// should use MkdirAllHandle. +// +// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin +func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) { + unixMode, err := toUnixMode(mode) + if err != nil { + return nil, err + } + // On Linux, mkdirat(2) (and os.Mkdir) silently ignore the suid and sgid + // bits. We could also silently ignore them but since we have very few + // users it seems more prudent to return an error so users notice that + // these bits will not be set. + if unixMode&^0o1777 != 0 { + return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", errInvalidMode, mode) + } + + // Try to open as much of the path as possible. + currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath) + defer func() { + if Err != nil { + _ = currentDir.Close() + } + }() + if err != nil && !errors.Is(err, unix.ENOENT) { + return nil, fmt.Errorf("find existing subpath of %q: %w", unsafePath, err) + } + + // If there is an attacker deleting directories as we walk into them, + // detect this proactively. Note this is guaranteed to detect if the + // attacker deleted any part of the tree up to currentDir. + // + // Once we walk into a dead directory, partialLookupInRoot would not be + // able to walk further down the tree (directories must be empty before + // they are deleted), and if the attacker has removed the entire tree we + // can be sure that anything that was originally inside a dead directory + // must also be deleted and thus is a dead directory in its own right. + // + // This is mostly a quality-of-life check, because mkdir will simply fail + // later if the attacker deletes the tree after this check. + if err := fd.IsDeadInode(currentDir); err != nil { + return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err) + } + + // Re-open the path to match the O_DIRECTORY reopen loop later (so that we + // always return a non-O_PATH handle). We also check that we actually got a + // directory. + if reopenDir, err := Reopen(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) { + return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR) + } else if err != nil { + return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err) + } else { //nolint:revive // indent-error-flow lint doesn't make sense here + _ = currentDir.Close() + currentDir = reopenDir + } + + remainingParts := strings.Split(remainingPath, string(filepath.Separator)) + if gocompat.SlicesContains(remainingParts, "..") { + // The path contained ".." components after the end of the "real" + // components. We could try to safely resolve ".." here but that would + // add a bunch of extra logic for something that it's not clear even + // needs to be supported. So just return an error. + // + // If we do filepath.Clean(remainingPath) then we end up with the + // problem that ".." can erase a trailing dangling symlink and produce + // a path that doesn't quite match what the user asked for. + return nil, fmt.Errorf("%w: yet-to-be-created path %q contains '..' components", unix.ENOENT, remainingPath) + } + + // Create the remaining components. + for _, part := range remainingParts { + switch part { + case "", ".": + // Skip over no-op paths. + continue + } + + // NOTE: mkdir(2) will not follow trailing symlinks, so we can safely + // create the final component without worrying about symlink-exchange + // attacks. + // + // If we get -EEXIST, it's possible that another program created the + // directory at the same time as us. In that case, just continue on as + // if we created it (if the created inode is not a directory, the + // following open call will fail). + if err := unix.Mkdirat(int(currentDir.Fd()), part, unixMode); err != nil && !errors.Is(err, unix.EEXIST) { + err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err} + // Make the error a bit nicer if the directory is dead. + if deadErr := fd.IsDeadInode(currentDir); deadErr != nil { + // TODO: Once we bump the minimum Go version to 1.20, we can use + // multiple %w verbs for this wrapping. For now we need to use a + // compatibility shim for older Go versions. + // err = fmt.Errorf("%w (%w)", err, deadErr) + err = gocompat.WrapBaseError(err, deadErr) + } + return nil, err + } + + // Get a handle to the next component. O_DIRECTORY means we don't need + // to use O_PATH. + var nextDir *os.File + if linux.HasOpenat2() { + nextDir, err = openat2(currentDir, part, &unix.OpenHow{ + Flags: unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV, + }) + } else { + nextDir, err = fd.Openat(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) + } + if err != nil { + return nil, err + } + _ = currentDir.Close() + currentDir = nextDir + + // It's possible that the directory we just opened was swapped by an + // attacker. Unfortunately there isn't much we can do to protect + // against this, and MkdirAll's behaviour is that we will reuse + // existing directories anyway so the need to protect against this is + // incredibly limited (and arguably doesn't even deserve mention here). + // + // Ideally we might want to check that the owner and mode match what we + // would've created -- unfortunately, it is non-trivial to verify that + // the owner and mode of the created directory match. While plain Unix + // DAC rules seem simple enough to emulate, there are a bunch of other + // factors that can change the mode or owner of created directories + // (default POSIX ACLs, mount options like uid=1,gid=2,umask=0 on + // filesystems like vfat, etc etc). We used to try to verify this but + // it just lead to a series of spurious errors. + // + // We could also check that the directory is non-empty, but + // unfortunately some pseduofilesystems (like cgroupfs) create + // non-empty directories, which would result in different spurious + // errors. + } + return currentDir, nil +} + +// MkdirAll is a race-safe alternative to the [os.MkdirAll] function, +// where the new directory is guaranteed to be within the root directory (if an +// attacker can move directories from inside the root to outside the root, the +// created directory tree might be outside of the root but the key constraint +// is that at no point will we walk outside of the directory tree we are +// creating). +// +// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to +// +// path, _ := securejoin.SecureJoin(root, unsafePath) +// err := os.MkdirAll(path, mode) +// +// But is much safer. The above implementation is unsafe because if an attacker +// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is +// possible for MkdirAll to resolve unsafe symlink components and create +// directories outside of the root. +// +// If you plan to open the directory after you have created it or want to use +// an open directory handle as the root, you should use [MkdirAllHandle] instead. +// This function is a wrapper around [MkdirAllHandle]. +// +// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin +func MkdirAll(root, unsafePath string, mode os.FileMode) error { + rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) + if err != nil { + return err + } + defer rootDir.Close() //nolint:errcheck // close failures aren't critical here + + f, err := MkdirAllHandle(rootDir, unsafePath, mode) + if err != nil { + return err + } + _ = f.Close() + return nil +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go new file mode 100644 index 00000000..7492d8cf --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/open_linux.go @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "os" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" +) + +// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided +// using an *[os.File] handle, to ensure that the correct root directory is used. +func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) { + handle, err := completeLookupInRoot(root, unsafePath) + if err != nil { + return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err} + } + return handle, nil +} + +// OpenInRoot safely opens the provided unsafePath within the root. +// Effectively, OpenInRoot(root, unsafePath) is equivalent to +// +// path, _ := securejoin.SecureJoin(root, unsafePath) +// handle, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC) +// +// But is much safer. The above implementation is unsafe because if an attacker +// can modify the filesystem tree between [SecureJoin] and [os.OpenFile], it is +// possible for the returned file to be outside of the root. +// +// Note that the returned handle is an O_PATH handle, meaning that only a very +// limited set of operations will work on the handle. This is done to avoid +// accidentally opening an untrusted file that could cause issues (such as a +// disconnected TTY that could cause a DoS, or some other issue). In order to +// use the returned handle, you can "upgrade" it to a proper handle using +// [Reopen]. +// +// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin +func OpenInRoot(root, unsafePath string) (*os.File, error) { + rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) + if err != nil { + return nil, err + } + defer rootDir.Close() //nolint:errcheck // close failures aren't critical here + return OpenatInRoot(rootDir, unsafePath) +} + +// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd. +// Reopen(file, flags) is effectively equivalent to +// +// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd()) +// os.OpenFile(fdPath, flags|unix.O_CLOEXEC) +// +// But with some extra hardenings to ensure that we are not tricked by a +// maliciously-configured /proc mount. While this attack scenario is not +// common, in container runtimes it is possible for higher-level runtimes to be +// tricked into configuring an unsafe /proc that can be used to attack file +// operations. See [CVE-2019-19921] for more details. +// +// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw +func Reopen(handle *os.File, flags int) (*os.File, error) { + return procfs.ReopenFd(handle, flags) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go new file mode 100644 index 00000000..dbbb88c2 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/openat2_linux.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package pathrs + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "golang.org/x/sys/unix" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd" + "github.com/cyphar/filepath-securejoin/pathrs-lite/procfs" +) + +func openat2(dir fd.Fd, path string, how *unix.OpenHow) (*os.File, error) { + file, err := fd.Openat2(dir, path, how) + if err != nil { + return nil, err + } + // If we are using RESOLVE_IN_ROOT, the name we generated may be wrong. + if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT { + if actualPath, err := procfs.ProcSelfFdReadlink(file); err == nil { + // TODO: Ideally we would not need to dup the fd, but you cannot + // easily just swap an *os.File with one from the same fd + // (the GC will close the old one, and you cannot clear the + // finaliser easily because it is associated with an internal + // field of *os.File not *os.File itself). + newFile, err := fd.DupWithName(file, actualPath) + if err != nil { + return nil, err + } + _ = file.Close() + file = newFile + } + } + return file, nil +} + +func lookupOpenat2(root fd.Fd, unsafePath string, partial bool) (*os.File, string, error) { + if !partial { + file, err := openat2(root, unsafePath, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, + }) + return file, "", err + } + return partialLookupOpenat2(root, unsafePath) +} + +// partialLookupOpenat2 is an alternative implementation of +// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a +// handle to the deepest existing child of the requested path within the root. +func partialLookupOpenat2(root fd.Fd, unsafePath string) (*os.File, string, error) { + // TODO: Implement this as a git-bisect-like binary search. + + unsafePath = filepath.ToSlash(unsafePath) // noop + endIdx := len(unsafePath) + var lastError error + for endIdx > 0 { + subpath := unsafePath[:endIdx] + + handle, err := openat2(root, subpath, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS, + }) + if err == nil { + // Jump over the slash if we have a non-"" remainingPath. + if endIdx < len(unsafePath) { + endIdx++ + } + // We found a subpath! + return handle, unsafePath[endIdx:], lastError + } + if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) { + // That path doesn't exist, let's try the next directory up. + endIdx = strings.LastIndexByte(subpath, '/') + lastError = err + continue + } + return nil, "", fmt.Errorf("open subpath: %w", err) + } + // If we couldn't open anything, the whole subpath is missing. Return a + // copy of the root fd so that the caller doesn't close this one by + // accident. + rootClone, err := fd.Dup(root) + if err != nil { + return nil, "", err + } + return rootClone, unsafePath, lastError +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go new file mode 100644 index 00000000..ec187a41 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs/procfs_linux.go @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MPL-2.0 + +//go:build linux + +// Copyright (C) 2024-2025 Aleksa Sarai +// Copyright (C) 2024-2025 SUSE LLC +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Package procfs provides a safe API for operating on /proc on Linux. +package procfs + +import ( + "os" + + "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs" +) + +// This package mostly just wraps internal/procfs APIs. This is necessary +// because we are forced to export some things from internal/procfs in order to +// avoid some dependency cycle issues, but we don't want users to see or use +// them. + +// ProcThreadSelfCloser is a callback that needs to be called when you are done +// operating on an [os.File] fetched using [Handle.OpenThreadSelf]. +// +// [os.File]: https://pkg.go.dev/os#File +type ProcThreadSelfCloser = procfs.ProcThreadSelfCloser + +// Handle is a wrapper around an *os.File handle to "/proc", which can be used +// to do further procfs-related operations in a safe way. +type Handle struct { + inner *procfs.Handle +} + +// Close close the resources associated with this [Handle]. Note that if this +// [Handle] was created with [OpenProcRoot], on some kernels the underlying +// procfs handle is cached and so this Close operation may be a no-op. However, +// you should always call Close on [Handle]s once you are done with them. +func (proc *Handle) Close() error { return proc.inner.Close() } + +// OpenProcRoot tries to open a "safer" handle to "/proc" (i.e., one with the +// "subset=pid" mount option applied, available from Linux 5.8). Unless you +// plan to do many [Handle.OpenRoot] operations, users should prefer to use +// this over [OpenUnsafeProcRoot] which is far more dangerous to keep open. +// +// If a safe handle cannot be opened, OpenProcRoot will fall back to opening a +// regular "/proc" handle. +// +// Note that using [Handle.OpenRoot] will still work with handles returned by +// this function. If a subpath cannot be operated on with a safe "/proc" +// handle, then [OpenUnsafeProcRoot] will be called internally and a temporary +// unsafe handle will be used. +func OpenProcRoot() (*Handle, error) { + proc, err := procfs.OpenProcRoot() + if err != nil { + return nil, err + } + return &Handle{inner: proc}, nil +} + +// OpenUnsafeProcRoot opens a handle to "/proc" without any overmounts or +// masked paths. You must be extremely careful to make sure this handle is +// never leaked to a container and that you program cannot be tricked into +// writing to arbitrary paths within it. +// +// This is not necessary if you just wish to use [Handle.OpenRoot], as handles +// returned by [OpenProcRoot] will fall back to using a *temporary* unsafe +// handle in that case. You should only really use this if you need to do many +// operations with [Handle.OpenRoot] and the performance overhead of making +// many procfs handles is an issue. If you do use OpenUnsafeProcRoot, you +// should make sure to close the handle as soon as possible to avoid +// known-fd-number attacks. +func OpenUnsafeProcRoot() (*Handle, error) { + proc, err := procfs.OpenUnsafeProcRoot() + if err != nil { + return nil, err + } + return &Handle{inner: proc}, nil +} + +// OpenThreadSelf returns a handle to "/proc/thread-self/" (or an +// equivalent handle on older kernels where "/proc/thread-self" doesn't exist). +// Once finished with the handle, you must call the returned closer function +// ([runtime.UnlockOSThread]). You must not pass the returned *os.File to other +// Go threads or use the handle after calling the closer. +// +// [runtime.UnlockOSThread]: https://pkg.go.dev/runtime#UnlockOSThread +func (proc *Handle) OpenThreadSelf(subpath string) (*os.File, ProcThreadSelfCloser, error) { + return proc.inner.OpenThreadSelf(subpath) +} + +// OpenSelf returns a handle to /proc/self/. +// +// Note that in Go programs with non-homogenous threads, this may result in +// spurious errors. If you are monkeying around with APIs that are +// thread-specific, you probably want to use [Handle.OpenThreadSelf] instead +// which will guarantee that the handle refers to the same thread as the caller +// is executing on. +func (proc *Handle) OpenSelf(subpath string) (*os.File, error) { + return proc.inner.OpenSelf(subpath) +} + +// OpenRoot returns a handle to /proc/. +// +// You should only use this when you need to operate on global procfs files +// (such as sysctls in /proc/sys). Unlike [Handle.OpenThreadSelf], +// [Handle.OpenSelf], and [Handle.OpenPid], the procfs handle used internally +// for this operation will never use "subset=pid", which makes it a more juicy +// target for [CVE-2024-21626]-style attacks (and doing something like opening +// a directory with OpenRoot effectively leaks [OpenUnsafeProcRoot] as long as +// the file descriptor is open). +// +// [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv +func (proc *Handle) OpenRoot(subpath string) (*os.File, error) { + return proc.inner.OpenRoot(subpath) +} + +// OpenPid returns a handle to /proc/$pid/ (pid can be a pid or tid). +// This is mainly intended for usage when operating on other processes. +// +// You should not use this for the current thread, as special handling is +// needed for /proc/thread-self (or /proc/self/task/) when dealing with +// goroutine scheduling -- use [Handle.OpenThreadSelf] instead. +// +// To refer to the current thread-group, you should use prefer +// [Handle.OpenSelf] to passing os.Getpid as the pid argument. +func (proc *Handle) OpenPid(pid int, subpath string) (*os.File, error) { + return proc.inner.OpenPid(pid, subpath) +} + +// ProcSelfFdReadlink gets the real path of the given file by looking at +// /proc/self/fd/ with [readlink]. It is effectively just shorthand for +// something along the lines of: +// +// proc, err := procfs.OpenProcRoot() +// if err != nil { +// return err +// } +// link, err := proc.OpenThreadSelf(fmt.Sprintf("fd/%d", f.Fd())) +// if err != nil { +// return err +// } +// defer link.Close() +// var buf [4096]byte +// n, err := unix.Readlinkat(int(link.Fd()), "", buf[:]) +// if err != nil { +// return err +// } +// pathname := buf[:n] +// +// [readlink]: https://pkg.go.dev/golang.org/x/sys/unix#Readlinkat +func ProcSelfFdReadlink(f *os.File) (string, error) { + return procfs.ProcSelfFdReadlink(f) +} diff --git a/vendor/github.com/cyphar/filepath-securejoin/vfs.go b/vendor/github.com/cyphar/filepath-securejoin/vfs.go new file mode 100644 index 00000000..4d89a481 --- /dev/null +++ b/vendor/github.com/cyphar/filepath-securejoin/vfs.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: BSD-3-Clause + +// Copyright (C) 2017-2024 SUSE LLC. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package securejoin + +import "os" + +// In future this should be moved into a separate package, because now there +// are several projects (umoci and go-mtree) that are using this sort of +// interface. + +// VFS is the minimal interface necessary to use [SecureJoinVFS]. A nil VFS is +// equivalent to using the standard [os].* family of functions. This is mainly +// used for the purposes of mock testing, but also can be used to otherwise use +// [SecureJoinVFS] with VFS-like system. +type VFS interface { + // Lstat returns an [os.FileInfo] describing the named file. If the + // file is a symbolic link, the returned [os.FileInfo] describes the + // symbolic link. Lstat makes no attempt to follow the link. + // The semantics are identical to [os.Lstat]. + Lstat(name string) (os.FileInfo, error) + + // Readlink returns the destination of the named symbolic link. + // The semantics are identical to [os.Readlink]. + Readlink(name string) (string, error) +} + +// osVFS is the "nil" VFS, in that it just passes everything through to the os +// module. +type osVFS struct{} + +func (o osVFS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) } + +func (o osVFS) Readlink(name string) (string, error) { return os.Readlink(name) } diff --git a/vendor/github.com/disiqueira/gotree/v3/.gitignore b/vendor/github.com/disiqueira/gotree/v3/.gitignore new file mode 100644 index 00000000..3236c30a --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/.gitignore @@ -0,0 +1,137 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.idea/ +GoTree.iml +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* +### Windows template +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Go template +# Compiled Object files, Static and Dynamic libs (Shared Objects) + +# Folders + +# Architecture specific extensions/prefixes + + + +### OSX template +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/vendor/github.com/disiqueira/gotree/v3/.travis.yml b/vendor/github.com/disiqueira/gotree/v3/.travis.yml new file mode 100644 index 00000000..29261dff --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/.travis.yml @@ -0,0 +1,11 @@ +language: go +go_import_path: github.com/disiqueira/gotree +git: + depth: 1 +env: + - GO111MODULE=on + - GO111MODULE=off +go: [ 1.11.x, 1.12.x, 1.13.x ] +os: [ linux, osx ] +script: + - go test -race -v ./... diff --git a/vendor/github.com/disiqueira/gotree/v3/LICENSE b/vendor/github.com/disiqueira/gotree/v3/LICENSE new file mode 100644 index 00000000..e790b5a5 --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Diego Siqueira + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/disiqueira/gotree/v3/README.md b/vendor/github.com/disiqueira/gotree/v3/README.md new file mode 100644 index 00000000..d09d4a98 --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/README.md @@ -0,0 +1,104 @@ +# ![GoTree](https://rawgit.com/DiSiqueira/GoTree/master/gotree-logo.png) + +# GoTree ![Language Badge](https://img.shields.io/badge/Language-Go-blue.svg) ![Go Report](https://goreportcard.com/badge/github.com/DiSiqueira/GoTree) ![License Badge](https://img.shields.io/badge/License-MIT-blue.svg) ![Status Badge](https://img.shields.io/badge/Status-Beta-brightgreen.svg) [![GoDoc](https://godoc.org/github.com/DiSiqueira/GoTree?status.svg)](https://godoc.org/github.com/DiSiqueira/GoTree) [![Build Status](https://travis-ci.org/DiSiqueira/GoTree.svg?branch=master)](https://travis-ci.org/DiSiqueira/GoTree) + +Simple Go module to print tree structures in terminal. Heavily inpired by [The Tree Command for Linux][treecommand] + +The GoTree's goal is to be a simple tool providing a stupidly easy-to-use and fast way to print recursive structures. + +[treecommand]: http://mama.indstate.edu/users/ice/tree/ + +## Project Status + +GoTree is on beta. Pull Requests [are welcome](https://github.com/DiSiqueira/GoTree#social-coding) + +![](http://image.prntscr.com/image/2a0dbf0777454446b8083fb6a0dc51fe.png) + +## Features + +- Very simple and fast code +- Intuitive names +- Easy to extend +- Uses only native libs +- STUPIDLY [EASY TO USE](https://github.com/DiSiqueira/GoTree#usage) + +## Installation + +### Go Get + +```bash +$ go get github.com/disiqueira/gotree +``` + +## Usage + +### Simple create, populate and print example + +![](http://image.prntscr.com/image/dd2fe3737e6543f7b21941a6953598c2.png) + +```golang +package main + +import ( + "fmt" + + "github.com/disiqueira/gotree" +) + +func main() { + artist := gotree.New("Pantera") + album := artist.Add("Far Beyond Driven") + album.Add("5 minutes Alone") + + fmt.Println(artist.Print()) +} +``` + +## Contributing + +### Bug Reports & Feature Requests + +Please use the [issue tracker](https://github.com/DiSiqueira/GoTree/issues) to report any bugs or file feature requests. + +### Developing + +PRs are welcome. To begin developing, do this: + +```bash +$ git clone --recursive git@github.com:DiSiqueira/GoTree.git +$ cd GoTree/ +``` + +## Social Coding + +1. Create an issue to discuss about your idea +2. [Fork it] (https://github.com/DiSiqueira/GoTree/fork) +3. Create your feature branch (`git checkout -b my-new-feature`) +4. Commit your changes (`git commit -am 'Add some feature'`) +5. Push to the branch (`git push origin my-new-feature`) +6. Create a new Pull Request +7. Profit! :white_check_mark: + +## License + +The MIT License (MIT) + +Copyright (c) 2013-2018 Diego Siqueira + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/disiqueira/gotree/v3/_config.yml b/vendor/github.com/disiqueira/gotree/v3/_config.yml new file mode 100644 index 00000000..c7418817 --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file diff --git a/vendor/github.com/disiqueira/gotree/v3/gotree-logo.png b/vendor/github.com/disiqueira/gotree/v3/gotree-logo.png new file mode 100644 index 00000000..1735c600 Binary files /dev/null and b/vendor/github.com/disiqueira/gotree/v3/gotree-logo.png differ diff --git a/vendor/github.com/disiqueira/gotree/v3/gotree.go b/vendor/github.com/disiqueira/gotree/v3/gotree.go new file mode 100644 index 00000000..c529f62b --- /dev/null +++ b/vendor/github.com/disiqueira/gotree/v3/gotree.go @@ -0,0 +1,129 @@ +// Package gotree create and print tree. +package gotree + +import ( + "strings" +) + +const ( + newLine = "\n" + emptySpace = " " + middleItem = "├── " + continueItem = "│ " + lastItem = "└── " +) + +type ( + tree struct { + text string + items []Tree + } + + // Tree is tree interface + Tree interface { + Add(text string) Tree + AddTree(tree Tree) + Items() []Tree + Text() string + Print() string + } + + printer struct { + } + + // Printer is printer interface + Printer interface { + Print(Tree) string + } +) + +//New returns a new GoTree.Tree +func New(text string) Tree { + return &tree{ + text: text, + items: []Tree{}, + } +} + +//Add adds a node to the tree +func (t *tree) Add(text string) Tree { + n := New(text) + t.items = append(t.items, n) + return n +} + +//AddTree adds a tree as an item +func (t *tree) AddTree(tree Tree) { + t.items = append(t.items, tree) +} + +//Text returns the node's value +func (t *tree) Text() string { + return t.text +} + +//Items returns all items in the tree +func (t *tree) Items() []Tree { + return t.items +} + +//Print returns an visual representation of the tree +func (t *tree) Print() string { + return newPrinter().Print(t) +} + +func newPrinter() Printer { + return &printer{} +} + +//Print prints a tree to a string +func (p *printer) Print(t Tree) string { + return t.Text() + newLine + p.printItems(t.Items(), []bool{}) +} + +func (p *printer) printText(text string, spaces []bool, last bool) string { + var result string + for _, space := range spaces { + if space { + result += emptySpace + } else { + result += continueItem + } + } + + indicator := middleItem + if last { + indicator = lastItem + } + + var out string + lines := strings.Split(text, "\n") + for i := range lines { + text := lines[i] + if i == 0 { + out += result + indicator + text + newLine + continue + } + if last { + indicator = emptySpace + } else { + indicator = continueItem + } + out += result + indicator + text + newLine + } + + return out +} + +func (p *printer) printItems(t []Tree, spaces []bool) string { + var result string + for i, f := range t { + last := i == len(t)-1 + result += p.printText(f.Text(), spaces, last) + if len(f.Items()) > 0 { + spacesChild := append(spaces, last) + result += p.printItems(f.Items(), spacesChild) + } + } + return result +} diff --git a/vendor/github.com/docker/cli/AUTHORS b/vendor/github.com/docker/cli/AUTHORS deleted file mode 100644 index ad1abd49..00000000 --- a/vendor/github.com/docker/cli/AUTHORS +++ /dev/null @@ -1,910 +0,0 @@ -# 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. - -A. Lester Buck III -Aanand Prasad -Aaron L. Xu -Aaron Lehmann -Aaron.L.Xu -Abdur Rehman -Abhinandan Prativadi -Abin Shahab -Abreto FU -Ace Tang -Addam Hardy -Adolfo Ochagavía -Adrian Plata -Adrien Duermael -Adrien Folie -Adyanth Hosavalike -Ahmet Alp Balkan -Aidan Feldman -Aidan Hobson Sayers -AJ Bowen -Akhil Mohan -Akihiro Suda -Akim Demaille -Alan Thompson -Alano Terblanche -Albert Callarisa -Alberto Roura -Albin Kerouanton -Aleksa Sarai -Aleksander Piotrowski -Alessandro Boch -Alex Couture-Beil -Alex Mavrogiannis -Alex Mayer -Alexander Boyd -Alexander Chneerov -Alexander Larsson -Alexander Morozov -Alexander Ryabov -Alexandre González -Alexey Igrychev -Alexis Couvreur -Alfred Landrum -Ali Rostami -Alicia Lauerman -Allen Sun -Alvin Deng -Amen Belayneh -Amey Shrivastava <72866602+AmeyShrivastava@users.noreply.github.com> -Amir Goldstein -Amit Krishnan -Amit Shukla -Amy Lindburg -Anca Iordache -Anda Xu -Andrea Luzzardi -Andreas Köhler -Andres G. Aragoneses -Andres Leon Rangel -Andrew France -Andrew Hsu -Andrew Macpherson -Andrew McDonnell -Andrew Po -Andrew-Zipperer -Andrey Petrov -Andrii Berehuliak -André Martins -Andy Goldstein -Andy Rothfusz -Anil Madhavapeddy -Ankush Agarwal -Anne Henmi -Anton Polonskiy -Antonio Murdaca -Antonis Kalipetis -Anusha Ragunathan -Ao Li -Arash Deshmeh -Arko Dasgupta -Arnaud Porterie -Arnaud Rebillout -Arthur Peka -Ashly Mathew -Ashwini Oruganti -Aslam Ahemad -Azat Khuyiyakhmetov -Bardia Keyoumarsi -Barnaby Gray -Bastiaan Bakker -BastianHofmann -Ben Bodenmiller -Ben Bonnefoy -Ben Creasy -Ben Firshman -Benjamin Boudreau -Benjamin Böhmke -Benjamin Nater -Benoit Sigoure -Bhumika Bayani -Bill Wang -Bin Liu -Bingshen Wang -Bishal Das -Bjorn Neergaard -Boaz Shuster -Boban Acimovic -Bogdan Anton -Boris Pruessmann -Brad Baker -Bradley Cicenas -Brandon Mitchell -Brandon Philips -Brent Salisbury -Bret Fisher -Brian (bex) Exelbierd -Brian Goff -Brian Tracy -Brian Wieder -Bruno Sousa -Bryan Bess -Bryan Boreham -Bryan Murphy -bryfry -Calvin Liu -Cameron Spear -Cao Weiwei -Carlo Mion -Carlos Alexandro Becker -Carlos de Paula -Casey Korver -Ce Gao -Cedric Davies -Cezar Sa Espinola -Chad Faragher -Chao Wang -Charles Chan -Charles Law -Charles Smith -Charlie Drage -Charlotte Mach -ChaYoung You -Chee Hau Lim -Chen Chuanliang -Chen Hanxiao -Chen Mingjie -Chen Qiu -Chris Chinchilla -Chris Couzens -Chris Gavin -Chris Gibson -Chris McKinnel -Chris Snow -Chris Vermilion -Chris Weyl -Christian Persson -Christian Stefanescu -Christophe Robin -Christophe Vidal -Christopher Biscardi -Christopher Crone -Christopher Jones -Christopher Petito <47751006+krissetto@users.noreply.github.com> -Christopher Petito -Christopher Svensson -Christy Norman -Chun Chen -Clinton Kitson -Coenraad Loubser -Colin Hebert -Collin Guarino -Colm Hally -Comical Derskeal <27731088+derskeal@users.noreply.github.com> -Conner Crosby -Corey Farrell -Corey Quon -Cory Bennet -Cory Snider -Craig Osterhout -Craig Wilhite -Cristian Staretu -Daehyeok Mun -Dafydd Crosby -Daisuke Ito -dalanlan -Damien Nadé -Dan Cotora -Danial Gharib -Daniel Artine -Daniel Cassidy -Daniel Dao -Daniel Farrell -Daniel Gasienica -Daniel Goosen -Daniel Helfand -Daniel Hiltgen -Daniel J Walsh -Daniel Nephin -Daniel Norberg -Daniel Watkins -Daniel Zhang -Daniil Nikolenko -Danny Berger -Darren Shepherd -Darren Stahl -Dattatraya Kumbhar -Dave Goodchild -Dave Henderson -Dave Tucker -David Alvarez -David Beitey -David Calavera -David Cramer -David Dooling -David Gageot -David Karlsson -David le Blanc -David Lechner -David Scott -David Sheets -David Williamson -David Xia -David Young -Deng Guangxing -Denis Defreyne -Denis Gladkikh -Denis Ollier -Dennis Docter -dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> -Derek McGowan -Des Preston -Deshi Xiao -Dharmit Shah -Dhawal Yogesh Bhanushali -Dieter Reuter -Dima Stopel -Dimitry Andric -Ding Fei -Diogo Monica -Djordje Lukic -Dmitriy Fishman -Dmitry Gusev -Dmitry Smirnov -Dmitry V. Krivenok -Dominik Braun -Don Kjer -Dong Chen -DongGeon Lee -Doug Davis -Drew Erny -Ed Costello -Ed Morley <501702+edmorley@users.noreply.github.com> -Elango Sivanandam -Eli Uriegas -Eli Uriegas -Elias Faxö -Elliot Luo <956941328@qq.com> -Eric Bode -Eric Curtin -Eric Engestrom -Eric G. Noriega -Eric Rosenberg -Eric Sage -Eric-Olivier Lamey -Erica Windisch -Erik Hollensbe -Erik Humphrey -Erik St. Martin -Essam A. Hassan -Ethan Haynes -Euan Kemp -Eugene Yakubovich -Evan Allrich -Evan Hazlett -Evan Krall -Evan Lezar -Evelyn Xu -Everett Toews -Fabio Falci -Fabrizio Soppelsa -Felix Geyer -Felix Hupfeld -Felix Rabe -fezzik1620 -Filip Jareš -Flavio Crisciani -Florian Klein -Forest Johnson -Foysal Iqbal -François Scala -Fred Lifton -Frederic Hemberger -Frederick F. Kautz IV -Frederik Nordahl Jul Sabroe -Frieder Bluemle -Gabriel Gore -Gabriel Nicolas Avellaneda -Gabriela Georgieva -Gaetan de Villele -Gang Qiao -Gary Schaetz -Genki Takiuchi -George MacRorie -George Margaritis -George Xie -Gianluca Borello -Gildas Cuisinier -Gio d'Amelio -Gleb Stsenov -Goksu Toprak -Gou Rao -Govind Rai -Grace Choi -Graeme Wiebe -Grant Reaber -Greg Pflaum -Gsealy -Guilhem Lettron -Guillaume J. Charmes -Guillaume Le Floch -Guillaume Tardif -gwx296173 -Günther Jungbluth -Hakan Özler -Hao Zhang <21521210@zju.edu.cn> -Harald Albers -Harold Cooper -Harry Zhang -He Simei -Hector S -Helen Xie -Henning Sprang -Henry N -Hernan Garcia -Hongbin Lu -Hu Keping -Huayi Zhang -Hugo Chastel -Hugo Gabriel Eyherabide -huqun -Huu Nguyen -Hyzhou Zhy -Iain Samuel McLean Elder -Ian Campbell -Ian Philpot -Ignacio Capurro -Ilya Dmitrichenko -Ilya Khlopotov -Ilya Sotkov -Ioan Eugen Stan -Isabel Jimenez -Ivan Grcic -Ivan Grund -Ivan Markin -Jacob Atzen -Jacob Tomlinson -Jacopo Rigoli -Jaivish Kothari -Jake Lambert -Jake Sanders -Jake Stokes -Jakub Panek -James Nesbitt -James Turnbull -Jamie Hannaford -Jan Koprowski -Jan Pazdziora -Jan-Jaap Driessen -Jana Radhakrishnan -Jared Hocutt -Jasmine Hegman -Jason Hall -Jason Heiss -Jason Plum -Jay Kamat -Jean Lecordier -Jean Rouge -Jean-Christophe Sirot -Jean-Pierre Huynh -Jeff Lindsay -Jeff Nickoloff -Jeff Silberman -Jennings Zhang -Jeremy Chambers -Jeremy Unruh -Jeremy Yallop -Jeroen Franse -Jesse Adametz -Jessica Frazelle -Jezeniel Zapanta -Jian Zhang -Jie Luo -Jilles Oldenbeuving -Jim Chen -Jim Galasyn -Jim Lin -Jimmy Leger -Jimmy Song -jimmyxian -Jintao Zhang -Joao Fernandes -Joe Abbey -Joe Doliner -Joe Gordon -Joel Handwell -Joey Geiger -Joffrey F -Johan Euphrosine -Johannes 'fish' Ziemke -John Feminella -John Harris -John Howard -John Howard -John Laswell -John Maguire -John Mulhausen -John Starks -John Stephens -John Tims -John V. Martinez -John Willis -Jon Johnson -Jon Zeolla -Jonatas Baldin -Jonathan A. Sternberg -Jonathan Boulle -Jonathan Lee -Jonathan Lomas -Jonathan McCrohan -Jonathan Warriss-Simmons -Jonh Wendell -Jordan Jennings -Jorge Vallecillo -Jose J. Escobar <53836904+jescobar-docker@users.noreply.github.com> -Joseph Kern -Josh Bodah -Josh Chorlton -Josh Hawn -Josh Horwitz -Josh Soref -Julian -Julien Barbier -Julien Kassar -Julien Maitrehenry -Justas Brazauskas -Justin Chadwell -Justin Cormack -Justin Simonelis -Justyn Temme -Jyrki Puttonen -Jérémie Drouet -Jérôme Petazzoni -Jörg Thalheim -Kai Blin -Kai Qiang Wu (Kennan) -Kara Alexandra -Kareem Khazem -Karthik Nayak -Kat Samperi -Kathryn Spiers -Katie McLaughlin -Ke Xu -Kei Ohmura -Keith Hudgins -Kelton Bassingthwaite -Ken Cochrane -Ken ICHIKAWA -Kenfe-Mickaël Laventure -Kevin Alvarez -Kevin Burke -Kevin Feyrer -Kevin Kern -Kevin Kirsche -Kevin Meredith -Kevin Richardson -Kevin Woblick -khaled souf -Kim Eik -Kir Kolyshkin -Kirill A. Korinsky -Kotaro Yoshimatsu -Krasi Georgiev -Kris-Mikael Krister -Kun Zhang -Kunal Kushwaha -Kyle Mitofsky -Lachlan Cooper -Lai Jiangshan -Lars Kellogg-Stedman -Laura Brehm -Laura Frank -Laurent Erignoux -Lee Gaines -Lei Jitang -Lennie -Leo Gallucci -Leonid Skorospelov -Lewis Daly -Li Fu Bang -Li Yi -Li Yi -Liang-Chi Hsieh -Lihua Tang -Lily Guo -Lin Lu -Linus Heckemann -Liping Xue -Liron Levin -liwenqi -lixiaobing10051267 -Lloyd Dewolf -Lorenzo Fontana -Louis Opter -Luca Favatella -Luca Marturana -Lucas Chan -Luis Henrique Mulinari -Luka Hartwig -Lukas Heeren -Lukasz Zajaczkowski -Lydell Manganti -Lénaïc Huard -Ma Shimiao -Mabin -Maciej Kalisz -Madhav Puri -Madhu Venugopal -Madhur Batra -Malte Janduda -Manjunath A Kumatagi -Mansi Nahar -mapk0y -Marc Bihlmaier -Marc Cornellà -Marco Mariani -Marco Spiess -Marco Vedovati -Marcus Martins -Marianna Tessel -Marius Ileana -Marius Meschter -Marius Sturm -Mark Oates -Marsh Macy -Martin Mosegaard Amdisen -Mary Anthony -Mason Fish -Mason Malone -Mateusz Major -Mathias Duedahl <64321057+Lussebullen@users.noreply.github.com> -Mathieu Champlon -Mathieu Rollet -Matt Gucci -Matt Robenolt -Matteo Orefice -Matthew Heon -Matthieu Hauglustaine -Mauro Porras P -Max Shytikov -Max-Julian Pogner -Maxime Petazzoni -Maximillian Fan Xavier -Mei ChunTao -Melroy van den Berg -Metal <2466052+tedhexaflow@users.noreply.github.com> -Micah Zoltu -Michael A. Smith -Michael Bridgen -Michael Crosby -Michael Friis -Michael Irwin -Michael Käufl -Michael Prokop -Michael Scharf -Michael Spetsiotis -Michael Steinert -Michael West -Michal Minář -Michał Czeraszkiewicz -Miguel Angel Alvarez Cabrerizo -Mihai Borobocea -Mihuleacc Sergiu -Mike Brown -Mike Casas -Mike Dalton -Mike Danese -Mike Dillon -Mike Goelzer -Mike MacCana -mikelinjie <294893458@qq.com> -Mikhail Vasin -Milind Chawre -Mindaugas Rukas -Miroslav Gula -Misty Stanley-Jones -Mohammad Banikazemi -Mohammed Aaqib Ansari -Mohini Anne Dsouza -Moorthy RS -Morgan Bauer -Morten Hekkvang -Morten Linderud -Moysés Borges -Mozi <29089388+pzhlkj6612@users.noreply.github.com> -Mrunal Patel -muicoder -Murukesh Mohanan -Muthukumar R -Máximo Cuadros -Mårten Cassel -Nace Oroz -Nahum Shalman -Nalin Dahyabhai -Nao YONASHIRO -Nassim 'Nass' Eddequiouaq -Natalie Parker -Nate Brennand -Nathan Hsieh -Nathan LeClaire -Nathan McCauley -Neil Peterson -Nick Adcock -Nick Santos -Nick Sieger -Nico Stapelbroek -Nicola Kabar -Nicolas Borboën -Nicolas De Loof -Nikhil Chawla -Nikolas Garofil -Nikolay Milovanov -Nir Soffer -Nishant Totla -NIWA Hideyuki -Noah Treuhaft -O.S. Tezer -Oded Arbel -Odin Ugedal -ohmystack -OKA Naoya -Oliver Pomeroy -Olle Jonsson -Olli Janatuinen -Oscar Wieman -Otto Kekäläinen -Ovidio Mallo -Pascal Borreli -Patrick Böänziger -Patrick Daigle <114765035+pdaig@users.noreply.github.com> -Patrick Hemmer -Patrick Lang -Paul -Paul Kehrer -Paul Lietar -Paul Mulders -Paul Seyfert -Paul Weaver -Pavel Pospisil -Paweł Gronowski -Paweł Pokrywka -Paweł Szczekutowicz -Peeyush Gupta -Per Lundberg -Peter Dave Hello -Peter Edge -Peter Hsu -Peter Jaffe -Peter Kehl -Peter Nagy -Peter Salvatore -Peter Waller -Phil Estes -Philip Alexander Etling -Philipp Gillé -Philipp Schmied -Phong Tran -pidster -Pieter E Smit -pixelistik -Pratik Karki -Prayag Verma -Preston Cowley -Pure White -Qiang Huang -Qinglan Peng -QQ喵 -qudongfang -Raghavendra K T -Rahul Kadyan -Rahul Zoldyck -Ravi Shekhar Jethani -Ray Tsang -Reficul -Remy Suen -Renaud Gaubert -Ricardo N Feliciano -Rich Moyse -Richard Chen Zheng <58443436+rchenzheng@users.noreply.github.com> -Richard Mathie -Richard Scothern -Rick Wieman -Ritesh H Shukla -Riyaz Faizullabhoy -Rob Gulewich -Rob Murray -Robert Wallis -Robin Naundorf -Robin Speekenbrink -Roch Feuillade -Rodolfo Ortiz -Rogelio Canedo -Rohan Verma -Roland Kammerer -Roman Dudin -Rory Hunter -Ross Boucher -Rubens Figueiredo -Rui Cao -Rui JingAn -Ryan Belgrave -Ryan Detzel -Ryan Stelly -Ryan Wilson-Perkin -Ryan Zhang -Sainath Grandhi -Sakeven Jiang -Sally O'Malley -Sam Neirinck -Sam Thibault -Samarth Shah -Sambuddha Basu -Sami Tabet -Samuel Cochran -Samuel Karp -Sandro Jäckel -Santhosh Manohar -Sargun Dhillon -Saswat Bhattacharya -Saurabh Kumar -Scott Brenner -Scott Collier -Sean Christopherson -Sean Rodman -Sebastiaan van Stijn -Sergey Tryuber -Serhat Gülçiçek -Sevki Hasirci -Shaun Kaasten -Sheng Yang -Shijiang Wei -Shishir Mahajan -Shoubhik Bose -Shukui Yang -Sian Lerk Lau -Sidhartha Mani -sidharthamani -Silvin Lubecki -Simei He -Simon Ferquel -Simon Heimberg -Sindhu S -Slava Semushin -Solomon Hykes -Song Gao -Spencer Brown -Spring Lee -squeegels -Srini Brahmaroutu -Stefan S. -Stefan Scherer -Stefan Weil -Stephane Jeandeaux -Stephen Day -Stephen Rust -Steve Durrheimer -Steve Richards -Steven Burgess -Stoica-Marcu Floris-Andrei -Subhajit Ghosh -Sun Jianbo -Sune Keller -Sungwon Han -Sunny Gogoi -Sven Dowideit -Sylvain Baubeau -Sébastien HOUZÉ -T K Sourabh -TAGOMORI Satoshi -taiji-tech -Takeshi Koenuma -Takuya Noguchi -Taylor Jones -Teiva Harsanyi -Tejaswini Duggaraju -Tengfei Wang -Teppei Fukuda -Thatcher Peskens -Thibault Coupin -Thomas Gazagnaire -Thomas Krzero -Thomas Leonard -Thomas Léveil -Thomas Riccardi -Thomas Swift -Tianon Gravi -Tianyi Wang -Tibor Vass -Tim Dettrick -Tim Hockin -Tim Sampson -Tim Smith -Tim Waugh -Tim Welsh -Tim Wraight -timfeirg -Timothy Hobbs -Tobias Bradtke -Tobias Gesellchen -Todd Whiteman -Tom Denham -Tom Fotherby -Tom Klingenberg -Tom Milligan -Tom X. Tobin -Tomas Bäckman -Tomas Tomecek -Tomasz Kopczynski -Tomáš Hrčka -Tony Abboud -Tõnis Tiigi -Trapier Marshall -Travis Cline -Tristan Carel -Tycho Andersen -Tycho Andersen -uhayate -Ulrich Bareth -Ulysses Souza -Umesh Yadav -Vaclav Struhar -Valentin Lorentz -Vardan Pogosian -Venkateswara Reddy Bukkasamudram -Veres Lajos -Victor Vieux -Victoria Bialas -Viktor Stanchev -Ville Skyttä -Vimal Raghubir -Vincent Batts -Vincent Bernat -Vincent Demeester -Vincent Woo -Vishnu Kannan -Vivek Goyal -Wang Jie -Wang Lei -Wang Long -Wang Ping -Wang Xing -Wang Yuexiao -Wang Yumu <37442693@qq.com> -Wataru Ishida -Wayne Song -Wen Cheng Ma -Wenzhi Liang -Wes Morgan -Wewang Xiaorenfine -William Henry -Xianglin Gao -Xiaodong Liu -Xiaodong Zhang -Xiaoxi He -Xinbo Weng -Xuecong Liao -Yan Feng -Yanqiang Miao -Yassine Tijani -Yi EungJun -Ying Li -Yong Tang -Yosef Fertel -Yu Peng -Yuan Sun -Yucheng Wu -Yue Zhang -Yunxiang Huang -Zachary Romero -Zander Mackie -zebrilee -Zeel B Patel -Zhang Kun -Zhang Wei -Zhang Wentao -ZhangHang -zhenghenghuo -Zhiwei Liang -Zhou Hao -Zhoulin Xie -Zhu Guihua -Zhuo Zhi -Álex González -Álvaro Lázaro -Átila Camurça Alves -Александр Менщиков <__Singleton__@hackerdom.ru> -徐俊杰 diff --git a/vendor/github.com/docker/cli/cli/config/config.go b/vendor/github.com/docker/cli/cli/config/config.go deleted file mode 100644 index 5a518432..00000000 --- a/vendor/github.com/docker/cli/cli/config/config.go +++ /dev/null @@ -1,172 +0,0 @@ -package config - -import ( - "fmt" - "io" - "os" - "os/user" - "path/filepath" - "runtime" - "strings" - "sync" - - "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/config/credentials" - "github.com/docker/cli/cli/config/types" - "github.com/pkg/errors" -) - -const ( - // EnvOverrideConfigDir is the name of the environment variable that can be - // used to override the location of the client configuration files (~/.docker). - // - // It takes priority over the default, but can be overridden by the "--config" - // command line option. - EnvOverrideConfigDir = "DOCKER_CONFIG" - - // ConfigFileName is the name of the client configuration file inside the - // config-directory. - ConfigFileName = "config.json" - configFileDir = ".docker" - contextsDir = "contexts" -) - -var ( - initConfigDir = new(sync.Once) - configDir string -) - -// resetConfigDir is used in testing to reset the "configDir" package variable -// and its sync.Once to force re-lookup between tests. -func resetConfigDir() { - configDir = "" - initConfigDir = new(sync.Once) -} - -// getHomeDir returns the home directory of the current user with the help of -// environment variables depending on the target operating system. -// Returned path should be used with "path/filepath" to form new paths. -// -// On non-Windows platforms, it falls back to nss lookups, if the home -// directory cannot be obtained from environment-variables. -// -// If linking statically with cgo enabled against glibc, ensure the -// osusergo build tag is used. -// -// If needing to do nss lookups, do not disable cgo or set osusergo. -// -// getHomeDir is a copy of [pkg/homedir.Get] to prevent adding docker/docker -// as dependency for consumers that only need to read the config-file. -// -// [pkg/homedir.Get]: https://pkg.go.dev/github.com/docker/docker@v26.1.4+incompatible/pkg/homedir#Get -func getHomeDir() string { - home, _ := os.UserHomeDir() - if home == "" && runtime.GOOS != "windows" { - if u, err := user.Current(); err == nil { - return u.HomeDir - } - } - return home -} - -// Dir returns the directory the configuration file is stored in -func Dir() string { - initConfigDir.Do(func() { - configDir = os.Getenv(EnvOverrideConfigDir) - if configDir == "" { - configDir = filepath.Join(getHomeDir(), configFileDir) - } - }) - return configDir -} - -// ContextStoreDir returns the directory the docker contexts are stored in -func ContextStoreDir() string { - return filepath.Join(Dir(), contextsDir) -} - -// SetDir sets the directory the configuration file is stored in -func SetDir(dir string) { - // trigger the sync.Once to synchronise with Dir() - initConfigDir.Do(func() {}) - configDir = filepath.Clean(dir) -} - -// Path returns the path to a file relative to the config dir -func Path(p ...string) (string, error) { - path := filepath.Join(append([]string{Dir()}, p...)...) - if !strings.HasPrefix(path, Dir()+string(filepath.Separator)) { - return "", errors.Errorf("path %q is outside of root config directory %q", path, Dir()) - } - return path, nil -} - -// LoadFromReader is a convenience function that creates a ConfigFile object from -// a reader. It returns an error if configData is malformed. -func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) { - configFile := configfile.ConfigFile{ - AuthConfigs: make(map[string]types.AuthConfig), - } - err := configFile.LoadFromReader(configData) - return &configFile, err -} - -// Load reads the configuration file ([ConfigFileName]) from the given directory. -// If no directory is given, it uses the default [Dir]. A [*configfile.ConfigFile] -// is returned containing the contents of the configuration file, or a default -// struct if no configfile exists in the given location. -// -// Load returns an error if a configuration file exists in the given location, -// but cannot be read, or is malformed. Consumers must handle errors to prevent -// overwriting an existing configuration file. -func Load(configDir string) (*configfile.ConfigFile, error) { - if configDir == "" { - configDir = Dir() - } - return load(configDir) -} - -func load(configDir string) (*configfile.ConfigFile, error) { - filename := filepath.Join(configDir, ConfigFileName) - configFile := configfile.New(filename) - - file, err := os.Open(filename) - if err != nil { - if os.IsNotExist(err) { - // It is OK for no configuration file to be present, in which - // case we return a default struct. - return configFile, nil - } - // Any other error happening when failing to read the file must be returned. - return configFile, errors.Wrap(err, "loading config file") - } - defer file.Close() - err = configFile.LoadFromReader(file) - if err != nil { - err = errors.Wrapf(err, "loading config file: %s: ", filename) - } - return configFile, err -} - -// LoadDefaultConfigFile attempts to load the default config file and returns -// a reference to the ConfigFile struct. If none is found or when failing to load -// the configuration file, it initializes a default ConfigFile struct. If no -// credentials-store is set in the configuration file, it attempts to discover -// the default store to use for the current platform. -// -// Important: LoadDefaultConfigFile prints a warning to stderr when failing to -// load the configuration file, but otherwise ignores errors. Consumers should -// consider using [Load] (and [credentials.DetectDefaultStore]) to detect errors -// when updating the configuration file, to prevent discarding a (malformed) -// configuration file. -func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile { - configFile, err := load(Dir()) - if err != nil { - // FIXME(thaJeztah): we should not proceed here to prevent overwriting existing (but malformed) config files; see https://github.com/docker/cli/issues/5075 - _, _ = fmt.Fprintln(stderr, "WARNING: Error", err) - } - if !configFile.ContainsAuth() { - configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore) - } - return configFile -} diff --git a/vendor/github.com/docker/cli/cli/config/configfile/file.go b/vendor/github.com/docker/cli/cli/config/configfile/file.go deleted file mode 100644 index ae9dcb33..00000000 --- a/vendor/github.com/docker/cli/cli/config/configfile/file.go +++ /dev/null @@ -1,354 +0,0 @@ -package configfile - -import ( - "encoding/base64" - "encoding/json" - "io" - "os" - "path/filepath" - "strings" - - "github.com/docker/cli/cli/config/credentials" - "github.com/docker/cli/cli/config/types" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// ConfigFile ~/.docker/config.json file info -type ConfigFile struct { - AuthConfigs map[string]types.AuthConfig `json:"auths"` - HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` - PsFormat string `json:"psFormat,omitempty"` - ImagesFormat string `json:"imagesFormat,omitempty"` - NetworksFormat string `json:"networksFormat,omitempty"` - PluginsFormat string `json:"pluginsFormat,omitempty"` - VolumesFormat string `json:"volumesFormat,omitempty"` - StatsFormat string `json:"statsFormat,omitempty"` - DetachKeys string `json:"detachKeys,omitempty"` - CredentialsStore string `json:"credsStore,omitempty"` - CredentialHelpers map[string]string `json:"credHelpers,omitempty"` - Filename string `json:"-"` // Note: for internal use only - ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"` - ServicesFormat string `json:"servicesFormat,omitempty"` - TasksFormat string `json:"tasksFormat,omitempty"` - SecretFormat string `json:"secretFormat,omitempty"` - ConfigFormat string `json:"configFormat,omitempty"` - NodesFormat string `json:"nodesFormat,omitempty"` - PruneFilters []string `json:"pruneFilters,omitempty"` - Proxies map[string]ProxyConfig `json:"proxies,omitempty"` - Experimental string `json:"experimental,omitempty"` - CurrentContext string `json:"currentContext,omitempty"` - CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"` - Plugins map[string]map[string]string `json:"plugins,omitempty"` - Aliases map[string]string `json:"aliases,omitempty"` - Features map[string]string `json:"features,omitempty"` -} - -// ProxyConfig contains proxy configuration settings -type ProxyConfig struct { - HTTPProxy string `json:"httpProxy,omitempty"` - HTTPSProxy string `json:"httpsProxy,omitempty"` - NoProxy string `json:"noProxy,omitempty"` - FTPProxy string `json:"ftpProxy,omitempty"` - AllProxy string `json:"allProxy,omitempty"` -} - -// New initializes an empty configuration file for the given filename 'fn' -func New(fn string) *ConfigFile { - return &ConfigFile{ - AuthConfigs: make(map[string]types.AuthConfig), - HTTPHeaders: make(map[string]string), - Filename: fn, - Plugins: make(map[string]map[string]string), - Aliases: make(map[string]string), - } -} - -// 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 { - if err := json.NewDecoder(configData).Decode(configFile); err != nil && !errors.Is(err, io.EOF) { - return err - } - var err error - for addr, ac := range configFile.AuthConfigs { - if ac.Auth != "" { - ac.Username, ac.Password, err = decodeAuth(ac.Auth) - if err != nil { - return err - } - } - ac.Auth = "" - ac.ServerAddress = addr - configFile.AuthConfigs[addr] = ac - } - return nil -} - -// ContainsAuth returns whether there is authentication configured -// in this file or not. -func (configFile *ConfigFile) ContainsAuth() bool { - return configFile.CredentialsStore != "" || - len(configFile.CredentialHelpers) > 0 || - len(configFile.AuthConfigs) > 0 -} - -// 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 -} - -// SaveToWriter encodes and writes out all the authorization information to -// the given writer -func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error { - // Encode sensitive data into a new/temp struct - tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs)) - for k, authConfig := range configFile.AuthConfigs { - authCopy := authConfig - // encode and save the authstring, while blanking out the original fields - authCopy.Auth = encodeAuth(&authCopy) - authCopy.Username = "" - authCopy.Password = "" - authCopy.ServerAddress = "" - tmpAuthConfigs[k] = authCopy - } - - saveAuthConfigs := configFile.AuthConfigs - configFile.AuthConfigs = tmpAuthConfigs - defer func() { configFile.AuthConfigs = saveAuthConfigs }() - - // User-Agent header is automatically set, and should not be stored in the configuration - for v := range configFile.HTTPHeaders { - if strings.EqualFold(v, "User-Agent") { - delete(configFile.HTTPHeaders, v) - } - } - - data, err := json.MarshalIndent(configFile, "", "\t") - if err != nil { - return err - } - _, err = writer.Write(data) - return err -} - -// Save encodes and writes out all the authorization information -func (configFile *ConfigFile) Save() (retErr error) { - if configFile.Filename == "" { - return errors.Errorf("Can't save config with empty filename") - } - - dir := filepath.Dir(configFile.Filename) - if err := os.MkdirAll(dir, 0o700); err != nil { - return err - } - temp, err := os.CreateTemp(dir, filepath.Base(configFile.Filename)) - if err != nil { - return err - } - defer func() { - temp.Close() - if retErr != nil { - if err := os.Remove(temp.Name()); err != nil { - logrus.WithError(err).WithField("file", temp.Name()).Debug("Error cleaning up temp file") - } - } - }() - - err = configFile.SaveToWriter(temp) - if err != nil { - return err - } - - if err := temp.Close(); err != nil { - return errors.Wrap(err, "error closing temp file") - } - - // Handle situation where the configfile is a symlink - cfgFile := configFile.Filename - if f, err := os.Readlink(cfgFile); err == nil { - cfgFile = f - } - - // Try copying the current config file (if any) ownership and permissions - copyFilePermissions(cfgFile, temp.Name()) - return os.Rename(temp.Name(), cfgFile) -} - -// ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and -// then checking this against any environment variables provided to the container -func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts map[string]*string) map[string]*string { - var cfgKey string - - if _, ok := configFile.Proxies[host]; !ok { - cfgKey = "default" - } else { - cfgKey = host - } - - config := configFile.Proxies[cfgKey] - permitted := map[string]*string{ - "HTTP_PROXY": &config.HTTPProxy, - "HTTPS_PROXY": &config.HTTPSProxy, - "NO_PROXY": &config.NoProxy, - "FTP_PROXY": &config.FTPProxy, - "ALL_PROXY": &config.AllProxy, - } - m := runOpts - if m == nil { - m = make(map[string]*string) - } - for k := range permitted { - if *permitted[k] == "" { - continue - } - if _, ok := m[k]; !ok { - m[k] = permitted[k] - } - if _, ok := m[strings.ToLower(k)]; !ok { - m[strings.ToLower(k)] = permitted[k] - } - } - return m -} - -// encodeAuth creates a base64 encoded string to containing authorization information -func encodeAuth(authConfig *types.AuthConfig) string { - if authConfig.Username == "" && authConfig.Password == "" { - return "" - } - - authStr := authConfig.Username + ":" + authConfig.Password - msg := []byte(authStr) - encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) - base64.StdEncoding.Encode(encoded, msg) - return string(encoded) -} - -// decodeAuth decodes a base64 encoded string and returns username and password -func decodeAuth(authStr string) (string, string, error) { - if authStr == "" { - return "", "", nil - } - - decLen := base64.StdEncoding.DecodedLen(len(authStr)) - decoded := make([]byte, decLen) - authByte := []byte(authStr) - n, err := base64.StdEncoding.Decode(decoded, authByte) - if err != nil { - return "", "", err - } - if n > decLen { - return "", "", errors.Errorf("Something went wrong decoding auth config") - } - userName, password, ok := strings.Cut(string(decoded), ":") - if !ok || userName == "" { - return "", "", errors.Errorf("Invalid auth configuration file") - } - return userName, strings.Trim(password, "\x00"), nil -} - -// GetCredentialsStore returns a new credentials store from the settings in the -// configuration file -func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store { - if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" { - return newNativeStore(configFile, helper) - } - return credentials.NewFileStore(configFile) -} - -// var for unit testing. -var newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store { - return credentials.NewNativeStore(configFile, helperSuffix) -} - -// GetAuthConfig for a repository from the credential store -func (configFile *ConfigFile) GetAuthConfig(registryHostname string) (types.AuthConfig, error) { - return configFile.GetCredentialsStore(registryHostname).Get(registryHostname) -} - -// getConfiguredCredentialStore returns the credential helper configured for the -// given registry, the default credsStore, or the empty string if neither are -// configured. -func getConfiguredCredentialStore(c *ConfigFile, registryHostname string) string { - if c.CredentialHelpers != nil && registryHostname != "" { - if helper, exists := c.CredentialHelpers[registryHostname]; exists { - return helper - } - } - return c.CredentialsStore -} - -// GetAllCredentials returns all of the credentials stored in all of the -// configured credential stores. -func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, error) { - auths := make(map[string]types.AuthConfig) - addAll := func(from map[string]types.AuthConfig) { - for reg, ac := range from { - auths[reg] = ac - } - } - - defaultStore := configFile.GetCredentialsStore("") - newAuths, err := defaultStore.GetAll() - if err != nil { - return nil, err - } - addAll(newAuths) - - // Auth configs from a registry-specific helper should override those from the default store. - for registryHostname := range configFile.CredentialHelpers { - newAuth, err := configFile.GetAuthConfig(registryHostname) - if err != nil { - // TODO(thaJeztah): use context-logger, so that this output can be suppressed (in tests). - logrus.WithError(err).Warnf("Failed to get credentials for registry: %s", registryHostname) - continue - } - auths[registryHostname] = newAuth - } - return auths, nil -} - -// GetFilename returns the file name that this config file is based on. -func (configFile *ConfigFile) GetFilename() string { - return configFile.Filename -} - -// PluginConfig retrieves the requested option for the given plugin. -func (configFile *ConfigFile) PluginConfig(pluginname, option string) (string, bool) { - if configFile.Plugins == nil { - return "", false - } - pluginConfig, ok := configFile.Plugins[pluginname] - if !ok { - return "", false - } - value, ok := pluginConfig[option] - return value, ok -} - -// SetPluginConfig sets the option to the given value for the given -// plugin. Passing a value of "" will remove the option. If removing -// the final config item for a given plugin then also cleans up the -// overall plugin entry. -func (configFile *ConfigFile) SetPluginConfig(pluginname, option, value string) { - if configFile.Plugins == nil { - configFile.Plugins = make(map[string]map[string]string) - } - pluginConfig, ok := configFile.Plugins[pluginname] - if !ok { - pluginConfig = make(map[string]string) - configFile.Plugins[pluginname] = pluginConfig - } - if value != "" { - pluginConfig[option] = value - } else { - delete(pluginConfig, option) - } - if len(pluginConfig) == 0 { - delete(configFile.Plugins, pluginname) - } -} diff --git a/vendor/github.com/docker/cli/cli/config/configfile/file_unix.go b/vendor/github.com/docker/cli/cli/config/configfile/file_unix.go deleted file mode 100644 index 06b811e7..00000000 --- a/vendor/github.com/docker/cli/cli/config/configfile/file_unix.go +++ /dev/null @@ -1,35 +0,0 @@ -//go:build !windows - -package configfile - -import ( - "os" - "syscall" -) - -// copyFilePermissions copies file ownership and permissions from "src" to "dst", -// ignoring any error during the process. -func copyFilePermissions(src, dst string) { - var ( - mode os.FileMode = 0o600 - uid, gid int - ) - - fi, err := os.Stat(src) - if err != nil { - return - } - if fi.Mode().IsRegular() { - mode = fi.Mode() - } - if err := os.Chmod(dst, mode); err != nil { - return - } - - uid = int(fi.Sys().(*syscall.Stat_t).Uid) - gid = int(fi.Sys().(*syscall.Stat_t).Gid) - - if uid > 0 && gid > 0 { - _ = os.Chown(dst, uid, gid) - } -} diff --git a/vendor/github.com/docker/cli/cli/config/configfile/file_windows.go b/vendor/github.com/docker/cli/cli/config/configfile/file_windows.go deleted file mode 100644 index 42fffc39..00000000 --- a/vendor/github.com/docker/cli/cli/config/configfile/file_windows.go +++ /dev/null @@ -1,5 +0,0 @@ -package configfile - -func copyFilePermissions(src, dst string) { - // TODO implement for Windows -} diff --git a/vendor/github.com/docker/cli/cli/config/credentials/credentials.go b/vendor/github.com/docker/cli/cli/config/credentials/credentials.go deleted file mode 100644 index 28d58ec4..00000000 --- a/vendor/github.com/docker/cli/cli/config/credentials/credentials.go +++ /dev/null @@ -1,17 +0,0 @@ -package credentials - -import ( - "github.com/docker/cli/cli/config/types" -) - -// Store is the interface that any credentials store must implement. -type Store interface { - // Erase removes credentials from the store for a given server. - Erase(serverAddress string) error - // Get retrieves credentials from the store for a given server. - Get(serverAddress string) (types.AuthConfig, error) - // GetAll retrieves all the credentials from the store. - GetAll() (map[string]types.AuthConfig, error) - // Store saves credentials in the store. - Store(authConfig types.AuthConfig) error -} diff --git a/vendor/github.com/docker/cli/cli/config/credentials/default_store.go b/vendor/github.com/docker/cli/cli/config/credentials/default_store.go deleted file mode 100644 index a36afc41..00000000 --- a/vendor/github.com/docker/cli/cli/config/credentials/default_store.go +++ /dev/null @@ -1,22 +0,0 @@ -package credentials - -import "os/exec" - -// DetectDefaultStore return the default credentials store for the platform if -// no user-defined store is passed, and the store executable is available. -func DetectDefaultStore(store string) string { - if store != "" { - // use user-defined - return store - } - - platformDefault := defaultCredentialsStore() - if platformDefault == "" { - return "" - } - - if _, err := exec.LookPath(remoteCredentialsPrefix + platformDefault); err != nil { - return "" - } - return platformDefault -} diff --git a/vendor/github.com/docker/cli/cli/config/credentials/default_store_darwin.go b/vendor/github.com/docker/cli/cli/config/credentials/default_store_darwin.go deleted file mode 100644 index 5d42dec6..00000000 --- a/vendor/github.com/docker/cli/cli/config/credentials/default_store_darwin.go +++ /dev/null @@ -1,5 +0,0 @@ -package credentials - -func defaultCredentialsStore() string { - return "osxkeychain" -} diff --git a/vendor/github.com/docker/cli/cli/config/credentials/default_store_linux.go b/vendor/github.com/docker/cli/cli/config/credentials/default_store_linux.go deleted file mode 100644 index a9012c6d..00000000 --- a/vendor/github.com/docker/cli/cli/config/credentials/default_store_linux.go +++ /dev/null @@ -1,13 +0,0 @@ -package credentials - -import ( - "os/exec" -) - -func defaultCredentialsStore() string { - if _, err := exec.LookPath("pass"); err == nil { - return "pass" - } - - return "secretservice" -} diff --git a/vendor/github.com/docker/cli/cli/config/credentials/default_store_unsupported.go b/vendor/github.com/docker/cli/cli/config/credentials/default_store_unsupported.go deleted file mode 100644 index 40c16eb8..00000000 --- a/vendor/github.com/docker/cli/cli/config/credentials/default_store_unsupported.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !windows && !darwin && !linux - -package credentials - -func defaultCredentialsStore() string { - return "" -} diff --git a/vendor/github.com/docker/cli/cli/config/credentials/default_store_windows.go b/vendor/github.com/docker/cli/cli/config/credentials/default_store_windows.go deleted file mode 100644 index bb799ca6..00000000 --- a/vendor/github.com/docker/cli/cli/config/credentials/default_store_windows.go +++ /dev/null @@ -1,5 +0,0 @@ -package credentials - -func defaultCredentialsStore() string { - return "wincred" -} diff --git a/vendor/github.com/docker/cli/cli/config/credentials/file_store.go b/vendor/github.com/docker/cli/cli/config/credentials/file_store.go deleted file mode 100644 index 3b895599..00000000 --- a/vendor/github.com/docker/cli/cli/config/credentials/file_store.go +++ /dev/null @@ -1,86 +0,0 @@ -package credentials - -import ( - "net" - "net/url" - "strings" - - "github.com/docker/cli/cli/config/types" -) - -type store interface { - Save() error - GetAuthConfigs() map[string]types.AuthConfig - GetFilename() string -} - -// fileStore implements a credentials store using -// the docker configuration file to keep the credentials in plain text. -type fileStore struct { - file store -} - -// NewFileStore creates a new file credentials store. -func NewFileStore(file store) Store { - return &fileStore{file: file} -} - -// Erase removes the given credentials from the file store. -func (c *fileStore) Erase(serverAddress string) error { - delete(c.file.GetAuthConfigs(), serverAddress) - return c.file.Save() -} - -// Get retrieves credentials for a specific server from the file store. -func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) { - authConfig, ok := c.file.GetAuthConfigs()[serverAddress] - if !ok { - // Maybe they have a legacy config file, we will iterate the keys converting - // them to the new format and testing - for r, ac := range c.file.GetAuthConfigs() { - if serverAddress == ConvertToHostname(r) { - return ac, nil - } - } - - authConfig = types.AuthConfig{} - } - return authConfig, nil -} - -func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) { - return c.file.GetAuthConfigs(), nil -} - -// Store saves the given credentials in the file store. -func (c *fileStore) Store(authConfig types.AuthConfig) error { - authConfigs := c.file.GetAuthConfigs() - authConfigs[authConfig.ServerAddress] = authConfig - return c.file.Save() -} - -func (c *fileStore) GetFilename() string { - return c.file.GetFilename() -} - -func (c *fileStore) IsFileStore() bool { - return true -} - -// ConvertToHostname converts a registry url which has http|https prepended -// to just an hostname. -// Copied from github.com/docker/docker/registry.ConvertToHostname to reduce dependencies. -func ConvertToHostname(maybeURL string) string { - stripped := maybeURL - if strings.Contains(stripped, "://") { - u, err := url.Parse(stripped) - if err == nil && u.Hostname() != "" { - if u.Port() == "" { - return u.Hostname() - } - return net.JoinHostPort(u.Hostname(), u.Port()) - } - } - hostName, _, _ := strings.Cut(stripped, "/") - return hostName -} diff --git a/vendor/github.com/docker/cli/cli/config/credentials/native_store.go b/vendor/github.com/docker/cli/cli/config/credentials/native_store.go deleted file mode 100644 index b9af145b..00000000 --- a/vendor/github.com/docker/cli/cli/config/credentials/native_store.go +++ /dev/null @@ -1,147 +0,0 @@ -package credentials - -import ( - "github.com/docker/cli/cli/config/types" - "github.com/docker/docker-credential-helpers/client" - "github.com/docker/docker-credential-helpers/credentials" -) - -const ( - remoteCredentialsPrefix = "docker-credential-" //nolint:gosec // ignore G101: Potential hardcoded credentials - tokenUsername = "" -) - -// nativeStore implements a credentials store -// using native keychain to keep credentials secure. -// It piggybacks into a file store to keep users' emails. -type nativeStore struct { - programFunc client.ProgramFunc - fileStore Store -} - -// NewNativeStore creates a new native store that -// uses a remote helper program to manage credentials. -func NewNativeStore(file store, helperSuffix string) Store { - name := remoteCredentialsPrefix + helperSuffix - return &nativeStore{ - programFunc: client.NewShellProgramFunc(name), - fileStore: NewFileStore(file), - } -} - -// Erase removes the given credentials from the native store. -func (c *nativeStore) Erase(serverAddress string) error { - if err := client.Erase(c.programFunc, serverAddress); err != nil { - return err - } - - // Fallback to plain text store to remove email - return c.fileStore.Erase(serverAddress) -} - -// Get retrieves credentials for a specific server from the native store. -func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) { - // load user email if it exist or an empty auth config. - auth, _ := c.fileStore.Get(serverAddress) - - creds, err := c.getCredentialsFromStore(serverAddress) - if err != nil { - return auth, err - } - auth.Username = creds.Username - auth.IdentityToken = creds.IdentityToken - auth.Password = creds.Password - auth.ServerAddress = creds.ServerAddress - - return auth, nil -} - -// GetAll retrieves all the credentials from the native store. -func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) { - auths, err := c.listCredentialsInStore() - if err != nil { - return nil, err - } - - // Emails are only stored in the file store. - // This call can be safely eliminated when emails are removed. - fileConfigs, _ := c.fileStore.GetAll() - - authConfigs := make(map[string]types.AuthConfig) - for registry := range auths { - creds, err := c.getCredentialsFromStore(registry) - if err != nil { - return nil, err - } - ac := fileConfigs[registry] // might contain Email - ac.Username = creds.Username - ac.Password = creds.Password - ac.IdentityToken = creds.IdentityToken - if ac.ServerAddress == "" { - ac.ServerAddress = creds.ServerAddress - } - authConfigs[registry] = ac - } - - return authConfigs, nil -} - -// Store saves the given credentials in the file store. -func (c *nativeStore) Store(authConfig types.AuthConfig) error { - if err := c.storeCredentialsInStore(authConfig); err != nil { - return err - } - authConfig.Username = "" - authConfig.Password = "" - authConfig.IdentityToken = "" - - // Fallback to old credential in plain text to save only the email - return c.fileStore.Store(authConfig) -} - -// storeCredentialsInStore executes the command to store the credentials in the native store. -func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error { - creds := &credentials.Credentials{ - ServerURL: config.ServerAddress, - Username: config.Username, - Secret: config.Password, - } - - if config.IdentityToken != "" { - creds.Username = tokenUsername - creds.Secret = config.IdentityToken - } - - return client.Store(c.programFunc, creds) -} - -// getCredentialsFromStore executes the command to get the credentials from the native store. -func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) { - var ret types.AuthConfig - - creds, err := client.Get(c.programFunc, serverAddress) - if err != nil { - if credentials.IsErrCredentialsNotFound(err) { - // do not return an error if the credentials are not - // in the keychain. Let docker ask for new credentials. - return ret, nil - } - return ret, err - } - - if creds.Username == tokenUsername { - ret.IdentityToken = creds.Secret - } else { - ret.Password = creds.Secret - ret.Username = creds.Username - } - - ret.ServerAddress = serverAddress - return ret, nil -} - -// listCredentialsInStore returns a listing of stored credentials as a map of -// URL -> username. -func (c *nativeStore) listCredentialsInStore() (map[string]string, error) { - return client.List(c.programFunc) -} diff --git a/vendor/github.com/docker/cli/cli/connhelper/commandconn/commandconn.go b/vendor/github.com/docker/cli/cli/connhelper/commandconn/commandconn.go deleted file mode 100644 index cfc2b86a..00000000 --- a/vendor/github.com/docker/cli/cli/connhelper/commandconn/commandconn.go +++ /dev/null @@ -1,289 +0,0 @@ -// Package commandconn provides a net.Conn implementation that can be used for -// proxying (or emulating) stream via a custom command. -// -// 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") -// }, -// }, -// } -package commandconn - -import ( - "bytes" - "context" - "fmt" - "io" - "net" - "os" - "os/exec" - "runtime" - "strings" - "sync" - "sync/atomic" - "syscall" - "time" - - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// New returns net.Conn -func New(_ context.Context, cmd string, args ...string) (net.Conn, error) { - var ( - c commandConn - err error - ) - 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() - c.cmd.SysProcAttr = &syscall.SysProcAttr{} - setPdeathsig(c.cmd) - createSession(c.cmd) - c.stdin, err = c.cmd.StdinPipe() - if err != nil { - return nil, err - } - c.stdout, err = c.cmd.StdoutPipe() - if err != nil { - return nil, err - } - c.cmd.Stderr = &stderrWriter{ - stderrMu: &c.stderrMu, - stderr: &c.stderr, - debugPrefix: fmt.Sprintf("commandconn (%s):", cmd), - } - c.localAddr = dummyAddr{network: "dummy", s: "dummy-0"} - c.remoteAddr = dummyAddr{network: "dummy", s: "dummy-1"} - return &c, c.cmd.Start() -} - -// commandConn implements net.Conn -type commandConn struct { - cmdMutex sync.Mutex // for cmd, cmdWaitErr - cmd *exec.Cmd - cmdWaitErr error - cmdExited atomic.Bool - stdin io.WriteCloser - stdout io.ReadCloser - stderrMu sync.Mutex // for stderr - stderr bytes.Buffer - stdinClosed atomic.Bool - stdoutClosed atomic.Bool - closing atomic.Bool - localAddr net.Addr - remoteAddr net.Addr -} - -// kill terminates the process. On Windows it kills the process directly, -// whereas on other platforms, a SIGTERM is sent, before forcefully terminating -// the process after 3 seconds. -func (c *commandConn) kill() { - if c.cmdExited.Load() { - return - } - c.cmdMutex.Lock() - var werr error - if runtime.GOOS != "windows" { - werrCh := make(chan error) - go func() { werrCh <- c.cmd.Wait() }() - _ = c.cmd.Process.Signal(syscall.SIGTERM) - select { - case werr = <-werrCh: - case <-time.After(3 * time.Second): - _ = c.cmd.Process.Kill() - werr = <-werrCh - } - } else { - _ = c.cmd.Process.Kill() - werr = c.cmd.Wait() - } - c.cmdWaitErr = werr - c.cmdMutex.Unlock() - c.cmdExited.Store(true) -} - -// handleEOF handles io.EOF errors while reading or writing from the underlying -// command pipes. -// -// When we've received an EOF we expect that the command will -// be terminated soon. As such, we call Wait() on the command -// and return EOF or the error depending on whether the command -// exited with an error. -// -// If Wait() does not return within 10s, an error is returned -func (c *commandConn) handleEOF(err error) error { - if err != io.EOF { - return err - } - - c.cmdMutex.Lock() - defer c.cmdMutex.Unlock() - - var werr error - if c.cmdExited.Load() { - werr = c.cmdWaitErr - } else { - werrCh := make(chan error) - go func() { werrCh <- c.cmd.Wait() }() - select { - case werr = <-werrCh: - c.cmdWaitErr = werr - c.cmdExited.Store(true) - case <-time.After(10 * time.Second): - c.stderrMu.Lock() - stderr := c.stderr.String() - c.stderrMu.Unlock() - return errors.Errorf("command %v did not exit after %v: stderr=%q", c.cmd.Args, err, stderr) - } - } - - if werr == nil { - return err - } - c.stderrMu.Lock() - stderr := c.stderr.String() - c.stderrMu.Unlock() - return errors.Errorf("command %v has exited with %v, make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=%s", c.cmd.Args, werr, stderr) -} - -func ignorableCloseError(err error) bool { - return strings.Contains(err.Error(), os.ErrClosed.Error()) -} - -func (c *commandConn) Read(p []byte) (int, error) { - n, err := c.stdout.Read(p) - // check after the call to Read, since - // it is blocking, and while waiting on it - // Close might get called - if c.closing.Load() { - // If we're currently closing the connection - // we don't want to call onEOF - return n, err - } - - return n, c.handleEOF(err) -} - -func (c *commandConn) Write(p []byte) (int, error) { - n, err := c.stdin.Write(p) - // check after the call to Write, since - // it is blocking, and while waiting on it - // Close might get called - if c.closing.Load() { - // If we're currently closing the connection - // we don't want to call onEOF - return n, err - } - - return n, c.handleEOF(err) -} - -// CloseRead allows commandConn to implement halfCloser -func (c *commandConn) CloseRead() error { - // NOTE: maybe already closed here - if err := c.stdout.Close(); err != nil && !ignorableCloseError(err) { - return err - } - c.stdoutClosed.Store(true) - - if c.stdinClosed.Load() { - c.kill() - } - - return nil -} - -// CloseWrite allows commandConn to implement halfCloser -func (c *commandConn) CloseWrite() error { - // NOTE: maybe already closed here - if err := c.stdin.Close(); err != nil && !ignorableCloseError(err) { - return err - } - c.stdinClosed.Store(true) - - if c.stdoutClosed.Load() { - c.kill() - } - return nil -} - -// Close is the net.Conn func that gets called -// by the transport when a dial is cancelled -// due to it's context timing out. Any blocked -// Read or Write calls will be unblocked and -// return errors. It will block until the underlying -// command has terminated. -func (c *commandConn) Close() error { - c.closing.Store(true) - defer c.closing.Store(false) - - if err := c.CloseRead(); err != nil { - logrus.Warnf("commandConn.Close: CloseRead: %v", err) - return err - } - if err := c.CloseWrite(); err != nil { - logrus.Warnf("commandConn.Close: CloseWrite: %v", err) - return err - } - - return nil -} - -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 -} - -type dummyAddr struct { - network string - s string -} - -func (d dummyAddr) Network() string { - return d.network -} - -func (d dummyAddr) String() string { - return d.s -} - -type stderrWriter struct { - stderrMu *sync.Mutex - stderr *bytes.Buffer - debugPrefix string -} - -func (w *stderrWriter) Write(p []byte) (int, error) { - logrus.Debugf("%s%s", w.debugPrefix, string(p)) - w.stderrMu.Lock() - if w.stderr.Len() > 4096 { - w.stderr.Reset() - } - n, err := w.stderr.Write(p) - w.stderrMu.Unlock() - return n, err -} diff --git a/vendor/github.com/docker/cli/cli/connhelper/commandconn/pdeathsig_linux.go b/vendor/github.com/docker/cli/cli/connhelper/commandconn/pdeathsig_linux.go deleted file mode 100644 index 1cdd788c..00000000 --- a/vendor/github.com/docker/cli/cli/connhelper/commandconn/pdeathsig_linux.go +++ /dev/null @@ -1,10 +0,0 @@ -package commandconn - -import ( - "os/exec" - "syscall" -) - -func setPdeathsig(cmd *exec.Cmd) { - cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL -} diff --git a/vendor/github.com/docker/cli/cli/connhelper/commandconn/pdeathsig_nolinux.go b/vendor/github.com/docker/cli/cli/connhelper/commandconn/pdeathsig_nolinux.go deleted file mode 100644 index 149349e9..00000000 --- a/vendor/github.com/docker/cli/cli/connhelper/commandconn/pdeathsig_nolinux.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !linux - -package commandconn - -import ( - "os/exec" -) - -func setPdeathsig(*exec.Cmd) {} diff --git a/vendor/github.com/docker/cli/cli/connhelper/commandconn/session_unix.go b/vendor/github.com/docker/cli/cli/connhelper/commandconn/session_unix.go deleted file mode 100644 index 3d2cb277..00000000 --- a/vendor/github.com/docker/cli/cli/connhelper/commandconn/session_unix.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !windows - -package commandconn - -import ( - "os/exec" -) - -func createSession(cmd *exec.Cmd) { - // for supporting ssh connection helper with ProxyCommand - // https://github.com/docker/cli/issues/1707 - cmd.SysProcAttr.Setsid = true -} diff --git a/vendor/github.com/docker/cli/cli/connhelper/commandconn/session_windows.go b/vendor/github.com/docker/cli/cli/connhelper/commandconn/session_windows.go deleted file mode 100644 index b9260745..00000000 --- a/vendor/github.com/docker/cli/cli/connhelper/commandconn/session_windows.go +++ /dev/null @@ -1,8 +0,0 @@ -package commandconn - -import ( - "os/exec" -) - -func createSession(cmd *exec.Cmd) { -} diff --git a/vendor/github.com/docker/cli/cli/connhelper/connhelper.go b/vendor/github.com/docker/cli/cli/connhelper/connhelper.go deleted file mode 100644 index 1797abae..00000000 --- a/vendor/github.com/docker/cli/cli/connhelper/connhelper.go +++ /dev/null @@ -1,81 +0,0 @@ -// Package connhelper provides helpers for connecting to a remote daemon host with custom logic. -package connhelper - -import ( - "context" - "net" - "net/url" - "strings" - - "github.com/docker/cli/cli/connhelper/commandconn" - "github.com/docker/cli/cli/connhelper/ssh" - "github.com/pkg/errors" -) - -// ConnectionHelper allows to connect to a remote host with custom stream provider binary. -type ConnectionHelper struct { - Dialer func(ctx context.Context, network, addr string) (net.Conn, error) - Host string // dummy URL used for HTTP requests. e.g. "http://docker" -} - -// GetConnectionHelper returns Docker-specific connection helper for the given URL. -// GetConnectionHelper returns nil without error when no helper is registered for the scheme. -// -// ssh://@ URL requires Docker 18.09 or later on the remote host. -func GetConnectionHelper(daemonURL string) (*ConnectionHelper, error) { - return getConnectionHelper(daemonURL, nil) -} - -// GetConnectionHelperWithSSHOpts returns Docker-specific connection helper for -// the given URL, and accepts additional options for ssh connections. It returns -// nil without error when no helper is registered for the scheme. -// -// Requires Docker 18.09 or later on the remote host. -func GetConnectionHelperWithSSHOpts(daemonURL string, sshFlags []string) (*ConnectionHelper, error) { - return getConnectionHelper(daemonURL, sshFlags) -} - -func getConnectionHelper(daemonURL string, sshFlags []string) (*ConnectionHelper, error) { - u, err := url.Parse(daemonURL) - if err != nil { - return nil, err - } - if u.Scheme == "ssh" { - sp, err := ssh.ParseURL(daemonURL) - if err != nil { - return nil, errors.Wrap(err, "ssh host connection is not valid") - } - return &ConnectionHelper{ - Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) { - args := []string{"docker"} - if sp.Path != "" { - args = append(args, "--host", "unix://"+sp.Path) - } - sshFlags = addSSHTimeout(sshFlags) - args = append(args, "system", "dial-stdio") - return commandconn.New(ctx, "ssh", append(sshFlags, sp.Args(args...)...)...) - }, - Host: "http://docker.example.com", - }, nil - } - // Future version may support plugins via ~/.docker/config.json. e.g. "dind" - // See docker/cli#889 for the previous discussion. - return nil, err -} - -// GetCommandConnectionHelper returns Docker-specific connection helper constructed from an arbitrary command. -func GetCommandConnectionHelper(cmd string, flags ...string) (*ConnectionHelper, error) { - return &ConnectionHelper{ - Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) { - return commandconn.New(ctx, cmd, flags...) - }, - Host: "http://docker.example.com", - }, nil -} - -func addSSHTimeout(sshFlags []string) []string { - if !strings.Contains(strings.Join(sshFlags, ""), "ConnectTimeout") { - sshFlags = append(sshFlags, "-o ConnectTimeout=30") - } - return sshFlags -} diff --git a/vendor/github.com/docker/cli/cli/connhelper/ssh/ssh.go b/vendor/github.com/docker/cli/cli/connhelper/ssh/ssh.go deleted file mode 100644 index fb4c9111..00000000 --- a/vendor/github.com/docker/cli/cli/connhelper/ssh/ssh.go +++ /dev/null @@ -1,63 +0,0 @@ -// Package ssh provides the connection helper for ssh:// URL. -package ssh - -import ( - "net/url" - - "github.com/pkg/errors" -) - -// ParseURL parses URL -func ParseURL(daemonURL string) (*Spec, error) { - u, err := url.Parse(daemonURL) - if err != nil { - return nil, err - } - if u.Scheme != "ssh" { - return nil, errors.Errorf("expected scheme ssh, got %q", u.Scheme) - } - - var sp Spec - - if u.User != nil { - sp.User = u.User.Username() - if _, ok := u.User.Password(); ok { - return nil, errors.New("plain-text password is not supported") - } - } - sp.Host = u.Hostname() - if sp.Host == "" { - return nil, errors.Errorf("no host specified") - } - sp.Port = u.Port() - sp.Path = u.Path - if u.RawQuery != "" { - return nil, errors.Errorf("extra query after the host: %q", u.RawQuery) - } - if u.Fragment != "" { - return nil, errors.Errorf("extra fragment after the host: %q", u.Fragment) - } - return &sp, err -} - -// Spec of SSH URL -type Spec struct { - User string - Host string - Port string - Path string -} - -// Args returns args except "ssh" itself combined with optional additional command args -func (sp *Spec) Args(add ...string) []string { - var args []string - if sp.User != "" { - args = append(args, "-l", sp.User) - } - if sp.Port != "" { - args = append(args, "-p", sp.Port) - } - args = append(args, "--", sp.Host) - args = append(args, add...) - return args -} diff --git a/vendor/github.com/docker/cli/cli/context/docker/constants.go b/vendor/github.com/docker/cli/cli/context/docker/constants.go deleted file mode 100644 index 1db5556d..00000000 --- a/vendor/github.com/docker/cli/cli/context/docker/constants.go +++ /dev/null @@ -1,6 +0,0 @@ -package docker - -const ( - // DockerEndpoint is the name of the docker endpoint in a stored context - DockerEndpoint = "docker" -) diff --git a/vendor/github.com/docker/cli/cli/context/docker/load.go b/vendor/github.com/docker/cli/cli/context/docker/load.go deleted file mode 100644 index 700b73c9..00000000 --- a/vendor/github.com/docker/cli/cli/context/docker/load.go +++ /dev/null @@ -1,149 +0,0 @@ -package docker - -import ( - "crypto/tls" - "crypto/x509" - "encoding/pem" - "net" - "net/http" - "time" - - "github.com/docker/cli/cli/connhelper" - "github.com/docker/cli/cli/context" - "github.com/docker/cli/cli/context/store" - "github.com/docker/docker/client" - "github.com/docker/go-connections/tlsconfig" - "github.com/pkg/errors" -) - -// EndpointMeta is a typed wrapper around a context-store generic endpoint describing -// a Docker Engine endpoint, without its tls config -type EndpointMeta = context.EndpointMetaBase - -// Endpoint is a typed wrapper around a context-store generic endpoint describing -// a Docker Engine endpoint, with its tls data -type Endpoint struct { - EndpointMeta - TLSData *context.TLSData -} - -// WithTLSData loads TLS materials for the endpoint -func WithTLSData(s store.Reader, contextName string, m EndpointMeta) (Endpoint, error) { - tlsData, err := context.LoadTLSData(s, contextName, DockerEndpoint) - if err != nil { - return Endpoint{}, err - } - return Endpoint{ - EndpointMeta: m, - TLSData: tlsData, - }, nil -} - -// tlsConfig extracts a context docker endpoint TLS config -func (ep *Endpoint) tlsConfig() (*tls.Config, error) { - if ep.TLSData == nil && !ep.SkipTLSVerify { - // there is no specific tls config - return nil, nil - } - var tlsOpts []func(*tls.Config) - if ep.TLSData != nil && ep.TLSData.CA != nil { - certPool := x509.NewCertPool() - if !certPool.AppendCertsFromPEM(ep.TLSData.CA) { - return nil, errors.New("failed to retrieve context tls info: ca.pem seems invalid") - } - tlsOpts = append(tlsOpts, func(cfg *tls.Config) { - cfg.RootCAs = certPool - }) - } - if ep.TLSData != nil && ep.TLSData.Key != nil && ep.TLSData.Cert != nil { - keyBytes := ep.TLSData.Key - pemBlock, _ := pem.Decode(keyBytes) - if pemBlock == nil { - return nil, errors.New("no valid private key found") - } - 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(ep.TLSData.Cert, keyBytes) - if err != nil { - return nil, errors.Wrap(err, "failed to retrieve context tls info") - } - tlsOpts = append(tlsOpts, func(cfg *tls.Config) { - cfg.Certificates = []tls.Certificate{x509cert} - }) - } - if ep.SkipTLSVerify { - tlsOpts = append(tlsOpts, func(cfg *tls.Config) { - cfg.InsecureSkipVerify = true - }) - } - return tlsconfig.ClientDefault(tlsOpts...), nil -} - -// ClientOpts returns a slice of Client options to configure an API client with this endpoint -func (ep *Endpoint) ClientOpts() ([]client.Opt, error) { - var result []client.Opt - if ep.Host != "" { - helper, err := connhelper.GetConnectionHelper(ep.Host) - if err != nil { - return nil, err - } - if helper == nil { - tlsConfig, err := ep.tlsConfig() - if err != nil { - return nil, err - } - result = append(result, - withHTTPClient(tlsConfig), - client.WithHost(ep.Host), - ) - } else { - result = append(result, - client.WithHTTPClient(&http.Client{ - // No TLS, and no proxy. - Transport: &http.Transport{ - DialContext: helper.Dialer, - }, - }), - client.WithHost(helper.Host), - client.WithDialContext(helper.Dialer), - ) - } - } - - result = append(result, client.WithVersionFromEnv(), client.WithAPIVersionNegotiation()) - return result, nil -} - -func withHTTPClient(tlsConfig *tls.Config) func(*client.Client) error { - return func(c *client.Client) error { - if tlsConfig == nil { - // Use the default HTTPClient - return nil - } - return client.WithHTTPClient(&http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - DialContext: (&net.Dialer{ - KeepAlive: 30 * time.Second, - Timeout: 30 * time.Second, - }).DialContext, - }, - CheckRedirect: client.CheckRedirect, - })(c) - } -} - -// EndpointFromContext parses a context docker endpoint metadata into a typed EndpointMeta structure -func EndpointFromContext(metadata store.Metadata) (EndpointMeta, error) { - ep, ok := metadata.Endpoints[DockerEndpoint] - if !ok { - return EndpointMeta{}, errors.New("cannot find docker endpoint in context") - } - typed, ok := ep.(EndpointMeta) - if !ok { - return EndpointMeta{}, errors.Errorf("endpoint %q is not of type EndpointMeta", DockerEndpoint) - } - return typed, nil -} diff --git a/vendor/github.com/docker/cli/cli/context/endpoint.go b/vendor/github.com/docker/cli/cli/context/endpoint.go deleted file mode 100644 index f2735246..00000000 --- a/vendor/github.com/docker/cli/cli/context/endpoint.go +++ /dev/null @@ -1,7 +0,0 @@ -package context - -// EndpointMetaBase contains fields we expect to be common for most context endpoints -type EndpointMetaBase struct { - Host string `json:",omitempty"` - SkipTLSVerify bool -} diff --git a/vendor/github.com/docker/cli/cli/context/store/doc.go b/vendor/github.com/docker/cli/cli/context/store/doc.go deleted file mode 100644 index 705982ae..00000000 --- a/vendor/github.com/docker/cli/cli/context/store/doc.go +++ /dev/null @@ -1,32 +0,0 @@ -// 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 macOS the -// user Keychain...). -// -// Current implementation is purely file based with the following structure: -// -// ${CONTEXT_ROOT} -// meta/ -// /meta.json: contains context medata (key/value pairs) as -// well as a list of endpoints (themselves containing -// key/value pair metadata). -// tls/ -// /endpoint1/: directory containing TLS data for the endpoint1 -// in the corresponding context. -// -// 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 diff --git a/vendor/github.com/docker/cli/cli/context/store/io_utils.go b/vendor/github.com/docker/cli/cli/context/store/io_utils.go deleted file mode 100644 index 6f854c8e..00000000 --- a/vendor/github.com/docker/cli/cli/context/store/io_utils.go +++ /dev/null @@ -1,29 +0,0 @@ -package store - -import ( - "errors" - "io" -) - -// LimitedReader is a fork of io.LimitedReader to override Read. -type LimitedReader struct { - R io.Reader - N int64 // max bytes remaining -} - -// Read is a fork of io.LimitedReader.Read that returns an error when limit exceeded. -func (l *LimitedReader) Read(p []byte) (n int, err error) { - if l.N < 0 { - return 0, errors.New("read exceeds the defined limit") - } - if l.N == 0 { - return 0, io.EOF - } - // have to cap N + 1 otherwise we won't hit limit err - if int64(len(p)) > l.N+1 { - p = p[0 : l.N+1] - } - n, err = l.R.Read(p) - l.N -= int64(n) - return n, err -} diff --git a/vendor/github.com/docker/cli/cli/context/store/metadatastore.go b/vendor/github.com/docker/cli/cli/context/store/metadatastore.go deleted file mode 100644 index 0c6986fa..00000000 --- a/vendor/github.com/docker/cli/cli/context/store/metadatastore.go +++ /dev/null @@ -1,167 +0,0 @@ -// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: -//go:build go1.21 - -package store - -import ( - "encoding/json" - "fmt" - "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 ( - metadataDir = "meta" - metaFile = "meta.json" -) - -type metadataStore struct { - root string - config Config -} - -func (s *metadataStore) contextDir(id contextdir) string { - return filepath.Join(s.root, string(id)) -} - -func (s *metadataStore) createOrUpdate(meta Metadata) error { - contextDir := s.contextDir(contextdirOf(meta.Name)) - if err := os.MkdirAll(contextDir, 0o755); err != nil { - return err - } - bytes, err := json.Marshal(&meta) - if err != nil { - return err - } - return ioutils.AtomicWriteFile(filepath.Join(contextDir, metaFile), bytes, 0o644) -} - -func parseTypedOrMap(payload []byte, getter TypeGetter) (any, error) { - if len(payload) == 0 || string(payload) == "null" { - return nil, nil - } - if getter == nil { - var res map[string]any - if err := json.Unmarshal(payload, &res); err != nil { - return nil, err - } - return res, nil - } - typed := getter() - if err := json.Unmarshal(payload, typed); err != nil { - return nil, err - } - return reflect.ValueOf(typed).Elem().Interface(), nil -} - -func (s *metadataStore) get(name string) (Metadata, error) { - m, err := s.getByID(contextdirOf(name)) - if err != nil { - 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]any), - } - if err := json.Unmarshal(bytes, &untyped); err != nil { - 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{}, 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{}, fmt.Errorf("parsing %s: %v", fileName, err) - } - } - return r, err -} - -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 errors.Is(err, os.ErrNotExist) { - return nil, nil - } - return nil, err - } - res := make([]Metadata, 0, len(ctxDirs)) - for _, dir := range ctxDirs { - c, err := s.getByID(contextdir(dir)) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - continue - } - return nil, errors.Wrap(err, "failed to read metadata") - } - res = append(res, c) - } - sort.Slice(res, func(i, j int) bool { - return sortorder.NaturalLess(res[i].Name, res[j].Name) - }) - return res, nil -} - -func isContextDir(path string) bool { - s, err := os.Stat(filepath.Join(path, metaFile)) - if err != nil { - return false - } - return !s.IsDir() -} - -func listRecursivelyMetadataDirs(root string) ([]string, error) { - fis, err := os.ReadDir(root) - if err != nil { - return nil, err - } - var result []string - for _, fi := range fis { - if fi.IsDir() { - if isContextDir(filepath.Join(root, fi.Name())) { - result = append(result, fi.Name()) - } - subs, err := listRecursivelyMetadataDirs(filepath.Join(root, fi.Name())) - if err != nil { - return nil, err - } - for _, s := range subs { - result = append(result, filepath.Join(fi.Name(), s)) - } - } - } - return result, nil -} - -type untypedContextMetadata struct { - Metadata json.RawMessage `json:"metadata,omitempty"` - Endpoints map[string]json.RawMessage `json:"endpoints,omitempty"` - Name string `json:"name,omitempty"` -} diff --git a/vendor/github.com/docker/cli/cli/context/store/store.go b/vendor/github.com/docker/cli/cli/context/store/store.go deleted file mode 100644 index 066b5769..00000000 --- a/vendor/github.com/docker/cli/cli/context/store/store.go +++ /dev/null @@ -1,507 +0,0 @@ -// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: -//go:build go1.21 - -package store - -import ( - "archive/tar" - "archive/zip" - "bufio" - "bytes" - _ "crypto/sha256" // ensure ids can be computed - "encoding/json" - "io" - "net/http" - "path" - "path/filepath" - "regexp" - "strings" - - "github.com/docker/docker/errdefs" - "github.com/opencontainers/go-digest" - "github.com/pkg/errors" -) - -const restrictedNamePattern = "^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$" - -var restrictedNameRegEx = regexp.MustCompile(restrictedNamePattern) - -// Store provides a context store for easily remembering endpoints configuration -type Store interface { - Reader - Lister - Writer - StorageInfoProvider -} - -// Reader provides read-only (without list) access to context data -type Reader interface { - GetMetadata(name string) (Metadata, error) - ListTLSFiles(name string) (map[string]EndpointFiles, error) - GetTLSData(contextName, endpointName, fileName string) ([]byte, error) -} - -// Lister provides listing of contexts -type Lister interface { - List() ([]Metadata, error) -} - -// ReaderLister combines Reader and Lister interfaces -type ReaderLister interface { - Reader - Lister -} - -// StorageInfoProvider provides more information about storage details of contexts -type StorageInfoProvider interface { - GetStorageInfo(contextName string) StorageInfo -} - -// Writer provides write access to context data -type Writer interface { - CreateOrUpdate(meta Metadata) error - Remove(name string) error - ResetTLSMaterial(name string, data *ContextTLSData) error - ResetEndpointTLSMaterial(contextName string, endpointName string, data *EndpointTLSData) error -} - -// ReaderWriter combines Reader and Writer interfaces -type ReaderWriter interface { - Reader - Writer -} - -// Metadata contains metadata about a context and its endpoints -type Metadata struct { - Name string `json:",omitempty"` - Metadata any `json:",omitempty"` - Endpoints map[string]any `json:",omitempty"` -} - -// StorageInfo contains data about where a given context is stored -type StorageInfo struct { - MetadataPath string - TLSPath string -} - -// EndpointTLSData represents tls data for a given endpoint -type EndpointTLSData struct { - Files map[string][]byte -} - -// ContextTLSData represents tls data for a whole context -type ContextTLSData struct { - Endpoints map[string]EndpointTLSData -} - -// 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) *ContextStore { - metaRoot := filepath.Join(dir, metadataDir) - tlsRoot := filepath.Join(dir, tlsDir) - - return &ContextStore{ - meta: &metadataStore{ - root: metaRoot, - config: cfg, - }, - tls: &tlsStore{ - root: tlsRoot, - }, - } -} - -// ContextStore implements Store. -type ContextStore struct { - meta *metadataStore - tls *tlsStore -} - -// List return all contexts. -func (s *ContextStore) List() ([]Metadata, error) { - return s.meta.list() -} - -// Names return Metadata names for a Lister -func Names(s Lister) ([]string, error) { - if s == nil { - return nil, errors.New("nil lister") - } - list, err := s.List() - if err != nil { - return nil, err - } - names := make([]string, 0, len(list)) - 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) -} - -// 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) - } - if err := s.tls.remove(name); err != nil { - return errors.Wrapf(err, "failed to remove context %s", name) - } - return nil -} - -// 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) -} - -// 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(name, ep, fileName, data); err != nil { - return err - } - } - } - return nil -} - -// 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(contextName, endpointName, fileName, data); err != nil { - return err - } - } - return nil -} - -// 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) -} - -// 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) -} - -// 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(contextdirOf(contextName)), - TLSPath: s.tls.contextDir(contextName), - } -} - -// ValidateContextName checks a context name is valid. -func ValidateContextName(name string) error { - if name == "" { - return errors.New("context name cannot be empty") - } - if name == "default" { - return errors.New(`"default" is a reserved context name`) - } - if !restrictedNameRegEx.MatchString(name) { - return errors.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern) - } - return nil -} - -// Export exports an existing namespace into an opaque data stream -// This stream is actually a tarball containing context metadata and TLS materials, but it does -// not map 1:1 the layout of the context store (don't try to restore it manually without calling store.Import) -func Export(name string, s Reader) io.ReadCloser { - reader, writer := io.Pipe() - go func() { - tw := tar.NewWriter(writer) - defer tw.Close() - defer writer.Close() - meta, err := s.GetMetadata(name) - if err != nil { - writer.CloseWithError(err) - return - } - metaBytes, err := json.Marshal(&meta) - if err != nil { - writer.CloseWithError(err) - return - } - if err = tw.WriteHeader(&tar.Header{ - Name: metaFile, - Mode: 0o644, - Size: int64(len(metaBytes)), - }); err != nil { - writer.CloseWithError(err) - return - } - if _, err = tw.Write(metaBytes); err != nil { - writer.CloseWithError(err) - return - } - tlsFiles, err := s.ListTLSFiles(name) - if err != nil { - writer.CloseWithError(err) - return - } - if err = tw.WriteHeader(&tar.Header{ - Name: "tls", - Mode: 0o700, - Size: 0, - Typeflag: tar.TypeDir, - }); err != nil { - writer.CloseWithError(err) - return - } - for endpointName, endpointFiles := range tlsFiles { - if err = tw.WriteHeader(&tar.Header{ - Name: path.Join("tls", endpointName), - Mode: 0o700, - Size: 0, - Typeflag: tar.TypeDir, - }); err != nil { - writer.CloseWithError(err) - return - } - for _, fileName := range endpointFiles { - data, err := s.GetTLSData(name, endpointName, fileName) - if err != nil { - writer.CloseWithError(err) - return - } - if err = tw.WriteHeader(&tar.Header{ - Name: path.Join("tls", endpointName, fileName), - Mode: 0o600, - Size: int64(len(data)), - }); err != nil { - writer.CloseWithError(err) - return - } - if _, err = tw.Write(data); err != nil { - writer.CloseWithError(err) - return - } - } - } - }() - return reader -} - -const ( - maxAllowedFileSizeToImport int64 = 10 << 20 - zipType string = "application/zip" -) - -func getImportContentType(r *bufio.Reader) (string, error) { - head, err := r.Peek(512) - if err != nil && err != io.EOF { - return "", err - } - - return http.DetectContentType(head), nil -} - -// Import imports an exported context into a store -func Import(name string, s Writer, reader io.Reader) error { - // Buffered reader will not advance the buffer, needed to determine content type - r := bufio.NewReader(reader) - - importContentType, err := getImportContentType(r) - if err != nil { - return err - } - switch importContentType { - case zipType: - return importZip(name, s, r) - default: - // Assume it's a TAR (TAR does not have a "magic number") - return importTar(name, s, r) - } -} - -func isValidFilePath(p string) error { - if p != metaFile && !strings.HasPrefix(p, "tls/") { - return errors.New("unexpected context file") - } - if path.Clean(p) != p { - return errors.New("unexpected path format") - } - if strings.Contains(p, `\`) { - return errors.New(`unexpected '\' in path`) - } - return nil -} - -func importTar(name string, s Writer, reader io.Reader) error { - tr := tar.NewReader(&LimitedReader{R: reader, N: maxAllowedFileSizeToImport}) - tlsData := ContextTLSData{ - Endpoints: map[string]EndpointTLSData{}, - } - var importedMetaFile bool - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - if hdr.Typeflag != tar.TypeReg { - // skip this entry, only taking files into account - continue - } - if err := isValidFilePath(hdr.Name); err != nil { - return errors.Wrap(err, hdr.Name) - } - if hdr.Name == metaFile { - data, err := io.ReadAll(tr) - if err != nil { - return err - } - meta, err := parseMetadata(data, name) - if err != nil { - return err - } - if err := s.CreateOrUpdate(meta); err != nil { - return err - } - importedMetaFile = true - } else if strings.HasPrefix(hdr.Name, "tls/") { - data, err := io.ReadAll(tr) - if err != nil { - return err - } - if err := importEndpointTLS(&tlsData, hdr.Name, data); err != nil { - return err - } - } - } - if !importedMetaFile { - return errdefs.InvalidParameter(errors.New("invalid context: no metadata found")) - } - return s.ResetTLSMaterial(name, &tlsData) -} - -func importZip(name string, s Writer, reader io.Reader) error { - body, err := io.ReadAll(&LimitedReader{R: reader, N: maxAllowedFileSizeToImport}) - if err != nil { - return err - } - zr, err := zip.NewReader(bytes.NewReader(body), int64(len(body))) - if err != nil { - return err - } - tlsData := ContextTLSData{ - Endpoints: map[string]EndpointTLSData{}, - } - - var importedMetaFile bool - for _, zf := range zr.File { - fi := zf.FileInfo() - if !fi.Mode().IsRegular() { - // skip this entry, only taking regular files into account - continue - } - if err := isValidFilePath(zf.Name); err != nil { - return errors.Wrap(err, zf.Name) - } - if zf.Name == metaFile { - f, err := zf.Open() - if err != nil { - return err - } - - data, err := io.ReadAll(&LimitedReader{R: f, N: maxAllowedFileSizeToImport}) - defer f.Close() - if err != nil { - return err - } - meta, err := parseMetadata(data, name) - if err != nil { - return err - } - if err := s.CreateOrUpdate(meta); err != nil { - return err - } - importedMetaFile = true - } else if strings.HasPrefix(zf.Name, "tls/") { - f, err := zf.Open() - if err != nil { - return err - } - data, err := io.ReadAll(f) - defer f.Close() - if err != nil { - return err - } - err = importEndpointTLS(&tlsData, zf.Name, data) - if err != nil { - return err - } - } - } - if !importedMetaFile { - return errdefs.InvalidParameter(errors.New("invalid context: no metadata found")) - } - return s.ResetTLSMaterial(name, &tlsData) -} - -func parseMetadata(data []byte, name string) (Metadata, error) { - var meta Metadata - if err := json.Unmarshal(data, &meta); err != nil { - return meta, err - } - if err := ValidateContextName(name); err != nil { - return Metadata{}, err - } - meta.Name = name - return meta, nil -} - -func importEndpointTLS(tlsData *ContextTLSData, tlsPath string, data []byte) error { - parts := strings.SplitN(strings.TrimPrefix(tlsPath, "tls/"), "/", 2) - if len(parts) != 2 { - // TLS endpoints require archived file directory with 2 layers - // i.e. tls/{endpointName}/{fileName} - return errors.New("archive format is invalid") - } - - epName := parts[0] - fileName := parts[1] - if _, ok := tlsData.Endpoints[epName]; !ok { - tlsData.Endpoints[epName] = EndpointTLSData{ - Files: map[string][]byte{}, - } - } - tlsData.Endpoints[epName].Files[fileName] = data - return nil -} - -type contextdir string - -func contextdirOf(name string) contextdir { - return contextdir(digest.FromString(name).Encoded()) -} diff --git a/vendor/github.com/docker/cli/cli/context/store/storeconfig.go b/vendor/github.com/docker/cli/cli/context/store/storeconfig.go deleted file mode 100644 index aa516a19..00000000 --- a/vendor/github.com/docker/cli/cli/context/store/storeconfig.go +++ /dev/null @@ -1,56 +0,0 @@ -// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: -//go:build go1.21 - -package store - -// TypeGetter is a func used to determine the concrete type of a context or -// endpoint metadata by returning a pointer to an instance of the object -// eg: for a context of type DockerContext, the corresponding TypeGetter should return new(DockerContext) -type TypeGetter func() any - -// NamedTypeGetter is a TypeGetter associated with a name -type NamedTypeGetter struct { - name string - typeGetter TypeGetter -} - -// EndpointTypeGetter returns a NamedTypeGetter with the specified name and getter -func EndpointTypeGetter(name string, getter TypeGetter) NamedTypeGetter { - return NamedTypeGetter{ - name: name, - typeGetter: getter, - } -} - -// Config is used to configure the metadata marshaler of the context ContextStore -type Config struct { - contextType TypeGetter - endpointTypes map[string]TypeGetter -} - -// SetEndpoint set an endpoint typing information -func (c Config) SetEndpoint(name string, getter TypeGetter) { - c.endpointTypes[name] = getter -} - -// ForeachEndpointType calls cb on every endpoint type registered with the Config -func (c Config) ForeachEndpointType(cb func(string, TypeGetter) error) error { - for n, ep := range c.endpointTypes { - if err := cb(n, ep); err != nil { - return err - } - } - return nil -} - -// NewConfig creates a config object -func NewConfig(contextType TypeGetter, endpoints ...NamedTypeGetter) Config { - res := Config{ - contextType: contextType, - endpointTypes: make(map[string]TypeGetter), - } - for _, e := range endpoints { - res.endpointTypes[e.name] = e.typeGetter - } - return res -} diff --git a/vendor/github.com/docker/cli/cli/context/store/tlsstore.go b/vendor/github.com/docker/cli/cli/context/store/tlsstore.go deleted file mode 100644 index ffbbde7c..00000000 --- a/vendor/github.com/docker/cli/cli/context/store/tlsstore.go +++ /dev/null @@ -1,96 +0,0 @@ -package store - -import ( - "os" - "path/filepath" - - "github.com/docker/docker/errdefs" - "github.com/docker/docker/pkg/ioutils" - "github.com/pkg/errors" -) - -const tlsDir = "tls" - -type tlsStore struct { - root string -} - -func (s *tlsStore) contextDir(name string) string { - return filepath.Join(s.root, string(contextdirOf(name))) -} - -func (s *tlsStore) endpointDir(name, endpointName string) string { - return filepath.Join(s.contextDir(name), endpointName) -} - -func (s *tlsStore) createOrUpdate(name, endpointName, filename string, data []byte) error { - parentOfRoot := filepath.Dir(s.root) - if err := os.MkdirAll(parentOfRoot, 0o755); err != nil { - return err - } - endpointDir := s.endpointDir(name, endpointName) - if err := os.MkdirAll(endpointDir, 0o700); err != nil { - return err - } - return ioutils.AtomicWriteFile(filepath.Join(endpointDir, filename), data, 0o600) -} - -func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error) { - data, err := os.ReadFile(filepath.Join(s.endpointDir(name, endpointName), filename)) - if err != nil { - 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 -} - -// 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 nil -} - -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(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, errors.Wrapf(err, "failed to list TLS files for context %s", name) - } - r := make(map[string]EndpointFiles) - for _, epFS := range epFSs { - if epFS.IsDir() { - fss, err := os.ReadDir(filepath.Join(contextDir, epFS.Name())) - if os.IsNotExist(err) { - continue - } - if err != nil { - return nil, errors.Wrapf(err, "failed to list TLS files for endpoint %s", epFS.Name()) - } - var files EndpointFiles - for _, fs := range fss { - if !fs.IsDir() { - files = append(files, fs.Name()) - } - } - r[epFS.Name()] = files - } - } - return r, nil -} - -// EndpointFiles is a slice of strings representing file names -type EndpointFiles []string diff --git a/vendor/github.com/docker/cli/cli/context/tlsdata.go b/vendor/github.com/docker/cli/cli/context/tlsdata.go deleted file mode 100644 index c758612a..00000000 --- a/vendor/github.com/docker/cli/cli/context/tlsdata.go +++ /dev/null @@ -1,98 +0,0 @@ -package context - -import ( - "os" - - "github.com/docker/cli/cli/context/store" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -const ( - caKey = "ca.pem" - certKey = "cert.pem" - keyKey = "key.pem" -) - -// TLSData holds ca/cert/key raw data -type TLSData struct { - CA []byte - Key []byte - Cert []byte -} - -// ToStoreTLSData converts TLSData to the store representation -func (data *TLSData) ToStoreTLSData() *store.EndpointTLSData { - if data == nil { - return nil - } - result := store.EndpointTLSData{ - Files: make(map[string][]byte), - } - if data.CA != nil { - result.Files[caKey] = data.CA - } - if data.Cert != nil { - result.Files[certKey] = data.Cert - } - if data.Key != nil { - result.Files[keyKey] = data.Key - } - return &result -} - -// LoadTLSData loads TLS data from the store -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 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 TLS data (%s) for context %q", f, contextName) - } - switch f { - case caKey: - tlsData.CA = data - case certKey: - tlsData.Cert = data - case keyKey: - tlsData.Key = data - default: - logrus.Warnf("unknown file in context %s TLS bundle: %s", contextName, f) - } - } - return &tlsData, nil - } - return nil, nil -} - -// TLSDataFromFiles reads files into a TLSData struct (or returns nil if all paths are empty) -func TLSDataFromFiles(caPath, certPath, keyPath string) (*TLSData, error) { - var ( - ca, cert, key []byte - err error - ) - if caPath != "" { - if ca, err = os.ReadFile(caPath); err != nil { - return nil, err - } - } - if certPath != "" { - if cert, err = os.ReadFile(certPath); err != nil { - return nil, err - } - } - if keyPath != "" { - if key, err = os.ReadFile(keyPath); err != nil { - return nil, err - } - } - if ca == nil && cert == nil && key == nil { - return nil, nil - } - return &TLSData{CA: ca, Cert: cert, Key: key}, nil -} diff --git a/vendor/github.com/docker/distribution/LICENSE b/vendor/github.com/docker/distribution/LICENSE new file mode 100644 index 00000000..e06d2081 --- /dev/null +++ b/vendor/github.com/docker/distribution/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/docker/distribution/registry/api/errcode/errors.go b/vendor/github.com/docker/distribution/registry/api/errcode/errors.go new file mode 100644 index 00000000..4c35b879 --- /dev/null +++ b/vendor/github.com/docker/distribution/registry/api/errcode/errors.go @@ -0,0 +1,267 @@ +package errcode + +import ( + "encoding/json" + "fmt" + "strings" +) + +// ErrorCoder is the base interface for ErrorCode and Error allowing +// users of each to just call ErrorCode to get the real ID of each +type ErrorCoder interface { + ErrorCode() ErrorCode +} + +// ErrorCode represents the error type. The errors are serialized via strings +// and the integer format may change and should *never* be exported. +type ErrorCode int + +var _ error = ErrorCode(0) + +// ErrorCode just returns itself +func (ec ErrorCode) ErrorCode() ErrorCode { + return ec +} + +// Error returns the ID/Value +func (ec ErrorCode) Error() string { + // NOTE(stevvooe): Cannot use message here since it may have unpopulated args. + return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1)) +} + +// Descriptor returns the descriptor for the error code. +func (ec ErrorCode) Descriptor() ErrorDescriptor { + d, ok := errorCodeToDescriptors[ec] + + if !ok { + return ErrorCodeUnknown.Descriptor() + } + + return d +} + +// String returns the canonical identifier for this error code. +func (ec ErrorCode) String() string { + return ec.Descriptor().Value +} + +// Message returned the human-readable error message for this error code. +func (ec ErrorCode) Message() string { + return ec.Descriptor().Message +} + +// MarshalText encodes the receiver into UTF-8-encoded text and returns the +// result. +func (ec ErrorCode) MarshalText() (text []byte, err error) { + return []byte(ec.String()), nil +} + +// UnmarshalText decodes the form generated by MarshalText. +func (ec *ErrorCode) UnmarshalText(text []byte) error { + desc, ok := idToDescriptors[string(text)] + + if !ok { + desc = ErrorCodeUnknown.Descriptor() + } + + *ec = desc.Code + + return nil +} + +// WithMessage creates a new Error struct based on the passed-in info and +// overrides the Message property. +func (ec ErrorCode) WithMessage(message string) Error { + return Error{ + Code: ec, + Message: message, + } +} + +// WithDetail creates a new Error struct based on the passed-in info and +// set the Detail property appropriately +func (ec ErrorCode) WithDetail(detail interface{}) Error { + return Error{ + Code: ec, + Message: ec.Message(), + }.WithDetail(detail) +} + +// WithArgs creates a new Error struct and sets the Args slice +func (ec ErrorCode) WithArgs(args ...interface{}) Error { + return Error{ + Code: ec, + Message: ec.Message(), + }.WithArgs(args...) +} + +// Error provides a wrapper around ErrorCode with extra Details provided. +type Error struct { + Code ErrorCode `json:"code"` + Message string `json:"message"` + Detail interface{} `json:"detail,omitempty"` + + // TODO(duglin): See if we need an "args" property so we can do the + // variable substitution right before showing the message to the user +} + +var _ error = Error{} + +// ErrorCode returns the ID/Value of this Error +func (e Error) ErrorCode() ErrorCode { + return e.Code +} + +// Error returns a human readable representation of the error. +func (e Error) Error() string { + return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message) +} + +// WithDetail will return a new Error, based on the current one, but with +// some Detail info added +func (e Error) WithDetail(detail interface{}) Error { + return Error{ + Code: e.Code, + Message: e.Message, + Detail: detail, + } +} + +// WithArgs uses the passed-in list of interface{} as the substitution +// variables in the Error's Message string, but returns a new Error +func (e Error) WithArgs(args ...interface{}) Error { + return Error{ + Code: e.Code, + Message: fmt.Sprintf(e.Code.Message(), args...), + Detail: e.Detail, + } +} + +// ErrorDescriptor provides relevant information about a given error code. +type ErrorDescriptor struct { + // Code is the error code that this descriptor describes. + Code ErrorCode + + // Value provides a unique, string key, often captilized with + // underscores, to identify the error code. This value is used as the + // keyed value when serializing api errors. + Value string + + // Message is a short, human readable decription of the error condition + // included in API responses. + Message string + + // Description provides a complete account of the errors purpose, suitable + // for use in documentation. + Description string + + // HTTPStatusCode provides the http status code that is associated with + // this error condition. + HTTPStatusCode int +} + +// ParseErrorCode returns the value by the string error code. +// `ErrorCodeUnknown` will be returned if the error is not known. +func ParseErrorCode(value string) ErrorCode { + ed, ok := idToDescriptors[value] + if ok { + return ed.Code + } + + return ErrorCodeUnknown +} + +// Errors provides the envelope for multiple errors and a few sugar methods +// for use within the application. +type Errors []error + +var _ error = Errors{} + +func (errs Errors) Error() string { + switch len(errs) { + case 0: + return "" + case 1: + return errs[0].Error() + default: + msg := "errors:\n" + for _, err := range errs { + msg += err.Error() + "\n" + } + return msg + } +} + +// Len returns the current number of errors. +func (errs Errors) Len() int { + return len(errs) +} + +// MarshalJSON converts slice of error, ErrorCode or Error into a +// slice of Error - then serializes +func (errs Errors) MarshalJSON() ([]byte, error) { + var tmpErrs struct { + Errors []Error `json:"errors,omitempty"` + } + + for _, daErr := range errs { + var err Error + + switch daErr := daErr.(type) { + case ErrorCode: + err = daErr.WithDetail(nil) + case Error: + err = daErr + default: + err = ErrorCodeUnknown.WithDetail(daErr) + + } + + // If the Error struct was setup and they forgot to set the + // Message field (meaning its "") then grab it from the ErrCode + msg := err.Message + if msg == "" { + msg = err.Code.Message() + } + + tmpErrs.Errors = append(tmpErrs.Errors, Error{ + Code: err.Code, + Message: msg, + Detail: err.Detail, + }) + } + + return json.Marshal(tmpErrs) +} + +// UnmarshalJSON deserializes []Error and then converts it into slice of +// Error or ErrorCode +func (errs *Errors) UnmarshalJSON(data []byte) error { + var tmpErrs struct { + Errors []Error + } + + if err := json.Unmarshal(data, &tmpErrs); err != nil { + return err + } + + var newErrs Errors + for _, daErr := range tmpErrs.Errors { + // If Message is empty or exactly matches the Code's message string + // then just use the Code, no need for a full Error struct + if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) { + // Error's w/o details get converted to ErrorCode + newErrs = append(newErrs, daErr.Code) + } else { + // Error's w/ details are untouched + newErrs = append(newErrs, Error{ + Code: daErr.Code, + Message: daErr.Message, + Detail: daErr.Detail, + }) + } + } + + *errs = newErrs + return nil +} diff --git a/vendor/github.com/docker/distribution/registry/api/errcode/handler.go b/vendor/github.com/docker/distribution/registry/api/errcode/handler.go new file mode 100644 index 00000000..d77e7047 --- /dev/null +++ b/vendor/github.com/docker/distribution/registry/api/errcode/handler.go @@ -0,0 +1,40 @@ +package errcode + +import ( + "encoding/json" + "net/http" +) + +// ServeJSON attempts to serve the errcode in a JSON envelope. It marshals err +// and sets the content-type header to 'application/json'. It will handle +// ErrorCoder and Errors, and if necessary will create an envelope. +func ServeJSON(w http.ResponseWriter, err error) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + var sc int + + switch errs := err.(type) { + case Errors: + if len(errs) < 1 { + break + } + + if err, ok := errs[0].(ErrorCoder); ok { + sc = err.ErrorCode().Descriptor().HTTPStatusCode + } + case ErrorCoder: + sc = errs.ErrorCode().Descriptor().HTTPStatusCode + err = Errors{err} // create an envelope. + default: + // We just have an unhandled error type, so just place in an envelope + // and move along. + err = Errors{err} + } + + if sc == 0 { + sc = http.StatusInternalServerError + } + + w.WriteHeader(sc) + + return json.NewEncoder(w).Encode(err) +} diff --git a/vendor/github.com/docker/distribution/registry/api/errcode/register.go b/vendor/github.com/docker/distribution/registry/api/errcode/register.go new file mode 100644 index 00000000..d1e8826c --- /dev/null +++ b/vendor/github.com/docker/distribution/registry/api/errcode/register.go @@ -0,0 +1,138 @@ +package errcode + +import ( + "fmt" + "net/http" + "sort" + "sync" +) + +var ( + errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{} + idToDescriptors = map[string]ErrorDescriptor{} + groupToDescriptors = map[string][]ErrorDescriptor{} +) + +var ( + // ErrorCodeUnknown is a generic error that can be used as a last + // resort if there is no situation-specific error message that can be used + ErrorCodeUnknown = Register("errcode", ErrorDescriptor{ + Value: "UNKNOWN", + Message: "unknown error", + Description: `Generic error returned when the error does not have an + API classification.`, + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeUnsupported is returned when an operation is not supported. + ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{ + Value: "UNSUPPORTED", + Message: "The operation is unsupported.", + Description: `The operation was unsupported due to a missing + implementation or invalid set of parameters.`, + HTTPStatusCode: http.StatusMethodNotAllowed, + }) + + // ErrorCodeUnauthorized is returned if a request requires + // authentication. + ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{ + Value: "UNAUTHORIZED", + Message: "authentication required", + Description: `The access controller was unable to authenticate + the client. Often this will be accompanied by a + Www-Authenticate HTTP response header indicating how to + authenticate.`, + HTTPStatusCode: http.StatusUnauthorized, + }) + + // ErrorCodeDenied is returned if a client does not have sufficient + // permission to perform an action. + ErrorCodeDenied = Register("errcode", ErrorDescriptor{ + Value: "DENIED", + Message: "requested access to the resource is denied", + Description: `The access controller denied access for the + operation on a resource.`, + HTTPStatusCode: http.StatusForbidden, + }) + + // ErrorCodeUnavailable provides a common error to report unavailability + // of a service or endpoint. + ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{ + Value: "UNAVAILABLE", + Message: "service unavailable", + Description: "Returned when a service is not available", + HTTPStatusCode: http.StatusServiceUnavailable, + }) + + // ErrorCodeTooManyRequests is returned if a client attempts too many + // times to contact a service endpoint. + ErrorCodeTooManyRequests = Register("errcode", ErrorDescriptor{ + Value: "TOOMANYREQUESTS", + Message: "too many requests", + Description: `Returned when a client attempts to contact a + service too many times`, + HTTPStatusCode: http.StatusTooManyRequests, + }) +) + +var nextCode = 1000 +var registerLock sync.Mutex + +// Register will make the passed-in error known to the environment and +// return a new ErrorCode +func Register(group string, descriptor ErrorDescriptor) ErrorCode { + registerLock.Lock() + defer registerLock.Unlock() + + descriptor.Code = ErrorCode(nextCode) + + if _, ok := idToDescriptors[descriptor.Value]; ok { + panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value)) + } + if _, ok := errorCodeToDescriptors[descriptor.Code]; ok { + panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code)) + } + + groupToDescriptors[group] = append(groupToDescriptors[group], descriptor) + errorCodeToDescriptors[descriptor.Code] = descriptor + idToDescriptors[descriptor.Value] = descriptor + + nextCode++ + return descriptor.Code +} + +type byValue []ErrorDescriptor + +func (a byValue) Len() int { return len(a) } +func (a byValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value } + +// GetGroupNames returns the list of Error group names that are registered +func GetGroupNames() []string { + keys := []string{} + + for k := range groupToDescriptors { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// GetErrorCodeGroup returns the named group of error descriptors +func GetErrorCodeGroup(name string) []ErrorDescriptor { + desc := groupToDescriptors[name] + sort.Sort(byValue(desc)) + return desc +} + +// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are +// registered, irrespective of what group they're in +func GetErrorAllDescriptors() []ErrorDescriptor { + result := []ErrorDescriptor{} + + for _, group := range GetGroupNames() { + result = append(result, GetErrorCodeGroup(group)...) + } + sort.Sort(byValue(result)) + return result +} diff --git a/vendor/github.com/docker/distribution/registry/api/v2/descriptors.go b/vendor/github.com/docker/distribution/registry/api/v2/descriptors.go new file mode 100644 index 00000000..7fceefbc --- /dev/null +++ b/vendor/github.com/docker/distribution/registry/api/v2/descriptors.go @@ -0,0 +1,1613 @@ +package v2 + +import ( + "net/http" + "regexp" + + "github.com/distribution/reference" + "github.com/docker/distribution/registry/api/errcode" + "github.com/opencontainers/go-digest" +) + +var ( + nameParameterDescriptor = ParameterDescriptor{ + Name: "name", + Type: "string", + Format: reference.NameRegexp.String(), + Required: true, + Description: `Name of the target repository.`, + } + + referenceParameterDescriptor = ParameterDescriptor{ + Name: "reference", + Type: "string", + Format: reference.TagRegexp.String(), + Required: true, + Description: `Tag or digest of the target manifest.`, + } + + uuidParameterDescriptor = ParameterDescriptor{ + Name: "uuid", + Type: "opaque", + Required: true, + Description: "A uuid identifying the upload. This field can accept characters that match `[a-zA-Z0-9-_.=]+`.", + } + + digestPathParameter = ParameterDescriptor{ + Name: "digest", + Type: "path", + Required: true, + Format: digest.DigestRegexp.String(), + Description: `Digest of desired blob.`, + } + + hostHeader = ParameterDescriptor{ + Name: "Host", + Type: "string", + Description: "Standard HTTP Host Header. Should be set to the registry host.", + Format: "", + Examples: []string{"registry-1.docker.io"}, + } + + authHeader = ParameterDescriptor{ + Name: "Authorization", + Type: "string", + Description: "An RFC7235 compliant authorization header.", + Format: " ", + Examples: []string{"Bearer dGhpcyBpcyBhIGZha2UgYmVhcmVyIHRva2VuIQ=="}, + } + + authChallengeHeader = ParameterDescriptor{ + Name: "WWW-Authenticate", + Type: "string", + Description: "An RFC7235 compliant authentication challenge header.", + Format: ` realm="", ..."`, + Examples: []string{ + `Bearer realm="https://auth.docker.com/", service="registry.docker.com", scopes="repository:library/ubuntu:pull"`, + }, + } + + contentLengthZeroHeader = ParameterDescriptor{ + Name: "Content-Length", + Description: "The `Content-Length` header must be zero and the body must be empty.", + Type: "integer", + Format: "0", + } + + dockerUploadUUIDHeader = ParameterDescriptor{ + Name: "Docker-Upload-UUID", + Description: "Identifies the docker upload uuid for the current request.", + Type: "uuid", + Format: "", + } + + digestHeader = ParameterDescriptor{ + Name: "Docker-Content-Digest", + Description: "Digest of the targeted content for the request.", + Type: "digest", + Format: "", + } + + linkHeader = ParameterDescriptor{ + Name: "Link", + Type: "link", + Description: "RFC5988 compliant rel='next' with URL to next result set, if available", + Format: `<?n=&last=>; rel="next"`, + } + + paginationParameters = []ParameterDescriptor{ + { + Name: "n", + Type: "integer", + Description: "Limit the number of entries in each response. It not present, all entries will be returned.", + Format: "", + Required: false, + }, + { + Name: "last", + Type: "string", + Description: "Result set will include values lexically after last.", + Format: "", + Required: false, + }, + } + + unauthorizedResponseDescriptor = ResponseDescriptor{ + Name: "Authentication Required", + StatusCode: http.StatusUnauthorized, + Description: "The client is not authenticated.", + Headers: []ParameterDescriptor{ + authChallengeHeader, + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeUnauthorized, + }, + } + + invalidPaginationResponseDescriptor = ResponseDescriptor{ + Name: "Invalid pagination number", + Description: "The received parameter n was invalid in some way, as described by the error code. The client should resolve the issue and retry the request.", + StatusCode: http.StatusBadRequest, + Body: BodyDescriptor{ + ContentType: "application/json", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodePaginationNumberInvalid, + }, + } + + repositoryNotFoundResponseDescriptor = ResponseDescriptor{ + Name: "No Such Repository Error", + StatusCode: http.StatusNotFound, + Description: "The repository is not known to the registry.", + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameUnknown, + }, + } + + deniedResponseDescriptor = ResponseDescriptor{ + Name: "Access Denied", + StatusCode: http.StatusForbidden, + Description: "The client does not have required access to the repository.", + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeDenied, + }, + } + + tooManyRequestsDescriptor = ResponseDescriptor{ + Name: "Too Many Requests", + StatusCode: http.StatusTooManyRequests, + Description: "The client made too many requests within a time interval.", + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeTooManyRequests, + }, + } +) + +const ( + manifestBody = `{ + "name": , + "tag": , + "fsLayers": [ + { + "blobSum": "" + }, + ... + ] + ], + "history": , + "signature": +}` + + errorsBody = `{ + "errors:" [ + { + "code": , + "message": "", + "detail": ... + }, + ... + ] +}` +) + +// APIDescriptor exports descriptions of the layout of the v2 registry API. +var APIDescriptor = struct { + // RouteDescriptors provides a list of the routes available in the API. + RouteDescriptors []RouteDescriptor +}{ + RouteDescriptors: routeDescriptors, +} + +// RouteDescriptor describes a route specified by name. +type RouteDescriptor struct { + // Name is the name of the route, as specified in RouteNameXXX exports. + // These names a should be considered a unique reference for a route. If + // the route is registered with gorilla, this is the name that will be + // used. + Name string + + // Path is a gorilla/mux-compatible regexp that can be used to match the + // route. For any incoming method and path, only one route descriptor + // should match. + Path string + + // Entity should be a short, human-readalbe description of the object + // targeted by the endpoint. + Entity string + + // Description should provide an accurate overview of the functionality + // provided by the route. + Description string + + // Methods should describe the various HTTP methods that may be used on + // this route, including request and response formats. + Methods []MethodDescriptor +} + +// MethodDescriptor provides a description of the requests that may be +// conducted with the target method. +type MethodDescriptor struct { + + // Method is an HTTP method, such as GET, PUT or POST. + Method string + + // Description should provide an overview of the functionality provided by + // the covered method, suitable for use in documentation. Use of markdown + // here is encouraged. + Description string + + // Requests is a slice of request descriptors enumerating how this + // endpoint may be used. + Requests []RequestDescriptor +} + +// RequestDescriptor covers a particular set of headers and parameters that +// can be carried out with the parent method. Its most helpful to have one +// RequestDescriptor per API use case. +type RequestDescriptor struct { + // Name provides a short identifier for the request, usable as a title or + // to provide quick context for the particular request. + Name string + + // Description should cover the requests purpose, covering any details for + // this particular use case. + Description string + + // Headers describes headers that must be used with the HTTP request. + Headers []ParameterDescriptor + + // PathParameters enumerate the parameterized path components for the + // given request, as defined in the route's regular expression. + PathParameters []ParameterDescriptor + + // QueryParameters provides a list of query parameters for the given + // request. + QueryParameters []ParameterDescriptor + + // Body describes the format of the request body. + Body BodyDescriptor + + // Successes enumerates the possible responses that are considered to be + // the result of a successful request. + Successes []ResponseDescriptor + + // Failures covers the possible failures from this particular request. + Failures []ResponseDescriptor +} + +// ResponseDescriptor describes the components of an API response. +type ResponseDescriptor struct { + // Name provides a short identifier for the response, usable as a title or + // to provide quick context for the particular response. + Name string + + // Description should provide a brief overview of the role of the + // response. + Description string + + // StatusCode specifies the status received by this particular response. + StatusCode int + + // Headers covers any headers that may be returned from the response. + Headers []ParameterDescriptor + + // Fields describes any fields that may be present in the response. + Fields []ParameterDescriptor + + // ErrorCodes enumerates the error codes that may be returned along with + // the response. + ErrorCodes []errcode.ErrorCode + + // Body describes the body of the response, if any. + Body BodyDescriptor +} + +// BodyDescriptor describes a request body and its expected content type. For +// the most part, it should be example json or some placeholder for body +// data in documentation. +type BodyDescriptor struct { + ContentType string + Format string +} + +// ParameterDescriptor describes the format of a request parameter, which may +// be a header, path parameter or query parameter. +type ParameterDescriptor struct { + // Name is the name of the parameter, either of the path component or + // query parameter. + Name string + + // Type specifies the type of the parameter, such as string, integer, etc. + Type string + + // Description provides a human-readable description of the parameter. + Description string + + // Required means the field is required when set. + Required bool + + // Format is a specifying the string format accepted by this parameter. + Format string + + // Regexp is a compiled regular expression that can be used to validate + // the contents of the parameter. + Regexp *regexp.Regexp + + // Examples provides multiple examples for the values that might be valid + // for this parameter. + Examples []string +} + +var routeDescriptors = []RouteDescriptor{ + { + Name: RouteNameBase, + Path: "/v2/", + Entity: "Base", + Description: `Base V2 API route. Typically, this can be used for lightweight version checks and to validate registry authentication.`, + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Check that the endpoint implements Docker Registry API V2.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + Successes: []ResponseDescriptor{ + { + Description: "The API implements V2 protocol and is accessible.", + StatusCode: http.StatusOK, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "The registry does not implement the V2 API.", + StatusCode: http.StatusNotFound, + }, + unauthorizedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + }, + }, + { + Name: RouteNameTags, + Path: "/v2/{name:" + reference.NameRegexp.String() + "}/tags/list", + Entity: "Tags", + Description: "Retrieve information about tags.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Fetch the tags under the repository identified by `name`.", + Requests: []RequestDescriptor{ + { + Name: "Tags", + Description: "Return all tags for the repository", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusOK, + Description: "A list of tags for the named repository.", + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "name": , + "tags": [ + , + ... + ] +}`, + }, + }, + }, + Failures: []ResponseDescriptor{ + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + { + Name: "Tags Paginated", + Description: "Return a portion of the tags for the specified repository.", + PathParameters: []ParameterDescriptor{nameParameterDescriptor}, + QueryParameters: paginationParameters, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusOK, + Description: "A list of tags for the named repository.", + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + linkHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "name": , + "tags": [ + , + ... + ], +}`, + }, + }, + }, + Failures: []ResponseDescriptor{ + invalidPaginationResponseDescriptor, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + }, + }, + { + Name: RouteNameManifest, + Path: "/v2/{name:" + reference.NameRegexp.String() + "}/manifests/{reference:" + reference.TagRegexp.String() + "|" + digest.DigestRegexp.String() + "}", + Entity: "Manifest", + Description: "Create, update, delete and retrieve manifests.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Fetch the manifest identified by `name` and `reference` where `reference` can be a tag or digest. A `HEAD` request can also be issued to this endpoint to obtain resource information without receiving all data.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + referenceParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + Description: "The manifest identified by `name` and `reference`. The contents can be used to identify and resolve resources required to run the specified image.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + digestHeader, + }, + Body: BodyDescriptor{ + ContentType: "", + Format: manifestBody, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "The name or reference was invalid.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeTagInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + { + Method: "PUT", + Description: "Put the manifest identified by `name` and `reference` where `reference` can be a tag or digest.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + referenceParameterDescriptor, + }, + Body: BodyDescriptor{ + ContentType: "", + Format: manifestBody, + }, + Successes: []ResponseDescriptor{ + { + Description: "The manifest has been accepted by the registry and is stored under the specified `name` and `tag`.", + StatusCode: http.StatusCreated, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Description: "The canonical location url of the uploaded manifest.", + Format: "", + }, + contentLengthZeroHeader, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Manifest", + Description: "The received manifest was invalid in some way, as described by the error codes. The client should resolve the issue and retry the request.", + StatusCode: http.StatusBadRequest, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeTagInvalid, + ErrorCodeManifestInvalid, + ErrorCodeManifestUnverified, + ErrorCodeBlobUnknown, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + { + Name: "Missing Layer(s)", + Description: "One or more layers may be missing during a manifest upload. If so, the missing layers will be enumerated in the error response.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeBlobUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "errors:" [{ + "code": "BLOB_UNKNOWN", + "message": "blob unknown to registry", + "detail": { + "digest": "" + } + }, + ... + ] +}`, + }, + }, + { + Name: "Not allowed", + Description: "Manifest put is not allowed because the registry is configured as a pull-through cache or for some other reason", + StatusCode: http.StatusMethodNotAllowed, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeUnsupported, + }, + }, + }, + }, + }, + }, + { + Method: "DELETE", + Description: "Delete the manifest identified by `name` and `reference`. Note that a manifest can _only_ be deleted by `digest`.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + referenceParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusAccepted, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Reference", + Description: "The specified `name` or `reference` were invalid and the delete was unable to proceed.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeTagInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + { + Name: "Unknown Manifest", + Description: "The specified `name` or `reference` are unknown to the registry and the delete was unable to proceed. Clients can assume the manifest was already deleted if this response is returned.", + StatusCode: http.StatusNotFound, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeManifestUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Name: "Not allowed", + Description: "Manifest delete is not allowed because the registry is configured as a pull-through cache or `delete` has been disabled.", + StatusCode: http.StatusMethodNotAllowed, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeUnsupported, + }, + }, + }, + }, + }, + }, + }, + }, + + { + Name: RouteNameBlob, + Path: "/v2/{name:" + reference.NameRegexp.String() + "}/blobs/{digest:" + digest.DigestRegexp.String() + "}", + Entity: "Blob", + Description: "Operations on blobs identified by `name` and `digest`. Used to fetch or delete layers by digest.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Retrieve the blob from the registry identified by `digest`. A `HEAD` request can also be issued to this endpoint to obtain resource information without receiving all data.", + Requests: []RequestDescriptor{ + { + Name: "Fetch Blob", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + digestPathParameter, + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob identified by `digest` is available. The blob content will be present in the body of the request.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "The length of the requested blob content.", + Format: "", + }, + digestHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + }, + { + Description: "The blob identified by `digest` is available at the provided location.", + StatusCode: http.StatusTemporaryRedirect, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Description: "The location where the layer should be accessible.", + Format: "", + }, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was a problem with the request that needs to be addressed by the client, such as an invalid `name` or `tag`.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeDigestInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The blob, identified by `name` and `digest`, is unknown to the registry.", + StatusCode: http.StatusNotFound, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeBlobUnknown, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + { + Name: "Fetch Blob Part", + Description: "This endpoint may also support RFC7233 compliant range requests. Support can be detected by issuing a HEAD request. If the header `Accept-Range: bytes` is returned, range requests can be used to fetch partial content.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Range", + Type: "string", + Description: "HTTP Range header specifying blob chunk.", + Format: "bytes=-", + }, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + digestPathParameter, + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob identified by `digest` is available. The specified chunk of blob content will be present in the body of the request.", + StatusCode: http.StatusPartialContent, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "The length of the requested blob chunk.", + Format: "", + }, + { + Name: "Content-Range", + Type: "byte range", + Description: "Content range of blob chunk.", + Format: "bytes -/", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was a problem with the request that needs to be addressed by the client, such as an invalid `name` or `tag`.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeDigestInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + StatusCode: http.StatusNotFound, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeBlobUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The range specification cannot be satisfied for the requested content. This can happen when the range is not formatted correctly or if the range is outside of the valid size of the content.", + StatusCode: http.StatusRequestedRangeNotSatisfiable, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + { + Method: "DELETE", + Description: "Delete the blob identified by `name` and `digest`", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + digestPathParameter, + }, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusAccepted, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "0", + Format: "0", + }, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Digest", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + }, + }, + { + Description: "The blob, identified by `name` and `digest`, is unknown to the registry.", + StatusCode: http.StatusNotFound, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeBlobUnknown, + }, + }, + { + Description: "Blob delete is not allowed because the registry is configured as a pull-through cache or `delete` has been disabled", + StatusCode: http.StatusMethodNotAllowed, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeUnsupported, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + + // TODO(stevvooe): We may want to add a PUT request here to + // kickoff an upload of a blob, integrated with the blob upload + // API. + }, + }, + + { + Name: RouteNameBlobUpload, + Path: "/v2/{name:" + reference.NameRegexp.String() + "}/blobs/uploads/", + Entity: "Initiate Blob Upload", + Description: "Initiate a blob upload. This endpoint can be used to create resumable uploads or monolithic uploads.", + Methods: []MethodDescriptor{ + { + Method: "POST", + Description: "Initiate a resumable blob upload. If successful, an upload location will be provided to complete the upload. Optionally, if the `digest` parameter is present, the request body will be used to complete the upload in a single request.", + Requests: []RequestDescriptor{ + { + Name: "Initiate Monolithic Blob Upload", + Description: "Upload a blob identified by the `digest` parameter in single request. This upload will not be resumable unless a recoverable error is returned.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Content-Length", + Type: "integer", + Format: "", + }, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + QueryParameters: []ParameterDescriptor{ + { + Name: "digest", + Type: "query", + Format: "", + Regexp: digest.DigestRegexp, + Description: `Digest of uploaded blob. If present, the upload will be completed, in a single request, with contents of the request body as the resulting blob.`, + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octect-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob has been created in the registry and is available at the provided location.", + StatusCode: http.StatusCreated, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Digest", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + }, + }, + { + Name: "Not allowed", + Description: "Blob upload is not allowed because the registry is configured as a pull-through cache or for some other reason", + StatusCode: http.StatusMethodNotAllowed, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeUnsupported, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + { + Name: "Initiate Resumable Blob Upload", + Description: "Initiate a resumable blob upload with an empty request body.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + contentLengthZeroHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + Description: "The upload has been created. The `Location` header must be used to complete the upload. The response should be identical to a `GET` request on the contents of the returned `Location` header.", + StatusCode: http.StatusAccepted, + Headers: []ParameterDescriptor{ + contentLengthZeroHeader, + { + Name: "Location", + Type: "url", + Format: "/v2//blobs/uploads/", + Description: "The location of the created upload. Clients should use the contents verbatim to complete the upload, adding parameters where required.", + }, + { + Name: "Range", + Format: "0-0", + Description: "Range header indicating the progress of the upload. When starting an upload, it will return an empty range, since no content has been received.", + }, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Digest", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + { + Name: "Mount Blob", + Description: "Mount a blob identified by the `mount` parameter from another repository.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + contentLengthZeroHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + QueryParameters: []ParameterDescriptor{ + { + Name: "mount", + Type: "query", + Format: "", + Regexp: digest.DigestRegexp, + Description: `Digest of blob to mount from the source repository.`, + }, + { + Name: "from", + Type: "query", + Format: "", + Regexp: reference.NameRegexp, + Description: `Name of the source repository.`, + }, + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob has been mounted in the repository and is available at the provided location.", + StatusCode: http.StatusCreated, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Digest", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + }, + }, + { + Name: "Not allowed", + Description: "Blob mount is not allowed because the registry is configured as a pull-through cache or for some other reason", + StatusCode: http.StatusMethodNotAllowed, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeUnsupported, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + }, + }, + + { + Name: RouteNameBlobUploadChunk, + Path: "/v2/{name:" + reference.NameRegexp.String() + "}/blobs/uploads/{uuid:[a-zA-Z0-9-_.=]+}", + Entity: "Blob Upload", + Description: "Interact with blob uploads. Clients should never assemble URLs for this endpoint and should only take it through the `Location` header on related API requests. The `Location` header and its parameters should be preserved by clients, using the latest value returned via upload related API calls.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Retrieve status of upload identified by `uuid`. The primary purpose of this endpoint is to resolve the current status of a resumable upload.", + Requests: []RequestDescriptor{ + { + Description: "Retrieve the progress of the current upload, as reported by the `Range` header.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + Name: "Upload Progress", + Description: "The upload is known and in progress. The last received offset is available in the `Range` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Range", + Type: "header", + Format: "0-", + Description: "Range indicating the current progress of the upload.", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + { + Method: "PATCH", + Description: "Upload a chunk of data for the specified upload.", + Requests: []RequestDescriptor{ + { + Name: "Stream upload", + Description: "Upload a stream of data to upload without completing the upload.", + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Name: "Data Accepted", + Description: "The stream of data has been accepted and the current progress is available in the range header. The updated upload location is available in the `Location` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "/v2//blobs/uploads/", + Description: "The location of the upload. Clients should assume this changes after each request. Clients should use the contents verbatim to complete the upload, adding parameters where required.", + }, + { + Name: "Range", + Type: "header", + Format: "0-", + Description: "Range indicating the current progress of the upload.", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + { + Name: "Chunked upload", + Description: "Upload a chunk of data to specified upload without completing the upload. The data will be uploaded to the specified Content Range.", + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Content-Range", + Type: "header", + Format: "-", + Required: true, + Description: "Range of bytes identifying the desired block of content represented by the body. Start must the end offset retrieved via status check plus one. Note that this is a non-standard use of the `Content-Range` header.", + }, + { + Name: "Content-Length", + Type: "integer", + Format: "", + Description: "Length of the chunk being uploaded, corresponding the length of the request body.", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Name: "Chunk Accepted", + Description: "The chunk of data has been accepted and the current progress is available in the range header. The updated upload location is available in the `Location` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "/v2//blobs/uploads/", + Description: "The location of the upload. Clients should assume this changes after each request. Clients should use the contents verbatim to complete the upload, adding parameters where required.", + }, + { + Name: "Range", + Type: "header", + Format: "0-", + Description: "Range indicating the current progress of the upload.", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The `Content-Range` specification cannot be accepted, either because it does not overlap with the current progress or it is invalid.", + StatusCode: http.StatusRequestedRangeNotSatisfiable, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + { + Method: "PUT", + Description: "Complete the upload specified by `uuid`, optionally appending the body as the final chunk.", + Requests: []RequestDescriptor{ + { + Description: "Complete the upload, providing all the data in the body, if necessary. A request without a body will just complete the upload with previously uploaded content.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Content-Length", + Type: "integer", + Format: "", + Description: "Length of the data being uploaded, corresponding to the length of the request body. May be zero if no data is provided.", + }, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + QueryParameters: []ParameterDescriptor{ + { + Name: "digest", + Type: "string", + Format: "", + Regexp: digest.DigestRegexp, + Required: true, + Description: `Digest of uploaded blob.`, + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Name: "Upload Complete", + Description: "The upload has been completed and accepted by the registry. The canonical location will be available in the `Location` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "", + Description: "The canonical location of the blob for retrieval", + }, + { + Name: "Content-Range", + Type: "header", + Format: "-", + Description: "Range of bytes identifying the desired block of content represented by the body. Start must match the end of offset retrieved via status check. Note that this is a non-standard use of the `Content-Range` header.", + }, + contentLengthZeroHeader, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + errcode.ErrorCodeUnsupported, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + { + Method: "DELETE", + Description: "Cancel outstanding upload processes, releasing associated resources. If this is not called, the unfinished uploads will eventually timeout.", + Requests: []RequestDescriptor{ + { + Description: "Cancel the upload specified by `uuid`.", + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + contentLengthZeroHeader, + }, + Successes: []ResponseDescriptor{ + { + Name: "Upload Deleted", + Description: "The upload has been successfully deleted.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + contentLengthZeroHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "An error was encountered processing the delete. The client may ignore this error.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The upload is unknown to the registry. The client may ignore this error and assume the upload has been deleted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + }, + }, + { + Name: RouteNameCatalog, + Path: "/v2/_catalog", + Entity: "Catalog", + Description: "List a set of available repositories in the local registry cluster. Does not provide any indication of what may be available upstream. Applications can only determine if a repository is available but not if it is not available.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Retrieve a sorted, json list of repositories available in the registry.", + Requests: []RequestDescriptor{ + { + Name: "Catalog Fetch", + Description: "Request an unabridged list of repositories available. The implementation may impose a maximum limit and return a partial set with pagination links.", + Successes: []ResponseDescriptor{ + { + Description: "Returns the unabridged list of repositories as a json response.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "repositories": [ + , + ... + ] +}`, + }, + }, + }, + }, + { + Name: "Catalog Fetch Paginated", + Description: "Return the specified portion of repositories.", + QueryParameters: paginationParameters, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusOK, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "repositories": [ + , + ... + ] + "next": "?last=&n=" +}`, + }, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + linkHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + invalidPaginationResponseDescriptor, + }, + }, + }, + }, + }, + }, +} + +var routeDescriptorsMap map[string]RouteDescriptor + +func init() { + routeDescriptorsMap = make(map[string]RouteDescriptor, len(routeDescriptors)) + + for _, descriptor := range routeDescriptors { + routeDescriptorsMap[descriptor.Name] = descriptor + } +} diff --git a/vendor/github.com/docker/distribution/registry/api/v2/doc.go b/vendor/github.com/docker/distribution/registry/api/v2/doc.go new file mode 100644 index 00000000..cde01195 --- /dev/null +++ b/vendor/github.com/docker/distribution/registry/api/v2/doc.go @@ -0,0 +1,9 @@ +// Package v2 describes routes, urls and the error codes used in the Docker +// Registry JSON HTTP API V2. In addition to declarations, descriptors are +// provided for routes and error codes that can be used for implementation and +// automatically generating documentation. +// +// Definitions here are considered to be locked down for the V2 registry api. +// Any changes must be considered carefully and should not proceed without a +// change proposal in docker core. +package v2 diff --git a/vendor/github.com/docker/distribution/registry/api/v2/errors.go b/vendor/github.com/docker/distribution/registry/api/v2/errors.go new file mode 100644 index 00000000..87e9f3c1 --- /dev/null +++ b/vendor/github.com/docker/distribution/registry/api/v2/errors.go @@ -0,0 +1,145 @@ +package v2 + +import ( + "net/http" + + "github.com/docker/distribution/registry/api/errcode" +) + +const errGroup = "registry.api.v2" + +var ( + // ErrorCodeDigestInvalid is returned when uploading a blob if the + // provided digest does not match the blob contents. + ErrorCodeDigestInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "DIGEST_INVALID", + Message: "provided digest did not match uploaded content", + Description: `When a blob is uploaded, the registry will check that + the content matches the digest provided by the client. The error may + include a detail structure with the key "digest", including the + invalid digest string. This error may also be returned when a manifest + includes an invalid layer digest.`, + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorCodeSizeInvalid is returned when uploading a blob if the provided + ErrorCodeSizeInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "SIZE_INVALID", + Message: "provided length did not match content length", + Description: `When a layer is uploaded, the provided size will be + checked against the uploaded content. If they do not match, this error + will be returned.`, + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorCodeNameInvalid is returned when the name in the manifest does not + // match the provided name. + ErrorCodeNameInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NAME_INVALID", + Message: "invalid repository name", + Description: `Invalid repository name encountered either during + manifest validation or any API operation.`, + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorCodeTagInvalid is returned when the tag in the manifest does not + // match the provided tag. + ErrorCodeTagInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "TAG_INVALID", + Message: "manifest tag did not match URI", + Description: `During a manifest upload, if the tag in the manifest + does not match the uri tag, this error will be returned.`, + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorCodeNameUnknown when the repository name is not known. + ErrorCodeNameUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NAME_UNKNOWN", + Message: "repository name not known to registry", + Description: `This is returned if the name used during an operation is + unknown to the registry.`, + HTTPStatusCode: http.StatusNotFound, + }) + + // ErrorCodeManifestUnknown returned when image manifest is unknown. + ErrorCodeManifestUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MANIFEST_UNKNOWN", + Message: "manifest unknown", + Description: `This error is returned when the manifest, identified by + name and tag is unknown to the repository.`, + HTTPStatusCode: http.StatusNotFound, + }) + + // ErrorCodeManifestInvalid returned when an image manifest is invalid, + // typically during a PUT operation. This error encompasses all errors + // encountered during manifest validation that aren't signature errors. + ErrorCodeManifestInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MANIFEST_INVALID", + Message: "manifest invalid", + Description: `During upload, manifests undergo several checks ensuring + validity. If those checks fail, this error may be returned, unless a + more specific error is included. The detail will contain information + the failed validation.`, + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorCodeManifestUnverified is returned when the manifest fails + // signature verification. + ErrorCodeManifestUnverified = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MANIFEST_UNVERIFIED", + Message: "manifest failed signature verification", + Description: `During manifest upload, if the manifest fails signature + verification, this error will be returned.`, + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorCodeManifestBlobUnknown is returned when a manifest blob is + // unknown to the registry. + ErrorCodeManifestBlobUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MANIFEST_BLOB_UNKNOWN", + Message: "blob unknown to registry", + Description: `This error may be returned when a manifest blob is + unknown to the registry.`, + HTTPStatusCode: http.StatusBadRequest, + }) + + // ErrorCodeBlobUnknown is returned when a blob is unknown to the + // registry. This can happen when the manifest references a nonexistent + // layer or the result is not found by a blob fetch. + ErrorCodeBlobUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "BLOB_UNKNOWN", + Message: "blob unknown to registry", + Description: `This error may be returned when a blob is unknown to the + registry in a specified repository. This can be returned with a + standard get or if a manifest references an unknown layer during + upload.`, + HTTPStatusCode: http.StatusNotFound, + }) + + // ErrorCodeBlobUploadUnknown is returned when an upload is unknown. + ErrorCodeBlobUploadUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "BLOB_UPLOAD_UNKNOWN", + Message: "blob upload unknown to registry", + Description: `If a blob upload has been cancelled or was never + started, this error code may be returned.`, + HTTPStatusCode: http.StatusNotFound, + }) + + // ErrorCodeBlobUploadInvalid is returned when an upload is invalid. + ErrorCodeBlobUploadInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "BLOB_UPLOAD_INVALID", + Message: "blob upload invalid", + Description: `The blob upload encountered an error and can no + longer proceed.`, + HTTPStatusCode: http.StatusNotFound, + }) + + ErrorCodePaginationNumberInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "PAGINATION_NUMBER_INVALID", + Message: "invalid number of results requested", + Description: `Returned when the "n" parameter (number of results + to return) is not an integer, "n" is negative or "n" is bigger than + the maximum allowed.`, + HTTPStatusCode: http.StatusBadRequest, + }) +) diff --git a/vendor/github.com/docker/distribution/registry/api/v2/headerparser.go b/vendor/github.com/docker/distribution/registry/api/v2/headerparser.go new file mode 100644 index 00000000..9bc41a3a --- /dev/null +++ b/vendor/github.com/docker/distribution/registry/api/v2/headerparser.go @@ -0,0 +1,161 @@ +package v2 + +import ( + "fmt" + "regexp" + "strings" + "unicode" +) + +var ( + // according to rfc7230 + reToken = regexp.MustCompile(`^[^"(),/:;<=>?@[\]{}[:space:][:cntrl:]]+`) + reQuotedValue = regexp.MustCompile(`^[^\\"]+`) + reEscapedCharacter = regexp.MustCompile(`^[[:blank:][:graph:]]`) +) + +// parseForwardedHeader is a benevolent parser of Forwarded header defined in rfc7239. The header contains +// a comma-separated list of forwarding key-value pairs. Each list element is set by single proxy. The +// function parses only the first element of the list, which is set by the very first proxy. It returns a map +// of corresponding key-value pairs and an unparsed slice of the input string. +// +// Examples of Forwarded header values: +// +// 1. Forwarded: For=192.0.2.43; Proto=https,For="[2001:db8:cafe::17]",For=unknown +// 2. Forwarded: for="192.0.2.43:443"; host="registry.example.org", for="10.10.05.40:80" +// +// The first will be parsed into {"for": "192.0.2.43", "proto": "https"} while the second into +// {"for": "192.0.2.43:443", "host": "registry.example.org"}. +func parseForwardedHeader(forwarded string) (map[string]string, string, error) { + // Following are states of forwarded header parser. Any state could transition to a failure. + const ( + // terminating state; can transition to Parameter + stateElement = iota + // terminating state; can transition to KeyValueDelimiter + stateParameter + // can transition to Value + stateKeyValueDelimiter + // can transition to one of { QuotedValue, PairEnd } + stateValue + // can transition to one of { EscapedCharacter, PairEnd } + stateQuotedValue + // can transition to one of { QuotedValue } + stateEscapedCharacter + // terminating state; can transition to one of { Parameter, Element } + statePairEnd + ) + + var ( + parameter string + value string + parse = forwarded[:] + res = map[string]string{} + state = stateElement + ) + +Loop: + for { + // skip spaces unless in quoted value + if state != stateQuotedValue && state != stateEscapedCharacter { + parse = strings.TrimLeftFunc(parse, unicode.IsSpace) + } + + if len(parse) == 0 { + if state != stateElement && state != statePairEnd && state != stateParameter { + return nil, parse, fmt.Errorf("unexpected end of input") + } + // terminating + break + } + + switch state { + // terminate at list element delimiter + case stateElement: + if parse[0] == ',' { + parse = parse[1:] + break Loop + } + state = stateParameter + + // parse parameter (the key of key-value pair) + case stateParameter: + match := reToken.FindString(parse) + if len(match) == 0 { + return nil, parse, fmt.Errorf("failed to parse token at position %d", len(forwarded)-len(parse)) + } + parameter = strings.ToLower(match) + parse = parse[len(match):] + state = stateKeyValueDelimiter + + // parse '=' + case stateKeyValueDelimiter: + if parse[0] != '=' { + return nil, parse, fmt.Errorf("expected '=', not '%c' at position %d", parse[0], len(forwarded)-len(parse)) + } + parse = parse[1:] + state = stateValue + + // parse value or quoted value + case stateValue: + if parse[0] == '"' { + parse = parse[1:] + state = stateQuotedValue + } else { + value = reToken.FindString(parse) + if len(value) == 0 { + return nil, parse, fmt.Errorf("failed to parse value at position %d", len(forwarded)-len(parse)) + } + if _, exists := res[parameter]; exists { + return nil, parse, fmt.Errorf("duplicate parameter %q at position %d", parameter, len(forwarded)-len(parse)) + } + res[parameter] = value + parse = parse[len(value):] + value = "" + state = statePairEnd + } + + // parse a part of quoted value until the first backslash + case stateQuotedValue: + match := reQuotedValue.FindString(parse) + value += match + parse = parse[len(match):] + switch { + case len(parse) == 0: + return nil, parse, fmt.Errorf("unterminated quoted string") + case parse[0] == '"': + res[parameter] = value + value = "" + parse = parse[1:] + state = statePairEnd + case parse[0] == '\\': + parse = parse[1:] + state = stateEscapedCharacter + } + + // parse escaped character in a quoted string, ignore the backslash + // transition back to QuotedValue state + case stateEscapedCharacter: + c := reEscapedCharacter.FindString(parse) + if len(c) == 0 { + return nil, parse, fmt.Errorf("invalid escape sequence at position %d", len(forwarded)-len(parse)-1) + } + value += c + parse = parse[1:] + state = stateQuotedValue + + // expect either a new key-value pair, new list or end of input + case statePairEnd: + switch parse[0] { + case ';': + parse = parse[1:] + state = stateParameter + case ',': + state = stateElement + default: + return nil, parse, fmt.Errorf("expected ',' or ';', not %c at position %d", parse[0], len(forwarded)-len(parse)) + } + } + } + + return res, parse, nil +} diff --git a/vendor/github.com/docker/distribution/registry/api/v2/routes.go b/vendor/github.com/docker/distribution/registry/api/v2/routes.go new file mode 100644 index 00000000..9612ac2e --- /dev/null +++ b/vendor/github.com/docker/distribution/registry/api/v2/routes.go @@ -0,0 +1,40 @@ +package v2 + +import "github.com/gorilla/mux" + +// The following are definitions of the name under which all V2 routes are +// registered. These symbols can be used to look up a route based on the name. +const ( + RouteNameBase = "base" + RouteNameManifest = "manifest" + RouteNameTags = "tags" + RouteNameBlob = "blob" + RouteNameBlobUpload = "blob-upload" + RouteNameBlobUploadChunk = "blob-upload-chunk" + RouteNameCatalog = "catalog" +) + +// Router builds a gorilla router with named routes for the various API +// methods. This can be used directly by both server implementations and +// clients. +func Router() *mux.Router { + return RouterWithPrefix("") +} + +// RouterWithPrefix builds a gorilla router with a configured prefix +// on all routes. +func RouterWithPrefix(prefix string) *mux.Router { + rootRouter := mux.NewRouter() + router := rootRouter + if prefix != "" { + router = router.PathPrefix(prefix).Subrouter() + } + + router.StrictSlash(true) + + for _, descriptor := range routeDescriptors { + router.Path(descriptor.Path).Name(descriptor.Name) + } + + return rootRouter +} diff --git a/vendor/github.com/docker/distribution/registry/api/v2/urls.go b/vendor/github.com/docker/distribution/registry/api/v2/urls.go new file mode 100644 index 00000000..ab640633 --- /dev/null +++ b/vendor/github.com/docker/distribution/registry/api/v2/urls.go @@ -0,0 +1,254 @@ +package v2 + +import ( + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/distribution/reference" + "github.com/gorilla/mux" +) + +// URLBuilder creates registry API urls from a single base endpoint. It can be +// used to create urls for use in a registry client or server. +// +// All urls will be created from the given base, including the api version. +// For example, if a root of "/foo/" is provided, urls generated will be fall +// under "/foo/v2/...". Most application will only provide a schema, host and +// port, such as "https://localhost:5000/". +type URLBuilder struct { + root *url.URL // url root (ie http://localhost/) + router *mux.Router + relative bool +} + +// NewURLBuilder creates a URLBuilder with provided root url object. +func NewURLBuilder(root *url.URL, relative bool) *URLBuilder { + return &URLBuilder{ + root: root, + router: Router(), + relative: relative, + } +} + +// NewURLBuilderFromString workes identically to NewURLBuilder except it takes +// a string argument for the root, returning an error if it is not a valid +// url. +func NewURLBuilderFromString(root string, relative bool) (*URLBuilder, error) { + u, err := url.Parse(root) + if err != nil { + return nil, err + } + + return NewURLBuilder(u, relative), nil +} + +// NewURLBuilderFromRequest uses information from an *http.Request to +// construct the root url. +func NewURLBuilderFromRequest(r *http.Request, relative bool) *URLBuilder { + var ( + scheme = "http" + host = r.Host + ) + + if r.TLS != nil { + scheme = "https" + } else if len(r.URL.Scheme) > 0 { + scheme = r.URL.Scheme + } + + // Handle fowarded headers + // Prefer "Forwarded" header as defined by rfc7239 if given + // see https://tools.ietf.org/html/rfc7239 + if forwarded := r.Header.Get("Forwarded"); len(forwarded) > 0 { + forwardedHeader, _, err := parseForwardedHeader(forwarded) + if err == nil { + if fproto := forwardedHeader["proto"]; len(fproto) > 0 { + scheme = fproto + } + if fhost := forwardedHeader["host"]; len(fhost) > 0 { + host = fhost + } + } + } else { + if forwardedProto := r.Header.Get("X-Forwarded-Proto"); len(forwardedProto) > 0 { + scheme = forwardedProto + } + if forwardedHost := r.Header.Get("X-Forwarded-Host"); len(forwardedHost) > 0 { + // According to the Apache mod_proxy docs, X-Forwarded-Host can be a + // comma-separated list of hosts, to which each proxy appends the + // requested host. We want to grab the first from this comma-separated + // list. + hosts := strings.SplitN(forwardedHost, ",", 2) + host = strings.TrimSpace(hosts[0]) + } + } + + basePath := routeDescriptorsMap[RouteNameBase].Path + + requestPath := r.URL.Path + index := strings.Index(requestPath, basePath) + + u := &url.URL{ + Scheme: scheme, + Host: host, + } + + if index > 0 { + // N.B. index+1 is important because we want to include the trailing / + u.Path = requestPath[0 : index+1] + } + + return NewURLBuilder(u, relative) +} + +// BuildBaseURL constructs a base url for the API, typically just "/v2/". +func (ub *URLBuilder) BuildBaseURL() (string, error) { + route := ub.cloneRoute(RouteNameBase) + + baseURL, err := route.URL() + if err != nil { + return "", err + } + + return baseURL.String(), nil +} + +// BuildCatalogURL constructs a url get a catalog of repositories +func (ub *URLBuilder) BuildCatalogURL(values ...url.Values) (string, error) { + route := ub.cloneRoute(RouteNameCatalog) + + catalogURL, err := route.URL() + if err != nil { + return "", err + } + + return appendValuesURL(catalogURL, values...).String(), nil +} + +// BuildTagsURL constructs a url to list the tags in the named repository. +func (ub *URLBuilder) BuildTagsURL(name reference.Named) (string, error) { + route := ub.cloneRoute(RouteNameTags) + + tagsURL, err := route.URL("name", name.Name()) + if err != nil { + return "", err + } + + return tagsURL.String(), nil +} + +// BuildManifestURL constructs a url for the manifest identified by name and +// reference. The argument reference may be either a tag or digest. +func (ub *URLBuilder) BuildManifestURL(ref reference.Named) (string, error) { + route := ub.cloneRoute(RouteNameManifest) + + tagOrDigest := "" + switch v := ref.(type) { + case reference.Tagged: + tagOrDigest = v.Tag() + case reference.Digested: + tagOrDigest = v.Digest().String() + default: + return "", fmt.Errorf("reference must have a tag or digest") + } + + manifestURL, err := route.URL("name", ref.Name(), "reference", tagOrDigest) + if err != nil { + return "", err + } + + return manifestURL.String(), nil +} + +// BuildBlobURL constructs the url for the blob identified by name and dgst. +func (ub *URLBuilder) BuildBlobURL(ref reference.Canonical) (string, error) { + route := ub.cloneRoute(RouteNameBlob) + + layerURL, err := route.URL("name", ref.Name(), "digest", ref.Digest().String()) + if err != nil { + return "", err + } + + return layerURL.String(), nil +} + +// BuildBlobUploadURL constructs a url to begin a blob upload in the +// repository identified by name. +func (ub *URLBuilder) BuildBlobUploadURL(name reference.Named, values ...url.Values) (string, error) { + route := ub.cloneRoute(RouteNameBlobUpload) + + uploadURL, err := route.URL("name", name.Name()) + if err != nil { + return "", err + } + + return appendValuesURL(uploadURL, values...).String(), nil +} + +// BuildBlobUploadChunkURL constructs a url for the upload identified by uuid, +// including any url values. This should generally not be used by clients, as +// this url is provided by server implementations during the blob upload +// process. +func (ub *URLBuilder) BuildBlobUploadChunkURL(name reference.Named, uuid string, values ...url.Values) (string, error) { + route := ub.cloneRoute(RouteNameBlobUploadChunk) + + uploadURL, err := route.URL("name", name.Name(), "uuid", uuid) + if err != nil { + return "", err + } + + return appendValuesURL(uploadURL, values...).String(), nil +} + +// clondedRoute returns a clone of the named route from the router. Routes +// must be cloned to avoid modifying them during url generation. +func (ub *URLBuilder) cloneRoute(name string) clonedRoute { + route := new(mux.Route) + root := new(url.URL) + + *route = *ub.router.GetRoute(name) // clone the route + *root = *ub.root + + return clonedRoute{Route: route, root: root, relative: ub.relative} +} + +type clonedRoute struct { + *mux.Route + root *url.URL + relative bool +} + +func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) { + routeURL, err := cr.Route.URL(pairs...) + if err != nil { + return nil, err + } + + if cr.relative { + return routeURL, nil + } + + if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" { + routeURL.Path = routeURL.Path[1:] + } + + url := cr.root.ResolveReference(routeURL) + url.Scheme = cr.root.Scheme + return url, nil +} + +// appendValuesURL appends the parameters to the url. +func appendValuesURL(u *url.URL, values ...url.Values) *url.URL { + merged := u.Query() + + for _, v := range values { + for k, vv := range v { + merged[k] = append(merged[k], vv...) + } + } + + u.RawQuery = merged.Encode() + return u +} diff --git a/vendor/github.com/docker/docker-credential-helpers/client/command.go b/vendor/github.com/docker/docker-credential-helpers/client/command.go index 1936234b..93863480 100644 --- a/vendor/github.com/docker/docker-credential-helpers/client/command.go +++ b/vendor/github.com/docker/docker-credential-helpers/client/command.go @@ -15,27 +15,30 @@ type Program interface { // ProgramFunc is a type of function that initializes programs based on arguments. type ProgramFunc func(args ...string) Program -// NewShellProgramFunc creates programs that are executed in a Shell. -func NewShellProgramFunc(name string) ProgramFunc { - return NewShellProgramFuncWithEnv(name, nil) -} - -// NewShellProgramFuncWithEnv creates programs that are executed in a Shell with environment variables -func NewShellProgramFuncWithEnv(name string, env *map[string]string) ProgramFunc { +// NewShellProgramFunc creates a [ProgramFunc] to run command in a [Shell]. +func NewShellProgramFunc(command string) ProgramFunc { return func(args ...string) Program { - return &Shell{cmd: createProgramCmdRedirectErr(name, args, env)} + return createProgramCmdRedirectErr(command, args, nil) } } -func createProgramCmdRedirectErr(commandName string, args []string, env *map[string]string) *exec.Cmd { - programCmd := exec.Command(commandName, args...) +// NewShellProgramFuncWithEnv creates a [ProgramFunc] tu run command +// in a [Shell] with the given environment variables. +func NewShellProgramFuncWithEnv(command string, env *map[string]string) ProgramFunc { + return func(args ...string) Program { + return createProgramCmdRedirectErr(command, args, env) + } +} + +func createProgramCmdRedirectErr(command string, args []string, env *map[string]string) *Shell { + ec := exec.Command(command, args...) if env != nil { for k, v := range *env { - programCmd.Env = append(programCmd.Environ(), k+"="+v) + ec.Env = append(ec.Environ(), k+"="+v) } } - programCmd.Stderr = os.Stderr - return programCmd + ec.Stderr = os.Stderr + return &Shell{cmd: ec} } // Shell invokes shell commands to talk with a remote credentials-helper. diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index feb42e80..6ca2c2b0 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -2653,6 +2653,14 @@ definitions: description: | Unique ID of the build cache record. example: "ndlpt0hhvkqcdfkputsk4cq9c" + Parent: + description: | + ID of the parent build cache record. + + > **Deprecated**: This field is deprecated, and omitted if empty. + type: "string" + x-nullable: true + example: "" Parents: description: | List of parent build cache record IDs. diff --git a/vendor/github.com/docker/docker/api/types/backend/backend.go b/vendor/github.com/docker/docker/api/types/backend/backend.go new file mode 100644 index 00000000..98fe019e --- /dev/null +++ b/vendor/github.com/docker/docker/api/types/backend/backend.go @@ -0,0 +1,191 @@ +// Package backend includes types to send information to server backends. +package backend + +import ( + "io" + "time" + + "github.com/distribution/reference" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// ContainerCreateConfig is the parameter set to ContainerCreate() +type ContainerCreateConfig struct { + Name string + Config *container.Config + HostConfig *container.HostConfig + NetworkingConfig *network.NetworkingConfig + Platform *ocispec.Platform + DefaultReadOnlyNonRecursive bool +} + +// ContainerRmConfig holds arguments for the container remove +// operation. This struct is used to tell the backend what operations +// to perform. +type ContainerRmConfig struct { + ForceRemove, RemoveVolume, RemoveLink bool +} + +// ContainerAttachConfig holds the streams to use when connecting to a container to view logs. +type ContainerAttachConfig struct { + GetStreams func(multiplexed bool, cancel func()) (io.ReadCloser, io.Writer, io.Writer, error) + UseStdin bool + UseStdout bool + UseStderr bool + Logs bool + Stream bool + DetachKeys string + // Used to signify that streams must be multiplexed by producer as endpoint can't manage multiple streams. + // This is typically set by HTTP endpoint, while websocket can transport raw streams + MuxStreams bool +} + +// PartialLogMetaData provides meta data for a partial log message. Messages +// exceeding a predefined size are split into chunks with this metadata. The +// expectation is for the logger endpoints to assemble the chunks using this +// metadata. +type PartialLogMetaData struct { + Last bool // true if this message is last of a partial + ID string // identifies group of messages comprising a single record + Ordinal int // ordering of message in partial group +} + +// LogMessage is datastructure that represents piece of output produced by some +// container. The Line member is a slice of an array whose contents can be +// changed after a log driver's Log() method returns. +type LogMessage struct { + Line []byte + Source string + Timestamp time.Time + Attrs []LogAttr + PLogMetaData *PartialLogMetaData + + // Err is an error associated with a message. Completeness of a message + // with Err is not expected, tho it may be partially complete (fields may + // be missing, gibberish, or nil) + Err error +} + +// LogAttr is used to hold the extra attributes available in the log message. +type LogAttr struct { + Key string + Value string +} + +// LogSelector is a list of services and tasks that should be returned as part +// of a log stream. It is similar to swarmapi.LogSelector, with the difference +// that the names don't have to be resolved to IDs; this is mostly to avoid +// accidents later where a swarmapi LogSelector might have been incorrectly +// used verbatim (and to avoid the handler having to import swarmapi types) +type LogSelector struct { + Services []string + Tasks []string +} + +// ContainerStatsConfig holds information for configuring the runtime +// behavior of a backend.ContainerStats() call. +type ContainerStatsConfig struct { + Stream bool + OneShot bool + OutStream func() io.Writer +} + +// ContainerInspectOptions defines options for the backend.ContainerInspect +// call. +type ContainerInspectOptions struct { + // Size controls whether to propagate the container's size fields. + Size bool +} + +// ExecStartConfig holds the options to start container's exec. +type ExecStartConfig struct { + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + ConsoleSize *[2]uint `json:",omitempty"` +} + +// ExecInspect holds information about a running process started +// with docker exec. +type ExecInspect struct { + ID string + Running bool + ExitCode *int + ProcessConfig *ExecProcessConfig + OpenStdin bool + OpenStderr bool + OpenStdout bool + CanRemove bool + ContainerID string + DetachKeys []byte + Pid int +} + +// ExecProcessConfig holds information about the exec process +// running on the host. +type ExecProcessConfig struct { + Tty bool `json:"tty"` + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + Privileged *bool `json:"privileged,omitempty"` + User string `json:"user,omitempty"` +} + +// CreateImageConfig is the configuration for creating an image from a +// container. +type CreateImageConfig struct { + Tag reference.NamedTagged + Pause bool + Author string + Comment string + Config *container.Config + Changes []string +} + +// GetImageOpts holds parameters to retrieve image information +// from the backend. +type GetImageOpts struct { + Platform *ocispec.Platform +} + +// ImageInspectOpts holds parameters to inspect an image. +type ImageInspectOpts struct { + Manifests bool + Platform *ocispec.Platform +} + +// CommitConfig is the configuration for creating an image as part of a build. +type CommitConfig struct { + Author string + Comment string + Config *container.Config // TODO(thaJeztah); change this to [dockerspec.DockerOCIImageConfig] + ContainerConfig *container.Config + ContainerID string + ContainerMountLabel string + ContainerOS string + ParentImageID string +} + +// PluginRmConfig holds arguments for plugin remove. +type PluginRmConfig struct { + ForceRemove bool +} + +// PluginEnableConfig holds arguments for plugin enable +type PluginEnableConfig struct { + Timeout int +} + +// PluginDisableConfig holds arguments for plugin disable. +type PluginDisableConfig struct { + ForceDisable bool +} + +// NetworkListConfig stores the options available for listing networks +type NetworkListConfig struct { + // TODO(@cpuguy83): naming is hard, this is pulled from what was being used in the router before moving here + Detailed bool + Verbose bool +} diff --git a/vendor/github.com/docker/docker/api/types/backend/build.go b/vendor/github.com/docker/docker/api/types/backend/build.go new file mode 100644 index 00000000..19505baf --- /dev/null +++ b/vendor/github.com/docker/docker/api/types/backend/build.go @@ -0,0 +1,46 @@ +package backend + +import ( + "io" + + "github.com/docker/docker/api/types/build" + "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/pkg/streamformatter" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// PullOption defines different modes for accessing images +type PullOption int + +const ( + // PullOptionNoPull only returns local images + PullOptionNoPull PullOption = iota + // PullOptionForcePull always tries to pull a ref from the registry first + PullOptionForcePull + // PullOptionPreferLocal uses local image if it exists, otherwise pulls + PullOptionPreferLocal +) + +// ProgressWriter is a data object to transport progress streams to the client +type ProgressWriter struct { + Output io.Writer + StdoutFormatter io.Writer + StderrFormatter io.Writer + AuxFormatter *streamformatter.AuxFormatter + ProgressReaderFunc func(io.ReadCloser) io.ReadCloser +} + +// BuildConfig is the configuration used by a BuildManager to start a build +type BuildConfig struct { + Source io.ReadCloser + ProgressWriter ProgressWriter + Options *build.ImageBuildOptions +} + +// GetImageAndLayerOptions are the options supported by GetImageAndReleasableLayer +type GetImageAndLayerOptions struct { + PullOption PullOption + AuthConfig map[string]registry.AuthConfig + Output io.Writer + Platform *ocispec.Platform +} diff --git a/vendor/github.com/docker/docker/api/types/backend/disk_usage.go b/vendor/github.com/docker/docker/api/types/backend/disk_usage.go new file mode 100644 index 00000000..a2c4e24d --- /dev/null +++ b/vendor/github.com/docker/docker/api/types/backend/disk_usage.go @@ -0,0 +1,29 @@ +package backend + +import ( + "github.com/docker/docker/api/types/build" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/volume" +) + +// DiskUsageOptions holds parameters for system disk usage query. +type DiskUsageOptions struct { + // Containers controls whether container disk usage should be computed. + Containers bool + + // Images controls whether image disk usage should be computed. + Images bool + + // Volumes controls whether volume disk usage should be computed. + Volumes bool +} + +// DiskUsage contains the information returned by the backend for the +// GET "/system/df" endpoint. +type DiskUsage struct { + Images *image.DiskUsage + Containers *container.DiskUsage + Volumes *volume.DiskUsage + BuildCache *build.CacheDiskUsage +} diff --git a/vendor/github.com/docker/docker/errdefs/defs.go b/vendor/github.com/docker/docker/errdefs/defs.go deleted file mode 100644 index a5523c3e..00000000 --- a/vendor/github.com/docker/docker/errdefs/defs.go +++ /dev/null @@ -1,69 +0,0 @@ -package errdefs - -// ErrNotFound signals that the requested object doesn't exist -type ErrNotFound interface { - NotFound() -} - -// ErrInvalidParameter signals that the user input is invalid -type ErrInvalidParameter interface { - InvalidParameter() -} - -// ErrConflict signals that some internal state conflicts with the requested action and can't be performed. -// A change in state should be able to clear this error. -type ErrConflict interface { - Conflict() -} - -// ErrUnauthorized is used to signify that the user is not authorized to perform a specific action -type ErrUnauthorized interface { - Unauthorized() -} - -// ErrUnavailable signals that the requested action/subsystem is not available. -type ErrUnavailable interface { - Unavailable() -} - -// ErrForbidden signals that the requested action cannot be performed under any circumstances. -// When a ErrForbidden is returned, the caller should never retry the action. -type ErrForbidden interface { - Forbidden() -} - -// ErrSystem signals that some internal error occurred. -// An example of this would be a failed mount request. -type ErrSystem interface { - System() -} - -// ErrNotModified signals that an action can't be performed because it's already in the desired state -type ErrNotModified interface { - NotModified() -} - -// ErrNotImplemented signals that the requested action/feature is not implemented on the system as configured. -type ErrNotImplemented interface { - NotImplemented() -} - -// ErrUnknown signals that the kind of error that occurred is not known. -type ErrUnknown interface { - Unknown() -} - -// ErrCancelled signals that the action was cancelled. -type ErrCancelled interface { - Cancelled() -} - -// ErrDeadline signals that the deadline was reached before the action completed. -type ErrDeadline interface { - DeadlineExceeded() -} - -// ErrDataLoss indicates that data was lost or there is data corruption. -type ErrDataLoss interface { - DataLoss() -} diff --git a/vendor/github.com/docker/docker/errdefs/doc.go b/vendor/github.com/docker/docker/errdefs/doc.go deleted file mode 100644 index efbe8ba9..00000000 --- a/vendor/github.com/docker/docker/errdefs/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Package errdefs defines a set of error interfaces that packages should use for communicating classes of errors. -// Errors that cross the package boundary should implement one (and only one) of these interfaces. -// -// Packages should not reference these interfaces directly, only implement them. -// To check if a particular error implements one of these interfaces, there are helper -// functions provided (e.g. `Is`) which can be used rather than asserting the interfaces directly. -// If you must assert on these interfaces, be sure to check the causal chain (`err.Unwrap()`). -package errdefs diff --git a/vendor/github.com/docker/docker/errdefs/helpers.go b/vendor/github.com/docker/docker/errdefs/helpers.go deleted file mode 100644 index 2a9f7ffd..00000000 --- a/vendor/github.com/docker/docker/errdefs/helpers.go +++ /dev/null @@ -1,309 +0,0 @@ -package errdefs - -import ( - "context" - - cerrdefs "github.com/containerd/errdefs" -) - -type errNotFound struct{ error } - -func (errNotFound) NotFound() {} - -func (e errNotFound) Cause() error { - return e.error -} - -func (e errNotFound) Unwrap() error { - return e.error -} - -// NotFound creates an [ErrNotFound] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrNotFound], -func NotFound(err error) error { - if err == nil || cerrdefs.IsNotFound(err) { - return err - } - return errNotFound{err} -} - -type errInvalidParameter struct{ error } - -func (errInvalidParameter) InvalidParameter() {} - -func (e errInvalidParameter) Cause() error { - return e.error -} - -func (e errInvalidParameter) Unwrap() error { - return e.error -} - -// InvalidParameter creates an [ErrInvalidParameter] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrInvalidParameter], -func InvalidParameter(err error) error { - if err == nil || cerrdefs.IsInvalidArgument(err) { - return err - } - return errInvalidParameter{err} -} - -type errConflict struct{ error } - -func (errConflict) Conflict() {} - -func (e errConflict) Cause() error { - return e.error -} - -func (e errConflict) Unwrap() error { - return e.error -} - -// Conflict creates an [ErrConflict] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrConflict], -func Conflict(err error) error { - if err == nil || cerrdefs.IsConflict(err) { - return err - } - return errConflict{err} -} - -type errUnauthorized struct{ error } - -func (errUnauthorized) Unauthorized() {} - -func (e errUnauthorized) Cause() error { - return e.error -} - -func (e errUnauthorized) Unwrap() error { - return e.error -} - -// Unauthorized creates an [ErrUnauthorized] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrUnauthorized], -func Unauthorized(err error) error { - if err == nil || cerrdefs.IsUnauthorized(err) { - return err - } - return errUnauthorized{err} -} - -type errUnavailable struct{ error } - -func (errUnavailable) Unavailable() {} - -func (e errUnavailable) Cause() error { - return e.error -} - -func (e errUnavailable) Unwrap() error { - return e.error -} - -// Unavailable creates an [ErrUnavailable] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrUnavailable], -func Unavailable(err error) error { - if err == nil || cerrdefs.IsUnavailable(err) { - return err - } - return errUnavailable{err} -} - -type errForbidden struct{ error } - -func (errForbidden) Forbidden() {} - -func (e errForbidden) Cause() error { - return e.error -} - -func (e errForbidden) Unwrap() error { - return e.error -} - -// Forbidden creates an [ErrForbidden] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrForbidden], -func Forbidden(err error) error { - if err == nil || cerrdefs.IsPermissionDenied(err) { - return err - } - return errForbidden{err} -} - -type errSystem struct{ error } - -func (errSystem) System() {} - -func (e errSystem) Cause() error { - return e.error -} - -func (e errSystem) Unwrap() error { - return e.error -} - -// System creates an [ErrSystem] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrSystem], -func System(err error) error { - if err == nil || cerrdefs.IsInternal(err) { - return err - } - return errSystem{err} -} - -type errNotModified struct{ error } - -func (errNotModified) NotModified() {} - -func (e errNotModified) Cause() error { - return e.error -} - -func (e errNotModified) Unwrap() error { - return e.error -} - -// NotModified creates an [ErrNotModified] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [NotModified], -func NotModified(err error) error { - if err == nil || cerrdefs.IsNotModified(err) { - return err - } - return errNotModified{err} -} - -type errNotImplemented struct{ error } - -func (errNotImplemented) NotImplemented() {} - -func (e errNotImplemented) Cause() error { - return e.error -} - -func (e errNotImplemented) Unwrap() error { - return e.error -} - -// NotImplemented creates an [ErrNotImplemented] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrNotImplemented], -func NotImplemented(err error) error { - if err == nil || cerrdefs.IsNotImplemented(err) { - return err - } - return errNotImplemented{err} -} - -type errUnknown struct{ error } - -func (errUnknown) Unknown() {} - -func (e errUnknown) Cause() error { - return e.error -} - -func (e errUnknown) Unwrap() error { - return e.error -} - -// Unknown creates an [ErrUnknown] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrUnknown], -func Unknown(err error) error { - if err == nil || cerrdefs.IsUnknown(err) { - return err - } - return errUnknown{err} -} - -type errCancelled struct{ error } - -func (errCancelled) Cancelled() {} - -func (e errCancelled) Cause() error { - return e.error -} - -func (e errCancelled) Unwrap() error { - return e.error -} - -// Cancelled creates an [ErrCancelled] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrCancelled], -func Cancelled(err error) error { - if err == nil || cerrdefs.IsCanceled(err) { - return err - } - return errCancelled{err} -} - -type errDeadline struct{ error } - -func (errDeadline) DeadlineExceeded() {} - -func (e errDeadline) Cause() error { - return e.error -} - -func (e errDeadline) Unwrap() error { - return e.error -} - -// Deadline creates an [ErrDeadline] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrDeadline], -func Deadline(err error) error { - if err == nil || cerrdefs.IsDeadlineExceeded(err) { - return err - } - return errDeadline{err} -} - -type errDataLoss struct{ error } - -func (errDataLoss) DataLoss() {} - -func (e errDataLoss) Cause() error { - return e.error -} - -func (e errDataLoss) Unwrap() error { - return e.error -} - -// DataLoss creates an [ErrDataLoss] error from the given error. -// It returns the error as-is if it is either nil (no error) or already implements -// [ErrDataLoss], -func DataLoss(err error) error { - if err == nil || cerrdefs.IsDataLoss(err) { - return err - } - return errDataLoss{err} -} - -// FromContext returns the error class from the passed in context -func FromContext(ctx context.Context) error { - e := ctx.Err() - if e == nil { - return nil - } - - if e == context.Canceled { - return Cancelled(e) - } - if e == context.DeadlineExceeded { - return Deadline(e) - } - return Unknown(e) -} diff --git a/vendor/github.com/docker/docker/errdefs/http_helpers.go b/vendor/github.com/docker/docker/errdefs/http_helpers.go deleted file mode 100644 index 823ff2d9..00000000 --- a/vendor/github.com/docker/docker/errdefs/http_helpers.go +++ /dev/null @@ -1,49 +0,0 @@ -package errdefs - -import ( - "net/http" -) - -// FromStatusCode creates an errdef error, based on the provided HTTP status-code -// -// Deprecated: Use [cerrdefs.ToNative] instead -func FromStatusCode(err error, statusCode int) error { - if err == nil { - return nil - } - switch statusCode { - case http.StatusNotFound: - return NotFound(err) - case http.StatusBadRequest: - return InvalidParameter(err) - case http.StatusConflict: - return Conflict(err) - case http.StatusUnauthorized: - return Unauthorized(err) - case http.StatusServiceUnavailable: - return Unavailable(err) - case http.StatusForbidden: - return Forbidden(err) - case http.StatusNotModified: - return NotModified(err) - case http.StatusNotImplemented: - return NotImplemented(err) - case http.StatusInternalServerError: - if IsCancelled(err) || IsSystem(err) || IsUnknown(err) || IsDataLoss(err) || IsDeadline(err) { - return err - } - return System(err) - default: - switch { - case statusCode >= http.StatusOK && statusCode < http.StatusBadRequest: - // it's a client error - return err - case statusCode >= http.StatusBadRequest && statusCode < http.StatusInternalServerError: - return InvalidParameter(err) - case statusCode >= http.StatusInternalServerError && statusCode < 600: - return System(err) - default: - return Unknown(err) - } - } -} diff --git a/vendor/github.com/docker/docker/errdefs/is.go b/vendor/github.com/docker/docker/errdefs/is.go deleted file mode 100644 index ceb754a9..00000000 --- a/vendor/github.com/docker/docker/errdefs/is.go +++ /dev/null @@ -1,78 +0,0 @@ -package errdefs - -import ( - "context" - "errors" - - cerrdefs "github.com/containerd/errdefs" -) - -// IsNotFound returns if the passed in error is an [ErrNotFound], -// -// Deprecated: use containerd [cerrdefs.IsNotFound] -var IsNotFound = cerrdefs.IsNotFound - -// IsInvalidParameter returns if the passed in error is an [ErrInvalidParameter]. -// -// Deprecated: use containerd [cerrdefs.IsInvalidArgument] -var IsInvalidParameter = cerrdefs.IsInvalidArgument - -// IsConflict returns if the passed in error is an [ErrConflict]. -// -// Deprecated: use containerd [cerrdefs.IsConflict] -var IsConflict = cerrdefs.IsConflict - -// IsUnauthorized returns if the passed in error is an [ErrUnauthorized]. -// -// Deprecated: use containerd [cerrdefs.IsUnauthorized] -var IsUnauthorized = cerrdefs.IsUnauthorized - -// IsUnavailable returns if the passed in error is an [ErrUnavailable]. -// -// Deprecated: use containerd [cerrdefs.IsUnavailable] -var IsUnavailable = cerrdefs.IsUnavailable - -// IsForbidden returns if the passed in error is an [ErrForbidden]. -// -// Deprecated: use containerd [cerrdefs.IsPermissionDenied] -var IsForbidden = cerrdefs.IsPermissionDenied - -// IsSystem returns if the passed in error is an [ErrSystem]. -// -// Deprecated: use containerd [cerrdefs.IsInternal] -var IsSystem = cerrdefs.IsInternal - -// IsNotModified returns if the passed in error is an [ErrNotModified]. -// -// Deprecated: use containerd [cerrdefs.IsNotModified] -var IsNotModified = cerrdefs.IsNotModified - -// IsNotImplemented returns if the passed in error is an [ErrNotImplemented]. -// -// Deprecated: use containerd [cerrdefs.IsNotImplemented] -var IsNotImplemented = cerrdefs.IsNotImplemented - -// IsUnknown returns if the passed in error is an [ErrUnknown]. -// -// Deprecated: use containerd [cerrdefs.IsUnknown] -var IsUnknown = cerrdefs.IsUnknown - -// IsCancelled returns if the passed in error is an [ErrCancelled]. -// -// Deprecated: use containerd [cerrdefs.IsCanceled] -var IsCancelled = cerrdefs.IsCanceled - -// IsDeadline returns if the passed in error is an [ErrDeadline]. -// -// Deprecated: use containerd [cerrdefs.IsDeadlineExceeded] -var IsDeadline = cerrdefs.IsDeadlineExceeded - -// IsDataLoss returns if the passed in error is an [ErrDataLoss]. -// -// Deprecated: use containerd [cerrdefs.IsDataLoss] -var IsDataLoss = cerrdefs.IsDataLoss - -// IsContext returns if the passed in error is due to context cancellation or deadline exceeded. -func IsContext(err error) bool { - return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) -} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir.go b/vendor/github.com/docker/docker/pkg/homedir/homedir.go new file mode 100644 index 00000000..c0ab3f5b --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir.go @@ -0,0 +1,28 @@ +package homedir + +import ( + "os" + "os/user" + "runtime" +) + +// Get returns the home directory of the current user with the help of +// environment variables depending on the target operating system. +// Returned path should be used with "path/filepath" to form new paths. +// +// On non-Windows platforms, it falls back to nss lookups, if the home +// directory cannot be obtained from environment-variables. +// +// If linking statically with cgo enabled against glibc, ensure the +// osusergo build tag is used. +// +// If needing to do nss lookups, do not disable cgo or set osusergo. +func Get() string { + home, _ := os.UserHomeDir() + if home == "" && runtime.GOOS != "windows" { + if u, err := user.Current(); err == nil { + return u.HomeDir + } + } + return home +} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go new file mode 100644 index 00000000..469395f1 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go @@ -0,0 +1,105 @@ +package homedir + +import ( + "errors" + "os" + "path/filepath" + "strings" +) + +// GetRuntimeDir returns XDG_RUNTIME_DIR. +// XDG_RUNTIME_DIR is typically configured via pam_systemd. +// GetRuntimeDir returns non-nil error if XDG_RUNTIME_DIR is not set. +// +// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html +func GetRuntimeDir() (string, error) { + if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" { + return xdgRuntimeDir, nil + } + return "", errors.New("could not get XDG_RUNTIME_DIR") +} + +// StickRuntimeDirContents sets the sticky bit on files that are under +// XDG_RUNTIME_DIR, so that the files won't be periodically removed by the system. +// +// StickyRuntimeDir returns slice of sticked files. +// StickyRuntimeDir returns nil error if XDG_RUNTIME_DIR is not set. +// +// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html +func StickRuntimeDirContents(files []string) ([]string, error) { + runtimeDir, err := GetRuntimeDir() + if err != nil { + // ignore error if runtimeDir is empty + return nil, nil + } + runtimeDir, err = filepath.Abs(runtimeDir) + if err != nil { + return nil, err + } + var sticked []string + for _, f := range files { + f, err = filepath.Abs(f) + if err != nil { + return sticked, err + } + if strings.HasPrefix(f, runtimeDir+"/") { + if err = stick(f); err != nil { + return sticked, err + } + sticked = append(sticked, f) + } + } + return sticked, nil +} + +func stick(f string) error { + st, err := os.Stat(f) + if err != nil { + return err + } + m := st.Mode() + m |= os.ModeSticky + return os.Chmod(f, m) +} + +// GetDataHome returns XDG_DATA_HOME. +// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set. +// If HOME and XDG_DATA_HOME are not set, getpwent(3) is consulted to determine the users home directory. +// +// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html +func GetDataHome() (string, error) { + if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" { + return xdgDataHome, nil + } + home := Get() + if home == "" { + return "", errors.New("could not get either XDG_DATA_HOME or HOME") + } + return filepath.Join(home, ".local", "share"), nil +} + +// GetConfigHome returns XDG_CONFIG_HOME. +// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set. +// If HOME and XDG_CONFIG_HOME are not set, getpwent(3) is consulted to determine the users home directory. +// +// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html +func GetConfigHome() (string, error) { + if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" { + return xdgConfigHome, nil + } + home := Get() + if home == "" { + return "", errors.New("could not get either XDG_CONFIG_HOME or HOME") + } + return filepath.Join(home, ".config"), nil +} + +// GetLibHome returns $HOME/.local/lib +// If HOME is not set, getpwent(3) is consulted to determine the users home directory. +func GetLibHome() (string, error) { + home := Get() + if home == "" { + return "", errors.New("could not get HOME") + } + return filepath.Join(home, ".local/lib"), nil +} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go new file mode 100644 index 00000000..1e41e6aa --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go @@ -0,0 +1,32 @@ +//go:build !linux + +package homedir + +import ( + "errors" +) + +// GetRuntimeDir is unsupported on non-linux system. +func GetRuntimeDir() (string, error) { + return "", errors.New("homedir.GetRuntimeDir() is not supported on this system") +} + +// StickRuntimeDirContents is unsupported on non-linux system. +func StickRuntimeDirContents(files []string) ([]string, error) { + return nil, errors.New("homedir.StickRuntimeDirContents() is not supported on this system") +} + +// GetDataHome is unsupported on non-linux system. +func GetDataHome() (string, error) { + return "", errors.New("homedir.GetDataHome() is not supported on this system") +} + +// GetConfigHome is unsupported on non-linux system. +func GetConfigHome() (string, error) { + return "", errors.New("homedir.GetConfigHome() is not supported on this system") +} + +// GetLibHome is unsupported on non-linux system. +func GetLibHome() (string, error) { + return "", errors.New("homedir.GetLibHome() is not supported on this system") +} diff --git a/vendor/github.com/docker/docker/pkg/ioutils/fswriters_deprecated.go b/vendor/github.com/docker/docker/pkg/ioutils/fswriters_deprecated.go deleted file mode 100644 index f34e0691..00000000 --- a/vendor/github.com/docker/docker/pkg/ioutils/fswriters_deprecated.go +++ /dev/null @@ -1,44 +0,0 @@ -package ioutils - -import ( - "io" - "os" - - "github.com/moby/sys/atomicwriter" -) - -// 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. -// NOTE: umask is not considered for the file's permissions. -// -// Deprecated: use [atomicwriter.New] instead. -func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) { - return atomicwriter.New(filename, perm) -} - -// AtomicWriteFile atomically writes data to a file named by filename and with the specified permission bits. -// NOTE: umask is not considered for the file's permissions. -// -// Deprecated: use [atomicwriter.WriteFile] instead. -func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error { - return atomicwriter.WriteFile(filename, data, perm) -} - -// 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. -// -// Deprecated: use [atomicwriter.WriteSet] instead. -type AtomicWriteSet = atomicwriter.WriteSet - -// 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. -// -// Deprecated: use [atomicwriter.NewWriteSet] instead. -func NewAtomicWriteSet(tmpDir string) (*atomicwriter.WriteSet, error) { - return atomicwriter.NewWriteSet(tmpDir) -} diff --git a/vendor/github.com/docker/docker/pkg/ioutils/writers.go b/vendor/github.com/docker/docker/pkg/ioutils/writers.go deleted file mode 100644 index 4cf2d4de..00000000 --- a/vendor/github.com/docker/docker/pkg/ioutils/writers.go +++ /dev/null @@ -1,28 +0,0 @@ -package ioutils - -import ( - "io" - "sync/atomic" -) - -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, - } -} diff --git a/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go b/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go new file mode 100644 index 00000000..13df228c --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go @@ -0,0 +1,314 @@ +package jsonmessage + +import ( + "encoding/json" + "fmt" + "io" + "strings" + "time" + + "github.com/docker/go-units" + "github.com/moby/term" + "github.com/morikuni/aec" +) + +// RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to +// ensure the formatted time isalways the same number of characters. +const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" + +// JSONError wraps a concrete Code and Message, Code is +// an integer error code, Message is the error message. +type JSONError struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +func (e *JSONError) Error() string { + return e.Message +} + +// JSONProgress describes a progress message in a JSON stream. +type JSONProgress struct { + // Current is the current status and value of the progress made towards Total. + Current int64 `json:"current,omitempty"` + // Total is the end value describing when we made 100% progress for an operation. + Total int64 `json:"total,omitempty"` + // Start is the initial value for the operation. + Start int64 `json:"start,omitempty"` + // HideCounts. if true, hides the progress count indicator (xB/yB). + HideCounts bool `json:"hidecounts,omitempty"` + // Units is the unit to print for progress. It defaults to "bytes" if empty. + Units string `json:"units,omitempty"` + + // terminalFd is the fd of the current terminal, if any. It is used + // to get the terminal width. + terminalFd uintptr + + // nowFunc is used to override the current time in tests. + nowFunc func() time.Time + + // winSize is used to override the terminal width in tests. + winSize int +} + +func (p *JSONProgress) String() string { + var ( + width = p.width() + pbBox string + numbersBox string + ) + if p.Current <= 0 && p.Total <= 0 { + return "" + } + if p.Total <= 0 { + switch p.Units { + case "": + return fmt.Sprintf("%8v", units.HumanSize(float64(p.Current))) + default: + return fmt.Sprintf("%d %s", p.Current, p.Units) + } + } + + percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 + if percentage > 50 { + percentage = 50 + } + if width > 110 { + // this number can't be negative gh#7136 + numSpaces := 0 + if 50-percentage > 0 { + numSpaces = 50 - percentage + } + pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) + } + + switch { + case p.HideCounts: + case p.Units == "": // no units, use bytes + current := units.HumanSize(float64(p.Current)) + total := units.HumanSize(float64(p.Total)) + + numbersBox = fmt.Sprintf("%8v/%v", current, total) + + if p.Current > p.Total { + // remove total display if the reported current is wonky. + numbersBox = fmt.Sprintf("%8v", current) + } + default: + numbersBox = fmt.Sprintf("%d/%d %s", p.Current, p.Total, p.Units) + + if p.Current > p.Total { + // remove total display if the reported current is wonky. + numbersBox = fmt.Sprintf("%d %s", p.Current, p.Units) + } + } + + // Show approximation of remaining time if there's enough width. + var timeLeftBox string + if width > 50 { + if p.Current > 0 && p.Start > 0 && percentage < 50 { + fromStart := p.now().Sub(time.Unix(p.Start, 0)) + perEntry := fromStart / time.Duration(p.Current) + left := time.Duration(p.Total-p.Current) * perEntry + timeLeftBox = " " + left.Round(time.Second).String() + } + } + return pbBox + numbersBox + timeLeftBox +} + +// now returns the current time in UTC, but can be overridden in tests +// by setting JSONProgress.nowFunc to a custom function. +func (p *JSONProgress) now() time.Time { + if p.nowFunc != nil { + return p.nowFunc() + } + return time.Now().UTC() +} + +// width returns the current terminal's width, but can be overridden +// in tests by setting JSONProgress.winSize to a non-zero value. +func (p *JSONProgress) width() int { + if p.winSize != 0 { + return p.winSize + } + ws, err := term.GetWinsize(p.terminalFd) + if err == nil { + return int(ws.Width) + } + return 200 +} + +// JSONMessage defines a message struct. It describes +// the created time, where it from, status, ID of the +// message. It's used for docker events. +type JSONMessage struct { + Stream string `json:"stream,omitempty"` + Status string `json:"status,omitempty"` + Progress *JSONProgress `json:"progressDetail,omitempty"` + + // ProgressMessage is a pre-formatted presentation of [Progress]. + // + // Deprecated: this field is deprecated since docker v0.7.1 / API v1.8. Use the information in [Progress] instead. This field will be omitted in a future release. + ProgressMessage string `json:"progress,omitempty"` + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` // Deprecated: this field is no longer set in stream responses and should not be used. + Time int64 `json:"time,omitempty"` // Deprecated: this field is no longer set in stream responses and should not be used. + TimeNano int64 `json:"timeNano,omitempty"` // Deprecated: this field is no longer set in stream responses and should not be used. + Error *JSONError `json:"errorDetail,omitempty"` + + // ErrorMessage contains errors encountered during the operation. + // + // Deprecated: this field is deprecated since docker v0.6.0 / API v1.4. Use [Error.Message] instead. This field will be omitted in a future release. + ErrorMessage string `json:"error,omitempty"` // deprecated + // Aux contains out-of-band data, such as digests for push signing and image id after building. + Aux *json.RawMessage `json:"aux,omitempty"` +} + +func clearLine(out io.Writer) { + eraseMode := aec.EraseModes.All + cl := aec.EraseLine(eraseMode) + fmt.Fprint(out, cl) +} + +func cursorUp(out io.Writer, l uint) { + fmt.Fprint(out, aec.Up(l)) +} + +func cursorDown(out io.Writer, l uint) { + fmt.Fprint(out, aec.Down(l)) +} + +// Display prints the JSONMessage to out. If isTerminal is true, it erases +// the entire current line when displaying the progressbar. It returns an +// error if the [JSONMessage.Error] field is non-nil. +func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error { + if jm.Error != nil { + return jm.Error + } + var endl string + if isTerminal && jm.Stream == "" && jm.Progress != nil { + clearLine(out) + endl = "\r" + fmt.Fprint(out, endl) + } else if jm.Progress != nil && jm.Progress.String() != "" { // disable progressbar in non-terminal + return nil + } + if jm.TimeNano != 0 { + fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(RFC3339NanoFixed)) + } else if jm.Time != 0 { + fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(RFC3339NanoFixed)) + } + if jm.ID != "" { + fmt.Fprintf(out, "%s: ", jm.ID) + } + if jm.From != "" { + fmt.Fprintf(out, "(from %s) ", jm.From) + } + if jm.Progress != nil && isTerminal { + fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl) + } else if jm.ProgressMessage != "" { // deprecated + fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl) + } else if jm.Stream != "" { + fmt.Fprintf(out, "%s%s", jm.Stream, endl) + } else { + fmt.Fprintf(out, "%s%s\n", jm.Status, endl) + } + return nil +} + +// DisplayJSONMessagesStream reads a JSON message stream from in, and writes +// each [JSONMessage] to out. It returns an error if an invalid JSONMessage +// is received, or if a JSONMessage containers a non-zero [JSONMessage.Error]. +// +// Presentation of the JSONMessage depends on whether a terminal is attached, +// and on the terminal width. Progress bars ([JSONProgress]) are suppressed +// on narrower terminals (< 110 characters). +// +// - isTerminal describes if out is a terminal, in which case it prints +// a newline ("\n") at the end of each line and moves the cursor while +// displaying. +// - terminalFd is the fd of the current terminal (if any), and used +// to get the terminal width. +// - auxCallback allows handling the [JSONMessage.Aux] field. It is +// called if a JSONMessage contains an Aux field, in which case +// DisplayJSONMessagesStream does not present the JSONMessage. +func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error { + var ( + dec = json.NewDecoder(in) + ids = make(map[string]uint) + ) + + for { + var diff uint + var jm JSONMessage + if err := dec.Decode(&jm); err != nil { + if err == io.EOF { + break + } + return err + } + + if jm.Aux != nil { + if auxCallback != nil { + auxCallback(jm) + } + continue + } + + if jm.Progress != nil { + jm.Progress.terminalFd = terminalFd + } + if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") { + line, ok := ids[jm.ID] + if !ok { + // NOTE: This approach of using len(id) to + // figure out the number of lines of history + // only works as long as we clear the history + // when we output something that's not + // accounted for in the map, such as a line + // with no ID. + line = uint(len(ids)) + ids[jm.ID] = line + if isTerminal { + fmt.Fprintf(out, "\n") + } + } + diff = uint(len(ids)) - line + if isTerminal { + cursorUp(out, diff) + } + } else { + // When outputting something that isn't progress + // output, clear the history of previous lines. We + // don't want progress entries from some previous + // operation to be updated (for example, pull -a + // with multiple tags). + ids = make(map[string]uint) + } + err := jm.Display(out, isTerminal) + if jm.ID != "" && isTerminal { + cursorDown(out, diff) + } + if err != nil { + return err + } + } + return nil +} + +// Stream is an io.Writer for output with utilities to get the output's file +// descriptor and to detect whether it's a terminal. +// +// it is subset of the streams.Out type in +// https://pkg.go.dev/github.com/docker/cli@v20.10.17+incompatible/cli/streams#Out +type Stream interface { + io.Writer + FD() uintptr + IsTerminal() bool +} + +// DisplayJSONMessagesToStream prints json messages to the output Stream. It is +// used by the Docker CLI to print JSONMessage streams. +func DisplayJSONMessagesToStream(in io.Reader, stream Stream, auxCallback func(JSONMessage)) error { + return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback) +} diff --git a/vendor/github.com/docker/docker/pkg/namesgenerator/names-generator.go b/vendor/github.com/docker/docker/pkg/namesgenerator/names-generator.go new file mode 100644 index 00000000..4d0bf0f7 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/namesgenerator/names-generator.go @@ -0,0 +1,860 @@ +// Package namesgenerator generates random names. +// +// This package is officially "frozen" - no new additions will be accepted. +// +// For a long time, this package provided a lot of joy within the project, but +// at some point the conflicts of opinion became greater than the added joy. +// +// At some future time, this may be replaced with something that sparks less +// controversy, but for now it will remain as-is. +// +// See also https://github.com/moby/moby/pull/43210#issuecomment-1029934277 +package namesgenerator + +import ( + "math/rand" + "strconv" +) + +var ( + left = [...]string{ + "admiring", + "adoring", + "affectionate", + "agitated", + "amazing", + "angry", + "awesome", + "beautiful", + "blissful", + "bold", + "boring", + "brave", + "busy", + "charming", + "clever", + "compassionate", + "competent", + "condescending", + "confident", + "cool", + "cranky", + "crazy", + "dazzling", + "determined", + "distracted", + "dreamy", + "eager", + "ecstatic", + "elastic", + "elated", + "elegant", + "eloquent", + "epic", + "exciting", + "fervent", + "festive", + "flamboyant", + "focused", + "friendly", + "frosty", + "funny", + "gallant", + "gifted", + "goofy", + "gracious", + "great", + "happy", + "hardcore", + "heuristic", + "hopeful", + "hungry", + "infallible", + "inspiring", + "intelligent", + "interesting", + "jolly", + "jovial", + "keen", + "kind", + "laughing", + "loving", + "lucid", + "magical", + "modest", + "musing", + "mystifying", + "naughty", + "nervous", + "nice", + "nifty", + "nostalgic", + "objective", + "optimistic", + "peaceful", + "pedantic", + "pensive", + "practical", + "priceless", + "quirky", + "quizzical", + "recursing", + "relaxed", + "reverent", + "romantic", + "sad", + "serene", + "sharp", + "silly", + "sleepy", + "stoic", + "strange", + "stupefied", + "suspicious", + "sweet", + "tender", + "thirsty", + "trusting", + "unruffled", + "upbeat", + "vibrant", + "vigilant", + "vigorous", + "wizardly", + "wonderful", + "xenodochial", + "youthful", + "zealous", + "zen", + } + + // Docker, starting from 0.7.x, generates names from notable scientists and hackers. + // Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa. + right = [...]string{ + // Maria Gaetana Agnesi - Italian mathematician, philosopher, theologian and humanitarian. She was the first woman to write a mathematics handbook and the first woman appointed as a Mathematics Professor at a University. https://en.wikipedia.org/wiki/Maria_Gaetana_Agnesi + "agnesi", + + // Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. https://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB + "albattani", + + // Frances E. Allen, became the first female IBM Fellow in 1989. In 2006, she became the first female recipient of the ACM's Turing Award. https://en.wikipedia.org/wiki/Frances_E._Allen + "allen", + + // June Almeida - Scottish virologist who took the first pictures of the rubella virus - https://en.wikipedia.org/wiki/June_Almeida + "almeida", + + // Kathleen Antonelli, American computer programmer and one of the six original programmers of the ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli + "antonelli", + + // Archimedes was a physicist, engineer and mathematician who invented too many things to list them here. https://en.wikipedia.org/wiki/Archimedes + "archimedes", + + // Maria Ardinghelli - Italian translator, mathematician and physicist - https://en.wikipedia.org/wiki/Maria_Ardinghelli + "ardinghelli", + + // Aryabhata - Ancient Indian mathematician-astronomer during 476-550 CE https://en.wikipedia.org/wiki/Aryabhata + "aryabhata", + + // Wanda Austin - Wanda Austin is the President and CEO of The Aerospace Corporation, a leading architect for the US security space programs. https://en.wikipedia.org/wiki/Wanda_Austin + "austin", + + // Charles Babbage invented the concept of a programmable computer. https://en.wikipedia.org/wiki/Charles_Babbage. + "babbage", + + // Stefan Banach - Polish mathematician, was one of the founders of modern functional analysis. https://en.wikipedia.org/wiki/Stefan_Banach + "banach", + + // Buckaroo Banzai and his mentor Dr. Hikita perfected the "oscillation overthruster", a device that allows one to pass through solid matter. - https://en.wikipedia.org/wiki/The_Adventures_of_Buckaroo_Banzai_Across_the_8th_Dimension + "banzai", + + // John Bardeen co-invented the transistor - https://en.wikipedia.org/wiki/John_Bardeen + "bardeen", + + // Jean Bartik, born Betty Jean Jennings, was one of the original programmers for the ENIAC computer. https://en.wikipedia.org/wiki/Jean_Bartik + "bartik", + + // Laura Bassi, the world's first female professor https://en.wikipedia.org/wiki/Laura_Bassi + "bassi", + + // Hugh Beaver, British engineer, founder of the Guinness Book of World Records https://en.wikipedia.org/wiki/Hugh_Beaver + "beaver", + + // Alexander Graham Bell - an eminent Scottish-born scientist, inventor, engineer and innovator who is credited with inventing the first practical telephone - https://en.wikipedia.org/wiki/Alexander_Graham_Bell + "bell", + + // Karl Friedrich Benz - a German automobile engineer. Inventor of the first practical motorcar. https://en.wikipedia.org/wiki/Karl_Benz + "benz", + + // Homi J Bhabha - was an Indian nuclear physicist, founding director, and professor of physics at the Tata Institute of Fundamental Research. Colloquially known as "father of Indian nuclear programme"- https://en.wikipedia.org/wiki/Homi_J._Bhabha + "bhabha", + + // Bhaskara II - Ancient Indian mathematician-astronomer whose work on calculus predates Newton and Leibniz by over half a millennium - https://en.wikipedia.org/wiki/Bh%C4%81skara_II#Calculus + "bhaskara", + + // Sue Black - British computer scientist and campaigner. She has been instrumental in saving Bletchley Park, the site of World War II codebreaking - https://en.wikipedia.org/wiki/Sue_Black_(computer_scientist) + "black", + + // Elizabeth Helen Blackburn - Australian-American Nobel laureate; best known for co-discovering telomerase. https://en.wikipedia.org/wiki/Elizabeth_Blackburn + "blackburn", + + // Elizabeth Blackwell - American doctor and first American woman to receive a medical degree - https://en.wikipedia.org/wiki/Elizabeth_Blackwell + "blackwell", + + // Niels Bohr is the father of quantum theory. https://en.wikipedia.org/wiki/Niels_Bohr. + "bohr", + + // Kathleen Booth, she's credited with writing the first assembly language. https://en.wikipedia.org/wiki/Kathleen_Booth + "booth", + + // Anita Borg - Anita Borg was the founding director of the Institute for Women and Technology (IWT). https://en.wikipedia.org/wiki/Anita_Borg + "borg", + + // Satyendra Nath Bose - He provided the foundation for Bose–Einstein statistics and the theory of the Bose–Einstein condensate. - https://en.wikipedia.org/wiki/Satyendra_Nath_Bose + "bose", + + // Katherine Louise Bouman is an imaging scientist and Assistant Professor of Computer Science at the California Institute of Technology. She researches computational methods for imaging, and developed an algorithm that made possible the picture first visualization of a black hole using the Event Horizon Telescope. - https://en.wikipedia.org/wiki/Katie_Bouman + "bouman", + + // Evelyn Boyd Granville - She was one of the first African-American woman to receive a Ph.D. in mathematics; she earned it in 1949 from Yale University. https://en.wikipedia.org/wiki/Evelyn_Boyd_Granville + "boyd", + + // Brahmagupta - Ancient Indian mathematician during 598-670 CE who gave rules to compute with zero - https://en.wikipedia.org/wiki/Brahmagupta#Zero + "brahmagupta", + + // Walter Houser Brattain co-invented the transistor - https://en.wikipedia.org/wiki/Walter_Houser_Brattain + "brattain", + + // Emmett Brown invented time travel. https://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff) + "brown", + + // Linda Brown Buck - American biologist and Nobel laureate best known for her genetic and molecular analyses of the mechanisms of smell. https://en.wikipedia.org/wiki/Linda_B._Buck + "buck", + + // Dame Susan Jocelyn Bell Burnell - Northern Irish astrophysicist who discovered radio pulsars and was the first to analyse them. https://en.wikipedia.org/wiki/Jocelyn_Bell_Burnell + "burnell", + + // Annie Jump Cannon - pioneering female astronomer who classified hundreds of thousands of stars and created the system we use to understand stars today. https://en.wikipedia.org/wiki/Annie_Jump_Cannon + "cannon", + + // Rachel Carson - American marine biologist and conservationist, her book Silent Spring and other writings are credited with advancing the global environmental movement. https://en.wikipedia.org/wiki/Rachel_Carson + "carson", + + // Dame Mary Lucy Cartwright - British mathematician who was one of the first to study what is now known as chaos theory. Also known for Cartwright's theorem which finds applications in signal processing. https://en.wikipedia.org/wiki/Mary_Cartwright + "cartwright", + + // George Washington Carver - American agricultural scientist and inventor. He was the most prominent black scientist of the early 20th century. https://en.wikipedia.org/wiki/George_Washington_Carver + "carver", + + // Vinton Gray Cerf - American Internet pioneer, recognised as one of "the fathers of the Internet". With Robert Elliot Kahn, he designed TCP and IP, the primary data communication protocols of the Internet and other computer networks. https://en.wikipedia.org/wiki/Vint_Cerf + "cerf", + + // Subrahmanyan Chandrasekhar - Astrophysicist known for his mathematical theory on different stages and evolution in structures of the stars. He has won nobel prize for physics - https://en.wikipedia.org/wiki/Subrahmanyan_Chandrasekhar + "chandrasekhar", + + // Sergey Alexeyevich Chaplygin (Russian: Серге́й Алексе́евич Чаплы́гин; April 5, 1869 – October 8, 1942) was a Russian and Soviet physicist, mathematician, and mechanical engineer. He is known for mathematical formulas such as Chaplygin's equation and for a hypothetical substance in cosmology called Chaplygin gas, named after him. https://en.wikipedia.org/wiki/Sergey_Chaplygin + "chaplygin", + + // Émilie du Châtelet - French natural philosopher, mathematician, physicist, and author during the early 1730s, known for her translation of and commentary on Isaac Newton's book Principia containing basic laws of physics. https://en.wikipedia.org/wiki/%C3%89milie_du_Ch%C3%A2telet + "chatelet", + + // Asima Chatterjee was an Indian organic chemist noted for her research on vinca alkaloids, development of drugs for treatment of epilepsy and malaria - https://en.wikipedia.org/wiki/Asima_Chatterjee + "chatterjee", + + // David Lee Chaum - American computer scientist and cryptographer. Known for his seminal contributions in the field of anonymous communication. https://en.wikipedia.org/wiki/David_Chaum + "chaum", + + // Pafnuty Chebyshev - Russian mathematician. He is known fo his works on probability, statistics, mechanics, analytical geometry and number theory https://en.wikipedia.org/wiki/Pafnuty_Chebyshev + "chebyshev", + + // Joan Clarke - Bletchley Park code breaker during the Second World War who pioneered techniques that remained top secret for decades. Also an accomplished numismatist https://en.wikipedia.org/wiki/Joan_Clarke + "clarke", + + // Bram Cohen - American computer programmer and author of the BitTorrent peer-to-peer protocol. https://en.wikipedia.org/wiki/Bram_Cohen + "cohen", + + // Jane Colden - American botanist widely considered the first female American botanist - https://en.wikipedia.org/wiki/Jane_Colden + "colden", + + // Gerty Theresa Cori - American biochemist who became the third woman—and first American woman—to win a Nobel Prize in science, and the first woman to be awarded the Nobel Prize in Physiology or Medicine. Cori was born in Prague. https://en.wikipedia.org/wiki/Gerty_Cori + "cori", + + // Seymour Roger Cray was an American electrical engineer and supercomputer architect who designed a series of computers that were the fastest in the world for decades. https://en.wikipedia.org/wiki/Seymour_Cray + "cray", + + // Marie Curie discovered radioactivity. https://en.wikipedia.org/wiki/Marie_Curie. + "curie", + + // This entry reflects a husband and wife team who worked together: + // Joan Curran was a Welsh scientist who developed radar and invented chaff, a radar countermeasure. https://en.wikipedia.org/wiki/Joan_Curran + // Samuel Curran was an Irish physicist who worked alongside his wife during WWII and invented the proximity fuse. https://en.wikipedia.org/wiki/Samuel_Curran + "curran", + + // Charles Darwin established the principles of natural evolution. https://en.wikipedia.org/wiki/Charles_Darwin. + "darwin", + + // Leonardo Da Vinci invented too many things to list here. https://en.wikipedia.org/wiki/Leonardo_da_Vinci. + "davinci", + + // A. K. (Alexander Keewatin) Dewdney, Canadian mathematician, computer scientist, author and filmmaker. Contributor to Scientific American's "Computer Recreations" from 1984 to 1991. Author of Core War (program), The Planiverse, The Armchair Universe, The Magic Machine, The New Turing Omnibus, and more. https://en.wikipedia.org/wiki/Alexander_Dewdney + "dewdney", + + // Satish Dhawan - Indian mathematician and aerospace engineer, known for leading the successful and indigenous development of the Indian space programme. https://en.wikipedia.org/wiki/Satish_Dhawan + "dhawan", + + // Bailey Whitfield Diffie - American cryptographer and one of the pioneers of public-key cryptography. https://en.wikipedia.org/wiki/Whitfield_Diffie + "diffie", + + // Edsger Wybe Dijkstra was a Dutch computer scientist and mathematical scientist. https://en.wikipedia.org/wiki/Edsger_W._Dijkstra. + "dijkstra", + + // Paul Adrien Maurice Dirac - English theoretical physicist who made fundamental contributions to the early development of both quantum mechanics and quantum electrodynamics. https://en.wikipedia.org/wiki/Paul_Dirac + "dirac", + + // Agnes Meyer Driscoll - American cryptanalyst during World Wars I and II who successfully cryptanalysed a number of Japanese ciphers. She was also the co-developer of one of the cipher machines of the US Navy, the CM. https://en.wikipedia.org/wiki/Agnes_Meyer_Driscoll + "driscoll", + + // Donna Dubinsky - played an integral role in the development of personal digital assistants (PDAs) serving as CEO of Palm, Inc. and co-founding Handspring. https://en.wikipedia.org/wiki/Donna_Dubinsky + "dubinsky", + + // Annie Easley - She was a leading member of the team which developed software for the Centaur rocket stage and one of the first African-Americans in her field. https://en.wikipedia.org/wiki/Annie_Easley + "easley", + + // Thomas Alva Edison, prolific inventor https://en.wikipedia.org/wiki/Thomas_Edison + "edison", + + // Albert Einstein invented the general theory of relativity. https://en.wikipedia.org/wiki/Albert_Einstein + "einstein", + + // Alexandra Asanovna Elbakyan (Russian: Алекса́ндра Аса́новна Элбакя́н) is a Kazakhstani graduate student, computer programmer, internet pirate in hiding, and the creator of the site Sci-Hub. Nature has listed her in 2016 in the top ten people that mattered in science, and Ars Technica has compared her to Aaron Swartz. - https://en.wikipedia.org/wiki/Alexandra_Elbakyan + "elbakyan", + + // Taher A. ElGamal - Egyptian cryptographer best known for the ElGamal discrete log cryptosystem and the ElGamal digital signature scheme. https://en.wikipedia.org/wiki/Taher_Elgamal + "elgamal", + + // Gertrude Elion - American biochemist, pharmacologist and the 1988 recipient of the Nobel Prize in Medicine - https://en.wikipedia.org/wiki/Gertrude_Elion + "elion", + + // James Henry Ellis - British engineer and cryptographer employed by the GCHQ. Best known for conceiving for the first time, the idea of public-key cryptography. https://en.wikipedia.org/wiki/James_H._Ellis + "ellis", + + // Douglas Engelbart gave the mother of all demos: https://en.wikipedia.org/wiki/Douglas_Engelbart + "engelbart", + + // Euclid invented geometry. https://en.wikipedia.org/wiki/Euclid + "euclid", + + // Leonhard Euler invented large parts of modern mathematics. https://de.wikipedia.org/wiki/Leonhard_Euler + "euler", + + // Michael Faraday - British scientist who contributed to the study of electromagnetism and electrochemistry. https://en.wikipedia.org/wiki/Michael_Faraday + "faraday", + + // Horst Feistel - German-born American cryptographer who was one of the earliest non-government researchers to study the design and theory of block ciphers. Co-developer of DES and Lucifer. Feistel networks, a symmetric structure used in the construction of block ciphers are named after him. https://en.wikipedia.org/wiki/Horst_Feistel + "feistel", + + // Pierre de Fermat pioneered several aspects of modern mathematics. https://en.wikipedia.org/wiki/Pierre_de_Fermat + "fermat", + + // Enrico Fermi invented the first nuclear reactor. https://en.wikipedia.org/wiki/Enrico_Fermi. + "fermi", + + // Richard Feynman was a key contributor to quantum mechanics and particle physics. https://en.wikipedia.org/wiki/Richard_Feynman + "feynman", + + // Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod. + "franklin", + + // Yuri Alekseyevich Gagarin - Soviet pilot and cosmonaut, best known as the first human to journey into outer space. https://en.wikipedia.org/wiki/Yuri_Gagarin + "gagarin", + + // Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. https://en.wikipedia.org/wiki/Galileo_Galilei + "galileo", + + // Évariste Galois - French mathematician whose work laid the foundations of Galois theory and group theory, two major branches of abstract algebra, and the subfield of Galois connections, all while still in his late teens. https://en.wikipedia.org/wiki/%C3%89variste_Galois + "galois", + + // Kadambini Ganguly - Indian physician, known for being the first South Asian female physician, trained in western medicine, to graduate in South Asia. https://en.wikipedia.org/wiki/Kadambini_Ganguly + "ganguly", + + // William Henry "Bill" Gates III is an American business magnate, philanthropist, investor, computer programmer, and inventor. https://en.wikipedia.org/wiki/Bill_Gates + "gates", + + // Johann Carl Friedrich Gauss - German mathematician who made significant contributions to many fields, including number theory, algebra, statistics, analysis, differential geometry, geodesy, geophysics, mechanics, electrostatics, magnetic fields, astronomy, matrix theory, and optics. https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss + "gauss", + + // Marie-Sophie Germain - French mathematician, physicist and philosopher. Known for her work on elasticity theory, number theory and philosophy. https://en.wikipedia.org/wiki/Sophie_Germain + "germain", + + // Adele Goldberg, was one of the designers and developers of the Smalltalk language. https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist) + "goldberg", + + // Adele Goldstine, born Adele Katz, wrote the complete technical description for the first electronic digital computer, ENIAC. https://en.wikipedia.org/wiki/Adele_Goldstine + "goldstine", + + // Shafi Goldwasser is a computer scientist known for creating theoretical foundations of modern cryptography. Winner of 2012 ACM Turing Award. https://en.wikipedia.org/wiki/Shafi_Goldwasser + "goldwasser", + + // James Golick, all around gangster. + "golick", + + // Jane Goodall - British primatologist, ethologist, and anthropologist who is considered to be the world's foremost expert on chimpanzees - https://en.wikipedia.org/wiki/Jane_Goodall + "goodall", + + // Stephen Jay Gould was an American paleontologist, evolutionary biologist, and historian of science. He is most famous for the theory of punctuated equilibrium - https://en.wikipedia.org/wiki/Stephen_Jay_Gould + "gould", + + // Carolyn Widney Greider - American molecular biologist and joint winner of the 2009 Nobel Prize for Physiology or Medicine for the discovery of telomerase. https://en.wikipedia.org/wiki/Carol_W._Greider + "greider", + + // Alexander Grothendieck - German-born French mathematician who became a leading figure in the creation of modern algebraic geometry. https://en.wikipedia.org/wiki/Alexander_Grothendieck + "grothendieck", + + // Lois Haibt - American computer scientist, part of the team at IBM that developed FORTRAN - https://en.wikipedia.org/wiki/Lois_Haibt + "haibt", + + // Margaret Hamilton - Director of the Software Engineering Division of the MIT Instrumentation Laboratory, which developed on-board flight software for the Apollo space program. https://en.wikipedia.org/wiki/Margaret_Hamilton_(scientist) + "hamilton", + + // Caroline Harriet Haslett - English electrical engineer, electricity industry administrator and champion of women's rights. Co-author of British Standard 1363 that specifies AC power plugs and sockets used across the United Kingdom (which is widely considered as one of the safest designs). https://en.wikipedia.org/wiki/Caroline_Haslett + "haslett", + + // Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. https://en.wikipedia.org/wiki/Stephen_Hawking + "hawking", + + // Werner Heisenberg was a founding father of quantum mechanics. https://en.wikipedia.org/wiki/Werner_Heisenberg + "heisenberg", + + // Martin Edward Hellman - American cryptologist, best known for his invention of public-key cryptography in co-operation with Whitfield Diffie and Ralph Merkle. https://en.wikipedia.org/wiki/Martin_Hellman + "hellman", + + // Grete Hermann was a German philosopher noted for her philosophical work on the foundations of quantum mechanics. https://en.wikipedia.org/wiki/Grete_Hermann + "hermann", + + // Caroline Lucretia Herschel - German astronomer and discoverer of several comets. https://en.wikipedia.org/wiki/Caroline_Herschel + "herschel", + + // Heinrich Rudolf Hertz - German physicist who first conclusively proved the existence of the electromagnetic waves. https://en.wikipedia.org/wiki/Heinrich_Hertz + "hertz", + + // Jaroslav Heyrovský was the inventor of the polarographic method, father of the electroanalytical method, and recipient of the Nobel Prize in 1959. His main field of work was polarography. https://en.wikipedia.org/wiki/Jaroslav_Heyrovsk%C3%BD + "heyrovsky", + + // Dorothy Hodgkin was a British biochemist, credited with the development of protein crystallography. She was awarded the Nobel Prize in Chemistry in 1964. https://en.wikipedia.org/wiki/Dorothy_Hodgkin + "hodgkin", + + // Douglas R. Hofstadter is an American professor of cognitive science and author of the Pulitzer Prize and American Book Award-winning work Goedel, Escher, Bach: An Eternal Golden Braid in 1979. A mind-bending work which coined Hofstadter's Law: "It always takes longer than you expect, even when you take into account Hofstadter's Law." https://en.wikipedia.org/wiki/Douglas_Hofstadter + "hofstadter", + + // Erna Schneider Hoover revolutionized modern communication by inventing a computerized telephone switching method. https://en.wikipedia.org/wiki/Erna_Schneider_Hoover + "hoover", + + // Grace Hopper developed the first compiler for a computer programming language and is credited with popularizing the term "debugging" for fixing computer glitches. https://en.wikipedia.org/wiki/Grace_Hopper + "hopper", + + // Frances Hugle, she was an American scientist, engineer, and inventor who contributed to the understanding of semiconductors, integrated circuitry, and the unique electrical principles of microscopic materials. https://en.wikipedia.org/wiki/Frances_Hugle + "hugle", + + // Hypatia - Greek Alexandrine Neoplatonist philosopher in Egypt who was one of the earliest mothers of mathematics - https://en.wikipedia.org/wiki/Hypatia + "hypatia", + + // Teruko Ishizaka - Japanese scientist and immunologist who co-discovered the antibody class Immunoglobulin E. https://en.wikipedia.org/wiki/Teruko_Ishizaka + "ishizaka", + + // Mary Jackson, American mathematician and aerospace engineer who earned the highest title within NASA's engineering department - https://en.wikipedia.org/wiki/Mary_Jackson_(engineer) + "jackson", + + // Yeong-Sil Jang was a Korean scientist and astronomer during the Joseon Dynasty; he invented the first metal printing press and water gauge. https://en.wikipedia.org/wiki/Jang_Yeong-sil + "jang", + + // Mae Carol Jemison - is an American engineer, physician, and former NASA astronaut. She became the first black woman to travel in space when she served as a mission specialist aboard the Space Shuttle Endeavour - https://en.wikipedia.org/wiki/Mae_Jemison + "jemison", + + // Betty Jennings - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Jean_Bartik + "jennings", + + // Mary Lou Jepsen, was the founder and chief technology officer of One Laptop Per Child (OLPC), and the founder of Pixel Qi. https://en.wikipedia.org/wiki/Mary_Lou_Jepsen + "jepsen", + + // Katherine Coleman Goble Johnson - American physicist and mathematician contributed to the NASA. https://en.wikipedia.org/wiki/Katherine_Johnson + "johnson", + + // Irène Joliot-Curie - French scientist who was awarded the Nobel Prize for Chemistry in 1935. Daughter of Marie and Pierre Curie. https://en.wikipedia.org/wiki/Ir%C3%A8ne_Joliot-Curie + "joliot", + + // Karen Spärck Jones came up with the concept of inverse document frequency, which is used in most search engines today. https://en.wikipedia.org/wiki/Karen_Sp%C3%A4rck_Jones + "jones", + + // A. P. J. Abdul Kalam - is an Indian scientist aka Missile Man of India for his work on the development of ballistic missile and launch vehicle technology - https://en.wikipedia.org/wiki/A._P._J._Abdul_Kalam + "kalam", + + // Sergey Petrovich Kapitsa (Russian: Серге́й Петро́вич Капи́ца; 14 February 1928 – 14 August 2012) was a Russian physicist and demographer. He was best known as host of the popular and long-running Russian scientific TV show, Evident, but Incredible. His father was the Nobel laureate Soviet-era physicist Pyotr Kapitsa, and his brother was the geographer and Antarctic explorer Andrey Kapitsa. - https://en.wikipedia.org/wiki/Sergey_Kapitsa + "kapitsa", + + // Susan Kare, created the icons and many of the interface elements for the original Apple Macintosh in the 1980s, and was an original employee of NeXT, working as the Creative Director. https://en.wikipedia.org/wiki/Susan_Kare + "kare", + + // Mstislav Keldysh - a Soviet scientist in the field of mathematics and mechanics, academician of the USSR Academy of Sciences (1946), President of the USSR Academy of Sciences (1961–1975), three times Hero of Socialist Labor (1956, 1961, 1971), fellow of the Royal Society of Edinburgh (1968). https://en.wikipedia.org/wiki/Mstislav_Keldysh + "keldysh", + + // Mary Kenneth Keller, Sister Mary Kenneth Keller became the first American woman to earn a PhD in Computer Science in 1965. https://en.wikipedia.org/wiki/Mary_Kenneth_Keller + "keller", + + // Johannes Kepler, German astronomer known for his three laws of planetary motion - https://en.wikipedia.org/wiki/Johannes_Kepler + "kepler", + + // Omar Khayyam - Persian mathematician, astronomer and poet. Known for his work on the classification and solution of cubic equations, for his contribution to the understanding of Euclid's fifth postulate and for computing the length of a year very accurately. https://en.wikipedia.org/wiki/Omar_Khayyam + "khayyam", + + // Har Gobind Khorana - Indian-American biochemist who shared the 1968 Nobel Prize for Physiology - https://en.wikipedia.org/wiki/Har_Gobind_Khorana + "khorana", + + // Jack Kilby invented silicon integrated circuits and gave Silicon Valley its name. - https://en.wikipedia.org/wiki/Jack_Kilby + "kilby", + + // Maria Kirch - German astronomer and first woman to discover a comet - https://en.wikipedia.org/wiki/Maria_Margarethe_Kirch + "kirch", + + // Donald Knuth - American computer scientist, author of "The Art of Computer Programming" and creator of the TeX typesetting system. https://en.wikipedia.org/wiki/Donald_Knuth + "knuth", + + // Sophie Kowalevski - Russian mathematician responsible for important original contributions to analysis, differential equations and mechanics - https://en.wikipedia.org/wiki/Sofia_Kovalevskaya + "kowalevski", + + // Marie-Jeanne de Lalande - French astronomer, mathematician and cataloguer of stars - https://en.wikipedia.org/wiki/Marie-Jeanne_de_Lalande + "lalande", + + // Hedy Lamarr - Actress and inventor. The principles of her work are now incorporated into modern Wi-Fi, CDMA and Bluetooth technology. https://en.wikipedia.org/wiki/Hedy_Lamarr + "lamarr", + + // Leslie B. Lamport - American computer scientist. Lamport is best known for his seminal work in distributed systems and was the winner of the 2013 Turing Award. https://en.wikipedia.org/wiki/Leslie_Lamport + "lamport", + + // Mary Leakey - British paleoanthropologist who discovered the first fossilized Proconsul skull - https://en.wikipedia.org/wiki/Mary_Leakey + "leakey", + + // Henrietta Swan Leavitt - she was an American astronomer who discovered the relation between the luminosity and the period of Cepheid variable stars. https://en.wikipedia.org/wiki/Henrietta_Swan_Leavitt + "leavitt", + + // Esther Miriam Zimmer Lederberg - American microbiologist and a pioneer of bacterial genetics. https://en.wikipedia.org/wiki/Esther_Lederberg + "lederberg", + + // Inge Lehmann - Danish seismologist and geophysicist. Known for discovering in 1936 that the Earth has a solid inner core inside a molten outer core. https://en.wikipedia.org/wiki/Inge_Lehmann + "lehmann", + + // Daniel Lewin - Mathematician, Akamai co-founder, soldier, 9/11 victim-- Developed optimization techniques for routing traffic on the internet. Died attempting to stop the 9-11 hijackers. https://en.wikipedia.org/wiki/Daniel_Lewin + "lewin", + + // Ruth Lichterman - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Ruth_Teitelbaum + "lichterman", + + // Barbara Liskov - co-developed the Liskov substitution principle. Liskov was also the winner of the Turing Prize in 2008. - https://en.wikipedia.org/wiki/Barbara_Liskov + "liskov", + + // Ada Lovelace invented the first algorithm. https://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull) + "lovelace", + + // Auguste and Louis Lumière - the first filmmakers in history - https://en.wikipedia.org/wiki/Auguste_and_Louis_Lumi%C3%A8re + "lumiere", + + // Mahavira - Ancient Indian mathematician during 9th century AD who discovered basic algebraic identities - https://en.wikipedia.org/wiki/Mah%C4%81v%C4%ABra_(mathematician) + "mahavira", + + // Lynn Margulis (b. Lynn Petra Alexander) - an American evolutionary theorist and biologist, science author, educator, and popularizer, and was the primary modern proponent for the significance of symbiosis in evolution. - https://en.wikipedia.org/wiki/Lynn_Margulis + "margulis", + + // Yukihiro Matsumoto - Japanese computer scientist and software programmer best known as the chief designer of the Ruby programming language. https://en.wikipedia.org/wiki/Yukihiro_Matsumoto + "matsumoto", + + // James Clerk Maxwell - Scottish physicist, best known for his formulation of electromagnetic theory. https://en.wikipedia.org/wiki/James_Clerk_Maxwell + "maxwell", + + // Maria Mayer - American theoretical physicist and Nobel laureate in Physics for proposing the nuclear shell model of the atomic nucleus - https://en.wikipedia.org/wiki/Maria_Mayer + "mayer", + + // John McCarthy invented LISP: https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist) + "mccarthy", + + // Barbara McClintock - a distinguished American cytogeneticist, 1983 Nobel Laureate in Physiology or Medicine for discovering transposons. https://en.wikipedia.org/wiki/Barbara_McClintock + "mcclintock", + + // Anne Laura Dorinthea McLaren - British developmental biologist whose work helped lead to human in-vitro fertilisation. https://en.wikipedia.org/wiki/Anne_McLaren + "mclaren", + + // Malcolm McLean invented the modern shipping container: https://en.wikipedia.org/wiki/Malcom_McLean + "mclean", + + // Kay McNulty - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli + "mcnulty", + + // Lise Meitner - Austrian/Swedish physicist who was involved in the discovery of nuclear fission. The element meitnerium is named after her - https://en.wikipedia.org/wiki/Lise_Meitner + "meitner", + + // Gregor Johann Mendel - Czech scientist and founder of genetics. https://en.wikipedia.org/wiki/Gregor_Mendel + "mendel", + + // Dmitri Mendeleev - a chemist and inventor. He formulated the Periodic Law, created a farsighted version of the periodic table of elements, and used it to correct the properties of some already discovered elements and also to predict the properties of eight elements yet to be discovered. https://en.wikipedia.org/wiki/Dmitri_Mendeleev + "mendeleev", + + // Carla Meninsky, was the game designer and programmer for Atari 2600 games Dodge 'Em and Warlords. https://en.wikipedia.org/wiki/Carla_Meninsky + "meninsky", + + // Ralph C. Merkle - American computer scientist, known for devising Merkle's puzzles - one of the very first schemes for public-key cryptography. Also, inventor of Merkle trees and co-inventor of the Merkle-Damgård construction for building collision-resistant cryptographic hash functions and the Merkle-Hellman knapsack cryptosystem. https://en.wikipedia.org/wiki/Ralph_Merkle + "merkle", + + // Johanna Mestorf - German prehistoric archaeologist and first female museum director in Germany - https://en.wikipedia.org/wiki/Johanna_Mestorf + "mestorf", + + // Maryam Mirzakhani - an Iranian mathematician and the first woman to win the Fields Medal. https://en.wikipedia.org/wiki/Maryam_Mirzakhani + "mirzakhani", + + // Rita Levi-Montalcini - Won Nobel Prize in Physiology or Medicine jointly with colleague Stanley Cohen for the discovery of nerve growth factor (https://en.wikipedia.org/wiki/Rita_Levi-Montalcini) + "montalcini", + + // Gordon Earle Moore - American engineer, Silicon Valley founding father, author of Moore's law. https://en.wikipedia.org/wiki/Gordon_Moore + "moore", + + // Samuel Morse - contributed to the invention of a single-wire telegraph system based on European telegraphs and was a co-developer of the Morse code - https://en.wikipedia.org/wiki/Samuel_Morse + "morse", + + // May-Britt Moser - Nobel prize winner neuroscientist who contributed to the discovery of grid cells in the brain. https://en.wikipedia.org/wiki/May-Britt_Moser + "moser", + + // Ian Murdock - founder of the Debian project - https://en.wikipedia.org/wiki/Ian_Murdock + "murdock", + + // John Napier of Merchiston - Scottish landowner known as an astronomer, mathematician and physicist. Best known for his discovery of logarithms. https://en.wikipedia.org/wiki/John_Napier + "napier", + + // John Forbes Nash, Jr. - American mathematician who made fundamental contributions to game theory, differential geometry, and the study of partial differential equations. https://en.wikipedia.org/wiki/John_Forbes_Nash_Jr. + "nash", + + // John von Neumann - todays computer architectures are based on the von Neumann architecture. https://en.wikipedia.org/wiki/Von_Neumann_architecture + "neumann", + + // Isaac Newton invented classic mechanics and modern optics. https://en.wikipedia.org/wiki/Isaac_Newton + "newton", + + // Florence Nightingale, more prominently known as a nurse, was also the first female member of the Royal Statistical Society and a pioneer in statistical graphics https://en.wikipedia.org/wiki/Florence_Nightingale#Statistics_and_sanitary_reform + "nightingale", + + // Alfred Nobel - a Swedish chemist, engineer, innovator, and armaments manufacturer (inventor of dynamite) - https://en.wikipedia.org/wiki/Alfred_Nobel + "nobel", + + // Emmy Noether, German mathematician. Noether's Theorem is named after her. https://en.wikipedia.org/wiki/Emmy_Noether + "noether", + + // Poppy Northcutt. Poppy Northcutt was the first woman to work as part of NASA’s Mission Control. http://www.businessinsider.com/poppy-northcutt-helped-apollo-astronauts-2014-12?op=1 + "northcutt", + + // Robert Noyce invented silicon integrated circuits and gave Silicon Valley its name. - https://en.wikipedia.org/wiki/Robert_Noyce + "noyce", + + // Panini - Ancient Indian linguist and grammarian from 4th century CE who worked on the world's first formal system - https://en.wikipedia.org/wiki/P%C4%81%E1%B9%87ini#Comparison_with_modern_formal_systems + "panini", + + // Ambroise Pare invented modern surgery. https://en.wikipedia.org/wiki/Ambroise_Par%C3%A9 + "pare", + + // Blaise Pascal, French mathematician, physicist, and inventor - https://en.wikipedia.org/wiki/Blaise_Pascal + "pascal", + + // Louis Pasteur discovered vaccination, fermentation and pasteurization. https://en.wikipedia.org/wiki/Louis_Pasteur. + "pasteur", + + // Cecilia Payne-Gaposchkin was an astronomer and astrophysicist who, in 1925, proposed in her Ph.D. thesis an explanation for the composition of stars in terms of the relative abundances of hydrogen and helium. https://en.wikipedia.org/wiki/Cecilia_Payne-Gaposchkin + "payne", + + // Radia Perlman is a software designer and network engineer and most famous for her invention of the spanning-tree protocol (STP). https://en.wikipedia.org/wiki/Radia_Perlman + "perlman", + + // Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language. https://en.wikipedia.org/wiki/Rob_Pike + "pike", + + // Henri Poincaré made fundamental contributions in several fields of mathematics. https://en.wikipedia.org/wiki/Henri_Poincar%C3%A9 + "poincare", + + // Laura Poitras is a director and producer whose work, made possible by open source crypto tools, advances the causes of truth and freedom of information by reporting disclosures by whistleblowers such as Edward Snowden. https://en.wikipedia.org/wiki/Laura_Poitras + "poitras", + + // Tat’yana Avenirovna Proskuriakova (Russian: Татья́на Авени́ровна Проскуряко́ва) (January 23 [O.S. January 10] 1909 – August 30, 1985) was a Russian-American Mayanist scholar and archaeologist who contributed significantly to the deciphering of Maya hieroglyphs, the writing system of the pre-Columbian Maya civilization of Mesoamerica. https://en.wikipedia.org/wiki/Tatiana_Proskouriakoff + "proskuriakova", + + // Claudius Ptolemy - a Greco-Egyptian writer of Alexandria, known as a mathematician, astronomer, geographer, astrologer, and poet of a single epigram in the Greek Anthology - https://en.wikipedia.org/wiki/Ptolemy + "ptolemy", + + // C. V. Raman - Indian physicist who won the Nobel Prize in 1930 for proposing the Raman effect. - https://en.wikipedia.org/wiki/C._V._Raman + "raman", + + // Srinivasa Ramanujan - Indian mathematician and autodidact who made extraordinary contributions to mathematical analysis, number theory, infinite series, and continued fractions. - https://en.wikipedia.org/wiki/Srinivasa_Ramanujan + "ramanujan", + + // Ida Rhodes - American pioneer in computer programming, designed the first computer used for Social Security. https://en.wikipedia.org/wiki/Ida_Rhodes + "rhodes", + + // Sally Kristen Ride was an American physicist and astronaut. She was the first American woman in space, and the youngest American astronaut. https://en.wikipedia.org/wiki/Sally_Ride + "ride", + + // Dennis Ritchie - co-creator of UNIX and the C programming language. - https://en.wikipedia.org/wiki/Dennis_Ritchie + "ritchie", + + // Julia Hall Bowman Robinson - American mathematician renowned for her contributions to the fields of computability theory and computational complexity theory. https://en.wikipedia.org/wiki/Julia_Robinson + "robinson", + + // Wilhelm Conrad Röntgen - German physicist who was awarded the first Nobel Prize in Physics in 1901 for the discovery of X-rays (Röntgen rays). https://en.wikipedia.org/wiki/Wilhelm_R%C3%B6ntgen + "roentgen", + + // Rosalind Franklin - British biophysicist and X-ray crystallographer whose research was critical to the understanding of DNA - https://en.wikipedia.org/wiki/Rosalind_Franklin + "rosalind", + + // Vera Rubin - American astronomer who pioneered work on galaxy rotation rates. https://en.wikipedia.org/wiki/Vera_Rubin + "rubin", + + // Meghnad Saha - Indian astrophysicist best known for his development of the Saha equation, used to describe chemical and physical conditions in stars - https://en.wikipedia.org/wiki/Meghnad_Saha + "saha", + + // Jean E. Sammet developed FORMAC, the first widely used computer language for symbolic manipulation of mathematical formulas. https://en.wikipedia.org/wiki/Jean_E._Sammet + "sammet", + + // Mildred Sanderson - American mathematician best known for Sanderson's theorem concerning modular invariants. https://en.wikipedia.org/wiki/Mildred_Sanderson + "sanderson", + + // Satoshi Nakamoto is the name used by the unknown person or group of people who developed bitcoin, authored the bitcoin white paper, and created and deployed bitcoin's original reference implementation. https://en.wikipedia.org/wiki/Satoshi_Nakamoto + "satoshi", + + // Adi Shamir - Israeli cryptographer whose numerous inventions and contributions to cryptography include the Ferge Fiat Shamir identification scheme, the Rivest Shamir Adleman (RSA) public-key cryptosystem, the Shamir's secret sharing scheme, the breaking of the Merkle-Hellman cryptosystem, the TWINKLE and TWIRL factoring devices and the discovery of differential cryptanalysis (with Eli Biham). https://en.wikipedia.org/wiki/Adi_Shamir + "shamir", + + // Claude Shannon - The father of information theory and founder of digital circuit design theory. (https://en.wikipedia.org/wiki/Claude_Shannon) + "shannon", + + // Carol Shaw - Originally an Atari employee, Carol Shaw is said to be the first female video game designer. https://en.wikipedia.org/wiki/Carol_Shaw_(video_game_designer) + "shaw", + + // Dame Stephanie "Steve" Shirley - Founded a software company in 1962 employing women working from home. https://en.wikipedia.org/wiki/Steve_Shirley + "shirley", + + // William Shockley co-invented the transistor - https://en.wikipedia.org/wiki/William_Shockley + "shockley", + + // Lina Solomonovna Stern (or Shtern; Russian: Лина Соломоновна Штерн; 26 August 1878 – 7 March 1968) was a Soviet biochemist, physiologist and humanist whose medical discoveries saved thousands of lives at the fronts of World War II. She is best known for her pioneering work on blood–brain barrier, which she described as hemato-encephalic barrier in 1921. https://en.wikipedia.org/wiki/Lina_Stern + "shtern", + + // Françoise Barré-Sinoussi - French virologist and Nobel Prize Laureate in Physiology or Medicine; her work was fundamental in identifying HIV as the cause of AIDS. https://en.wikipedia.org/wiki/Fran%C3%A7oise_Barr%C3%A9-Sinoussi + "sinoussi", + + // Betty Snyder - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Betty_Holberton + "snyder", + + // Cynthia Solomon - Pioneer in the fields of artificial intelligence, computer science and educational computing. Known for creation of Logo, an educational programming language. https://en.wikipedia.org/wiki/Cynthia_Solomon + "solomon", + + // Frances Spence - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Frances_Spence + "spence", + + // Michael Stonebraker is a database research pioneer and architect of Ingres, Postgres, VoltDB and SciDB. Winner of 2014 ACM Turing Award. https://en.wikipedia.org/wiki/Michael_Stonebraker + "stonebraker", + + // Ivan Edward Sutherland - American computer scientist and Internet pioneer, widely regarded as the father of computer graphics. https://en.wikipedia.org/wiki/Ivan_Sutherland + "sutherland", + + // Janese Swanson (with others) developed the first of the Carmen Sandiego games. She went on to found Girl Tech. https://en.wikipedia.org/wiki/Janese_Swanson + "swanson", + + // Aaron Swartz was influential in creating RSS, Markdown, Creative Commons, Reddit, and much of the internet as we know it today. He was devoted to freedom of information on the web. https://en.wikiquote.org/wiki/Aaron_Swartz + "swartz", + + // Bertha Swirles was a theoretical physicist who made a number of contributions to early quantum theory. https://en.wikipedia.org/wiki/Bertha_Swirles + "swirles", + + // Helen Brooke Taussig - American cardiologist and founder of the field of paediatric cardiology. https://en.wikipedia.org/wiki/Helen_B._Taussig + "taussig", + + // Nikola Tesla invented the AC electric system and every gadget ever used by a James Bond villain. https://en.wikipedia.org/wiki/Nikola_Tesla + "tesla", + + // Marie Tharp - American geologist and oceanic cartographer who co-created the first scientific map of the Atlantic Ocean floor. Her work led to the acceptance of the theories of plate tectonics and continental drift. https://en.wikipedia.org/wiki/Marie_Tharp + "tharp", + + // Ken Thompson - co-creator of UNIX and the C programming language - https://en.wikipedia.org/wiki/Ken_Thompson + "thompson", + + // Linus Torvalds invented Linux and Git. https://en.wikipedia.org/wiki/Linus_Torvalds + "torvalds", + + // Youyou Tu - Chinese pharmaceutical chemist and educator known for discovering artemisinin and dihydroartemisinin, used to treat malaria, which has saved millions of lives. Joint winner of the 2015 Nobel Prize in Physiology or Medicine. https://en.wikipedia.org/wiki/Tu_Youyou + "tu", + + // Alan Turing was a founding father of computer science. https://en.wikipedia.org/wiki/Alan_Turing. + "turing", + + // Varahamihira - Ancient Indian mathematician who discovered trigonometric formulae during 505-587 CE - https://en.wikipedia.org/wiki/Var%C4%81hamihira#Contributions + "varahamihira", + + // Dorothy Vaughan was a NASA mathematician and computer programmer on the SCOUT launch vehicle program that put America's first satellites into space - https://en.wikipedia.org/wiki/Dorothy_Vaughan + "vaughan", + + // Cédric Villani - French mathematician, won Fields Medal, Fermat Prize and Poincaré Price for his work in differential geometry and statistical mechanics. https://en.wikipedia.org/wiki/C%C3%A9dric_Villani + "villani", + + // Sir Mokshagundam Visvesvaraya - is a notable Indian engineer. He is a recipient of the Indian Republic's highest honour, the Bharat Ratna, in 1955. On his birthday, 15 September is celebrated as Engineer's Day in India in his memory - https://en.wikipedia.org/wiki/Visvesvaraya + "visvesvaraya", + + // Christiane Nüsslein-Volhard - German biologist, won Nobel Prize in Physiology or Medicine in 1995 for research on the genetic control of embryonic development. https://en.wikipedia.org/wiki/Christiane_N%C3%BCsslein-Volhard + "volhard", + + // Marlyn Wescoff - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Marlyn_Meltzer + "wescoff", + + // Sylvia B. Wilbur - British computer scientist who helped develop the ARPANET, was one of the first to exchange email in the UK and a leading researcher in computer-supported collaborative work. https://en.wikipedia.org/wiki/Sylvia_Wilbur + "wilbur", + + // Andrew Wiles - Notable British mathematician who proved the enigmatic Fermat's Last Theorem - https://en.wikipedia.org/wiki/Andrew_Wiles + "wiles", + + // Roberta Williams, did pioneering work in graphical adventure games for personal computers, particularly the King's Quest series. https://en.wikipedia.org/wiki/Roberta_Williams + "williams", + + // Malcolm John Williamson - British mathematician and cryptographer employed by the GCHQ. Developed in 1974 what is now known as Diffie-Hellman key exchange (Diffie and Hellman first published the scheme in 1976). https://en.wikipedia.org/wiki/Malcolm_J._Williamson + "williamson", + + // Sophie Wilson designed the first Acorn Micro-Computer and the instruction set for ARM processors. https://en.wikipedia.org/wiki/Sophie_Wilson + "wilson", + + // Jeannette Wing - co-developed the Liskov substitution principle. - https://en.wikipedia.org/wiki/Jeannette_Wing + "wing", + + // Steve Wozniak invented the Apple I and Apple II. https://en.wikipedia.org/wiki/Steve_Wozniak + "wozniak", + + // The Wright brothers, Orville and Wilbur - credited with inventing and building the world's first successful airplane and making the first controlled, powered and sustained heavier-than-air human flight - https://en.wikipedia.org/wiki/Wright_brothers + "wright", + + // Chien-Shiung Wu - Chinese-American experimental physicist who made significant contributions to nuclear physics. https://en.wikipedia.org/wiki/Chien-Shiung_Wu + "wu", + + // Rosalyn Sussman Yalow - Rosalyn Sussman Yalow was an American medical physicist, and a co-winner of the 1977 Nobel Prize in Physiology or Medicine for development of the radioimmunoassay technique. https://en.wikipedia.org/wiki/Rosalyn_Sussman_Yalow + "yalow", + + // Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the sciences. https://en.wikipedia.org/wiki/Ada_Yonath + "yonath", + + // Nikolay Yegorovich Zhukovsky (Russian: Никола́й Его́рович Жуко́вский, January 17 1847 – March 17, 1921) was a Russian scientist, mathematician and engineer, and a founding father of modern aero- and hydrodynamics. Whereas contemporary scientists scoffed at the idea of human flight, Zhukovsky was the first to undertake the study of airflow. He is often called the Father of Russian Aviation. https://en.wikipedia.org/wiki/Nikolay_Yegorovich_Zhukovsky + "zhukovsky", + } +) + +// GetRandomName generates a random name from the list of adjectives and surnames in this package +// formatted as "adjective_surname". For example 'focused_turing'. If retry is non-zero, a random +// integer between 0 and 10 will be added to the end of the name, e.g `focused_turing3` +func GetRandomName(retry int) string { +begin: + name := left[rand.Intn(len(left))] + "_" + right[rand.Intn(len(right))] //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) + if name == "boring_wozniak" /* Steve Wozniak is not boring */ { + goto begin + } + + if retry > 0 { + name += strconv.Itoa(rand.Intn(10)) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) + } + return name +} diff --git a/vendor/github.com/docker/docker/pkg/progress/progress.go b/vendor/github.com/docker/docker/pkg/progress/progress.go new file mode 100644 index 00000000..3f9887c5 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/progress/progress.go @@ -0,0 +1,93 @@ +package progress + +import ( + "fmt" +) + +// Progress represents the progress of a transfer. +type Progress struct { + ID string + + // Progress contains a Message or... + Message string + + // ...progress of an action + Action string + Current int64 + Total int64 + + // If true, don't show xB/yB + HideCounts bool + // If not empty, use units instead of bytes for counts + Units string + + // Aux contains extra information not presented to the user, such as + // digests for push signing. + Aux interface{} + + LastUpdate bool +} + +// Output is an interface for writing progress information. It's +// like a writer for progress, but we don't call it Writer because +// that would be confusing next to ProgressReader (also, because it +// doesn't implement the io.Writer interface). +type Output interface { + WriteProgress(Progress) error +} + +type chanOutput chan<- Progress + +func (out chanOutput) WriteProgress(p Progress) error { + // FIXME: workaround for panic in #37735 + defer func() { + recover() + }() + out <- p + return nil +} + +// ChanOutput returns an Output that writes progress updates to the +// supplied channel. +func ChanOutput(progressChan chan<- Progress) Output { + return chanOutput(progressChan) +} + +type discardOutput struct{} + +func (discardOutput) WriteProgress(Progress) error { + return nil +} + +// DiscardOutput returns an Output that discards progress +func DiscardOutput() Output { + return discardOutput{} +} + +// Update is a convenience function to write a progress update to the channel. +func Update(out Output, id, action string) { + out.WriteProgress(Progress{ID: id, Action: action}) +} + +// Updatef is a convenience function to write a printf-formatted progress update +// to the channel. +func Updatef(out Output, id, format string, a ...interface{}) { + Update(out, id, fmt.Sprintf(format, a...)) +} + +// Message is a convenience function to write a progress message to the channel. +func Message(out Output, id, message string) { + out.WriteProgress(Progress{ID: id, Message: message}) +} + +// Messagef is a convenience function to write a printf-formatted progress +// message to the channel. +func Messagef(out Output, id, format string, a ...interface{}) { + Message(out, id, fmt.Sprintf(format, a...)) +} + +// Aux sends auxiliary information over a progress interface, which will not be +// formatted for the UI. This is used for things such as push signing. +func Aux(out Output, a interface{}) { + out.WriteProgress(Progress{Aux: a}) +} diff --git a/vendor/github.com/docker/docker/pkg/progress/progressreader.go b/vendor/github.com/docker/docker/pkg/progress/progressreader.go new file mode 100644 index 00000000..7a2954ec --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/progress/progressreader.go @@ -0,0 +1,66 @@ +package progress + +import ( + "io" + "time" + + "golang.org/x/time/rate" +) + +// Reader is a Reader with progress bar. +type Reader struct { + in io.ReadCloser // Stream to read from + out Output // Where to send progress bar to + size int64 + current int64 + lastUpdate int64 + id string + action string + rateLimiter *rate.Limiter +} + +// NewProgressReader creates a new ProgressReader. +func NewProgressReader(in io.ReadCloser, out Output, size int64, id, action string) *Reader { + return &Reader{ + in: in, + out: out, + size: size, + id: id, + action: action, + rateLimiter: rate.NewLimiter(rate.Every(100*time.Millisecond), 1), + } +} + +func (p *Reader) Read(buf []byte) (int, error) { + read, err := p.in.Read(buf) + p.current += int64(read) + updateEvery := int64(1024 * 512) // 512kB + if p.size > 0 { + // Update progress for every 1% read if 1% < 512kB + if increment := int64(0.01 * float64(p.size)); increment < updateEvery { + updateEvery = increment + } + } + if p.current-p.lastUpdate > updateEvery || err != nil { + p.updateProgress(err != nil && read == 0) + p.lastUpdate = p.current + } + + return read, err +} + +// Close closes the progress reader and its underlying reader. +func (p *Reader) Close() error { + if p.current < p.size { + // print a full progress bar when closing prematurely + p.current = p.size + p.updateProgress(false) + } + return p.in.Close() +} + +func (p *Reader) updateProgress(last bool) { + if last || p.current == p.size || p.rateLimiter.Allow() { + p.out.WriteProgress(Progress{ID: p.id, Action: p.action, Current: p.current, Total: p.size, LastUpdate: last}) + } +} diff --git a/vendor/github.com/docker/docker/pkg/streamformatter/streamformatter.go b/vendor/github.com/docker/docker/pkg/streamformatter/streamformatter.go new file mode 100644 index 00000000..24e1b45f --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/streamformatter/streamformatter.go @@ -0,0 +1,164 @@ +// Package streamformatter provides helper functions to format a stream. +package streamformatter + +import ( + "encoding/json" + "fmt" + "io" + "sync" + + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/progress" +) + +const streamNewline = "\r\n" + +type jsonProgressFormatter struct{} + +func appendNewline(source []byte) []byte { + return append(source, []byte(streamNewline)...) +} + +// FormatStatus formats the specified objects according to the specified format (and id). +func FormatStatus(id, format string, a ...interface{}) []byte { + str := fmt.Sprintf(format, a...) + b, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Status: str}) + if err != nil { + return FormatError(err) + } + return appendNewline(b) +} + +// FormatError formats the error as a JSON object +func FormatError(err error) []byte { + jsonError, ok := err.(*jsonmessage.JSONError) + if !ok { + jsonError = &jsonmessage.JSONError{Message: err.Error()} + } + if b, err := json.Marshal(&jsonmessage.JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil { + return appendNewline(b) + } + return []byte(`{"error":"format error"}` + streamNewline) +} + +func (sf *jsonProgressFormatter) formatStatus(id, format string, a ...interface{}) []byte { + return FormatStatus(id, format, a...) +} + +// formatProgress formats the progress information for a specified action. +func (sf *jsonProgressFormatter) formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte { + if progress == nil { + progress = &jsonmessage.JSONProgress{} + } + var auxJSON *json.RawMessage + if aux != nil { + auxJSONBytes, err := json.Marshal(aux) + if err != nil { + return nil + } + auxJSON = new(json.RawMessage) + *auxJSON = auxJSONBytes + } + b, err := json.Marshal(&jsonmessage.JSONMessage{ + Status: action, + ProgressMessage: progress.String(), + Progress: progress, + ID: id, + Aux: auxJSON, + }) + if err != nil { + return nil + } + return appendNewline(b) +} + +type rawProgressFormatter struct{} + +func (sf *rawProgressFormatter) formatStatus(id, format string, a ...interface{}) []byte { + return []byte(fmt.Sprintf(format, a...) + streamNewline) +} + +func (sf *rawProgressFormatter) formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte { + if progress == nil { + progress = &jsonmessage.JSONProgress{} + } + endl := "\r" + if progress.String() == "" { + endl += "\n" + } + return []byte(action + " " + progress.String() + endl) +} + +// NewProgressOutput returns a progress.Output object that can be passed to +// progress.NewProgressReader. +func NewProgressOutput(out io.Writer) progress.Output { + return &progressOutput{sf: &rawProgressFormatter{}, out: out, newLines: true} +} + +// NewJSONProgressOutput returns a progress.Output that formats output +// using JSON objects +func NewJSONProgressOutput(out io.Writer, newLines bool) progress.Output { + return &progressOutput{sf: &jsonProgressFormatter{}, out: out, newLines: newLines} +} + +type formatProgress interface { + formatStatus(id, format string, a ...interface{}) []byte + formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte +} + +type progressOutput struct { + sf formatProgress + out io.Writer + newLines bool + mu sync.Mutex +} + +// WriteProgress formats progress information from a ProgressReader. +func (out *progressOutput) WriteProgress(prog progress.Progress) error { + var formatted []byte + if prog.Message != "" { + formatted = out.sf.formatStatus(prog.ID, prog.Message) + } else { + jsonProgress := jsonmessage.JSONProgress{Current: prog.Current, Total: prog.Total, HideCounts: prog.HideCounts, Units: prog.Units} + formatted = out.sf.formatProgress(prog.ID, prog.Action, &jsonProgress, prog.Aux) + } + + out.mu.Lock() + defer out.mu.Unlock() + _, err := out.out.Write(formatted) + if err != nil { + return err + } + + if out.newLines && prog.LastUpdate { + _, err = out.out.Write(out.sf.formatStatus("", "")) + return err + } + + return nil +} + +// AuxFormatter is a streamFormatter that writes aux progress messages +type AuxFormatter struct { + io.Writer +} + +// Emit emits the given interface as an aux progress message +func (sf *AuxFormatter) Emit(id string, aux interface{}) error { + auxJSONBytes, err := json.Marshal(aux) + if err != nil { + return err + } + auxJSON := new(json.RawMessage) + *auxJSON = auxJSONBytes + msgJSON, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Aux: auxJSON}) + if err != nil { + return err + } + msgJSON = appendNewline(msgJSON) + n, err := sf.Writer.Write(msgJSON) + if n != len(msgJSON) { + return io.ErrShortWrite + } + return err +} diff --git a/vendor/github.com/docker/docker/pkg/streamformatter/streamwriter.go b/vendor/github.com/docker/docker/pkg/streamformatter/streamwriter.go new file mode 100644 index 00000000..141d12e2 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/streamformatter/streamwriter.go @@ -0,0 +1,47 @@ +package streamformatter + +import ( + "encoding/json" + "io" + + "github.com/docker/docker/pkg/jsonmessage" +) + +type streamWriter struct { + io.Writer + lineFormat func([]byte) string +} + +func (sw *streamWriter) Write(buf []byte) (int, error) { + formattedBuf := sw.format(buf) + n, err := sw.Writer.Write(formattedBuf) + if n != len(formattedBuf) { + return n, io.ErrShortWrite + } + return len(buf), err +} + +func (sw *streamWriter) format(buf []byte) []byte { + msg := &jsonmessage.JSONMessage{Stream: sw.lineFormat(buf)} + b, err := json.Marshal(msg) + if err != nil { + return FormatError(err) + } + return appendNewline(b) +} + +// NewStdoutWriter returns a writer which formats the output as json message +// representing stdout lines +func NewStdoutWriter(out io.Writer) io.Writer { + return &streamWriter{Writer: out, lineFormat: func(buf []byte) string { + return string(buf) + }} +} + +// NewStderrWriter returns a writer which formats the output as json message +// representing stderr lines +func NewStderrWriter(out io.Writer) io.Writer { + return &streamWriter{Writer: out, lineFormat: func(buf []byte) string { + return "\033[91m" + string(buf) + "\033[0m" + }} +} diff --git a/vendor/github.com/docker/go-connections/nat/nat.go b/vendor/github.com/docker/go-connections/nat/nat.go index 4049d780..1ffe0355 100644 --- a/vendor/github.com/docker/go-connections/nat/nat.go +++ b/vendor/github.com/docker/go-connections/nat/nat.go @@ -2,6 +2,7 @@ package nat import ( + "errors" "fmt" "net" "strconv" @@ -43,19 +44,19 @@ func NewPort(proto, port string) (Port, error) { // ParsePort parses the port number string and returns an int func ParsePort(rawPort string) (int, error) { - if len(rawPort) == 0 { + if rawPort == "" { return 0, nil } port, err := strconv.ParseUint(rawPort, 10, 16) if err != nil { - return 0, err + return 0, fmt.Errorf("invalid port '%s': %w", rawPort, errors.Unwrap(err)) } return int(port), nil } // ParsePortRangeToInt parses the port range string and returns start/end ints func ParsePortRangeToInt(rawPort string) (int, int, error) { - if len(rawPort) == 0 { + if rawPort == "" { return 0, 0, nil } start, end, err := ParsePortRange(rawPort) @@ -91,29 +92,31 @@ func (p Port) Range() (int, int, error) { return ParsePortRangeToInt(p.Port()) } -// SplitProtoPort splits a port in the format of proto/port -func SplitProtoPort(rawPort string) (string, string) { - parts := strings.Split(rawPort, "/") - l := len(parts) - if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 { +// SplitProtoPort splits a port(range) and protocol, formatted as "/[]" +// "/[]". It returns an empty string for both if +// no port(range) is provided. If a port(range) is provided, but no protocol, +// the default ("tcp") protocol is returned. +// +// SplitProtoPort does not validate or normalize the returned values. +func SplitProtoPort(rawPort string) (proto string, port string) { + port, proto, _ = strings.Cut(rawPort, "/") + if port == "" { return "", "" } - if l == 1 { - return "tcp", rawPort + if proto == "" { + proto = "tcp" } - if len(parts[1]) == 0 { - return "tcp", parts[0] - } - return parts[1], parts[0] + return proto, port } -func validateProto(proto string) bool { - for _, availableProto := range []string{"tcp", "udp", "sctp"} { - if availableProto == proto { - return true - } +func validateProto(proto string) error { + switch proto { + case "tcp", "udp", "sctp": + // All good + return nil + default: + return errors.New("invalid proto: " + proto) } - return false } // ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses @@ -123,22 +126,18 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, exposedPorts = make(map[Port]struct{}, len(ports)) bindings = make(map[Port][]PortBinding) ) - for _, rawPort := range ports { - portMappings, err := ParsePortSpec(rawPort) + for _, p := range ports { + portMappings, err := ParsePortSpec(p) if err != nil { return nil, nil, err } - for _, portMapping := range portMappings { - port := portMapping.Port - if _, exists := exposedPorts[port]; !exists { + for _, pm := range portMappings { + port := pm.Port + if _, ok := exposedPorts[port]; !ok { exposedPorts[port] = struct{}{} } - bslice, exists := bindings[port] - if !exists { - bslice = []PortBinding{} - } - bindings[port] = append(bslice, portMapping.Binding) + bindings[port] = append(bindings[port], pm.Binding) } } return exposedPorts, bindings, nil @@ -150,28 +149,34 @@ type PortMapping struct { Binding PortBinding } -func splitParts(rawport string) (string, string, string) { - parts := strings.Split(rawport, ":") - n := len(parts) - containerPort := parts[n-1] +func (p *PortMapping) String() string { + return net.JoinHostPort(p.Binding.HostIP, p.Binding.HostPort+":"+string(p.Port)) +} - switch n { +func splitParts(rawport string) (hostIP, hostPort, containerPort string) { + parts := strings.Split(rawport, ":") + + switch len(parts) { case 1: - return "", "", containerPort + return "", "", parts[0] case 2: - return "", parts[0], containerPort + return "", parts[0], parts[1] case 3: - return parts[0], parts[1], containerPort + return parts[0], parts[1], parts[2] default: - return strings.Join(parts[:n-2], ":"), parts[n-2], containerPort + n := len(parts) + return strings.Join(parts[:n-2], ":"), parts[n-2], parts[n-1] } } // ParsePortSpec parses a port specification string into a slice of PortMappings func ParsePortSpec(rawPort string) ([]PortMapping, error) { - var proto string ip, hostPort, containerPort := splitParts(rawPort) - proto, containerPort = SplitProtoPort(containerPort) + proto, containerPort := SplitProtoPort(containerPort) + proto = strings.ToLower(proto) + if err := validateProto(proto); err != nil { + return nil, err + } if ip != "" && ip[0] == '[' { // Strip [] from IPV6 addresses @@ -182,7 +187,7 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) { ip = rawIP } if ip != "" && net.ParseIP(ip) == nil { - return nil, fmt.Errorf("invalid IP address: %s", ip) + return nil, errors.New("invalid IP address: " + ip) } if containerPort == "" { return nil, fmt.Errorf("no port specified: %s", rawPort) @@ -190,51 +195,43 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) { startPort, endPort, err := ParsePortRange(containerPort) if err != nil { - return nil, fmt.Errorf("invalid containerPort: %s", containerPort) + return nil, errors.New("invalid containerPort: " + containerPort) } - var startHostPort, endHostPort uint64 = 0, 0 - if len(hostPort) > 0 { + var startHostPort, endHostPort uint64 + if hostPort != "" { startHostPort, endHostPort, err = ParsePortRange(hostPort) if err != nil { - return nil, fmt.Errorf("invalid hostPort: %s", hostPort) + return nil, errors.New("invalid hostPort: " + hostPort) + } + if (endPort - startPort) != (endHostPort - startHostPort) { + // Allow host port range iff containerPort is not a range. + // In this case, use the host port range as the dynamic + // host port range to allocate into. + if endPort != startPort { + return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) + } } } - if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { - // Allow host port range iff containerPort is not a range. - // In this case, use the host port range as the dynamic - // host port range to allocate into. - if endPort != startPort { - return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) - } - } + count := endPort - startPort + 1 + ports := make([]PortMapping, 0, count) - if !validateProto(strings.ToLower(proto)) { - return nil, fmt.Errorf("invalid proto: %s", proto) - } - - ports := []PortMapping{} - for i := uint64(0); i <= (endPort - startPort); i++ { - containerPort = strconv.FormatUint(startPort+i, 10) - if len(hostPort) > 0 { - hostPort = strconv.FormatUint(startHostPort+i, 10) + for i := uint64(0); i < count; i++ { + cPort := Port(strconv.FormatUint(startPort+i, 10) + "/" + proto) + hPort := "" + if hostPort != "" { + hPort = strconv.FormatUint(startHostPort+i, 10) + // Set hostPort to a range only if there is a single container port + // and a dynamic host port. + if count == 1 && startHostPort != endHostPort { + hPort += "-" + strconv.FormatUint(endHostPort, 10) + } } - // Set hostPort to a range only if there is a single container port - // and a dynamic host port. - if startPort == endPort && startHostPort != endHostPort { - hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) - } - port, err := NewPort(strings.ToLower(proto), containerPort) - if err != nil { - return nil, err - } - - binding := PortBinding{ - HostIP: ip, - HostPort: hostPort, - } - ports = append(ports, PortMapping{Port: port, Binding: binding}) + ports = append(ports, PortMapping{ + Port: cPort, + Binding: PortBinding{HostIP: ip, HostPort: hPort}, + }) } return ports, nil } diff --git a/vendor/github.com/docker/go-connections/nat/parse.go b/vendor/github.com/docker/go-connections/nat/parse.go index e4b53e8a..64affa2a 100644 --- a/vendor/github.com/docker/go-connections/nat/parse.go +++ b/vendor/github.com/docker/go-connections/nat/parse.go @@ -1,7 +1,7 @@ package nat import ( - "fmt" + "errors" "strconv" "strings" ) @@ -9,7 +9,7 @@ import ( // ParsePortRange parses and validates the specified string as a port-range (8000-9000) func ParsePortRange(ports string) (uint64, uint64, error) { if ports == "" { - return 0, 0, fmt.Errorf("empty string specified for ports") + return 0, 0, errors.New("empty string specified for ports") } if !strings.Contains(ports, "-") { start, err := strconv.ParseUint(ports, 10, 16) @@ -27,7 +27,7 @@ func ParsePortRange(ports string) (uint64, uint64, error) { return 0, 0, err } if end < start { - return 0, 0, fmt.Errorf("invalid range specified for port: %s", ports) + return 0, 0, errors.New("invalid range specified for port: " + ports) } return start, end, nil } diff --git a/vendor/github.com/docker/go-connections/sockets/proxy.go b/vendor/github.com/docker/go-connections/sockets/proxy.go index c897cb02..f04980e4 100644 --- a/vendor/github.com/docker/go-connections/sockets/proxy.go +++ b/vendor/github.com/docker/go-connections/sockets/proxy.go @@ -9,6 +9,8 @@ import ( // GetProxyEnv allows access to the uppercase and the lowercase forms of // proxy-related variables. See the Go specification for details on these // variables. https://golang.org/pkg/net/http/ +// +// Deprecated: this function was used as helper for [DialerFromEnvironment] and is no longer used. It will be removed in the next release. func GetProxyEnv(key string) string { proxyValue := os.Getenv(strings.ToUpper(key)) if proxyValue == "" { @@ -19,10 +21,11 @@ func GetProxyEnv(key string) string { // DialerFromEnvironment was previously used to configure a net.Dialer to route // connections through a SOCKS proxy. -// DEPRECATED: SOCKS proxies are now supported by configuring only +// +// Deprecated: SOCKS proxies are now supported by configuring only // http.Transport.Proxy, and no longer require changing http.Transport.Dial. -// Therefore, only sockets.ConfigureTransport() needs to be called, and any -// sockets.DialerFromEnvironment() calls can be dropped. +// Therefore, only [sockets.ConfigureTransport] needs to be called, and any +// [sockets.DialerFromEnvironment] calls can be dropped. func DialerFromEnvironment(direct *net.Dialer) (*net.Dialer, error) { return direct, nil } diff --git a/vendor/github.com/docker/go-connections/sockets/sockets.go b/vendor/github.com/docker/go-connections/sockets/sockets.go index b0eae239..61172978 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets.go @@ -2,13 +2,19 @@ package sockets import ( + "context" "errors" + "fmt" "net" "net/http" + "syscall" "time" ) -const defaultTimeout = 10 * time.Second +const ( + defaultTimeout = 10 * time.Second + maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) +) // ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system. var ErrProtocolNotAvailable = errors.New("protocol not available") @@ -35,3 +41,26 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error { } return nil } + +// DialPipe connects to a Windows named pipe. It is not supported on +// non-Windows platforms. +// +// Deprecated: use [github.com/Microsoft/go-winio.DialPipe] or [github.com/Microsoft/go-winio.DialPipeContext]. +func DialPipe(addr string, timeout time.Duration) (net.Conn, error) { + return dialPipe(addr, timeout) +} + +func configureUnixTransport(tr *http.Transport, proto, addr string) error { + if len(addr) > maxUnixSocketPathSize { + return fmt.Errorf("unix socket path %q is too long", addr) + } + // No need for compression in local communications. + tr.DisableCompression = true + dialer := &net.Dialer{ + Timeout: defaultTimeout, + } + tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { + return dialer.DialContext(ctx, proto, addr) + } + return nil +} diff --git a/vendor/github.com/docker/go-connections/sockets/sockets_unix.go b/vendor/github.com/docker/go-connections/sockets/sockets_unix.go index 78a34a98..913d2f00 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets_unix.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets_unix.go @@ -3,37 +3,16 @@ package sockets import ( - "context" - "fmt" "net" "net/http" "syscall" "time" ) -const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) - -func configureUnixTransport(tr *http.Transport, proto, addr string) error { - if len(addr) > maxUnixSocketPathSize { - return fmt.Errorf("unix socket path %q is too long", addr) - } - // No need for compression in local communications. - tr.DisableCompression = true - dialer := &net.Dialer{ - Timeout: defaultTimeout, - } - tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { - return dialer.DialContext(ctx, proto, addr) - } - return nil -} - func configureNpipeTransport(tr *http.Transport, proto, addr string) error { return ErrProtocolNotAvailable } -// DialPipe connects to a Windows named pipe. -// This is not supported on other OSes. -func DialPipe(_ string, _ time.Duration) (net.Conn, error) { +func dialPipe(_ string, _ time.Duration) (net.Conn, error) { return nil, syscall.EAFNOSUPPORT } diff --git a/vendor/github.com/docker/go-connections/sockets/sockets_windows.go b/vendor/github.com/docker/go-connections/sockets/sockets_windows.go index 7acafc5a..6d6beb38 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets_windows.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets_windows.go @@ -9,10 +9,6 @@ import ( "github.com/Microsoft/go-winio" ) -func configureUnixTransport(tr *http.Transport, proto, addr string) error { - return ErrProtocolNotAvailable -} - func configureNpipeTransport(tr *http.Transport, proto, addr string) error { // No need for compression in local communications. tr.DisableCompression = true @@ -22,7 +18,6 @@ func configureNpipeTransport(tr *http.Transport, proto, addr string) error { return nil } -// DialPipe connects to a Windows named pipe. -func DialPipe(addr string, timeout time.Duration) (net.Conn, error) { +func dialPipe(addr string, timeout time.Duration) (net.Conn, error) { return winio.DialPipe(addr, &timeout) } diff --git a/vendor/github.com/docker/go-connections/sockets/unix_socket.go b/vendor/github.com/docker/go-connections/sockets/unix_socket.go index b9233521..e736f71d 100644 --- a/vendor/github.com/docker/go-connections/sockets/unix_socket.go +++ b/vendor/github.com/docker/go-connections/sockets/unix_socket.go @@ -1,5 +1,3 @@ -//go:build !windows - /* Package sockets is a simple unix domain socket wrapper. @@ -57,26 +55,6 @@ import ( // SockOption sets up socket file's creating option type SockOption func(string) error -// WithChown modifies the socket file's uid and gid -func WithChown(uid, gid int) SockOption { - return func(path string) error { - if err := os.Chown(path, uid, gid); err != nil { - return err - } - return nil - } -} - -// WithChmod modifies socket file's access mode. -func WithChmod(mask os.FileMode) SockOption { - return func(path string) error { - if err := os.Chmod(path, mask); err != nil { - return err - } - return nil - } -} - // NewUnixSocketWithOpts creates a unix socket with the specified options. // By default, socket permissions are 0000 (i.e.: no access for anyone); pass // WithChmod() and WithChown() to set the desired ownership and permissions. @@ -90,22 +68,7 @@ func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error return nil, err } - // net.Listen does not allow for permissions to be set. As a result, when - // specifying custom permissions ("WithChmod()"), there is a short time - // between creating the socket and applying the permissions, during which - // the socket permissions are Less restrictive than desired. - // - // To work around this limitation of net.Listen(), we temporarily set the - // umask to 0777, which forces the socket to be created with 000 permissions - // (i.e.: no access for anyone). After that, WithChmod() must be used to set - // the desired permissions. - // - // We don't use "defer" here, to reset the umask to its original value as soon - // as possible. Ideally we'd be able to detect if WithChmod() was passed as - // an option, and skip changing umask if default permissions are used. - origUmask := syscall.Umask(0o777) - l, err := net.Listen("unix", path) - syscall.Umask(origUmask) + l, err := listenUnix(path) if err != nil { return nil, err } @@ -119,8 +82,3 @@ func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error return l, nil } - -// NewUnixSocket creates a unix socket with the specified path and group. -func NewUnixSocket(path string, gid int) (net.Listener, error) { - return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660)) -} diff --git a/vendor/github.com/docker/go-connections/sockets/unix_socket_unix.go b/vendor/github.com/docker/go-connections/sockets/unix_socket_unix.go new file mode 100644 index 00000000..a41a7165 --- /dev/null +++ b/vendor/github.com/docker/go-connections/sockets/unix_socket_unix.go @@ -0,0 +1,54 @@ +//go:build !windows + +package sockets + +import ( + "net" + "os" + "syscall" +) + +// WithChown modifies the socket file's uid and gid +func WithChown(uid, gid int) SockOption { + return func(path string) error { + if err := os.Chown(path, uid, gid); err != nil { + return err + } + return nil + } +} + +// WithChmod modifies socket file's access mode. +func WithChmod(mask os.FileMode) SockOption { + return func(path string) error { + if err := os.Chmod(path, mask); err != nil { + return err + } + return nil + } +} + +// NewUnixSocket creates a unix socket with the specified path and group. +func NewUnixSocket(path string, gid int) (net.Listener, error) { + return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660)) +} + +func listenUnix(path string) (net.Listener, error) { + // net.Listen does not allow for permissions to be set. As a result, when + // specifying custom permissions ("WithChmod()"), there is a short time + // between creating the socket and applying the permissions, during which + // the socket permissions are Less restrictive than desired. + // + // To work around this limitation of net.Listen(), we temporarily set the + // umask to 0777, which forces the socket to be created with 000 permissions + // (i.e.: no access for anyone). After that, WithChmod() must be used to set + // the desired permissions. + // + // We don't use "defer" here, to reset the umask to its original value as soon + // as possible. Ideally we'd be able to detect if WithChmod() was passed as + // an option, and skip changing umask if default permissions are used. + origUmask := syscall.Umask(0o777) + l, err := net.Listen("unix", path) + syscall.Umask(origUmask) + return l, err +} diff --git a/vendor/github.com/docker/go-connections/sockets/unix_socket_windows.go b/vendor/github.com/docker/go-connections/sockets/unix_socket_windows.go new file mode 100644 index 00000000..5ec29e05 --- /dev/null +++ b/vendor/github.com/docker/go-connections/sockets/unix_socket_windows.go @@ -0,0 +1,7 @@ +package sockets + +import "net" + +func listenUnix(path string) (net.Listener, error) { + return net.Listen("unix", path) +} diff --git a/vendor/github.com/docker/go-connections/tlsconfig/config.go b/vendor/github.com/docker/go-connections/tlsconfig/config.go index 606c98a3..8b0264f6 100644 --- a/vendor/github.com/docker/go-connections/tlsconfig/config.go +++ b/vendor/github.com/docker/go-connections/tlsconfig/config.go @@ -34,51 +34,37 @@ type Options struct { // the system pool will be used. ExclusiveRootPools bool MinVersion uint16 - // If Passphrase is set, it will be used to decrypt a TLS private key - // if the key is encrypted. - // - // Deprecated: Use of encrypted TLS private keys has been deprecated, and - // will be removed in a future release. Golang has deprecated support for - // legacy PEM encryption (as specified in RFC 1423), as it is insecure by - // design (see https://go-review.googlesource.com/c/go/+/264159). - Passphrase string -} - -// Extra (server-side) accepted CBC cipher suites - will phase out in the future -var acceptedCBCCiphers = []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, } // DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls // options struct but wants to use a commonly accepted set of TLS cipher suites, with // known weak algorithms removed. -var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...) +var DefaultServerAcceptedCiphers = defaultCipherSuites + +// defaultCipherSuites is shared by both client and server as the default set. +var defaultCipherSuites = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, +} // ServerDefault returns a secure-enough TLS configuration for the server TLS configuration. func ServerDefault(ops ...func(*tls.Config)) *tls.Config { - tlsConfig := &tls.Config{ - // Avoid fallback by default to SSL protocols < TLS1.2 - MinVersion: tls.VersionTLS12, - PreferServerCipherSuites: true, - CipherSuites: DefaultServerAcceptedCiphers, - } - - for _, op := range ops { - op(tlsConfig) - } - - return tlsConfig + return defaultConfig(ops...) } // ClientDefault returns a secure-enough TLS configuration for the client TLS configuration. func ClientDefault(ops ...func(*tls.Config)) *tls.Config { + return defaultConfig(ops...) +} + +// defaultConfig is the default config used by both client and server TLS configuration. +func defaultConfig(ops ...func(*tls.Config)) *tls.Config { tlsConfig := &tls.Config{ - // Prefer TLS1.2 as the client minimum + // Avoid fallback by default to SSL protocols < TLS1.2 MinVersion: tls.VersionTLS12, - CipherSuites: clientCipherSuites, + CipherSuites: defaultCipherSuites, } for _, op := range ops { @@ -92,13 +78,13 @@ func ClientDefault(ops ...func(*tls.Config)) *tls.Config { func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) { // If we should verify the server, we need to load a trusted ca var ( - certPool *x509.CertPool - err error + pool *x509.CertPool + err error ) if exclusivePool { - certPool = x509.NewCertPool() + pool = x509.NewCertPool() } else { - certPool, err = SystemCertPool() + pool, err = SystemCertPool() if err != nil { return nil, fmt.Errorf("failed to read system certificates: %v", err) } @@ -107,10 +93,10 @@ func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) { if err != nil { return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err) } - if !certPool.AppendCertsFromPEM(pemData) { + if !pool.AppendCertsFromPEM(pemData) { return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile) } - return certPool, nil + return pool, nil } // allTLSVersions lists all the TLS versions and is used by the code that validates @@ -144,34 +130,32 @@ func adjustMinVersion(options Options, config *tls.Config) error { return nil } -// IsErrEncryptedKey returns true if the 'err' is an error of incorrect -// password when trying to decrypt a TLS private key. +// errEncryptedKeyDeprecated is produced when we encounter an encrypted +// (password-protected) key. From https://go-review.googlesource.com/c/go/+/264159; // -// Deprecated: Use of encrypted TLS private keys has been deprecated, and -// will be removed in a future release. Golang has deprecated support for -// legacy PEM encryption (as specified in RFC 1423), as it is insecure by -// design (see https://go-review.googlesource.com/c/go/+/264159). -func IsErrEncryptedKey(err error) bool { - return errors.Is(err, x509.IncorrectPasswordError) -} +// > Legacy PEM encryption as specified in RFC 1423 is insecure by design. Since +// > it does not authenticate the ciphertext, it is vulnerable to padding oracle +// > attacks that can let an attacker recover the plaintext +// > +// > It's unfortunate that we don't implement PKCS#8 encryption so we can't +// > recommend an alternative but PEM encryption is so broken that it's worth +// > deprecating outright. +// +// Also see https://docs.docker.com/go/deprecated/ +var errEncryptedKeyDeprecated = errors.New("private key is encrypted; encrypted private keys are obsolete, and not supported") // getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format. -// If the private key is encrypted, 'passphrase' is used to decrypted the -// private key. -func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) { +// It returns an error if the file could not be decoded or was protected by +// a passphrase. +func getPrivateKey(keyBytes []byte) ([]byte, error) { // this section makes some small changes to code from notary/tuf/utils/x509.go pemBlock, _ := pem.Decode(keyBytes) if pemBlock == nil { return nil, fmt.Errorf("no valid private key found") } - var err error if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // Ignore SA1019 (IsEncryptedPEMBlock is deprecated) - keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase)) //nolint:staticcheck // Ignore SA1019 (DecryptPEMBlock is deprecated) - if err != nil { - return nil, fmt.Errorf("private key is encrypted, but could not decrypt it: %w", err) - } - keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes}) + return nil, errEncryptedKeyDeprecated } return keyBytes, nil @@ -195,7 +179,7 @@ func getCert(options Options) ([]tls.Certificate, error) { return nil, err } - prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase) + prKeyBytes, err = getPrivateKey(prKeyBytes) if err != nil { return nil, err } @@ -210,7 +194,7 @@ func getCert(options Options) ([]tls.Certificate, error) { // Client returns a TLS configuration meant to be used by a client. func Client(options Options) (*tls.Config, error) { - tlsConfig := ClientDefault() + tlsConfig := defaultConfig() tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify if !options.InsecureSkipVerify && options.CAFile != "" { CAs, err := certPool(options.CAFile, options.ExclusiveRootPools) @@ -235,7 +219,7 @@ func Client(options Options) (*tls.Config, error) { // Server returns a TLS configuration meant to be used by a server. func Server(options Options) (*tls.Config, error) { - tlsConfig := ServerDefault() + tlsConfig := defaultConfig() tlsConfig.ClientAuth = options.ClientAuth tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) if err != nil { diff --git a/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go b/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go deleted file mode 100644 index a82f9fa5..00000000 --- a/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go +++ /dev/null @@ -1,14 +0,0 @@ -// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers. -package tlsconfig - -import ( - "crypto/tls" -) - -// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set) -var clientCipherSuites = []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, -} diff --git a/vendor/github.com/docker/go-plugins-helpers/LICENSE b/vendor/github.com/docker/go-plugins-helpers/LICENSE new file mode 100644 index 00000000..8f71f43f --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/docker/cli/NOTICE b/vendor/github.com/docker/go-plugins-helpers/NOTICE similarity index 75% rename from vendor/github.com/docker/cli/NOTICE rename to vendor/github.com/docker/go-plugins-helpers/NOTICE index 1c40faae..6e6f469a 100644 --- a/vendor/github.com/docker/cli/NOTICE +++ b/vendor/github.com/docker/go-plugins-helpers/NOTICE @@ -1,9 +1,9 @@ Docker -Copyright 2012-2017 Docker, Inc. +Copyright 2012-2015 Docker, Inc. This product includes software developed at Docker, Inc. (https://www.docker.com). -This product contains software (https://github.com/creack/pty) developed +This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License. The following is courtesy of our legal counsel: @@ -14,6 +14,6 @@ United States and other governments. It is your responsibility to ensure that your use and/or transfer does not violate applicable laws. -For more information, see https://www.bis.doc.gov +For more information, please see https://www.bis.doc.gov See also https://www.apache.org/dev/crypto.html and/or seek legal counsel. diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/encoder.go b/vendor/github.com/docker/go-plugins-helpers/sdk/encoder.go new file mode 100644 index 00000000..195812a4 --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/encoder.go @@ -0,0 +1,37 @@ +package sdk + +import ( + "encoding/json" + "fmt" + "io" + "net/http" +) + +// DefaultContentTypeV1_1 is the default content type accepted and sent by the plugins. +const DefaultContentTypeV1_1 = "application/vnd.docker.plugins.v1.1+json" + +// DecodeRequest decodes an http request into a given structure. +func DecodeRequest(w http.ResponseWriter, r *http.Request, req interface{}) (err error) { + if err = json.NewDecoder(r.Body).Decode(req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + return +} + +// EncodeResponse encodes the given structure into an http response. +func EncodeResponse(w http.ResponseWriter, res interface{}, err bool) { + w.Header().Set("Content-Type", DefaultContentTypeV1_1) + if err { + w.WriteHeader(http.StatusInternalServerError) + } + json.NewEncoder(w).Encode(res) +} + +// StreamResponse streams a response object to the client +func StreamResponse(w http.ResponseWriter, data io.ReadCloser) { + w.Header().Set("Content-Type", DefaultContentTypeV1_1) + if _, err := copyBuf(w, data); err != nil { + fmt.Printf("ERROR in stream: %v\n", err) + } + data.Close() +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/handler.go b/vendor/github.com/docker/go-plugins-helpers/sdk/handler.go new file mode 100644 index 00000000..c0d042ed --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/handler.go @@ -0,0 +1,88 @@ +package sdk + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "os" +) + +const activatePath = "/Plugin.Activate" + +// Handler is the base to create plugin handlers. +// It initializes connections and sockets to listen to. +type Handler struct { + mux *http.ServeMux +} + +// NewHandler creates a new Handler with an http mux. +func NewHandler(manifest string) Handler { + mux := http.NewServeMux() + + mux.HandleFunc(activatePath, func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", DefaultContentTypeV1_1) + fmt.Fprintln(w, manifest) + }) + + return Handler{mux: mux} +} + +// Serve sets up the handler to serve requests on the passed in listener +func (h Handler) Serve(l net.Listener) error { + server := http.Server{ + Addr: l.Addr().String(), + Handler: h.mux, + } + return server.Serve(l) +} + +// ServeTCP makes the handler to listen for request in a given TCP address. +// It also writes the spec file in the right directory for docker to read. +// Due to constrains for running Docker in Docker on Windows, data-root directory +// of docker daemon must be provided. To get default directory, use +// WindowsDefaultDaemonRootDir() function. On Unix, this parameter is ignored. +func (h Handler) ServeTCP(pluginName, addr, daemonDir string, tlsConfig *tls.Config) error { + l, spec, err := newTCPListener(addr, pluginName, daemonDir, tlsConfig) + if err != nil { + return err + } + if spec != "" { + defer os.Remove(spec) + } + return h.Serve(l) +} + +// ServeUnix makes the handler to listen for requests in a unix socket. +// It also creates the socket file in the right directory for docker to read. +func (h Handler) ServeUnix(addr string, gid int) error { + l, spec, err := newUnixListener(addr, gid) + if err != nil { + return err + } + if spec != "" { + defer os.Remove(spec) + } + return h.Serve(l) +} + +// ServeWindows makes the handler to listen for request in a Windows named pipe. +// It also creates the spec file in the right directory for docker to read. +// Due to constrains for running Docker in Docker on Windows, data-root directory +// of docker daemon must be provided. To get default directory, use +// WindowsDefaultDaemonRootDir() function. On Unix, this parameter is ignored. +func (h Handler) ServeWindows(addr, pluginName, daemonDir string, pipeConfig *WindowsPipeConfig) error { + l, spec, err := newWindowsListener(addr, pluginName, daemonDir, pipeConfig) + if err != nil { + return err + } + if spec != "" { + defer os.Remove(spec) + } + return h.Serve(l) +} + +// HandleFunc registers a function to handle a request path with. +func (h Handler) HandleFunc(path string, fn func(w http.ResponseWriter, r *http.Request)) { + h.mux.HandleFunc(path, fn) +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/pool.go b/vendor/github.com/docker/go-plugins-helpers/sdk/pool.go new file mode 100644 index 00000000..31677597 --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/pool.go @@ -0,0 +1,18 @@ +package sdk + +import ( + "io" + "sync" +) + +const buffer32K = 32 * 1024 + +var buffer32KPool = &sync.Pool{New: func() interface{} { return make([]byte, buffer32K) }} + +// copyBuf uses a shared buffer pool with io.CopyBuffer +func copyBuf(w io.Writer, r io.Reader) (int64, error) { + buf := buffer32KPool.Get().([]byte) + written, err := io.CopyBuffer(w, r, buf) + buffer32KPool.Put(buf) + return written, err +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/spec_file_generator.go b/vendor/github.com/docker/go-plugins-helpers/sdk/spec_file_generator.go new file mode 100644 index 00000000..c9cdbb2f --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/spec_file_generator.go @@ -0,0 +1,57 @@ +package sdk + +import ( + "fmt" + "os" + "path/filepath" +) + +type protocol string + +const ( + protoTCP protocol = "tcp" + protoNamedPipe protocol = "npipe" +) + +// PluginSpecDir returns plugin spec dir in relation to daemon root directory. +func PluginSpecDir(daemonRoot string) string { + return ([]string{filepath.Join(daemonRoot, "plugins")})[0] +} + +// WindowsDefaultDaemonRootDir returns default data directory of docker daemon on Windows. +func WindowsDefaultDaemonRootDir() string { + return filepath.Join(os.Getenv("programdata"), "docker") +} + +func createPluginSpecDirWindows(name, address, daemonRoot string) (string, error) { + _, err := os.Stat(daemonRoot) + if os.IsNotExist(err) { + return "", fmt.Errorf("Deamon root directory must already exist: %s", err) + } + + pluginSpecDir := PluginSpecDir(daemonRoot) + + if err := windowsCreateDirectoryWithACL(pluginSpecDir); err != nil { + return "", err + } + return pluginSpecDir, nil +} + +func createPluginSpecDirUnix(name, address string) (string, error) { + pluginSpecDir := PluginSpecDir("/etc/docker") + if err := os.MkdirAll(pluginSpecDir, 0755); err != nil { + return "", err + } + return pluginSpecDir, nil +} + +func writeSpecFile(name, address, pluginSpecDir string, proto protocol) (string, error) { + specFileDir := filepath.Join(pluginSpecDir, name+".spec") + + url := string(proto) + "://" + address + if err := os.WriteFile(specFileDir, []byte(url), 0644); err != nil { + return "", err + } + + return specFileDir, nil +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/tcp_listener.go b/vendor/github.com/docker/go-plugins-helpers/sdk/tcp_listener.go new file mode 100644 index 00000000..bad85f7f --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/tcp_listener.go @@ -0,0 +1,34 @@ +package sdk + +import ( + "crypto/tls" + "net" + "runtime" + + "github.com/docker/go-connections/sockets" +) + +func newTCPListener(address, pluginName, daemonDir string, tlsConfig *tls.Config) (net.Listener, string, error) { + listener, err := sockets.NewTCPSocket(address, tlsConfig) + if err != nil { + return nil, "", err + } + + addr := listener.Addr().String() + + var specDir string + if runtime.GOOS == "windows" { + specDir, err = createPluginSpecDirWindows(pluginName, addr, daemonDir) + } else { + specDir, err = createPluginSpecDirUnix(pluginName, addr) + } + if err != nil { + return nil, "", err + } + + specFile, err := writeSpecFile(pluginName, addr, specDir, protoTCP) + if err != nil { + return nil, "", err + } + return listener, specFile, nil +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener.go b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener.go new file mode 100644 index 00000000..00bc911f --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener.go @@ -0,0 +1,35 @@ +//go:build linux || freebsd + +package sdk + +import ( + "net" + "os" + "path/filepath" + + "github.com/docker/go-connections/sockets" +) + +const pluginSockDir = "/run/docker/plugins" + +func newUnixListener(pluginName string, gid int) (net.Listener, string, error) { + path, err := fullSocketAddress(pluginName) + if err != nil { + return nil, "", err + } + listener, err := sockets.NewUnixSocket(path, gid) + if err != nil { + return nil, "", err + } + return listener, path, nil +} + +func fullSocketAddress(address string) (string, error) { + if err := os.MkdirAll(pluginSockDir, 0755); err != nil { + return "", err + } + if filepath.IsAbs(address) { + return address, nil + } + return filepath.Join(pluginSockDir, address+".sock"), nil +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_nosystemd.go b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_nosystemd.go new file mode 100644 index 00000000..1a9247b0 --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_nosystemd.go @@ -0,0 +1,10 @@ +//go:build (linux || freebsd) && nosystemd + +package sdk + +import "net" + +// FIXME(thaJeztah): this code was added in https://github.com/docker/go-plugins-helpers/commit/008703b825c10311af1840deeaf5f4769df7b59e, but is not used anywhere +func setupSocketActivation() (net.Listener, error) { + return nil, nil +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_systemd.go b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_systemd.go new file mode 100644 index 00000000..59c53bf1 --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_systemd.go @@ -0,0 +1,45 @@ +//go:build (linux || freebsd) && !nosystemd + +package sdk + +import ( + "fmt" + "net" + "os" + + "github.com/coreos/go-systemd/activation" +) + +// isRunningSystemd checks whether the host was booted with systemd as its init +// system. This functions similarly to systemd's `sd_booted(3)`: internally, it +// checks whether /run/systemd/system/ exists and is a directory. +// http://www.freedesktop.org/software/systemd/man/sd_booted.html +// +// Copied from github.com/coreos/go-systemd/util.IsRunningSystemd +func isRunningSystemd() bool { + fi, err := os.Lstat("/run/systemd/system") + if err != nil { + return false + } + return fi.IsDir() +} + +// FIXME(thaJeztah): this code was added in https://github.com/docker/go-plugins-helpers/commit/008703b825c10311af1840deeaf5f4769df7b59e, but is not used anywhere +func setupSocketActivation() (net.Listener, error) { + if !isRunningSystemd() { + return nil, nil + } + listenFds := activation.Files(false) + if len(listenFds) > 1 { + return nil, fmt.Errorf("expected only one socket from systemd, got %d", len(listenFds)) + } + var listener net.Listener + if len(listenFds) == 1 { + l, err := net.FileListener(listenFds[0]) + if err != nil { + return nil, err + } + listener = l + } + return listener, nil +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_unsupported.go b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_unsupported.go new file mode 100644 index 00000000..a472f06e --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/unix_listener_unsupported.go @@ -0,0 +1,12 @@ +//go:build !linux && !freebsd + +package sdk + +import ( + "errors" + "net" +) + +func newUnixListener(pluginName string, gid int) (net.Listener, string, error) { + return nil, "", errors.New("unix socket creation is only supported on Linux and FreeBSD") +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener.go b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener.go new file mode 100644 index 00000000..afb0f0b6 --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener.go @@ -0,0 +1,69 @@ +//go:build windows + +package sdk + +import ( + "net" + "os" + "syscall" + "unsafe" + + "github.com/Microsoft/go-winio" +) + +// Named pipes use Windows Security Descriptor Definition Language to define ACL. Following are +// some useful definitions. +const ( + // AllowEveryone grants full access permissions for everyone. + AllowEveryone = "S:(ML;;NW;;;LW)D:(A;;0x12019f;;;WD)" + + // AllowServiceSystemAdmin grants full access permissions for Service, System, Administrator group and account. + AllowServiceSystemAdmin = "D:(A;ID;FA;;;SY)(A;ID;FA;;;BA)(A;ID;FA;;;LA)(A;ID;FA;;;LS)" +) + +func newWindowsListener(address, pluginName, daemonRoot string, pipeConfig *WindowsPipeConfig) (net.Listener, string, error) { + listener, err := winio.ListenPipe(address, &winio.PipeConfig{ + SecurityDescriptor: pipeConfig.SecurityDescriptor, + InputBufferSize: pipeConfig.InBufferSize, + OutputBufferSize: pipeConfig.OutBufferSize, + }) + if err != nil { + return nil, "", err + } + + addr := listener.Addr().String() + + specDir, err := createPluginSpecDirWindows(pluginName, addr, daemonRoot) + if err != nil { + return nil, "", err + } + + spec, err := writeSpecFile(pluginName, addr, specDir, protoNamedPipe) + if err != nil { + return nil, "", err + } + return listener, spec, nil +} + +func windowsCreateDirectoryWithACL(name string) error { + sa := syscall.SecurityAttributes{Length: 0} + const sddl = "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)" + sd, err := winio.SddlToSecurityDescriptor(sddl) + if err != nil { + return &os.PathError{Op: "mkdir", Path: name, Err: err} + } + sa.Length = uint32(unsafe.Sizeof(sa)) + sa.InheritHandle = 1 + sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0])) + + namep, err := syscall.UTF16PtrFromString(name) + if err != nil { + return &os.PathError{Op: "mkdir", Path: name, Err: err} + } + + e := syscall.CreateDirectory(namep, &sa) + if e != nil { + return &os.PathError{Op: "mkdir", Path: name, Err: e} + } + return nil +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener_unsupported.go b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener_unsupported.go new file mode 100644 index 00000000..1b187d0d --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_listener_unsupported.go @@ -0,0 +1,16 @@ +//go:build !windows + +package sdk + +import ( + "errors" + "net" +) + +func newWindowsListener(address, pluginName, daemonRoot string, pipeConfig *WindowsPipeConfig) (net.Listener, string, error) { + return nil, "", errors.New("named pipe creation is only supported on Windows") +} + +func windowsCreateDirectoryWithACL(name string) error { + return nil +} diff --git a/vendor/github.com/docker/go-plugins-helpers/sdk/windows_pipe_config.go b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_pipe_config.go new file mode 100644 index 00000000..256fa3d6 --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/sdk/windows_pipe_config.go @@ -0,0 +1,13 @@ +package sdk + +// WindowsPipeConfig is a helper structure for configuring named pipe parameters on Windows. +type WindowsPipeConfig struct { + // SecurityDescriptor contains a Windows security descriptor in SDDL format. + SecurityDescriptor string + + // InBufferSize in bytes. + InBufferSize int32 + + // OutBufferSize in bytes. + OutBufferSize int32 +} diff --git a/vendor/github.com/docker/go-plugins-helpers/volume/README.md b/vendor/github.com/docker/go-plugins-helpers/volume/README.md new file mode 100644 index 00000000..395aa643 --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/volume/README.md @@ -0,0 +1,36 @@ +# Docker volume extension api. + +Go handler to create external volume extensions for Docker. + +## Usage + +This library is designed to be integrated in your program. + +1. Implement the `volume.Driver` interface. +2. Initialize a `volume.Handler` with your implementation. +3. Call either `ServeTCP` or `ServeUnix` from the `volume.Handler`. + +### Example using TCP sockets: + +```go + d := MyVolumeDriver{} + h := volume.NewHandler(d) + h.ServeTCP("test_volume", ":8080") +``` + +### Example using Unix sockets: + +```go + d := MyVolumeDriver{} + h := volume.NewHandler(d) + u, _ := user.Lookup("root") + gid, _ := strconv.Atoi(u.Gid) + h.ServeUnix("test_volume", gid) +``` + +## Full example plugins + +- https://github.com/calavera/docker-volume-glusterfs +- https://github.com/calavera/docker-volume-keywhiz +- https://github.com/quobyte/docker-volume +- https://github.com/NimbleStorage/Nemo diff --git a/vendor/github.com/docker/go-plugins-helpers/volume/api.go b/vendor/github.com/docker/go-plugins-helpers/volume/api.go new file mode 100644 index 00000000..387e82ac --- /dev/null +++ b/vendor/github.com/docker/go-plugins-helpers/volume/api.go @@ -0,0 +1,221 @@ +package volume + +import ( + "net/http" + + "github.com/docker/go-plugins-helpers/sdk" +) + +const ( + // DefaultDockerRootDirectory is the default directory where volumes will be created. + DefaultDockerRootDirectory = "/var/lib/docker-volumes" + + manifest = `{"Implements": ["VolumeDriver"]}` + createPath = "/VolumeDriver.Create" + getPath = "/VolumeDriver.Get" + listPath = "/VolumeDriver.List" + removePath = "/VolumeDriver.Remove" + hostVirtualPath = "/VolumeDriver.Path" + mountPath = "/VolumeDriver.Mount" + unmountPath = "/VolumeDriver.Unmount" + capabilitiesPath = "/VolumeDriver.Capabilities" +) + +// CreateRequest is the structure that docker's requests are deserialized to. +type CreateRequest struct { + Name string + Options map[string]string `json:"Opts,omitempty"` +} + +// RemoveRequest structure for a volume remove request +type RemoveRequest struct { + Name string +} + +// MountRequest structure for a volume mount request +type MountRequest struct { + Name string + ID string +} + +// MountResponse structure for a volume mount response +type MountResponse struct { + Mountpoint string +} + +// UnmountRequest structure for a volume unmount request +type UnmountRequest struct { + Name string + ID string +} + +// PathRequest structure for a volume path request +type PathRequest struct { + Name string +} + +// PathResponse structure for a volume path response +type PathResponse struct { + Mountpoint string +} + +// GetRequest structure for a volume get request +type GetRequest struct { + Name string +} + +// GetResponse structure for a volume get response +type GetResponse struct { + Volume *Volume +} + +// ListResponse structure for a volume list response +type ListResponse struct { + Volumes []*Volume +} + +// CapabilitiesResponse structure for a volume capability response +type CapabilitiesResponse struct { + Capabilities Capability +} + +// Volume represents a volume object for use with `Get` and `List` requests +type Volume struct { + Name string + Mountpoint string `json:",omitempty"` + CreatedAt string `json:",omitempty"` + Status map[string]interface{} `json:",omitempty"` +} + +// Capability represents the list of capabilities a volume driver can return +type Capability struct { + Scope string +} + +// ErrorResponse is a formatted error message that docker can understand +type ErrorResponse struct { + Err string +} + +// NewErrorResponse creates an ErrorResponse with the provided message +func NewErrorResponse(msg string) *ErrorResponse { + return &ErrorResponse{Err: msg} +} + +// Driver represent the interface a driver must fulfill. +type Driver interface { + Create(*CreateRequest) error + List() (*ListResponse, error) + Get(*GetRequest) (*GetResponse, error) + Remove(*RemoveRequest) error + Path(*PathRequest) (*PathResponse, error) + Mount(*MountRequest) (*MountResponse, error) + Unmount(*UnmountRequest) error + Capabilities() *CapabilitiesResponse +} + +// Handler forwards requests and responses between the docker daemon and the plugin. +type Handler struct { + driver Driver + sdk.Handler +} + +// NewHandler initializes the request handler with a driver implementation. +func NewHandler(driver Driver) *Handler { + h := &Handler{driver, sdk.NewHandler(manifest)} + h.initMux() + return h +} + +func (h *Handler) initMux() { + h.HandleFunc(createPath, func(w http.ResponseWriter, r *http.Request) { + req := &CreateRequest{} + err := sdk.DecodeRequest(w, r, req) + if err != nil { + return + } + err = h.driver.Create(req) + if err != nil { + sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true) + return + } + sdk.EncodeResponse(w, struct{}{}, false) + }) + h.HandleFunc(removePath, func(w http.ResponseWriter, r *http.Request) { + req := &RemoveRequest{} + err := sdk.DecodeRequest(w, r, req) + if err != nil { + return + } + err = h.driver.Remove(req) + if err != nil { + sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true) + return + } + sdk.EncodeResponse(w, struct{}{}, false) + }) + h.HandleFunc(mountPath, func(w http.ResponseWriter, r *http.Request) { + req := &MountRequest{} + err := sdk.DecodeRequest(w, r, req) + if err != nil { + return + } + res, err := h.driver.Mount(req) + if err != nil { + sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true) + return + } + sdk.EncodeResponse(w, res, false) + }) + h.HandleFunc(hostVirtualPath, func(w http.ResponseWriter, r *http.Request) { + req := &PathRequest{} + err := sdk.DecodeRequest(w, r, req) + if err != nil { + return + } + res, err := h.driver.Path(req) + if err != nil { + sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true) + return + } + sdk.EncodeResponse(w, res, false) + }) + h.HandleFunc(getPath, func(w http.ResponseWriter, r *http.Request) { + req := &GetRequest{} + err := sdk.DecodeRequest(w, r, req) + if err != nil { + return + } + res, err := h.driver.Get(req) + if err != nil { + sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true) + return + } + sdk.EncodeResponse(w, res, false) + }) + h.HandleFunc(unmountPath, func(w http.ResponseWriter, r *http.Request) { + req := &UnmountRequest{} + err := sdk.DecodeRequest(w, r, req) + if err != nil { + return + } + err = h.driver.Unmount(req) + if err != nil { + sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true) + return + } + sdk.EncodeResponse(w, struct{}{}, false) + }) + h.HandleFunc(listPath, func(w http.ResponseWriter, r *http.Request) { + res, err := h.driver.List() + if err != nil { + sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true) + return + } + sdk.EncodeResponse(w, res, false) + }) + + h.HandleFunc(capabilitiesPath, func(w http.ResponseWriter, r *http.Request) { + sdk.EncodeResponse(w, h.driver.Capabilities(), false) + }) +} diff --git a/vendor/github.com/ebitengine/purego/.gitignore b/vendor/github.com/ebitengine/purego/.gitignore new file mode 100644 index 00000000..b25c15b8 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/vendor/github.com/ebitengine/purego/LICENSE b/vendor/github.com/ebitengine/purego/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/github.com/ebitengine/purego/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/ebitengine/purego/README.md b/vendor/github.com/ebitengine/purego/README.md new file mode 100644 index 00000000..523e9118 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/README.md @@ -0,0 +1,113 @@ +# purego +[![Go Reference](https://pkg.go.dev/badge/github.com/ebitengine/purego?GOOS=darwin.svg)](https://pkg.go.dev/github.com/ebitengine/purego?GOOS=darwin) + +A library for calling C functions from Go without Cgo. + +> This is beta software so expect bugs and potentially API breaking changes +> but each release will be tagged to avoid breaking people's code. +> Bug reports are encouraged. + +## Motivation + +The [Ebitengine](https://github.com/hajimehoshi/ebiten) game engine was ported to use only Go on Windows. This enabled +cross-compiling to Windows from any other operating system simply by setting `GOOS=windows`. The purego project was +born to bring that same vision to the other platforms supported by Ebitengine. + +## Benefits + +- **Simple Cross-Compilation**: No C means you can build for other platforms easily without a C compiler. +- **Faster Compilation**: Efficiently cache your entirely Go builds. +- **Smaller Binaries**: Using Cgo generates a C wrapper function for each C function called. Purego doesn't! +- **Dynamic Linking**: Load symbols at runtime and use it as a plugin system. +- **Foreign Function Interface**: Call into other languages that are compiled into shared objects. +- **Cgo Fallback**: Works even with CGO_ENABLED=1 so incremental porting is possible. +This also means unsupported GOARCHs (freebsd/riscv64, linux/mips, etc.) will still work +except for float arguments and return values. + +## Supported Platforms + +### Tier 1 + +Tier 1 platforms are the primary targets officially supported by PureGo. When a new version of PureGo is released, any critical bugs found on Tier 1 platforms are treated as release blockers. The release will be postponed until such issues are resolved. + +- **Android**: amd64, arm64 +- **iOS**: amd64, arm64 +- **Linux**: amd64, arm64 +- **macOS**: amd64, arm64 +- **Windows**: amd64, arm64 + +### Tier 2 + +Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs on Tier 2 platforms do not block new PureGo releases. However, fixes contributed by external contributors are very welcome and encouraged. + +- **Android**: 386, arm +- **FreeBSD**: amd64, arm64 +- **Linux**: 386, arm, loong64 +- **Windows**: 386*, arm* + +`*` These architectures only support `SyscallN` and `NewCallback` + +## Example + +The example below only showcases purego use for macOS and Linux. The other platforms require special handling which can +be seen in the complete example at [examples/libc](https://github.com/ebitengine/purego/tree/main/examples/libc) which supports FreeBSD and Windows. + +```go +package main + +import ( + "fmt" + "runtime" + + "github.com/ebitengine/purego" +) + +func getSystemLibrary() string { + switch runtime.GOOS { + case "darwin": + return "/usr/lib/libSystem.B.dylib" + case "linux": + return "libc.so.6" + default: + panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)) + } +} + +func main() { + libc, err := purego.Dlopen(getSystemLibrary(), purego.RTLD_NOW|purego.RTLD_GLOBAL) + if err != nil { + panic(err) + } + var puts func(string) + purego.RegisterLibFunc(&puts, libc, "puts") + puts("Calling C from Go without Cgo!") +} +``` + +Then to run: `CGO_ENABLED=0 go run main.go` + +## Questions + +If you have questions about how to incorporate purego in your project or want to discuss +how it works join the [Discord](https://discord.gg/HzGZVD6BkY)! + +### External Code + +Purego uses code that originates from the Go runtime. These files are under the BSD-3 +License that can be found [in the Go Source](https://github.com/golang/go/blob/master/LICENSE). +This is a list of the copied files: + +* `abi_*.h` from package `runtime/cgo` +* `wincallback.go` from package `runtime` +* `zcallback_darwin_*.s` from package `runtime` +* `internal/fakecgo/abi_*.h` from package `runtime/cgo` +* `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo` +* `internal/fakecgo/callbacks.go` from package `runtime/cgo` +* `internal/fakecgo/go_GOOS_GOARCH.go` from package `runtime/cgo` +* `internal/fakecgo/iscgo.go` from package `runtime/cgo` +* `internal/fakecgo/setenv.go` from package `runtime/cgo` +* `internal/fakecgo/freebsd.go` from package `runtime/cgo` +* `internal/fakecgo/netbsd.go` from package `runtime/cgo` + +The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of +`#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636)) diff --git a/vendor/github.com/ebitengine/purego/abi_amd64.h b/vendor/github.com/ebitengine/purego/abi_amd64.h new file mode 100644 index 00000000..9949435f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/abi_amd64.h @@ -0,0 +1,99 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These save the frame pointer, so in general, functions that use +// these should have zero frame size to suppress the automatic frame +// pointer, though it's harmless to not do this. + +#ifdef GOOS_windows + +// REGS_HOST_TO_ABI0_STACK is the stack bytes used by +// PUSH_REGS_HOST_TO_ABI0. +#define REGS_HOST_TO_ABI0_STACK (28*8 + 8) + +// PUSH_REGS_HOST_TO_ABI0 prepares for transitioning from +// the host ABI to Go ABI0 code. It saves all registers that are +// callee-save in the host ABI and caller-save in Go ABI0 and prepares +// for entry to Go. +// +// Save DI SI BP BX R12 R13 R14 R15 X6-X15 registers and the DF flag. +// Clear the DF flag for the Go ABI. +// MXCSR matches the Go ABI, so we don't have to set that, +// and Go doesn't modify it, so we don't have to save it. +#define PUSH_REGS_HOST_TO_ABI0() \ + PUSHFQ \ + CLD \ + ADJSP $(REGS_HOST_TO_ABI0_STACK - 8) \ + MOVQ DI, (0*0)(SP) \ + MOVQ SI, (1*8)(SP) \ + MOVQ BP, (2*8)(SP) \ + MOVQ BX, (3*8)(SP) \ + MOVQ R12, (4*8)(SP) \ + MOVQ R13, (5*8)(SP) \ + MOVQ R14, (6*8)(SP) \ + MOVQ R15, (7*8)(SP) \ + MOVUPS X6, (8*8)(SP) \ + MOVUPS X7, (10*8)(SP) \ + MOVUPS X8, (12*8)(SP) \ + MOVUPS X9, (14*8)(SP) \ + MOVUPS X10, (16*8)(SP) \ + MOVUPS X11, (18*8)(SP) \ + MOVUPS X12, (20*8)(SP) \ + MOVUPS X13, (22*8)(SP) \ + MOVUPS X14, (24*8)(SP) \ + MOVUPS X15, (26*8)(SP) + +#define POP_REGS_HOST_TO_ABI0() \ + MOVQ (0*0)(SP), DI \ + MOVQ (1*8)(SP), SI \ + MOVQ (2*8)(SP), BP \ + MOVQ (3*8)(SP), BX \ + MOVQ (4*8)(SP), R12 \ + MOVQ (5*8)(SP), R13 \ + MOVQ (6*8)(SP), R14 \ + MOVQ (7*8)(SP), R15 \ + MOVUPS (8*8)(SP), X6 \ + MOVUPS (10*8)(SP), X7 \ + MOVUPS (12*8)(SP), X8 \ + MOVUPS (14*8)(SP), X9 \ + MOVUPS (16*8)(SP), X10 \ + MOVUPS (18*8)(SP), X11 \ + MOVUPS (20*8)(SP), X12 \ + MOVUPS (22*8)(SP), X13 \ + MOVUPS (24*8)(SP), X14 \ + MOVUPS (26*8)(SP), X15 \ + ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8) \ + POPFQ + +#else +// SysV ABI + +#define REGS_HOST_TO_ABI0_STACK (6*8) + +// SysV MXCSR matches the Go ABI, so we don't have to set that, +// and Go doesn't modify it, so we don't have to save it. +// Both SysV and Go require DF to be cleared, so that's already clear. +// The SysV and Go frame pointer conventions are compatible. +#define PUSH_REGS_HOST_TO_ABI0() \ + ADJSP $(REGS_HOST_TO_ABI0_STACK) \ + MOVQ BP, (5*8)(SP) \ + LEAQ (5*8)(SP), BP \ + MOVQ BX, (0*8)(SP) \ + MOVQ R12, (1*8)(SP) \ + MOVQ R13, (2*8)(SP) \ + MOVQ R14, (3*8)(SP) \ + MOVQ R15, (4*8)(SP) + +#define POP_REGS_HOST_TO_ABI0() \ + MOVQ (0*8)(SP), BX \ + MOVQ (1*8)(SP), R12 \ + MOVQ (2*8)(SP), R13 \ + MOVQ (3*8)(SP), R14 \ + MOVQ (4*8)(SP), R15 \ + MOVQ (5*8)(SP), BP \ + ADJSP $-(REGS_HOST_TO_ABI0_STACK) + +#endif diff --git a/vendor/github.com/ebitengine/purego/abi_arm64.h b/vendor/github.com/ebitengine/purego/abi_arm64.h new file mode 100644 index 00000000..5d5061ec --- /dev/null +++ b/vendor/github.com/ebitengine/purego/abi_arm64.h @@ -0,0 +1,39 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These macros save and restore the callee-saved registers +// from the stack, but they don't adjust stack pointer, so +// the user should prepare stack space in advance. +// SAVE_R19_TO_R28(offset) saves R19 ~ R28 to the stack space +// of ((offset)+0*8)(RSP) ~ ((offset)+9*8)(RSP). +// +// SAVE_F8_TO_F15(offset) saves F8 ~ F15 to the stack space +// of ((offset)+0*8)(RSP) ~ ((offset)+7*8)(RSP). +// +// R29 is not saved because Go will save and restore it. + +#define SAVE_R19_TO_R28(offset) \ + STP (R19, R20), ((offset)+0*8)(RSP) \ + STP (R21, R22), ((offset)+2*8)(RSP) \ + STP (R23, R24), ((offset)+4*8)(RSP) \ + STP (R25, R26), ((offset)+6*8)(RSP) \ + STP (R27, g), ((offset)+8*8)(RSP) +#define RESTORE_R19_TO_R28(offset) \ + LDP ((offset)+0*8)(RSP), (R19, R20) \ + LDP ((offset)+2*8)(RSP), (R21, R22) \ + LDP ((offset)+4*8)(RSP), (R23, R24) \ + LDP ((offset)+6*8)(RSP), (R25, R26) \ + LDP ((offset)+8*8)(RSP), (R27, g) /* R28 */ +#define SAVE_F8_TO_F15(offset) \ + FSTPD (F8, F9), ((offset)+0*8)(RSP) \ + FSTPD (F10, F11), ((offset)+2*8)(RSP) \ + FSTPD (F12, F13), ((offset)+4*8)(RSP) \ + FSTPD (F14, F15), ((offset)+6*8)(RSP) +#define RESTORE_F8_TO_F15(offset) \ + FLDPD ((offset)+0*8)(RSP), (F8, F9) \ + FLDPD ((offset)+2*8)(RSP), (F10, F11) \ + FLDPD ((offset)+4*8)(RSP), (F12, F13) \ + FLDPD ((offset)+6*8)(RSP), (F14, F15) diff --git a/vendor/github.com/ebitengine/purego/abi_loong64.h b/vendor/github.com/ebitengine/purego/abi_loong64.h new file mode 100644 index 00000000..b10d8373 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/abi_loong64.h @@ -0,0 +1,60 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These macros save and restore the callee-saved registers +// from the stack, but they don't adjust stack pointer, so +// the user should prepare stack space in advance. +// SAVE_R22_TO_R31(offset) saves R22 ~ R31 to the stack space +// of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3). +// +// SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space +// of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3). +// +// Note: g is R22 + +#define SAVE_R22_TO_R31(offset) \ + MOVV g, ((offset)+(0*8))(R3) \ + MOVV R23, ((offset)+(1*8))(R3) \ + MOVV R24, ((offset)+(2*8))(R3) \ + MOVV R25, ((offset)+(3*8))(R3) \ + MOVV R26, ((offset)+(4*8))(R3) \ + MOVV R27, ((offset)+(5*8))(R3) \ + MOVV R28, ((offset)+(6*8))(R3) \ + MOVV R29, ((offset)+(7*8))(R3) \ + MOVV R30, ((offset)+(8*8))(R3) \ + MOVV R31, ((offset)+(9*8))(R3) + +#define SAVE_F24_TO_F31(offset) \ + MOVD F24, ((offset)+(0*8))(R3) \ + MOVD F25, ((offset)+(1*8))(R3) \ + MOVD F26, ((offset)+(2*8))(R3) \ + MOVD F27, ((offset)+(3*8))(R3) \ + MOVD F28, ((offset)+(4*8))(R3) \ + MOVD F29, ((offset)+(5*8))(R3) \ + MOVD F30, ((offset)+(6*8))(R3) \ + MOVD F31, ((offset)+(7*8))(R3) + +#define RESTORE_R22_TO_R31(offset) \ + MOVV ((offset)+(0*8))(R3), g \ + MOVV ((offset)+(1*8))(R3), R23 \ + MOVV ((offset)+(2*8))(R3), R24 \ + MOVV ((offset)+(3*8))(R3), R25 \ + MOVV ((offset)+(4*8))(R3), R26 \ + MOVV ((offset)+(5*8))(R3), R27 \ + MOVV ((offset)+(6*8))(R3), R28 \ + MOVV ((offset)+(7*8))(R3), R29 \ + MOVV ((offset)+(8*8))(R3), R30 \ + MOVV ((offset)+(9*8))(R3), R31 + +#define RESTORE_F24_TO_F31(offset) \ + MOVD ((offset)+(0*8))(R3), F24 \ + MOVD ((offset)+(1*8))(R3), F25 \ + MOVD ((offset)+(2*8))(R3), F26 \ + MOVD ((offset)+(3*8))(R3), F27 \ + MOVD ((offset)+(4*8))(R3), F28 \ + MOVD ((offset)+(5*8))(R3), F29 \ + MOVD ((offset)+(6*8))(R3), F30 \ + MOVD ((offset)+(7*8))(R3), F31 diff --git a/vendor/github.com/ebitengine/purego/cgo.go b/vendor/github.com/ebitengine/purego/cgo.go new file mode 100644 index 00000000..32bb256a --- /dev/null +++ b/vendor/github.com/ebitengine/purego/cgo.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build cgo && (darwin || freebsd || linux || netbsd) + +package purego + +// if CGO_ENABLED=1 import the Cgo runtime to ensure that it is set up properly. +// This is required since some frameworks need TLS setup the C way which Go doesn't do. +// We currently don't support ios in fakecgo mode so force Cgo or fail +// Even if CGO_ENABLED=1 the Cgo runtime is not imported unless `import "C"` is used. +// which will import this package automatically. Normally this isn't an issue since it +// usually isn't possible to call into C without using that import. However, with purego +// it is since we don't use `import "C"`! +import ( + _ "runtime/cgo" + + _ "github.com/ebitengine/purego/internal/cgo" +) diff --git a/vendor/github.com/ebitengine/purego/dlerror.go b/vendor/github.com/ebitengine/purego/dlerror.go new file mode 100644 index 00000000..ad52b436 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlerror.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 The Ebitengine Authors + +//go:build darwin || freebsd || linux || netbsd + +package purego + +// Dlerror represents an error value returned from Dlopen, Dlsym, or Dlclose. +// +// This type is not available on Windows as there is no counterpart to it on Windows. +type Dlerror struct { + s string +} + +func (e Dlerror) Error() string { + return e.s +} diff --git a/vendor/github.com/ebitengine/purego/dlfcn.go b/vendor/github.com/ebitengine/purego/dlfcn.go new file mode 100644 index 00000000..2730d82c --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn.go @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build (darwin || freebsd || linux || netbsd) && !android && !faketime + +package purego + +import ( + "unsafe" +) + +// Unix Specification for dlfcn.h: https://pubs.opengroup.org/onlinepubs/7908799/xsh/dlfcn.h.html + +var ( + fnDlopen func(path string, mode int) uintptr + fnDlsym func(handle uintptr, name string) uintptr + fnDlerror func() string + fnDlclose func(handle uintptr) bool +) + +func init() { + RegisterFunc(&fnDlopen, dlopenABI0) + RegisterFunc(&fnDlsym, dlsymABI0) + RegisterFunc(&fnDlerror, dlerrorABI0) + RegisterFunc(&fnDlclose, dlcloseABI0) +} + +// Dlopen examines the dynamic library or bundle file specified by path. If the file is compatible +// with the current process and has not already been loaded into the +// current process, it is loaded and linked. After being linked, if it contains +// any initializer functions, they are called, before Dlopen +// returns. It returns a handle that can be used with Dlsym and Dlclose. +// A second call to Dlopen with the same path will return the same handle, but the internal +// reference count for the handle will be incremented. Therefore, all +// Dlopen calls should be balanced with a Dlclose call. +// +// This function is not available on Windows. +// Use [golang.org/x/sys/windows.LoadLibrary], [golang.org/x/sys/windows.LoadLibraryEx], +// [golang.org/x/sys/windows.NewLazyDLL], or [golang.org/x/sys/windows.NewLazySystemDLL] for Windows instead. +func Dlopen(path string, mode int) (uintptr, error) { + u := fnDlopen(path, mode) + if u == 0 { + return 0, Dlerror{fnDlerror()} + } + return u, nil +} + +// Dlsym takes a "handle" of a dynamic library returned by Dlopen and the symbol name. +// It returns the address where that symbol is loaded into memory. If the symbol is not found, +// in the specified library or any of the libraries that were automatically loaded by Dlopen +// when that library was loaded, Dlsym returns zero. +// +// This function is not available on Windows. +// Use [golang.org/x/sys/windows.GetProcAddress] for Windows instead. +func Dlsym(handle uintptr, name string) (uintptr, error) { + u := fnDlsym(handle, name) + if u == 0 { + return 0, Dlerror{fnDlerror()} + } + return u, nil +} + +// Dlclose decrements the reference count on the dynamic library handle. +// If the reference count drops to zero and no other loaded libraries +// use symbols in it, then the dynamic library is unloaded. +// +// This function is not available on Windows. +// Use [golang.org/x/sys/windows.FreeLibrary] for Windows instead. +func Dlclose(handle uintptr) error { + if fnDlclose(handle) { + return Dlerror{fnDlerror()} + } + return nil +} + +func loadSymbol(handle uintptr, name string) (uintptr, error) { + return Dlsym(handle, name) +} + +// these functions exist in dlfcn_stubs.s and are calling C functions linked to in dlfcn_GOOS.go +// the indirection is necessary because a function is actually a pointer to the pointer to the code. +// sadly, I do not know of anyway to remove the assembly stubs entirely because //go:linkname doesn't +// appear to work if you link directly to the C function on darwin arm64. + +//go:linkname dlopen dlopen +var dlopen uint8 +var dlopenABI0 = uintptr(unsafe.Pointer(&dlopen)) + +//go:linkname dlsym dlsym +var dlsym uint8 +var dlsymABI0 = uintptr(unsafe.Pointer(&dlsym)) + +//go:linkname dlclose dlclose +var dlclose uint8 +var dlcloseABI0 = uintptr(unsafe.Pointer(&dlclose)) + +//go:linkname dlerror dlerror +var dlerror uint8 +var dlerrorABI0 = uintptr(unsafe.Pointer(&dlerror)) diff --git a/vendor/github.com/ebitengine/purego/dlfcn_android.go b/vendor/github.com/ebitengine/purego/dlfcn_android.go new file mode 100644 index 00000000..0d534176 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn_android.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +package purego + +import "github.com/ebitengine/purego/internal/cgo" + +// Source for constants: https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/include/dlfcn.h + +const ( + is64bit = 1 << (^uintptr(0) >> 63) / 2 + is32bit = 1 - is64bit + RTLD_DEFAULT = is32bit * 0xffffffff + RTLD_LAZY = 0x00000001 + RTLD_NOW = is64bit * 0x00000002 + RTLD_LOCAL = 0x00000000 + RTLD_GLOBAL = is64bit*0x00100 | is32bit*0x00000002 +) + +func Dlopen(path string, mode int) (uintptr, error) { + return cgo.Dlopen(path, mode) +} + +func Dlsym(handle uintptr, name string) (uintptr, error) { + return cgo.Dlsym(handle, name) +} + +func Dlclose(handle uintptr) error { + return cgo.Dlclose(handle) +} + +func loadSymbol(handle uintptr, name string) (uintptr, error) { + return Dlsym(handle, name) +} diff --git a/vendor/github.com/ebitengine/purego/dlfcn_darwin.go b/vendor/github.com/ebitengine/purego/dlfcn_darwin.go new file mode 100644 index 00000000..27f56071 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn_darwin.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package purego + +// Source for constants: https://opensource.apple.com/source/dyld/dyld-360.14/include/dlfcn.h.auto.html + +const ( + RTLD_DEFAULT = 1<<64 - 2 // Pseudo-handle for dlsym so search for any loaded symbol + RTLD_LAZY = 0x1 // Relocations are performed at an implementation-dependent time. + RTLD_NOW = 0x2 // Relocations are performed when the object is loaded. + RTLD_LOCAL = 0x4 // All symbols are not made available for relocation processing by other modules. + RTLD_GLOBAL = 0x8 // All symbols are available for relocation processing of other modules. +) + +//go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib" diff --git a/vendor/github.com/ebitengine/purego/dlfcn_freebsd.go b/vendor/github.com/ebitengine/purego/dlfcn_freebsd.go new file mode 100644 index 00000000..6b371620 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/dlfcn_freebsd.go @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package purego + +// Constants as defined in https://github.com/freebsd/freebsd-src/blob/main/include/dlfcn.h +const ( + intSize = 32 << (^uint(0) >> 63) // 32 or 64 + RTLD_DEFAULT = 1<> 63) // 32 or 64 + RTLD_DEFAULT = 1< C) +// +// string <=> char* +// bool <=> _Bool +// uintptr <=> uintptr_t +// uint <=> uint32_t or uint64_t +// uint8 <=> uint8_t +// uint16 <=> uint16_t +// uint32 <=> uint32_t +// uint64 <=> uint64_t +// int <=> int32_t or int64_t +// int8 <=> int8_t +// int16 <=> int16_t +// int32 <=> int32_t +// int64 <=> int64_t +// float32 <=> float +// float64 <=> double +// struct <=> struct (WIP - darwin only) +// func <=> C function +// unsafe.Pointer, *T <=> void* +// []T => void* +// +// There is a special case when the last argument of fptr is a variadic interface (or []interface} +// it will be expanded into a call to the C function as if it had the arguments in that slice. +// This means that using arg ...any is like a cast to the function with the arguments inside arg. +// This is not the same as C variadic. +// +// # Memory +// +// In general it is not possible for purego to guarantee the lifetimes of objects returned or received from +// calling functions using RegisterFunc. For arguments to a C function it is important that the C function doesn't +// hold onto a reference to Go memory. This is the same as the [Cgo rules]. +// +// However, there are some special cases. When passing a string as an argument if the string does not end in a null +// terminated byte (\x00) then the string will be copied into memory maintained by purego. The memory is only valid for +// that specific call. Therefore, if the C code keeps a reference to that string it may become invalid at some +// undefined time. However, if the string does already contain a null-terminated byte then no copy is done. +// It is then the responsibility of the caller to ensure the string stays alive as long as it's needed in C memory. +// This can be done using runtime.KeepAlive or allocating the string in C memory using malloc. When a C function +// returns a null-terminated pointer to char a Go string can be used. Purego will allocate a new string in Go memory +// and copy the data over. This string will be garbage collected whenever Go decides it's no longer referenced. +// This C created string will not be freed by purego. If the pointer to char is not null-terminated or must continue +// to point to C memory (because it's a buffer for example) then use a pointer to byte and then convert that to a slice +// using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime +// of the pointer +// +// # Structs +// +// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However, +// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure +// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example. +// +// # Example +// +// All functions below call this C function: +// +// char *foo(char *str); +// +// // Let purego convert types +// var foo func(s string) string +// goString := foo("copied") +// // Go will garbage collect this string +// +// // Manually, handle allocations +// var foo2 func(b string) *byte +// mustFree := foo2("not copied\x00") +// defer free(mustFree) +// +// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C +func RegisterFunc(fptr any, cfn uintptr) { + fn := reflect.ValueOf(fptr).Elem() + ty := fn.Type() + if ty.Kind() != reflect.Func { + panic("purego: fptr must be a function pointer") + } + if ty.NumOut() > 1 { + panic("purego: function can only return zero or one values") + } + if cfn == 0 { + panic("purego: cfn is nil") + } + if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && + runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { + panic("purego: float returns are not supported") + } + { + // this code checks how many registers and stack this function will use + // to avoid crashing with too many arguments + var ints int + var floats int + var stack int + for i := 0; i < ty.NumIn(); i++ { + arg := ty.In(i) + switch arg.Kind() { + case reflect.Func: + // This only does preliminary testing to ensure the CDecl argument + // is the first argument. Full testing is done when the callback is actually + // created in NewCallback. + for j := 0; j < arg.NumIn(); j++ { + in := arg.In(j) + if !in.AssignableTo(reflect.TypeOf(CDecl{})) { + continue + } + if j != 0 { + panic("purego: CDecl must be the first argument") + } + } + case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, + reflect.Slice, reflect.Bool: + if ints < numOfIntegerRegisters() { + ints++ + } else { + stack++ + } + case reflect.Float32, reflect.Float64: + const is32bit = unsafe.Sizeof(uintptr(0)) == 4 + if is32bit { + panic("purego: floats only supported on 64bit platforms") + } + if floats < numOfFloatRegisters { + floats++ + } else { + stack++ + } + case reflect.Struct: + if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") { + panic("purego: struct arguments are only supported on darwin amd64 & arm64") + } + if arg.Size() == 0 { + continue + } + addInt := func(u uintptr) { + ints++ + } + addFloat := func(u uintptr) { + floats++ + } + addStack := func(u uintptr) { + stack++ + } + _ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil) + default: + panic("purego: unsupported kind " + arg.Kind().String()) + } + } + if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { + if runtime.GOOS != "darwin" { + panic("purego: struct return values only supported on darwin arm64 & amd64") + } + outType := ty.Out(0) + checkStructFieldsSupported(outType) + if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { + // on amd64 if struct is bigger than 16 bytes allocate the return struct + // and pass it in as a hidden first argument. + ints++ + } + } + sizeOfStack := maxArgs - numOfIntegerRegisters() + if stack > sizeOfStack { + panic("purego: too many arguments") + } + } + v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { + var sysargs [maxArgs]uintptr + var floats [numOfFloatRegisters]uintptr + var numInts int + var numFloats int + var numStack int + var addStack, addInt, addFloat func(x uintptr) + if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { + // Windows arm64 uses the same calling convention as macOS and Linux + addStack = func(x uintptr) { + sysargs[numOfIntegerRegisters()+numStack] = x + numStack++ + } + addInt = func(x uintptr) { + if numInts >= numOfIntegerRegisters() { + addStack(x) + } else { + sysargs[numInts] = x + numInts++ + } + } + addFloat = func(x uintptr) { + if numFloats < len(floats) { + floats[numFloats] = x + numFloats++ + } else { + addStack(x) + } + } + } else { + // On Windows amd64 the arguments are passed in the numbered registered. + // So the first int is in the first integer register and the first float + // is in the second floating register if there is already a first int. + // This is in contrast to how macOS and Linux pass arguments which + // tries to use as many registers as possible in the calling convention. + addStack = func(x uintptr) { + sysargs[numStack] = x + numStack++ + } + addInt = addStack + addFloat = addStack + } + + var keepAlive []any + defer func() { + runtime.KeepAlive(keepAlive) + runtime.KeepAlive(args) + }() + + var arm64_r8 uintptr + if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { + outType := ty.Out(0) + if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64") && outType.Size() > maxRegAllocStructSize { + val := reflect.New(outType) + keepAlive = append(keepAlive, val) + addInt(val.Pointer()) + } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { + isAllFloats, numFields := isAllSameFloat(outType) + if !isAllFloats || numFields > 4 { + val := reflect.New(outType) + keepAlive = append(keepAlive, val) + arm64_r8 = val.Pointer() + } + } + } + for i, v := range args { + if variadic, ok := args[i].Interface().([]any); ok { + if i != len(args)-1 { + panic("purego: can only expand last parameter") + } + for _, x := range variadic { + keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) + } + continue + } + if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" && + (numInts >= numOfIntegerRegisters() || numFloats >= numOfFloatRegisters) && v.Kind() != reflect.Struct { // hit the stack + fields := make([]reflect.StructField, len(args[i:])) + + for j, val := range args[i:] { + if val.Kind() == reflect.String { + ptr := strings.CString(v.String()) + keepAlive = append(keepAlive, ptr) + val = reflect.ValueOf(ptr) + args[i+j] = val + } + fields[j] = reflect.StructField{ + Name: "X" + strconv.Itoa(j), + Type: val.Type(), + } + } + structType := reflect.StructOf(fields) + structInstance := reflect.New(structType).Elem() + for j, val := range args[i:] { + structInstance.Field(j).Set(val) + } + placeRegisters(structInstance, addFloat, addInt) + break + } + keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack) + } + + syscall := thePool.Get().(*syscall15Args) + defer thePool.Put(syscall) + + if runtime.GOARCH == "loong64" { + *syscall = syscall15Args{ + cfn, + sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], + sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], + sysargs[12], sysargs[13], sysargs[14], + floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], + 0, + } + runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) + } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { + // Use the normal arm64 calling convention even on Windows + *syscall = syscall15Args{ + cfn, + sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], + sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], + sysargs[12], sysargs[13], sysargs[14], + floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], + arm64_r8, + } + runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) + } else { + *syscall = syscall15Args{} + // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats + syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], + sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], + sysargs[12], sysargs[13], sysargs[14]) + syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support + } + if ty.NumOut() == 0 { + return nil + } + outType := ty.Out(0) + v := reflect.New(outType).Elem() + switch outType.Kind() { + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v.SetUint(uint64(syscall.a1)) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v.SetInt(int64(syscall.a1)) + case reflect.Bool: + v.SetBool(byte(syscall.a1) != 0) + case reflect.UnsafePointer: + // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer + v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) + case reflect.Ptr: + v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem() + case reflect.Func: + // wrap this C function in a nicely typed Go function + v = reflect.New(outType) + RegisterFunc(v.Interface(), syscall.a1) + case reflect.String: + v.SetString(strings.GoString(syscall.a1)) + case reflect.Float32: + // NOTE: syscall.r2 is only the floating return value on 64bit platforms. + // On 32bit platforms syscall.r2 is the upper part of a 64bit return. + v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) + case reflect.Float64: + // NOTE: syscall.r2 is only the floating return value on 64bit platforms. + // On 32bit platforms syscall.r2 is the upper part of a 64bit return. + v.SetFloat(math.Float64frombits(uint64(syscall.f1))) + case reflect.Struct: + v = getStruct(outType, *syscall) + default: + panic("purego: unsupported return kind: " + outType.Kind().String()) + } + if len(args) > 0 { + // reuse args slice instead of allocating one when possible + args[0] = v + return args[:1] + } else { + return []reflect.Value{v} + } + }) + fn.Set(v) +} + +func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any { + switch v.Kind() { + case reflect.String: + ptr := strings.CString(v.String()) + keepAlive = append(keepAlive, ptr) + addInt(uintptr(unsafe.Pointer(ptr))) + case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + addInt(uintptr(v.Uint())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + addInt(uintptr(v.Int())) + case reflect.Ptr, reflect.UnsafePointer, reflect.Slice: + // There is no need to keepAlive this pointer separately because it is kept alive in the args variable + addInt(v.Pointer()) + case reflect.Func: + addInt(NewCallback(v.Interface())) + case reflect.Bool: + if v.Bool() { + addInt(1) + } else { + addInt(0) + } + case reflect.Float32: + addFloat(uintptr(math.Float32bits(float32(v.Float())))) + case reflect.Float64: + addFloat(uintptr(math.Float64bits(v.Float()))) + case reflect.Struct: + keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) + default: + panic("purego: unsupported kind: " + v.Kind().String()) + } + return keepAlive +} + +// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers. +// if it is bigger than this than enough space must be allocated on the heap and then passed into +// the function as the first parameter on amd64 or in R8 on arm64. +// +// If you change this make sure to update it in objc_runtime_darwin.go +const maxRegAllocStructSize = 16 + +func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) { + allFloats = true + root := ty.Field(0).Type + for root.Kind() == reflect.Struct { + root = root.Field(0).Type + } + first := root.Kind() + if first != reflect.Float32 && first != reflect.Float64 { + allFloats = false + } + for i := 0; i < ty.NumField(); i++ { + f := ty.Field(i).Type + if f.Kind() == reflect.Struct { + var structNumFields int + allFloats, structNumFields = isAllSameFloat(f) + numFields += structNumFields + continue + } + numFields++ + if f.Kind() != first { + allFloats = false + } + } + return allFloats, numFields +} + +func checkStructFieldsSupported(ty reflect.Type) { + for i := 0; i < ty.NumField(); i++ { + f := ty.Field(i).Type + if f.Kind() == reflect.Array { + f = f.Elem() + } else if f.Kind() == reflect.Struct { + checkStructFieldsSupported(f) + continue + } + switch f.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32: + default: + panic(fmt.Sprintf("purego: struct field type %s is not supported", f)) + } + } +} + +func roundUpTo8(val uintptr) uintptr { + return (val + 7) &^ 7 +} + +func numOfIntegerRegisters() int { + switch runtime.GOARCH { + case "arm64", "loong64": + return 8 + case "amd64": + return 6 + default: + // since this platform isn't supported and can therefore only access + // integer registers it is fine to return the maxArgs + return maxArgs + } +} diff --git a/vendor/github.com/ebitengine/purego/gen.go b/vendor/github.com/ebitengine/purego/gen.go new file mode 100644 index 00000000..9cb7c453 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/gen.go @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2025 The Ebitengine Authors + +package purego + +//go:generate go run wincallback.go diff --git a/vendor/github.com/ebitengine/purego/go_runtime.go b/vendor/github.com/ebitengine/purego/go_runtime.go new file mode 100644 index 00000000..b327f786 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/go_runtime.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux || netbsd || windows + +package purego + +import ( + "unsafe" +) + +//go:linkname runtime_cgocall runtime.cgocall +func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go diff --git a/vendor/github.com/ebitengine/purego/internal/cgo/dlfcn_cgo_unix.go b/vendor/github.com/ebitengine/purego/internal/cgo/dlfcn_cgo_unix.go new file mode 100644 index 00000000..6d0571ab --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/cgo/dlfcn_cgo_unix.go @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +//go:build freebsd || linux || netbsd + +package cgo + +/* +#cgo !netbsd LDFLAGS: -ldl + +#include +#include +*/ +import "C" + +import ( + "errors" + "unsafe" +) + +func Dlopen(filename string, flag int) (uintptr, error) { + cfilename := C.CString(filename) + defer C.free(unsafe.Pointer(cfilename)) + handle := C.dlopen(cfilename, C.int(flag)) + if handle == nil { + return 0, errors.New(C.GoString(C.dlerror())) + } + return uintptr(handle), nil +} + +func Dlsym(handle uintptr, symbol string) (uintptr, error) { + csymbol := C.CString(symbol) + defer C.free(unsafe.Pointer(csymbol)) + symbolAddr := C.dlsym(*(*unsafe.Pointer)(unsafe.Pointer(&handle)), csymbol) + if symbolAddr == nil { + return 0, errors.New(C.GoString(C.dlerror())) + } + return uintptr(symbolAddr), nil +} + +func Dlclose(handle uintptr) error { + result := C.dlclose(*(*unsafe.Pointer)(unsafe.Pointer(&handle))) + if result != 0 { + return errors.New(C.GoString(C.dlerror())) + } + return nil +} + +// all that is needed is to assign each dl function because then its +// symbol will then be made available to the linker and linked to inside dlfcn.go +var ( + _ = C.dlopen + _ = C.dlsym + _ = C.dlerror + _ = C.dlclose +) diff --git a/vendor/github.com/ebitengine/purego/internal/cgo/empty.go b/vendor/github.com/ebitengine/purego/internal/cgo/empty.go new file mode 100644 index 00000000..1d7cffe2 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/cgo/empty.go @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +package cgo + +// Empty so that importing this package doesn't cause issue for certain platforms. diff --git a/vendor/github.com/ebitengine/purego/internal/cgo/syscall_cgo_unix.go b/vendor/github.com/ebitengine/purego/internal/cgo/syscall_cgo_unix.go new file mode 100644 index 00000000..10393fec --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/cgo/syscall_cgo_unix.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build freebsd || (linux && !(arm64 || amd64 || loong64)) || netbsd + +package cgo + +// this file is placed inside internal/cgo and not package purego +// because Cgo and assembly files can't be in the same package. + +/* +#cgo !netbsd LDFLAGS: -ldl + +#include +#include +#include +#include + +typedef struct syscall15Args { + uintptr_t fn; + uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15; + uintptr_t f1, f2, f3, f4, f5, f6, f7, f8; + uintptr_t err; +} syscall15Args; + +void syscall15(struct syscall15Args *args) { + assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0); + uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, + uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12, + uintptr_t a13, uintptr_t a14, uintptr_t a15); + *(void**)(&func_name) = (void*)(args->fn); + uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9, + args->a10,args->a11,args->a12,args->a13,args->a14,args->a15); + args->a1 = r1; + args->err = errno; +} + +*/ +import "C" +import "unsafe" + +// assign purego.syscall15XABI0 to the C version of this function. +var Syscall15XABI0 = unsafe.Pointer(C.syscall15) + +//go:nosplit +func Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { + args := C.syscall15Args{ + C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3), + C.uintptr_t(a4), C.uintptr_t(a5), C.uintptr_t(a6), + C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), C.uintptr_t(a10), C.uintptr_t(a11), C.uintptr_t(a12), + C.uintptr_t(a13), C.uintptr_t(a14), C.uintptr_t(a15), 0, 0, 0, 0, 0, 0, 0, 0, 0, + } + C.syscall15(&args) + return uintptr(args.a1), 0, uintptr(args.err) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_amd64.h b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_amd64.h new file mode 100644 index 00000000..9949435f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_amd64.h @@ -0,0 +1,99 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These save the frame pointer, so in general, functions that use +// these should have zero frame size to suppress the automatic frame +// pointer, though it's harmless to not do this. + +#ifdef GOOS_windows + +// REGS_HOST_TO_ABI0_STACK is the stack bytes used by +// PUSH_REGS_HOST_TO_ABI0. +#define REGS_HOST_TO_ABI0_STACK (28*8 + 8) + +// PUSH_REGS_HOST_TO_ABI0 prepares for transitioning from +// the host ABI to Go ABI0 code. It saves all registers that are +// callee-save in the host ABI and caller-save in Go ABI0 and prepares +// for entry to Go. +// +// Save DI SI BP BX R12 R13 R14 R15 X6-X15 registers and the DF flag. +// Clear the DF flag for the Go ABI. +// MXCSR matches the Go ABI, so we don't have to set that, +// and Go doesn't modify it, so we don't have to save it. +#define PUSH_REGS_HOST_TO_ABI0() \ + PUSHFQ \ + CLD \ + ADJSP $(REGS_HOST_TO_ABI0_STACK - 8) \ + MOVQ DI, (0*0)(SP) \ + MOVQ SI, (1*8)(SP) \ + MOVQ BP, (2*8)(SP) \ + MOVQ BX, (3*8)(SP) \ + MOVQ R12, (4*8)(SP) \ + MOVQ R13, (5*8)(SP) \ + MOVQ R14, (6*8)(SP) \ + MOVQ R15, (7*8)(SP) \ + MOVUPS X6, (8*8)(SP) \ + MOVUPS X7, (10*8)(SP) \ + MOVUPS X8, (12*8)(SP) \ + MOVUPS X9, (14*8)(SP) \ + MOVUPS X10, (16*8)(SP) \ + MOVUPS X11, (18*8)(SP) \ + MOVUPS X12, (20*8)(SP) \ + MOVUPS X13, (22*8)(SP) \ + MOVUPS X14, (24*8)(SP) \ + MOVUPS X15, (26*8)(SP) + +#define POP_REGS_HOST_TO_ABI0() \ + MOVQ (0*0)(SP), DI \ + MOVQ (1*8)(SP), SI \ + MOVQ (2*8)(SP), BP \ + MOVQ (3*8)(SP), BX \ + MOVQ (4*8)(SP), R12 \ + MOVQ (5*8)(SP), R13 \ + MOVQ (6*8)(SP), R14 \ + MOVQ (7*8)(SP), R15 \ + MOVUPS (8*8)(SP), X6 \ + MOVUPS (10*8)(SP), X7 \ + MOVUPS (12*8)(SP), X8 \ + MOVUPS (14*8)(SP), X9 \ + MOVUPS (16*8)(SP), X10 \ + MOVUPS (18*8)(SP), X11 \ + MOVUPS (20*8)(SP), X12 \ + MOVUPS (22*8)(SP), X13 \ + MOVUPS (24*8)(SP), X14 \ + MOVUPS (26*8)(SP), X15 \ + ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8) \ + POPFQ + +#else +// SysV ABI + +#define REGS_HOST_TO_ABI0_STACK (6*8) + +// SysV MXCSR matches the Go ABI, so we don't have to set that, +// and Go doesn't modify it, so we don't have to save it. +// Both SysV and Go require DF to be cleared, so that's already clear. +// The SysV and Go frame pointer conventions are compatible. +#define PUSH_REGS_HOST_TO_ABI0() \ + ADJSP $(REGS_HOST_TO_ABI0_STACK) \ + MOVQ BP, (5*8)(SP) \ + LEAQ (5*8)(SP), BP \ + MOVQ BX, (0*8)(SP) \ + MOVQ R12, (1*8)(SP) \ + MOVQ R13, (2*8)(SP) \ + MOVQ R14, (3*8)(SP) \ + MOVQ R15, (4*8)(SP) + +#define POP_REGS_HOST_TO_ABI0() \ + MOVQ (0*8)(SP), BX \ + MOVQ (1*8)(SP), R12 \ + MOVQ (2*8)(SP), R13 \ + MOVQ (3*8)(SP), R14 \ + MOVQ (4*8)(SP), R15 \ + MOVQ (5*8)(SP), BP \ + ADJSP $-(REGS_HOST_TO_ABI0_STACK) + +#endif diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_arm64.h b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_arm64.h new file mode 100644 index 00000000..5d5061ec --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_arm64.h @@ -0,0 +1,39 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These macros save and restore the callee-saved registers +// from the stack, but they don't adjust stack pointer, so +// the user should prepare stack space in advance. +// SAVE_R19_TO_R28(offset) saves R19 ~ R28 to the stack space +// of ((offset)+0*8)(RSP) ~ ((offset)+9*8)(RSP). +// +// SAVE_F8_TO_F15(offset) saves F8 ~ F15 to the stack space +// of ((offset)+0*8)(RSP) ~ ((offset)+7*8)(RSP). +// +// R29 is not saved because Go will save and restore it. + +#define SAVE_R19_TO_R28(offset) \ + STP (R19, R20), ((offset)+0*8)(RSP) \ + STP (R21, R22), ((offset)+2*8)(RSP) \ + STP (R23, R24), ((offset)+4*8)(RSP) \ + STP (R25, R26), ((offset)+6*8)(RSP) \ + STP (R27, g), ((offset)+8*8)(RSP) +#define RESTORE_R19_TO_R28(offset) \ + LDP ((offset)+0*8)(RSP), (R19, R20) \ + LDP ((offset)+2*8)(RSP), (R21, R22) \ + LDP ((offset)+4*8)(RSP), (R23, R24) \ + LDP ((offset)+6*8)(RSP), (R25, R26) \ + LDP ((offset)+8*8)(RSP), (R27, g) /* R28 */ +#define SAVE_F8_TO_F15(offset) \ + FSTPD (F8, F9), ((offset)+0*8)(RSP) \ + FSTPD (F10, F11), ((offset)+2*8)(RSP) \ + FSTPD (F12, F13), ((offset)+4*8)(RSP) \ + FSTPD (F14, F15), ((offset)+6*8)(RSP) +#define RESTORE_F8_TO_F15(offset) \ + FLDPD ((offset)+0*8)(RSP), (F8, F9) \ + FLDPD ((offset)+2*8)(RSP), (F10, F11) \ + FLDPD ((offset)+4*8)(RSP), (F12, F13) \ + FLDPD ((offset)+6*8)(RSP), (F14, F15) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_loong64.h b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_loong64.h new file mode 100644 index 00000000..b10d8373 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/abi_loong64.h @@ -0,0 +1,60 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Macros for transitioning from the host ABI to Go ABI0. +// +// These macros save and restore the callee-saved registers +// from the stack, but they don't adjust stack pointer, so +// the user should prepare stack space in advance. +// SAVE_R22_TO_R31(offset) saves R22 ~ R31 to the stack space +// of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3). +// +// SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space +// of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3). +// +// Note: g is R22 + +#define SAVE_R22_TO_R31(offset) \ + MOVV g, ((offset)+(0*8))(R3) \ + MOVV R23, ((offset)+(1*8))(R3) \ + MOVV R24, ((offset)+(2*8))(R3) \ + MOVV R25, ((offset)+(3*8))(R3) \ + MOVV R26, ((offset)+(4*8))(R3) \ + MOVV R27, ((offset)+(5*8))(R3) \ + MOVV R28, ((offset)+(6*8))(R3) \ + MOVV R29, ((offset)+(7*8))(R3) \ + MOVV R30, ((offset)+(8*8))(R3) \ + MOVV R31, ((offset)+(9*8))(R3) + +#define SAVE_F24_TO_F31(offset) \ + MOVD F24, ((offset)+(0*8))(R3) \ + MOVD F25, ((offset)+(1*8))(R3) \ + MOVD F26, ((offset)+(2*8))(R3) \ + MOVD F27, ((offset)+(3*8))(R3) \ + MOVD F28, ((offset)+(4*8))(R3) \ + MOVD F29, ((offset)+(5*8))(R3) \ + MOVD F30, ((offset)+(6*8))(R3) \ + MOVD F31, ((offset)+(7*8))(R3) + +#define RESTORE_R22_TO_R31(offset) \ + MOVV ((offset)+(0*8))(R3), g \ + MOVV ((offset)+(1*8))(R3), R23 \ + MOVV ((offset)+(2*8))(R3), R24 \ + MOVV ((offset)+(3*8))(R3), R25 \ + MOVV ((offset)+(4*8))(R3), R26 \ + MOVV ((offset)+(5*8))(R3), R27 \ + MOVV ((offset)+(6*8))(R3), R28 \ + MOVV ((offset)+(7*8))(R3), R29 \ + MOVV ((offset)+(8*8))(R3), R30 \ + MOVV ((offset)+(9*8))(R3), R31 + +#define RESTORE_F24_TO_F31(offset) \ + MOVD ((offset)+(0*8))(R3), F24 \ + MOVD ((offset)+(1*8))(R3), F25 \ + MOVD ((offset)+(2*8))(R3), F26 \ + MOVD ((offset)+(3*8))(R3), F27 \ + MOVD ((offset)+(4*8))(R3), F28 \ + MOVD ((offset)+(5*8))(R3), F29 \ + MOVD ((offset)+(6*8))(R3), F30 \ + MOVD ((offset)+(7*8))(R3), F31 diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_amd64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_amd64.s new file mode 100644 index 00000000..2b7eb57f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_amd64.s @@ -0,0 +1,39 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" +#include "abi_amd64.h" + +// Called by C code generated by cmd/cgo. +// func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) +// Saves C callee-saved registers and calls cgocallback with three arguments. +// fn is the PC of a func(a unsafe.Pointer) function. +// This signature is known to SWIG, so we can't change it. +TEXT crosscall2(SB), NOSPLIT, $0-0 + PUSH_REGS_HOST_TO_ABI0() + + // Make room for arguments to cgocallback. + ADJSP $0x18 + +#ifndef GOOS_windows + MOVQ DI, 0x0(SP) // fn + MOVQ SI, 0x8(SP) // arg + + // Skip n in DX. + MOVQ CX, 0x10(SP) // ctxt + +#else + MOVQ CX, 0x0(SP) // fn + MOVQ DX, 0x8(SP) // arg + + // Skip n in R8. + MOVQ R9, 0x10(SP) // ctxt + +#endif + + CALL runtime·cgocallback(SB) + + ADJSP $-0x18 + POP_REGS_HOST_TO_ABI0() + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm64.s new file mode 100644 index 00000000..50e5261d --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_arm64.s @@ -0,0 +1,36 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" +#include "abi_arm64.h" + +// Called by C code generated by cmd/cgo. +// func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) +// Saves C callee-saved registers and calls cgocallback with three arguments. +// fn is the PC of a func(a unsafe.Pointer) function. +TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 +/* + * We still need to save all callee save register as before, and then + * push 3 args for fn (R0, R1, R3), skipping R2. + * Also note that at procedure entry in gc world, 8(RSP) will be the + * first arg. + */ + SUB $(8*24), RSP + STP (R0, R1), (8*1)(RSP) + MOVD R3, (8*3)(RSP) + + SAVE_R19_TO_R28(8*4) + SAVE_F8_TO_F15(8*14) + STP (R29, R30), (8*22)(RSP) + + // Initialize Go ABI environment + BL runtime·load_g(SB) + BL runtime·cgocallback(SB) + + RESTORE_R19_TO_R28(8*4) + RESTORE_F8_TO_F15(8*14) + LDP (8*22)(RSP), (R29, R30) + + ADD $(8*24), RSP + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_loong64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_loong64.s new file mode 100644 index 00000000..aea4f8e6 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/asm_loong64.s @@ -0,0 +1,40 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" +#include "abi_loong64.h" + +// Called by C code generated by cmd/cgo. +// func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) +// Saves C callee-saved registers and calls cgocallback with three arguments. +// fn is the PC of a func(a unsafe.Pointer) function. +TEXT crosscall2(SB),NOSPLIT|NOFRAME,$0 + /* + * We still need to save all callee save register as before, and then + * push 3 args for fn (R4, R5, R7), skipping R6. + * Also note that at procedure entry in gc world, 8(R29) will be the + * first arg. + */ + + ADDV $(-23*8), R3 + MOVV R4, (1*8)(R3) // fn unsafe.Pointer + MOVV R5, (2*8)(R3) // a unsafe.Pointer + MOVV R7, (3*8)(R3) // ctxt uintptr + + SAVE_R22_TO_R31((4*8)) + SAVE_F24_TO_F31((14*8)) + MOVV R1, (22*8)(R3) + + // Initialize Go ABI environment + JAL runtime·load_g(SB) + + JAL runtime·cgocallback(SB) + + RESTORE_R22_TO_R31((4*8)) + RESTORE_F24_TO_F31((14*8)) + MOVV (22*8)(R3), R1 + + ADDV $(23*8), R3 + + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/callbacks.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/callbacks.go new file mode 100644 index 00000000..27d4c98c --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/callbacks.go @@ -0,0 +1,93 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +package fakecgo + +import ( + _ "unsafe" +) + +// TODO: decide if we need _runtime_cgo_panic_internal + +//go:linkname x_cgo_init_trampoline x_cgo_init_trampoline +//go:linkname _cgo_init _cgo_init +var x_cgo_init_trampoline byte +var _cgo_init = &x_cgo_init_trampoline + +// Creates a new system thread without updating any Go state. +// +// This method is invoked during shared library loading to create a new OS +// thread to perform the runtime initialization. This method is similar to +// _cgo_sys_thread_start except that it doesn't update any Go state. + +//go:linkname x_cgo_thread_start_trampoline x_cgo_thread_start_trampoline +//go:linkname _cgo_thread_start _cgo_thread_start +var x_cgo_thread_start_trampoline byte +var _cgo_thread_start = &x_cgo_thread_start_trampoline + +// Notifies that the runtime has been initialized. +// +// We currently block at every CGO entry point (via _cgo_wait_runtime_init_done) +// to ensure that the runtime has been initialized before the CGO call is +// executed. This is necessary for shared libraries where we kickoff runtime +// initialization in a separate thread and return without waiting for this +// thread to complete the init. + +//go:linkname x_cgo_notify_runtime_init_done_trampoline x_cgo_notify_runtime_init_done_trampoline +//go:linkname _cgo_notify_runtime_init_done _cgo_notify_runtime_init_done +var x_cgo_notify_runtime_init_done_trampoline byte +var _cgo_notify_runtime_init_done = &x_cgo_notify_runtime_init_done_trampoline + +// Indicates whether a dummy thread key has been created or not. +// +// When calling go exported function from C, we register a destructor +// callback, for a dummy thread key, by using pthread_key_create. + +//go:linkname _cgo_pthread_key_created _cgo_pthread_key_created +var x_cgo_pthread_key_created uintptr +var _cgo_pthread_key_created = &x_cgo_pthread_key_created + +// Set the x_crosscall2_ptr C function pointer variable point to crosscall2. +// It's for the runtime package to call at init time. +func set_crosscall2() { + // nothing needs to be done here for fakecgo + // because it's possible to just call cgocallback directly +} + +//go:linkname _set_crosscall2 runtime.set_crosscall2 +var _set_crosscall2 = set_crosscall2 + +// Store the g into the thread-specific value. +// So that pthread_key_destructor will dropm when the thread is exiting. + +//go:linkname x_cgo_bindm_trampoline x_cgo_bindm_trampoline +//go:linkname _cgo_bindm _cgo_bindm +var x_cgo_bindm_trampoline byte +var _cgo_bindm = &x_cgo_bindm_trampoline + +// TODO: decide if we need x_cgo_set_context_function +// TODO: decide if we need _cgo_yield + +var ( + // In Go 1.20 the race detector was rewritten to pure Go + // on darwin. This means that when CGO_ENABLED=0 is set + // fakecgo is built with race detector code. This is not + // good since this code is pretending to be C. The go:norace + // pragma is not enough, since it only applies to the native + // ABIInternal function. The ABIO wrapper (which is necessary, + // since all references to text symbols from assembly will use it) + // does not inherit the go:norace pragma, so it will still be + // instrumented by the race detector. + // + // To circumvent this issue, using closure calls in the + // assembly, which forces the compiler to use the ABIInternal + // native implementation (which has go:norace) instead. + threadentry_call = threadentry + x_cgo_init_call = x_cgo_init + x_cgo_setenv_call = x_cgo_setenv + x_cgo_unsetenv_call = x_cgo_unsetenv + x_cgo_thread_start_call = x_cgo_thread_start +) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/doc.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/doc.go new file mode 100644 index 00000000..e482c120 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/doc.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +// Package fakecgo implements the Cgo runtime (runtime/cgo) entirely in Go. +// This allows code that calls into C to function properly when CGO_ENABLED=0. +// +// # Goals +// +// fakecgo attempts to replicate the same naming structure as in the runtime. +// For example, functions that have the prefix "gcc_*" are named "go_*". +// This makes it easier to port other GOOSs and GOARCHs as well as to keep +// it in sync with runtime/cgo. +// +// # Support +// +// Currently, fakecgo only supports macOS on amd64 & arm64. It also cannot +// be used with -buildmode=c-archive because that requires special initialization +// that fakecgo does not implement at the moment. +// +// # Usage +// +// Using fakecgo is easy just import _ "github.com/ebitengine/purego" and then +// set the environment variable CGO_ENABLED=0. +// The recommended usage for fakecgo is to prefer using runtime/cgo if possible +// but if cross-compiling or fast build times are important fakecgo is available. +// Purego will pick which ever Cgo runtime is available and prefer the one that +// comes with Go (runtime/cgo). +package fakecgo + +//go:generate go run gen.go diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/freebsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/freebsd.go new file mode 100644 index 00000000..bb73a709 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/freebsd.go @@ -0,0 +1,27 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build freebsd && !cgo + +package fakecgo + +import _ "unsafe" // for go:linkname + +// Supply environ and __progname, because we don't +// link against the standard FreeBSD crt0.o and the +// libc dynamic library needs them. + +// Note: when building with cross-compiling or CGO_ENABLED=0, add +// the following argument to `go` so that these symbols are defined by +// making fakecgo the Cgo. +// -gcflags="github.com/ebitengine/purego/internal/fakecgo=-std" + +//go:linkname _environ environ +//go:linkname _progname __progname + +//go:cgo_export_dynamic environ +//go:cgo_export_dynamic __progname + +var _environ uintptr +var _progname uintptr diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_amd64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_amd64.go new file mode 100644 index 00000000..39f5ff1f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_amd64.go @@ -0,0 +1,73 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo + +package fakecgo + +import "unsafe" + +//go:nosplit +//go:norace +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + size = pthread_get_stacksize_np(pthread_self()) + pthread_attr_init(&attr) + pthread_attr_setstacksize(&attr, size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +//go:norace +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +//go:nosplit +//go:norace +func x_cgo_init(g *G, setg uintptr) { + var size size_t + + setg_func = setg + + size = pthread_get_stacksize_np(pthread_self()) + g.stacklo = uintptr(unsafe.Add(unsafe.Pointer(&size), -size+4096)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_arm64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_arm64.go new file mode 100644 index 00000000..d0868f0f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_darwin_arm64.go @@ -0,0 +1,88 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo + +package fakecgo + +import "unsafe" + +//go:nosplit +//go:norace +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + size = pthread_get_stacksize_np(pthread_self()) + pthread_attr_init(&attr) + pthread_attr_setstacksize(&attr, size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +//go:norace +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + // TODO: support ios + //#if TARGET_OS_IPHONE + // darwin_arm_init_thread_exception_port(); + //#endif + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +// x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) +// This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us +// Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup +// This function can't be go:systemstack since go is not in a state where the systemcheck would work. +// +//go:nosplit +//go:norace +func x_cgo_init(g *G, setg uintptr) { + var size size_t + + setg_func = setg + size = pthread_get_stacksize_np(pthread_self()) + g.stacklo = uintptr(unsafe.Add(unsafe.Pointer(&size), -size+4096)) + + //TODO: support ios + //#if TARGET_OS_IPHONE + // darwin_arm_init_mach_exception_handler(); + // darwin_arm_init_thread_exception_port(); + // init_working_dir(); + //#endif +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_amd64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_amd64.go new file mode 100644 index 00000000..c9ff7156 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_amd64.go @@ -0,0 +1,95 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` + // but this should be OK since we are taking the address of the first variable in this function. + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_arm64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_arm64.go new file mode 100644 index 00000000..e3a060b9 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_freebsd_arm64.go @@ -0,0 +1,98 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + // fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +// x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) +// This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us +// Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup +// This function can't be go:systemstack since go is not in a state where the systemcheck would work. +// +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go new file mode 100644 index 00000000..0c463066 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +package fakecgo + +import ( + "syscall" + "unsafe" +) + +var ( + pthread_g pthread_key_t + + runtime_init_cond = PTHREAD_COND_INITIALIZER + runtime_init_mu = PTHREAD_MUTEX_INITIALIZER + runtime_init_done int +) + +//go:nosplit +//go:norace +func x_cgo_notify_runtime_init_done() { + pthread_mutex_lock(&runtime_init_mu) + runtime_init_done = 1 + pthread_cond_broadcast(&runtime_init_cond) + pthread_mutex_unlock(&runtime_init_mu) +} + +// Store the g into a thread-specific value associated with the pthread key pthread_g. +// And pthread_key_destructor will dropm when the thread is exiting. +// +//go:norace +func x_cgo_bindm(g unsafe.Pointer) { + // We assume this will always succeed, otherwise, there might be extra M leaking, + // when a C thread exits after a cgo call. + // We only invoke this function once per thread in runtime.needAndBindM, + // and the next calls just reuse the bound m. + pthread_setspecific(pthread_g, g) +} + +// _cgo_try_pthread_create retries pthread_create if it fails with +// EAGAIN. +// +//go:nosplit +//go:norace +func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe.Pointer, arg *ThreadStart) int { + var ts syscall.Timespec + // tries needs to be the same type as syscall.Timespec.Nsec + // but the fields are int32 on 32bit and int64 on 64bit. + // tries is assigned to syscall.Timespec.Nsec in order to match its type. + tries := ts.Nsec + var err int + + for tries = 0; tries < 20; tries++ { + // inlined this call because it ran out of stack when inlining was disabled + err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0)) + if err == 0 { + // inlined this call because it ran out of stack when inlining was disabled + call5(pthread_detachABI0, uintptr(*thread), 0, 0, 0, 0) + return 0 + } + if err != int(syscall.EAGAIN) { + return err + } + ts.Sec = 0 + ts.Nsec = (tries + 1) * 1000 * 1000 // Milliseconds. + // inlined this call because it ran out of stack when inlining was disabled + call5(nanosleepABI0, uintptr(unsafe.Pointer(&ts)), 0, 0, 0, 0) + } + return int(syscall.EAGAIN) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_amd64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_amd64.go new file mode 100644 index 00000000..c9ff7156 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_amd64.go @@ -0,0 +1,95 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` + // but this should be OK since we are taking the address of the first variable in this function. + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_arm64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_arm64.go new file mode 100644 index 00000000..a3b1cca5 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_arm64.go @@ -0,0 +1,98 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + //fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +// x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) +// This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us +// Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup +// This function can't be go:systemstack since go is not in a state where the systemcheck would work. +// +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_loong64.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_loong64.go new file mode 100644 index 00000000..65293914 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_linux_loong64.go @@ -0,0 +1,92 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + ts := *(*ThreadStart)(v) + free(v) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_netbsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_netbsd.go new file mode 100644 index 00000000..935a334f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_netbsd.go @@ -0,0 +1,106 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo && (amd64 || arm64) + +package fakecgo + +import "unsafe" + +//go:nosplit +func _cgo_sys_thread_start(ts *ThreadStart) { + var attr pthread_attr_t + var ign, oset sigset_t + var p pthread_t + var size size_t + var err int + + // fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug + sigfillset(&ign) + pthread_sigmask(SIG_SETMASK, &ign, &oset) + + pthread_attr_init(&attr) + pthread_attr_getstacksize(&attr, &size) + // Leave stacklo=0 and set stackhi=size; mstart will do the rest. + ts.g.stackhi = uintptr(size) + + err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) + + pthread_sigmask(SIG_SETMASK, &oset, nil) + + if err != 0 { + print("fakecgo: pthread_create failed: ") + println(err) + abort() + } +} + +// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function +// +//go:linkname x_threadentry_trampoline threadentry_trampoline +var x_threadentry_trampoline byte +var threadentry_trampolineABI0 = &x_threadentry_trampoline + +//go:nosplit +func threadentry(v unsafe.Pointer) unsafe.Pointer { + var ss stack_t + ts := *(*ThreadStart)(v) + free(v) + + // On NetBSD, a new thread inherits the signal stack of the + // creating thread. That confuses minit, so we remove that + // signal stack here before calling the regular mstart. It's + // a bit baroque to remove a signal stack here only to add one + // in minit, but it's a simple change that keeps NetBSD + // working like other OS's. At this point all signals are + // blocked, so there is no race. + ss.ss_flags = SS_DISABLE + sigaltstack(&ss, nil) + + setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) + + // faking funcs in go is a bit a... involved - but the following works :) + fn := uintptr(unsafe.Pointer(&ts.fn)) + (*(*func())(unsafe.Pointer(&fn)))() + + return nil +} + +// here we will store a pointer to the provided setg func +var setg_func uintptr + +//go:nosplit +func x_cgo_init(g *G, setg uintptr) { + var size size_t + var attr *pthread_attr_t + + /* The memory sanitizer distributed with versions of clang + before 3.8 has a bug: if you call mmap before malloc, mmap + may return an address that is later overwritten by the msan + library. Avoid this problem by forcing a call to malloc + here, before we ever call malloc. + + This is only required for the memory sanitizer, so it's + unfortunate that we always run it. It should be possible + to remove this when we no longer care about versions of + clang before 3.8. The test for this is + misc/cgo/testsanitizers. + + GCC works hard to eliminate a seemingly unnecessary call to + malloc, so we actually use the memory we allocate. */ + + setg_func = setg + attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) + if attr == nil { + println("fakecgo: malloc failed") + abort() + } + pthread_attr_init(attr) + pthread_attr_getstacksize(attr, &size) + // runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))` + // but this should be OK since we are taking the address of the first variable in this function. + g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 + pthread_attr_destroy(attr) + free(unsafe.Pointer(attr)) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_setenv.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_setenv.go new file mode 100644 index 00000000..dfc6629e --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_setenv.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +package fakecgo + +//go:nosplit +//go:norace +func x_cgo_setenv(arg *[2]*byte) { + setenv(arg[0], arg[1], 1) +} + +//go:nosplit +//go:norace +func x_cgo_unsetenv(arg *[1]*byte) { + unsetenv(arg[0]) +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_util.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_util.go new file mode 100644 index 00000000..771cb525 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_util.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +package fakecgo + +import "unsafe" + +// _cgo_thread_start is split into three parts in cgo since only one part is system dependent (keep it here for easier handling) + +// _cgo_thread_start(ThreadStart *arg) (runtime/cgo/gcc_util.c) +// This get's called instead of the go code for creating new threads +// -> pthread_* stuff is used, so threads are setup correctly for C +// If this is missing, TLS is only setup correctly on thread 1! +// This function should be go:systemstack instead of go:nosplit (but that requires runtime) +// +//go:nosplit +//go:norace +func x_cgo_thread_start(arg *ThreadStart) { + var ts *ThreadStart + // Make our own copy that can persist after we return. + // _cgo_tsan_acquire(); + ts = (*ThreadStart)(malloc(unsafe.Sizeof(*ts))) + // _cgo_tsan_release(); + if ts == nil { + println("fakecgo: out of memory in thread_start") + abort() + } + // *ts = *arg would cause a writebarrier so copy using slices + s1 := unsafe.Slice((*uintptr)(unsafe.Pointer(ts)), unsafe.Sizeof(*ts)/8) + s2 := unsafe.Slice((*uintptr)(unsafe.Pointer(arg)), unsafe.Sizeof(*arg)/8) + for i := range s2 { + s1[i] = s2[i] + } + _cgo_sys_thread_start(ts) // OS-dependent half +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/iscgo.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/iscgo.go new file mode 100644 index 00000000..12e52147 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/iscgo.go @@ -0,0 +1,19 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +// The runtime package contains an uninitialized definition +// for runtime·iscgo. Override it to tell the runtime we're here. +// There are various function pointers that should be set too, +// but those depend on dynamic linker magic to get initialized +// correctly, and sometimes they break. This variable is a +// backup: it depends only on old C style static linking rules. + +package fakecgo + +import _ "unsafe" // for go:linkname + +//go:linkname _iscgo runtime.iscgo +var _iscgo bool = true diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo.go new file mode 100644 index 00000000..94fd8bea --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo.go @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +package fakecgo + +type ( + size_t uintptr + // Sources: + // Darwin (32 bytes) - https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/_types.h#L74 + // FreeBSD (32 bytes) - https://github.com/DoctorWkt/xv6-freebsd/blob/d2a294c2a984baed27676068b15ed9a29b06ab6f/include/signal.h#L98C9-L98C21 + // Linux (128 bytes) - https://github.com/torvalds/linux/blob/ab75170520d4964f3acf8bb1f91d34cbc650688e/arch/x86/include/asm/signal.h#L25 + sigset_t [128]byte + pthread_attr_t [64]byte + pthread_t int + pthread_key_t uint64 +) + +// for pthread_sigmask: + +type sighow int32 + +const ( + SIG_BLOCK sighow = 0 + SIG_UNBLOCK sighow = 1 + SIG_SETMASK sighow = 2 +) + +type G struct { + stacklo uintptr + stackhi uintptr +} + +type ThreadStart struct { + g *G + tls *uintptr + fn uintptr +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_darwin.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_darwin.go new file mode 100644 index 00000000..ecdcb2e7 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_darwin.go @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package fakecgo + +type ( + pthread_mutex_t struct { + sig int64 + opaque [56]byte + } + pthread_cond_t struct { + sig int64 + opaque [40]byte + } +) + +var ( + PTHREAD_COND_INITIALIZER = pthread_cond_t{sig: 0x3CB0B1BB} + PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{sig: 0x32AAABA7} +) + +type stack_t struct { + /* not implemented */ +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_freebsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_freebsd.go new file mode 100644 index 00000000..4bfb70c3 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_freebsd.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package fakecgo + +type ( + pthread_cond_t uintptr + pthread_mutex_t uintptr +) + +var ( + PTHREAD_COND_INITIALIZER = pthread_cond_t(0) + PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t(0) +) + +type stack_t struct { + /* not implemented */ +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_linux.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_linux.go new file mode 100644 index 00000000..b08a44a1 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_linux.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package fakecgo + +type ( + pthread_cond_t [48]byte + pthread_mutex_t [48]byte +) + +var ( + PTHREAD_COND_INITIALIZER = pthread_cond_t{} + PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{} +) + +type stack_t struct { + /* not implemented */ +} diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_netbsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_netbsd.go new file mode 100644 index 00000000..650f6953 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/libcgo_netbsd.go @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2025 The Ebitengine Authors + +//go:build !cgo + +package fakecgo + +type ( + pthread_cond_t uintptr + pthread_mutex_t uintptr +) + +var ( + PTHREAD_COND_INITIALIZER = pthread_cond_t(0) + PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t(0) +) + +// Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/compat/linux/arch/m68k/linux_signal.h#L133 +type stack_t struct { + ss_sp uintptr + ss_flags int32 + ss_size uintptr +} + +// Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/sys/signal.h#L261 +const SS_DISABLE = 0x004 diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/netbsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/netbsd.go new file mode 100644 index 00000000..2d499814 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/netbsd.go @@ -0,0 +1,23 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build netbsd + +package fakecgo + +import _ "unsafe" // for go:linkname + +// Supply environ and __progname, because we don't +// link against the standard NetBSD crt0.o and the +// libc dynamic library needs them. + +//go:linkname _environ environ +//go:linkname _progname __progname +//go:linkname ___ps_strings __ps_strings + +var ( + _environ uintptr + _progname uintptr + ___ps_strings uintptr +) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/setenv.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/setenv.go new file mode 100644 index 00000000..82308b8c --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/setenv.go @@ -0,0 +1,19 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +package fakecgo + +import _ "unsafe" // for go:linkname + +//go:linkname x_cgo_setenv_trampoline x_cgo_setenv_trampoline +//go:linkname _cgo_setenv runtime._cgo_setenv +var x_cgo_setenv_trampoline byte +var _cgo_setenv = &x_cgo_setenv_trampoline + +//go:linkname x_cgo_unsetenv_trampoline x_cgo_unsetenv_trampoline +//go:linkname _cgo_unsetenv runtime._cgo_unsetenv +var x_cgo_unsetenv_trampoline byte +var _cgo_unsetenv = &x_cgo_unsetenv_trampoline diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go new file mode 100644 index 00000000..135f6d21 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go @@ -0,0 +1,231 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +package fakecgo + +import ( + "syscall" + "unsafe" +) + +// setg_trampoline calls setg with the G provided +func setg_trampoline(setg uintptr, G uintptr) + +// call5 takes fn the C function and 5 arguments and calls the function with those arguments +func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr + +//go:nosplit +//go:norace +func malloc(size uintptr) unsafe.Pointer { + ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0) + // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer + return *(*unsafe.Pointer)(unsafe.Pointer(&ret)) +} + +//go:nosplit +//go:norace +func free(ptr unsafe.Pointer) { + call5(freeABI0, uintptr(ptr), 0, 0, 0, 0) +} + +//go:nosplit +//go:norace +func setenv(name *byte, value *byte, overwrite int32) int32 { + return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0)) +} + +//go:nosplit +//go:norace +func unsetenv(name *byte) int32 { + return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func sigfillset(set *sigset_t) int32 { + return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 { + return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0)) +} + +//go:nosplit +//go:norace +func abort() { + call5(abortABI0, 0, 0, 0, 0, 0) +} + +//go:nosplit +//go:norace +func sigaltstack(ss *stack_t, old_ss *stack_t) int32 { + return int32(call5(sigaltstackABI0, uintptr(unsafe.Pointer(ss)), uintptr(unsafe.Pointer(old_ss)), 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_attr_init(attr *pthread_attr_t) int32 { + return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 { + return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0)) +} + +//go:nosplit +//go:norace +func pthread_detach(thread pthread_t) int32 { + return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 { + return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_self() pthread_t { + return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_get_stacksize_np(thread pthread_t) size_t { + return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 { + return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 { + return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_attr_destroy(attr *pthread_attr_t) int32 { + return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_mutex_lock(mutex *pthread_mutex_t) int32 { + return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 { + return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_cond_broadcast(cond *pthread_cond_t) int32 { + return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0)) +} + +//go:nosplit +//go:norace +func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 { + return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0)) +} + +//go:linkname _malloc _malloc +var _malloc uint8 +var mallocABI0 = uintptr(unsafe.Pointer(&_malloc)) + +//go:linkname _free _free +var _free uint8 +var freeABI0 = uintptr(unsafe.Pointer(&_free)) + +//go:linkname _setenv _setenv +var _setenv uint8 +var setenvABI0 = uintptr(unsafe.Pointer(&_setenv)) + +//go:linkname _unsetenv _unsetenv +var _unsetenv uint8 +var unsetenvABI0 = uintptr(unsafe.Pointer(&_unsetenv)) + +//go:linkname _sigfillset _sigfillset +var _sigfillset uint8 +var sigfillsetABI0 = uintptr(unsafe.Pointer(&_sigfillset)) + +//go:linkname _nanosleep _nanosleep +var _nanosleep uint8 +var nanosleepABI0 = uintptr(unsafe.Pointer(&_nanosleep)) + +//go:linkname _abort _abort +var _abort uint8 +var abortABI0 = uintptr(unsafe.Pointer(&_abort)) + +//go:linkname _sigaltstack _sigaltstack +var _sigaltstack uint8 +var sigaltstackABI0 = uintptr(unsafe.Pointer(&_sigaltstack)) + +//go:linkname _pthread_attr_init _pthread_attr_init +var _pthread_attr_init uint8 +var pthread_attr_initABI0 = uintptr(unsafe.Pointer(&_pthread_attr_init)) + +//go:linkname _pthread_create _pthread_create +var _pthread_create uint8 +var pthread_createABI0 = uintptr(unsafe.Pointer(&_pthread_create)) + +//go:linkname _pthread_detach _pthread_detach +var _pthread_detach uint8 +var pthread_detachABI0 = uintptr(unsafe.Pointer(&_pthread_detach)) + +//go:linkname _pthread_sigmask _pthread_sigmask +var _pthread_sigmask uint8 +var pthread_sigmaskABI0 = uintptr(unsafe.Pointer(&_pthread_sigmask)) + +//go:linkname _pthread_self _pthread_self +var _pthread_self uint8 +var pthread_selfABI0 = uintptr(unsafe.Pointer(&_pthread_self)) + +//go:linkname _pthread_get_stacksize_np _pthread_get_stacksize_np +var _pthread_get_stacksize_np uint8 +var pthread_get_stacksize_npABI0 = uintptr(unsafe.Pointer(&_pthread_get_stacksize_np)) + +//go:linkname _pthread_attr_getstacksize _pthread_attr_getstacksize +var _pthread_attr_getstacksize uint8 +var pthread_attr_getstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_getstacksize)) + +//go:linkname _pthread_attr_setstacksize _pthread_attr_setstacksize +var _pthread_attr_setstacksize uint8 +var pthread_attr_setstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_setstacksize)) + +//go:linkname _pthread_attr_destroy _pthread_attr_destroy +var _pthread_attr_destroy uint8 +var pthread_attr_destroyABI0 = uintptr(unsafe.Pointer(&_pthread_attr_destroy)) + +//go:linkname _pthread_mutex_lock _pthread_mutex_lock +var _pthread_mutex_lock uint8 +var pthread_mutex_lockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_lock)) + +//go:linkname _pthread_mutex_unlock _pthread_mutex_unlock +var _pthread_mutex_unlock uint8 +var pthread_mutex_unlockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_unlock)) + +//go:linkname _pthread_cond_broadcast _pthread_cond_broadcast +var _pthread_cond_broadcast uint8 +var pthread_cond_broadcastABI0 = uintptr(unsafe.Pointer(&_pthread_cond_broadcast)) + +//go:linkname _pthread_setspecific _pthread_setspecific +var _pthread_setspecific uint8 +var pthread_setspecificABI0 = uintptr(unsafe.Pointer(&_pthread_setspecific)) diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_darwin.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_darwin.go new file mode 100644 index 00000000..8c4489f0 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_darwin.go @@ -0,0 +1,30 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package fakecgo + +//go:cgo_import_dynamic purego_malloc malloc "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_free free "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_setenv setenv "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_unsetenv unsetenv "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_sigfillset sigfillset "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_nanosleep nanosleep "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_abort abort "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_sigaltstack sigaltstack "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_create pthread_create "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_detach pthread_detach "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_self pthread_self "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "/usr/lib/libSystem.B.dylib" diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_freebsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_freebsd.go new file mode 100644 index 00000000..bbe1bd57 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_freebsd.go @@ -0,0 +1,30 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package fakecgo + +//go:cgo_import_dynamic purego_malloc malloc "libc.so.7" +//go:cgo_import_dynamic purego_free free "libc.so.7" +//go:cgo_import_dynamic purego_setenv setenv "libc.so.7" +//go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so.7" +//go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7" +//go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7" +//go:cgo_import_dynamic purego_abort abort "libc.so.7" +//go:cgo_import_dynamic purego_sigaltstack sigaltstack "libc.so.7" +//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so" +//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so" +//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so" +//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so" +//go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so" +//go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so" +//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so" +//go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so" +//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so" +//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so" +//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so" +//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so" +//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so" diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_linux.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_linux.go new file mode 100644 index 00000000..21652650 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_linux.go @@ -0,0 +1,30 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package fakecgo + +//go:cgo_import_dynamic purego_malloc malloc "libc.so.6" +//go:cgo_import_dynamic purego_free free "libc.so.6" +//go:cgo_import_dynamic purego_setenv setenv "libc.so.6" +//go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so.6" +//go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.6" +//go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.6" +//go:cgo_import_dynamic purego_abort abort "libc.so.6" +//go:cgo_import_dynamic purego_sigaltstack sigaltstack "libc.so.6" +//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so.0" +//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so.0" diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_netbsd.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_netbsd.go new file mode 100644 index 00000000..7c92bb0b --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols_netbsd.go @@ -0,0 +1,30 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package fakecgo + +//go:cgo_import_dynamic purego_malloc malloc "libc.so" +//go:cgo_import_dynamic purego_free free "libc.so" +//go:cgo_import_dynamic purego_setenv setenv "libc.so" +//go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so" +//go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so" +//go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so" +//go:cgo_import_dynamic purego_abort abort "libc.so" +//go:cgo_import_dynamic purego_sigaltstack sigaltstack "libc.so" +//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so" +//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so" +//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so" +//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so" +//go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so" +//go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so" +//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so" +//go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so" +//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so" +//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so" +//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so" +//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so" +//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so" diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s new file mode 100644 index 00000000..c9a3cc09 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_amd64.s @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || linux || freebsd) + +/* +trampoline for emulating required C functions for cgo in go (see cgo.go) +(we convert cdecl calling convention to go and vice-versa) + +Since we're called from go and call into C we can cheat a bit with the calling conventions: + - in go all the registers are caller saved + - in C we have a couple of callee saved registers + +=> we can use BX, R12, R13, R14, R15 instead of the stack + +C Calling convention cdecl used here (we only need integer args): +1. arg: DI +2. arg: SI +3. arg: DX +4. arg: CX +5. arg: R8 +6. arg: R9 +We don't need floats with these functions -> AX=0 +return value will be in AX +*/ +#include "textflag.h" +#include "go_asm.h" + +// these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. + +TEXT x_cgo_init_trampoline(SB), NOSPLIT, $16 + MOVQ DI, AX + MOVQ SI, BX + MOVQ ·x_cgo_init_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 + MOVQ DI, AX + MOVQ ·x_cgo_thread_start_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 + MOVQ DI, AX + MOVQ ·x_cgo_setenv_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 + MOVQ DI, AX + MOVQ ·x_cgo_unsetenv_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0 + CALL ·x_cgo_notify_runtime_init_done(SB) + RET + +TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 + CALL ·x_cgo_bindm(SB) + RET + +// func setg_trampoline(setg uintptr, g uintptr) +TEXT ·setg_trampoline(SB), NOSPLIT, $0-16 + MOVQ G+8(FP), DI + MOVQ setg+0(FP), BX + XORL AX, AX + CALL BX + RET + +TEXT threadentry_trampoline(SB), NOSPLIT, $16 + MOVQ DI, AX + MOVQ ·threadentry_call(SB), DX + MOVQ (DX), CX + CALL CX + RET + +TEXT ·call5(SB), NOSPLIT, $0-56 + MOVQ fn+0(FP), BX + MOVQ a1+8(FP), DI + MOVQ a2+16(FP), SI + MOVQ a3+24(FP), DX + MOVQ a4+32(FP), CX + MOVQ a5+40(FP), R8 + + XORL AX, AX // no floats + + PUSHQ BP // save BP + MOVQ SP, BP // save SP inside BP bc BP is callee-saved + SUBQ $16, SP // allocate space for alignment + ANDQ $-16, SP // align on 16 bytes for SSE + + CALL BX + + MOVQ BP, SP // get SP back + POPQ BP // restore BP + + MOVQ AX, ret+48(FP) + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s new file mode 100644 index 00000000..9dbdbc01 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_arm64.s @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux) + +#include "textflag.h" +#include "go_asm.h" + +// these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. + +TEXT x_cgo_init_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD R1, 16(RSP) + MOVD ·x_cgo_init_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + RET + +TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD ·x_cgo_thread_start_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + RET + +TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD ·x_cgo_setenv_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + RET + +TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD ·x_cgo_unsetenv_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + RET + +TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0-0 + CALL ·x_cgo_notify_runtime_init_done(SB) + RET + +TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 + CALL ·x_cgo_bindm(SB) + RET + +// func setg_trampoline(setg uintptr, g uintptr) +TEXT ·setg_trampoline(SB), NOSPLIT, $0-16 + MOVD G+8(FP), R0 + MOVD setg+0(FP), R1 + CALL R1 + RET + +TEXT threadentry_trampoline(SB), NOSPLIT, $0-0 + MOVD R0, 8(RSP) + MOVD ·threadentry_call(SB), R26 + MOVD (R26), R2 + CALL (R2) + MOVD $0, R0 // TODO: get the return value from threadentry + RET + +TEXT ·call5(SB), NOSPLIT, $0-0 + MOVD fn+0(FP), R6 + MOVD a1+8(FP), R0 + MOVD a2+16(FP), R1 + MOVD a3+24(FP), R2 + MOVD a4+32(FP), R3 + MOVD a5+40(FP), R4 + CALL R6 + MOVD R0, ret+48(FP) + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_loong64.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_loong64.s new file mode 100644 index 00000000..15b33543 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_loong64.s @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2025 The Ebitengine Authors + +//go:build !cgo && linux + +#include "textflag.h" +#include "go_asm.h" + +// these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. + +TEXT x_cgo_init_trampoline(SB), NOSPLIT, $16 + MOVV R4, 8(R3) + MOVV R5, 16(R3) + MOVV ·x_cgo_init_call(SB), R6 + MOVV (R6), R7 + CALL (R7) + RET + +TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 + MOVV R4, 8(R3) + MOVV ·x_cgo_thread_start_call(SB), R5 + MOVV (R5), R6 + CALL (R6) + RET + +TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 + MOVV R4, 8(R3) + MOVV ·x_cgo_setenv_call(SB), R5 + MOVV (R5), R6 + CALL (R6) + RET + +TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 + MOVV R4, 8(R3) + MOVV ·x_cgo_unsetenv_call(SB), R5 + MOVV (R5), R6 + CALL (R6) + RET + +TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0 + CALL ·x_cgo_notify_runtime_init_done(SB) + RET + +TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 + CALL ·x_cgo_bindm(SB) + RET + +// func setg_trampoline(setg uintptr, g uintptr) +TEXT ·setg_trampoline(SB), NOSPLIT, $0 + MOVV G+8(FP), R4 + MOVV setg+0(FP), R5 + CALL (R5) + RET + +TEXT threadentry_trampoline(SB), NOSPLIT, $16 + MOVV R4, 8(R3) + MOVV ·threadentry_call(SB), R5 + MOVV (R5), R6 + CALL (R6) + RET + +TEXT ·call5(SB), NOSPLIT, $0-0 + MOVV fn+0(FP), R9 + MOVV a1+8(FP), R4 + MOVV a2+16(FP), R5 + MOVV a3+24(FP), R6 + MOVV a4+32(FP), R7 + MOVV a5+40(FP), R8 + CALL (R9) + MOVV R4, ret+48(FP) + RET diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_stubs.s b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_stubs.s new file mode 100644 index 00000000..c93d783d --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/trampolines_stubs.s @@ -0,0 +1,94 @@ +// Code generated by 'go generate' with gen.go. DO NOT EDIT. + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +#include "textflag.h" + +// these stubs are here because it is not possible to go:linkname directly the C functions on darwin arm64 + +TEXT _malloc(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_malloc(SB) + RET + +TEXT _free(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_free(SB) + RET + +TEXT _setenv(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_setenv(SB) + RET + +TEXT _unsetenv(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_unsetenv(SB) + RET + +TEXT _sigfillset(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_sigfillset(SB) + RET + +TEXT _nanosleep(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_nanosleep(SB) + RET + +TEXT _abort(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_abort(SB) + RET + +TEXT _sigaltstack(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_sigaltstack(SB) + RET + +TEXT _pthread_attr_init(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_attr_init(SB) + RET + +TEXT _pthread_create(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_create(SB) + RET + +TEXT _pthread_detach(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_detach(SB) + RET + +TEXT _pthread_sigmask(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_sigmask(SB) + RET + +TEXT _pthread_self(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_self(SB) + RET + +TEXT _pthread_get_stacksize_np(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_get_stacksize_np(SB) + RET + +TEXT _pthread_attr_getstacksize(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_attr_getstacksize(SB) + RET + +TEXT _pthread_attr_setstacksize(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_attr_setstacksize(SB) + RET + +TEXT _pthread_attr_destroy(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_attr_destroy(SB) + RET + +TEXT _pthread_mutex_lock(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_mutex_lock(SB) + RET + +TEXT _pthread_mutex_unlock(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_mutex_unlock(SB) + RET + +TEXT _pthread_cond_broadcast(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_cond_broadcast(SB) + RET + +TEXT _pthread_setspecific(SB), NOSPLIT|NOFRAME, $0-0 + JMP purego_pthread_setspecific(SB) + RET diff --git a/vendor/github.com/ebitengine/purego/internal/strings/strings.go b/vendor/github.com/ebitengine/purego/internal/strings/strings.go new file mode 100644 index 00000000..5b0d2522 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/internal/strings/strings.go @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package strings + +import ( + "unsafe" +) + +// hasSuffix tests whether the string s ends with suffix. +func hasSuffix(s, suffix string) bool { + return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix +} + +// CString converts a go string to *byte that can be passed to C code. +func CString(name string) *byte { + if hasSuffix(name, "\x00") { + return &(*(*[]byte)(unsafe.Pointer(&name)))[0] + } + b := make([]byte, len(name)+1) + copy(b, name) + return &b[0] +} + +// GoString copies a null-terminated char* to a Go string. +func GoString(c uintptr) string { + // We take the address and then dereference it to trick go vet from creating a possible misuse of unsafe.Pointer + ptr := *(*unsafe.Pointer)(unsafe.Pointer(&c)) + if ptr == nil { + return "" + } + var length int + for { + if *(*byte)(unsafe.Add(ptr, uintptr(length))) == '\x00' { + break + } + length++ + } + return string(unsafe.Slice((*byte)(ptr), length)) +} diff --git a/vendor/github.com/ebitengine/purego/is_ios.go b/vendor/github.com/ebitengine/purego/is_ios.go new file mode 100644 index 00000000..ed31da97 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/is_ios.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo + +package purego + +// if you are getting this error it means that you have +// CGO_ENABLED=0 while trying to build for ios. +// purego does not support this mode yet. +// the fix is to set CGO_ENABLED=1 which will require +// a C compiler. +var _ = _PUREGO_REQUIRES_CGO_ON_IOS diff --git a/vendor/github.com/ebitengine/purego/nocgo.go b/vendor/github.com/ebitengine/purego/nocgo.go new file mode 100644 index 00000000..b91b9796 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/nocgo.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build !cgo && (darwin || freebsd || linux || netbsd) + +package purego + +// if CGO_ENABLED=0 import fakecgo to setup the Cgo runtime correctly. +// This is required since some frameworks need TLS setup the C way which Go doesn't do. +// We currently don't support ios in fakecgo mode so force Cgo or fail +// +// The way that the Cgo runtime (runtime/cgo) works is by setting some variables found +// in runtime with non-null GCC compiled functions. The variables that are replaced are +// var ( +// iscgo bool // in runtime/cgo.go +// _cgo_init unsafe.Pointer // in runtime/cgo.go +// _cgo_thread_start unsafe.Pointer // in runtime/cgo.go +// _cgo_notify_runtime_init_done unsafe.Pointer // in runtime/cgo.go +// _cgo_setenv unsafe.Pointer // in runtime/env_posix.go +// _cgo_unsetenv unsafe.Pointer // in runtime/env_posix.go +// ) +// importing fakecgo will set these (using //go:linkname) with functions written +// entirely in Go (except for some assembly trampolines to change GCC ABI to Go ABI). +// Doing so makes it possible to build applications that call into C without CGO_ENABLED=1. +import _ "github.com/ebitengine/purego/internal/fakecgo" diff --git a/vendor/github.com/ebitengine/purego/struct_amd64.go b/vendor/github.com/ebitengine/purego/struct_amd64.go new file mode 100644 index 00000000..bd6c977b --- /dev/null +++ b/vendor/github.com/ebitengine/purego/struct_amd64.go @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +package purego + +import ( + "math" + "reflect" + "unsafe" +) + +func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { + outSize := outType.Size() + switch { + case outSize == 0: + return reflect.New(outType).Elem() + case outSize <= 8: + if isAllFloats(outType) { + // 2 float32s or 1 float64s are return in the float register + return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem() + } + // up to 8 bytes is returned in RAX + return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() + case outSize <= 16: + r1, r2 := syscall.a1, syscall.a2 + if isAllFloats(outType) { + r1 = syscall.f1 + r2 = syscall.f2 + } else { + // check first 8 bytes if it's floats + hasFirstFloat := false + f1 := outType.Field(0).Type + if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 { + r1 = syscall.f1 + hasFirstFloat = true + } + + // find index of the field that starts the second 8 bytes + var i int + for i = 0; i < outType.NumField(); i++ { + if outType.Field(i).Offset == 8 { + break + } + } + + // check last 8 bytes if they are floats + f1 = outType.Field(i).Type + if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() { + r2 = syscall.f1 + } else if hasFirstFloat { + // if the first field was a float then that means the second integer field + // comes from the first integer register + r2 = syscall.a1 + } + } + return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() + default: + // create struct from the Go pointer created above + // weird pointer dereference to circumvent go vet + return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() + } +} + +func isAllFloats(ty reflect.Type) bool { + for i := 0; i < ty.NumField(); i++ { + f := ty.Field(i) + switch f.Type.Kind() { + case reflect.Float64, reflect.Float32: + default: + return false + } + } + return true +} + +// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf +// https://gitlab.com/x86-psABIs/x86-64-ABI +// Class determines where the 8 byte value goes. +// Higher value classes win over lower value classes +const ( + _NO_CLASS = 0b0000 + _SSE = 0b0001 + _X87 = 0b0011 // long double not used in Go + _INTEGER = 0b0111 + _MEMORY = 0b1111 +) + +func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { + if v.Type().Size() == 0 { + return keepAlive + } + + // if greater than 64 bytes place on stack + if v.Type().Size() > 8*8 { + placeStack(v, addStack) + return keepAlive + } + var ( + savedNumFloats = *numFloats + savedNumInts = *numInts + savedNumStack = *numStack + ) + placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt) + if placeOnStack { + // reset any values placed in registers + *numFloats = savedNumFloats + *numInts = savedNumInts + *numStack = savedNumStack + placeStack(v, addStack) + } + return keepAlive +} + +func postMerger(t reflect.Type) (passInMemory bool) { + // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other + // eightbyte isn’t SSEUP, the whole argument is passed in memory. + if t.Kind() != reflect.Struct { + return false + } + if t.Size() <= 2*8 { + return false + } + return true // Go does not have an SSE/SSEUP type so this is always true +} + +func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) { + ok = true + var val uint64 + var shift byte // # of bits to shift + var flushed bool + class := _NO_CLASS + flushIfNeeded := func() { + if flushed { + return + } + flushed = true + if class == _SSE { + addFloat(uintptr(val)) + } else { + addInt(uintptr(val)) + } + val = 0 + shift = 0 + class = _NO_CLASS + } + var place func(v reflect.Value) + place = func(v reflect.Value) { + var numFields int + if v.Kind() == reflect.Struct { + numFields = v.Type().NumField() + } else { + numFields = v.Type().Len() + } + + for i := 0; i < numFields; i++ { + flushed = false + var f reflect.Value + if v.Kind() == reflect.Struct { + f = v.Field(i) + } else { + f = v.Index(i) + } + switch f.Kind() { + case reflect.Struct: + place(f) + case reflect.Bool: + if f.Bool() { + val |= 1 + } + shift += 8 + class |= _INTEGER + case reflect.Pointer: + ok = false + return + case reflect.Int8: + val |= uint64(f.Int()&0xFF) << shift + shift += 8 + class |= _INTEGER + case reflect.Int16: + val |= uint64(f.Int()&0xFFFF) << shift + shift += 16 + class |= _INTEGER + case reflect.Int32: + val |= uint64(f.Int()&0xFFFF_FFFF) << shift + shift += 32 + class |= _INTEGER + case reflect.Int64, reflect.Int: + val = uint64(f.Int()) + shift = 64 + class = _INTEGER + case reflect.Uint8: + val |= f.Uint() << shift + shift += 8 + class |= _INTEGER + case reflect.Uint16: + val |= f.Uint() << shift + shift += 16 + class |= _INTEGER + case reflect.Uint32: + val |= f.Uint() << shift + shift += 32 + class |= _INTEGER + case reflect.Uint64, reflect.Uint, reflect.Uintptr: + val = f.Uint() + shift = 64 + class = _INTEGER + case reflect.Float32: + val |= uint64(math.Float32bits(float32(f.Float()))) << shift + shift += 32 + class |= _SSE + case reflect.Float64: + if v.Type().Size() > 16 { + ok = false + return + } + val = uint64(math.Float64bits(f.Float())) + shift = 64 + class = _SSE + case reflect.Array: + place(f) + default: + panic("purego: unsupported kind " + f.Kind().String()) + } + + if shift == 64 { + flushIfNeeded() + } else if shift > 64 { + // Should never happen, but may if we forget to reset shift after flush (or forget to flush), + // better fall apart here, than corrupt arguments. + panic("purego: tryPlaceRegisters shift > 64") + } + } + } + + place(v) + flushIfNeeded() + return ok +} + +func placeStack(v reflect.Value, addStack func(uintptr)) { + for i := 0; i < v.Type().NumField(); i++ { + f := v.Field(i) + switch f.Kind() { + case reflect.Pointer: + addStack(f.Pointer()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + addStack(uintptr(f.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + addStack(uintptr(f.Uint())) + case reflect.Float32: + addStack(uintptr(math.Float32bits(float32(f.Float())))) + case reflect.Float64: + addStack(uintptr(math.Float64bits(f.Float()))) + case reflect.Struct: + placeStack(f, addStack) + default: + panic("purego: unsupported kind " + f.Kind().String()) + } + } +} + +func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { + panic("purego: not needed on amd64") +} diff --git a/vendor/github.com/ebitengine/purego/struct_arm64.go b/vendor/github.com/ebitengine/purego/struct_arm64.go new file mode 100644 index 00000000..6c73e98f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/struct_arm64.go @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +package purego + +import ( + "math" + "reflect" + "unsafe" +) + +func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { + outSize := outType.Size() + switch { + case outSize == 0: + return reflect.New(outType).Elem() + case outSize <= 8: + r1 := syscall.a1 + if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { + r1 = syscall.f1 + if numFields == 2 { + r1 = syscall.f2<<32 | syscall.f1 + } + } + return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() + case outSize <= 16: + r1, r2 := syscall.a1, syscall.a2 + if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { + switch numFields { + case 4: + r1 = syscall.f2<<32 | syscall.f1 + r2 = syscall.f4<<32 | syscall.f3 + case 3: + r1 = syscall.f2<<32 | syscall.f1 + r2 = syscall.f3 + case 2: + r1 = syscall.f1 + r2 = syscall.f2 + default: + panic("unreachable") + } + } + return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() + default: + if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 { + switch numFields { + case 4: + return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem() + case 3: + return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem() + default: + panic("unreachable") + } + } + // create struct from the Go pointer created in arm64_r8 + // weird pointer dereference to circumvent go vet + return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem() + } +} + +// https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst +const ( + _NO_CLASS = 0b00 + _FLOAT = 0b01 + _INT = 0b11 +) + +func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { + if v.Type().Size() == 0 { + return keepAlive + } + + if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 { + // if this doesn't fit entirely in registers then + // each element goes onto the stack + if hfa && *numFloats+v.NumField() > numOfFloatRegisters { + *numFloats = numOfFloatRegisters + } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() { + *numInts = numOfIntegerRegisters() + } + + placeRegisters(v, addFloat, addInt) + } else { + keepAlive = placeStack(v, keepAlive, addInt) + } + return keepAlive // the struct was allocated so don't panic +} + +func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { + var val uint64 + var shift byte + var flushed bool + class := _NO_CLASS + var place func(v reflect.Value) + place = func(v reflect.Value) { + var numFields int + if v.Kind() == reflect.Struct { + numFields = v.Type().NumField() + } else { + numFields = v.Type().Len() + } + for k := 0; k < numFields; k++ { + flushed = false + var f reflect.Value + if v.Kind() == reflect.Struct { + f = v.Field(k) + } else { + f = v.Index(k) + } + align := byte(f.Type().Align()*8 - 1) + shift = (shift + align) &^ align + if shift >= 64 { + shift = 0 + flushed = true + if class == _FLOAT { + addFloat(uintptr(val)) + } else { + addInt(uintptr(val)) + } + } + switch f.Type().Kind() { + case reflect.Struct: + place(f) + case reflect.Bool: + if f.Bool() { + val |= 1 + } + shift += 8 + class |= _INT + case reflect.Uint8: + val |= f.Uint() << shift + shift += 8 + class |= _INT + case reflect.Uint16: + val |= f.Uint() << shift + shift += 16 + class |= _INT + case reflect.Uint32: + val |= f.Uint() << shift + shift += 32 + class |= _INT + case reflect.Uint64, reflect.Uint, reflect.Uintptr: + addInt(uintptr(f.Uint())) + shift = 0 + flushed = true + class = _NO_CLASS + case reflect.Int8: + val |= uint64(f.Int()&0xFF) << shift + shift += 8 + class |= _INT + case reflect.Int16: + val |= uint64(f.Int()&0xFFFF) << shift + shift += 16 + class |= _INT + case reflect.Int32: + val |= uint64(f.Int()&0xFFFF_FFFF) << shift + shift += 32 + class |= _INT + case reflect.Int64, reflect.Int: + addInt(uintptr(f.Int())) + shift = 0 + flushed = true + class = _NO_CLASS + case reflect.Float32: + if class == _FLOAT { + addFloat(uintptr(val)) + val = 0 + shift = 0 + } + val |= uint64(math.Float32bits(float32(f.Float()))) << shift + shift += 32 + class |= _FLOAT + case reflect.Float64: + addFloat(uintptr(math.Float64bits(float64(f.Float())))) + shift = 0 + flushed = true + class = _NO_CLASS + case reflect.Ptr: + addInt(f.Pointer()) + shift = 0 + flushed = true + class = _NO_CLASS + case reflect.Array: + place(f) + default: + panic("purego: unsupported kind " + f.Kind().String()) + } + } + } + place(v) + if !flushed { + if class == _FLOAT { + addFloat(uintptr(val)) + } else { + addInt(uintptr(val)) + } + } +} + +func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { + // Struct is too big to be placed in registers. + // Copy to heap and place the pointer in register + ptrStruct := reflect.New(v.Type()) + ptrStruct.Elem().Set(v) + ptr := ptrStruct.Elem().Addr().UnsafePointer() + keepAlive = append(keepAlive, ptr) + addInt(uintptr(ptr)) + return keepAlive +} + +// isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a +// Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]). +// This type of struct will be placed more compactly than the individual fields. +// +// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst +func isHFA(t reflect.Type) bool { + // round up struct size to nearest 8 see section B.4 + structSize := roundUpTo8(t.Size()) + if structSize == 0 || t.NumField() > 4 { + return false + } + first := t.Field(0) + switch first.Type.Kind() { + case reflect.Float32, reflect.Float64: + firstKind := first.Type.Kind() + for i := 0; i < t.NumField(); i++ { + if t.Field(i).Type.Kind() != firstKind { + return false + } + } + return true + case reflect.Array: + switch first.Type.Elem().Kind() { + case reflect.Float32, reflect.Float64: + return true + default: + return false + } + case reflect.Struct: + for i := 0; i < first.Type.NumField(); i++ { + if !isHFA(first.Type) { + return false + } + } + return true + default: + return false + } +} + +// isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type +// and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]). +// A short vector is a machine type that is composed of repeated instances of one fundamental integral or +// floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]). +// This type of struct will be placed more compactly than the individual fields. +// +// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst +func isHVA(t reflect.Type) bool { + // round up struct size to nearest 8 see section B.4 + structSize := roundUpTo8(t.Size()) + if structSize == 0 || (structSize != 8 && structSize != 16) { + return false + } + first := t.Field(0) + switch first.Type.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: + firstKind := first.Type.Kind() + for i := 0; i < t.NumField(); i++ { + if t.Field(i).Type.Kind() != firstKind { + return false + } + } + return true + case reflect.Array: + switch first.Type.Elem().Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32: + return true + default: + return false + } + default: + return false + } +} diff --git a/vendor/github.com/ebitengine/purego/struct_loong64.go b/vendor/github.com/ebitengine/purego/struct_loong64.go new file mode 100644 index 00000000..69f954f0 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/struct_loong64.go @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2025 The Ebitengine Authors + +package purego + +import ( + "math" + "reflect" + "unsafe" +) + +func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { + outSize := outType.Size() + switch { + case outSize == 0: + return reflect.New(outType).Elem() + case outSize <= 8: + r1 := syscall.a1 + if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { + r1 = syscall.f1 + if numFields == 2 { + r1 = syscall.f2<<32 | syscall.f1 + } + } + return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() + case outSize <= 16: + r1, r2 := syscall.a1, syscall.a2 + if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { + switch numFields { + case 4: + r1 = syscall.f2<<32 | syscall.f1 + r2 = syscall.f4<<32 | syscall.f3 + case 3: + r1 = syscall.f2<<32 | syscall.f1 + r2 = syscall.f3 + case 2: + r1 = syscall.f1 + r2 = syscall.f2 + default: + panic("unreachable") + } + } + return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() + default: + // create struct from the Go pointer created above + // weird pointer dereference to circumvent go vet + return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() + } +} + +const ( + _NO_CLASS = 0b00 + _FLOAT = 0b01 + _INT = 0b11 +) + +func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { + if v.Type().Size() == 0 { + return keepAlive + } + + if size := v.Type().Size(); size <= 16 { + placeRegisters(v, addFloat, addInt) + } else { + keepAlive = placeStack(v, keepAlive, addInt) + } + return keepAlive // the struct was allocated so don't panic +} + +func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { + var val uint64 + var shift byte + var flushed bool + class := _NO_CLASS + var place func(v reflect.Value) + place = func(v reflect.Value) { + var numFields int + if v.Kind() == reflect.Struct { + numFields = v.Type().NumField() + } else { + numFields = v.Type().Len() + } + for k := 0; k < numFields; k++ { + flushed = false + var f reflect.Value + if v.Kind() == reflect.Struct { + f = v.Field(k) + } else { + f = v.Index(k) + } + align := byte(f.Type().Align()*8 - 1) + shift = (shift + align) &^ align + if shift >= 64 { + shift = 0 + flushed = true + if class == _FLOAT { + addFloat(uintptr(val)) + } else { + addInt(uintptr(val)) + } + } + switch f.Type().Kind() { + case reflect.Struct: + place(f) + case reflect.Bool: + if f.Bool() { + val |= 1 + } + shift += 8 + class |= _INT + case reflect.Uint8: + val |= f.Uint() << shift + shift += 8 + class |= _INT + case reflect.Uint16: + val |= f.Uint() << shift + shift += 16 + class |= _INT + case reflect.Uint32: + val |= f.Uint() << shift + shift += 32 + class |= _INT + case reflect.Uint64, reflect.Uint, reflect.Uintptr: + addInt(uintptr(f.Uint())) + shift = 0 + flushed = true + class = _NO_CLASS + case reflect.Int8: + val |= uint64(f.Int()&0xFF) << shift + shift += 8 + class |= _INT + case reflect.Int16: + val |= uint64(f.Int()&0xFFFF) << shift + shift += 16 + class |= _INT + case reflect.Int32: + val |= uint64(f.Int()&0xFFFF_FFFF) << shift + shift += 32 + class |= _INT + case reflect.Int64, reflect.Int: + addInt(uintptr(f.Int())) + shift = 0 + flushed = true + class = _NO_CLASS + case reflect.Float32: + if class == _FLOAT { + addFloat(uintptr(val)) + val = 0 + shift = 0 + } + val |= uint64(math.Float32bits(float32(f.Float()))) << shift + shift += 32 + class |= _FLOAT + case reflect.Float64: + addFloat(uintptr(math.Float64bits(float64(f.Float())))) + shift = 0 + flushed = true + class = _NO_CLASS + case reflect.Ptr: + addInt(f.Pointer()) + shift = 0 + flushed = true + class = _NO_CLASS + case reflect.Array: + place(f) + default: + panic("purego: unsupported kind " + f.Kind().String()) + } + } + } + place(v) + if !flushed { + if class == _FLOAT { + addFloat(uintptr(val)) + } else { + addInt(uintptr(val)) + } + } +} + +func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { + // Struct is too big to be placed in registers. + // Copy to heap and place the pointer in register + ptrStruct := reflect.New(v.Type()) + ptrStruct.Elem().Set(v) + ptr := ptrStruct.Elem().Addr().UnsafePointer() + keepAlive = append(keepAlive, ptr) + addInt(uintptr(ptr)) + return keepAlive +} diff --git a/vendor/github.com/ebitengine/purego/struct_other.go b/vendor/github.com/ebitengine/purego/struct_other.go new file mode 100644 index 00000000..58ccc973 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/struct_other.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2024 The Ebitengine Authors + +//go:build !amd64 && !arm64 && !loong64 + +package purego + +import "reflect" + +func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { + panic("purego: struct arguments are not supported") +} + +func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { + panic("purego: struct returns are not supported") +} + +func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { + panic("purego: not needed on other platforms") +} diff --git a/vendor/github.com/ebitengine/purego/sys_amd64.s b/vendor/github.com/ebitengine/purego/sys_amd64.s new file mode 100644 index 00000000..a364dd0c --- /dev/null +++ b/vendor/github.com/ebitengine/purego/sys_amd64.s @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux || netbsd + +#include "textflag.h" +#include "abi_amd64.h" +#include "go_asm.h" +#include "funcdata.h" + +#define STACK_SIZE 80 +#define PTR_ADDRESS (STACK_SIZE - 8) + +// syscall15X calls a function in libc on behalf of the syscall package. +// syscall15X takes a pointer to a struct like: +// struct { +// fn uintptr +// a1 uintptr +// a2 uintptr +// a3 uintptr +// a4 uintptr +// a5 uintptr +// a6 uintptr +// a7 uintptr +// a8 uintptr +// a9 uintptr +// a10 uintptr +// a11 uintptr +// a12 uintptr +// a13 uintptr +// a14 uintptr +// a15 uintptr +// r1 uintptr +// r2 uintptr +// err uintptr +// } +// syscall15X must be called on the g0 stack with the +// C calling convention (use libcCall). +GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 +DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) +TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0 + PUSHQ BP + MOVQ SP, BP + SUBQ $STACK_SIZE, SP + MOVQ DI, PTR_ADDRESS(BP) // save the pointer + MOVQ DI, R11 + + MOVQ syscall15Args_f1(R11), X0 // f1 + MOVQ syscall15Args_f2(R11), X1 // f2 + MOVQ syscall15Args_f3(R11), X2 // f3 + MOVQ syscall15Args_f4(R11), X3 // f4 + MOVQ syscall15Args_f5(R11), X4 // f5 + MOVQ syscall15Args_f6(R11), X5 // f6 + MOVQ syscall15Args_f7(R11), X6 // f7 + MOVQ syscall15Args_f8(R11), X7 // f8 + + MOVQ syscall15Args_a1(R11), DI // a1 + MOVQ syscall15Args_a2(R11), SI // a2 + MOVQ syscall15Args_a3(R11), DX // a3 + MOVQ syscall15Args_a4(R11), CX // a4 + MOVQ syscall15Args_a5(R11), R8 // a5 + MOVQ syscall15Args_a6(R11), R9 // a6 + + // push the remaining paramters onto the stack + MOVQ syscall15Args_a7(R11), R12 + MOVQ R12, 0(SP) // push a7 + MOVQ syscall15Args_a8(R11), R12 + MOVQ R12, 8(SP) // push a8 + MOVQ syscall15Args_a9(R11), R12 + MOVQ R12, 16(SP) // push a9 + MOVQ syscall15Args_a10(R11), R12 + MOVQ R12, 24(SP) // push a10 + MOVQ syscall15Args_a11(R11), R12 + MOVQ R12, 32(SP) // push a11 + MOVQ syscall15Args_a12(R11), R12 + MOVQ R12, 40(SP) // push a12 + MOVQ syscall15Args_a13(R11), R12 + MOVQ R12, 48(SP) // push a13 + MOVQ syscall15Args_a14(R11), R12 + MOVQ R12, 56(SP) // push a14 + MOVQ syscall15Args_a15(R11), R12 + MOVQ R12, 64(SP) // push a15 + XORL AX, AX // vararg: say "no float args" + + MOVQ syscall15Args_fn(R11), R10 // fn + CALL R10 + + MOVQ PTR_ADDRESS(BP), DI // get the pointer back + MOVQ AX, syscall15Args_a1(DI) // r1 + MOVQ DX, syscall15Args_a2(DI) // r3 + MOVQ X0, syscall15Args_f1(DI) // f1 + MOVQ X1, syscall15Args_f2(DI) // f2 + + XORL AX, AX // no error (it's ignored anyway) + ADDQ $STACK_SIZE, SP + MOVQ BP, SP + POPQ BP + RET + +TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 + MOVQ 0(SP), AX // save the return address to calculate the cb index + MOVQ 8(SP), R10 // get the return SP so that we can align register args with stack args + ADDQ $8, SP // remove return address from stack, we are not returning to callbackasm, but to its caller. + + // make space for first six int and 8 float arguments below the frame + ADJSP $14*8, SP + MOVSD X0, (1*8)(SP) + MOVSD X1, (2*8)(SP) + MOVSD X2, (3*8)(SP) + MOVSD X3, (4*8)(SP) + MOVSD X4, (5*8)(SP) + MOVSD X5, (6*8)(SP) + MOVSD X6, (7*8)(SP) + MOVSD X7, (8*8)(SP) + MOVQ DI, (9*8)(SP) + MOVQ SI, (10*8)(SP) + MOVQ DX, (11*8)(SP) + MOVQ CX, (12*8)(SP) + MOVQ R8, (13*8)(SP) + MOVQ R9, (14*8)(SP) + LEAQ 8(SP), R8 // R8 = address of args vector + + PUSHQ R10 // push the stack pointer below registers + + // Switch from the host ABI to the Go ABI. + PUSH_REGS_HOST_TO_ABI0() + + // determine index into runtime·cbs table + MOVQ $callbackasm(SB), DX + SUBQ DX, AX + MOVQ $0, DX + MOVQ $5, CX // divide by 5 because each call instruction in ·callbacks is 5 bytes long + DIVL CX + SUBQ $1, AX // subtract 1 because return PC is to the next slot + + // Create a struct callbackArgs on our stack to be passed as + // the "frame" to cgocallback and on to callbackWrap. + // $24 to make enough room for the arguments to runtime.cgocallback + SUBQ $(24+callbackArgs__size), SP + MOVQ AX, (24+callbackArgs_index)(SP) // callback index + MOVQ R8, (24+callbackArgs_args)(SP) // address of args vector + MOVQ $0, (24+callbackArgs_result)(SP) // result + LEAQ 24(SP), AX // take the address of callbackArgs + + // Call cgocallback, which will call callbackWrap(frame). + MOVQ ·callbackWrap_call(SB), DI // Get the ABIInternal function pointer + MOVQ (DI), DI // without by using a closure. + MOVQ AX, SI // frame (address of callbackArgs) + MOVQ $0, CX // context + + CALL crosscall2(SB) // runtime.cgocallback(fn, frame, ctxt uintptr) + + // Get callback result. + MOVQ (24+callbackArgs_result)(SP), AX + ADDQ $(24+callbackArgs__size), SP // remove callbackArgs struct + + POP_REGS_HOST_TO_ABI0() + + POPQ R10 // get the SP back + ADJSP $-14*8, SP // remove arguments + + MOVQ R10, 0(SP) + + RET diff --git a/vendor/github.com/ebitengine/purego/sys_arm64.s b/vendor/github.com/ebitengine/purego/sys_arm64.s new file mode 100644 index 00000000..a4f5be72 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/sys_arm64.s @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux || netbsd || windows + +#include "textflag.h" +#include "go_asm.h" +#include "funcdata.h" + +#define STACK_SIZE 64 +#define PTR_ADDRESS (STACK_SIZE - 8) + +// syscall15X calls a function in libc on behalf of the syscall package. +// syscall15X takes a pointer to a struct like: +// struct { +// fn uintptr +// a1 uintptr +// a2 uintptr +// a3 uintptr +// a4 uintptr +// a5 uintptr +// a6 uintptr +// a7 uintptr +// a8 uintptr +// a9 uintptr +// a10 uintptr +// a11 uintptr +// a12 uintptr +// a13 uintptr +// a14 uintptr +// a15 uintptr +// r1 uintptr +// r2 uintptr +// err uintptr +// } +// syscall15X must be called on the g0 stack with the +// C calling convention (use libcCall). +GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 +DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) +TEXT syscall15X(SB), NOSPLIT, $0 + SUB $STACK_SIZE, RSP // push structure pointer + MOVD R0, PTR_ADDRESS(RSP) + MOVD R0, R9 + + FMOVD syscall15Args_f1(R9), F0 // f1 + FMOVD syscall15Args_f2(R9), F1 // f2 + FMOVD syscall15Args_f3(R9), F2 // f3 + FMOVD syscall15Args_f4(R9), F3 // f4 + FMOVD syscall15Args_f5(R9), F4 // f5 + FMOVD syscall15Args_f6(R9), F5 // f6 + FMOVD syscall15Args_f7(R9), F6 // f7 + FMOVD syscall15Args_f8(R9), F7 // f8 + + MOVD syscall15Args_a1(R9), R0 // a1 + MOVD syscall15Args_a2(R9), R1 // a2 + MOVD syscall15Args_a3(R9), R2 // a3 + MOVD syscall15Args_a4(R9), R3 // a4 + MOVD syscall15Args_a5(R9), R4 // a5 + MOVD syscall15Args_a6(R9), R5 // a6 + MOVD syscall15Args_a7(R9), R6 // a7 + MOVD syscall15Args_a8(R9), R7 // a8 + MOVD syscall15Args_arm64_r8(R9), R8 // r8 + + MOVD syscall15Args_a9(R9), R10 + MOVD R10, 0(RSP) // push a9 onto stack + MOVD syscall15Args_a10(R9), R10 + MOVD R10, 8(RSP) // push a10 onto stack + MOVD syscall15Args_a11(R9), R10 + MOVD R10, 16(RSP) // push a11 onto stack + MOVD syscall15Args_a12(R9), R10 + MOVD R10, 24(RSP) // push a12 onto stack + MOVD syscall15Args_a13(R9), R10 + MOVD R10, 32(RSP) // push a13 onto stack + MOVD syscall15Args_a14(R9), R10 + MOVD R10, 40(RSP) // push a14 onto stack + MOVD syscall15Args_a15(R9), R10 + MOVD R10, 48(RSP) // push a15 onto stack + + MOVD syscall15Args_fn(R9), R10 // fn + BL (R10) + + MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer + ADD $STACK_SIZE, RSP + + MOVD R0, syscall15Args_a1(R2) // save r1 + MOVD R1, syscall15Args_a2(R2) // save r3 + FMOVD F0, syscall15Args_f1(R2) // save f0 + FMOVD F1, syscall15Args_f2(R2) // save f1 + FMOVD F2, syscall15Args_f3(R2) // save f2 + FMOVD F3, syscall15Args_f4(R2) // save f3 + + RET diff --git a/vendor/github.com/ebitengine/purego/sys_loong64.s b/vendor/github.com/ebitengine/purego/sys_loong64.s new file mode 100644 index 00000000..0f34eaee --- /dev/null +++ b/vendor/github.com/ebitengine/purego/sys_loong64.s @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2025 The Ebitengine Authors + +//go:build linux + +#include "textflag.h" +#include "go_asm.h" +#include "funcdata.h" + +#define STACK_SIZE 64 +#define PTR_ADDRESS (STACK_SIZE - 8) + +// syscall15X calls a function in libc on behalf of the syscall package. +// syscall15X takes a pointer to a struct like: +// struct { +// fn uintptr +// a1 uintptr +// a2 uintptr +// a3 uintptr +// a4 uintptr +// a5 uintptr +// a6 uintptr +// a7 uintptr +// a8 uintptr +// a9 uintptr +// a10 uintptr +// a11 uintptr +// a12 uintptr +// a13 uintptr +// a14 uintptr +// a15 uintptr +// r1 uintptr +// r2 uintptr +// err uintptr +// } +// syscall15X must be called on the g0 stack with the +// C calling convention (use libcCall). +GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 +DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) +TEXT syscall15X(SB), NOSPLIT, $0 + // push structure pointer + SUBV $STACK_SIZE, R3 + MOVV R4, PTR_ADDRESS(R3) + MOVV R4, R13 + + MOVD syscall15Args_f1(R13), F0 // f1 + MOVD syscall15Args_f2(R13), F1 // f2 + MOVD syscall15Args_f3(R13), F2 // f3 + MOVD syscall15Args_f4(R13), F3 // f4 + MOVD syscall15Args_f5(R13), F4 // f5 + MOVD syscall15Args_f6(R13), F5 // f6 + MOVD syscall15Args_f7(R13), F6 // f7 + MOVD syscall15Args_f8(R13), F7 // f8 + + MOVV syscall15Args_a1(R13), R4 // a1 + MOVV syscall15Args_a2(R13), R5 // a2 + MOVV syscall15Args_a3(R13), R6 // a3 + MOVV syscall15Args_a4(R13), R7 // a4 + MOVV syscall15Args_a5(R13), R8 // a5 + MOVV syscall15Args_a6(R13), R9 // a6 + MOVV syscall15Args_a7(R13), R10 // a7 + MOVV syscall15Args_a8(R13), R11 // a8 + + // push a9-a15 onto stack + MOVV syscall15Args_a9(R13), R12 + MOVV R12, 0(R3) + MOVV syscall15Args_a10(R13), R12 + MOVV R12, 8(R3) + MOVV syscall15Args_a11(R13), R12 + MOVV R12, 16(R3) + MOVV syscall15Args_a12(R13), R12 + MOVV R12, 24(R3) + MOVV syscall15Args_a13(R13), R12 + MOVV R12, 32(R3) + MOVV syscall15Args_a14(R13), R12 + MOVV R12, 40(R3) + MOVV syscall15Args_a15(R13), R12 + MOVV R12, 48(R3) + + MOVV syscall15Args_fn(R13), R12 + JAL (R12) + + // pop structure pointer + MOVV PTR_ADDRESS(R3), R13 + ADDV $STACK_SIZE, R3 + + // save R4, R5 + MOVV R4, syscall15Args_a1(R13) + MOVV R5, syscall15Args_a2(R13) + + // save f0-f3 + MOVD F0, syscall15Args_f1(R13) + MOVD F1, syscall15Args_f2(R13) + MOVD F2, syscall15Args_f3(R13) + MOVD F3, syscall15Args_f4(R13) + RET diff --git a/vendor/github.com/ebitengine/purego/sys_unix_arm64.s b/vendor/github.com/ebitengine/purego/sys_unix_arm64.s new file mode 100644 index 00000000..cea803ef --- /dev/null +++ b/vendor/github.com/ebitengine/purego/sys_unix_arm64.s @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 The Ebitengine Authors + +//go:build darwin || freebsd || linux || netbsd + +#include "textflag.h" +#include "go_asm.h" +#include "funcdata.h" +#include "abi_arm64.h" + +TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 + NO_LOCAL_POINTERS + + // On entry, the trampoline in zcallback_darwin_arm64.s left + // the callback index in R12 (which is volatile in the C ABI). + + // Save callback register arguments R0-R7 and F0-F7. + // We do this at the top of the frame so they're contiguous with stack arguments. + SUB $(16*8), RSP, R14 + FSTPD (F0, F1), (0*8)(R14) + FSTPD (F2, F3), (2*8)(R14) + FSTPD (F4, F5), (4*8)(R14) + FSTPD (F6, F7), (6*8)(R14) + STP (R0, R1), (8*8)(R14) + STP (R2, R3), (10*8)(R14) + STP (R4, R5), (12*8)(R14) + STP (R6, R7), (14*8)(R14) + + // Adjust SP by frame size. + SUB $(26*8), RSP + + // It is important to save R27 because the go assembler + // uses it for move instructions for a variable. + // This line: + // MOVD ·callbackWrap_call(SB), R0 + // Creates the instructions: + // ADRP 14335(PC), R27 + // MOVD 388(27), R0 + // R27 is a callee saved register so we are responsible + // for ensuring its value doesn't change. So save it and + // restore it at the end of this function. + // R30 is the link register. crosscall2 doesn't save it + // so it's saved here. + STP (R27, R30), 0(RSP) + + // Create a struct callbackArgs on our stack. + MOVD $(callbackArgs__size)(RSP), R13 + MOVD R12, callbackArgs_index(R13) // callback index + MOVD R14, callbackArgs_args(R13) // address of args vector + MOVD ZR, callbackArgs_result(R13) // result + + // Move parameters into registers + // Get the ABIInternal function pointer + // without by using a closure. + MOVD ·callbackWrap_call(SB), R0 + MOVD (R0), R0 // fn unsafe.Pointer + MOVD R13, R1 // frame (&callbackArgs{...}) + MOVD $0, R3 // ctxt uintptr + + BL crosscall2(SB) + + // Get callback result. + MOVD $(callbackArgs__size)(RSP), R13 + MOVD callbackArgs_result(R13), R0 + + // Restore LR and R27 + LDP 0(RSP), (R27, R30) + ADD $(26*8), RSP + + RET diff --git a/vendor/github.com/ebitengine/purego/sys_unix_loong64.s b/vendor/github.com/ebitengine/purego/sys_unix_loong64.s new file mode 100644 index 00000000..89dbd7d1 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/sys_unix_loong64.s @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2025 The Ebitengine Authors + +//go:build linux + +#include "textflag.h" +#include "go_asm.h" +#include "funcdata.h" +#include "abi_loong64.h" + +TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 + NO_LOCAL_POINTERS + + SUBV $(16*8), R3, R14 + MOVD F0, 0(R14) + MOVD F1, 8(R14) + MOVD F2, 16(R14) + MOVD F3, 24(R14) + MOVD F4, 32(R14) + MOVD F5, 40(R14) + MOVD F6, 48(R14) + MOVD F7, 56(R14) + MOVV R4, 64(R14) + MOVV R5, 72(R14) + MOVV R6, 80(R14) + MOVV R7, 88(R14) + MOVV R8, 96(R14) + MOVV R9, 104(R14) + MOVV R10, 112(R14) + MOVV R11, 120(R14) + + // Adjust SP by frame size. + SUBV $(22*8), R3 + + // It is important to save R30 because the go assembler + // uses it for move instructions for a variable. + // This line: + // MOVV ·callbackWrap_call(SB), R4 + // Creates the instructions: + // PCALAU12I off1(PC), R30 + // MOVV off2(R30), R4 + // R30 is a callee saved register so we are responsible + // for ensuring its value doesn't change. So save it and + // restore it at the end of this function. + // R1 is the link register. crosscall2 doesn't save it + // so it's saved here. + MOVV R1, 0(R3) + MOVV R30, 8(R3) + + // Create a struct callbackArgs on our stack. + MOVV $(callbackArgs__size)(R3), R13 + MOVV R12, callbackArgs_index(R13) // callback index + MOVV R14, callbackArgs_args(R13) // address of args vector + MOVV $0, callbackArgs_result(R13) // result + + // Move parameters into registers + // Get the ABIInternal function pointer + // without by using a closure. + MOVV ·callbackWrap_call(SB), R4 + MOVV (R4), R4 // fn unsafe.Pointer + MOVV R13, R5 // frame (&callbackArgs{...}) + MOVV $0, R7 // ctxt uintptr + + JAL crosscall2(SB) + + // Get callback result. + MOVV $(callbackArgs__size)(R3), R13 + MOVV callbackArgs_result(R13), R4 + + // Restore LR and R30 + MOVV 0(R3), R1 + MOVV 8(R3), R30 + ADDV $(22*8), R3 + + RET diff --git a/vendor/github.com/ebitengine/purego/syscall.go b/vendor/github.com/ebitengine/purego/syscall.go new file mode 100644 index 00000000..ccfc4982 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/syscall.go @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || linux || netbsd || windows + +package purego + +// CDecl marks a function as being called using the __cdecl calling convention as defined in +// the [MSDocs] when passed to NewCallback. It must be the first argument to the function. +// This is only useful on 386 Windows, but it is safe to use on other platforms. +// +// [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 +type CDecl struct{} + +const ( + maxArgs = 15 + numOfFloatRegisters = 8 // arm64 and amd64 both have 8 float registers +) + +type syscall15Args struct { + fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr + f1, f2, f3, f4, f5, f6, f7, f8 uintptr + arm64_r8 uintptr +} + +// SyscallN takes fn, a C function pointer and a list of arguments as uintptr. +// There is an internal maximum number of arguments that SyscallN can take. It panics +// when the maximum is exceeded. It returns the result and the libc error code if there is one. +// +// In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] +// especially point 4. +// +// NOTE: SyscallN does not properly call functions that have both integer and float parameters. +// See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 +// for an explanation of why that is. +// +// On amd64, if there are more than 8 floats the 9th and so on will be placed incorrectly on the +// stack. +// +// The pragma go:nosplit is not needed at this function declaration because it uses go:uintptrescapes +// which forces all the objects that the uintptrs point to onto the heap where a stack split won't affect +// their memory location. +// +//go:uintptrescapes +func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { + if fn == 0 { + panic("purego: fn is nil") + } + if len(args) > maxArgs { + panic("purego: too many arguments to SyscallN") + } + // add padding so there is no out-of-bounds slicing + var tmp [maxArgs]uintptr + copy(tmp[:], args) + return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) +} diff --git a/vendor/github.com/ebitengine/purego/syscall_cgo_linux.go b/vendor/github.com/ebitengine/purego/syscall_cgo_linux.go new file mode 100644 index 00000000..7794c263 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/syscall_cgo_linux.go @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build cgo && !(amd64 || arm64 || loong64) + +package purego + +import ( + "github.com/ebitengine/purego/internal/cgo" +) + +var syscall15XABI0 = uintptr(cgo.Syscall15XABI0) + +//go:nosplit +func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { + return cgo.Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) +} + +func NewCallback(_ any) uintptr { + panic("purego: NewCallback on Linux is only supported on amd64/arm64/loong64") +} diff --git a/vendor/github.com/ebitengine/purego/syscall_sysv.go b/vendor/github.com/ebitengine/purego/syscall_sysv.go new file mode 100644 index 00000000..d794bc38 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/syscall_sysv.go @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd + +package purego + +import ( + "reflect" + "runtime" + "sync" + "unsafe" +) + +var syscall15XABI0 uintptr + +func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { + args := thePool.Get().(*syscall15Args) + defer thePool.Put(args) + + *args = syscall15Args{ + fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, + a1, a2, a3, a4, a5, a6, a7, a8, + 0, + } + + runtime_cgocall(syscall15XABI0, unsafe.Pointer(args)) + return args.a1, args.a2, 0 +} + +// NewCallback converts a Go function to a function pointer conforming to the C calling convention. +// This is useful when interoperating with C code requiring callbacks. The argument is expected to be a +// function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size +// of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated +// for these callbacks is never released. At least 2000 callbacks can always be created. Although this function +// provides similar functionality to windows.NewCallback it is distinct. +func NewCallback(fn any) uintptr { + ty := reflect.TypeOf(fn) + for i := 0; i < ty.NumIn(); i++ { + in := ty.In(i) + if !in.AssignableTo(reflect.TypeOf(CDecl{})) { + continue + } + if i != 0 { + panic("purego: CDecl must be the first argument") + } + } + return compileCallback(fn) +} + +// maxCb is the maximum number of callbacks +// only increase this if you have added more to the callbackasm function +const maxCB = 2000 + +var cbs struct { + lock sync.Mutex + numFn int // the number of functions currently in cbs.funcs + funcs [maxCB]reflect.Value // the saved callbacks +} + +type callbackArgs struct { + index uintptr + // args points to the argument block. + // + // The structure of the arguments goes + // float registers followed by the + // integer registers followed by the stack. + // + // This variable is treated as a continuous + // block of memory containing all of the arguments + // for this callback. + args unsafe.Pointer + // Below are out-args from callbackWrap + result uintptr +} + +func compileCallback(fn any) uintptr { + val := reflect.ValueOf(fn) + if val.Kind() != reflect.Func { + panic("purego: the type must be a function but was not") + } + if val.IsNil() { + panic("purego: function must not be nil") + } + ty := val.Type() + for i := 0; i < ty.NumIn(); i++ { + in := ty.In(i) + switch in.Kind() { + case reflect.Struct: + if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) { + continue + } + fallthrough + case reflect.Interface, reflect.Func, reflect.Slice, + reflect.Chan, reflect.Complex64, reflect.Complex128, + reflect.String, reflect.Map, reflect.Invalid: + panic("purego: unsupported argument type: " + in.Kind().String()) + } + } +output: + switch { + case ty.NumOut() == 1: + switch ty.Out(0).Kind() { + case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Bool, reflect.UnsafePointer: + break output + } + panic("purego: unsupported return type: " + ty.String()) + case ty.NumOut() > 1: + panic("purego: callbacks can only have one return") + } + cbs.lock.Lock() + defer cbs.lock.Unlock() + if cbs.numFn >= maxCB { + panic("purego: the maximum number of callbacks has been reached") + } + cbs.funcs[cbs.numFn] = val + cbs.numFn++ + return callbackasmAddr(cbs.numFn - 1) +} + +const ptrSize = unsafe.Sizeof((*int)(nil)) + +const callbackMaxFrame = 64 * ptrSize + +// callbackasm is implemented in zcallback_GOOS_GOARCH.s +// +//go:linkname __callbackasm callbackasm +var __callbackasm byte +var callbackasmABI0 = uintptr(unsafe.Pointer(&__callbackasm)) + +// callbackWrap_call allows the calling of the ABIInternal wrapper +// which is required for runtime.cgocallback without the +// tag which is only allowed in the runtime. +// This closure is used inside sys_darwin_GOARCH.s +var callbackWrap_call = callbackWrap + +// callbackWrap is called by assembly code which determines which Go function to call. +// This function takes the arguments and passes them to the Go function and returns the result. +func callbackWrap(a *callbackArgs) { + cbs.lock.Lock() + fn := cbs.funcs[a.index] + cbs.lock.Unlock() + fnType := fn.Type() + args := make([]reflect.Value, fnType.NumIn()) + frame := (*[callbackMaxFrame]uintptr)(a.args) + var floatsN int // floatsN represents the number of float arguments processed + var intsN int // intsN represents the number of integer arguments processed + // stack points to the index into frame of the current stack element. + // The stack begins after the float and integer registers. + stack := numOfIntegerRegisters() + numOfFloatRegisters + for i := range args { + var pos int + switch fnType.In(i).Kind() { + case reflect.Float32, reflect.Float64: + if floatsN >= numOfFloatRegisters { + pos = stack + stack++ + } else { + pos = floatsN + } + floatsN++ + case reflect.Struct: + // This is the CDecl field + args[i] = reflect.Zero(fnType.In(i)) + continue + default: + + if intsN >= numOfIntegerRegisters() { + pos = stack + stack++ + } else { + // the integers begin after the floats in frame + pos = intsN + numOfFloatRegisters + } + intsN++ + } + args[i] = reflect.NewAt(fnType.In(i), unsafe.Pointer(&frame[pos])).Elem() + } + ret := fn.Call(args) + if len(ret) > 0 { + switch k := ret[0].Kind(); k { + case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uintptr: + a.result = uintptr(ret[0].Uint()) + case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: + a.result = uintptr(ret[0].Int()) + case reflect.Bool: + if ret[0].Bool() { + a.result = 1 + } else { + a.result = 0 + } + case reflect.Pointer: + a.result = ret[0].Pointer() + case reflect.UnsafePointer: + a.result = ret[0].Pointer() + default: + panic("purego: unsupported kind: " + k.String()) + } + } +} + +// callbackasmAddr returns address of runtime.callbackasm +// function adjusted by i. +// On x86 and amd64, runtime.callbackasm is a series of CALL instructions, +// and we want callback to arrive at +// correspondent call instruction instead of start of +// runtime.callbackasm. +// On ARM, runtime.callbackasm is a series of mov and branch instructions. +// R12 is loaded with the callback index. Each entry is two instructions, +// hence 8 bytes. +func callbackasmAddr(i int) uintptr { + var entrySize int + switch runtime.GOARCH { + default: + panic("purego: unsupported architecture") + case "386", "amd64": + entrySize = 5 + case "arm", "arm64", "loong64": + // On ARM and ARM64, each entry is a MOV instruction + // followed by a branch instruction + entrySize = 8 + } + return callbackasmABI0 + uintptr(i*entrySize) +} diff --git a/vendor/github.com/ebitengine/purego/syscall_windows.go b/vendor/github.com/ebitengine/purego/syscall_windows.go new file mode 100644 index 00000000..5afd8d83 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/syscall_windows.go @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +package purego + +import ( + "reflect" + "syscall" +) + +var syscall15XABI0 uintptr + +func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { + r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) + return r1, r2, uintptr(errno) +} + +// NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention. +// This is useful when interoperating with Windows code requiring callbacks. The argument is expected to be a +// function with one uintptr-sized result. The function must not have arguments with size larger than the +// size of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory +// allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024 +// callbacks can always be created. Although this function is similiar to the darwin version it may act +// differently. +func NewCallback(fn any) uintptr { + isCDecl := false + ty := reflect.TypeOf(fn) + for i := 0; i < ty.NumIn(); i++ { + in := ty.In(i) + if !in.AssignableTo(reflect.TypeOf(CDecl{})) { + continue + } + if i != 0 { + panic("purego: CDecl must be the first argument") + } + isCDecl = true + } + if isCDecl { + return syscall.NewCallbackCDecl(fn) + } + return syscall.NewCallback(fn) +} + +func loadSymbol(handle uintptr, name string) (uintptr, error) { + return syscall.GetProcAddress(syscall.Handle(handle), name) +} diff --git a/vendor/github.com/ebitengine/purego/zcallback_amd64.s b/vendor/github.com/ebitengine/purego/zcallback_amd64.s new file mode 100644 index 00000000..42b54c48 --- /dev/null +++ b/vendor/github.com/ebitengine/purego/zcallback_amd64.s @@ -0,0 +1,2014 @@ +// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. + +//go:build darwin || freebsd || linux || netbsd + +// runtime·callbackasm is called by external code to +// execute Go implemented callback function. It is not +// called from the start, instead runtime·compilecallback +// always returns address into runtime·callbackasm offset +// appropriately so different callbacks start with different +// CALL instruction in runtime·callbackasm. This determines +// which Go callback function is executed later on. +#include "textflag.h" + +TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) + CALL callbackasm1(SB) diff --git a/vendor/github.com/ebitengine/purego/zcallback_arm64.s b/vendor/github.com/ebitengine/purego/zcallback_arm64.s new file mode 100644 index 00000000..087c2d4f --- /dev/null +++ b/vendor/github.com/ebitengine/purego/zcallback_arm64.s @@ -0,0 +1,4014 @@ +// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. + +//go:build darwin || freebsd || linux || netbsd + +// External code calls into callbackasm at an offset corresponding +// to the callback index. Callbackasm is a table of MOV and B instructions. +// The MOV instruction loads R12 with the callback index, and the +// B instruction branches to callbackasm1. +// callbackasm1 takes the callback index from R12 and +// indexes into an array that stores information about each callback. +// It then calls the Go implementation for that callback. +#include "textflag.h" + +TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 + MOVD $0, R12 + B callbackasm1(SB) + MOVD $1, R12 + B callbackasm1(SB) + MOVD $2, R12 + B callbackasm1(SB) + MOVD $3, R12 + B callbackasm1(SB) + MOVD $4, R12 + B callbackasm1(SB) + MOVD $5, R12 + B callbackasm1(SB) + MOVD $6, R12 + B callbackasm1(SB) + MOVD $7, R12 + B callbackasm1(SB) + MOVD $8, R12 + B callbackasm1(SB) + MOVD $9, R12 + B callbackasm1(SB) + MOVD $10, R12 + B callbackasm1(SB) + MOVD $11, R12 + B callbackasm1(SB) + MOVD $12, R12 + B callbackasm1(SB) + MOVD $13, R12 + B callbackasm1(SB) + MOVD $14, R12 + B callbackasm1(SB) + MOVD $15, R12 + B callbackasm1(SB) + MOVD $16, R12 + B callbackasm1(SB) + MOVD $17, R12 + B callbackasm1(SB) + MOVD $18, R12 + B callbackasm1(SB) + MOVD $19, R12 + B callbackasm1(SB) + MOVD $20, R12 + B callbackasm1(SB) + MOVD $21, R12 + B callbackasm1(SB) + MOVD $22, R12 + B callbackasm1(SB) + MOVD $23, R12 + B callbackasm1(SB) + MOVD $24, R12 + B callbackasm1(SB) + MOVD $25, R12 + B callbackasm1(SB) + MOVD $26, R12 + B callbackasm1(SB) + MOVD $27, R12 + B callbackasm1(SB) + MOVD $28, R12 + B callbackasm1(SB) + MOVD $29, R12 + B callbackasm1(SB) + MOVD $30, R12 + B callbackasm1(SB) + MOVD $31, R12 + B callbackasm1(SB) + MOVD $32, R12 + B callbackasm1(SB) + MOVD $33, R12 + B callbackasm1(SB) + MOVD $34, R12 + B callbackasm1(SB) + MOVD $35, R12 + B callbackasm1(SB) + MOVD $36, R12 + B callbackasm1(SB) + MOVD $37, R12 + B callbackasm1(SB) + MOVD $38, R12 + B callbackasm1(SB) + MOVD $39, R12 + B callbackasm1(SB) + MOVD $40, R12 + B callbackasm1(SB) + MOVD $41, R12 + B callbackasm1(SB) + MOVD $42, R12 + B callbackasm1(SB) + MOVD $43, R12 + B callbackasm1(SB) + MOVD $44, R12 + B callbackasm1(SB) + MOVD $45, R12 + B callbackasm1(SB) + MOVD $46, R12 + B callbackasm1(SB) + MOVD $47, R12 + B callbackasm1(SB) + MOVD $48, R12 + B callbackasm1(SB) + MOVD $49, R12 + B callbackasm1(SB) + MOVD $50, R12 + B callbackasm1(SB) + MOVD $51, R12 + B callbackasm1(SB) + MOVD $52, R12 + B callbackasm1(SB) + MOVD $53, R12 + B callbackasm1(SB) + MOVD $54, R12 + B callbackasm1(SB) + MOVD $55, R12 + B callbackasm1(SB) + MOVD $56, R12 + B callbackasm1(SB) + MOVD $57, R12 + B callbackasm1(SB) + MOVD $58, R12 + B callbackasm1(SB) + MOVD $59, R12 + B callbackasm1(SB) + MOVD $60, R12 + B callbackasm1(SB) + MOVD $61, R12 + B callbackasm1(SB) + MOVD $62, R12 + B callbackasm1(SB) + MOVD $63, R12 + B callbackasm1(SB) + MOVD $64, R12 + B callbackasm1(SB) + MOVD $65, R12 + B callbackasm1(SB) + MOVD $66, R12 + B callbackasm1(SB) + MOVD $67, R12 + B callbackasm1(SB) + MOVD $68, R12 + B callbackasm1(SB) + MOVD $69, R12 + B callbackasm1(SB) + MOVD $70, R12 + B callbackasm1(SB) + MOVD $71, R12 + B callbackasm1(SB) + MOVD $72, R12 + B callbackasm1(SB) + MOVD $73, R12 + B callbackasm1(SB) + MOVD $74, R12 + B callbackasm1(SB) + MOVD $75, R12 + B callbackasm1(SB) + MOVD $76, R12 + B callbackasm1(SB) + MOVD $77, R12 + B callbackasm1(SB) + MOVD $78, R12 + B callbackasm1(SB) + MOVD $79, R12 + B callbackasm1(SB) + MOVD $80, R12 + B callbackasm1(SB) + MOVD $81, R12 + B callbackasm1(SB) + MOVD $82, R12 + B callbackasm1(SB) + MOVD $83, R12 + B callbackasm1(SB) + MOVD $84, R12 + B callbackasm1(SB) + MOVD $85, R12 + B callbackasm1(SB) + MOVD $86, R12 + B callbackasm1(SB) + MOVD $87, R12 + B callbackasm1(SB) + MOVD $88, R12 + B callbackasm1(SB) + MOVD $89, R12 + B callbackasm1(SB) + MOVD $90, R12 + B callbackasm1(SB) + MOVD $91, R12 + B callbackasm1(SB) + MOVD $92, R12 + B callbackasm1(SB) + MOVD $93, R12 + B callbackasm1(SB) + MOVD $94, R12 + B callbackasm1(SB) + MOVD $95, R12 + B callbackasm1(SB) + MOVD $96, R12 + B callbackasm1(SB) + MOVD $97, R12 + B callbackasm1(SB) + MOVD $98, R12 + B callbackasm1(SB) + MOVD $99, R12 + B callbackasm1(SB) + MOVD $100, R12 + B callbackasm1(SB) + MOVD $101, R12 + B callbackasm1(SB) + MOVD $102, R12 + B callbackasm1(SB) + MOVD $103, R12 + B callbackasm1(SB) + MOVD $104, R12 + B callbackasm1(SB) + MOVD $105, R12 + B callbackasm1(SB) + MOVD $106, R12 + B callbackasm1(SB) + MOVD $107, R12 + B callbackasm1(SB) + MOVD $108, R12 + B callbackasm1(SB) + MOVD $109, R12 + B callbackasm1(SB) + MOVD $110, R12 + B callbackasm1(SB) + MOVD $111, R12 + B callbackasm1(SB) + MOVD $112, R12 + B callbackasm1(SB) + MOVD $113, R12 + B callbackasm1(SB) + MOVD $114, R12 + B callbackasm1(SB) + MOVD $115, R12 + B callbackasm1(SB) + MOVD $116, R12 + B callbackasm1(SB) + MOVD $117, R12 + B callbackasm1(SB) + MOVD $118, R12 + B callbackasm1(SB) + MOVD $119, R12 + B callbackasm1(SB) + MOVD $120, R12 + B callbackasm1(SB) + MOVD $121, R12 + B callbackasm1(SB) + MOVD $122, R12 + B callbackasm1(SB) + MOVD $123, R12 + B callbackasm1(SB) + MOVD $124, R12 + B callbackasm1(SB) + MOVD $125, R12 + B callbackasm1(SB) + MOVD $126, R12 + B callbackasm1(SB) + MOVD $127, R12 + B callbackasm1(SB) + MOVD $128, R12 + B callbackasm1(SB) + MOVD $129, R12 + B callbackasm1(SB) + MOVD $130, R12 + B callbackasm1(SB) + MOVD $131, R12 + B callbackasm1(SB) + MOVD $132, R12 + B callbackasm1(SB) + MOVD $133, R12 + B callbackasm1(SB) + MOVD $134, R12 + B callbackasm1(SB) + MOVD $135, R12 + B callbackasm1(SB) + MOVD $136, R12 + B callbackasm1(SB) + MOVD $137, R12 + B callbackasm1(SB) + MOVD $138, R12 + B callbackasm1(SB) + MOVD $139, R12 + B callbackasm1(SB) + MOVD $140, R12 + B callbackasm1(SB) + MOVD $141, R12 + B callbackasm1(SB) + MOVD $142, R12 + B callbackasm1(SB) + MOVD $143, R12 + B callbackasm1(SB) + MOVD $144, R12 + B callbackasm1(SB) + MOVD $145, R12 + B callbackasm1(SB) + MOVD $146, R12 + B callbackasm1(SB) + MOVD $147, R12 + B callbackasm1(SB) + MOVD $148, R12 + B callbackasm1(SB) + MOVD $149, R12 + B callbackasm1(SB) + MOVD $150, R12 + B callbackasm1(SB) + MOVD $151, R12 + B callbackasm1(SB) + MOVD $152, R12 + B callbackasm1(SB) + MOVD $153, R12 + B callbackasm1(SB) + MOVD $154, R12 + B callbackasm1(SB) + MOVD $155, R12 + B callbackasm1(SB) + MOVD $156, R12 + B callbackasm1(SB) + MOVD $157, R12 + B callbackasm1(SB) + MOVD $158, R12 + B callbackasm1(SB) + MOVD $159, R12 + B callbackasm1(SB) + MOVD $160, R12 + B callbackasm1(SB) + MOVD $161, R12 + B callbackasm1(SB) + MOVD $162, R12 + B callbackasm1(SB) + MOVD $163, R12 + B callbackasm1(SB) + MOVD $164, R12 + B callbackasm1(SB) + MOVD $165, R12 + B callbackasm1(SB) + MOVD $166, R12 + B callbackasm1(SB) + MOVD $167, R12 + B callbackasm1(SB) + MOVD $168, R12 + B callbackasm1(SB) + MOVD $169, R12 + B callbackasm1(SB) + MOVD $170, R12 + B callbackasm1(SB) + MOVD $171, R12 + B callbackasm1(SB) + MOVD $172, R12 + B callbackasm1(SB) + MOVD $173, R12 + B callbackasm1(SB) + MOVD $174, R12 + B callbackasm1(SB) + MOVD $175, R12 + B callbackasm1(SB) + MOVD $176, R12 + B callbackasm1(SB) + MOVD $177, R12 + B callbackasm1(SB) + MOVD $178, R12 + B callbackasm1(SB) + MOVD $179, R12 + B callbackasm1(SB) + MOVD $180, R12 + B callbackasm1(SB) + MOVD $181, R12 + B callbackasm1(SB) + MOVD $182, R12 + B callbackasm1(SB) + MOVD $183, R12 + B callbackasm1(SB) + MOVD $184, R12 + B callbackasm1(SB) + MOVD $185, R12 + B callbackasm1(SB) + MOVD $186, R12 + B callbackasm1(SB) + MOVD $187, R12 + B callbackasm1(SB) + MOVD $188, R12 + B callbackasm1(SB) + MOVD $189, R12 + B callbackasm1(SB) + MOVD $190, R12 + B callbackasm1(SB) + MOVD $191, R12 + B callbackasm1(SB) + MOVD $192, R12 + B callbackasm1(SB) + MOVD $193, R12 + B callbackasm1(SB) + MOVD $194, R12 + B callbackasm1(SB) + MOVD $195, R12 + B callbackasm1(SB) + MOVD $196, R12 + B callbackasm1(SB) + MOVD $197, R12 + B callbackasm1(SB) + MOVD $198, R12 + B callbackasm1(SB) + MOVD $199, R12 + B callbackasm1(SB) + MOVD $200, R12 + B callbackasm1(SB) + MOVD $201, R12 + B callbackasm1(SB) + MOVD $202, R12 + B callbackasm1(SB) + MOVD $203, R12 + B callbackasm1(SB) + MOVD $204, R12 + B callbackasm1(SB) + MOVD $205, R12 + B callbackasm1(SB) + MOVD $206, R12 + B callbackasm1(SB) + MOVD $207, R12 + B callbackasm1(SB) + MOVD $208, R12 + B callbackasm1(SB) + MOVD $209, R12 + B callbackasm1(SB) + MOVD $210, R12 + B callbackasm1(SB) + MOVD $211, R12 + B callbackasm1(SB) + MOVD $212, R12 + B callbackasm1(SB) + MOVD $213, R12 + B callbackasm1(SB) + MOVD $214, R12 + B callbackasm1(SB) + MOVD $215, R12 + B callbackasm1(SB) + MOVD $216, R12 + B callbackasm1(SB) + MOVD $217, R12 + B callbackasm1(SB) + MOVD $218, R12 + B callbackasm1(SB) + MOVD $219, R12 + B callbackasm1(SB) + MOVD $220, R12 + B callbackasm1(SB) + MOVD $221, R12 + B callbackasm1(SB) + MOVD $222, R12 + B callbackasm1(SB) + MOVD $223, R12 + B callbackasm1(SB) + MOVD $224, R12 + B callbackasm1(SB) + MOVD $225, R12 + B callbackasm1(SB) + MOVD $226, R12 + B callbackasm1(SB) + MOVD $227, R12 + B callbackasm1(SB) + MOVD $228, R12 + B callbackasm1(SB) + MOVD $229, R12 + B callbackasm1(SB) + MOVD $230, R12 + B callbackasm1(SB) + MOVD $231, R12 + B callbackasm1(SB) + MOVD $232, R12 + B callbackasm1(SB) + MOVD $233, R12 + B callbackasm1(SB) + MOVD $234, R12 + B callbackasm1(SB) + MOVD $235, R12 + B callbackasm1(SB) + MOVD $236, R12 + B callbackasm1(SB) + MOVD $237, R12 + B callbackasm1(SB) + MOVD $238, R12 + B callbackasm1(SB) + MOVD $239, R12 + B callbackasm1(SB) + MOVD $240, R12 + B callbackasm1(SB) + MOVD $241, R12 + B callbackasm1(SB) + MOVD $242, R12 + B callbackasm1(SB) + MOVD $243, R12 + B callbackasm1(SB) + MOVD $244, R12 + B callbackasm1(SB) + MOVD $245, R12 + B callbackasm1(SB) + MOVD $246, R12 + B callbackasm1(SB) + MOVD $247, R12 + B callbackasm1(SB) + MOVD $248, R12 + B callbackasm1(SB) + MOVD $249, R12 + B callbackasm1(SB) + MOVD $250, R12 + B callbackasm1(SB) + MOVD $251, R12 + B callbackasm1(SB) + MOVD $252, R12 + B callbackasm1(SB) + MOVD $253, R12 + B callbackasm1(SB) + MOVD $254, R12 + B callbackasm1(SB) + MOVD $255, R12 + B callbackasm1(SB) + MOVD $256, R12 + B callbackasm1(SB) + MOVD $257, R12 + B callbackasm1(SB) + MOVD $258, R12 + B callbackasm1(SB) + MOVD $259, R12 + B callbackasm1(SB) + MOVD $260, R12 + B callbackasm1(SB) + MOVD $261, R12 + B callbackasm1(SB) + MOVD $262, R12 + B callbackasm1(SB) + MOVD $263, R12 + B callbackasm1(SB) + MOVD $264, R12 + B callbackasm1(SB) + MOVD $265, R12 + B callbackasm1(SB) + MOVD $266, R12 + B callbackasm1(SB) + MOVD $267, R12 + B callbackasm1(SB) + MOVD $268, R12 + B callbackasm1(SB) + MOVD $269, R12 + B callbackasm1(SB) + MOVD $270, R12 + B callbackasm1(SB) + MOVD $271, R12 + B callbackasm1(SB) + MOVD $272, R12 + B callbackasm1(SB) + MOVD $273, R12 + B callbackasm1(SB) + MOVD $274, R12 + B callbackasm1(SB) + MOVD $275, R12 + B callbackasm1(SB) + MOVD $276, R12 + B callbackasm1(SB) + MOVD $277, R12 + B callbackasm1(SB) + MOVD $278, R12 + B callbackasm1(SB) + MOVD $279, R12 + B callbackasm1(SB) + MOVD $280, R12 + B callbackasm1(SB) + MOVD $281, R12 + B callbackasm1(SB) + MOVD $282, R12 + B callbackasm1(SB) + MOVD $283, R12 + B callbackasm1(SB) + MOVD $284, R12 + B callbackasm1(SB) + MOVD $285, R12 + B callbackasm1(SB) + MOVD $286, R12 + B callbackasm1(SB) + MOVD $287, R12 + B callbackasm1(SB) + MOVD $288, R12 + B callbackasm1(SB) + MOVD $289, R12 + B callbackasm1(SB) + MOVD $290, R12 + B callbackasm1(SB) + MOVD $291, R12 + B callbackasm1(SB) + MOVD $292, R12 + B callbackasm1(SB) + MOVD $293, R12 + B callbackasm1(SB) + MOVD $294, R12 + B callbackasm1(SB) + MOVD $295, R12 + B callbackasm1(SB) + MOVD $296, R12 + B callbackasm1(SB) + MOVD $297, R12 + B callbackasm1(SB) + MOVD $298, R12 + B callbackasm1(SB) + MOVD $299, R12 + B callbackasm1(SB) + MOVD $300, R12 + B callbackasm1(SB) + MOVD $301, R12 + B callbackasm1(SB) + MOVD $302, R12 + B callbackasm1(SB) + MOVD $303, R12 + B callbackasm1(SB) + MOVD $304, R12 + B callbackasm1(SB) + MOVD $305, R12 + B callbackasm1(SB) + MOVD $306, R12 + B callbackasm1(SB) + MOVD $307, R12 + B callbackasm1(SB) + MOVD $308, R12 + B callbackasm1(SB) + MOVD $309, R12 + B callbackasm1(SB) + MOVD $310, R12 + B callbackasm1(SB) + MOVD $311, R12 + B callbackasm1(SB) + MOVD $312, R12 + B callbackasm1(SB) + MOVD $313, R12 + B callbackasm1(SB) + MOVD $314, R12 + B callbackasm1(SB) + MOVD $315, R12 + B callbackasm1(SB) + MOVD $316, R12 + B callbackasm1(SB) + MOVD $317, R12 + B callbackasm1(SB) + MOVD $318, R12 + B callbackasm1(SB) + MOVD $319, R12 + B callbackasm1(SB) + MOVD $320, R12 + B callbackasm1(SB) + MOVD $321, R12 + B callbackasm1(SB) + MOVD $322, R12 + B callbackasm1(SB) + MOVD $323, R12 + B callbackasm1(SB) + MOVD $324, R12 + B callbackasm1(SB) + MOVD $325, R12 + B callbackasm1(SB) + MOVD $326, R12 + B callbackasm1(SB) + MOVD $327, R12 + B callbackasm1(SB) + MOVD $328, R12 + B callbackasm1(SB) + MOVD $329, R12 + B callbackasm1(SB) + MOVD $330, R12 + B callbackasm1(SB) + MOVD $331, R12 + B callbackasm1(SB) + MOVD $332, R12 + B callbackasm1(SB) + MOVD $333, R12 + B callbackasm1(SB) + MOVD $334, R12 + B callbackasm1(SB) + MOVD $335, R12 + B callbackasm1(SB) + MOVD $336, R12 + B callbackasm1(SB) + MOVD $337, R12 + B callbackasm1(SB) + MOVD $338, R12 + B callbackasm1(SB) + MOVD $339, R12 + B callbackasm1(SB) + MOVD $340, R12 + B callbackasm1(SB) + MOVD $341, R12 + B callbackasm1(SB) + MOVD $342, R12 + B callbackasm1(SB) + MOVD $343, R12 + B callbackasm1(SB) + MOVD $344, R12 + B callbackasm1(SB) + MOVD $345, R12 + B callbackasm1(SB) + MOVD $346, R12 + B callbackasm1(SB) + MOVD $347, R12 + B callbackasm1(SB) + MOVD $348, R12 + B callbackasm1(SB) + MOVD $349, R12 + B callbackasm1(SB) + MOVD $350, R12 + B callbackasm1(SB) + MOVD $351, R12 + B callbackasm1(SB) + MOVD $352, R12 + B callbackasm1(SB) + MOVD $353, R12 + B callbackasm1(SB) + MOVD $354, R12 + B callbackasm1(SB) + MOVD $355, R12 + B callbackasm1(SB) + MOVD $356, R12 + B callbackasm1(SB) + MOVD $357, R12 + B callbackasm1(SB) + MOVD $358, R12 + B callbackasm1(SB) + MOVD $359, R12 + B callbackasm1(SB) + MOVD $360, R12 + B callbackasm1(SB) + MOVD $361, R12 + B callbackasm1(SB) + MOVD $362, R12 + B callbackasm1(SB) + MOVD $363, R12 + B callbackasm1(SB) + MOVD $364, R12 + B callbackasm1(SB) + MOVD $365, R12 + B callbackasm1(SB) + MOVD $366, R12 + B callbackasm1(SB) + MOVD $367, R12 + B callbackasm1(SB) + MOVD $368, R12 + B callbackasm1(SB) + MOVD $369, R12 + B callbackasm1(SB) + MOVD $370, R12 + B callbackasm1(SB) + MOVD $371, R12 + B callbackasm1(SB) + MOVD $372, R12 + B callbackasm1(SB) + MOVD $373, R12 + B callbackasm1(SB) + MOVD $374, R12 + B callbackasm1(SB) + MOVD $375, R12 + B callbackasm1(SB) + MOVD $376, R12 + B callbackasm1(SB) + MOVD $377, R12 + B callbackasm1(SB) + MOVD $378, R12 + B callbackasm1(SB) + MOVD $379, R12 + B callbackasm1(SB) + MOVD $380, R12 + B callbackasm1(SB) + MOVD $381, R12 + B callbackasm1(SB) + MOVD $382, R12 + B callbackasm1(SB) + MOVD $383, R12 + B callbackasm1(SB) + MOVD $384, R12 + B callbackasm1(SB) + MOVD $385, R12 + B callbackasm1(SB) + MOVD $386, R12 + B callbackasm1(SB) + MOVD $387, R12 + B callbackasm1(SB) + MOVD $388, R12 + B callbackasm1(SB) + MOVD $389, R12 + B callbackasm1(SB) + MOVD $390, R12 + B callbackasm1(SB) + MOVD $391, R12 + B callbackasm1(SB) + MOVD $392, R12 + B callbackasm1(SB) + MOVD $393, R12 + B callbackasm1(SB) + MOVD $394, R12 + B callbackasm1(SB) + MOVD $395, R12 + B callbackasm1(SB) + MOVD $396, R12 + B callbackasm1(SB) + MOVD $397, R12 + B callbackasm1(SB) + MOVD $398, R12 + B callbackasm1(SB) + MOVD $399, R12 + B callbackasm1(SB) + MOVD $400, R12 + B callbackasm1(SB) + MOVD $401, R12 + B callbackasm1(SB) + MOVD $402, R12 + B callbackasm1(SB) + MOVD $403, R12 + B callbackasm1(SB) + MOVD $404, R12 + B callbackasm1(SB) + MOVD $405, R12 + B callbackasm1(SB) + MOVD $406, R12 + B callbackasm1(SB) + MOVD $407, R12 + B callbackasm1(SB) + MOVD $408, R12 + B callbackasm1(SB) + MOVD $409, R12 + B callbackasm1(SB) + MOVD $410, R12 + B callbackasm1(SB) + MOVD $411, R12 + B callbackasm1(SB) + MOVD $412, R12 + B callbackasm1(SB) + MOVD $413, R12 + B callbackasm1(SB) + MOVD $414, R12 + B callbackasm1(SB) + MOVD $415, R12 + B callbackasm1(SB) + MOVD $416, R12 + B callbackasm1(SB) + MOVD $417, R12 + B callbackasm1(SB) + MOVD $418, R12 + B callbackasm1(SB) + MOVD $419, R12 + B callbackasm1(SB) + MOVD $420, R12 + B callbackasm1(SB) + MOVD $421, R12 + B callbackasm1(SB) + MOVD $422, R12 + B callbackasm1(SB) + MOVD $423, R12 + B callbackasm1(SB) + MOVD $424, R12 + B callbackasm1(SB) + MOVD $425, R12 + B callbackasm1(SB) + MOVD $426, R12 + B callbackasm1(SB) + MOVD $427, R12 + B callbackasm1(SB) + MOVD $428, R12 + B callbackasm1(SB) + MOVD $429, R12 + B callbackasm1(SB) + MOVD $430, R12 + B callbackasm1(SB) + MOVD $431, R12 + B callbackasm1(SB) + MOVD $432, R12 + B callbackasm1(SB) + MOVD $433, R12 + B callbackasm1(SB) + MOVD $434, R12 + B callbackasm1(SB) + MOVD $435, R12 + B callbackasm1(SB) + MOVD $436, R12 + B callbackasm1(SB) + MOVD $437, R12 + B callbackasm1(SB) + MOVD $438, R12 + B callbackasm1(SB) + MOVD $439, R12 + B callbackasm1(SB) + MOVD $440, R12 + B callbackasm1(SB) + MOVD $441, R12 + B callbackasm1(SB) + MOVD $442, R12 + B callbackasm1(SB) + MOVD $443, R12 + B callbackasm1(SB) + MOVD $444, R12 + B callbackasm1(SB) + MOVD $445, R12 + B callbackasm1(SB) + MOVD $446, R12 + B callbackasm1(SB) + MOVD $447, R12 + B callbackasm1(SB) + MOVD $448, R12 + B callbackasm1(SB) + MOVD $449, R12 + B callbackasm1(SB) + MOVD $450, R12 + B callbackasm1(SB) + MOVD $451, R12 + B callbackasm1(SB) + MOVD $452, R12 + B callbackasm1(SB) + MOVD $453, R12 + B callbackasm1(SB) + MOVD $454, R12 + B callbackasm1(SB) + MOVD $455, R12 + B callbackasm1(SB) + MOVD $456, R12 + B callbackasm1(SB) + MOVD $457, R12 + B callbackasm1(SB) + MOVD $458, R12 + B callbackasm1(SB) + MOVD $459, R12 + B callbackasm1(SB) + MOVD $460, R12 + B callbackasm1(SB) + MOVD $461, R12 + B callbackasm1(SB) + MOVD $462, R12 + B callbackasm1(SB) + MOVD $463, R12 + B callbackasm1(SB) + MOVD $464, R12 + B callbackasm1(SB) + MOVD $465, R12 + B callbackasm1(SB) + MOVD $466, R12 + B callbackasm1(SB) + MOVD $467, R12 + B callbackasm1(SB) + MOVD $468, R12 + B callbackasm1(SB) + MOVD $469, R12 + B callbackasm1(SB) + MOVD $470, R12 + B callbackasm1(SB) + MOVD $471, R12 + B callbackasm1(SB) + MOVD $472, R12 + B callbackasm1(SB) + MOVD $473, R12 + B callbackasm1(SB) + MOVD $474, R12 + B callbackasm1(SB) + MOVD $475, R12 + B callbackasm1(SB) + MOVD $476, R12 + B callbackasm1(SB) + MOVD $477, R12 + B callbackasm1(SB) + MOVD $478, R12 + B callbackasm1(SB) + MOVD $479, R12 + B callbackasm1(SB) + MOVD $480, R12 + B callbackasm1(SB) + MOVD $481, R12 + B callbackasm1(SB) + MOVD $482, R12 + B callbackasm1(SB) + MOVD $483, R12 + B callbackasm1(SB) + MOVD $484, R12 + B callbackasm1(SB) + MOVD $485, R12 + B callbackasm1(SB) + MOVD $486, R12 + B callbackasm1(SB) + MOVD $487, R12 + B callbackasm1(SB) + MOVD $488, R12 + B callbackasm1(SB) + MOVD $489, R12 + B callbackasm1(SB) + MOVD $490, R12 + B callbackasm1(SB) + MOVD $491, R12 + B callbackasm1(SB) + MOVD $492, R12 + B callbackasm1(SB) + MOVD $493, R12 + B callbackasm1(SB) + MOVD $494, R12 + B callbackasm1(SB) + MOVD $495, R12 + B callbackasm1(SB) + MOVD $496, R12 + B callbackasm1(SB) + MOVD $497, R12 + B callbackasm1(SB) + MOVD $498, R12 + B callbackasm1(SB) + MOVD $499, R12 + B callbackasm1(SB) + MOVD $500, R12 + B callbackasm1(SB) + MOVD $501, R12 + B callbackasm1(SB) + MOVD $502, R12 + B callbackasm1(SB) + MOVD $503, R12 + B callbackasm1(SB) + MOVD $504, R12 + B callbackasm1(SB) + MOVD $505, R12 + B callbackasm1(SB) + MOVD $506, R12 + B callbackasm1(SB) + MOVD $507, R12 + B callbackasm1(SB) + MOVD $508, R12 + B callbackasm1(SB) + MOVD $509, R12 + B callbackasm1(SB) + MOVD $510, R12 + B callbackasm1(SB) + MOVD $511, R12 + B callbackasm1(SB) + MOVD $512, R12 + B callbackasm1(SB) + MOVD $513, R12 + B callbackasm1(SB) + MOVD $514, R12 + B callbackasm1(SB) + MOVD $515, R12 + B callbackasm1(SB) + MOVD $516, R12 + B callbackasm1(SB) + MOVD $517, R12 + B callbackasm1(SB) + MOVD $518, R12 + B callbackasm1(SB) + MOVD $519, R12 + B callbackasm1(SB) + MOVD $520, R12 + B callbackasm1(SB) + MOVD $521, R12 + B callbackasm1(SB) + MOVD $522, R12 + B callbackasm1(SB) + MOVD $523, R12 + B callbackasm1(SB) + MOVD $524, R12 + B callbackasm1(SB) + MOVD $525, R12 + B callbackasm1(SB) + MOVD $526, R12 + B callbackasm1(SB) + MOVD $527, R12 + B callbackasm1(SB) + MOVD $528, R12 + B callbackasm1(SB) + MOVD $529, R12 + B callbackasm1(SB) + MOVD $530, R12 + B callbackasm1(SB) + MOVD $531, R12 + B callbackasm1(SB) + MOVD $532, R12 + B callbackasm1(SB) + MOVD $533, R12 + B callbackasm1(SB) + MOVD $534, R12 + B callbackasm1(SB) + MOVD $535, R12 + B callbackasm1(SB) + MOVD $536, R12 + B callbackasm1(SB) + MOVD $537, R12 + B callbackasm1(SB) + MOVD $538, R12 + B callbackasm1(SB) + MOVD $539, R12 + B callbackasm1(SB) + MOVD $540, R12 + B callbackasm1(SB) + MOVD $541, R12 + B callbackasm1(SB) + MOVD $542, R12 + B callbackasm1(SB) + MOVD $543, R12 + B callbackasm1(SB) + MOVD $544, R12 + B callbackasm1(SB) + MOVD $545, R12 + B callbackasm1(SB) + MOVD $546, R12 + B callbackasm1(SB) + MOVD $547, R12 + B callbackasm1(SB) + MOVD $548, R12 + B callbackasm1(SB) + MOVD $549, R12 + B callbackasm1(SB) + MOVD $550, R12 + B callbackasm1(SB) + MOVD $551, R12 + B callbackasm1(SB) + MOVD $552, R12 + B callbackasm1(SB) + MOVD $553, R12 + B callbackasm1(SB) + MOVD $554, R12 + B callbackasm1(SB) + MOVD $555, R12 + B callbackasm1(SB) + MOVD $556, R12 + B callbackasm1(SB) + MOVD $557, R12 + B callbackasm1(SB) + MOVD $558, R12 + B callbackasm1(SB) + MOVD $559, R12 + B callbackasm1(SB) + MOVD $560, R12 + B callbackasm1(SB) + MOVD $561, R12 + B callbackasm1(SB) + MOVD $562, R12 + B callbackasm1(SB) + MOVD $563, R12 + B callbackasm1(SB) + MOVD $564, R12 + B callbackasm1(SB) + MOVD $565, R12 + B callbackasm1(SB) + MOVD $566, R12 + B callbackasm1(SB) + MOVD $567, R12 + B callbackasm1(SB) + MOVD $568, R12 + B callbackasm1(SB) + MOVD $569, R12 + B callbackasm1(SB) + MOVD $570, R12 + B callbackasm1(SB) + MOVD $571, R12 + B callbackasm1(SB) + MOVD $572, R12 + B callbackasm1(SB) + MOVD $573, R12 + B callbackasm1(SB) + MOVD $574, R12 + B callbackasm1(SB) + MOVD $575, R12 + B callbackasm1(SB) + MOVD $576, R12 + B callbackasm1(SB) + MOVD $577, R12 + B callbackasm1(SB) + MOVD $578, R12 + B callbackasm1(SB) + MOVD $579, R12 + B callbackasm1(SB) + MOVD $580, R12 + B callbackasm1(SB) + MOVD $581, R12 + B callbackasm1(SB) + MOVD $582, R12 + B callbackasm1(SB) + MOVD $583, R12 + B callbackasm1(SB) + MOVD $584, R12 + B callbackasm1(SB) + MOVD $585, R12 + B callbackasm1(SB) + MOVD $586, R12 + B callbackasm1(SB) + MOVD $587, R12 + B callbackasm1(SB) + MOVD $588, R12 + B callbackasm1(SB) + MOVD $589, R12 + B callbackasm1(SB) + MOVD $590, R12 + B callbackasm1(SB) + MOVD $591, R12 + B callbackasm1(SB) + MOVD $592, R12 + B callbackasm1(SB) + MOVD $593, R12 + B callbackasm1(SB) + MOVD $594, R12 + B callbackasm1(SB) + MOVD $595, R12 + B callbackasm1(SB) + MOVD $596, R12 + B callbackasm1(SB) + MOVD $597, R12 + B callbackasm1(SB) + MOVD $598, R12 + B callbackasm1(SB) + MOVD $599, R12 + B callbackasm1(SB) + MOVD $600, R12 + B callbackasm1(SB) + MOVD $601, R12 + B callbackasm1(SB) + MOVD $602, R12 + B callbackasm1(SB) + MOVD $603, R12 + B callbackasm1(SB) + MOVD $604, R12 + B callbackasm1(SB) + MOVD $605, R12 + B callbackasm1(SB) + MOVD $606, R12 + B callbackasm1(SB) + MOVD $607, R12 + B callbackasm1(SB) + MOVD $608, R12 + B callbackasm1(SB) + MOVD $609, R12 + B callbackasm1(SB) + MOVD $610, R12 + B callbackasm1(SB) + MOVD $611, R12 + B callbackasm1(SB) + MOVD $612, R12 + B callbackasm1(SB) + MOVD $613, R12 + B callbackasm1(SB) + MOVD $614, R12 + B callbackasm1(SB) + MOVD $615, R12 + B callbackasm1(SB) + MOVD $616, R12 + B callbackasm1(SB) + MOVD $617, R12 + B callbackasm1(SB) + MOVD $618, R12 + B callbackasm1(SB) + MOVD $619, R12 + B callbackasm1(SB) + MOVD $620, R12 + B callbackasm1(SB) + MOVD $621, R12 + B callbackasm1(SB) + MOVD $622, R12 + B callbackasm1(SB) + MOVD $623, R12 + B callbackasm1(SB) + MOVD $624, R12 + B callbackasm1(SB) + MOVD $625, R12 + B callbackasm1(SB) + MOVD $626, R12 + B callbackasm1(SB) + MOVD $627, R12 + B callbackasm1(SB) + MOVD $628, R12 + B callbackasm1(SB) + MOVD $629, R12 + B callbackasm1(SB) + MOVD $630, R12 + B callbackasm1(SB) + MOVD $631, R12 + B callbackasm1(SB) + MOVD $632, R12 + B callbackasm1(SB) + MOVD $633, R12 + B callbackasm1(SB) + MOVD $634, R12 + B callbackasm1(SB) + MOVD $635, R12 + B callbackasm1(SB) + MOVD $636, R12 + B callbackasm1(SB) + MOVD $637, R12 + B callbackasm1(SB) + MOVD $638, R12 + B callbackasm1(SB) + MOVD $639, R12 + B callbackasm1(SB) + MOVD $640, R12 + B callbackasm1(SB) + MOVD $641, R12 + B callbackasm1(SB) + MOVD $642, R12 + B callbackasm1(SB) + MOVD $643, R12 + B callbackasm1(SB) + MOVD $644, R12 + B callbackasm1(SB) + MOVD $645, R12 + B callbackasm1(SB) + MOVD $646, R12 + B callbackasm1(SB) + MOVD $647, R12 + B callbackasm1(SB) + MOVD $648, R12 + B callbackasm1(SB) + MOVD $649, R12 + B callbackasm1(SB) + MOVD $650, R12 + B callbackasm1(SB) + MOVD $651, R12 + B callbackasm1(SB) + MOVD $652, R12 + B callbackasm1(SB) + MOVD $653, R12 + B callbackasm1(SB) + MOVD $654, R12 + B callbackasm1(SB) + MOVD $655, R12 + B callbackasm1(SB) + MOVD $656, R12 + B callbackasm1(SB) + MOVD $657, R12 + B callbackasm1(SB) + MOVD $658, R12 + B callbackasm1(SB) + MOVD $659, R12 + B callbackasm1(SB) + MOVD $660, R12 + B callbackasm1(SB) + MOVD $661, R12 + B callbackasm1(SB) + MOVD $662, R12 + B callbackasm1(SB) + MOVD $663, R12 + B callbackasm1(SB) + MOVD $664, R12 + B callbackasm1(SB) + MOVD $665, R12 + B callbackasm1(SB) + MOVD $666, R12 + B callbackasm1(SB) + MOVD $667, R12 + B callbackasm1(SB) + MOVD $668, R12 + B callbackasm1(SB) + MOVD $669, R12 + B callbackasm1(SB) + MOVD $670, R12 + B callbackasm1(SB) + MOVD $671, R12 + B callbackasm1(SB) + MOVD $672, R12 + B callbackasm1(SB) + MOVD $673, R12 + B callbackasm1(SB) + MOVD $674, R12 + B callbackasm1(SB) + MOVD $675, R12 + B callbackasm1(SB) + MOVD $676, R12 + B callbackasm1(SB) + MOVD $677, R12 + B callbackasm1(SB) + MOVD $678, R12 + B callbackasm1(SB) + MOVD $679, R12 + B callbackasm1(SB) + MOVD $680, R12 + B callbackasm1(SB) + MOVD $681, R12 + B callbackasm1(SB) + MOVD $682, R12 + B callbackasm1(SB) + MOVD $683, R12 + B callbackasm1(SB) + MOVD $684, R12 + B callbackasm1(SB) + MOVD $685, R12 + B callbackasm1(SB) + MOVD $686, R12 + B callbackasm1(SB) + MOVD $687, R12 + B callbackasm1(SB) + MOVD $688, R12 + B callbackasm1(SB) + MOVD $689, R12 + B callbackasm1(SB) + MOVD $690, R12 + B callbackasm1(SB) + MOVD $691, R12 + B callbackasm1(SB) + MOVD $692, R12 + B callbackasm1(SB) + MOVD $693, R12 + B callbackasm1(SB) + MOVD $694, R12 + B callbackasm1(SB) + MOVD $695, R12 + B callbackasm1(SB) + MOVD $696, R12 + B callbackasm1(SB) + MOVD $697, R12 + B callbackasm1(SB) + MOVD $698, R12 + B callbackasm1(SB) + MOVD $699, R12 + B callbackasm1(SB) + MOVD $700, R12 + B callbackasm1(SB) + MOVD $701, R12 + B callbackasm1(SB) + MOVD $702, R12 + B callbackasm1(SB) + MOVD $703, R12 + B callbackasm1(SB) + MOVD $704, R12 + B callbackasm1(SB) + MOVD $705, R12 + B callbackasm1(SB) + MOVD $706, R12 + B callbackasm1(SB) + MOVD $707, R12 + B callbackasm1(SB) + MOVD $708, R12 + B callbackasm1(SB) + MOVD $709, R12 + B callbackasm1(SB) + MOVD $710, R12 + B callbackasm1(SB) + MOVD $711, R12 + B callbackasm1(SB) + MOVD $712, R12 + B callbackasm1(SB) + MOVD $713, R12 + B callbackasm1(SB) + MOVD $714, R12 + B callbackasm1(SB) + MOVD $715, R12 + B callbackasm1(SB) + MOVD $716, R12 + B callbackasm1(SB) + MOVD $717, R12 + B callbackasm1(SB) + MOVD $718, R12 + B callbackasm1(SB) + MOVD $719, R12 + B callbackasm1(SB) + MOVD $720, R12 + B callbackasm1(SB) + MOVD $721, R12 + B callbackasm1(SB) + MOVD $722, R12 + B callbackasm1(SB) + MOVD $723, R12 + B callbackasm1(SB) + MOVD $724, R12 + B callbackasm1(SB) + MOVD $725, R12 + B callbackasm1(SB) + MOVD $726, R12 + B callbackasm1(SB) + MOVD $727, R12 + B callbackasm1(SB) + MOVD $728, R12 + B callbackasm1(SB) + MOVD $729, R12 + B callbackasm1(SB) + MOVD $730, R12 + B callbackasm1(SB) + MOVD $731, R12 + B callbackasm1(SB) + MOVD $732, R12 + B callbackasm1(SB) + MOVD $733, R12 + B callbackasm1(SB) + MOVD $734, R12 + B callbackasm1(SB) + MOVD $735, R12 + B callbackasm1(SB) + MOVD $736, R12 + B callbackasm1(SB) + MOVD $737, R12 + B callbackasm1(SB) + MOVD $738, R12 + B callbackasm1(SB) + MOVD $739, R12 + B callbackasm1(SB) + MOVD $740, R12 + B callbackasm1(SB) + MOVD $741, R12 + B callbackasm1(SB) + MOVD $742, R12 + B callbackasm1(SB) + MOVD $743, R12 + B callbackasm1(SB) + MOVD $744, R12 + B callbackasm1(SB) + MOVD $745, R12 + B callbackasm1(SB) + MOVD $746, R12 + B callbackasm1(SB) + MOVD $747, R12 + B callbackasm1(SB) + MOVD $748, R12 + B callbackasm1(SB) + MOVD $749, R12 + B callbackasm1(SB) + MOVD $750, R12 + B callbackasm1(SB) + MOVD $751, R12 + B callbackasm1(SB) + MOVD $752, R12 + B callbackasm1(SB) + MOVD $753, R12 + B callbackasm1(SB) + MOVD $754, R12 + B callbackasm1(SB) + MOVD $755, R12 + B callbackasm1(SB) + MOVD $756, R12 + B callbackasm1(SB) + MOVD $757, R12 + B callbackasm1(SB) + MOVD $758, R12 + B callbackasm1(SB) + MOVD $759, R12 + B callbackasm1(SB) + MOVD $760, R12 + B callbackasm1(SB) + MOVD $761, R12 + B callbackasm1(SB) + MOVD $762, R12 + B callbackasm1(SB) + MOVD $763, R12 + B callbackasm1(SB) + MOVD $764, R12 + B callbackasm1(SB) + MOVD $765, R12 + B callbackasm1(SB) + MOVD $766, R12 + B callbackasm1(SB) + MOVD $767, R12 + B callbackasm1(SB) + MOVD $768, R12 + B callbackasm1(SB) + MOVD $769, R12 + B callbackasm1(SB) + MOVD $770, R12 + B callbackasm1(SB) + MOVD $771, R12 + B callbackasm1(SB) + MOVD $772, R12 + B callbackasm1(SB) + MOVD $773, R12 + B callbackasm1(SB) + MOVD $774, R12 + B callbackasm1(SB) + MOVD $775, R12 + B callbackasm1(SB) + MOVD $776, R12 + B callbackasm1(SB) + MOVD $777, R12 + B callbackasm1(SB) + MOVD $778, R12 + B callbackasm1(SB) + MOVD $779, R12 + B callbackasm1(SB) + MOVD $780, R12 + B callbackasm1(SB) + MOVD $781, R12 + B callbackasm1(SB) + MOVD $782, R12 + B callbackasm1(SB) + MOVD $783, R12 + B callbackasm1(SB) + MOVD $784, R12 + B callbackasm1(SB) + MOVD $785, R12 + B callbackasm1(SB) + MOVD $786, R12 + B callbackasm1(SB) + MOVD $787, R12 + B callbackasm1(SB) + MOVD $788, R12 + B callbackasm1(SB) + MOVD $789, R12 + B callbackasm1(SB) + MOVD $790, R12 + B callbackasm1(SB) + MOVD $791, R12 + B callbackasm1(SB) + MOVD $792, R12 + B callbackasm1(SB) + MOVD $793, R12 + B callbackasm1(SB) + MOVD $794, R12 + B callbackasm1(SB) + MOVD $795, R12 + B callbackasm1(SB) + MOVD $796, R12 + B callbackasm1(SB) + MOVD $797, R12 + B callbackasm1(SB) + MOVD $798, R12 + B callbackasm1(SB) + MOVD $799, R12 + B callbackasm1(SB) + MOVD $800, R12 + B callbackasm1(SB) + MOVD $801, R12 + B callbackasm1(SB) + MOVD $802, R12 + B callbackasm1(SB) + MOVD $803, R12 + B callbackasm1(SB) + MOVD $804, R12 + B callbackasm1(SB) + MOVD $805, R12 + B callbackasm1(SB) + MOVD $806, R12 + B callbackasm1(SB) + MOVD $807, R12 + B callbackasm1(SB) + MOVD $808, R12 + B callbackasm1(SB) + MOVD $809, R12 + B callbackasm1(SB) + MOVD $810, R12 + B callbackasm1(SB) + MOVD $811, R12 + B callbackasm1(SB) + MOVD $812, R12 + B callbackasm1(SB) + MOVD $813, R12 + B callbackasm1(SB) + MOVD $814, R12 + B callbackasm1(SB) + MOVD $815, R12 + B callbackasm1(SB) + MOVD $816, R12 + B callbackasm1(SB) + MOVD $817, R12 + B callbackasm1(SB) + MOVD $818, R12 + B callbackasm1(SB) + MOVD $819, R12 + B callbackasm1(SB) + MOVD $820, R12 + B callbackasm1(SB) + MOVD $821, R12 + B callbackasm1(SB) + MOVD $822, R12 + B callbackasm1(SB) + MOVD $823, R12 + B callbackasm1(SB) + MOVD $824, R12 + B callbackasm1(SB) + MOVD $825, R12 + B callbackasm1(SB) + MOVD $826, R12 + B callbackasm1(SB) + MOVD $827, R12 + B callbackasm1(SB) + MOVD $828, R12 + B callbackasm1(SB) + MOVD $829, R12 + B callbackasm1(SB) + MOVD $830, R12 + B callbackasm1(SB) + MOVD $831, R12 + B callbackasm1(SB) + MOVD $832, R12 + B callbackasm1(SB) + MOVD $833, R12 + B callbackasm1(SB) + MOVD $834, R12 + B callbackasm1(SB) + MOVD $835, R12 + B callbackasm1(SB) + MOVD $836, R12 + B callbackasm1(SB) + MOVD $837, R12 + B callbackasm1(SB) + MOVD $838, R12 + B callbackasm1(SB) + MOVD $839, R12 + B callbackasm1(SB) + MOVD $840, R12 + B callbackasm1(SB) + MOVD $841, R12 + B callbackasm1(SB) + MOVD $842, R12 + B callbackasm1(SB) + MOVD $843, R12 + B callbackasm1(SB) + MOVD $844, R12 + B callbackasm1(SB) + MOVD $845, R12 + B callbackasm1(SB) + MOVD $846, R12 + B callbackasm1(SB) + MOVD $847, R12 + B callbackasm1(SB) + MOVD $848, R12 + B callbackasm1(SB) + MOVD $849, R12 + B callbackasm1(SB) + MOVD $850, R12 + B callbackasm1(SB) + MOVD $851, R12 + B callbackasm1(SB) + MOVD $852, R12 + B callbackasm1(SB) + MOVD $853, R12 + B callbackasm1(SB) + MOVD $854, R12 + B callbackasm1(SB) + MOVD $855, R12 + B callbackasm1(SB) + MOVD $856, R12 + B callbackasm1(SB) + MOVD $857, R12 + B callbackasm1(SB) + MOVD $858, R12 + B callbackasm1(SB) + MOVD $859, R12 + B callbackasm1(SB) + MOVD $860, R12 + B callbackasm1(SB) + MOVD $861, R12 + B callbackasm1(SB) + MOVD $862, R12 + B callbackasm1(SB) + MOVD $863, R12 + B callbackasm1(SB) + MOVD $864, R12 + B callbackasm1(SB) + MOVD $865, R12 + B callbackasm1(SB) + MOVD $866, R12 + B callbackasm1(SB) + MOVD $867, R12 + B callbackasm1(SB) + MOVD $868, R12 + B callbackasm1(SB) + MOVD $869, R12 + B callbackasm1(SB) + MOVD $870, R12 + B callbackasm1(SB) + MOVD $871, R12 + B callbackasm1(SB) + MOVD $872, R12 + B callbackasm1(SB) + MOVD $873, R12 + B callbackasm1(SB) + MOVD $874, R12 + B callbackasm1(SB) + MOVD $875, R12 + B callbackasm1(SB) + MOVD $876, R12 + B callbackasm1(SB) + MOVD $877, R12 + B callbackasm1(SB) + MOVD $878, R12 + B callbackasm1(SB) + MOVD $879, R12 + B callbackasm1(SB) + MOVD $880, R12 + B callbackasm1(SB) + MOVD $881, R12 + B callbackasm1(SB) + MOVD $882, R12 + B callbackasm1(SB) + MOVD $883, R12 + B callbackasm1(SB) + MOVD $884, R12 + B callbackasm1(SB) + MOVD $885, R12 + B callbackasm1(SB) + MOVD $886, R12 + B callbackasm1(SB) + MOVD $887, R12 + B callbackasm1(SB) + MOVD $888, R12 + B callbackasm1(SB) + MOVD $889, R12 + B callbackasm1(SB) + MOVD $890, R12 + B callbackasm1(SB) + MOVD $891, R12 + B callbackasm1(SB) + MOVD $892, R12 + B callbackasm1(SB) + MOVD $893, R12 + B callbackasm1(SB) + MOVD $894, R12 + B callbackasm1(SB) + MOVD $895, R12 + B callbackasm1(SB) + MOVD $896, R12 + B callbackasm1(SB) + MOVD $897, R12 + B callbackasm1(SB) + MOVD $898, R12 + B callbackasm1(SB) + MOVD $899, R12 + B callbackasm1(SB) + MOVD $900, R12 + B callbackasm1(SB) + MOVD $901, R12 + B callbackasm1(SB) + MOVD $902, R12 + B callbackasm1(SB) + MOVD $903, R12 + B callbackasm1(SB) + MOVD $904, R12 + B callbackasm1(SB) + MOVD $905, R12 + B callbackasm1(SB) + MOVD $906, R12 + B callbackasm1(SB) + MOVD $907, R12 + B callbackasm1(SB) + MOVD $908, R12 + B callbackasm1(SB) + MOVD $909, R12 + B callbackasm1(SB) + MOVD $910, R12 + B callbackasm1(SB) + MOVD $911, R12 + B callbackasm1(SB) + MOVD $912, R12 + B callbackasm1(SB) + MOVD $913, R12 + B callbackasm1(SB) + MOVD $914, R12 + B callbackasm1(SB) + MOVD $915, R12 + B callbackasm1(SB) + MOVD $916, R12 + B callbackasm1(SB) + MOVD $917, R12 + B callbackasm1(SB) + MOVD $918, R12 + B callbackasm1(SB) + MOVD $919, R12 + B callbackasm1(SB) + MOVD $920, R12 + B callbackasm1(SB) + MOVD $921, R12 + B callbackasm1(SB) + MOVD $922, R12 + B callbackasm1(SB) + MOVD $923, R12 + B callbackasm1(SB) + MOVD $924, R12 + B callbackasm1(SB) + MOVD $925, R12 + B callbackasm1(SB) + MOVD $926, R12 + B callbackasm1(SB) + MOVD $927, R12 + B callbackasm1(SB) + MOVD $928, R12 + B callbackasm1(SB) + MOVD $929, R12 + B callbackasm1(SB) + MOVD $930, R12 + B callbackasm1(SB) + MOVD $931, R12 + B callbackasm1(SB) + MOVD $932, R12 + B callbackasm1(SB) + MOVD $933, R12 + B callbackasm1(SB) + MOVD $934, R12 + B callbackasm1(SB) + MOVD $935, R12 + B callbackasm1(SB) + MOVD $936, R12 + B callbackasm1(SB) + MOVD $937, R12 + B callbackasm1(SB) + MOVD $938, R12 + B callbackasm1(SB) + MOVD $939, R12 + B callbackasm1(SB) + MOVD $940, R12 + B callbackasm1(SB) + MOVD $941, R12 + B callbackasm1(SB) + MOVD $942, R12 + B callbackasm1(SB) + MOVD $943, R12 + B callbackasm1(SB) + MOVD $944, R12 + B callbackasm1(SB) + MOVD $945, R12 + B callbackasm1(SB) + MOVD $946, R12 + B callbackasm1(SB) + MOVD $947, R12 + B callbackasm1(SB) + MOVD $948, R12 + B callbackasm1(SB) + MOVD $949, R12 + B callbackasm1(SB) + MOVD $950, R12 + B callbackasm1(SB) + MOVD $951, R12 + B callbackasm1(SB) + MOVD $952, R12 + B callbackasm1(SB) + MOVD $953, R12 + B callbackasm1(SB) + MOVD $954, R12 + B callbackasm1(SB) + MOVD $955, R12 + B callbackasm1(SB) + MOVD $956, R12 + B callbackasm1(SB) + MOVD $957, R12 + B callbackasm1(SB) + MOVD $958, R12 + B callbackasm1(SB) + MOVD $959, R12 + B callbackasm1(SB) + MOVD $960, R12 + B callbackasm1(SB) + MOVD $961, R12 + B callbackasm1(SB) + MOVD $962, R12 + B callbackasm1(SB) + MOVD $963, R12 + B callbackasm1(SB) + MOVD $964, R12 + B callbackasm1(SB) + MOVD $965, R12 + B callbackasm1(SB) + MOVD $966, R12 + B callbackasm1(SB) + MOVD $967, R12 + B callbackasm1(SB) + MOVD $968, R12 + B callbackasm1(SB) + MOVD $969, R12 + B callbackasm1(SB) + MOVD $970, R12 + B callbackasm1(SB) + MOVD $971, R12 + B callbackasm1(SB) + MOVD $972, R12 + B callbackasm1(SB) + MOVD $973, R12 + B callbackasm1(SB) + MOVD $974, R12 + B callbackasm1(SB) + MOVD $975, R12 + B callbackasm1(SB) + MOVD $976, R12 + B callbackasm1(SB) + MOVD $977, R12 + B callbackasm1(SB) + MOVD $978, R12 + B callbackasm1(SB) + MOVD $979, R12 + B callbackasm1(SB) + MOVD $980, R12 + B callbackasm1(SB) + MOVD $981, R12 + B callbackasm1(SB) + MOVD $982, R12 + B callbackasm1(SB) + MOVD $983, R12 + B callbackasm1(SB) + MOVD $984, R12 + B callbackasm1(SB) + MOVD $985, R12 + B callbackasm1(SB) + MOVD $986, R12 + B callbackasm1(SB) + MOVD $987, R12 + B callbackasm1(SB) + MOVD $988, R12 + B callbackasm1(SB) + MOVD $989, R12 + B callbackasm1(SB) + MOVD $990, R12 + B callbackasm1(SB) + MOVD $991, R12 + B callbackasm1(SB) + MOVD $992, R12 + B callbackasm1(SB) + MOVD $993, R12 + B callbackasm1(SB) + MOVD $994, R12 + B callbackasm1(SB) + MOVD $995, R12 + B callbackasm1(SB) + MOVD $996, R12 + B callbackasm1(SB) + MOVD $997, R12 + B callbackasm1(SB) + MOVD $998, R12 + B callbackasm1(SB) + MOVD $999, R12 + B callbackasm1(SB) + MOVD $1000, R12 + B callbackasm1(SB) + MOVD $1001, R12 + B callbackasm1(SB) + MOVD $1002, R12 + B callbackasm1(SB) + MOVD $1003, R12 + B callbackasm1(SB) + MOVD $1004, R12 + B callbackasm1(SB) + MOVD $1005, R12 + B callbackasm1(SB) + MOVD $1006, R12 + B callbackasm1(SB) + MOVD $1007, R12 + B callbackasm1(SB) + MOVD $1008, R12 + B callbackasm1(SB) + MOVD $1009, R12 + B callbackasm1(SB) + MOVD $1010, R12 + B callbackasm1(SB) + MOVD $1011, R12 + B callbackasm1(SB) + MOVD $1012, R12 + B callbackasm1(SB) + MOVD $1013, R12 + B callbackasm1(SB) + MOVD $1014, R12 + B callbackasm1(SB) + MOVD $1015, R12 + B callbackasm1(SB) + MOVD $1016, R12 + B callbackasm1(SB) + MOVD $1017, R12 + B callbackasm1(SB) + MOVD $1018, R12 + B callbackasm1(SB) + MOVD $1019, R12 + B callbackasm1(SB) + MOVD $1020, R12 + B callbackasm1(SB) + MOVD $1021, R12 + B callbackasm1(SB) + MOVD $1022, R12 + B callbackasm1(SB) + MOVD $1023, R12 + B callbackasm1(SB) + MOVD $1024, R12 + B callbackasm1(SB) + MOVD $1025, R12 + B callbackasm1(SB) + MOVD $1026, R12 + B callbackasm1(SB) + MOVD $1027, R12 + B callbackasm1(SB) + MOVD $1028, R12 + B callbackasm1(SB) + MOVD $1029, R12 + B callbackasm1(SB) + MOVD $1030, R12 + B callbackasm1(SB) + MOVD $1031, R12 + B callbackasm1(SB) + MOVD $1032, R12 + B callbackasm1(SB) + MOVD $1033, R12 + B callbackasm1(SB) + MOVD $1034, R12 + B callbackasm1(SB) + MOVD $1035, R12 + B callbackasm1(SB) + MOVD $1036, R12 + B callbackasm1(SB) + MOVD $1037, R12 + B callbackasm1(SB) + MOVD $1038, R12 + B callbackasm1(SB) + MOVD $1039, R12 + B callbackasm1(SB) + MOVD $1040, R12 + B callbackasm1(SB) + MOVD $1041, R12 + B callbackasm1(SB) + MOVD $1042, R12 + B callbackasm1(SB) + MOVD $1043, R12 + B callbackasm1(SB) + MOVD $1044, R12 + B callbackasm1(SB) + MOVD $1045, R12 + B callbackasm1(SB) + MOVD $1046, R12 + B callbackasm1(SB) + MOVD $1047, R12 + B callbackasm1(SB) + MOVD $1048, R12 + B callbackasm1(SB) + MOVD $1049, R12 + B callbackasm1(SB) + MOVD $1050, R12 + B callbackasm1(SB) + MOVD $1051, R12 + B callbackasm1(SB) + MOVD $1052, R12 + B callbackasm1(SB) + MOVD $1053, R12 + B callbackasm1(SB) + MOVD $1054, R12 + B callbackasm1(SB) + MOVD $1055, R12 + B callbackasm1(SB) + MOVD $1056, R12 + B callbackasm1(SB) + MOVD $1057, R12 + B callbackasm1(SB) + MOVD $1058, R12 + B callbackasm1(SB) + MOVD $1059, R12 + B callbackasm1(SB) + MOVD $1060, R12 + B callbackasm1(SB) + MOVD $1061, R12 + B callbackasm1(SB) + MOVD $1062, R12 + B callbackasm1(SB) + MOVD $1063, R12 + B callbackasm1(SB) + MOVD $1064, R12 + B callbackasm1(SB) + MOVD $1065, R12 + B callbackasm1(SB) + MOVD $1066, R12 + B callbackasm1(SB) + MOVD $1067, R12 + B callbackasm1(SB) + MOVD $1068, R12 + B callbackasm1(SB) + MOVD $1069, R12 + B callbackasm1(SB) + MOVD $1070, R12 + B callbackasm1(SB) + MOVD $1071, R12 + B callbackasm1(SB) + MOVD $1072, R12 + B callbackasm1(SB) + MOVD $1073, R12 + B callbackasm1(SB) + MOVD $1074, R12 + B callbackasm1(SB) + MOVD $1075, R12 + B callbackasm1(SB) + MOVD $1076, R12 + B callbackasm1(SB) + MOVD $1077, R12 + B callbackasm1(SB) + MOVD $1078, R12 + B callbackasm1(SB) + MOVD $1079, R12 + B callbackasm1(SB) + MOVD $1080, R12 + B callbackasm1(SB) + MOVD $1081, R12 + B callbackasm1(SB) + MOVD $1082, R12 + B callbackasm1(SB) + MOVD $1083, R12 + B callbackasm1(SB) + MOVD $1084, R12 + B callbackasm1(SB) + MOVD $1085, R12 + B callbackasm1(SB) + MOVD $1086, R12 + B callbackasm1(SB) + MOVD $1087, R12 + B callbackasm1(SB) + MOVD $1088, R12 + B callbackasm1(SB) + MOVD $1089, R12 + B callbackasm1(SB) + MOVD $1090, R12 + B callbackasm1(SB) + MOVD $1091, R12 + B callbackasm1(SB) + MOVD $1092, R12 + B callbackasm1(SB) + MOVD $1093, R12 + B callbackasm1(SB) + MOVD $1094, R12 + B callbackasm1(SB) + MOVD $1095, R12 + B callbackasm1(SB) + MOVD $1096, R12 + B callbackasm1(SB) + MOVD $1097, R12 + B callbackasm1(SB) + MOVD $1098, R12 + B callbackasm1(SB) + MOVD $1099, R12 + B callbackasm1(SB) + MOVD $1100, R12 + B callbackasm1(SB) + MOVD $1101, R12 + B callbackasm1(SB) + MOVD $1102, R12 + B callbackasm1(SB) + MOVD $1103, R12 + B callbackasm1(SB) + MOVD $1104, R12 + B callbackasm1(SB) + MOVD $1105, R12 + B callbackasm1(SB) + MOVD $1106, R12 + B callbackasm1(SB) + MOVD $1107, R12 + B callbackasm1(SB) + MOVD $1108, R12 + B callbackasm1(SB) + MOVD $1109, R12 + B callbackasm1(SB) + MOVD $1110, R12 + B callbackasm1(SB) + MOVD $1111, R12 + B callbackasm1(SB) + MOVD $1112, R12 + B callbackasm1(SB) + MOVD $1113, R12 + B callbackasm1(SB) + MOVD $1114, R12 + B callbackasm1(SB) + MOVD $1115, R12 + B callbackasm1(SB) + MOVD $1116, R12 + B callbackasm1(SB) + MOVD $1117, R12 + B callbackasm1(SB) + MOVD $1118, R12 + B callbackasm1(SB) + MOVD $1119, R12 + B callbackasm1(SB) + MOVD $1120, R12 + B callbackasm1(SB) + MOVD $1121, R12 + B callbackasm1(SB) + MOVD $1122, R12 + B callbackasm1(SB) + MOVD $1123, R12 + B callbackasm1(SB) + MOVD $1124, R12 + B callbackasm1(SB) + MOVD $1125, R12 + B callbackasm1(SB) + MOVD $1126, R12 + B callbackasm1(SB) + MOVD $1127, R12 + B callbackasm1(SB) + MOVD $1128, R12 + B callbackasm1(SB) + MOVD $1129, R12 + B callbackasm1(SB) + MOVD $1130, R12 + B callbackasm1(SB) + MOVD $1131, R12 + B callbackasm1(SB) + MOVD $1132, R12 + B callbackasm1(SB) + MOVD $1133, R12 + B callbackasm1(SB) + MOVD $1134, R12 + B callbackasm1(SB) + MOVD $1135, R12 + B callbackasm1(SB) + MOVD $1136, R12 + B callbackasm1(SB) + MOVD $1137, R12 + B callbackasm1(SB) + MOVD $1138, R12 + B callbackasm1(SB) + MOVD $1139, R12 + B callbackasm1(SB) + MOVD $1140, R12 + B callbackasm1(SB) + MOVD $1141, R12 + B callbackasm1(SB) + MOVD $1142, R12 + B callbackasm1(SB) + MOVD $1143, R12 + B callbackasm1(SB) + MOVD $1144, R12 + B callbackasm1(SB) + MOVD $1145, R12 + B callbackasm1(SB) + MOVD $1146, R12 + B callbackasm1(SB) + MOVD $1147, R12 + B callbackasm1(SB) + MOVD $1148, R12 + B callbackasm1(SB) + MOVD $1149, R12 + B callbackasm1(SB) + MOVD $1150, R12 + B callbackasm1(SB) + MOVD $1151, R12 + B callbackasm1(SB) + MOVD $1152, R12 + B callbackasm1(SB) + MOVD $1153, R12 + B callbackasm1(SB) + MOVD $1154, R12 + B callbackasm1(SB) + MOVD $1155, R12 + B callbackasm1(SB) + MOVD $1156, R12 + B callbackasm1(SB) + MOVD $1157, R12 + B callbackasm1(SB) + MOVD $1158, R12 + B callbackasm1(SB) + MOVD $1159, R12 + B callbackasm1(SB) + MOVD $1160, R12 + B callbackasm1(SB) + MOVD $1161, R12 + B callbackasm1(SB) + MOVD $1162, R12 + B callbackasm1(SB) + MOVD $1163, R12 + B callbackasm1(SB) + MOVD $1164, R12 + B callbackasm1(SB) + MOVD $1165, R12 + B callbackasm1(SB) + MOVD $1166, R12 + B callbackasm1(SB) + MOVD $1167, R12 + B callbackasm1(SB) + MOVD $1168, R12 + B callbackasm1(SB) + MOVD $1169, R12 + B callbackasm1(SB) + MOVD $1170, R12 + B callbackasm1(SB) + MOVD $1171, R12 + B callbackasm1(SB) + MOVD $1172, R12 + B callbackasm1(SB) + MOVD $1173, R12 + B callbackasm1(SB) + MOVD $1174, R12 + B callbackasm1(SB) + MOVD $1175, R12 + B callbackasm1(SB) + MOVD $1176, R12 + B callbackasm1(SB) + MOVD $1177, R12 + B callbackasm1(SB) + MOVD $1178, R12 + B callbackasm1(SB) + MOVD $1179, R12 + B callbackasm1(SB) + MOVD $1180, R12 + B callbackasm1(SB) + MOVD $1181, R12 + B callbackasm1(SB) + MOVD $1182, R12 + B callbackasm1(SB) + MOVD $1183, R12 + B callbackasm1(SB) + MOVD $1184, R12 + B callbackasm1(SB) + MOVD $1185, R12 + B callbackasm1(SB) + MOVD $1186, R12 + B callbackasm1(SB) + MOVD $1187, R12 + B callbackasm1(SB) + MOVD $1188, R12 + B callbackasm1(SB) + MOVD $1189, R12 + B callbackasm1(SB) + MOVD $1190, R12 + B callbackasm1(SB) + MOVD $1191, R12 + B callbackasm1(SB) + MOVD $1192, R12 + B callbackasm1(SB) + MOVD $1193, R12 + B callbackasm1(SB) + MOVD $1194, R12 + B callbackasm1(SB) + MOVD $1195, R12 + B callbackasm1(SB) + MOVD $1196, R12 + B callbackasm1(SB) + MOVD $1197, R12 + B callbackasm1(SB) + MOVD $1198, R12 + B callbackasm1(SB) + MOVD $1199, R12 + B callbackasm1(SB) + MOVD $1200, R12 + B callbackasm1(SB) + MOVD $1201, R12 + B callbackasm1(SB) + MOVD $1202, R12 + B callbackasm1(SB) + MOVD $1203, R12 + B callbackasm1(SB) + MOVD $1204, R12 + B callbackasm1(SB) + MOVD $1205, R12 + B callbackasm1(SB) + MOVD $1206, R12 + B callbackasm1(SB) + MOVD $1207, R12 + B callbackasm1(SB) + MOVD $1208, R12 + B callbackasm1(SB) + MOVD $1209, R12 + B callbackasm1(SB) + MOVD $1210, R12 + B callbackasm1(SB) + MOVD $1211, R12 + B callbackasm1(SB) + MOVD $1212, R12 + B callbackasm1(SB) + MOVD $1213, R12 + B callbackasm1(SB) + MOVD $1214, R12 + B callbackasm1(SB) + MOVD $1215, R12 + B callbackasm1(SB) + MOVD $1216, R12 + B callbackasm1(SB) + MOVD $1217, R12 + B callbackasm1(SB) + MOVD $1218, R12 + B callbackasm1(SB) + MOVD $1219, R12 + B callbackasm1(SB) + MOVD $1220, R12 + B callbackasm1(SB) + MOVD $1221, R12 + B callbackasm1(SB) + MOVD $1222, R12 + B callbackasm1(SB) + MOVD $1223, R12 + B callbackasm1(SB) + MOVD $1224, R12 + B callbackasm1(SB) + MOVD $1225, R12 + B callbackasm1(SB) + MOVD $1226, R12 + B callbackasm1(SB) + MOVD $1227, R12 + B callbackasm1(SB) + MOVD $1228, R12 + B callbackasm1(SB) + MOVD $1229, R12 + B callbackasm1(SB) + MOVD $1230, R12 + B callbackasm1(SB) + MOVD $1231, R12 + B callbackasm1(SB) + MOVD $1232, R12 + B callbackasm1(SB) + MOVD $1233, R12 + B callbackasm1(SB) + MOVD $1234, R12 + B callbackasm1(SB) + MOVD $1235, R12 + B callbackasm1(SB) + MOVD $1236, R12 + B callbackasm1(SB) + MOVD $1237, R12 + B callbackasm1(SB) + MOVD $1238, R12 + B callbackasm1(SB) + MOVD $1239, R12 + B callbackasm1(SB) + MOVD $1240, R12 + B callbackasm1(SB) + MOVD $1241, R12 + B callbackasm1(SB) + MOVD $1242, R12 + B callbackasm1(SB) + MOVD $1243, R12 + B callbackasm1(SB) + MOVD $1244, R12 + B callbackasm1(SB) + MOVD $1245, R12 + B callbackasm1(SB) + MOVD $1246, R12 + B callbackasm1(SB) + MOVD $1247, R12 + B callbackasm1(SB) + MOVD $1248, R12 + B callbackasm1(SB) + MOVD $1249, R12 + B callbackasm1(SB) + MOVD $1250, R12 + B callbackasm1(SB) + MOVD $1251, R12 + B callbackasm1(SB) + MOVD $1252, R12 + B callbackasm1(SB) + MOVD $1253, R12 + B callbackasm1(SB) + MOVD $1254, R12 + B callbackasm1(SB) + MOVD $1255, R12 + B callbackasm1(SB) + MOVD $1256, R12 + B callbackasm1(SB) + MOVD $1257, R12 + B callbackasm1(SB) + MOVD $1258, R12 + B callbackasm1(SB) + MOVD $1259, R12 + B callbackasm1(SB) + MOVD $1260, R12 + B callbackasm1(SB) + MOVD $1261, R12 + B callbackasm1(SB) + MOVD $1262, R12 + B callbackasm1(SB) + MOVD $1263, R12 + B callbackasm1(SB) + MOVD $1264, R12 + B callbackasm1(SB) + MOVD $1265, R12 + B callbackasm1(SB) + MOVD $1266, R12 + B callbackasm1(SB) + MOVD $1267, R12 + B callbackasm1(SB) + MOVD $1268, R12 + B callbackasm1(SB) + MOVD $1269, R12 + B callbackasm1(SB) + MOVD $1270, R12 + B callbackasm1(SB) + MOVD $1271, R12 + B callbackasm1(SB) + MOVD $1272, R12 + B callbackasm1(SB) + MOVD $1273, R12 + B callbackasm1(SB) + MOVD $1274, R12 + B callbackasm1(SB) + MOVD $1275, R12 + B callbackasm1(SB) + MOVD $1276, R12 + B callbackasm1(SB) + MOVD $1277, R12 + B callbackasm1(SB) + MOVD $1278, R12 + B callbackasm1(SB) + MOVD $1279, R12 + B callbackasm1(SB) + MOVD $1280, R12 + B callbackasm1(SB) + MOVD $1281, R12 + B callbackasm1(SB) + MOVD $1282, R12 + B callbackasm1(SB) + MOVD $1283, R12 + B callbackasm1(SB) + MOVD $1284, R12 + B callbackasm1(SB) + MOVD $1285, R12 + B callbackasm1(SB) + MOVD $1286, R12 + B callbackasm1(SB) + MOVD $1287, R12 + B callbackasm1(SB) + MOVD $1288, R12 + B callbackasm1(SB) + MOVD $1289, R12 + B callbackasm1(SB) + MOVD $1290, R12 + B callbackasm1(SB) + MOVD $1291, R12 + B callbackasm1(SB) + MOVD $1292, R12 + B callbackasm1(SB) + MOVD $1293, R12 + B callbackasm1(SB) + MOVD $1294, R12 + B callbackasm1(SB) + MOVD $1295, R12 + B callbackasm1(SB) + MOVD $1296, R12 + B callbackasm1(SB) + MOVD $1297, R12 + B callbackasm1(SB) + MOVD $1298, R12 + B callbackasm1(SB) + MOVD $1299, R12 + B callbackasm1(SB) + MOVD $1300, R12 + B callbackasm1(SB) + MOVD $1301, R12 + B callbackasm1(SB) + MOVD $1302, R12 + B callbackasm1(SB) + MOVD $1303, R12 + B callbackasm1(SB) + MOVD $1304, R12 + B callbackasm1(SB) + MOVD $1305, R12 + B callbackasm1(SB) + MOVD $1306, R12 + B callbackasm1(SB) + MOVD $1307, R12 + B callbackasm1(SB) + MOVD $1308, R12 + B callbackasm1(SB) + MOVD $1309, R12 + B callbackasm1(SB) + MOVD $1310, R12 + B callbackasm1(SB) + MOVD $1311, R12 + B callbackasm1(SB) + MOVD $1312, R12 + B callbackasm1(SB) + MOVD $1313, R12 + B callbackasm1(SB) + MOVD $1314, R12 + B callbackasm1(SB) + MOVD $1315, R12 + B callbackasm1(SB) + MOVD $1316, R12 + B callbackasm1(SB) + MOVD $1317, R12 + B callbackasm1(SB) + MOVD $1318, R12 + B callbackasm1(SB) + MOVD $1319, R12 + B callbackasm1(SB) + MOVD $1320, R12 + B callbackasm1(SB) + MOVD $1321, R12 + B callbackasm1(SB) + MOVD $1322, R12 + B callbackasm1(SB) + MOVD $1323, R12 + B callbackasm1(SB) + MOVD $1324, R12 + B callbackasm1(SB) + MOVD $1325, R12 + B callbackasm1(SB) + MOVD $1326, R12 + B callbackasm1(SB) + MOVD $1327, R12 + B callbackasm1(SB) + MOVD $1328, R12 + B callbackasm1(SB) + MOVD $1329, R12 + B callbackasm1(SB) + MOVD $1330, R12 + B callbackasm1(SB) + MOVD $1331, R12 + B callbackasm1(SB) + MOVD $1332, R12 + B callbackasm1(SB) + MOVD $1333, R12 + B callbackasm1(SB) + MOVD $1334, R12 + B callbackasm1(SB) + MOVD $1335, R12 + B callbackasm1(SB) + MOVD $1336, R12 + B callbackasm1(SB) + MOVD $1337, R12 + B callbackasm1(SB) + MOVD $1338, R12 + B callbackasm1(SB) + MOVD $1339, R12 + B callbackasm1(SB) + MOVD $1340, R12 + B callbackasm1(SB) + MOVD $1341, R12 + B callbackasm1(SB) + MOVD $1342, R12 + B callbackasm1(SB) + MOVD $1343, R12 + B callbackasm1(SB) + MOVD $1344, R12 + B callbackasm1(SB) + MOVD $1345, R12 + B callbackasm1(SB) + MOVD $1346, R12 + B callbackasm1(SB) + MOVD $1347, R12 + B callbackasm1(SB) + MOVD $1348, R12 + B callbackasm1(SB) + MOVD $1349, R12 + B callbackasm1(SB) + MOVD $1350, R12 + B callbackasm1(SB) + MOVD $1351, R12 + B callbackasm1(SB) + MOVD $1352, R12 + B callbackasm1(SB) + MOVD $1353, R12 + B callbackasm1(SB) + MOVD $1354, R12 + B callbackasm1(SB) + MOVD $1355, R12 + B callbackasm1(SB) + MOVD $1356, R12 + B callbackasm1(SB) + MOVD $1357, R12 + B callbackasm1(SB) + MOVD $1358, R12 + B callbackasm1(SB) + MOVD $1359, R12 + B callbackasm1(SB) + MOVD $1360, R12 + B callbackasm1(SB) + MOVD $1361, R12 + B callbackasm1(SB) + MOVD $1362, R12 + B callbackasm1(SB) + MOVD $1363, R12 + B callbackasm1(SB) + MOVD $1364, R12 + B callbackasm1(SB) + MOVD $1365, R12 + B callbackasm1(SB) + MOVD $1366, R12 + B callbackasm1(SB) + MOVD $1367, R12 + B callbackasm1(SB) + MOVD $1368, R12 + B callbackasm1(SB) + MOVD $1369, R12 + B callbackasm1(SB) + MOVD $1370, R12 + B callbackasm1(SB) + MOVD $1371, R12 + B callbackasm1(SB) + MOVD $1372, R12 + B callbackasm1(SB) + MOVD $1373, R12 + B callbackasm1(SB) + MOVD $1374, R12 + B callbackasm1(SB) + MOVD $1375, R12 + B callbackasm1(SB) + MOVD $1376, R12 + B callbackasm1(SB) + MOVD $1377, R12 + B callbackasm1(SB) + MOVD $1378, R12 + B callbackasm1(SB) + MOVD $1379, R12 + B callbackasm1(SB) + MOVD $1380, R12 + B callbackasm1(SB) + MOVD $1381, R12 + B callbackasm1(SB) + MOVD $1382, R12 + B callbackasm1(SB) + MOVD $1383, R12 + B callbackasm1(SB) + MOVD $1384, R12 + B callbackasm1(SB) + MOVD $1385, R12 + B callbackasm1(SB) + MOVD $1386, R12 + B callbackasm1(SB) + MOVD $1387, R12 + B callbackasm1(SB) + MOVD $1388, R12 + B callbackasm1(SB) + MOVD $1389, R12 + B callbackasm1(SB) + MOVD $1390, R12 + B callbackasm1(SB) + MOVD $1391, R12 + B callbackasm1(SB) + MOVD $1392, R12 + B callbackasm1(SB) + MOVD $1393, R12 + B callbackasm1(SB) + MOVD $1394, R12 + B callbackasm1(SB) + MOVD $1395, R12 + B callbackasm1(SB) + MOVD $1396, R12 + B callbackasm1(SB) + MOVD $1397, R12 + B callbackasm1(SB) + MOVD $1398, R12 + B callbackasm1(SB) + MOVD $1399, R12 + B callbackasm1(SB) + MOVD $1400, R12 + B callbackasm1(SB) + MOVD $1401, R12 + B callbackasm1(SB) + MOVD $1402, R12 + B callbackasm1(SB) + MOVD $1403, R12 + B callbackasm1(SB) + MOVD $1404, R12 + B callbackasm1(SB) + MOVD $1405, R12 + B callbackasm1(SB) + MOVD $1406, R12 + B callbackasm1(SB) + MOVD $1407, R12 + B callbackasm1(SB) + MOVD $1408, R12 + B callbackasm1(SB) + MOVD $1409, R12 + B callbackasm1(SB) + MOVD $1410, R12 + B callbackasm1(SB) + MOVD $1411, R12 + B callbackasm1(SB) + MOVD $1412, R12 + B callbackasm1(SB) + MOVD $1413, R12 + B callbackasm1(SB) + MOVD $1414, R12 + B callbackasm1(SB) + MOVD $1415, R12 + B callbackasm1(SB) + MOVD $1416, R12 + B callbackasm1(SB) + MOVD $1417, R12 + B callbackasm1(SB) + MOVD $1418, R12 + B callbackasm1(SB) + MOVD $1419, R12 + B callbackasm1(SB) + MOVD $1420, R12 + B callbackasm1(SB) + MOVD $1421, R12 + B callbackasm1(SB) + MOVD $1422, R12 + B callbackasm1(SB) + MOVD $1423, R12 + B callbackasm1(SB) + MOVD $1424, R12 + B callbackasm1(SB) + MOVD $1425, R12 + B callbackasm1(SB) + MOVD $1426, R12 + B callbackasm1(SB) + MOVD $1427, R12 + B callbackasm1(SB) + MOVD $1428, R12 + B callbackasm1(SB) + MOVD $1429, R12 + B callbackasm1(SB) + MOVD $1430, R12 + B callbackasm1(SB) + MOVD $1431, R12 + B callbackasm1(SB) + MOVD $1432, R12 + B callbackasm1(SB) + MOVD $1433, R12 + B callbackasm1(SB) + MOVD $1434, R12 + B callbackasm1(SB) + MOVD $1435, R12 + B callbackasm1(SB) + MOVD $1436, R12 + B callbackasm1(SB) + MOVD $1437, R12 + B callbackasm1(SB) + MOVD $1438, R12 + B callbackasm1(SB) + MOVD $1439, R12 + B callbackasm1(SB) + MOVD $1440, R12 + B callbackasm1(SB) + MOVD $1441, R12 + B callbackasm1(SB) + MOVD $1442, R12 + B callbackasm1(SB) + MOVD $1443, R12 + B callbackasm1(SB) + MOVD $1444, R12 + B callbackasm1(SB) + MOVD $1445, R12 + B callbackasm1(SB) + MOVD $1446, R12 + B callbackasm1(SB) + MOVD $1447, R12 + B callbackasm1(SB) + MOVD $1448, R12 + B callbackasm1(SB) + MOVD $1449, R12 + B callbackasm1(SB) + MOVD $1450, R12 + B callbackasm1(SB) + MOVD $1451, R12 + B callbackasm1(SB) + MOVD $1452, R12 + B callbackasm1(SB) + MOVD $1453, R12 + B callbackasm1(SB) + MOVD $1454, R12 + B callbackasm1(SB) + MOVD $1455, R12 + B callbackasm1(SB) + MOVD $1456, R12 + B callbackasm1(SB) + MOVD $1457, R12 + B callbackasm1(SB) + MOVD $1458, R12 + B callbackasm1(SB) + MOVD $1459, R12 + B callbackasm1(SB) + MOVD $1460, R12 + B callbackasm1(SB) + MOVD $1461, R12 + B callbackasm1(SB) + MOVD $1462, R12 + B callbackasm1(SB) + MOVD $1463, R12 + B callbackasm1(SB) + MOVD $1464, R12 + B callbackasm1(SB) + MOVD $1465, R12 + B callbackasm1(SB) + MOVD $1466, R12 + B callbackasm1(SB) + MOVD $1467, R12 + B callbackasm1(SB) + MOVD $1468, R12 + B callbackasm1(SB) + MOVD $1469, R12 + B callbackasm1(SB) + MOVD $1470, R12 + B callbackasm1(SB) + MOVD $1471, R12 + B callbackasm1(SB) + MOVD $1472, R12 + B callbackasm1(SB) + MOVD $1473, R12 + B callbackasm1(SB) + MOVD $1474, R12 + B callbackasm1(SB) + MOVD $1475, R12 + B callbackasm1(SB) + MOVD $1476, R12 + B callbackasm1(SB) + MOVD $1477, R12 + B callbackasm1(SB) + MOVD $1478, R12 + B callbackasm1(SB) + MOVD $1479, R12 + B callbackasm1(SB) + MOVD $1480, R12 + B callbackasm1(SB) + MOVD $1481, R12 + B callbackasm1(SB) + MOVD $1482, R12 + B callbackasm1(SB) + MOVD $1483, R12 + B callbackasm1(SB) + MOVD $1484, R12 + B callbackasm1(SB) + MOVD $1485, R12 + B callbackasm1(SB) + MOVD $1486, R12 + B callbackasm1(SB) + MOVD $1487, R12 + B callbackasm1(SB) + MOVD $1488, R12 + B callbackasm1(SB) + MOVD $1489, R12 + B callbackasm1(SB) + MOVD $1490, R12 + B callbackasm1(SB) + MOVD $1491, R12 + B callbackasm1(SB) + MOVD $1492, R12 + B callbackasm1(SB) + MOVD $1493, R12 + B callbackasm1(SB) + MOVD $1494, R12 + B callbackasm1(SB) + MOVD $1495, R12 + B callbackasm1(SB) + MOVD $1496, R12 + B callbackasm1(SB) + MOVD $1497, R12 + B callbackasm1(SB) + MOVD $1498, R12 + B callbackasm1(SB) + MOVD $1499, R12 + B callbackasm1(SB) + MOVD $1500, R12 + B callbackasm1(SB) + MOVD $1501, R12 + B callbackasm1(SB) + MOVD $1502, R12 + B callbackasm1(SB) + MOVD $1503, R12 + B callbackasm1(SB) + MOVD $1504, R12 + B callbackasm1(SB) + MOVD $1505, R12 + B callbackasm1(SB) + MOVD $1506, R12 + B callbackasm1(SB) + MOVD $1507, R12 + B callbackasm1(SB) + MOVD $1508, R12 + B callbackasm1(SB) + MOVD $1509, R12 + B callbackasm1(SB) + MOVD $1510, R12 + B callbackasm1(SB) + MOVD $1511, R12 + B callbackasm1(SB) + MOVD $1512, R12 + B callbackasm1(SB) + MOVD $1513, R12 + B callbackasm1(SB) + MOVD $1514, R12 + B callbackasm1(SB) + MOVD $1515, R12 + B callbackasm1(SB) + MOVD $1516, R12 + B callbackasm1(SB) + MOVD $1517, R12 + B callbackasm1(SB) + MOVD $1518, R12 + B callbackasm1(SB) + MOVD $1519, R12 + B callbackasm1(SB) + MOVD $1520, R12 + B callbackasm1(SB) + MOVD $1521, R12 + B callbackasm1(SB) + MOVD $1522, R12 + B callbackasm1(SB) + MOVD $1523, R12 + B callbackasm1(SB) + MOVD $1524, R12 + B callbackasm1(SB) + MOVD $1525, R12 + B callbackasm1(SB) + MOVD $1526, R12 + B callbackasm1(SB) + MOVD $1527, R12 + B callbackasm1(SB) + MOVD $1528, R12 + B callbackasm1(SB) + MOVD $1529, R12 + B callbackasm1(SB) + MOVD $1530, R12 + B callbackasm1(SB) + MOVD $1531, R12 + B callbackasm1(SB) + MOVD $1532, R12 + B callbackasm1(SB) + MOVD $1533, R12 + B callbackasm1(SB) + MOVD $1534, R12 + B callbackasm1(SB) + MOVD $1535, R12 + B callbackasm1(SB) + MOVD $1536, R12 + B callbackasm1(SB) + MOVD $1537, R12 + B callbackasm1(SB) + MOVD $1538, R12 + B callbackasm1(SB) + MOVD $1539, R12 + B callbackasm1(SB) + MOVD $1540, R12 + B callbackasm1(SB) + MOVD $1541, R12 + B callbackasm1(SB) + MOVD $1542, R12 + B callbackasm1(SB) + MOVD $1543, R12 + B callbackasm1(SB) + MOVD $1544, R12 + B callbackasm1(SB) + MOVD $1545, R12 + B callbackasm1(SB) + MOVD $1546, R12 + B callbackasm1(SB) + MOVD $1547, R12 + B callbackasm1(SB) + MOVD $1548, R12 + B callbackasm1(SB) + MOVD $1549, R12 + B callbackasm1(SB) + MOVD $1550, R12 + B callbackasm1(SB) + MOVD $1551, R12 + B callbackasm1(SB) + MOVD $1552, R12 + B callbackasm1(SB) + MOVD $1553, R12 + B callbackasm1(SB) + MOVD $1554, R12 + B callbackasm1(SB) + MOVD $1555, R12 + B callbackasm1(SB) + MOVD $1556, R12 + B callbackasm1(SB) + MOVD $1557, R12 + B callbackasm1(SB) + MOVD $1558, R12 + B callbackasm1(SB) + MOVD $1559, R12 + B callbackasm1(SB) + MOVD $1560, R12 + B callbackasm1(SB) + MOVD $1561, R12 + B callbackasm1(SB) + MOVD $1562, R12 + B callbackasm1(SB) + MOVD $1563, R12 + B callbackasm1(SB) + MOVD $1564, R12 + B callbackasm1(SB) + MOVD $1565, R12 + B callbackasm1(SB) + MOVD $1566, R12 + B callbackasm1(SB) + MOVD $1567, R12 + B callbackasm1(SB) + MOVD $1568, R12 + B callbackasm1(SB) + MOVD $1569, R12 + B callbackasm1(SB) + MOVD $1570, R12 + B callbackasm1(SB) + MOVD $1571, R12 + B callbackasm1(SB) + MOVD $1572, R12 + B callbackasm1(SB) + MOVD $1573, R12 + B callbackasm1(SB) + MOVD $1574, R12 + B callbackasm1(SB) + MOVD $1575, R12 + B callbackasm1(SB) + MOVD $1576, R12 + B callbackasm1(SB) + MOVD $1577, R12 + B callbackasm1(SB) + MOVD $1578, R12 + B callbackasm1(SB) + MOVD $1579, R12 + B callbackasm1(SB) + MOVD $1580, R12 + B callbackasm1(SB) + MOVD $1581, R12 + B callbackasm1(SB) + MOVD $1582, R12 + B callbackasm1(SB) + MOVD $1583, R12 + B callbackasm1(SB) + MOVD $1584, R12 + B callbackasm1(SB) + MOVD $1585, R12 + B callbackasm1(SB) + MOVD $1586, R12 + B callbackasm1(SB) + MOVD $1587, R12 + B callbackasm1(SB) + MOVD $1588, R12 + B callbackasm1(SB) + MOVD $1589, R12 + B callbackasm1(SB) + MOVD $1590, R12 + B callbackasm1(SB) + MOVD $1591, R12 + B callbackasm1(SB) + MOVD $1592, R12 + B callbackasm1(SB) + MOVD $1593, R12 + B callbackasm1(SB) + MOVD $1594, R12 + B callbackasm1(SB) + MOVD $1595, R12 + B callbackasm1(SB) + MOVD $1596, R12 + B callbackasm1(SB) + MOVD $1597, R12 + B callbackasm1(SB) + MOVD $1598, R12 + B callbackasm1(SB) + MOVD $1599, R12 + B callbackasm1(SB) + MOVD $1600, R12 + B callbackasm1(SB) + MOVD $1601, R12 + B callbackasm1(SB) + MOVD $1602, R12 + B callbackasm1(SB) + MOVD $1603, R12 + B callbackasm1(SB) + MOVD $1604, R12 + B callbackasm1(SB) + MOVD $1605, R12 + B callbackasm1(SB) + MOVD $1606, R12 + B callbackasm1(SB) + MOVD $1607, R12 + B callbackasm1(SB) + MOVD $1608, R12 + B callbackasm1(SB) + MOVD $1609, R12 + B callbackasm1(SB) + MOVD $1610, R12 + B callbackasm1(SB) + MOVD $1611, R12 + B callbackasm1(SB) + MOVD $1612, R12 + B callbackasm1(SB) + MOVD $1613, R12 + B callbackasm1(SB) + MOVD $1614, R12 + B callbackasm1(SB) + MOVD $1615, R12 + B callbackasm1(SB) + MOVD $1616, R12 + B callbackasm1(SB) + MOVD $1617, R12 + B callbackasm1(SB) + MOVD $1618, R12 + B callbackasm1(SB) + MOVD $1619, R12 + B callbackasm1(SB) + MOVD $1620, R12 + B callbackasm1(SB) + MOVD $1621, R12 + B callbackasm1(SB) + MOVD $1622, R12 + B callbackasm1(SB) + MOVD $1623, R12 + B callbackasm1(SB) + MOVD $1624, R12 + B callbackasm1(SB) + MOVD $1625, R12 + B callbackasm1(SB) + MOVD $1626, R12 + B callbackasm1(SB) + MOVD $1627, R12 + B callbackasm1(SB) + MOVD $1628, R12 + B callbackasm1(SB) + MOVD $1629, R12 + B callbackasm1(SB) + MOVD $1630, R12 + B callbackasm1(SB) + MOVD $1631, R12 + B callbackasm1(SB) + MOVD $1632, R12 + B callbackasm1(SB) + MOVD $1633, R12 + B callbackasm1(SB) + MOVD $1634, R12 + B callbackasm1(SB) + MOVD $1635, R12 + B callbackasm1(SB) + MOVD $1636, R12 + B callbackasm1(SB) + MOVD $1637, R12 + B callbackasm1(SB) + MOVD $1638, R12 + B callbackasm1(SB) + MOVD $1639, R12 + B callbackasm1(SB) + MOVD $1640, R12 + B callbackasm1(SB) + MOVD $1641, R12 + B callbackasm1(SB) + MOVD $1642, R12 + B callbackasm1(SB) + MOVD $1643, R12 + B callbackasm1(SB) + MOVD $1644, R12 + B callbackasm1(SB) + MOVD $1645, R12 + B callbackasm1(SB) + MOVD $1646, R12 + B callbackasm1(SB) + MOVD $1647, R12 + B callbackasm1(SB) + MOVD $1648, R12 + B callbackasm1(SB) + MOVD $1649, R12 + B callbackasm1(SB) + MOVD $1650, R12 + B callbackasm1(SB) + MOVD $1651, R12 + B callbackasm1(SB) + MOVD $1652, R12 + B callbackasm1(SB) + MOVD $1653, R12 + B callbackasm1(SB) + MOVD $1654, R12 + B callbackasm1(SB) + MOVD $1655, R12 + B callbackasm1(SB) + MOVD $1656, R12 + B callbackasm1(SB) + MOVD $1657, R12 + B callbackasm1(SB) + MOVD $1658, R12 + B callbackasm1(SB) + MOVD $1659, R12 + B callbackasm1(SB) + MOVD $1660, R12 + B callbackasm1(SB) + MOVD $1661, R12 + B callbackasm1(SB) + MOVD $1662, R12 + B callbackasm1(SB) + MOVD $1663, R12 + B callbackasm1(SB) + MOVD $1664, R12 + B callbackasm1(SB) + MOVD $1665, R12 + B callbackasm1(SB) + MOVD $1666, R12 + B callbackasm1(SB) + MOVD $1667, R12 + B callbackasm1(SB) + MOVD $1668, R12 + B callbackasm1(SB) + MOVD $1669, R12 + B callbackasm1(SB) + MOVD $1670, R12 + B callbackasm1(SB) + MOVD $1671, R12 + B callbackasm1(SB) + MOVD $1672, R12 + B callbackasm1(SB) + MOVD $1673, R12 + B callbackasm1(SB) + MOVD $1674, R12 + B callbackasm1(SB) + MOVD $1675, R12 + B callbackasm1(SB) + MOVD $1676, R12 + B callbackasm1(SB) + MOVD $1677, R12 + B callbackasm1(SB) + MOVD $1678, R12 + B callbackasm1(SB) + MOVD $1679, R12 + B callbackasm1(SB) + MOVD $1680, R12 + B callbackasm1(SB) + MOVD $1681, R12 + B callbackasm1(SB) + MOVD $1682, R12 + B callbackasm1(SB) + MOVD $1683, R12 + B callbackasm1(SB) + MOVD $1684, R12 + B callbackasm1(SB) + MOVD $1685, R12 + B callbackasm1(SB) + MOVD $1686, R12 + B callbackasm1(SB) + MOVD $1687, R12 + B callbackasm1(SB) + MOVD $1688, R12 + B callbackasm1(SB) + MOVD $1689, R12 + B callbackasm1(SB) + MOVD $1690, R12 + B callbackasm1(SB) + MOVD $1691, R12 + B callbackasm1(SB) + MOVD $1692, R12 + B callbackasm1(SB) + MOVD $1693, R12 + B callbackasm1(SB) + MOVD $1694, R12 + B callbackasm1(SB) + MOVD $1695, R12 + B callbackasm1(SB) + MOVD $1696, R12 + B callbackasm1(SB) + MOVD $1697, R12 + B callbackasm1(SB) + MOVD $1698, R12 + B callbackasm1(SB) + MOVD $1699, R12 + B callbackasm1(SB) + MOVD $1700, R12 + B callbackasm1(SB) + MOVD $1701, R12 + B callbackasm1(SB) + MOVD $1702, R12 + B callbackasm1(SB) + MOVD $1703, R12 + B callbackasm1(SB) + MOVD $1704, R12 + B callbackasm1(SB) + MOVD $1705, R12 + B callbackasm1(SB) + MOVD $1706, R12 + B callbackasm1(SB) + MOVD $1707, R12 + B callbackasm1(SB) + MOVD $1708, R12 + B callbackasm1(SB) + MOVD $1709, R12 + B callbackasm1(SB) + MOVD $1710, R12 + B callbackasm1(SB) + MOVD $1711, R12 + B callbackasm1(SB) + MOVD $1712, R12 + B callbackasm1(SB) + MOVD $1713, R12 + B callbackasm1(SB) + MOVD $1714, R12 + B callbackasm1(SB) + MOVD $1715, R12 + B callbackasm1(SB) + MOVD $1716, R12 + B callbackasm1(SB) + MOVD $1717, R12 + B callbackasm1(SB) + MOVD $1718, R12 + B callbackasm1(SB) + MOVD $1719, R12 + B callbackasm1(SB) + MOVD $1720, R12 + B callbackasm1(SB) + MOVD $1721, R12 + B callbackasm1(SB) + MOVD $1722, R12 + B callbackasm1(SB) + MOVD $1723, R12 + B callbackasm1(SB) + MOVD $1724, R12 + B callbackasm1(SB) + MOVD $1725, R12 + B callbackasm1(SB) + MOVD $1726, R12 + B callbackasm1(SB) + MOVD $1727, R12 + B callbackasm1(SB) + MOVD $1728, R12 + B callbackasm1(SB) + MOVD $1729, R12 + B callbackasm1(SB) + MOVD $1730, R12 + B callbackasm1(SB) + MOVD $1731, R12 + B callbackasm1(SB) + MOVD $1732, R12 + B callbackasm1(SB) + MOVD $1733, R12 + B callbackasm1(SB) + MOVD $1734, R12 + B callbackasm1(SB) + MOVD $1735, R12 + B callbackasm1(SB) + MOVD $1736, R12 + B callbackasm1(SB) + MOVD $1737, R12 + B callbackasm1(SB) + MOVD $1738, R12 + B callbackasm1(SB) + MOVD $1739, R12 + B callbackasm1(SB) + MOVD $1740, R12 + B callbackasm1(SB) + MOVD $1741, R12 + B callbackasm1(SB) + MOVD $1742, R12 + B callbackasm1(SB) + MOVD $1743, R12 + B callbackasm1(SB) + MOVD $1744, R12 + B callbackasm1(SB) + MOVD $1745, R12 + B callbackasm1(SB) + MOVD $1746, R12 + B callbackasm1(SB) + MOVD $1747, R12 + B callbackasm1(SB) + MOVD $1748, R12 + B callbackasm1(SB) + MOVD $1749, R12 + B callbackasm1(SB) + MOVD $1750, R12 + B callbackasm1(SB) + MOVD $1751, R12 + B callbackasm1(SB) + MOVD $1752, R12 + B callbackasm1(SB) + MOVD $1753, R12 + B callbackasm1(SB) + MOVD $1754, R12 + B callbackasm1(SB) + MOVD $1755, R12 + B callbackasm1(SB) + MOVD $1756, R12 + B callbackasm1(SB) + MOVD $1757, R12 + B callbackasm1(SB) + MOVD $1758, R12 + B callbackasm1(SB) + MOVD $1759, R12 + B callbackasm1(SB) + MOVD $1760, R12 + B callbackasm1(SB) + MOVD $1761, R12 + B callbackasm1(SB) + MOVD $1762, R12 + B callbackasm1(SB) + MOVD $1763, R12 + B callbackasm1(SB) + MOVD $1764, R12 + B callbackasm1(SB) + MOVD $1765, R12 + B callbackasm1(SB) + MOVD $1766, R12 + B callbackasm1(SB) + MOVD $1767, R12 + B callbackasm1(SB) + MOVD $1768, R12 + B callbackasm1(SB) + MOVD $1769, R12 + B callbackasm1(SB) + MOVD $1770, R12 + B callbackasm1(SB) + MOVD $1771, R12 + B callbackasm1(SB) + MOVD $1772, R12 + B callbackasm1(SB) + MOVD $1773, R12 + B callbackasm1(SB) + MOVD $1774, R12 + B callbackasm1(SB) + MOVD $1775, R12 + B callbackasm1(SB) + MOVD $1776, R12 + B callbackasm1(SB) + MOVD $1777, R12 + B callbackasm1(SB) + MOVD $1778, R12 + B callbackasm1(SB) + MOVD $1779, R12 + B callbackasm1(SB) + MOVD $1780, R12 + B callbackasm1(SB) + MOVD $1781, R12 + B callbackasm1(SB) + MOVD $1782, R12 + B callbackasm1(SB) + MOVD $1783, R12 + B callbackasm1(SB) + MOVD $1784, R12 + B callbackasm1(SB) + MOVD $1785, R12 + B callbackasm1(SB) + MOVD $1786, R12 + B callbackasm1(SB) + MOVD $1787, R12 + B callbackasm1(SB) + MOVD $1788, R12 + B callbackasm1(SB) + MOVD $1789, R12 + B callbackasm1(SB) + MOVD $1790, R12 + B callbackasm1(SB) + MOVD $1791, R12 + B callbackasm1(SB) + MOVD $1792, R12 + B callbackasm1(SB) + MOVD $1793, R12 + B callbackasm1(SB) + MOVD $1794, R12 + B callbackasm1(SB) + MOVD $1795, R12 + B callbackasm1(SB) + MOVD $1796, R12 + B callbackasm1(SB) + MOVD $1797, R12 + B callbackasm1(SB) + MOVD $1798, R12 + B callbackasm1(SB) + MOVD $1799, R12 + B callbackasm1(SB) + MOVD $1800, R12 + B callbackasm1(SB) + MOVD $1801, R12 + B callbackasm1(SB) + MOVD $1802, R12 + B callbackasm1(SB) + MOVD $1803, R12 + B callbackasm1(SB) + MOVD $1804, R12 + B callbackasm1(SB) + MOVD $1805, R12 + B callbackasm1(SB) + MOVD $1806, R12 + B callbackasm1(SB) + MOVD $1807, R12 + B callbackasm1(SB) + MOVD $1808, R12 + B callbackasm1(SB) + MOVD $1809, R12 + B callbackasm1(SB) + MOVD $1810, R12 + B callbackasm1(SB) + MOVD $1811, R12 + B callbackasm1(SB) + MOVD $1812, R12 + B callbackasm1(SB) + MOVD $1813, R12 + B callbackasm1(SB) + MOVD $1814, R12 + B callbackasm1(SB) + MOVD $1815, R12 + B callbackasm1(SB) + MOVD $1816, R12 + B callbackasm1(SB) + MOVD $1817, R12 + B callbackasm1(SB) + MOVD $1818, R12 + B callbackasm1(SB) + MOVD $1819, R12 + B callbackasm1(SB) + MOVD $1820, R12 + B callbackasm1(SB) + MOVD $1821, R12 + B callbackasm1(SB) + MOVD $1822, R12 + B callbackasm1(SB) + MOVD $1823, R12 + B callbackasm1(SB) + MOVD $1824, R12 + B callbackasm1(SB) + MOVD $1825, R12 + B callbackasm1(SB) + MOVD $1826, R12 + B callbackasm1(SB) + MOVD $1827, R12 + B callbackasm1(SB) + MOVD $1828, R12 + B callbackasm1(SB) + MOVD $1829, R12 + B callbackasm1(SB) + MOVD $1830, R12 + B callbackasm1(SB) + MOVD $1831, R12 + B callbackasm1(SB) + MOVD $1832, R12 + B callbackasm1(SB) + MOVD $1833, R12 + B callbackasm1(SB) + MOVD $1834, R12 + B callbackasm1(SB) + MOVD $1835, R12 + B callbackasm1(SB) + MOVD $1836, R12 + B callbackasm1(SB) + MOVD $1837, R12 + B callbackasm1(SB) + MOVD $1838, R12 + B callbackasm1(SB) + MOVD $1839, R12 + B callbackasm1(SB) + MOVD $1840, R12 + B callbackasm1(SB) + MOVD $1841, R12 + B callbackasm1(SB) + MOVD $1842, R12 + B callbackasm1(SB) + MOVD $1843, R12 + B callbackasm1(SB) + MOVD $1844, R12 + B callbackasm1(SB) + MOVD $1845, R12 + B callbackasm1(SB) + MOVD $1846, R12 + B callbackasm1(SB) + MOVD $1847, R12 + B callbackasm1(SB) + MOVD $1848, R12 + B callbackasm1(SB) + MOVD $1849, R12 + B callbackasm1(SB) + MOVD $1850, R12 + B callbackasm1(SB) + MOVD $1851, R12 + B callbackasm1(SB) + MOVD $1852, R12 + B callbackasm1(SB) + MOVD $1853, R12 + B callbackasm1(SB) + MOVD $1854, R12 + B callbackasm1(SB) + MOVD $1855, R12 + B callbackasm1(SB) + MOVD $1856, R12 + B callbackasm1(SB) + MOVD $1857, R12 + B callbackasm1(SB) + MOVD $1858, R12 + B callbackasm1(SB) + MOVD $1859, R12 + B callbackasm1(SB) + MOVD $1860, R12 + B callbackasm1(SB) + MOVD $1861, R12 + B callbackasm1(SB) + MOVD $1862, R12 + B callbackasm1(SB) + MOVD $1863, R12 + B callbackasm1(SB) + MOVD $1864, R12 + B callbackasm1(SB) + MOVD $1865, R12 + B callbackasm1(SB) + MOVD $1866, R12 + B callbackasm1(SB) + MOVD $1867, R12 + B callbackasm1(SB) + MOVD $1868, R12 + B callbackasm1(SB) + MOVD $1869, R12 + B callbackasm1(SB) + MOVD $1870, R12 + B callbackasm1(SB) + MOVD $1871, R12 + B callbackasm1(SB) + MOVD $1872, R12 + B callbackasm1(SB) + MOVD $1873, R12 + B callbackasm1(SB) + MOVD $1874, R12 + B callbackasm1(SB) + MOVD $1875, R12 + B callbackasm1(SB) + MOVD $1876, R12 + B callbackasm1(SB) + MOVD $1877, R12 + B callbackasm1(SB) + MOVD $1878, R12 + B callbackasm1(SB) + MOVD $1879, R12 + B callbackasm1(SB) + MOVD $1880, R12 + B callbackasm1(SB) + MOVD $1881, R12 + B callbackasm1(SB) + MOVD $1882, R12 + B callbackasm1(SB) + MOVD $1883, R12 + B callbackasm1(SB) + MOVD $1884, R12 + B callbackasm1(SB) + MOVD $1885, R12 + B callbackasm1(SB) + MOVD $1886, R12 + B callbackasm1(SB) + MOVD $1887, R12 + B callbackasm1(SB) + MOVD $1888, R12 + B callbackasm1(SB) + MOVD $1889, R12 + B callbackasm1(SB) + MOVD $1890, R12 + B callbackasm1(SB) + MOVD $1891, R12 + B callbackasm1(SB) + MOVD $1892, R12 + B callbackasm1(SB) + MOVD $1893, R12 + B callbackasm1(SB) + MOVD $1894, R12 + B callbackasm1(SB) + MOVD $1895, R12 + B callbackasm1(SB) + MOVD $1896, R12 + B callbackasm1(SB) + MOVD $1897, R12 + B callbackasm1(SB) + MOVD $1898, R12 + B callbackasm1(SB) + MOVD $1899, R12 + B callbackasm1(SB) + MOVD $1900, R12 + B callbackasm1(SB) + MOVD $1901, R12 + B callbackasm1(SB) + MOVD $1902, R12 + B callbackasm1(SB) + MOVD $1903, R12 + B callbackasm1(SB) + MOVD $1904, R12 + B callbackasm1(SB) + MOVD $1905, R12 + B callbackasm1(SB) + MOVD $1906, R12 + B callbackasm1(SB) + MOVD $1907, R12 + B callbackasm1(SB) + MOVD $1908, R12 + B callbackasm1(SB) + MOVD $1909, R12 + B callbackasm1(SB) + MOVD $1910, R12 + B callbackasm1(SB) + MOVD $1911, R12 + B callbackasm1(SB) + MOVD $1912, R12 + B callbackasm1(SB) + MOVD $1913, R12 + B callbackasm1(SB) + MOVD $1914, R12 + B callbackasm1(SB) + MOVD $1915, R12 + B callbackasm1(SB) + MOVD $1916, R12 + B callbackasm1(SB) + MOVD $1917, R12 + B callbackasm1(SB) + MOVD $1918, R12 + B callbackasm1(SB) + MOVD $1919, R12 + B callbackasm1(SB) + MOVD $1920, R12 + B callbackasm1(SB) + MOVD $1921, R12 + B callbackasm1(SB) + MOVD $1922, R12 + B callbackasm1(SB) + MOVD $1923, R12 + B callbackasm1(SB) + MOVD $1924, R12 + B callbackasm1(SB) + MOVD $1925, R12 + B callbackasm1(SB) + MOVD $1926, R12 + B callbackasm1(SB) + MOVD $1927, R12 + B callbackasm1(SB) + MOVD $1928, R12 + B callbackasm1(SB) + MOVD $1929, R12 + B callbackasm1(SB) + MOVD $1930, R12 + B callbackasm1(SB) + MOVD $1931, R12 + B callbackasm1(SB) + MOVD $1932, R12 + B callbackasm1(SB) + MOVD $1933, R12 + B callbackasm1(SB) + MOVD $1934, R12 + B callbackasm1(SB) + MOVD $1935, R12 + B callbackasm1(SB) + MOVD $1936, R12 + B callbackasm1(SB) + MOVD $1937, R12 + B callbackasm1(SB) + MOVD $1938, R12 + B callbackasm1(SB) + MOVD $1939, R12 + B callbackasm1(SB) + MOVD $1940, R12 + B callbackasm1(SB) + MOVD $1941, R12 + B callbackasm1(SB) + MOVD $1942, R12 + B callbackasm1(SB) + MOVD $1943, R12 + B callbackasm1(SB) + MOVD $1944, R12 + B callbackasm1(SB) + MOVD $1945, R12 + B callbackasm1(SB) + MOVD $1946, R12 + B callbackasm1(SB) + MOVD $1947, R12 + B callbackasm1(SB) + MOVD $1948, R12 + B callbackasm1(SB) + MOVD $1949, R12 + B callbackasm1(SB) + MOVD $1950, R12 + B callbackasm1(SB) + MOVD $1951, R12 + B callbackasm1(SB) + MOVD $1952, R12 + B callbackasm1(SB) + MOVD $1953, R12 + B callbackasm1(SB) + MOVD $1954, R12 + B callbackasm1(SB) + MOVD $1955, R12 + B callbackasm1(SB) + MOVD $1956, R12 + B callbackasm1(SB) + MOVD $1957, R12 + B callbackasm1(SB) + MOVD $1958, R12 + B callbackasm1(SB) + MOVD $1959, R12 + B callbackasm1(SB) + MOVD $1960, R12 + B callbackasm1(SB) + MOVD $1961, R12 + B callbackasm1(SB) + MOVD $1962, R12 + B callbackasm1(SB) + MOVD $1963, R12 + B callbackasm1(SB) + MOVD $1964, R12 + B callbackasm1(SB) + MOVD $1965, R12 + B callbackasm1(SB) + MOVD $1966, R12 + B callbackasm1(SB) + MOVD $1967, R12 + B callbackasm1(SB) + MOVD $1968, R12 + B callbackasm1(SB) + MOVD $1969, R12 + B callbackasm1(SB) + MOVD $1970, R12 + B callbackasm1(SB) + MOVD $1971, R12 + B callbackasm1(SB) + MOVD $1972, R12 + B callbackasm1(SB) + MOVD $1973, R12 + B callbackasm1(SB) + MOVD $1974, R12 + B callbackasm1(SB) + MOVD $1975, R12 + B callbackasm1(SB) + MOVD $1976, R12 + B callbackasm1(SB) + MOVD $1977, R12 + B callbackasm1(SB) + MOVD $1978, R12 + B callbackasm1(SB) + MOVD $1979, R12 + B callbackasm1(SB) + MOVD $1980, R12 + B callbackasm1(SB) + MOVD $1981, R12 + B callbackasm1(SB) + MOVD $1982, R12 + B callbackasm1(SB) + MOVD $1983, R12 + B callbackasm1(SB) + MOVD $1984, R12 + B callbackasm1(SB) + MOVD $1985, R12 + B callbackasm1(SB) + MOVD $1986, R12 + B callbackasm1(SB) + MOVD $1987, R12 + B callbackasm1(SB) + MOVD $1988, R12 + B callbackasm1(SB) + MOVD $1989, R12 + B callbackasm1(SB) + MOVD $1990, R12 + B callbackasm1(SB) + MOVD $1991, R12 + B callbackasm1(SB) + MOVD $1992, R12 + B callbackasm1(SB) + MOVD $1993, R12 + B callbackasm1(SB) + MOVD $1994, R12 + B callbackasm1(SB) + MOVD $1995, R12 + B callbackasm1(SB) + MOVD $1996, R12 + B callbackasm1(SB) + MOVD $1997, R12 + B callbackasm1(SB) + MOVD $1998, R12 + B callbackasm1(SB) + MOVD $1999, R12 + B callbackasm1(SB) diff --git a/vendor/github.com/ebitengine/purego/zcallback_loong64.s b/vendor/github.com/ebitengine/purego/zcallback_loong64.s new file mode 100644 index 00000000..e20c598a --- /dev/null +++ b/vendor/github.com/ebitengine/purego/zcallback_loong64.s @@ -0,0 +1,4014 @@ +// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. + +//go:build darwin || freebsd || linux || netbsd + +// External code calls into callbackasm at an offset corresponding +// to the callback index. Callbackasm is a table of MOVV and JMP instructions. +// The MOVV instruction loads R12 with the callback index, and the +// JMP instruction branches to callbackasm1. +// callbackasm1 takes the callback index from R12 and +// indexes into an array that stores information about each callback. +// It then calls the Go implementation for that callback. +#include "textflag.h" + +TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 + MOVV $0, R12 + JMP callbackasm1(SB) + MOVV $1, R12 + JMP callbackasm1(SB) + MOVV $2, R12 + JMP callbackasm1(SB) + MOVV $3, R12 + JMP callbackasm1(SB) + MOVV $4, R12 + JMP callbackasm1(SB) + MOVV $5, R12 + JMP callbackasm1(SB) + MOVV $6, R12 + JMP callbackasm1(SB) + MOVV $7, R12 + JMP callbackasm1(SB) + MOVV $8, R12 + JMP callbackasm1(SB) + MOVV $9, R12 + JMP callbackasm1(SB) + MOVV $10, R12 + JMP callbackasm1(SB) + MOVV $11, R12 + JMP callbackasm1(SB) + MOVV $12, R12 + JMP callbackasm1(SB) + MOVV $13, R12 + JMP callbackasm1(SB) + MOVV $14, R12 + JMP callbackasm1(SB) + MOVV $15, R12 + JMP callbackasm1(SB) + MOVV $16, R12 + JMP callbackasm1(SB) + MOVV $17, R12 + JMP callbackasm1(SB) + MOVV $18, R12 + JMP callbackasm1(SB) + MOVV $19, R12 + JMP callbackasm1(SB) + MOVV $20, R12 + JMP callbackasm1(SB) + MOVV $21, R12 + JMP callbackasm1(SB) + MOVV $22, R12 + JMP callbackasm1(SB) + MOVV $23, R12 + JMP callbackasm1(SB) + MOVV $24, R12 + JMP callbackasm1(SB) + MOVV $25, R12 + JMP callbackasm1(SB) + MOVV $26, R12 + JMP callbackasm1(SB) + MOVV $27, R12 + JMP callbackasm1(SB) + MOVV $28, R12 + JMP callbackasm1(SB) + MOVV $29, R12 + JMP callbackasm1(SB) + MOVV $30, R12 + JMP callbackasm1(SB) + MOVV $31, R12 + JMP callbackasm1(SB) + MOVV $32, R12 + JMP callbackasm1(SB) + MOVV $33, R12 + JMP callbackasm1(SB) + MOVV $34, R12 + JMP callbackasm1(SB) + MOVV $35, R12 + JMP callbackasm1(SB) + MOVV $36, R12 + JMP callbackasm1(SB) + MOVV $37, R12 + JMP callbackasm1(SB) + MOVV $38, R12 + JMP callbackasm1(SB) + MOVV $39, R12 + JMP callbackasm1(SB) + MOVV $40, R12 + JMP callbackasm1(SB) + MOVV $41, R12 + JMP callbackasm1(SB) + MOVV $42, R12 + JMP callbackasm1(SB) + MOVV $43, R12 + JMP callbackasm1(SB) + MOVV $44, R12 + JMP callbackasm1(SB) + MOVV $45, R12 + JMP callbackasm1(SB) + MOVV $46, R12 + JMP callbackasm1(SB) + MOVV $47, R12 + JMP callbackasm1(SB) + MOVV $48, R12 + JMP callbackasm1(SB) + MOVV $49, R12 + JMP callbackasm1(SB) + MOVV $50, R12 + JMP callbackasm1(SB) + MOVV $51, R12 + JMP callbackasm1(SB) + MOVV $52, R12 + JMP callbackasm1(SB) + MOVV $53, R12 + JMP callbackasm1(SB) + MOVV $54, R12 + JMP callbackasm1(SB) + MOVV $55, R12 + JMP callbackasm1(SB) + MOVV $56, R12 + JMP callbackasm1(SB) + MOVV $57, R12 + JMP callbackasm1(SB) + MOVV $58, R12 + JMP callbackasm1(SB) + MOVV $59, R12 + JMP callbackasm1(SB) + MOVV $60, R12 + JMP callbackasm1(SB) + MOVV $61, R12 + JMP callbackasm1(SB) + MOVV $62, R12 + JMP callbackasm1(SB) + MOVV $63, R12 + JMP callbackasm1(SB) + MOVV $64, R12 + JMP callbackasm1(SB) + MOVV $65, R12 + JMP callbackasm1(SB) + MOVV $66, R12 + JMP callbackasm1(SB) + MOVV $67, R12 + JMP callbackasm1(SB) + MOVV $68, R12 + JMP callbackasm1(SB) + MOVV $69, R12 + JMP callbackasm1(SB) + MOVV $70, R12 + JMP callbackasm1(SB) + MOVV $71, R12 + JMP callbackasm1(SB) + MOVV $72, R12 + JMP callbackasm1(SB) + MOVV $73, R12 + JMP callbackasm1(SB) + MOVV $74, R12 + JMP callbackasm1(SB) + MOVV $75, R12 + JMP callbackasm1(SB) + MOVV $76, R12 + JMP callbackasm1(SB) + MOVV $77, R12 + JMP callbackasm1(SB) + MOVV $78, R12 + JMP callbackasm1(SB) + MOVV $79, R12 + JMP callbackasm1(SB) + MOVV $80, R12 + JMP callbackasm1(SB) + MOVV $81, R12 + JMP callbackasm1(SB) + MOVV $82, R12 + JMP callbackasm1(SB) + MOVV $83, R12 + JMP callbackasm1(SB) + MOVV $84, R12 + JMP callbackasm1(SB) + MOVV $85, R12 + JMP callbackasm1(SB) + MOVV $86, R12 + JMP callbackasm1(SB) + MOVV $87, R12 + JMP callbackasm1(SB) + MOVV $88, R12 + JMP callbackasm1(SB) + MOVV $89, R12 + JMP callbackasm1(SB) + MOVV $90, R12 + JMP callbackasm1(SB) + MOVV $91, R12 + JMP callbackasm1(SB) + MOVV $92, R12 + JMP callbackasm1(SB) + MOVV $93, R12 + JMP callbackasm1(SB) + MOVV $94, R12 + JMP callbackasm1(SB) + MOVV $95, R12 + JMP callbackasm1(SB) + MOVV $96, R12 + JMP callbackasm1(SB) + MOVV $97, R12 + JMP callbackasm1(SB) + MOVV $98, R12 + JMP callbackasm1(SB) + MOVV $99, R12 + JMP callbackasm1(SB) + MOVV $100, R12 + JMP callbackasm1(SB) + MOVV $101, R12 + JMP callbackasm1(SB) + MOVV $102, R12 + JMP callbackasm1(SB) + MOVV $103, R12 + JMP callbackasm1(SB) + MOVV $104, R12 + JMP callbackasm1(SB) + MOVV $105, R12 + JMP callbackasm1(SB) + MOVV $106, R12 + JMP callbackasm1(SB) + MOVV $107, R12 + JMP callbackasm1(SB) + MOVV $108, R12 + JMP callbackasm1(SB) + MOVV $109, R12 + JMP callbackasm1(SB) + MOVV $110, R12 + JMP callbackasm1(SB) + MOVV $111, R12 + JMP callbackasm1(SB) + MOVV $112, R12 + JMP callbackasm1(SB) + MOVV $113, R12 + JMP callbackasm1(SB) + MOVV $114, R12 + JMP callbackasm1(SB) + MOVV $115, R12 + JMP callbackasm1(SB) + MOVV $116, R12 + JMP callbackasm1(SB) + MOVV $117, R12 + JMP callbackasm1(SB) + MOVV $118, R12 + JMP callbackasm1(SB) + MOVV $119, R12 + JMP callbackasm1(SB) + MOVV $120, R12 + JMP callbackasm1(SB) + MOVV $121, R12 + JMP callbackasm1(SB) + MOVV $122, R12 + JMP callbackasm1(SB) + MOVV $123, R12 + JMP callbackasm1(SB) + MOVV $124, R12 + JMP callbackasm1(SB) + MOVV $125, R12 + JMP callbackasm1(SB) + MOVV $126, R12 + JMP callbackasm1(SB) + MOVV $127, R12 + JMP callbackasm1(SB) + MOVV $128, R12 + JMP callbackasm1(SB) + MOVV $129, R12 + JMP callbackasm1(SB) + MOVV $130, R12 + JMP callbackasm1(SB) + MOVV $131, R12 + JMP callbackasm1(SB) + MOVV $132, R12 + JMP callbackasm1(SB) + MOVV $133, R12 + JMP callbackasm1(SB) + MOVV $134, R12 + JMP callbackasm1(SB) + MOVV $135, R12 + JMP callbackasm1(SB) + MOVV $136, R12 + JMP callbackasm1(SB) + MOVV $137, R12 + JMP callbackasm1(SB) + MOVV $138, R12 + JMP callbackasm1(SB) + MOVV $139, R12 + JMP callbackasm1(SB) + MOVV $140, R12 + JMP callbackasm1(SB) + MOVV $141, R12 + JMP callbackasm1(SB) + MOVV $142, R12 + JMP callbackasm1(SB) + MOVV $143, R12 + JMP callbackasm1(SB) + MOVV $144, R12 + JMP callbackasm1(SB) + MOVV $145, R12 + JMP callbackasm1(SB) + MOVV $146, R12 + JMP callbackasm1(SB) + MOVV $147, R12 + JMP callbackasm1(SB) + MOVV $148, R12 + JMP callbackasm1(SB) + MOVV $149, R12 + JMP callbackasm1(SB) + MOVV $150, R12 + JMP callbackasm1(SB) + MOVV $151, R12 + JMP callbackasm1(SB) + MOVV $152, R12 + JMP callbackasm1(SB) + MOVV $153, R12 + JMP callbackasm1(SB) + MOVV $154, R12 + JMP callbackasm1(SB) + MOVV $155, R12 + JMP callbackasm1(SB) + MOVV $156, R12 + JMP callbackasm1(SB) + MOVV $157, R12 + JMP callbackasm1(SB) + MOVV $158, R12 + JMP callbackasm1(SB) + MOVV $159, R12 + JMP callbackasm1(SB) + MOVV $160, R12 + JMP callbackasm1(SB) + MOVV $161, R12 + JMP callbackasm1(SB) + MOVV $162, R12 + JMP callbackasm1(SB) + MOVV $163, R12 + JMP callbackasm1(SB) + MOVV $164, R12 + JMP callbackasm1(SB) + MOVV $165, R12 + JMP callbackasm1(SB) + MOVV $166, R12 + JMP callbackasm1(SB) + MOVV $167, R12 + JMP callbackasm1(SB) + MOVV $168, R12 + JMP callbackasm1(SB) + MOVV $169, R12 + JMP callbackasm1(SB) + MOVV $170, R12 + JMP callbackasm1(SB) + MOVV $171, R12 + JMP callbackasm1(SB) + MOVV $172, R12 + JMP callbackasm1(SB) + MOVV $173, R12 + JMP callbackasm1(SB) + MOVV $174, R12 + JMP callbackasm1(SB) + MOVV $175, R12 + JMP callbackasm1(SB) + MOVV $176, R12 + JMP callbackasm1(SB) + MOVV $177, R12 + JMP callbackasm1(SB) + MOVV $178, R12 + JMP callbackasm1(SB) + MOVV $179, R12 + JMP callbackasm1(SB) + MOVV $180, R12 + JMP callbackasm1(SB) + MOVV $181, R12 + JMP callbackasm1(SB) + MOVV $182, R12 + JMP callbackasm1(SB) + MOVV $183, R12 + JMP callbackasm1(SB) + MOVV $184, R12 + JMP callbackasm1(SB) + MOVV $185, R12 + JMP callbackasm1(SB) + MOVV $186, R12 + JMP callbackasm1(SB) + MOVV $187, R12 + JMP callbackasm1(SB) + MOVV $188, R12 + JMP callbackasm1(SB) + MOVV $189, R12 + JMP callbackasm1(SB) + MOVV $190, R12 + JMP callbackasm1(SB) + MOVV $191, R12 + JMP callbackasm1(SB) + MOVV $192, R12 + JMP callbackasm1(SB) + MOVV $193, R12 + JMP callbackasm1(SB) + MOVV $194, R12 + JMP callbackasm1(SB) + MOVV $195, R12 + JMP callbackasm1(SB) + MOVV $196, R12 + JMP callbackasm1(SB) + MOVV $197, R12 + JMP callbackasm1(SB) + MOVV $198, R12 + JMP callbackasm1(SB) + MOVV $199, R12 + JMP callbackasm1(SB) + MOVV $200, R12 + JMP callbackasm1(SB) + MOVV $201, R12 + JMP callbackasm1(SB) + MOVV $202, R12 + JMP callbackasm1(SB) + MOVV $203, R12 + JMP callbackasm1(SB) + MOVV $204, R12 + JMP callbackasm1(SB) + MOVV $205, R12 + JMP callbackasm1(SB) + MOVV $206, R12 + JMP callbackasm1(SB) + MOVV $207, R12 + JMP callbackasm1(SB) + MOVV $208, R12 + JMP callbackasm1(SB) + MOVV $209, R12 + JMP callbackasm1(SB) + MOVV $210, R12 + JMP callbackasm1(SB) + MOVV $211, R12 + JMP callbackasm1(SB) + MOVV $212, R12 + JMP callbackasm1(SB) + MOVV $213, R12 + JMP callbackasm1(SB) + MOVV $214, R12 + JMP callbackasm1(SB) + MOVV $215, R12 + JMP callbackasm1(SB) + MOVV $216, R12 + JMP callbackasm1(SB) + MOVV $217, R12 + JMP callbackasm1(SB) + MOVV $218, R12 + JMP callbackasm1(SB) + MOVV $219, R12 + JMP callbackasm1(SB) + MOVV $220, R12 + JMP callbackasm1(SB) + MOVV $221, R12 + JMP callbackasm1(SB) + MOVV $222, R12 + JMP callbackasm1(SB) + MOVV $223, R12 + JMP callbackasm1(SB) + MOVV $224, R12 + JMP callbackasm1(SB) + MOVV $225, R12 + JMP callbackasm1(SB) + MOVV $226, R12 + JMP callbackasm1(SB) + MOVV $227, R12 + JMP callbackasm1(SB) + MOVV $228, R12 + JMP callbackasm1(SB) + MOVV $229, R12 + JMP callbackasm1(SB) + MOVV $230, R12 + JMP callbackasm1(SB) + MOVV $231, R12 + JMP callbackasm1(SB) + MOVV $232, R12 + JMP callbackasm1(SB) + MOVV $233, R12 + JMP callbackasm1(SB) + MOVV $234, R12 + JMP callbackasm1(SB) + MOVV $235, R12 + JMP callbackasm1(SB) + MOVV $236, R12 + JMP callbackasm1(SB) + MOVV $237, R12 + JMP callbackasm1(SB) + MOVV $238, R12 + JMP callbackasm1(SB) + MOVV $239, R12 + JMP callbackasm1(SB) + MOVV $240, R12 + JMP callbackasm1(SB) + MOVV $241, R12 + JMP callbackasm1(SB) + MOVV $242, R12 + JMP callbackasm1(SB) + MOVV $243, R12 + JMP callbackasm1(SB) + MOVV $244, R12 + JMP callbackasm1(SB) + MOVV $245, R12 + JMP callbackasm1(SB) + MOVV $246, R12 + JMP callbackasm1(SB) + MOVV $247, R12 + JMP callbackasm1(SB) + MOVV $248, R12 + JMP callbackasm1(SB) + MOVV $249, R12 + JMP callbackasm1(SB) + MOVV $250, R12 + JMP callbackasm1(SB) + MOVV $251, R12 + JMP callbackasm1(SB) + MOVV $252, R12 + JMP callbackasm1(SB) + MOVV $253, R12 + JMP callbackasm1(SB) + MOVV $254, R12 + JMP callbackasm1(SB) + MOVV $255, R12 + JMP callbackasm1(SB) + MOVV $256, R12 + JMP callbackasm1(SB) + MOVV $257, R12 + JMP callbackasm1(SB) + MOVV $258, R12 + JMP callbackasm1(SB) + MOVV $259, R12 + JMP callbackasm1(SB) + MOVV $260, R12 + JMP callbackasm1(SB) + MOVV $261, R12 + JMP callbackasm1(SB) + MOVV $262, R12 + JMP callbackasm1(SB) + MOVV $263, R12 + JMP callbackasm1(SB) + MOVV $264, R12 + JMP callbackasm1(SB) + MOVV $265, R12 + JMP callbackasm1(SB) + MOVV $266, R12 + JMP callbackasm1(SB) + MOVV $267, R12 + JMP callbackasm1(SB) + MOVV $268, R12 + JMP callbackasm1(SB) + MOVV $269, R12 + JMP callbackasm1(SB) + MOVV $270, R12 + JMP callbackasm1(SB) + MOVV $271, R12 + JMP callbackasm1(SB) + MOVV $272, R12 + JMP callbackasm1(SB) + MOVV $273, R12 + JMP callbackasm1(SB) + MOVV $274, R12 + JMP callbackasm1(SB) + MOVV $275, R12 + JMP callbackasm1(SB) + MOVV $276, R12 + JMP callbackasm1(SB) + MOVV $277, R12 + JMP callbackasm1(SB) + MOVV $278, R12 + JMP callbackasm1(SB) + MOVV $279, R12 + JMP callbackasm1(SB) + MOVV $280, R12 + JMP callbackasm1(SB) + MOVV $281, R12 + JMP callbackasm1(SB) + MOVV $282, R12 + JMP callbackasm1(SB) + MOVV $283, R12 + JMP callbackasm1(SB) + MOVV $284, R12 + JMP callbackasm1(SB) + MOVV $285, R12 + JMP callbackasm1(SB) + MOVV $286, R12 + JMP callbackasm1(SB) + MOVV $287, R12 + JMP callbackasm1(SB) + MOVV $288, R12 + JMP callbackasm1(SB) + MOVV $289, R12 + JMP callbackasm1(SB) + MOVV $290, R12 + JMP callbackasm1(SB) + MOVV $291, R12 + JMP callbackasm1(SB) + MOVV $292, R12 + JMP callbackasm1(SB) + MOVV $293, R12 + JMP callbackasm1(SB) + MOVV $294, R12 + JMP callbackasm1(SB) + MOVV $295, R12 + JMP callbackasm1(SB) + MOVV $296, R12 + JMP callbackasm1(SB) + MOVV $297, R12 + JMP callbackasm1(SB) + MOVV $298, R12 + JMP callbackasm1(SB) + MOVV $299, R12 + JMP callbackasm1(SB) + MOVV $300, R12 + JMP callbackasm1(SB) + MOVV $301, R12 + JMP callbackasm1(SB) + MOVV $302, R12 + JMP callbackasm1(SB) + MOVV $303, R12 + JMP callbackasm1(SB) + MOVV $304, R12 + JMP callbackasm1(SB) + MOVV $305, R12 + JMP callbackasm1(SB) + MOVV $306, R12 + JMP callbackasm1(SB) + MOVV $307, R12 + JMP callbackasm1(SB) + MOVV $308, R12 + JMP callbackasm1(SB) + MOVV $309, R12 + JMP callbackasm1(SB) + MOVV $310, R12 + JMP callbackasm1(SB) + MOVV $311, R12 + JMP callbackasm1(SB) + MOVV $312, R12 + JMP callbackasm1(SB) + MOVV $313, R12 + JMP callbackasm1(SB) + MOVV $314, R12 + JMP callbackasm1(SB) + MOVV $315, R12 + JMP callbackasm1(SB) + MOVV $316, R12 + JMP callbackasm1(SB) + MOVV $317, R12 + JMP callbackasm1(SB) + MOVV $318, R12 + JMP callbackasm1(SB) + MOVV $319, R12 + JMP callbackasm1(SB) + MOVV $320, R12 + JMP callbackasm1(SB) + MOVV $321, R12 + JMP callbackasm1(SB) + MOVV $322, R12 + JMP callbackasm1(SB) + MOVV $323, R12 + JMP callbackasm1(SB) + MOVV $324, R12 + JMP callbackasm1(SB) + MOVV $325, R12 + JMP callbackasm1(SB) + MOVV $326, R12 + JMP callbackasm1(SB) + MOVV $327, R12 + JMP callbackasm1(SB) + MOVV $328, R12 + JMP callbackasm1(SB) + MOVV $329, R12 + JMP callbackasm1(SB) + MOVV $330, R12 + JMP callbackasm1(SB) + MOVV $331, R12 + JMP callbackasm1(SB) + MOVV $332, R12 + JMP callbackasm1(SB) + MOVV $333, R12 + JMP callbackasm1(SB) + MOVV $334, R12 + JMP callbackasm1(SB) + MOVV $335, R12 + JMP callbackasm1(SB) + MOVV $336, R12 + JMP callbackasm1(SB) + MOVV $337, R12 + JMP callbackasm1(SB) + MOVV $338, R12 + JMP callbackasm1(SB) + MOVV $339, R12 + JMP callbackasm1(SB) + MOVV $340, R12 + JMP callbackasm1(SB) + MOVV $341, R12 + JMP callbackasm1(SB) + MOVV $342, R12 + JMP callbackasm1(SB) + MOVV $343, R12 + JMP callbackasm1(SB) + MOVV $344, R12 + JMP callbackasm1(SB) + MOVV $345, R12 + JMP callbackasm1(SB) + MOVV $346, R12 + JMP callbackasm1(SB) + MOVV $347, R12 + JMP callbackasm1(SB) + MOVV $348, R12 + JMP callbackasm1(SB) + MOVV $349, R12 + JMP callbackasm1(SB) + MOVV $350, R12 + JMP callbackasm1(SB) + MOVV $351, R12 + JMP callbackasm1(SB) + MOVV $352, R12 + JMP callbackasm1(SB) + MOVV $353, R12 + JMP callbackasm1(SB) + MOVV $354, R12 + JMP callbackasm1(SB) + MOVV $355, R12 + JMP callbackasm1(SB) + MOVV $356, R12 + JMP callbackasm1(SB) + MOVV $357, R12 + JMP callbackasm1(SB) + MOVV $358, R12 + JMP callbackasm1(SB) + MOVV $359, R12 + JMP callbackasm1(SB) + MOVV $360, R12 + JMP callbackasm1(SB) + MOVV $361, R12 + JMP callbackasm1(SB) + MOVV $362, R12 + JMP callbackasm1(SB) + MOVV $363, R12 + JMP callbackasm1(SB) + MOVV $364, R12 + JMP callbackasm1(SB) + MOVV $365, R12 + JMP callbackasm1(SB) + MOVV $366, R12 + JMP callbackasm1(SB) + MOVV $367, R12 + JMP callbackasm1(SB) + MOVV $368, R12 + JMP callbackasm1(SB) + MOVV $369, R12 + JMP callbackasm1(SB) + MOVV $370, R12 + JMP callbackasm1(SB) + MOVV $371, R12 + JMP callbackasm1(SB) + MOVV $372, R12 + JMP callbackasm1(SB) + MOVV $373, R12 + JMP callbackasm1(SB) + MOVV $374, R12 + JMP callbackasm1(SB) + MOVV $375, R12 + JMP callbackasm1(SB) + MOVV $376, R12 + JMP callbackasm1(SB) + MOVV $377, R12 + JMP callbackasm1(SB) + MOVV $378, R12 + JMP callbackasm1(SB) + MOVV $379, R12 + JMP callbackasm1(SB) + MOVV $380, R12 + JMP callbackasm1(SB) + MOVV $381, R12 + JMP callbackasm1(SB) + MOVV $382, R12 + JMP callbackasm1(SB) + MOVV $383, R12 + JMP callbackasm1(SB) + MOVV $384, R12 + JMP callbackasm1(SB) + MOVV $385, R12 + JMP callbackasm1(SB) + MOVV $386, R12 + JMP callbackasm1(SB) + MOVV $387, R12 + JMP callbackasm1(SB) + MOVV $388, R12 + JMP callbackasm1(SB) + MOVV $389, R12 + JMP callbackasm1(SB) + MOVV $390, R12 + JMP callbackasm1(SB) + MOVV $391, R12 + JMP callbackasm1(SB) + MOVV $392, R12 + JMP callbackasm1(SB) + MOVV $393, R12 + JMP callbackasm1(SB) + MOVV $394, R12 + JMP callbackasm1(SB) + MOVV $395, R12 + JMP callbackasm1(SB) + MOVV $396, R12 + JMP callbackasm1(SB) + MOVV $397, R12 + JMP callbackasm1(SB) + MOVV $398, R12 + JMP callbackasm1(SB) + MOVV $399, R12 + JMP callbackasm1(SB) + MOVV $400, R12 + JMP callbackasm1(SB) + MOVV $401, R12 + JMP callbackasm1(SB) + MOVV $402, R12 + JMP callbackasm1(SB) + MOVV $403, R12 + JMP callbackasm1(SB) + MOVV $404, R12 + JMP callbackasm1(SB) + MOVV $405, R12 + JMP callbackasm1(SB) + MOVV $406, R12 + JMP callbackasm1(SB) + MOVV $407, R12 + JMP callbackasm1(SB) + MOVV $408, R12 + JMP callbackasm1(SB) + MOVV $409, R12 + JMP callbackasm1(SB) + MOVV $410, R12 + JMP callbackasm1(SB) + MOVV $411, R12 + JMP callbackasm1(SB) + MOVV $412, R12 + JMP callbackasm1(SB) + MOVV $413, R12 + JMP callbackasm1(SB) + MOVV $414, R12 + JMP callbackasm1(SB) + MOVV $415, R12 + JMP callbackasm1(SB) + MOVV $416, R12 + JMP callbackasm1(SB) + MOVV $417, R12 + JMP callbackasm1(SB) + MOVV $418, R12 + JMP callbackasm1(SB) + MOVV $419, R12 + JMP callbackasm1(SB) + MOVV $420, R12 + JMP callbackasm1(SB) + MOVV $421, R12 + JMP callbackasm1(SB) + MOVV $422, R12 + JMP callbackasm1(SB) + MOVV $423, R12 + JMP callbackasm1(SB) + MOVV $424, R12 + JMP callbackasm1(SB) + MOVV $425, R12 + JMP callbackasm1(SB) + MOVV $426, R12 + JMP callbackasm1(SB) + MOVV $427, R12 + JMP callbackasm1(SB) + MOVV $428, R12 + JMP callbackasm1(SB) + MOVV $429, R12 + JMP callbackasm1(SB) + MOVV $430, R12 + JMP callbackasm1(SB) + MOVV $431, R12 + JMP callbackasm1(SB) + MOVV $432, R12 + JMP callbackasm1(SB) + MOVV $433, R12 + JMP callbackasm1(SB) + MOVV $434, R12 + JMP callbackasm1(SB) + MOVV $435, R12 + JMP callbackasm1(SB) + MOVV $436, R12 + JMP callbackasm1(SB) + MOVV $437, R12 + JMP callbackasm1(SB) + MOVV $438, R12 + JMP callbackasm1(SB) + MOVV $439, R12 + JMP callbackasm1(SB) + MOVV $440, R12 + JMP callbackasm1(SB) + MOVV $441, R12 + JMP callbackasm1(SB) + MOVV $442, R12 + JMP callbackasm1(SB) + MOVV $443, R12 + JMP callbackasm1(SB) + MOVV $444, R12 + JMP callbackasm1(SB) + MOVV $445, R12 + JMP callbackasm1(SB) + MOVV $446, R12 + JMP callbackasm1(SB) + MOVV $447, R12 + JMP callbackasm1(SB) + MOVV $448, R12 + JMP callbackasm1(SB) + MOVV $449, R12 + JMP callbackasm1(SB) + MOVV $450, R12 + JMP callbackasm1(SB) + MOVV $451, R12 + JMP callbackasm1(SB) + MOVV $452, R12 + JMP callbackasm1(SB) + MOVV $453, R12 + JMP callbackasm1(SB) + MOVV $454, R12 + JMP callbackasm1(SB) + MOVV $455, R12 + JMP callbackasm1(SB) + MOVV $456, R12 + JMP callbackasm1(SB) + MOVV $457, R12 + JMP callbackasm1(SB) + MOVV $458, R12 + JMP callbackasm1(SB) + MOVV $459, R12 + JMP callbackasm1(SB) + MOVV $460, R12 + JMP callbackasm1(SB) + MOVV $461, R12 + JMP callbackasm1(SB) + MOVV $462, R12 + JMP callbackasm1(SB) + MOVV $463, R12 + JMP callbackasm1(SB) + MOVV $464, R12 + JMP callbackasm1(SB) + MOVV $465, R12 + JMP callbackasm1(SB) + MOVV $466, R12 + JMP callbackasm1(SB) + MOVV $467, R12 + JMP callbackasm1(SB) + MOVV $468, R12 + JMP callbackasm1(SB) + MOVV $469, R12 + JMP callbackasm1(SB) + MOVV $470, R12 + JMP callbackasm1(SB) + MOVV $471, R12 + JMP callbackasm1(SB) + MOVV $472, R12 + JMP callbackasm1(SB) + MOVV $473, R12 + JMP callbackasm1(SB) + MOVV $474, R12 + JMP callbackasm1(SB) + MOVV $475, R12 + JMP callbackasm1(SB) + MOVV $476, R12 + JMP callbackasm1(SB) + MOVV $477, R12 + JMP callbackasm1(SB) + MOVV $478, R12 + JMP callbackasm1(SB) + MOVV $479, R12 + JMP callbackasm1(SB) + MOVV $480, R12 + JMP callbackasm1(SB) + MOVV $481, R12 + JMP callbackasm1(SB) + MOVV $482, R12 + JMP callbackasm1(SB) + MOVV $483, R12 + JMP callbackasm1(SB) + MOVV $484, R12 + JMP callbackasm1(SB) + MOVV $485, R12 + JMP callbackasm1(SB) + MOVV $486, R12 + JMP callbackasm1(SB) + MOVV $487, R12 + JMP callbackasm1(SB) + MOVV $488, R12 + JMP callbackasm1(SB) + MOVV $489, R12 + JMP callbackasm1(SB) + MOVV $490, R12 + JMP callbackasm1(SB) + MOVV $491, R12 + JMP callbackasm1(SB) + MOVV $492, R12 + JMP callbackasm1(SB) + MOVV $493, R12 + JMP callbackasm1(SB) + MOVV $494, R12 + JMP callbackasm1(SB) + MOVV $495, R12 + JMP callbackasm1(SB) + MOVV $496, R12 + JMP callbackasm1(SB) + MOVV $497, R12 + JMP callbackasm1(SB) + MOVV $498, R12 + JMP callbackasm1(SB) + MOVV $499, R12 + JMP callbackasm1(SB) + MOVV $500, R12 + JMP callbackasm1(SB) + MOVV $501, R12 + JMP callbackasm1(SB) + MOVV $502, R12 + JMP callbackasm1(SB) + MOVV $503, R12 + JMP callbackasm1(SB) + MOVV $504, R12 + JMP callbackasm1(SB) + MOVV $505, R12 + JMP callbackasm1(SB) + MOVV $506, R12 + JMP callbackasm1(SB) + MOVV $507, R12 + JMP callbackasm1(SB) + MOVV $508, R12 + JMP callbackasm1(SB) + MOVV $509, R12 + JMP callbackasm1(SB) + MOVV $510, R12 + JMP callbackasm1(SB) + MOVV $511, R12 + JMP callbackasm1(SB) + MOVV $512, R12 + JMP callbackasm1(SB) + MOVV $513, R12 + JMP callbackasm1(SB) + MOVV $514, R12 + JMP callbackasm1(SB) + MOVV $515, R12 + JMP callbackasm1(SB) + MOVV $516, R12 + JMP callbackasm1(SB) + MOVV $517, R12 + JMP callbackasm1(SB) + MOVV $518, R12 + JMP callbackasm1(SB) + MOVV $519, R12 + JMP callbackasm1(SB) + MOVV $520, R12 + JMP callbackasm1(SB) + MOVV $521, R12 + JMP callbackasm1(SB) + MOVV $522, R12 + JMP callbackasm1(SB) + MOVV $523, R12 + JMP callbackasm1(SB) + MOVV $524, R12 + JMP callbackasm1(SB) + MOVV $525, R12 + JMP callbackasm1(SB) + MOVV $526, R12 + JMP callbackasm1(SB) + MOVV $527, R12 + JMP callbackasm1(SB) + MOVV $528, R12 + JMP callbackasm1(SB) + MOVV $529, R12 + JMP callbackasm1(SB) + MOVV $530, R12 + JMP callbackasm1(SB) + MOVV $531, R12 + JMP callbackasm1(SB) + MOVV $532, R12 + JMP callbackasm1(SB) + MOVV $533, R12 + JMP callbackasm1(SB) + MOVV $534, R12 + JMP callbackasm1(SB) + MOVV $535, R12 + JMP callbackasm1(SB) + MOVV $536, R12 + JMP callbackasm1(SB) + MOVV $537, R12 + JMP callbackasm1(SB) + MOVV $538, R12 + JMP callbackasm1(SB) + MOVV $539, R12 + JMP callbackasm1(SB) + MOVV $540, R12 + JMP callbackasm1(SB) + MOVV $541, R12 + JMP callbackasm1(SB) + MOVV $542, R12 + JMP callbackasm1(SB) + MOVV $543, R12 + JMP callbackasm1(SB) + MOVV $544, R12 + JMP callbackasm1(SB) + MOVV $545, R12 + JMP callbackasm1(SB) + MOVV $546, R12 + JMP callbackasm1(SB) + MOVV $547, R12 + JMP callbackasm1(SB) + MOVV $548, R12 + JMP callbackasm1(SB) + MOVV $549, R12 + JMP callbackasm1(SB) + MOVV $550, R12 + JMP callbackasm1(SB) + MOVV $551, R12 + JMP callbackasm1(SB) + MOVV $552, R12 + JMP callbackasm1(SB) + MOVV $553, R12 + JMP callbackasm1(SB) + MOVV $554, R12 + JMP callbackasm1(SB) + MOVV $555, R12 + JMP callbackasm1(SB) + MOVV $556, R12 + JMP callbackasm1(SB) + MOVV $557, R12 + JMP callbackasm1(SB) + MOVV $558, R12 + JMP callbackasm1(SB) + MOVV $559, R12 + JMP callbackasm1(SB) + MOVV $560, R12 + JMP callbackasm1(SB) + MOVV $561, R12 + JMP callbackasm1(SB) + MOVV $562, R12 + JMP callbackasm1(SB) + MOVV $563, R12 + JMP callbackasm1(SB) + MOVV $564, R12 + JMP callbackasm1(SB) + MOVV $565, R12 + JMP callbackasm1(SB) + MOVV $566, R12 + JMP callbackasm1(SB) + MOVV $567, R12 + JMP callbackasm1(SB) + MOVV $568, R12 + JMP callbackasm1(SB) + MOVV $569, R12 + JMP callbackasm1(SB) + MOVV $570, R12 + JMP callbackasm1(SB) + MOVV $571, R12 + JMP callbackasm1(SB) + MOVV $572, R12 + JMP callbackasm1(SB) + MOVV $573, R12 + JMP callbackasm1(SB) + MOVV $574, R12 + JMP callbackasm1(SB) + MOVV $575, R12 + JMP callbackasm1(SB) + MOVV $576, R12 + JMP callbackasm1(SB) + MOVV $577, R12 + JMP callbackasm1(SB) + MOVV $578, R12 + JMP callbackasm1(SB) + MOVV $579, R12 + JMP callbackasm1(SB) + MOVV $580, R12 + JMP callbackasm1(SB) + MOVV $581, R12 + JMP callbackasm1(SB) + MOVV $582, R12 + JMP callbackasm1(SB) + MOVV $583, R12 + JMP callbackasm1(SB) + MOVV $584, R12 + JMP callbackasm1(SB) + MOVV $585, R12 + JMP callbackasm1(SB) + MOVV $586, R12 + JMP callbackasm1(SB) + MOVV $587, R12 + JMP callbackasm1(SB) + MOVV $588, R12 + JMP callbackasm1(SB) + MOVV $589, R12 + JMP callbackasm1(SB) + MOVV $590, R12 + JMP callbackasm1(SB) + MOVV $591, R12 + JMP callbackasm1(SB) + MOVV $592, R12 + JMP callbackasm1(SB) + MOVV $593, R12 + JMP callbackasm1(SB) + MOVV $594, R12 + JMP callbackasm1(SB) + MOVV $595, R12 + JMP callbackasm1(SB) + MOVV $596, R12 + JMP callbackasm1(SB) + MOVV $597, R12 + JMP callbackasm1(SB) + MOVV $598, R12 + JMP callbackasm1(SB) + MOVV $599, R12 + JMP callbackasm1(SB) + MOVV $600, R12 + JMP callbackasm1(SB) + MOVV $601, R12 + JMP callbackasm1(SB) + MOVV $602, R12 + JMP callbackasm1(SB) + MOVV $603, R12 + JMP callbackasm1(SB) + MOVV $604, R12 + JMP callbackasm1(SB) + MOVV $605, R12 + JMP callbackasm1(SB) + MOVV $606, R12 + JMP callbackasm1(SB) + MOVV $607, R12 + JMP callbackasm1(SB) + MOVV $608, R12 + JMP callbackasm1(SB) + MOVV $609, R12 + JMP callbackasm1(SB) + MOVV $610, R12 + JMP callbackasm1(SB) + MOVV $611, R12 + JMP callbackasm1(SB) + MOVV $612, R12 + JMP callbackasm1(SB) + MOVV $613, R12 + JMP callbackasm1(SB) + MOVV $614, R12 + JMP callbackasm1(SB) + MOVV $615, R12 + JMP callbackasm1(SB) + MOVV $616, R12 + JMP callbackasm1(SB) + MOVV $617, R12 + JMP callbackasm1(SB) + MOVV $618, R12 + JMP callbackasm1(SB) + MOVV $619, R12 + JMP callbackasm1(SB) + MOVV $620, R12 + JMP callbackasm1(SB) + MOVV $621, R12 + JMP callbackasm1(SB) + MOVV $622, R12 + JMP callbackasm1(SB) + MOVV $623, R12 + JMP callbackasm1(SB) + MOVV $624, R12 + JMP callbackasm1(SB) + MOVV $625, R12 + JMP callbackasm1(SB) + MOVV $626, R12 + JMP callbackasm1(SB) + MOVV $627, R12 + JMP callbackasm1(SB) + MOVV $628, R12 + JMP callbackasm1(SB) + MOVV $629, R12 + JMP callbackasm1(SB) + MOVV $630, R12 + JMP callbackasm1(SB) + MOVV $631, R12 + JMP callbackasm1(SB) + MOVV $632, R12 + JMP callbackasm1(SB) + MOVV $633, R12 + JMP callbackasm1(SB) + MOVV $634, R12 + JMP callbackasm1(SB) + MOVV $635, R12 + JMP callbackasm1(SB) + MOVV $636, R12 + JMP callbackasm1(SB) + MOVV $637, R12 + JMP callbackasm1(SB) + MOVV $638, R12 + JMP callbackasm1(SB) + MOVV $639, R12 + JMP callbackasm1(SB) + MOVV $640, R12 + JMP callbackasm1(SB) + MOVV $641, R12 + JMP callbackasm1(SB) + MOVV $642, R12 + JMP callbackasm1(SB) + MOVV $643, R12 + JMP callbackasm1(SB) + MOVV $644, R12 + JMP callbackasm1(SB) + MOVV $645, R12 + JMP callbackasm1(SB) + MOVV $646, R12 + JMP callbackasm1(SB) + MOVV $647, R12 + JMP callbackasm1(SB) + MOVV $648, R12 + JMP callbackasm1(SB) + MOVV $649, R12 + JMP callbackasm1(SB) + MOVV $650, R12 + JMP callbackasm1(SB) + MOVV $651, R12 + JMP callbackasm1(SB) + MOVV $652, R12 + JMP callbackasm1(SB) + MOVV $653, R12 + JMP callbackasm1(SB) + MOVV $654, R12 + JMP callbackasm1(SB) + MOVV $655, R12 + JMP callbackasm1(SB) + MOVV $656, R12 + JMP callbackasm1(SB) + MOVV $657, R12 + JMP callbackasm1(SB) + MOVV $658, R12 + JMP callbackasm1(SB) + MOVV $659, R12 + JMP callbackasm1(SB) + MOVV $660, R12 + JMP callbackasm1(SB) + MOVV $661, R12 + JMP callbackasm1(SB) + MOVV $662, R12 + JMP callbackasm1(SB) + MOVV $663, R12 + JMP callbackasm1(SB) + MOVV $664, R12 + JMP callbackasm1(SB) + MOVV $665, R12 + JMP callbackasm1(SB) + MOVV $666, R12 + JMP callbackasm1(SB) + MOVV $667, R12 + JMP callbackasm1(SB) + MOVV $668, R12 + JMP callbackasm1(SB) + MOVV $669, R12 + JMP callbackasm1(SB) + MOVV $670, R12 + JMP callbackasm1(SB) + MOVV $671, R12 + JMP callbackasm1(SB) + MOVV $672, R12 + JMP callbackasm1(SB) + MOVV $673, R12 + JMP callbackasm1(SB) + MOVV $674, R12 + JMP callbackasm1(SB) + MOVV $675, R12 + JMP callbackasm1(SB) + MOVV $676, R12 + JMP callbackasm1(SB) + MOVV $677, R12 + JMP callbackasm1(SB) + MOVV $678, R12 + JMP callbackasm1(SB) + MOVV $679, R12 + JMP callbackasm1(SB) + MOVV $680, R12 + JMP callbackasm1(SB) + MOVV $681, R12 + JMP callbackasm1(SB) + MOVV $682, R12 + JMP callbackasm1(SB) + MOVV $683, R12 + JMP callbackasm1(SB) + MOVV $684, R12 + JMP callbackasm1(SB) + MOVV $685, R12 + JMP callbackasm1(SB) + MOVV $686, R12 + JMP callbackasm1(SB) + MOVV $687, R12 + JMP callbackasm1(SB) + MOVV $688, R12 + JMP callbackasm1(SB) + MOVV $689, R12 + JMP callbackasm1(SB) + MOVV $690, R12 + JMP callbackasm1(SB) + MOVV $691, R12 + JMP callbackasm1(SB) + MOVV $692, R12 + JMP callbackasm1(SB) + MOVV $693, R12 + JMP callbackasm1(SB) + MOVV $694, R12 + JMP callbackasm1(SB) + MOVV $695, R12 + JMP callbackasm1(SB) + MOVV $696, R12 + JMP callbackasm1(SB) + MOVV $697, R12 + JMP callbackasm1(SB) + MOVV $698, R12 + JMP callbackasm1(SB) + MOVV $699, R12 + JMP callbackasm1(SB) + MOVV $700, R12 + JMP callbackasm1(SB) + MOVV $701, R12 + JMP callbackasm1(SB) + MOVV $702, R12 + JMP callbackasm1(SB) + MOVV $703, R12 + JMP callbackasm1(SB) + MOVV $704, R12 + JMP callbackasm1(SB) + MOVV $705, R12 + JMP callbackasm1(SB) + MOVV $706, R12 + JMP callbackasm1(SB) + MOVV $707, R12 + JMP callbackasm1(SB) + MOVV $708, R12 + JMP callbackasm1(SB) + MOVV $709, R12 + JMP callbackasm1(SB) + MOVV $710, R12 + JMP callbackasm1(SB) + MOVV $711, R12 + JMP callbackasm1(SB) + MOVV $712, R12 + JMP callbackasm1(SB) + MOVV $713, R12 + JMP callbackasm1(SB) + MOVV $714, R12 + JMP callbackasm1(SB) + MOVV $715, R12 + JMP callbackasm1(SB) + MOVV $716, R12 + JMP callbackasm1(SB) + MOVV $717, R12 + JMP callbackasm1(SB) + MOVV $718, R12 + JMP callbackasm1(SB) + MOVV $719, R12 + JMP callbackasm1(SB) + MOVV $720, R12 + JMP callbackasm1(SB) + MOVV $721, R12 + JMP callbackasm1(SB) + MOVV $722, R12 + JMP callbackasm1(SB) + MOVV $723, R12 + JMP callbackasm1(SB) + MOVV $724, R12 + JMP callbackasm1(SB) + MOVV $725, R12 + JMP callbackasm1(SB) + MOVV $726, R12 + JMP callbackasm1(SB) + MOVV $727, R12 + JMP callbackasm1(SB) + MOVV $728, R12 + JMP callbackasm1(SB) + MOVV $729, R12 + JMP callbackasm1(SB) + MOVV $730, R12 + JMP callbackasm1(SB) + MOVV $731, R12 + JMP callbackasm1(SB) + MOVV $732, R12 + JMP callbackasm1(SB) + MOVV $733, R12 + JMP callbackasm1(SB) + MOVV $734, R12 + JMP callbackasm1(SB) + MOVV $735, R12 + JMP callbackasm1(SB) + MOVV $736, R12 + JMP callbackasm1(SB) + MOVV $737, R12 + JMP callbackasm1(SB) + MOVV $738, R12 + JMP callbackasm1(SB) + MOVV $739, R12 + JMP callbackasm1(SB) + MOVV $740, R12 + JMP callbackasm1(SB) + MOVV $741, R12 + JMP callbackasm1(SB) + MOVV $742, R12 + JMP callbackasm1(SB) + MOVV $743, R12 + JMP callbackasm1(SB) + MOVV $744, R12 + JMP callbackasm1(SB) + MOVV $745, R12 + JMP callbackasm1(SB) + MOVV $746, R12 + JMP callbackasm1(SB) + MOVV $747, R12 + JMP callbackasm1(SB) + MOVV $748, R12 + JMP callbackasm1(SB) + MOVV $749, R12 + JMP callbackasm1(SB) + MOVV $750, R12 + JMP callbackasm1(SB) + MOVV $751, R12 + JMP callbackasm1(SB) + MOVV $752, R12 + JMP callbackasm1(SB) + MOVV $753, R12 + JMP callbackasm1(SB) + MOVV $754, R12 + JMP callbackasm1(SB) + MOVV $755, R12 + JMP callbackasm1(SB) + MOVV $756, R12 + JMP callbackasm1(SB) + MOVV $757, R12 + JMP callbackasm1(SB) + MOVV $758, R12 + JMP callbackasm1(SB) + MOVV $759, R12 + JMP callbackasm1(SB) + MOVV $760, R12 + JMP callbackasm1(SB) + MOVV $761, R12 + JMP callbackasm1(SB) + MOVV $762, R12 + JMP callbackasm1(SB) + MOVV $763, R12 + JMP callbackasm1(SB) + MOVV $764, R12 + JMP callbackasm1(SB) + MOVV $765, R12 + JMP callbackasm1(SB) + MOVV $766, R12 + JMP callbackasm1(SB) + MOVV $767, R12 + JMP callbackasm1(SB) + MOVV $768, R12 + JMP callbackasm1(SB) + MOVV $769, R12 + JMP callbackasm1(SB) + MOVV $770, R12 + JMP callbackasm1(SB) + MOVV $771, R12 + JMP callbackasm1(SB) + MOVV $772, R12 + JMP callbackasm1(SB) + MOVV $773, R12 + JMP callbackasm1(SB) + MOVV $774, R12 + JMP callbackasm1(SB) + MOVV $775, R12 + JMP callbackasm1(SB) + MOVV $776, R12 + JMP callbackasm1(SB) + MOVV $777, R12 + JMP callbackasm1(SB) + MOVV $778, R12 + JMP callbackasm1(SB) + MOVV $779, R12 + JMP callbackasm1(SB) + MOVV $780, R12 + JMP callbackasm1(SB) + MOVV $781, R12 + JMP callbackasm1(SB) + MOVV $782, R12 + JMP callbackasm1(SB) + MOVV $783, R12 + JMP callbackasm1(SB) + MOVV $784, R12 + JMP callbackasm1(SB) + MOVV $785, R12 + JMP callbackasm1(SB) + MOVV $786, R12 + JMP callbackasm1(SB) + MOVV $787, R12 + JMP callbackasm1(SB) + MOVV $788, R12 + JMP callbackasm1(SB) + MOVV $789, R12 + JMP callbackasm1(SB) + MOVV $790, R12 + JMP callbackasm1(SB) + MOVV $791, R12 + JMP callbackasm1(SB) + MOVV $792, R12 + JMP callbackasm1(SB) + MOVV $793, R12 + JMP callbackasm1(SB) + MOVV $794, R12 + JMP callbackasm1(SB) + MOVV $795, R12 + JMP callbackasm1(SB) + MOVV $796, R12 + JMP callbackasm1(SB) + MOVV $797, R12 + JMP callbackasm1(SB) + MOVV $798, R12 + JMP callbackasm1(SB) + MOVV $799, R12 + JMP callbackasm1(SB) + MOVV $800, R12 + JMP callbackasm1(SB) + MOVV $801, R12 + JMP callbackasm1(SB) + MOVV $802, R12 + JMP callbackasm1(SB) + MOVV $803, R12 + JMP callbackasm1(SB) + MOVV $804, R12 + JMP callbackasm1(SB) + MOVV $805, R12 + JMP callbackasm1(SB) + MOVV $806, R12 + JMP callbackasm1(SB) + MOVV $807, R12 + JMP callbackasm1(SB) + MOVV $808, R12 + JMP callbackasm1(SB) + MOVV $809, R12 + JMP callbackasm1(SB) + MOVV $810, R12 + JMP callbackasm1(SB) + MOVV $811, R12 + JMP callbackasm1(SB) + MOVV $812, R12 + JMP callbackasm1(SB) + MOVV $813, R12 + JMP callbackasm1(SB) + MOVV $814, R12 + JMP callbackasm1(SB) + MOVV $815, R12 + JMP callbackasm1(SB) + MOVV $816, R12 + JMP callbackasm1(SB) + MOVV $817, R12 + JMP callbackasm1(SB) + MOVV $818, R12 + JMP callbackasm1(SB) + MOVV $819, R12 + JMP callbackasm1(SB) + MOVV $820, R12 + JMP callbackasm1(SB) + MOVV $821, R12 + JMP callbackasm1(SB) + MOVV $822, R12 + JMP callbackasm1(SB) + MOVV $823, R12 + JMP callbackasm1(SB) + MOVV $824, R12 + JMP callbackasm1(SB) + MOVV $825, R12 + JMP callbackasm1(SB) + MOVV $826, R12 + JMP callbackasm1(SB) + MOVV $827, R12 + JMP callbackasm1(SB) + MOVV $828, R12 + JMP callbackasm1(SB) + MOVV $829, R12 + JMP callbackasm1(SB) + MOVV $830, R12 + JMP callbackasm1(SB) + MOVV $831, R12 + JMP callbackasm1(SB) + MOVV $832, R12 + JMP callbackasm1(SB) + MOVV $833, R12 + JMP callbackasm1(SB) + MOVV $834, R12 + JMP callbackasm1(SB) + MOVV $835, R12 + JMP callbackasm1(SB) + MOVV $836, R12 + JMP callbackasm1(SB) + MOVV $837, R12 + JMP callbackasm1(SB) + MOVV $838, R12 + JMP callbackasm1(SB) + MOVV $839, R12 + JMP callbackasm1(SB) + MOVV $840, R12 + JMP callbackasm1(SB) + MOVV $841, R12 + JMP callbackasm1(SB) + MOVV $842, R12 + JMP callbackasm1(SB) + MOVV $843, R12 + JMP callbackasm1(SB) + MOVV $844, R12 + JMP callbackasm1(SB) + MOVV $845, R12 + JMP callbackasm1(SB) + MOVV $846, R12 + JMP callbackasm1(SB) + MOVV $847, R12 + JMP callbackasm1(SB) + MOVV $848, R12 + JMP callbackasm1(SB) + MOVV $849, R12 + JMP callbackasm1(SB) + MOVV $850, R12 + JMP callbackasm1(SB) + MOVV $851, R12 + JMP callbackasm1(SB) + MOVV $852, R12 + JMP callbackasm1(SB) + MOVV $853, R12 + JMP callbackasm1(SB) + MOVV $854, R12 + JMP callbackasm1(SB) + MOVV $855, R12 + JMP callbackasm1(SB) + MOVV $856, R12 + JMP callbackasm1(SB) + MOVV $857, R12 + JMP callbackasm1(SB) + MOVV $858, R12 + JMP callbackasm1(SB) + MOVV $859, R12 + JMP callbackasm1(SB) + MOVV $860, R12 + JMP callbackasm1(SB) + MOVV $861, R12 + JMP callbackasm1(SB) + MOVV $862, R12 + JMP callbackasm1(SB) + MOVV $863, R12 + JMP callbackasm1(SB) + MOVV $864, R12 + JMP callbackasm1(SB) + MOVV $865, R12 + JMP callbackasm1(SB) + MOVV $866, R12 + JMP callbackasm1(SB) + MOVV $867, R12 + JMP callbackasm1(SB) + MOVV $868, R12 + JMP callbackasm1(SB) + MOVV $869, R12 + JMP callbackasm1(SB) + MOVV $870, R12 + JMP callbackasm1(SB) + MOVV $871, R12 + JMP callbackasm1(SB) + MOVV $872, R12 + JMP callbackasm1(SB) + MOVV $873, R12 + JMP callbackasm1(SB) + MOVV $874, R12 + JMP callbackasm1(SB) + MOVV $875, R12 + JMP callbackasm1(SB) + MOVV $876, R12 + JMP callbackasm1(SB) + MOVV $877, R12 + JMP callbackasm1(SB) + MOVV $878, R12 + JMP callbackasm1(SB) + MOVV $879, R12 + JMP callbackasm1(SB) + MOVV $880, R12 + JMP callbackasm1(SB) + MOVV $881, R12 + JMP callbackasm1(SB) + MOVV $882, R12 + JMP callbackasm1(SB) + MOVV $883, R12 + JMP callbackasm1(SB) + MOVV $884, R12 + JMP callbackasm1(SB) + MOVV $885, R12 + JMP callbackasm1(SB) + MOVV $886, R12 + JMP callbackasm1(SB) + MOVV $887, R12 + JMP callbackasm1(SB) + MOVV $888, R12 + JMP callbackasm1(SB) + MOVV $889, R12 + JMP callbackasm1(SB) + MOVV $890, R12 + JMP callbackasm1(SB) + MOVV $891, R12 + JMP callbackasm1(SB) + MOVV $892, R12 + JMP callbackasm1(SB) + MOVV $893, R12 + JMP callbackasm1(SB) + MOVV $894, R12 + JMP callbackasm1(SB) + MOVV $895, R12 + JMP callbackasm1(SB) + MOVV $896, R12 + JMP callbackasm1(SB) + MOVV $897, R12 + JMP callbackasm1(SB) + MOVV $898, R12 + JMP callbackasm1(SB) + MOVV $899, R12 + JMP callbackasm1(SB) + MOVV $900, R12 + JMP callbackasm1(SB) + MOVV $901, R12 + JMP callbackasm1(SB) + MOVV $902, R12 + JMP callbackasm1(SB) + MOVV $903, R12 + JMP callbackasm1(SB) + MOVV $904, R12 + JMP callbackasm1(SB) + MOVV $905, R12 + JMP callbackasm1(SB) + MOVV $906, R12 + JMP callbackasm1(SB) + MOVV $907, R12 + JMP callbackasm1(SB) + MOVV $908, R12 + JMP callbackasm1(SB) + MOVV $909, R12 + JMP callbackasm1(SB) + MOVV $910, R12 + JMP callbackasm1(SB) + MOVV $911, R12 + JMP callbackasm1(SB) + MOVV $912, R12 + JMP callbackasm1(SB) + MOVV $913, R12 + JMP callbackasm1(SB) + MOVV $914, R12 + JMP callbackasm1(SB) + MOVV $915, R12 + JMP callbackasm1(SB) + MOVV $916, R12 + JMP callbackasm1(SB) + MOVV $917, R12 + JMP callbackasm1(SB) + MOVV $918, R12 + JMP callbackasm1(SB) + MOVV $919, R12 + JMP callbackasm1(SB) + MOVV $920, R12 + JMP callbackasm1(SB) + MOVV $921, R12 + JMP callbackasm1(SB) + MOVV $922, R12 + JMP callbackasm1(SB) + MOVV $923, R12 + JMP callbackasm1(SB) + MOVV $924, R12 + JMP callbackasm1(SB) + MOVV $925, R12 + JMP callbackasm1(SB) + MOVV $926, R12 + JMP callbackasm1(SB) + MOVV $927, R12 + JMP callbackasm1(SB) + MOVV $928, R12 + JMP callbackasm1(SB) + MOVV $929, R12 + JMP callbackasm1(SB) + MOVV $930, R12 + JMP callbackasm1(SB) + MOVV $931, R12 + JMP callbackasm1(SB) + MOVV $932, R12 + JMP callbackasm1(SB) + MOVV $933, R12 + JMP callbackasm1(SB) + MOVV $934, R12 + JMP callbackasm1(SB) + MOVV $935, R12 + JMP callbackasm1(SB) + MOVV $936, R12 + JMP callbackasm1(SB) + MOVV $937, R12 + JMP callbackasm1(SB) + MOVV $938, R12 + JMP callbackasm1(SB) + MOVV $939, R12 + JMP callbackasm1(SB) + MOVV $940, R12 + JMP callbackasm1(SB) + MOVV $941, R12 + JMP callbackasm1(SB) + MOVV $942, R12 + JMP callbackasm1(SB) + MOVV $943, R12 + JMP callbackasm1(SB) + MOVV $944, R12 + JMP callbackasm1(SB) + MOVV $945, R12 + JMP callbackasm1(SB) + MOVV $946, R12 + JMP callbackasm1(SB) + MOVV $947, R12 + JMP callbackasm1(SB) + MOVV $948, R12 + JMP callbackasm1(SB) + MOVV $949, R12 + JMP callbackasm1(SB) + MOVV $950, R12 + JMP callbackasm1(SB) + MOVV $951, R12 + JMP callbackasm1(SB) + MOVV $952, R12 + JMP callbackasm1(SB) + MOVV $953, R12 + JMP callbackasm1(SB) + MOVV $954, R12 + JMP callbackasm1(SB) + MOVV $955, R12 + JMP callbackasm1(SB) + MOVV $956, R12 + JMP callbackasm1(SB) + MOVV $957, R12 + JMP callbackasm1(SB) + MOVV $958, R12 + JMP callbackasm1(SB) + MOVV $959, R12 + JMP callbackasm1(SB) + MOVV $960, R12 + JMP callbackasm1(SB) + MOVV $961, R12 + JMP callbackasm1(SB) + MOVV $962, R12 + JMP callbackasm1(SB) + MOVV $963, R12 + JMP callbackasm1(SB) + MOVV $964, R12 + JMP callbackasm1(SB) + MOVV $965, R12 + JMP callbackasm1(SB) + MOVV $966, R12 + JMP callbackasm1(SB) + MOVV $967, R12 + JMP callbackasm1(SB) + MOVV $968, R12 + JMP callbackasm1(SB) + MOVV $969, R12 + JMP callbackasm1(SB) + MOVV $970, R12 + JMP callbackasm1(SB) + MOVV $971, R12 + JMP callbackasm1(SB) + MOVV $972, R12 + JMP callbackasm1(SB) + MOVV $973, R12 + JMP callbackasm1(SB) + MOVV $974, R12 + JMP callbackasm1(SB) + MOVV $975, R12 + JMP callbackasm1(SB) + MOVV $976, R12 + JMP callbackasm1(SB) + MOVV $977, R12 + JMP callbackasm1(SB) + MOVV $978, R12 + JMP callbackasm1(SB) + MOVV $979, R12 + JMP callbackasm1(SB) + MOVV $980, R12 + JMP callbackasm1(SB) + MOVV $981, R12 + JMP callbackasm1(SB) + MOVV $982, R12 + JMP callbackasm1(SB) + MOVV $983, R12 + JMP callbackasm1(SB) + MOVV $984, R12 + JMP callbackasm1(SB) + MOVV $985, R12 + JMP callbackasm1(SB) + MOVV $986, R12 + JMP callbackasm1(SB) + MOVV $987, R12 + JMP callbackasm1(SB) + MOVV $988, R12 + JMP callbackasm1(SB) + MOVV $989, R12 + JMP callbackasm1(SB) + MOVV $990, R12 + JMP callbackasm1(SB) + MOVV $991, R12 + JMP callbackasm1(SB) + MOVV $992, R12 + JMP callbackasm1(SB) + MOVV $993, R12 + JMP callbackasm1(SB) + MOVV $994, R12 + JMP callbackasm1(SB) + MOVV $995, R12 + JMP callbackasm1(SB) + MOVV $996, R12 + JMP callbackasm1(SB) + MOVV $997, R12 + JMP callbackasm1(SB) + MOVV $998, R12 + JMP callbackasm1(SB) + MOVV $999, R12 + JMP callbackasm1(SB) + MOVV $1000, R12 + JMP callbackasm1(SB) + MOVV $1001, R12 + JMP callbackasm1(SB) + MOVV $1002, R12 + JMP callbackasm1(SB) + MOVV $1003, R12 + JMP callbackasm1(SB) + MOVV $1004, R12 + JMP callbackasm1(SB) + MOVV $1005, R12 + JMP callbackasm1(SB) + MOVV $1006, R12 + JMP callbackasm1(SB) + MOVV $1007, R12 + JMP callbackasm1(SB) + MOVV $1008, R12 + JMP callbackasm1(SB) + MOVV $1009, R12 + JMP callbackasm1(SB) + MOVV $1010, R12 + JMP callbackasm1(SB) + MOVV $1011, R12 + JMP callbackasm1(SB) + MOVV $1012, R12 + JMP callbackasm1(SB) + MOVV $1013, R12 + JMP callbackasm1(SB) + MOVV $1014, R12 + JMP callbackasm1(SB) + MOVV $1015, R12 + JMP callbackasm1(SB) + MOVV $1016, R12 + JMP callbackasm1(SB) + MOVV $1017, R12 + JMP callbackasm1(SB) + MOVV $1018, R12 + JMP callbackasm1(SB) + MOVV $1019, R12 + JMP callbackasm1(SB) + MOVV $1020, R12 + JMP callbackasm1(SB) + MOVV $1021, R12 + JMP callbackasm1(SB) + MOVV $1022, R12 + JMP callbackasm1(SB) + MOVV $1023, R12 + JMP callbackasm1(SB) + MOVV $1024, R12 + JMP callbackasm1(SB) + MOVV $1025, R12 + JMP callbackasm1(SB) + MOVV $1026, R12 + JMP callbackasm1(SB) + MOVV $1027, R12 + JMP callbackasm1(SB) + MOVV $1028, R12 + JMP callbackasm1(SB) + MOVV $1029, R12 + JMP callbackasm1(SB) + MOVV $1030, R12 + JMP callbackasm1(SB) + MOVV $1031, R12 + JMP callbackasm1(SB) + MOVV $1032, R12 + JMP callbackasm1(SB) + MOVV $1033, R12 + JMP callbackasm1(SB) + MOVV $1034, R12 + JMP callbackasm1(SB) + MOVV $1035, R12 + JMP callbackasm1(SB) + MOVV $1036, R12 + JMP callbackasm1(SB) + MOVV $1037, R12 + JMP callbackasm1(SB) + MOVV $1038, R12 + JMP callbackasm1(SB) + MOVV $1039, R12 + JMP callbackasm1(SB) + MOVV $1040, R12 + JMP callbackasm1(SB) + MOVV $1041, R12 + JMP callbackasm1(SB) + MOVV $1042, R12 + JMP callbackasm1(SB) + MOVV $1043, R12 + JMP callbackasm1(SB) + MOVV $1044, R12 + JMP callbackasm1(SB) + MOVV $1045, R12 + JMP callbackasm1(SB) + MOVV $1046, R12 + JMP callbackasm1(SB) + MOVV $1047, R12 + JMP callbackasm1(SB) + MOVV $1048, R12 + JMP callbackasm1(SB) + MOVV $1049, R12 + JMP callbackasm1(SB) + MOVV $1050, R12 + JMP callbackasm1(SB) + MOVV $1051, R12 + JMP callbackasm1(SB) + MOVV $1052, R12 + JMP callbackasm1(SB) + MOVV $1053, R12 + JMP callbackasm1(SB) + MOVV $1054, R12 + JMP callbackasm1(SB) + MOVV $1055, R12 + JMP callbackasm1(SB) + MOVV $1056, R12 + JMP callbackasm1(SB) + MOVV $1057, R12 + JMP callbackasm1(SB) + MOVV $1058, R12 + JMP callbackasm1(SB) + MOVV $1059, R12 + JMP callbackasm1(SB) + MOVV $1060, R12 + JMP callbackasm1(SB) + MOVV $1061, R12 + JMP callbackasm1(SB) + MOVV $1062, R12 + JMP callbackasm1(SB) + MOVV $1063, R12 + JMP callbackasm1(SB) + MOVV $1064, R12 + JMP callbackasm1(SB) + MOVV $1065, R12 + JMP callbackasm1(SB) + MOVV $1066, R12 + JMP callbackasm1(SB) + MOVV $1067, R12 + JMP callbackasm1(SB) + MOVV $1068, R12 + JMP callbackasm1(SB) + MOVV $1069, R12 + JMP callbackasm1(SB) + MOVV $1070, R12 + JMP callbackasm1(SB) + MOVV $1071, R12 + JMP callbackasm1(SB) + MOVV $1072, R12 + JMP callbackasm1(SB) + MOVV $1073, R12 + JMP callbackasm1(SB) + MOVV $1074, R12 + JMP callbackasm1(SB) + MOVV $1075, R12 + JMP callbackasm1(SB) + MOVV $1076, R12 + JMP callbackasm1(SB) + MOVV $1077, R12 + JMP callbackasm1(SB) + MOVV $1078, R12 + JMP callbackasm1(SB) + MOVV $1079, R12 + JMP callbackasm1(SB) + MOVV $1080, R12 + JMP callbackasm1(SB) + MOVV $1081, R12 + JMP callbackasm1(SB) + MOVV $1082, R12 + JMP callbackasm1(SB) + MOVV $1083, R12 + JMP callbackasm1(SB) + MOVV $1084, R12 + JMP callbackasm1(SB) + MOVV $1085, R12 + JMP callbackasm1(SB) + MOVV $1086, R12 + JMP callbackasm1(SB) + MOVV $1087, R12 + JMP callbackasm1(SB) + MOVV $1088, R12 + JMP callbackasm1(SB) + MOVV $1089, R12 + JMP callbackasm1(SB) + MOVV $1090, R12 + JMP callbackasm1(SB) + MOVV $1091, R12 + JMP callbackasm1(SB) + MOVV $1092, R12 + JMP callbackasm1(SB) + MOVV $1093, R12 + JMP callbackasm1(SB) + MOVV $1094, R12 + JMP callbackasm1(SB) + MOVV $1095, R12 + JMP callbackasm1(SB) + MOVV $1096, R12 + JMP callbackasm1(SB) + MOVV $1097, R12 + JMP callbackasm1(SB) + MOVV $1098, R12 + JMP callbackasm1(SB) + MOVV $1099, R12 + JMP callbackasm1(SB) + MOVV $1100, R12 + JMP callbackasm1(SB) + MOVV $1101, R12 + JMP callbackasm1(SB) + MOVV $1102, R12 + JMP callbackasm1(SB) + MOVV $1103, R12 + JMP callbackasm1(SB) + MOVV $1104, R12 + JMP callbackasm1(SB) + MOVV $1105, R12 + JMP callbackasm1(SB) + MOVV $1106, R12 + JMP callbackasm1(SB) + MOVV $1107, R12 + JMP callbackasm1(SB) + MOVV $1108, R12 + JMP callbackasm1(SB) + MOVV $1109, R12 + JMP callbackasm1(SB) + MOVV $1110, R12 + JMP callbackasm1(SB) + MOVV $1111, R12 + JMP callbackasm1(SB) + MOVV $1112, R12 + JMP callbackasm1(SB) + MOVV $1113, R12 + JMP callbackasm1(SB) + MOVV $1114, R12 + JMP callbackasm1(SB) + MOVV $1115, R12 + JMP callbackasm1(SB) + MOVV $1116, R12 + JMP callbackasm1(SB) + MOVV $1117, R12 + JMP callbackasm1(SB) + MOVV $1118, R12 + JMP callbackasm1(SB) + MOVV $1119, R12 + JMP callbackasm1(SB) + MOVV $1120, R12 + JMP callbackasm1(SB) + MOVV $1121, R12 + JMP callbackasm1(SB) + MOVV $1122, R12 + JMP callbackasm1(SB) + MOVV $1123, R12 + JMP callbackasm1(SB) + MOVV $1124, R12 + JMP callbackasm1(SB) + MOVV $1125, R12 + JMP callbackasm1(SB) + MOVV $1126, R12 + JMP callbackasm1(SB) + MOVV $1127, R12 + JMP callbackasm1(SB) + MOVV $1128, R12 + JMP callbackasm1(SB) + MOVV $1129, R12 + JMP callbackasm1(SB) + MOVV $1130, R12 + JMP callbackasm1(SB) + MOVV $1131, R12 + JMP callbackasm1(SB) + MOVV $1132, R12 + JMP callbackasm1(SB) + MOVV $1133, R12 + JMP callbackasm1(SB) + MOVV $1134, R12 + JMP callbackasm1(SB) + MOVV $1135, R12 + JMP callbackasm1(SB) + MOVV $1136, R12 + JMP callbackasm1(SB) + MOVV $1137, R12 + JMP callbackasm1(SB) + MOVV $1138, R12 + JMP callbackasm1(SB) + MOVV $1139, R12 + JMP callbackasm1(SB) + MOVV $1140, R12 + JMP callbackasm1(SB) + MOVV $1141, R12 + JMP callbackasm1(SB) + MOVV $1142, R12 + JMP callbackasm1(SB) + MOVV $1143, R12 + JMP callbackasm1(SB) + MOVV $1144, R12 + JMP callbackasm1(SB) + MOVV $1145, R12 + JMP callbackasm1(SB) + MOVV $1146, R12 + JMP callbackasm1(SB) + MOVV $1147, R12 + JMP callbackasm1(SB) + MOVV $1148, R12 + JMP callbackasm1(SB) + MOVV $1149, R12 + JMP callbackasm1(SB) + MOVV $1150, R12 + JMP callbackasm1(SB) + MOVV $1151, R12 + JMP callbackasm1(SB) + MOVV $1152, R12 + JMP callbackasm1(SB) + MOVV $1153, R12 + JMP callbackasm1(SB) + MOVV $1154, R12 + JMP callbackasm1(SB) + MOVV $1155, R12 + JMP callbackasm1(SB) + MOVV $1156, R12 + JMP callbackasm1(SB) + MOVV $1157, R12 + JMP callbackasm1(SB) + MOVV $1158, R12 + JMP callbackasm1(SB) + MOVV $1159, R12 + JMP callbackasm1(SB) + MOVV $1160, R12 + JMP callbackasm1(SB) + MOVV $1161, R12 + JMP callbackasm1(SB) + MOVV $1162, R12 + JMP callbackasm1(SB) + MOVV $1163, R12 + JMP callbackasm1(SB) + MOVV $1164, R12 + JMP callbackasm1(SB) + MOVV $1165, R12 + JMP callbackasm1(SB) + MOVV $1166, R12 + JMP callbackasm1(SB) + MOVV $1167, R12 + JMP callbackasm1(SB) + MOVV $1168, R12 + JMP callbackasm1(SB) + MOVV $1169, R12 + JMP callbackasm1(SB) + MOVV $1170, R12 + JMP callbackasm1(SB) + MOVV $1171, R12 + JMP callbackasm1(SB) + MOVV $1172, R12 + JMP callbackasm1(SB) + MOVV $1173, R12 + JMP callbackasm1(SB) + MOVV $1174, R12 + JMP callbackasm1(SB) + MOVV $1175, R12 + JMP callbackasm1(SB) + MOVV $1176, R12 + JMP callbackasm1(SB) + MOVV $1177, R12 + JMP callbackasm1(SB) + MOVV $1178, R12 + JMP callbackasm1(SB) + MOVV $1179, R12 + JMP callbackasm1(SB) + MOVV $1180, R12 + JMP callbackasm1(SB) + MOVV $1181, R12 + JMP callbackasm1(SB) + MOVV $1182, R12 + JMP callbackasm1(SB) + MOVV $1183, R12 + JMP callbackasm1(SB) + MOVV $1184, R12 + JMP callbackasm1(SB) + MOVV $1185, R12 + JMP callbackasm1(SB) + MOVV $1186, R12 + JMP callbackasm1(SB) + MOVV $1187, R12 + JMP callbackasm1(SB) + MOVV $1188, R12 + JMP callbackasm1(SB) + MOVV $1189, R12 + JMP callbackasm1(SB) + MOVV $1190, R12 + JMP callbackasm1(SB) + MOVV $1191, R12 + JMP callbackasm1(SB) + MOVV $1192, R12 + JMP callbackasm1(SB) + MOVV $1193, R12 + JMP callbackasm1(SB) + MOVV $1194, R12 + JMP callbackasm1(SB) + MOVV $1195, R12 + JMP callbackasm1(SB) + MOVV $1196, R12 + JMP callbackasm1(SB) + MOVV $1197, R12 + JMP callbackasm1(SB) + MOVV $1198, R12 + JMP callbackasm1(SB) + MOVV $1199, R12 + JMP callbackasm1(SB) + MOVV $1200, R12 + JMP callbackasm1(SB) + MOVV $1201, R12 + JMP callbackasm1(SB) + MOVV $1202, R12 + JMP callbackasm1(SB) + MOVV $1203, R12 + JMP callbackasm1(SB) + MOVV $1204, R12 + JMP callbackasm1(SB) + MOVV $1205, R12 + JMP callbackasm1(SB) + MOVV $1206, R12 + JMP callbackasm1(SB) + MOVV $1207, R12 + JMP callbackasm1(SB) + MOVV $1208, R12 + JMP callbackasm1(SB) + MOVV $1209, R12 + JMP callbackasm1(SB) + MOVV $1210, R12 + JMP callbackasm1(SB) + MOVV $1211, R12 + JMP callbackasm1(SB) + MOVV $1212, R12 + JMP callbackasm1(SB) + MOVV $1213, R12 + JMP callbackasm1(SB) + MOVV $1214, R12 + JMP callbackasm1(SB) + MOVV $1215, R12 + JMP callbackasm1(SB) + MOVV $1216, R12 + JMP callbackasm1(SB) + MOVV $1217, R12 + JMP callbackasm1(SB) + MOVV $1218, R12 + JMP callbackasm1(SB) + MOVV $1219, R12 + JMP callbackasm1(SB) + MOVV $1220, R12 + JMP callbackasm1(SB) + MOVV $1221, R12 + JMP callbackasm1(SB) + MOVV $1222, R12 + JMP callbackasm1(SB) + MOVV $1223, R12 + JMP callbackasm1(SB) + MOVV $1224, R12 + JMP callbackasm1(SB) + MOVV $1225, R12 + JMP callbackasm1(SB) + MOVV $1226, R12 + JMP callbackasm1(SB) + MOVV $1227, R12 + JMP callbackasm1(SB) + MOVV $1228, R12 + JMP callbackasm1(SB) + MOVV $1229, R12 + JMP callbackasm1(SB) + MOVV $1230, R12 + JMP callbackasm1(SB) + MOVV $1231, R12 + JMP callbackasm1(SB) + MOVV $1232, R12 + JMP callbackasm1(SB) + MOVV $1233, R12 + JMP callbackasm1(SB) + MOVV $1234, R12 + JMP callbackasm1(SB) + MOVV $1235, R12 + JMP callbackasm1(SB) + MOVV $1236, R12 + JMP callbackasm1(SB) + MOVV $1237, R12 + JMP callbackasm1(SB) + MOVV $1238, R12 + JMP callbackasm1(SB) + MOVV $1239, R12 + JMP callbackasm1(SB) + MOVV $1240, R12 + JMP callbackasm1(SB) + MOVV $1241, R12 + JMP callbackasm1(SB) + MOVV $1242, R12 + JMP callbackasm1(SB) + MOVV $1243, R12 + JMP callbackasm1(SB) + MOVV $1244, R12 + JMP callbackasm1(SB) + MOVV $1245, R12 + JMP callbackasm1(SB) + MOVV $1246, R12 + JMP callbackasm1(SB) + MOVV $1247, R12 + JMP callbackasm1(SB) + MOVV $1248, R12 + JMP callbackasm1(SB) + MOVV $1249, R12 + JMP callbackasm1(SB) + MOVV $1250, R12 + JMP callbackasm1(SB) + MOVV $1251, R12 + JMP callbackasm1(SB) + MOVV $1252, R12 + JMP callbackasm1(SB) + MOVV $1253, R12 + JMP callbackasm1(SB) + MOVV $1254, R12 + JMP callbackasm1(SB) + MOVV $1255, R12 + JMP callbackasm1(SB) + MOVV $1256, R12 + JMP callbackasm1(SB) + MOVV $1257, R12 + JMP callbackasm1(SB) + MOVV $1258, R12 + JMP callbackasm1(SB) + MOVV $1259, R12 + JMP callbackasm1(SB) + MOVV $1260, R12 + JMP callbackasm1(SB) + MOVV $1261, R12 + JMP callbackasm1(SB) + MOVV $1262, R12 + JMP callbackasm1(SB) + MOVV $1263, R12 + JMP callbackasm1(SB) + MOVV $1264, R12 + JMP callbackasm1(SB) + MOVV $1265, R12 + JMP callbackasm1(SB) + MOVV $1266, R12 + JMP callbackasm1(SB) + MOVV $1267, R12 + JMP callbackasm1(SB) + MOVV $1268, R12 + JMP callbackasm1(SB) + MOVV $1269, R12 + JMP callbackasm1(SB) + MOVV $1270, R12 + JMP callbackasm1(SB) + MOVV $1271, R12 + JMP callbackasm1(SB) + MOVV $1272, R12 + JMP callbackasm1(SB) + MOVV $1273, R12 + JMP callbackasm1(SB) + MOVV $1274, R12 + JMP callbackasm1(SB) + MOVV $1275, R12 + JMP callbackasm1(SB) + MOVV $1276, R12 + JMP callbackasm1(SB) + MOVV $1277, R12 + JMP callbackasm1(SB) + MOVV $1278, R12 + JMP callbackasm1(SB) + MOVV $1279, R12 + JMP callbackasm1(SB) + MOVV $1280, R12 + JMP callbackasm1(SB) + MOVV $1281, R12 + JMP callbackasm1(SB) + MOVV $1282, R12 + JMP callbackasm1(SB) + MOVV $1283, R12 + JMP callbackasm1(SB) + MOVV $1284, R12 + JMP callbackasm1(SB) + MOVV $1285, R12 + JMP callbackasm1(SB) + MOVV $1286, R12 + JMP callbackasm1(SB) + MOVV $1287, R12 + JMP callbackasm1(SB) + MOVV $1288, R12 + JMP callbackasm1(SB) + MOVV $1289, R12 + JMP callbackasm1(SB) + MOVV $1290, R12 + JMP callbackasm1(SB) + MOVV $1291, R12 + JMP callbackasm1(SB) + MOVV $1292, R12 + JMP callbackasm1(SB) + MOVV $1293, R12 + JMP callbackasm1(SB) + MOVV $1294, R12 + JMP callbackasm1(SB) + MOVV $1295, R12 + JMP callbackasm1(SB) + MOVV $1296, R12 + JMP callbackasm1(SB) + MOVV $1297, R12 + JMP callbackasm1(SB) + MOVV $1298, R12 + JMP callbackasm1(SB) + MOVV $1299, R12 + JMP callbackasm1(SB) + MOVV $1300, R12 + JMP callbackasm1(SB) + MOVV $1301, R12 + JMP callbackasm1(SB) + MOVV $1302, R12 + JMP callbackasm1(SB) + MOVV $1303, R12 + JMP callbackasm1(SB) + MOVV $1304, R12 + JMP callbackasm1(SB) + MOVV $1305, R12 + JMP callbackasm1(SB) + MOVV $1306, R12 + JMP callbackasm1(SB) + MOVV $1307, R12 + JMP callbackasm1(SB) + MOVV $1308, R12 + JMP callbackasm1(SB) + MOVV $1309, R12 + JMP callbackasm1(SB) + MOVV $1310, R12 + JMP callbackasm1(SB) + MOVV $1311, R12 + JMP callbackasm1(SB) + MOVV $1312, R12 + JMP callbackasm1(SB) + MOVV $1313, R12 + JMP callbackasm1(SB) + MOVV $1314, R12 + JMP callbackasm1(SB) + MOVV $1315, R12 + JMP callbackasm1(SB) + MOVV $1316, R12 + JMP callbackasm1(SB) + MOVV $1317, R12 + JMP callbackasm1(SB) + MOVV $1318, R12 + JMP callbackasm1(SB) + MOVV $1319, R12 + JMP callbackasm1(SB) + MOVV $1320, R12 + JMP callbackasm1(SB) + MOVV $1321, R12 + JMP callbackasm1(SB) + MOVV $1322, R12 + JMP callbackasm1(SB) + MOVV $1323, R12 + JMP callbackasm1(SB) + MOVV $1324, R12 + JMP callbackasm1(SB) + MOVV $1325, R12 + JMP callbackasm1(SB) + MOVV $1326, R12 + JMP callbackasm1(SB) + MOVV $1327, R12 + JMP callbackasm1(SB) + MOVV $1328, R12 + JMP callbackasm1(SB) + MOVV $1329, R12 + JMP callbackasm1(SB) + MOVV $1330, R12 + JMP callbackasm1(SB) + MOVV $1331, R12 + JMP callbackasm1(SB) + MOVV $1332, R12 + JMP callbackasm1(SB) + MOVV $1333, R12 + JMP callbackasm1(SB) + MOVV $1334, R12 + JMP callbackasm1(SB) + MOVV $1335, R12 + JMP callbackasm1(SB) + MOVV $1336, R12 + JMP callbackasm1(SB) + MOVV $1337, R12 + JMP callbackasm1(SB) + MOVV $1338, R12 + JMP callbackasm1(SB) + MOVV $1339, R12 + JMP callbackasm1(SB) + MOVV $1340, R12 + JMP callbackasm1(SB) + MOVV $1341, R12 + JMP callbackasm1(SB) + MOVV $1342, R12 + JMP callbackasm1(SB) + MOVV $1343, R12 + JMP callbackasm1(SB) + MOVV $1344, R12 + JMP callbackasm1(SB) + MOVV $1345, R12 + JMP callbackasm1(SB) + MOVV $1346, R12 + JMP callbackasm1(SB) + MOVV $1347, R12 + JMP callbackasm1(SB) + MOVV $1348, R12 + JMP callbackasm1(SB) + MOVV $1349, R12 + JMP callbackasm1(SB) + MOVV $1350, R12 + JMP callbackasm1(SB) + MOVV $1351, R12 + JMP callbackasm1(SB) + MOVV $1352, R12 + JMP callbackasm1(SB) + MOVV $1353, R12 + JMP callbackasm1(SB) + MOVV $1354, R12 + JMP callbackasm1(SB) + MOVV $1355, R12 + JMP callbackasm1(SB) + MOVV $1356, R12 + JMP callbackasm1(SB) + MOVV $1357, R12 + JMP callbackasm1(SB) + MOVV $1358, R12 + JMP callbackasm1(SB) + MOVV $1359, R12 + JMP callbackasm1(SB) + MOVV $1360, R12 + JMP callbackasm1(SB) + MOVV $1361, R12 + JMP callbackasm1(SB) + MOVV $1362, R12 + JMP callbackasm1(SB) + MOVV $1363, R12 + JMP callbackasm1(SB) + MOVV $1364, R12 + JMP callbackasm1(SB) + MOVV $1365, R12 + JMP callbackasm1(SB) + MOVV $1366, R12 + JMP callbackasm1(SB) + MOVV $1367, R12 + JMP callbackasm1(SB) + MOVV $1368, R12 + JMP callbackasm1(SB) + MOVV $1369, R12 + JMP callbackasm1(SB) + MOVV $1370, R12 + JMP callbackasm1(SB) + MOVV $1371, R12 + JMP callbackasm1(SB) + MOVV $1372, R12 + JMP callbackasm1(SB) + MOVV $1373, R12 + JMP callbackasm1(SB) + MOVV $1374, R12 + JMP callbackasm1(SB) + MOVV $1375, R12 + JMP callbackasm1(SB) + MOVV $1376, R12 + JMP callbackasm1(SB) + MOVV $1377, R12 + JMP callbackasm1(SB) + MOVV $1378, R12 + JMP callbackasm1(SB) + MOVV $1379, R12 + JMP callbackasm1(SB) + MOVV $1380, R12 + JMP callbackasm1(SB) + MOVV $1381, R12 + JMP callbackasm1(SB) + MOVV $1382, R12 + JMP callbackasm1(SB) + MOVV $1383, R12 + JMP callbackasm1(SB) + MOVV $1384, R12 + JMP callbackasm1(SB) + MOVV $1385, R12 + JMP callbackasm1(SB) + MOVV $1386, R12 + JMP callbackasm1(SB) + MOVV $1387, R12 + JMP callbackasm1(SB) + MOVV $1388, R12 + JMP callbackasm1(SB) + MOVV $1389, R12 + JMP callbackasm1(SB) + MOVV $1390, R12 + JMP callbackasm1(SB) + MOVV $1391, R12 + JMP callbackasm1(SB) + MOVV $1392, R12 + JMP callbackasm1(SB) + MOVV $1393, R12 + JMP callbackasm1(SB) + MOVV $1394, R12 + JMP callbackasm1(SB) + MOVV $1395, R12 + JMP callbackasm1(SB) + MOVV $1396, R12 + JMP callbackasm1(SB) + MOVV $1397, R12 + JMP callbackasm1(SB) + MOVV $1398, R12 + JMP callbackasm1(SB) + MOVV $1399, R12 + JMP callbackasm1(SB) + MOVV $1400, R12 + JMP callbackasm1(SB) + MOVV $1401, R12 + JMP callbackasm1(SB) + MOVV $1402, R12 + JMP callbackasm1(SB) + MOVV $1403, R12 + JMP callbackasm1(SB) + MOVV $1404, R12 + JMP callbackasm1(SB) + MOVV $1405, R12 + JMP callbackasm1(SB) + MOVV $1406, R12 + JMP callbackasm1(SB) + MOVV $1407, R12 + JMP callbackasm1(SB) + MOVV $1408, R12 + JMP callbackasm1(SB) + MOVV $1409, R12 + JMP callbackasm1(SB) + MOVV $1410, R12 + JMP callbackasm1(SB) + MOVV $1411, R12 + JMP callbackasm1(SB) + MOVV $1412, R12 + JMP callbackasm1(SB) + MOVV $1413, R12 + JMP callbackasm1(SB) + MOVV $1414, R12 + JMP callbackasm1(SB) + MOVV $1415, R12 + JMP callbackasm1(SB) + MOVV $1416, R12 + JMP callbackasm1(SB) + MOVV $1417, R12 + JMP callbackasm1(SB) + MOVV $1418, R12 + JMP callbackasm1(SB) + MOVV $1419, R12 + JMP callbackasm1(SB) + MOVV $1420, R12 + JMP callbackasm1(SB) + MOVV $1421, R12 + JMP callbackasm1(SB) + MOVV $1422, R12 + JMP callbackasm1(SB) + MOVV $1423, R12 + JMP callbackasm1(SB) + MOVV $1424, R12 + JMP callbackasm1(SB) + MOVV $1425, R12 + JMP callbackasm1(SB) + MOVV $1426, R12 + JMP callbackasm1(SB) + MOVV $1427, R12 + JMP callbackasm1(SB) + MOVV $1428, R12 + JMP callbackasm1(SB) + MOVV $1429, R12 + JMP callbackasm1(SB) + MOVV $1430, R12 + JMP callbackasm1(SB) + MOVV $1431, R12 + JMP callbackasm1(SB) + MOVV $1432, R12 + JMP callbackasm1(SB) + MOVV $1433, R12 + JMP callbackasm1(SB) + MOVV $1434, R12 + JMP callbackasm1(SB) + MOVV $1435, R12 + JMP callbackasm1(SB) + MOVV $1436, R12 + JMP callbackasm1(SB) + MOVV $1437, R12 + JMP callbackasm1(SB) + MOVV $1438, R12 + JMP callbackasm1(SB) + MOVV $1439, R12 + JMP callbackasm1(SB) + MOVV $1440, R12 + JMP callbackasm1(SB) + MOVV $1441, R12 + JMP callbackasm1(SB) + MOVV $1442, R12 + JMP callbackasm1(SB) + MOVV $1443, R12 + JMP callbackasm1(SB) + MOVV $1444, R12 + JMP callbackasm1(SB) + MOVV $1445, R12 + JMP callbackasm1(SB) + MOVV $1446, R12 + JMP callbackasm1(SB) + MOVV $1447, R12 + JMP callbackasm1(SB) + MOVV $1448, R12 + JMP callbackasm1(SB) + MOVV $1449, R12 + JMP callbackasm1(SB) + MOVV $1450, R12 + JMP callbackasm1(SB) + MOVV $1451, R12 + JMP callbackasm1(SB) + MOVV $1452, R12 + JMP callbackasm1(SB) + MOVV $1453, R12 + JMP callbackasm1(SB) + MOVV $1454, R12 + JMP callbackasm1(SB) + MOVV $1455, R12 + JMP callbackasm1(SB) + MOVV $1456, R12 + JMP callbackasm1(SB) + MOVV $1457, R12 + JMP callbackasm1(SB) + MOVV $1458, R12 + JMP callbackasm1(SB) + MOVV $1459, R12 + JMP callbackasm1(SB) + MOVV $1460, R12 + JMP callbackasm1(SB) + MOVV $1461, R12 + JMP callbackasm1(SB) + MOVV $1462, R12 + JMP callbackasm1(SB) + MOVV $1463, R12 + JMP callbackasm1(SB) + MOVV $1464, R12 + JMP callbackasm1(SB) + MOVV $1465, R12 + JMP callbackasm1(SB) + MOVV $1466, R12 + JMP callbackasm1(SB) + MOVV $1467, R12 + JMP callbackasm1(SB) + MOVV $1468, R12 + JMP callbackasm1(SB) + MOVV $1469, R12 + JMP callbackasm1(SB) + MOVV $1470, R12 + JMP callbackasm1(SB) + MOVV $1471, R12 + JMP callbackasm1(SB) + MOVV $1472, R12 + JMP callbackasm1(SB) + MOVV $1473, R12 + JMP callbackasm1(SB) + MOVV $1474, R12 + JMP callbackasm1(SB) + MOVV $1475, R12 + JMP callbackasm1(SB) + MOVV $1476, R12 + JMP callbackasm1(SB) + MOVV $1477, R12 + JMP callbackasm1(SB) + MOVV $1478, R12 + JMP callbackasm1(SB) + MOVV $1479, R12 + JMP callbackasm1(SB) + MOVV $1480, R12 + JMP callbackasm1(SB) + MOVV $1481, R12 + JMP callbackasm1(SB) + MOVV $1482, R12 + JMP callbackasm1(SB) + MOVV $1483, R12 + JMP callbackasm1(SB) + MOVV $1484, R12 + JMP callbackasm1(SB) + MOVV $1485, R12 + JMP callbackasm1(SB) + MOVV $1486, R12 + JMP callbackasm1(SB) + MOVV $1487, R12 + JMP callbackasm1(SB) + MOVV $1488, R12 + JMP callbackasm1(SB) + MOVV $1489, R12 + JMP callbackasm1(SB) + MOVV $1490, R12 + JMP callbackasm1(SB) + MOVV $1491, R12 + JMP callbackasm1(SB) + MOVV $1492, R12 + JMP callbackasm1(SB) + MOVV $1493, R12 + JMP callbackasm1(SB) + MOVV $1494, R12 + JMP callbackasm1(SB) + MOVV $1495, R12 + JMP callbackasm1(SB) + MOVV $1496, R12 + JMP callbackasm1(SB) + MOVV $1497, R12 + JMP callbackasm1(SB) + MOVV $1498, R12 + JMP callbackasm1(SB) + MOVV $1499, R12 + JMP callbackasm1(SB) + MOVV $1500, R12 + JMP callbackasm1(SB) + MOVV $1501, R12 + JMP callbackasm1(SB) + MOVV $1502, R12 + JMP callbackasm1(SB) + MOVV $1503, R12 + JMP callbackasm1(SB) + MOVV $1504, R12 + JMP callbackasm1(SB) + MOVV $1505, R12 + JMP callbackasm1(SB) + MOVV $1506, R12 + JMP callbackasm1(SB) + MOVV $1507, R12 + JMP callbackasm1(SB) + MOVV $1508, R12 + JMP callbackasm1(SB) + MOVV $1509, R12 + JMP callbackasm1(SB) + MOVV $1510, R12 + JMP callbackasm1(SB) + MOVV $1511, R12 + JMP callbackasm1(SB) + MOVV $1512, R12 + JMP callbackasm1(SB) + MOVV $1513, R12 + JMP callbackasm1(SB) + MOVV $1514, R12 + JMP callbackasm1(SB) + MOVV $1515, R12 + JMP callbackasm1(SB) + MOVV $1516, R12 + JMP callbackasm1(SB) + MOVV $1517, R12 + JMP callbackasm1(SB) + MOVV $1518, R12 + JMP callbackasm1(SB) + MOVV $1519, R12 + JMP callbackasm1(SB) + MOVV $1520, R12 + JMP callbackasm1(SB) + MOVV $1521, R12 + JMP callbackasm1(SB) + MOVV $1522, R12 + JMP callbackasm1(SB) + MOVV $1523, R12 + JMP callbackasm1(SB) + MOVV $1524, R12 + JMP callbackasm1(SB) + MOVV $1525, R12 + JMP callbackasm1(SB) + MOVV $1526, R12 + JMP callbackasm1(SB) + MOVV $1527, R12 + JMP callbackasm1(SB) + MOVV $1528, R12 + JMP callbackasm1(SB) + MOVV $1529, R12 + JMP callbackasm1(SB) + MOVV $1530, R12 + JMP callbackasm1(SB) + MOVV $1531, R12 + JMP callbackasm1(SB) + MOVV $1532, R12 + JMP callbackasm1(SB) + MOVV $1533, R12 + JMP callbackasm1(SB) + MOVV $1534, R12 + JMP callbackasm1(SB) + MOVV $1535, R12 + JMP callbackasm1(SB) + MOVV $1536, R12 + JMP callbackasm1(SB) + MOVV $1537, R12 + JMP callbackasm1(SB) + MOVV $1538, R12 + JMP callbackasm1(SB) + MOVV $1539, R12 + JMP callbackasm1(SB) + MOVV $1540, R12 + JMP callbackasm1(SB) + MOVV $1541, R12 + JMP callbackasm1(SB) + MOVV $1542, R12 + JMP callbackasm1(SB) + MOVV $1543, R12 + JMP callbackasm1(SB) + MOVV $1544, R12 + JMP callbackasm1(SB) + MOVV $1545, R12 + JMP callbackasm1(SB) + MOVV $1546, R12 + JMP callbackasm1(SB) + MOVV $1547, R12 + JMP callbackasm1(SB) + MOVV $1548, R12 + JMP callbackasm1(SB) + MOVV $1549, R12 + JMP callbackasm1(SB) + MOVV $1550, R12 + JMP callbackasm1(SB) + MOVV $1551, R12 + JMP callbackasm1(SB) + MOVV $1552, R12 + JMP callbackasm1(SB) + MOVV $1553, R12 + JMP callbackasm1(SB) + MOVV $1554, R12 + JMP callbackasm1(SB) + MOVV $1555, R12 + JMP callbackasm1(SB) + MOVV $1556, R12 + JMP callbackasm1(SB) + MOVV $1557, R12 + JMP callbackasm1(SB) + MOVV $1558, R12 + JMP callbackasm1(SB) + MOVV $1559, R12 + JMP callbackasm1(SB) + MOVV $1560, R12 + JMP callbackasm1(SB) + MOVV $1561, R12 + JMP callbackasm1(SB) + MOVV $1562, R12 + JMP callbackasm1(SB) + MOVV $1563, R12 + JMP callbackasm1(SB) + MOVV $1564, R12 + JMP callbackasm1(SB) + MOVV $1565, R12 + JMP callbackasm1(SB) + MOVV $1566, R12 + JMP callbackasm1(SB) + MOVV $1567, R12 + JMP callbackasm1(SB) + MOVV $1568, R12 + JMP callbackasm1(SB) + MOVV $1569, R12 + JMP callbackasm1(SB) + MOVV $1570, R12 + JMP callbackasm1(SB) + MOVV $1571, R12 + JMP callbackasm1(SB) + MOVV $1572, R12 + JMP callbackasm1(SB) + MOVV $1573, R12 + JMP callbackasm1(SB) + MOVV $1574, R12 + JMP callbackasm1(SB) + MOVV $1575, R12 + JMP callbackasm1(SB) + MOVV $1576, R12 + JMP callbackasm1(SB) + MOVV $1577, R12 + JMP callbackasm1(SB) + MOVV $1578, R12 + JMP callbackasm1(SB) + MOVV $1579, R12 + JMP callbackasm1(SB) + MOVV $1580, R12 + JMP callbackasm1(SB) + MOVV $1581, R12 + JMP callbackasm1(SB) + MOVV $1582, R12 + JMP callbackasm1(SB) + MOVV $1583, R12 + JMP callbackasm1(SB) + MOVV $1584, R12 + JMP callbackasm1(SB) + MOVV $1585, R12 + JMP callbackasm1(SB) + MOVV $1586, R12 + JMP callbackasm1(SB) + MOVV $1587, R12 + JMP callbackasm1(SB) + MOVV $1588, R12 + JMP callbackasm1(SB) + MOVV $1589, R12 + JMP callbackasm1(SB) + MOVV $1590, R12 + JMP callbackasm1(SB) + MOVV $1591, R12 + JMP callbackasm1(SB) + MOVV $1592, R12 + JMP callbackasm1(SB) + MOVV $1593, R12 + JMP callbackasm1(SB) + MOVV $1594, R12 + JMP callbackasm1(SB) + MOVV $1595, R12 + JMP callbackasm1(SB) + MOVV $1596, R12 + JMP callbackasm1(SB) + MOVV $1597, R12 + JMP callbackasm1(SB) + MOVV $1598, R12 + JMP callbackasm1(SB) + MOVV $1599, R12 + JMP callbackasm1(SB) + MOVV $1600, R12 + JMP callbackasm1(SB) + MOVV $1601, R12 + JMP callbackasm1(SB) + MOVV $1602, R12 + JMP callbackasm1(SB) + MOVV $1603, R12 + JMP callbackasm1(SB) + MOVV $1604, R12 + JMP callbackasm1(SB) + MOVV $1605, R12 + JMP callbackasm1(SB) + MOVV $1606, R12 + JMP callbackasm1(SB) + MOVV $1607, R12 + JMP callbackasm1(SB) + MOVV $1608, R12 + JMP callbackasm1(SB) + MOVV $1609, R12 + JMP callbackasm1(SB) + MOVV $1610, R12 + JMP callbackasm1(SB) + MOVV $1611, R12 + JMP callbackasm1(SB) + MOVV $1612, R12 + JMP callbackasm1(SB) + MOVV $1613, R12 + JMP callbackasm1(SB) + MOVV $1614, R12 + JMP callbackasm1(SB) + MOVV $1615, R12 + JMP callbackasm1(SB) + MOVV $1616, R12 + JMP callbackasm1(SB) + MOVV $1617, R12 + JMP callbackasm1(SB) + MOVV $1618, R12 + JMP callbackasm1(SB) + MOVV $1619, R12 + JMP callbackasm1(SB) + MOVV $1620, R12 + JMP callbackasm1(SB) + MOVV $1621, R12 + JMP callbackasm1(SB) + MOVV $1622, R12 + JMP callbackasm1(SB) + MOVV $1623, R12 + JMP callbackasm1(SB) + MOVV $1624, R12 + JMP callbackasm1(SB) + MOVV $1625, R12 + JMP callbackasm1(SB) + MOVV $1626, R12 + JMP callbackasm1(SB) + MOVV $1627, R12 + JMP callbackasm1(SB) + MOVV $1628, R12 + JMP callbackasm1(SB) + MOVV $1629, R12 + JMP callbackasm1(SB) + MOVV $1630, R12 + JMP callbackasm1(SB) + MOVV $1631, R12 + JMP callbackasm1(SB) + MOVV $1632, R12 + JMP callbackasm1(SB) + MOVV $1633, R12 + JMP callbackasm1(SB) + MOVV $1634, R12 + JMP callbackasm1(SB) + MOVV $1635, R12 + JMP callbackasm1(SB) + MOVV $1636, R12 + JMP callbackasm1(SB) + MOVV $1637, R12 + JMP callbackasm1(SB) + MOVV $1638, R12 + JMP callbackasm1(SB) + MOVV $1639, R12 + JMP callbackasm1(SB) + MOVV $1640, R12 + JMP callbackasm1(SB) + MOVV $1641, R12 + JMP callbackasm1(SB) + MOVV $1642, R12 + JMP callbackasm1(SB) + MOVV $1643, R12 + JMP callbackasm1(SB) + MOVV $1644, R12 + JMP callbackasm1(SB) + MOVV $1645, R12 + JMP callbackasm1(SB) + MOVV $1646, R12 + JMP callbackasm1(SB) + MOVV $1647, R12 + JMP callbackasm1(SB) + MOVV $1648, R12 + JMP callbackasm1(SB) + MOVV $1649, R12 + JMP callbackasm1(SB) + MOVV $1650, R12 + JMP callbackasm1(SB) + MOVV $1651, R12 + JMP callbackasm1(SB) + MOVV $1652, R12 + JMP callbackasm1(SB) + MOVV $1653, R12 + JMP callbackasm1(SB) + MOVV $1654, R12 + JMP callbackasm1(SB) + MOVV $1655, R12 + JMP callbackasm1(SB) + MOVV $1656, R12 + JMP callbackasm1(SB) + MOVV $1657, R12 + JMP callbackasm1(SB) + MOVV $1658, R12 + JMP callbackasm1(SB) + MOVV $1659, R12 + JMP callbackasm1(SB) + MOVV $1660, R12 + JMP callbackasm1(SB) + MOVV $1661, R12 + JMP callbackasm1(SB) + MOVV $1662, R12 + JMP callbackasm1(SB) + MOVV $1663, R12 + JMP callbackasm1(SB) + MOVV $1664, R12 + JMP callbackasm1(SB) + MOVV $1665, R12 + JMP callbackasm1(SB) + MOVV $1666, R12 + JMP callbackasm1(SB) + MOVV $1667, R12 + JMP callbackasm1(SB) + MOVV $1668, R12 + JMP callbackasm1(SB) + MOVV $1669, R12 + JMP callbackasm1(SB) + MOVV $1670, R12 + JMP callbackasm1(SB) + MOVV $1671, R12 + JMP callbackasm1(SB) + MOVV $1672, R12 + JMP callbackasm1(SB) + MOVV $1673, R12 + JMP callbackasm1(SB) + MOVV $1674, R12 + JMP callbackasm1(SB) + MOVV $1675, R12 + JMP callbackasm1(SB) + MOVV $1676, R12 + JMP callbackasm1(SB) + MOVV $1677, R12 + JMP callbackasm1(SB) + MOVV $1678, R12 + JMP callbackasm1(SB) + MOVV $1679, R12 + JMP callbackasm1(SB) + MOVV $1680, R12 + JMP callbackasm1(SB) + MOVV $1681, R12 + JMP callbackasm1(SB) + MOVV $1682, R12 + JMP callbackasm1(SB) + MOVV $1683, R12 + JMP callbackasm1(SB) + MOVV $1684, R12 + JMP callbackasm1(SB) + MOVV $1685, R12 + JMP callbackasm1(SB) + MOVV $1686, R12 + JMP callbackasm1(SB) + MOVV $1687, R12 + JMP callbackasm1(SB) + MOVV $1688, R12 + JMP callbackasm1(SB) + MOVV $1689, R12 + JMP callbackasm1(SB) + MOVV $1690, R12 + JMP callbackasm1(SB) + MOVV $1691, R12 + JMP callbackasm1(SB) + MOVV $1692, R12 + JMP callbackasm1(SB) + MOVV $1693, R12 + JMP callbackasm1(SB) + MOVV $1694, R12 + JMP callbackasm1(SB) + MOVV $1695, R12 + JMP callbackasm1(SB) + MOVV $1696, R12 + JMP callbackasm1(SB) + MOVV $1697, R12 + JMP callbackasm1(SB) + MOVV $1698, R12 + JMP callbackasm1(SB) + MOVV $1699, R12 + JMP callbackasm1(SB) + MOVV $1700, R12 + JMP callbackasm1(SB) + MOVV $1701, R12 + JMP callbackasm1(SB) + MOVV $1702, R12 + JMP callbackasm1(SB) + MOVV $1703, R12 + JMP callbackasm1(SB) + MOVV $1704, R12 + JMP callbackasm1(SB) + MOVV $1705, R12 + JMP callbackasm1(SB) + MOVV $1706, R12 + JMP callbackasm1(SB) + MOVV $1707, R12 + JMP callbackasm1(SB) + MOVV $1708, R12 + JMP callbackasm1(SB) + MOVV $1709, R12 + JMP callbackasm1(SB) + MOVV $1710, R12 + JMP callbackasm1(SB) + MOVV $1711, R12 + JMP callbackasm1(SB) + MOVV $1712, R12 + JMP callbackasm1(SB) + MOVV $1713, R12 + JMP callbackasm1(SB) + MOVV $1714, R12 + JMP callbackasm1(SB) + MOVV $1715, R12 + JMP callbackasm1(SB) + MOVV $1716, R12 + JMP callbackasm1(SB) + MOVV $1717, R12 + JMP callbackasm1(SB) + MOVV $1718, R12 + JMP callbackasm1(SB) + MOVV $1719, R12 + JMP callbackasm1(SB) + MOVV $1720, R12 + JMP callbackasm1(SB) + MOVV $1721, R12 + JMP callbackasm1(SB) + MOVV $1722, R12 + JMP callbackasm1(SB) + MOVV $1723, R12 + JMP callbackasm1(SB) + MOVV $1724, R12 + JMP callbackasm1(SB) + MOVV $1725, R12 + JMP callbackasm1(SB) + MOVV $1726, R12 + JMP callbackasm1(SB) + MOVV $1727, R12 + JMP callbackasm1(SB) + MOVV $1728, R12 + JMP callbackasm1(SB) + MOVV $1729, R12 + JMP callbackasm1(SB) + MOVV $1730, R12 + JMP callbackasm1(SB) + MOVV $1731, R12 + JMP callbackasm1(SB) + MOVV $1732, R12 + JMP callbackasm1(SB) + MOVV $1733, R12 + JMP callbackasm1(SB) + MOVV $1734, R12 + JMP callbackasm1(SB) + MOVV $1735, R12 + JMP callbackasm1(SB) + MOVV $1736, R12 + JMP callbackasm1(SB) + MOVV $1737, R12 + JMP callbackasm1(SB) + MOVV $1738, R12 + JMP callbackasm1(SB) + MOVV $1739, R12 + JMP callbackasm1(SB) + MOVV $1740, R12 + JMP callbackasm1(SB) + MOVV $1741, R12 + JMP callbackasm1(SB) + MOVV $1742, R12 + JMP callbackasm1(SB) + MOVV $1743, R12 + JMP callbackasm1(SB) + MOVV $1744, R12 + JMP callbackasm1(SB) + MOVV $1745, R12 + JMP callbackasm1(SB) + MOVV $1746, R12 + JMP callbackasm1(SB) + MOVV $1747, R12 + JMP callbackasm1(SB) + MOVV $1748, R12 + JMP callbackasm1(SB) + MOVV $1749, R12 + JMP callbackasm1(SB) + MOVV $1750, R12 + JMP callbackasm1(SB) + MOVV $1751, R12 + JMP callbackasm1(SB) + MOVV $1752, R12 + JMP callbackasm1(SB) + MOVV $1753, R12 + JMP callbackasm1(SB) + MOVV $1754, R12 + JMP callbackasm1(SB) + MOVV $1755, R12 + JMP callbackasm1(SB) + MOVV $1756, R12 + JMP callbackasm1(SB) + MOVV $1757, R12 + JMP callbackasm1(SB) + MOVV $1758, R12 + JMP callbackasm1(SB) + MOVV $1759, R12 + JMP callbackasm1(SB) + MOVV $1760, R12 + JMP callbackasm1(SB) + MOVV $1761, R12 + JMP callbackasm1(SB) + MOVV $1762, R12 + JMP callbackasm1(SB) + MOVV $1763, R12 + JMP callbackasm1(SB) + MOVV $1764, R12 + JMP callbackasm1(SB) + MOVV $1765, R12 + JMP callbackasm1(SB) + MOVV $1766, R12 + JMP callbackasm1(SB) + MOVV $1767, R12 + JMP callbackasm1(SB) + MOVV $1768, R12 + JMP callbackasm1(SB) + MOVV $1769, R12 + JMP callbackasm1(SB) + MOVV $1770, R12 + JMP callbackasm1(SB) + MOVV $1771, R12 + JMP callbackasm1(SB) + MOVV $1772, R12 + JMP callbackasm1(SB) + MOVV $1773, R12 + JMP callbackasm1(SB) + MOVV $1774, R12 + JMP callbackasm1(SB) + MOVV $1775, R12 + JMP callbackasm1(SB) + MOVV $1776, R12 + JMP callbackasm1(SB) + MOVV $1777, R12 + JMP callbackasm1(SB) + MOVV $1778, R12 + JMP callbackasm1(SB) + MOVV $1779, R12 + JMP callbackasm1(SB) + MOVV $1780, R12 + JMP callbackasm1(SB) + MOVV $1781, R12 + JMP callbackasm1(SB) + MOVV $1782, R12 + JMP callbackasm1(SB) + MOVV $1783, R12 + JMP callbackasm1(SB) + MOVV $1784, R12 + JMP callbackasm1(SB) + MOVV $1785, R12 + JMP callbackasm1(SB) + MOVV $1786, R12 + JMP callbackasm1(SB) + MOVV $1787, R12 + JMP callbackasm1(SB) + MOVV $1788, R12 + JMP callbackasm1(SB) + MOVV $1789, R12 + JMP callbackasm1(SB) + MOVV $1790, R12 + JMP callbackasm1(SB) + MOVV $1791, R12 + JMP callbackasm1(SB) + MOVV $1792, R12 + JMP callbackasm1(SB) + MOVV $1793, R12 + JMP callbackasm1(SB) + MOVV $1794, R12 + JMP callbackasm1(SB) + MOVV $1795, R12 + JMP callbackasm1(SB) + MOVV $1796, R12 + JMP callbackasm1(SB) + MOVV $1797, R12 + JMP callbackasm1(SB) + MOVV $1798, R12 + JMP callbackasm1(SB) + MOVV $1799, R12 + JMP callbackasm1(SB) + MOVV $1800, R12 + JMP callbackasm1(SB) + MOVV $1801, R12 + JMP callbackasm1(SB) + MOVV $1802, R12 + JMP callbackasm1(SB) + MOVV $1803, R12 + JMP callbackasm1(SB) + MOVV $1804, R12 + JMP callbackasm1(SB) + MOVV $1805, R12 + JMP callbackasm1(SB) + MOVV $1806, R12 + JMP callbackasm1(SB) + MOVV $1807, R12 + JMP callbackasm1(SB) + MOVV $1808, R12 + JMP callbackasm1(SB) + MOVV $1809, R12 + JMP callbackasm1(SB) + MOVV $1810, R12 + JMP callbackasm1(SB) + MOVV $1811, R12 + JMP callbackasm1(SB) + MOVV $1812, R12 + JMP callbackasm1(SB) + MOVV $1813, R12 + JMP callbackasm1(SB) + MOVV $1814, R12 + JMP callbackasm1(SB) + MOVV $1815, R12 + JMP callbackasm1(SB) + MOVV $1816, R12 + JMP callbackasm1(SB) + MOVV $1817, R12 + JMP callbackasm1(SB) + MOVV $1818, R12 + JMP callbackasm1(SB) + MOVV $1819, R12 + JMP callbackasm1(SB) + MOVV $1820, R12 + JMP callbackasm1(SB) + MOVV $1821, R12 + JMP callbackasm1(SB) + MOVV $1822, R12 + JMP callbackasm1(SB) + MOVV $1823, R12 + JMP callbackasm1(SB) + MOVV $1824, R12 + JMP callbackasm1(SB) + MOVV $1825, R12 + JMP callbackasm1(SB) + MOVV $1826, R12 + JMP callbackasm1(SB) + MOVV $1827, R12 + JMP callbackasm1(SB) + MOVV $1828, R12 + JMP callbackasm1(SB) + MOVV $1829, R12 + JMP callbackasm1(SB) + MOVV $1830, R12 + JMP callbackasm1(SB) + MOVV $1831, R12 + JMP callbackasm1(SB) + MOVV $1832, R12 + JMP callbackasm1(SB) + MOVV $1833, R12 + JMP callbackasm1(SB) + MOVV $1834, R12 + JMP callbackasm1(SB) + MOVV $1835, R12 + JMP callbackasm1(SB) + MOVV $1836, R12 + JMP callbackasm1(SB) + MOVV $1837, R12 + JMP callbackasm1(SB) + MOVV $1838, R12 + JMP callbackasm1(SB) + MOVV $1839, R12 + JMP callbackasm1(SB) + MOVV $1840, R12 + JMP callbackasm1(SB) + MOVV $1841, R12 + JMP callbackasm1(SB) + MOVV $1842, R12 + JMP callbackasm1(SB) + MOVV $1843, R12 + JMP callbackasm1(SB) + MOVV $1844, R12 + JMP callbackasm1(SB) + MOVV $1845, R12 + JMP callbackasm1(SB) + MOVV $1846, R12 + JMP callbackasm1(SB) + MOVV $1847, R12 + JMP callbackasm1(SB) + MOVV $1848, R12 + JMP callbackasm1(SB) + MOVV $1849, R12 + JMP callbackasm1(SB) + MOVV $1850, R12 + JMP callbackasm1(SB) + MOVV $1851, R12 + JMP callbackasm1(SB) + MOVV $1852, R12 + JMP callbackasm1(SB) + MOVV $1853, R12 + JMP callbackasm1(SB) + MOVV $1854, R12 + JMP callbackasm1(SB) + MOVV $1855, R12 + JMP callbackasm1(SB) + MOVV $1856, R12 + JMP callbackasm1(SB) + MOVV $1857, R12 + JMP callbackasm1(SB) + MOVV $1858, R12 + JMP callbackasm1(SB) + MOVV $1859, R12 + JMP callbackasm1(SB) + MOVV $1860, R12 + JMP callbackasm1(SB) + MOVV $1861, R12 + JMP callbackasm1(SB) + MOVV $1862, R12 + JMP callbackasm1(SB) + MOVV $1863, R12 + JMP callbackasm1(SB) + MOVV $1864, R12 + JMP callbackasm1(SB) + MOVV $1865, R12 + JMP callbackasm1(SB) + MOVV $1866, R12 + JMP callbackasm1(SB) + MOVV $1867, R12 + JMP callbackasm1(SB) + MOVV $1868, R12 + JMP callbackasm1(SB) + MOVV $1869, R12 + JMP callbackasm1(SB) + MOVV $1870, R12 + JMP callbackasm1(SB) + MOVV $1871, R12 + JMP callbackasm1(SB) + MOVV $1872, R12 + JMP callbackasm1(SB) + MOVV $1873, R12 + JMP callbackasm1(SB) + MOVV $1874, R12 + JMP callbackasm1(SB) + MOVV $1875, R12 + JMP callbackasm1(SB) + MOVV $1876, R12 + JMP callbackasm1(SB) + MOVV $1877, R12 + JMP callbackasm1(SB) + MOVV $1878, R12 + JMP callbackasm1(SB) + MOVV $1879, R12 + JMP callbackasm1(SB) + MOVV $1880, R12 + JMP callbackasm1(SB) + MOVV $1881, R12 + JMP callbackasm1(SB) + MOVV $1882, R12 + JMP callbackasm1(SB) + MOVV $1883, R12 + JMP callbackasm1(SB) + MOVV $1884, R12 + JMP callbackasm1(SB) + MOVV $1885, R12 + JMP callbackasm1(SB) + MOVV $1886, R12 + JMP callbackasm1(SB) + MOVV $1887, R12 + JMP callbackasm1(SB) + MOVV $1888, R12 + JMP callbackasm1(SB) + MOVV $1889, R12 + JMP callbackasm1(SB) + MOVV $1890, R12 + JMP callbackasm1(SB) + MOVV $1891, R12 + JMP callbackasm1(SB) + MOVV $1892, R12 + JMP callbackasm1(SB) + MOVV $1893, R12 + JMP callbackasm1(SB) + MOVV $1894, R12 + JMP callbackasm1(SB) + MOVV $1895, R12 + JMP callbackasm1(SB) + MOVV $1896, R12 + JMP callbackasm1(SB) + MOVV $1897, R12 + JMP callbackasm1(SB) + MOVV $1898, R12 + JMP callbackasm1(SB) + MOVV $1899, R12 + JMP callbackasm1(SB) + MOVV $1900, R12 + JMP callbackasm1(SB) + MOVV $1901, R12 + JMP callbackasm1(SB) + MOVV $1902, R12 + JMP callbackasm1(SB) + MOVV $1903, R12 + JMP callbackasm1(SB) + MOVV $1904, R12 + JMP callbackasm1(SB) + MOVV $1905, R12 + JMP callbackasm1(SB) + MOVV $1906, R12 + JMP callbackasm1(SB) + MOVV $1907, R12 + JMP callbackasm1(SB) + MOVV $1908, R12 + JMP callbackasm1(SB) + MOVV $1909, R12 + JMP callbackasm1(SB) + MOVV $1910, R12 + JMP callbackasm1(SB) + MOVV $1911, R12 + JMP callbackasm1(SB) + MOVV $1912, R12 + JMP callbackasm1(SB) + MOVV $1913, R12 + JMP callbackasm1(SB) + MOVV $1914, R12 + JMP callbackasm1(SB) + MOVV $1915, R12 + JMP callbackasm1(SB) + MOVV $1916, R12 + JMP callbackasm1(SB) + MOVV $1917, R12 + JMP callbackasm1(SB) + MOVV $1918, R12 + JMP callbackasm1(SB) + MOVV $1919, R12 + JMP callbackasm1(SB) + MOVV $1920, R12 + JMP callbackasm1(SB) + MOVV $1921, R12 + JMP callbackasm1(SB) + MOVV $1922, R12 + JMP callbackasm1(SB) + MOVV $1923, R12 + JMP callbackasm1(SB) + MOVV $1924, R12 + JMP callbackasm1(SB) + MOVV $1925, R12 + JMP callbackasm1(SB) + MOVV $1926, R12 + JMP callbackasm1(SB) + MOVV $1927, R12 + JMP callbackasm1(SB) + MOVV $1928, R12 + JMP callbackasm1(SB) + MOVV $1929, R12 + JMP callbackasm1(SB) + MOVV $1930, R12 + JMP callbackasm1(SB) + MOVV $1931, R12 + JMP callbackasm1(SB) + MOVV $1932, R12 + JMP callbackasm1(SB) + MOVV $1933, R12 + JMP callbackasm1(SB) + MOVV $1934, R12 + JMP callbackasm1(SB) + MOVV $1935, R12 + JMP callbackasm1(SB) + MOVV $1936, R12 + JMP callbackasm1(SB) + MOVV $1937, R12 + JMP callbackasm1(SB) + MOVV $1938, R12 + JMP callbackasm1(SB) + MOVV $1939, R12 + JMP callbackasm1(SB) + MOVV $1940, R12 + JMP callbackasm1(SB) + MOVV $1941, R12 + JMP callbackasm1(SB) + MOVV $1942, R12 + JMP callbackasm1(SB) + MOVV $1943, R12 + JMP callbackasm1(SB) + MOVV $1944, R12 + JMP callbackasm1(SB) + MOVV $1945, R12 + JMP callbackasm1(SB) + MOVV $1946, R12 + JMP callbackasm1(SB) + MOVV $1947, R12 + JMP callbackasm1(SB) + MOVV $1948, R12 + JMP callbackasm1(SB) + MOVV $1949, R12 + JMP callbackasm1(SB) + MOVV $1950, R12 + JMP callbackasm1(SB) + MOVV $1951, R12 + JMP callbackasm1(SB) + MOVV $1952, R12 + JMP callbackasm1(SB) + MOVV $1953, R12 + JMP callbackasm1(SB) + MOVV $1954, R12 + JMP callbackasm1(SB) + MOVV $1955, R12 + JMP callbackasm1(SB) + MOVV $1956, R12 + JMP callbackasm1(SB) + MOVV $1957, R12 + JMP callbackasm1(SB) + MOVV $1958, R12 + JMP callbackasm1(SB) + MOVV $1959, R12 + JMP callbackasm1(SB) + MOVV $1960, R12 + JMP callbackasm1(SB) + MOVV $1961, R12 + JMP callbackasm1(SB) + MOVV $1962, R12 + JMP callbackasm1(SB) + MOVV $1963, R12 + JMP callbackasm1(SB) + MOVV $1964, R12 + JMP callbackasm1(SB) + MOVV $1965, R12 + JMP callbackasm1(SB) + MOVV $1966, R12 + JMP callbackasm1(SB) + MOVV $1967, R12 + JMP callbackasm1(SB) + MOVV $1968, R12 + JMP callbackasm1(SB) + MOVV $1969, R12 + JMP callbackasm1(SB) + MOVV $1970, R12 + JMP callbackasm1(SB) + MOVV $1971, R12 + JMP callbackasm1(SB) + MOVV $1972, R12 + JMP callbackasm1(SB) + MOVV $1973, R12 + JMP callbackasm1(SB) + MOVV $1974, R12 + JMP callbackasm1(SB) + MOVV $1975, R12 + JMP callbackasm1(SB) + MOVV $1976, R12 + JMP callbackasm1(SB) + MOVV $1977, R12 + JMP callbackasm1(SB) + MOVV $1978, R12 + JMP callbackasm1(SB) + MOVV $1979, R12 + JMP callbackasm1(SB) + MOVV $1980, R12 + JMP callbackasm1(SB) + MOVV $1981, R12 + JMP callbackasm1(SB) + MOVV $1982, R12 + JMP callbackasm1(SB) + MOVV $1983, R12 + JMP callbackasm1(SB) + MOVV $1984, R12 + JMP callbackasm1(SB) + MOVV $1985, R12 + JMP callbackasm1(SB) + MOVV $1986, R12 + JMP callbackasm1(SB) + MOVV $1987, R12 + JMP callbackasm1(SB) + MOVV $1988, R12 + JMP callbackasm1(SB) + MOVV $1989, R12 + JMP callbackasm1(SB) + MOVV $1990, R12 + JMP callbackasm1(SB) + MOVV $1991, R12 + JMP callbackasm1(SB) + MOVV $1992, R12 + JMP callbackasm1(SB) + MOVV $1993, R12 + JMP callbackasm1(SB) + MOVV $1994, R12 + JMP callbackasm1(SB) + MOVV $1995, R12 + JMP callbackasm1(SB) + MOVV $1996, R12 + JMP callbackasm1(SB) + MOVV $1997, R12 + JMP callbackasm1(SB) + MOVV $1998, R12 + JMP callbackasm1(SB) + MOVV $1999, R12 + JMP callbackasm1(SB) diff --git a/vendor/github.com/fatih/color/README.md b/vendor/github.com/fatih/color/README.md index d62e4024..be82827c 100644 --- a/vendor/github.com/fatih/color/README.md +++ b/vendor/github.com/fatih/color/README.md @@ -7,7 +7,6 @@ suits you. ![Color](https://user-images.githubusercontent.com/438920/96832689-03b3e000-13f4-11eb-9803-46f4c4de3406.jpg) - ## Install ```bash @@ -78,7 +77,7 @@ notice("Don't forget this...") ### Custom fprint functions (FprintFunc) ```go -blue := color.New(FgBlue).FprintfFunc() +blue := color.New(color.FgBlue).FprintfFunc() blue(myWriter, "important notice: %s", stars) // Mix up with multiple attributes @@ -124,17 +123,19 @@ fmt.Println("All text will now be bold magenta.") ``` ### Disable/Enable color - + There might be a case where you want to explicitly disable/enable color output. the `go-isatty` package will automatically disable color output for non-tty output streams -(for example if the output were piped directly to `less`) +(for example if the output were piped directly to `less`). -`Color` has support to disable/enable colors both globally and for single color -definitions. For example suppose you have a CLI app and a `--no-color` bool flag. You -can easily disable the color output with: +The `color` package also disables color output if the [`NO_COLOR`](https://no-color.org) environment +variable is set to a non-empty string. + +`Color` has support to disable/enable colors programmatically both globally and +for single color definitions. For example suppose you have a CLI app and a +`-no-color` bool flag. You can easily disable the color output with: ```go - var flagNoColor = flag.Bool("no-color", false, "Disable color output") if *flagNoColor { @@ -156,18 +157,20 @@ c.EnableColor() c.Println("This prints again cyan...") ``` +## GitHub Actions + +To output color in GitHub Actions (or other CI systems that support ANSI colors), make sure to set `color.NoColor = false` so that it bypasses the check for non-tty output streams. + ## Todo * Save/Return previous values * Evaluate fmt.Formatter interface - ## Credits - * [Fatih Arslan](https://github.com/fatih) - * Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable) +* [Fatih Arslan](https://github.com/fatih) +* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable) ## License The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details - diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go index 91c8e9f0..889f9e77 100644 --- a/vendor/github.com/fatih/color/color.go +++ b/vendor/github.com/fatih/color/color.go @@ -15,12 +15,14 @@ import ( var ( // NoColor defines if the output is colorized or not. It's dynamically set to // false or true based on the stdout's file descriptor referring to a terminal - // or not. This is a global option and affects all colors. For more control - // over each color block use the methods DisableColor() individually. - NoColor = os.Getenv("TERM") == "dumb" || + // or not. It's also set to true if the NO_COLOR environment variable is + // set (regardless of its value). This is a global option and affects all + // colors. For more control over each color block use the methods + // DisableColor() individually. + NoColor = noColorIsSet() || os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) - // Output defines the standard output of the print functions. By default + // Output defines the standard output of the print functions. By default, // os.Stdout is used. Output = colorable.NewColorableStdout() @@ -33,6 +35,11 @@ var ( colorsCacheMu sync.Mutex // protects colorsCache ) +// noColorIsSet returns true if the environment variable NO_COLOR is set to a non-empty string. +func noColorIsSet() bool { + return os.Getenv("NO_COLOR") != "" +} + // Color defines a custom color object which is defined by SGR parameters. type Color struct { params []Attribute @@ -108,7 +115,14 @@ const ( // New returns a newly created color object. func New(value ...Attribute) *Color { - c := &Color{params: make([]Attribute, 0)} + c := &Color{ + params: make([]Attribute, 0), + } + + if noColorIsSet() { + c.noColor = boolPtr(true) + } + c.Add(value...) return c } @@ -137,7 +151,7 @@ func (c *Color) Set() *Color { return c } - fmt.Fprintf(Output, c.format()) + fmt.Fprint(Output, c.format()) return c } @@ -149,16 +163,21 @@ func (c *Color) unset() { Unset() } -func (c *Color) setWriter(w io.Writer) *Color { +// SetWriter is used to set the SGR sequence with the given io.Writer. This is +// a low-level function, and users should use the higher-level functions, such +// as color.Fprint, color.Print, etc. +func (c *Color) SetWriter(w io.Writer) *Color { if c.isNoColorSet() { return c } - fmt.Fprintf(w, c.format()) + fmt.Fprint(w, c.format()) return c } -func (c *Color) unsetWriter(w io.Writer) { +// UnsetWriter resets all escape attributes and clears the output with the give +// io.Writer. Usually should be called after SetWriter(). +func (c *Color) UnsetWriter(w io.Writer) { if c.isNoColorSet() { return } @@ -177,20 +196,14 @@ func (c *Color) Add(value ...Attribute) *Color { return c } -func (c *Color) prepend(value Attribute) { - c.params = append(c.params, 0) - copy(c.params[1:], c.params[0:]) - c.params[0] = value -} - // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. // On Windows, users should wrap w with colorable.NewColorable() if w is of // type *os.File. func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) { - c.setWriter(w) - defer c.unsetWriter(w) + c.SetWriter(w) + defer c.UnsetWriter(w) return fmt.Fprint(w, a...) } @@ -212,8 +225,8 @@ func (c *Color) Print(a ...interface{}) (n int, err error) { // On Windows, users should wrap w with colorable.NewColorable() if w is of // type *os.File. func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { - c.setWriter(w) - defer c.unsetWriter(w) + c.SetWriter(w) + defer c.UnsetWriter(w) return fmt.Fprintf(w, format, a...) } @@ -233,8 +246,8 @@ func (c *Color) Printf(format string, a ...interface{}) (n int, err error) { // On Windows, users should wrap w with colorable.NewColorable() if w is of // type *os.File. func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { - c.setWriter(w) - defer c.unsetWriter(w) + c.SetWriter(w) + defer c.UnsetWriter(w) return fmt.Fprintln(w, a...) } @@ -381,13 +394,13 @@ func (c *Color) DisableColor() { } // EnableColor enables the color output. Use it in conjunction with -// DisableColor(). Otherwise this method has no side effects. +// DisableColor(). Otherwise, this method has no side effects. func (c *Color) EnableColor() { c.noColor = boolPtr(false) } func (c *Color) isNoColorSet() bool { - // check first if we have user setted action + // check first if we have user set action if c.noColor != nil { return *c.noColor } diff --git a/vendor/github.com/fatih/color/color_windows.go b/vendor/github.com/fatih/color/color_windows.go new file mode 100644 index 00000000..be01c558 --- /dev/null +++ b/vendor/github.com/fatih/color/color_windows.go @@ -0,0 +1,19 @@ +package color + +import ( + "os" + + "golang.org/x/sys/windows" +) + +func init() { + // Opt-in for ansi color support for current process. + // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#output-sequences + var outMode uint32 + out := windows.Handle(os.Stdout.Fd()) + if err := windows.GetConsoleMode(out, &outMode); err != nil { + return + } + outMode |= windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING + _ = windows.SetConsoleMode(out, outMode) +} diff --git a/vendor/github.com/fatih/color/doc.go b/vendor/github.com/fatih/color/doc.go index cf1e9650..9491ad54 100644 --- a/vendor/github.com/fatih/color/doc.go +++ b/vendor/github.com/fatih/color/doc.go @@ -5,106 +5,105 @@ that suits you. Use simple and default helper functions with predefined foreground colors: - color.Cyan("Prints text in cyan.") + color.Cyan("Prints text in cyan.") - // a newline will be appended automatically - color.Blue("Prints %s in blue.", "text") + // a newline will be appended automatically + color.Blue("Prints %s in blue.", "text") - // More default foreground colors.. - color.Red("We have red") - color.Yellow("Yellow color too!") - color.Magenta("And many others ..") + // More default foreground colors.. + color.Red("We have red") + color.Yellow("Yellow color too!") + color.Magenta("And many others ..") - // Hi-intensity colors - color.HiGreen("Bright green color.") - color.HiBlack("Bright black means gray..") - color.HiWhite("Shiny white color!") + // Hi-intensity colors + color.HiGreen("Bright green color.") + color.HiBlack("Bright black means gray..") + color.HiWhite("Shiny white color!") -However there are times where custom color mixes are required. Below are some +However, there are times when custom color mixes are required. Below are some examples to create custom color objects and use the print functions of each separate color object. - // Create a new color object - c := color.New(color.FgCyan).Add(color.Underline) - c.Println("Prints cyan text with an underline.") + // Create a new color object + c := color.New(color.FgCyan).Add(color.Underline) + c.Println("Prints cyan text with an underline.") - // Or just add them to New() - d := color.New(color.FgCyan, color.Bold) - d.Printf("This prints bold cyan %s\n", "too!.") + // Or just add them to New() + d := color.New(color.FgCyan, color.Bold) + d.Printf("This prints bold cyan %s\n", "too!.") - // Mix up foreground and background colors, create new mixes! - red := color.New(color.FgRed) + // Mix up foreground and background colors, create new mixes! + red := color.New(color.FgRed) - boldRed := red.Add(color.Bold) - boldRed.Println("This will print text in bold red.") + boldRed := red.Add(color.Bold) + boldRed.Println("This will print text in bold red.") - whiteBackground := red.Add(color.BgWhite) - whiteBackground.Println("Red text with White background.") + whiteBackground := red.Add(color.BgWhite) + whiteBackground.Println("Red text with White background.") - // Use your own io.Writer output - color.New(color.FgBlue).Fprintln(myWriter, "blue color!") + // Use your own io.Writer output + color.New(color.FgBlue).Fprintln(myWriter, "blue color!") - blue := color.New(color.FgBlue) - blue.Fprint(myWriter, "This will print text in blue.") + blue := color.New(color.FgBlue) + blue.Fprint(myWriter, "This will print text in blue.") You can create PrintXxx functions to simplify even more: - // Create a custom print function for convenient - red := color.New(color.FgRed).PrintfFunc() - red("warning") - red("error: %s", err) + // Create a custom print function for convenient + red := color.New(color.FgRed).PrintfFunc() + red("warning") + red("error: %s", err) - // Mix up multiple attributes - notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() - notice("don't forget this...") + // Mix up multiple attributes + notice := color.New(color.Bold, color.FgGreen).PrintlnFunc() + notice("don't forget this...") You can also FprintXxx functions to pass your own io.Writer: - blue := color.New(FgBlue).FprintfFunc() - blue(myWriter, "important notice: %s", stars) - - // Mix up with multiple attributes - success := color.New(color.Bold, color.FgGreen).FprintlnFunc() - success(myWriter, don't forget this...") + blue := color.New(FgBlue).FprintfFunc() + blue(myWriter, "important notice: %s", stars) + // Mix up with multiple attributes + success := color.New(color.Bold, color.FgGreen).FprintlnFunc() + success(myWriter, don't forget this...") Or create SprintXxx functions to mix strings with other non-colorized strings: - yellow := New(FgYellow).SprintFunc() - red := New(FgRed).SprintFunc() + yellow := New(FgYellow).SprintFunc() + red := New(FgRed).SprintFunc() - fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error")) + fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error")) - info := New(FgWhite, BgGreen).SprintFunc() - fmt.Printf("this %s rocks!\n", info("package")) + info := New(FgWhite, BgGreen).SprintFunc() + fmt.Printf("this %s rocks!\n", info("package")) Windows support is enabled by default. All Print functions work as intended. -However only for color.SprintXXX functions, user should use fmt.FprintXXX and +However, only for color.SprintXXX functions, user should use fmt.FprintXXX and set the output to color.Output: - fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) + fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS")) - info := New(FgWhite, BgGreen).SprintFunc() - fmt.Fprintf(color.Output, "this %s rocks!\n", info("package")) + info := New(FgWhite, BgGreen).SprintFunc() + fmt.Fprintf(color.Output, "this %s rocks!\n", info("package")) Using with existing code is possible. Just use the Set() method to set the standard output to the given parameters. That way a rewrite of an existing code is not required. - // Use handy standard colors. - color.Set(color.FgYellow) + // Use handy standard colors. + color.Set(color.FgYellow) - fmt.Println("Existing text will be now in Yellow") - fmt.Printf("This one %s\n", "too") + fmt.Println("Existing text will be now in Yellow") + fmt.Printf("This one %s\n", "too") - color.Unset() // don't forget to unset + color.Unset() // don't forget to unset - // You can mix up parameters - color.Set(color.FgMagenta, color.Bold) - defer color.Unset() // use it in your function + // You can mix up parameters + color.Set(color.FgMagenta, color.Bold) + defer color.Unset() // use it in your function - fmt.Println("All text will be now bold magenta.") + fmt.Println("All text will be now bold magenta.") There might be a case where you want to disable color output (for example to pipe the standard output of your app to somewhere else). `Color` has support to @@ -112,22 +111,24 @@ disable colors both globally and for single color definition. For example suppose you have a CLI app and a `--no-color` bool flag. You can easily disable the color output with: - var flagNoColor = flag.Bool("no-color", false, "Disable color output") + var flagNoColor = flag.Bool("no-color", false, "Disable color output") - if *flagNoColor { - color.NoColor = true // disables colorized output - } + if *flagNoColor { + color.NoColor = true // disables colorized output + } + +You can also disable the color by setting the NO_COLOR environment variable to any value. It also has support for single color definitions (local). You can disable/enable color output on the fly: - c := color.New(color.FgCyan) - c.Println("Prints cyan text") + c := color.New(color.FgCyan) + c.Println("Prints cyan text") - c.DisableColor() - c.Println("This is printed without any color") + c.DisableColor() + c.Println("This is printed without any color") - c.EnableColor() - c.Println("This prints again cyan...") + c.EnableColor() + c.Println("This prints again cyan...") */ package color diff --git a/vendor/github.com/fsnotify/fsnotify/.cirrus.yml b/vendor/github.com/fsnotify/fsnotify/.cirrus.yml new file mode 100644 index 00000000..7f257e99 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.cirrus.yml @@ -0,0 +1,14 @@ +freebsd_task: + name: 'FreeBSD' + freebsd_instance: + image_family: freebsd-14-2 + install_script: + - pkg update -f + - pkg install -y go + test_script: + # run tests as user "cirrus" instead of root + - pw useradd cirrus -m + - chown -R cirrus:cirrus . + - FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./... + - sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./... + - FSNOTIFY_DEBUG=1 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race -v ./... diff --git a/vendor/github.com/fsnotify/fsnotify/.gitignore b/vendor/github.com/fsnotify/fsnotify/.gitignore new file mode 100644 index 00000000..daea9dd6 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.gitignore @@ -0,0 +1,10 @@ +# go test -c output +*.test +*.test.exe + +# Output of go build ./cmd/fsnotify +/fsnotify +/fsnotify.exe + +/test/kqueue +/test/a.out diff --git a/vendor/github.com/fsnotify/fsnotify/.mailmap b/vendor/github.com/fsnotify/fsnotify/.mailmap new file mode 100644 index 00000000..a04f2907 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.mailmap @@ -0,0 +1,2 @@ +Chris Howey +Nathan Youngman <4566+nathany@users.noreply.github.com> diff --git a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md new file mode 100644 index 00000000..6468d2cf --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md @@ -0,0 +1,602 @@ +# Changelog + +1.9.0 2024-04-04 +---------------- + +### Changes and fixes + +- all: make BufferedWatcher buffered again ([#657]) + +- inotify: fix race when adding/removing watches while a watched path is being + deleted ([#678], [#686]) + +- inotify: don't send empty event if a watched path is unmounted ([#655]) + +- inotify: don't register duplicate watches when watching both a symlink and its + target; previously that would get "half-added" and removing the second would + panic ([#679]) + +- kqueue: fix watching relative symlinks ([#681]) + +- kqueue: correctly mark pre-existing entries when watching a link to a dir on + kqueue ([#682]) + +- illumos: don't send error if changed file is deleted while processing the + event ([#678]) + + +[#657]: https://github.com/fsnotify/fsnotify/pull/657 +[#678]: https://github.com/fsnotify/fsnotify/pull/678 +[#686]: https://github.com/fsnotify/fsnotify/pull/686 +[#655]: https://github.com/fsnotify/fsnotify/pull/655 +[#681]: https://github.com/fsnotify/fsnotify/pull/681 +[#679]: https://github.com/fsnotify/fsnotify/pull/679 +[#682]: https://github.com/fsnotify/fsnotify/pull/682 + +1.8.0 2024-10-31 +---------------- + +### Additions + +- all: add `FSNOTIFY_DEBUG` to print debug logs to stderr ([#619]) + +### Changes and fixes + +- windows: fix behaviour of `WatchList()` to be consistent with other platforms ([#610]) + +- kqueue: ignore events with Ident=0 ([#590]) + +- kqueue: set O_CLOEXEC to prevent passing file descriptors to children ([#617]) + +- kqueue: emit events as "/path/dir/file" instead of "path/link/file" when watching a symlink ([#625]) + +- inotify: don't send event for IN_DELETE_SELF when also watching the parent ([#620]) + +- inotify: fix panic when calling Remove() in a goroutine ([#650]) + +- fen: allow watching subdirectories of watched directories ([#621]) + +[#590]: https://github.com/fsnotify/fsnotify/pull/590 +[#610]: https://github.com/fsnotify/fsnotify/pull/610 +[#617]: https://github.com/fsnotify/fsnotify/pull/617 +[#619]: https://github.com/fsnotify/fsnotify/pull/619 +[#620]: https://github.com/fsnotify/fsnotify/pull/620 +[#621]: https://github.com/fsnotify/fsnotify/pull/621 +[#625]: https://github.com/fsnotify/fsnotify/pull/625 +[#650]: https://github.com/fsnotify/fsnotify/pull/650 + +1.7.0 - 2023-10-22 +------------------ +This version of fsnotify needs Go 1.17. + +### Additions + +- illumos: add FEN backend to support illumos and Solaris. ([#371]) + +- all: add `NewBufferedWatcher()` to use a buffered channel, which can be useful + in cases where you can't control the kernel buffer and receive a large number + of events in bursts. ([#550], [#572]) + +- all: add `AddWith()`, which is identical to `Add()` but allows passing + options. ([#521]) + +- windows: allow setting the ReadDirectoryChangesW() buffer size with + `fsnotify.WithBufferSize()`; the default of 64K is the highest value that + works on all platforms and is enough for most purposes, but in some cases a + highest buffer is needed. ([#521]) + +### Changes and fixes + +- inotify: remove watcher if a watched path is renamed ([#518]) + + After a rename the reported name wasn't updated, or even an empty string. + Inotify doesn't provide any good facilities to update it, so just remove the + watcher. This is already how it worked on kqueue and FEN. + + On Windows this does work, and remains working. + +- windows: don't listen for file attribute changes ([#520]) + + File attribute changes are sent as `FILE_ACTION_MODIFIED` by the Windows API, + with no way to see if they're a file write or attribute change, so would show + up as a fsnotify.Write event. This is never useful, and could result in many + spurious Write events. + +- windows: return `ErrEventOverflow` if the buffer is full ([#525]) + + Before it would merely return "short read", making it hard to detect this + error. + +- kqueue: make sure events for all files are delivered properly when removing a + watched directory ([#526]) + + Previously they would get sent with `""` (empty string) or `"."` as the path + name. + +- kqueue: don't emit spurious Create events for symbolic links ([#524]) + + The link would get resolved but kqueue would "forget" it already saw the link + itself, resulting on a Create for every Write event for the directory. + +- all: return `ErrClosed` on `Add()` when the watcher is closed ([#516]) + +- other: add `Watcher.Errors` and `Watcher.Events` to the no-op `Watcher` in + `backend_other.go`, making it easier to use on unsupported platforms such as + WASM, AIX, etc. ([#528]) + +- other: use the `backend_other.go` no-op if the `appengine` build tag is set; + Google AppEngine forbids usage of the unsafe package so the inotify backend + won't compile there. + +[#371]: https://github.com/fsnotify/fsnotify/pull/371 +[#516]: https://github.com/fsnotify/fsnotify/pull/516 +[#518]: https://github.com/fsnotify/fsnotify/pull/518 +[#520]: https://github.com/fsnotify/fsnotify/pull/520 +[#521]: https://github.com/fsnotify/fsnotify/pull/521 +[#524]: https://github.com/fsnotify/fsnotify/pull/524 +[#525]: https://github.com/fsnotify/fsnotify/pull/525 +[#526]: https://github.com/fsnotify/fsnotify/pull/526 +[#528]: https://github.com/fsnotify/fsnotify/pull/528 +[#537]: https://github.com/fsnotify/fsnotify/pull/537 +[#550]: https://github.com/fsnotify/fsnotify/pull/550 +[#572]: https://github.com/fsnotify/fsnotify/pull/572 + +1.6.0 - 2022-10-13 +------------------ +This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1, +but not documented). It also increases the minimum Linux version to 2.6.32. + +### Additions + +- all: add `Event.Has()` and `Op.Has()` ([#477]) + + This makes checking events a lot easier; for example: + + if event.Op&Write == Write && !(event.Op&Remove == Remove) { + } + + Becomes: + + if event.Has(Write) && !event.Has(Remove) { + } + +- all: add cmd/fsnotify ([#463]) + + A command-line utility for testing and some examples. + +### Changes and fixes + +- inotify: don't ignore events for files that don't exist ([#260], [#470]) + + Previously the inotify watcher would call `os.Lstat()` to check if a file + still exists before emitting events. + + This was inconsistent with other platforms and resulted in inconsistent event + reporting (e.g. when a file is quickly removed and re-created), and generally + a source of confusion. It was added in 2013 to fix a memory leak that no + longer exists. + +- all: return `ErrNonExistentWatch` when `Remove()` is called on a path that's + not watched ([#460]) + +- inotify: replace epoll() with non-blocking inotify ([#434]) + + Non-blocking inotify was not generally available at the time this library was + written in 2014, but now it is. As a result, the minimum Linux version is + bumped from 2.6.27 to 2.6.32. This hugely simplifies the code and is faster. + +- kqueue: don't check for events every 100ms ([#480]) + + The watcher would wake up every 100ms, even when there was nothing to do. Now + it waits until there is something to do. + +- macos: retry opening files on EINTR ([#475]) + +- kqueue: skip unreadable files ([#479]) + + kqueue requires a file descriptor for every file in a directory; this would + fail if a file was unreadable by the current user. Now these files are simply + skipped. + +- windows: fix renaming a watched directory if the parent is also watched ([#370]) + +- windows: increase buffer size from 4K to 64K ([#485]) + +- windows: close file handle on Remove() ([#288]) + +- kqueue: put pathname in the error if watching a file fails ([#471]) + +- inotify, windows: calling Close() more than once could race ([#465]) + +- kqueue: improve Close() performance ([#233]) + +- all: various documentation additions and clarifications. + +[#233]: https://github.com/fsnotify/fsnotify/pull/233 +[#260]: https://github.com/fsnotify/fsnotify/pull/260 +[#288]: https://github.com/fsnotify/fsnotify/pull/288 +[#370]: https://github.com/fsnotify/fsnotify/pull/370 +[#434]: https://github.com/fsnotify/fsnotify/pull/434 +[#460]: https://github.com/fsnotify/fsnotify/pull/460 +[#463]: https://github.com/fsnotify/fsnotify/pull/463 +[#465]: https://github.com/fsnotify/fsnotify/pull/465 +[#470]: https://github.com/fsnotify/fsnotify/pull/470 +[#471]: https://github.com/fsnotify/fsnotify/pull/471 +[#475]: https://github.com/fsnotify/fsnotify/pull/475 +[#477]: https://github.com/fsnotify/fsnotify/pull/477 +[#479]: https://github.com/fsnotify/fsnotify/pull/479 +[#480]: https://github.com/fsnotify/fsnotify/pull/480 +[#485]: https://github.com/fsnotify/fsnotify/pull/485 + +## [1.5.4] - 2022-04-25 + +* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447) +* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444) +* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443) + +## [1.5.3] - 2022-04-22 + +* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445) + +## [1.5.2] - 2022-04-21 + +* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374) +* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361) +* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424) +* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406) +* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416) + +## [1.5.1] - 2021-08-24 + +* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394) + +## [1.5.0] - 2021-08-20 + +* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381) +* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298) +* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289) +* CI: Use GitHub Actions for CI and cover go 1.12-1.17 + [#378](https://github.com/fsnotify/fsnotify/pull/378) + [#381](https://github.com/fsnotify/fsnotify/pull/381) + [#385](https://github.com/fsnotify/fsnotify/pull/385) +* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325) + +## [1.4.9] - 2020-03-11 + +* Move example usage to the readme #329. This may resolve #328. + +## [1.4.8] - 2020-03-10 + +* CI: test more go versions (@nathany 1d13583d846ea9d66dcabbfefbfb9d8e6fb05216) +* Tests: Queued inotify events could have been read by the test before max_queued_events was hit (@matthias-stone #265) +* Tests: t.Fatalf -> t.Errorf in go routines (@gdey #266) +* CI: Less verbosity (@nathany #267) +* Tests: Darwin: Exchangedata is deprecated on 10.13 (@nathany #267) +* Tests: Check if channels are closed in the example (@alexeykazakov #244) +* CI: Only run golint on latest version of go and fix issues (@cpuguy83 #284) +* CI: Add windows to travis matrix (@cpuguy83 #284) +* Docs: Remover appveyor badge (@nathany 11844c0959f6fff69ba325d097fce35bd85a8e93) +* Linux: create epoll and pipe fds with close-on-exec (@JohannesEbke #219) +* Linux: open files with close-on-exec (@linxiulei #273) +* Docs: Plan to support fanotify (@nathany ab058b44498e8b7566a799372a39d150d9ea0119 ) +* Project: Add go.mod (@nathany #309) +* Project: Revise editor config (@nathany #309) +* Project: Update copyright for 2019 (@nathany #309) +* CI: Drop go1.8 from CI matrix (@nathany #309) +* Docs: Updating the FAQ section for supportability with NFS & FUSE filesystems (@Pratik32 4bf2d1fec78374803a39307bfb8d340688f4f28e ) + +## [1.4.7] - 2018-01-09 + +* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine) +* Tests: Fix missing verb on format string (thanks @rchiossi) +* Linux: Fix deadlock in Remove (thanks @aarondl) +* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne) +* Docs: Moved FAQ into the README (thanks @vahe) +* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich) +* Docs: replace references to OS X with macOS + +## [1.4.2] - 2016-10-10 + +* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack) + +## [1.4.1] - 2016-10-04 + +* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack) + +## [1.4.0] - 2016-10-01 + +* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie) + +## [1.3.1] - 2016-06-28 + +* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc) + +## [1.3.0] - 2016-04-19 + +* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135) + +## [1.2.10] - 2016-03-02 + +* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj) + +## [1.2.9] - 2016-01-13 + +kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep) + +## [1.2.8] - 2015-12-17 + +* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test) +* inotify: fix race in test +* enable race detection for continuous integration (Linux, Mac, Windows) + +## [1.2.5] - 2015-10-17 + +* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki) +* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken) +* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie) +* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion) + +## [1.2.1] - 2015-10-14 + +* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx) + +## [1.2.0] - 2015-02-08 + +* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD) +* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD) +* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59) + +## [1.1.1] - 2015-02-05 + +* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD) + +## [1.1.0] - 2014-12-12 + +* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43) + * add low-level functions + * only need to store flags on directories + * less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13) + * done can be an unbuffered channel + * remove calls to os.NewSyscallError +* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher) +* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48) +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## [1.0.4] - 2014-09-07 + +* kqueue: add dragonfly to the build tags. +* Rename source code files, rearrange code so exported APIs are at the top. +* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang) + +## [1.0.3] - 2014-08-19 + +* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36) + +## [1.0.2] - 2014-08-17 + +* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) +* [Fix] Make ./path and path equivalent. (thanks @zhsso) + +## [1.0.0] - 2014-08-15 + +* [API] Remove AddWatch on Windows, use Add. +* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30) +* Minor updates based on feedback from golint. + +## dev / 2014-07-09 + +* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify). +* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno) + +## dev / 2014-07-04 + +* kqueue: fix incorrect mutex used in Close() +* Update example to demonstrate usage of Op. + +## dev / 2014-06-28 + +* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4) +* Fix for String() method on Event (thanks Alex Brainman) +* Don't build on Plan 9 or Solaris (thanks @4ad) + +## dev / 2014-06-21 + +* Events channel of type Event rather than *Event. +* [internal] use syscall constants directly for inotify and kqueue. +* [internal] kqueue: rename events to kevents and fileEvent to event. + +## dev / 2014-06-19 + +* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally). +* [internal] remove cookie from Event struct (unused). +* [internal] Event struct has the same definition across every OS. +* [internal] remove internal watch and removeWatch methods. + +## dev / 2014-06-12 + +* [API] Renamed Watch() to Add() and RemoveWatch() to Remove(). +* [API] Pluralized channel names: Events and Errors. +* [API] Renamed FileEvent struct to Event. +* [API] Op constants replace methods like IsCreate(). + +## dev / 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## dev / 2014-05-23 + +* [API] Remove current implementation of WatchFlags. + * current implementation doesn't take advantage of OS for efficiency + * provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes + * no tests for the current implementation + * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) + +## [0.9.3] - 2014-12-31 + +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## [0.9.2] - 2014-08-17 + +* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) + +## [0.9.1] - 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## [0.9.0] - 2014-01-17 + +* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany) +* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare) +* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library. + +## [0.8.12] - 2013-11-13 + +* [API] Remove FD_SET and friends from Linux adapter + +## [0.8.11] - 2013-11-02 + +* [Doc] Add Changelog [#72][] (thanks @nathany) +* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond) + +## [0.8.10] - 2013-10-19 + +* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott) +* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer) +* [Doc] specify OS-specific limits in README (thanks @debrando) + +## [0.8.9] - 2013-09-08 + +* [Doc] Contributing (thanks @nathany) +* [Doc] update package path in example code [#63][] (thanks @paulhammond) +* [Doc] GoCI badge in README (Linux only) [#60][] +* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany) + +## [0.8.8] - 2013-06-17 + +* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie) + +## [0.8.7] - 2013-06-03 + +* [API] Make syscall flags internal +* [Fix] inotify: ignore event changes +* [Fix] race in symlink test [#45][] (reported by @srid) +* [Fix] tests on Windows +* lower case error messages + +## [0.8.6] - 2013-05-23 + +* kqueue: Use EVT_ONLY flag on Darwin +* [Doc] Update README with full example + +## [0.8.5] - 2013-05-09 + +* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg) + +## [0.8.4] - 2013-04-07 + +* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz) + +## [0.8.3] - 2013-03-13 + +* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin) +* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin) + +## [0.8.2] - 2013-02-07 + +* [Doc] add Authors +* [Fix] fix data races for map access [#29][] (thanks @fsouza) + +## [0.8.1] - 2013-01-09 + +* [Fix] Windows path separators +* [Doc] BSD License + +## [0.8.0] - 2012-11-09 + +* kqueue: directory watching improvements (thanks @vmirage) +* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto) +* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr) + +## [0.7.4] - 2012-10-09 + +* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji) +* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig) +* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig) +* [Fix] kqueue: modify after recreation of file + +## [0.7.3] - 2012-09-27 + +* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage) +* [Fix] kqueue: no longer get duplicate CREATE events + +## [0.7.2] - 2012-09-01 + +* kqueue: events for created directories + +## [0.7.1] - 2012-07-14 + +* [Fix] for renaming files + +## [0.7.0] - 2012-07-02 + +* [Feature] FSNotify flags +* [Fix] inotify: Added file name back to event path + +## [0.6.0] - 2012-06-06 + +* kqueue: watch files after directory created (thanks @tmc) + +## [0.5.1] - 2012-05-22 + +* [Fix] inotify: remove all watches before Close() + +## [0.5.0] - 2012-05-03 + +* [API] kqueue: return errors during watch instead of sending over channel +* kqueue: match symlink behavior on Linux +* inotify: add `DELETE_SELF` (requested by @taralx) +* [Fix] kqueue: handle EINTR (reported by @robfig) +* [Doc] Godoc example [#1][] (thanks @davecheney) + +## [0.4.0] - 2012-03-30 + +* Go 1 released: build with go tool +* [Feature] Windows support using winfsnotify +* Windows does not have attribute change notifications +* Roll attribute notifications into IsModify + +## [0.3.0] - 2012-02-19 + +* kqueue: add files when watch directory + +## [0.2.0] - 2011-12-30 + +* update to latest Go weekly code + +## [0.1.0] - 2011-10-19 + +* kqueue: add watch on file creation to match inotify +* kqueue: create file event +* inotify: ignore `IN_IGNORED` events +* event String() +* linux: common FileEvent functions +* initial commit + +[#79]: https://github.com/howeyc/fsnotify/pull/79 +[#77]: https://github.com/howeyc/fsnotify/pull/77 +[#72]: https://github.com/howeyc/fsnotify/issues/72 +[#71]: https://github.com/howeyc/fsnotify/issues/71 +[#70]: https://github.com/howeyc/fsnotify/issues/70 +[#63]: https://github.com/howeyc/fsnotify/issues/63 +[#62]: https://github.com/howeyc/fsnotify/issues/62 +[#60]: https://github.com/howeyc/fsnotify/issues/60 +[#59]: https://github.com/howeyc/fsnotify/issues/59 +[#49]: https://github.com/howeyc/fsnotify/issues/49 +[#45]: https://github.com/howeyc/fsnotify/issues/45 +[#40]: https://github.com/howeyc/fsnotify/issues/40 +[#36]: https://github.com/howeyc/fsnotify/issues/36 +[#33]: https://github.com/howeyc/fsnotify/issues/33 +[#29]: https://github.com/howeyc/fsnotify/issues/29 +[#25]: https://github.com/howeyc/fsnotify/issues/25 +[#24]: https://github.com/howeyc/fsnotify/issues/24 +[#21]: https://github.com/howeyc/fsnotify/issues/21 diff --git a/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md new file mode 100644 index 00000000..4cc40fa5 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md @@ -0,0 +1,145 @@ +Thank you for your interest in contributing to fsnotify! We try to review and +merge PRs in a reasonable timeframe, but please be aware that: + +- To avoid "wasted" work, please discuss changes on the issue tracker first. You + can just send PRs, but they may end up being rejected for one reason or the + other. + +- fsnotify is a cross-platform library, and changes must work reasonably well on + all supported platforms. + +- Changes will need to be compatible; old code should still compile, and the + runtime behaviour can't change in ways that are likely to lead to problems for + users. + +Testing +------- +Just `go test ./...` runs all the tests; the CI runs this on all supported +platforms. Testing different platforms locally can be done with something like +[goon] or [Vagrant], but this isn't super-easy to set up at the moment. + +Use the `-short` flag to make the "stress test" run faster. + +Writing new tests +----------------- +Scripts in the testdata directory allow creating test cases in a "shell-like" +syntax. The basic format is: + + script + + Output: + desired output + +For example: + + # Create a new empty file with some data. + watch / + echo data >/file + + Output: + create /file + write /file + +Just create a new file to add a new test; select which tests to run with +`-run TestScript/[path]`. + +script +------ +The script is a "shell-like" script: + + cmd arg arg + +Comments are supported with `#`: + + # Comment + cmd arg arg # Comment + +All operations are done in a temp directory; a path like "/foo" is rewritten to +"/tmp/TestFoo/foo". + +Arguments can be quoted with `"` or `'`; there are no escapes and they're +functionally identical right now, but this may change in the future, so best to +assume shell-like rules. + + touch "/file with spaces" + +End-of-line escapes with `\` are not supported. + +### Supported commands + + watch path [ops] # Watch the path, reporting events for it. Nothing is + # watched by default. Optionally a list of ops can be + # given, as with AddWith(path, WithOps(...)). + unwatch path # Stop watching the path. + watchlist n # Assert watchlist length. + + stop # Stop running the script; for debugging. + debug [yes/no] # Enable/disable FSNOTIFY_DEBUG (tests are run in + parallel by default, so -parallel=1 is probably a good + idea). + print [any strings] # Print text to stdout; for debugging. + + touch path + mkdir [-p] dir + ln -s target link # Only ln -s supported. + mkfifo path + mknod dev path + mv src dst + rm [-r] path + chmod mode path # Octal only + sleep time-in-ms + + cat path # Read path (does nothing with the data; just reads it). + echo str >>path # Append "str" to "path". + echo str >path # Truncate "path" and write "str". + + require reason # Skip the test if "reason" is true; "skip" and + skip reason # "require" behave identical; it supports both for + # readability. Possible reasons are: + # + # always Always skip this test. + # symlink Symlinks are supported (requires admin + # permissions on Windows). + # mkfifo Platform doesn't support FIFO named sockets. + # mknod Platform doesn't support device nodes. + + +output +------ +After `Output:` the desired output is given; this is indented by convention, but +that's not required. + +The format of that is: + + # Comment + event path # Comment + + system: + event path + system2: + event path + +Every event is one line, and any whitespace between the event and path are +ignored. The path can optionally be surrounded in ". Anything after a "#" is +ignored. + +Platform-specific tests can be added after GOOS; for example: + + watch / + touch /file + + Output: + # Tested if nothing else matches + create /file + + # Windows-specific test. + windows: + write /file + +You can specify multiple platforms with a comma (e.g. "windows, linux:"). +"kqueue" is a shortcut for all kqueue systems (BSD, macOS). + + +[goon]: https://github.com/arp242/goon +[Vagrant]: https://www.vagrantup.com/ +[integration_test.go]: /integration_test.go diff --git a/vendor/github.com/fsnotify/fsnotify/LICENSE b/vendor/github.com/fsnotify/fsnotify/LICENSE new file mode 100644 index 00000000..fb03ade7 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/LICENSE @@ -0,0 +1,25 @@ +Copyright © 2012 The Go Authors. All rights reserved. +Copyright © fsnotify Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +* Neither the name of Google Inc. nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/fsnotify/fsnotify/README.md b/vendor/github.com/fsnotify/fsnotify/README.md new file mode 100644 index 00000000..1f4eb583 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/README.md @@ -0,0 +1,182 @@ +fsnotify is a Go library to provide cross-platform filesystem notifications on +Windows, Linux, macOS, BSD, and illumos. + +Go 1.17 or newer is required; the full documentation is at +https://pkg.go.dev/github.com/fsnotify/fsnotify + +--- + +Platform support: + +| Backend | OS | Status | +| :-------------------- | :--------- | :------------------------------------------------------------------------ | +| inotify | Linux | Supported | +| kqueue | BSD, macOS | Supported | +| ReadDirectoryChangesW | Windows | Supported | +| FEN | illumos | Supported | +| fanotify | Linux 5.9+ | [Not yet](https://github.com/fsnotify/fsnotify/issues/114) | +| FSEvents | macOS | [Needs support in x/sys/unix][fsevents] | +| USN Journals | Windows | [Needs support in x/sys/windows][usn] | +| Polling | *All* | [Not yet](https://github.com/fsnotify/fsnotify/issues/9) | + +Linux and illumos should include Android and Solaris, but these are currently +untested. + +[fsevents]: https://github.com/fsnotify/fsnotify/issues/11#issuecomment-1279133120 +[usn]: https://github.com/fsnotify/fsnotify/issues/53#issuecomment-1279829847 + +Usage +----- +A basic example: + +```go +package main + +import ( + "log" + + "github.com/fsnotify/fsnotify" +) + +func main() { + // Create new watcher. + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + // Start listening for events. + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + log.Println("event:", event) + if event.Has(fsnotify.Write) { + log.Println("modified file:", event.Name) + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("error:", err) + } + } + }() + + // Add a path. + err = watcher.Add("/tmp") + if err != nil { + log.Fatal(err) + } + + // Block main goroutine forever. + <-make(chan struct{}) +} +``` + +Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be +run with: + + % go run ./cmd/fsnotify + +Further detailed documentation can be found in godoc: +https://pkg.go.dev/github.com/fsnotify/fsnotify + +FAQ +--- +### Will a file still be watched when it's moved to another directory? +No, not unless you are watching the location it was moved to. + +### Are subdirectories watched? +No, you must add watches for any directory you want to watch (a recursive +watcher is on the roadmap: [#18]). + +[#18]: https://github.com/fsnotify/fsnotify/issues/18 + +### Do I have to watch the Error and Event channels in a goroutine? +Yes. You can read both channels in the same goroutine using `select` (you don't +need a separate goroutine for both channels; see the example). + +### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys? +fsnotify requires support from underlying OS to work. The current NFS and SMB +protocols does not provide network level support for file notifications, and +neither do the /proc and /sys virtual filesystems. + +This could be fixed with a polling watcher ([#9]), but it's not yet implemented. + +[#9]: https://github.com/fsnotify/fsnotify/issues/9 + +### Why do I get many Chmod events? +Some programs may generate a lot of attribute changes; for example Spotlight on +macOS, anti-virus programs, backup applications, and some others are known to do +this. As a rule, it's typically best to ignore Chmod events. They're often not +useful, and tend to cause problems. + +Spotlight indexing on macOS can result in multiple events (see [#15]). A +temporary workaround is to add your folder(s) to the *Spotlight Privacy +settings* until we have a native FSEvents implementation (see [#11]). + +[#11]: https://github.com/fsnotify/fsnotify/issues/11 +[#15]: https://github.com/fsnotify/fsnotify/issues/15 + +### Watching a file doesn't work well +Watching individual files (rather than directories) is generally not recommended +as many programs (especially editors) update files atomically: it will write to +a temporary file which is then moved to to destination, overwriting the original +(or some variant thereof). The watcher on the original file is now lost, as that +no longer exists. + +The upshot of this is that a power failure or crash won't leave a half-written +file. + +Watch the parent directory and use `Event.Name` to filter out files you're not +interested in. There is an example of this in `cmd/fsnotify/file.go`. + +Platform-specific notes +----------------------- +### Linux +When a file is removed a REMOVE event won't be emitted until all file +descriptors are closed; it will emit a CHMOD instead: + + fp := os.Open("file") + os.Remove("file") // CHMOD + fp.Close() // REMOVE + +This is the event that inotify sends, so not much can be changed about this. + +The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for +the number of watches per user, and `fs.inotify.max_user_instances` specifies +the maximum number of inotify instances per user. Every Watcher you create is an +"instance", and every path you add is a "watch". + +These are also exposed in `/proc` as `/proc/sys/fs/inotify/max_user_watches` and +`/proc/sys/fs/inotify/max_user_instances` + +To increase them you can use `sysctl` or write the value to proc file: + + # The default values on Linux 5.18 + sysctl fs.inotify.max_user_watches=124983 + sysctl fs.inotify.max_user_instances=128 + +To make the changes persist on reboot edit `/etc/sysctl.conf` or +`/usr/lib/sysctl.d/50-default.conf` (details differ per Linux distro; check your +distro's documentation): + + fs.inotify.max_user_watches=124983 + fs.inotify.max_user_instances=128 + +Reaching the limit will result in a "no space left on device" or "too many open +files" error. + +### kqueue (macOS, all BSD systems) +kqueue requires opening a file descriptor for every file that's being watched; +so if you're watching a directory with five files then that's six file +descriptors. You will run in to your system's "max open files" limit faster on +these platforms. + +The sysctl variables `kern.maxfiles` and `kern.maxfilesperproc` can be used to +control the maximum number of open files. diff --git a/vendor/github.com/fsnotify/fsnotify/backend_fen.go b/vendor/github.com/fsnotify/fsnotify/backend_fen.go new file mode 100644 index 00000000..57fc6928 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/backend_fen.go @@ -0,0 +1,467 @@ +//go:build solaris + +// FEN backend for illumos (supported) and Solaris (untested, but should work). +// +// See port_create(3c) etc. for docs. https://www.illumos.org/man/3C/port_create + +package fsnotify + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "sync" + "time" + + "github.com/fsnotify/fsnotify/internal" + "golang.org/x/sys/unix" +) + +type fen struct { + *shared + Events chan Event + Errors chan error + + mu sync.Mutex + port *unix.EventPort + dirs map[string]Op // Explicitly watched directories + watches map[string]Op // Explicitly watched non-directories +} + +var defaultBufferSize = 0 + +func newBackend(ev chan Event, errs chan error) (backend, error) { + w := &fen{ + shared: newShared(ev, errs), + Events: ev, + Errors: errs, + dirs: make(map[string]Op), + watches: make(map[string]Op), + } + + var err error + w.port, err = unix.NewEventPort() + if err != nil { + return nil, fmt.Errorf("fsnotify.NewWatcher: %w", err) + } + + go w.readEvents() + return w, nil +} + +func (w *fen) Close() error { + if w.shared.close() { + return nil + } + return w.port.Close() +} + +func (w *fen) Add(name string) error { return w.AddWith(name) } + +func (w *fen) AddWith(name string, opts ...addOpt) error { + if w.isClosed() { + return ErrClosed + } + if debug { + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n", + time.Now().Format("15:04:05.000000000"), name) + } + + with := getOptions(opts...) + if !w.xSupports(with.op) { + return fmt.Errorf("%w: %s", xErrUnsupported, with.op) + } + + // Currently we resolve symlinks that were explicitly requested to be + // watched. Otherwise we would use LStat here. + stat, err := os.Stat(name) + if err != nil { + return err + } + + // Associate all files in the directory. + if stat.IsDir() { + err := w.handleDirectory(name, stat, true, w.associateFile) + if err != nil { + return err + } + + w.mu.Lock() + w.dirs[name] = with.op + w.mu.Unlock() + return nil + } + + err = w.associateFile(name, stat, true) + if err != nil { + return err + } + + w.mu.Lock() + w.watches[name] = with.op + w.mu.Unlock() + return nil +} + +func (w *fen) Remove(name string) error { + if w.isClosed() { + return nil + } + if !w.port.PathIsWatched(name) { + return fmt.Errorf("%w: %s", ErrNonExistentWatch, name) + } + if debug { + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n", + time.Now().Format("15:04:05.000000000"), name) + } + + // The user has expressed an intent. Immediately remove this name from + // whichever watch list it might be in. If it's not in there the delete + // doesn't cause harm. + w.mu.Lock() + delete(w.watches, name) + delete(w.dirs, name) + w.mu.Unlock() + + stat, err := os.Stat(name) + if err != nil { + return err + } + + // Remove associations for every file in the directory. + if stat.IsDir() { + err := w.handleDirectory(name, stat, false, w.dissociateFile) + if err != nil { + return err + } + return nil + } + + err = w.port.DissociatePath(name) + if err != nil { + return err + } + + return nil +} + +// readEvents contains the main loop that runs in a goroutine watching for events. +func (w *fen) readEvents() { + // If this function returns, the watcher has been closed and we can close + // these channels + defer func() { + close(w.Errors) + close(w.Events) + }() + + pevents := make([]unix.PortEvent, 8) + for { + count, err := w.port.Get(pevents, 1, nil) + if err != nil && err != unix.ETIME { + // Interrupted system call (count should be 0) ignore and continue + if errors.Is(err, unix.EINTR) && count == 0 { + continue + } + // Get failed because we called w.Close() + if errors.Is(err, unix.EBADF) && w.isClosed() { + return + } + // There was an error not caused by calling w.Close() + if !w.sendError(fmt.Errorf("port.Get: %w", err)) { + return + } + } + + p := pevents[:count] + for _, pevent := range p { + if pevent.Source != unix.PORT_SOURCE_FILE { + // Event from unexpected source received; should never happen. + if !w.sendError(errors.New("Event from unexpected source received")) { + return + } + continue + } + + if debug { + internal.Debug(pevent.Path, pevent.Events) + } + + err = w.handleEvent(&pevent) + if !w.sendError(err) { + return + } + } + } +} + +func (w *fen) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error { + files, err := os.ReadDir(path) + if err != nil { + return err + } + + // Handle all children of the directory. + for _, entry := range files { + finfo, err := entry.Info() + if err != nil { + return err + } + err = handler(filepath.Join(path, finfo.Name()), finfo, false) + if err != nil { + return err + } + } + + // And finally handle the directory itself. + return handler(path, stat, follow) +} + +// handleEvent might need to emit more than one fsnotify event if the events +// bitmap matches more than one event type (e.g. the file was both modified and +// had the attributes changed between when the association was created and the +// when event was returned) +func (w *fen) handleEvent(event *unix.PortEvent) error { + var ( + events = event.Events + path = event.Path + fmode = event.Cookie.(os.FileMode) + reRegister = true + ) + + w.mu.Lock() + _, watchedDir := w.dirs[path] + _, watchedPath := w.watches[path] + w.mu.Unlock() + isWatched := watchedDir || watchedPath + + if events&unix.FILE_DELETE != 0 { + if !w.sendEvent(Event{Name: path, Op: Remove}) { + return nil + } + reRegister = false + } + if events&unix.FILE_RENAME_FROM != 0 { + if !w.sendEvent(Event{Name: path, Op: Rename}) { + return nil + } + // Don't keep watching the new file name + reRegister = false + } + if events&unix.FILE_RENAME_TO != 0 { + // We don't report a Rename event for this case, because Rename events + // are interpreted as referring to the _old_ name of the file, and in + // this case the event would refer to the new name of the file. This + // type of rename event is not supported by fsnotify. + + // inotify reports a Remove event in this case, so we simulate this + // here. + if !w.sendEvent(Event{Name: path, Op: Remove}) { + return nil + } + // Don't keep watching the file that was removed + reRegister = false + } + + // The file is gone, nothing left to do. + if !reRegister { + if watchedDir { + w.mu.Lock() + delete(w.dirs, path) + w.mu.Unlock() + } + if watchedPath { + w.mu.Lock() + delete(w.watches, path) + w.mu.Unlock() + } + return nil + } + + // If we didn't get a deletion the file still exists and we're going to have + // to watch it again. Let's Stat it now so that we can compare permissions + // and have what we need to continue watching the file + + stat, err := os.Lstat(path) + if err != nil { + // This is unexpected, but we should still emit an event. This happens + // most often on "rm -r" of a subdirectory inside a watched directory We + // get a modify event of something happening inside, but by the time we + // get here, the sudirectory is already gone. Clearly we were watching + // this path but now it is gone. Let's tell the user that it was + // removed. + if !w.sendEvent(Event{Name: path, Op: Remove}) { + return nil + } + // Suppress extra write events on removed directories; they are not + // informative and can be confusing. + return nil + } + + // resolve symlinks that were explicitly watched as we would have at Add() + // time. this helps suppress spurious Chmod events on watched symlinks + if isWatched { + stat, err = os.Stat(path) + if err != nil { + // The symlink still exists, but the target is gone. Report the + // Remove similar to above. + if !w.sendEvent(Event{Name: path, Op: Remove}) { + return nil + } + // Don't return the error + } + } + + if events&unix.FILE_MODIFIED != 0 { + if fmode.IsDir() && watchedDir { + if err := w.updateDirectory(path); err != nil { + return err + } + } else { + if !w.sendEvent(Event{Name: path, Op: Write}) { + return nil + } + } + } + if events&unix.FILE_ATTRIB != 0 && stat != nil { + // Only send Chmod if perms changed + if stat.Mode().Perm() != fmode.Perm() { + if !w.sendEvent(Event{Name: path, Op: Chmod}) { + return nil + } + } + } + + if stat != nil { + // If we get here, it means we've hit an event above that requires us to + // continue watching the file or directory + err := w.associateFile(path, stat, isWatched) + if errors.Is(err, fs.ErrNotExist) { + // Path may have been removed since the stat. + err = nil + } + return err + } + return nil +} + +// The directory was modified, so we must find unwatched entities and watch +// them. If something was removed from the directory, nothing will happen, as +// everything else should still be watched. +func (w *fen) updateDirectory(path string) error { + files, err := os.ReadDir(path) + if err != nil { + // Directory no longer exists: probably just deleted since we got the + // event. + if errors.Is(err, fs.ErrNotExist) { + return nil + } + return err + } + + for _, entry := range files { + path := filepath.Join(path, entry.Name()) + if w.port.PathIsWatched(path) { + continue + } + + finfo, err := entry.Info() + if err != nil { + return err + } + err = w.associateFile(path, finfo, false) + if errors.Is(err, fs.ErrNotExist) { + // File may have disappeared between getting the dir listing and + // adding the port: that's okay to ignore. + continue + } + if !w.sendError(err) { + return nil + } + if !w.sendEvent(Event{Name: path, Op: Create}) { + return nil + } + } + return nil +} + +func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error { + if w.isClosed() { + return ErrClosed + } + // This is primarily protecting the call to AssociatePath but it is + // important and intentional that the call to PathIsWatched is also + // protected by this mutex. Without this mutex, AssociatePath has been seen + // to error out that the path is already associated. + w.mu.Lock() + defer w.mu.Unlock() + + if w.port.PathIsWatched(path) { + // Remove the old association in favor of this one If we get ENOENT, + // then while the x/sys/unix wrapper still thought that this path was + // associated, the underlying event port did not. This call will have + // cleared up that discrepancy. The most likely cause is that the event + // has fired but we haven't processed it yet. + err := w.port.DissociatePath(path) + if err != nil && !errors.Is(err, unix.ENOENT) { + return fmt.Errorf("port.DissociatePath(%q): %w", path, err) + } + } + + var events int + if !follow { + // Watch symlinks themselves rather than their targets unless this entry + // is explicitly watched. + events |= unix.FILE_NOFOLLOW + } + if true { // TODO: implement withOps() + events |= unix.FILE_MODIFIED + } + if true { + events |= unix.FILE_ATTRIB + } + err := w.port.AssociatePath(path, stat, events, stat.Mode()) + if err != nil { + return fmt.Errorf("port.AssociatePath(%q): %w", path, err) + } + return nil +} + +func (w *fen) dissociateFile(path string, stat os.FileInfo, unused bool) error { + if !w.port.PathIsWatched(path) { + return nil + } + err := w.port.DissociatePath(path) + if err != nil { + return fmt.Errorf("port.DissociatePath(%q): %w", path, err) + } + return nil +} + +func (w *fen) WatchList() []string { + if w.isClosed() { + return nil + } + + w.mu.Lock() + defer w.mu.Unlock() + + entries := make([]string, 0, len(w.watches)+len(w.dirs)) + for pathname := range w.dirs { + entries = append(entries, pathname) + } + for pathname := range w.watches { + entries = append(entries, pathname) + } + + return entries +} + +func (w *fen) xSupports(op Op) bool { + if op.Has(xUnportableOpen) || op.Has(xUnportableRead) || + op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) { + return false + } + return true +} diff --git a/vendor/github.com/fsnotify/fsnotify/backend_inotify.go b/vendor/github.com/fsnotify/fsnotify/backend_inotify.go new file mode 100644 index 00000000..a36cb89d --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/backend_inotify.go @@ -0,0 +1,583 @@ +//go:build linux && !appengine + +package fsnotify + +import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + "sync" + "time" + "unsafe" + + "github.com/fsnotify/fsnotify/internal" + "golang.org/x/sys/unix" +) + +type inotify struct { + *shared + Events chan Event + Errors chan error + + // Store fd here as os.File.Read() will no longer return on close after + // calling Fd(). See: https://github.com/golang/go/issues/26439 + fd int + inotifyFile *os.File + watches *watches + doneResp chan struct{} // Channel to respond to Close + + // Store rename cookies in an array, with the index wrapping to 0. Almost + // all of the time what we get is a MOVED_FROM to set the cookie and the + // next event inotify sends will be MOVED_TO to read it. However, this is + // not guaranteed – as described in inotify(7) – and we may get other events + // between the two MOVED_* events (including other MOVED_* ones). + // + // A second issue is that moving a file outside the watched directory will + // trigger a MOVED_FROM to set the cookie, but we never see the MOVED_TO to + // read and delete it. So just storing it in a map would slowly leak memory. + // + // Doing it like this gives us a simple fast LRU-cache that won't allocate. + // Ten items should be more than enough for our purpose, and a loop over + // such a short array is faster than a map access anyway (not that it hugely + // matters since we're talking about hundreds of ns at the most, but still). + cookies [10]koekje + cookieIndex uint8 + cookiesMu sync.Mutex +} + +type ( + watches struct { + wd map[uint32]*watch // wd → watch + path map[string]uint32 // pathname → wd + } + watch struct { + wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) + flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) + path string // Watch path. + recurse bool // Recursion with ./...? + } + koekje struct { + cookie uint32 + path string + } +) + +func newWatches() *watches { + return &watches{ + wd: make(map[uint32]*watch), + path: make(map[string]uint32), + } +} + +func (w *watches) byPath(path string) *watch { return w.wd[w.path[path]] } +func (w *watches) byWd(wd uint32) *watch { return w.wd[wd] } +func (w *watches) len() int { return len(w.wd) } +func (w *watches) add(ww *watch) { w.wd[ww.wd] = ww; w.path[ww.path] = ww.wd } +func (w *watches) remove(watch *watch) { delete(w.path, watch.path); delete(w.wd, watch.wd) } + +func (w *watches) removePath(path string) ([]uint32, error) { + path, recurse := recursivePath(path) + wd, ok := w.path[path] + if !ok { + return nil, fmt.Errorf("%w: %s", ErrNonExistentWatch, path) + } + + watch := w.wd[wd] + if recurse && !watch.recurse { + return nil, fmt.Errorf("can't use /... with non-recursive watch %q", path) + } + + delete(w.path, path) + delete(w.wd, wd) + if !watch.recurse { + return []uint32{wd}, nil + } + + wds := make([]uint32, 0, 8) + wds = append(wds, wd) + for p, rwd := range w.path { + if strings.HasPrefix(p, path) { + delete(w.path, p) + delete(w.wd, rwd) + wds = append(wds, rwd) + } + } + return wds, nil +} + +func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error { + var existing *watch + wd, ok := w.path[path] + if ok { + existing = w.wd[wd] + } + + upd, err := f(existing) + if err != nil { + return err + } + if upd != nil { + w.wd[upd.wd] = upd + w.path[upd.path] = upd.wd + + if upd.wd != wd { + delete(w.wd, wd) + } + } + + return nil +} + +var defaultBufferSize = 0 + +func newBackend(ev chan Event, errs chan error) (backend, error) { + // Need to set nonblocking mode for SetDeadline to work, otherwise blocking + // I/O operations won't terminate on close. + fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK) + if fd == -1 { + return nil, errno + } + + w := &inotify{ + shared: newShared(ev, errs), + Events: ev, + Errors: errs, + fd: fd, + inotifyFile: os.NewFile(uintptr(fd), ""), + watches: newWatches(), + doneResp: make(chan struct{}), + } + + go w.readEvents() + return w, nil +} + +func (w *inotify) Close() error { + if w.shared.close() { + return nil + } + + // Causes any blocking reads to return with an error, provided the file + // still supports deadline operations. + err := w.inotifyFile.Close() + if err != nil { + return err + } + + <-w.doneResp // Wait for readEvents() to finish. + return nil +} + +func (w *inotify) Add(name string) error { return w.AddWith(name) } + +func (w *inotify) AddWith(path string, opts ...addOpt) error { + if w.isClosed() { + return ErrClosed + } + if debug { + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n", + time.Now().Format("15:04:05.000000000"), path) + } + + with := getOptions(opts...) + if !w.xSupports(with.op) { + return fmt.Errorf("%w: %s", xErrUnsupported, with.op) + } + + add := func(path string, with withOpts, recurse bool) error { + var flags uint32 + if with.noFollow { + flags |= unix.IN_DONT_FOLLOW + } + if with.op.Has(Create) { + flags |= unix.IN_CREATE + } + if with.op.Has(Write) { + flags |= unix.IN_MODIFY + } + if with.op.Has(Remove) { + flags |= unix.IN_DELETE | unix.IN_DELETE_SELF + } + if with.op.Has(Rename) { + flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF + } + if with.op.Has(Chmod) { + flags |= unix.IN_ATTRIB + } + if with.op.Has(xUnportableOpen) { + flags |= unix.IN_OPEN + } + if with.op.Has(xUnportableRead) { + flags |= unix.IN_ACCESS + } + if with.op.Has(xUnportableCloseWrite) { + flags |= unix.IN_CLOSE_WRITE + } + if with.op.Has(xUnportableCloseRead) { + flags |= unix.IN_CLOSE_NOWRITE + } + return w.register(path, flags, recurse) + } + + w.mu.Lock() + defer w.mu.Unlock() + path, recurse := recursivePath(path) + if recurse { + return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() { + if root == path { + return fmt.Errorf("fsnotify: not a directory: %q", path) + } + return nil + } + + // Send a Create event when adding new directory from a recursive + // watch; this is for "mkdir -p one/two/three". Usually all those + // directories will be created before we can set up watchers on the + // subdirectories, so only "one" would be sent as a Create event and + // not "one/two" and "one/two/three" (inotifywait -r has the same + // problem). + if with.sendCreate && root != path { + w.sendEvent(Event{Name: root, Op: Create}) + } + + return add(root, with, true) + }) + } + + return add(path, with, false) +} + +func (w *inotify) register(path string, flags uint32, recurse bool) error { + return w.watches.updatePath(path, func(existing *watch) (*watch, error) { + if existing != nil { + flags |= existing.flags | unix.IN_MASK_ADD + } + + wd, err := unix.InotifyAddWatch(w.fd, path, flags) + if wd == -1 { + return nil, err + } + + if e, ok := w.watches.wd[uint32(wd)]; ok { + return e, nil + } + + if existing == nil { + return &watch{ + wd: uint32(wd), + path: path, + flags: flags, + recurse: recurse, + }, nil + } + + existing.wd = uint32(wd) + existing.flags = flags + return existing, nil + }) +} + +func (w *inotify) Remove(name string) error { + if w.isClosed() { + return nil + } + if debug { + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n", + time.Now().Format("15:04:05.000000000"), name) + } + + w.mu.Lock() + defer w.mu.Unlock() + return w.remove(filepath.Clean(name)) +} + +func (w *inotify) remove(name string) error { + wds, err := w.watches.removePath(name) + if err != nil { + return err + } + + for _, wd := range wds { + _, err := unix.InotifyRmWatch(w.fd, wd) + if err != nil { + // TODO: Perhaps it's not helpful to return an error here in every + // case; the only two possible errors are: + // + // EBADF, which happens when w.fd is not a valid file descriptor of + // any kind. + // + // EINVAL, which is when fd is not an inotify descriptor or wd is + // not a valid watch descriptor. Watch descriptors are invalidated + // when they are removed explicitly or implicitly; explicitly by + // inotify_rm_watch, implicitly when the file they are watching is + // deleted. + return err + } + } + return nil +} + +func (w *inotify) WatchList() []string { + if w.isClosed() { + return nil + } + + w.mu.Lock() + defer w.mu.Unlock() + entries := make([]string, 0, w.watches.len()) + for pathname := range w.watches.path { + entries = append(entries, pathname) + } + return entries +} + +// readEvents reads from the inotify file descriptor, converts the +// received events into Event objects and sends them via the Events channel +func (w *inotify) readEvents() { + defer func() { + close(w.doneResp) + close(w.Errors) + close(w.Events) + }() + + var buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events + for { + if w.isClosed() { + return + } + + n, err := w.inotifyFile.Read(buf[:]) + if err != nil { + if errors.Is(err, os.ErrClosed) { + return + } + if !w.sendError(err) { + return + } + continue + } + + if n < unix.SizeofInotifyEvent { + err := errors.New("notify: short read in readEvents()") // Read was too short. + if n == 0 { + err = io.EOF // If EOF is received. This should really never happen. + } + if !w.sendError(err) { + return + } + continue + } + + // We don't know how many events we just read into the buffer While the + // offset points to at least one whole event. + var offset uint32 + for offset <= uint32(n-unix.SizeofInotifyEvent) { + // Point to the event in the buffer. + inEvent := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) + + if inEvent.Mask&unix.IN_Q_OVERFLOW != 0 { + if !w.sendError(ErrEventOverflow) { + return + } + } + + ev, ok := w.handleEvent(inEvent, &buf, offset) + if !ok { + return + } + if !w.sendEvent(ev) { + return + } + + // Move to the next event in the buffer + offset += unix.SizeofInotifyEvent + inEvent.Len + } + } +} + +func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offset uint32) (Event, bool) { + w.mu.Lock() + defer w.mu.Unlock() + + /// If the event happened to the watched directory or the watched file, the + /// kernel doesn't append the filename to the event, but we would like to + /// always fill the the "Name" field with a valid filename. We retrieve the + /// path of the watch from the "paths" map. + /// + /// Can be nil if Remove() was called in another goroutine for this path + /// inbetween reading the events from the kernel and reading the internal + /// state. Not much we can do about it, so just skip. See #616. + watch := w.watches.byWd(uint32(inEvent.Wd)) + if watch == nil { + return Event{}, true + } + + var ( + name = watch.path + nameLen = uint32(inEvent.Len) + ) + if nameLen > 0 { + /// Point "bytes" at the first byte of the filename + bb := *buf + bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&bb[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen] + /// The filename is padded with NULL bytes. TrimRight() gets rid of those. + name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\x00") + } + + if debug { + internal.Debug(name, inEvent.Mask, inEvent.Cookie) + } + + if inEvent.Mask&unix.IN_IGNORED != 0 || inEvent.Mask&unix.IN_UNMOUNT != 0 { + w.watches.remove(watch) + return Event{}, true + } + + // inotify will automatically remove the watch on deletes; just need + // to clean our state here. + if inEvent.Mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { + w.watches.remove(watch) + } + + // We can't really update the state when a watched path is moved; only + // IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove the watch. + if inEvent.Mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF { + if watch.recurse { // Do nothing + return Event{}, true + } + + err := w.remove(watch.path) + if err != nil && !errors.Is(err, ErrNonExistentWatch) { + if !w.sendError(err) { + return Event{}, false + } + } + } + + /// Skip if we're watching both this path and the parent; the parent will + /// already send a delete so no need to do it twice. + if inEvent.Mask&unix.IN_DELETE_SELF != 0 { + _, ok := w.watches.path[filepath.Dir(watch.path)] + if ok { + return Event{}, true + } + } + + ev := w.newEvent(name, inEvent.Mask, inEvent.Cookie) + // Need to update watch path for recurse. + if watch.recurse { + isDir := inEvent.Mask&unix.IN_ISDIR == unix.IN_ISDIR + /// New directory created: set up watch on it. + if isDir && ev.Has(Create) { + err := w.register(ev.Name, watch.flags, true) + if !w.sendError(err) { + return Event{}, false + } + + // This was a directory rename, so we need to update all the + // children. + // + // TODO: this is of course pretty slow; we should use a better data + // structure for storing all of this, e.g. store children in the + // watch. I have some code for this in my kqueue refactor we can use + // in the future. For now I'm okay with this as it's not publicly + // available. Correctness first, performance second. + if ev.renamedFrom != "" { + for k, ww := range w.watches.wd { + if k == watch.wd || ww.path == ev.Name { + continue + } + if strings.HasPrefix(ww.path, ev.renamedFrom) { + ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1) + w.watches.wd[k] = ww + } + } + } + } + } + + return ev, true +} + +func (w *inotify) isRecursive(path string) bool { + ww := w.watches.byPath(path) + if ww == nil { // path could be a file, so also check the Dir. + ww = w.watches.byPath(filepath.Dir(path)) + } + return ww != nil && ww.recurse +} + +func (w *inotify) newEvent(name string, mask, cookie uint32) Event { + e := Event{Name: name} + if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { + e.Op |= Create + } + if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE { + e.Op |= Remove + } + if mask&unix.IN_MODIFY == unix.IN_MODIFY { + e.Op |= Write + } + if mask&unix.IN_OPEN == unix.IN_OPEN { + e.Op |= xUnportableOpen + } + if mask&unix.IN_ACCESS == unix.IN_ACCESS { + e.Op |= xUnportableRead + } + if mask&unix.IN_CLOSE_WRITE == unix.IN_CLOSE_WRITE { + e.Op |= xUnportableCloseWrite + } + if mask&unix.IN_CLOSE_NOWRITE == unix.IN_CLOSE_NOWRITE { + e.Op |= xUnportableCloseRead + } + if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { + e.Op |= Rename + } + if mask&unix.IN_ATTRIB == unix.IN_ATTRIB { + e.Op |= Chmod + } + + if cookie != 0 { + if mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { + w.cookiesMu.Lock() + w.cookies[w.cookieIndex] = koekje{cookie: cookie, path: e.Name} + w.cookieIndex++ + if w.cookieIndex > 9 { + w.cookieIndex = 0 + } + w.cookiesMu.Unlock() + } else if mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { + w.cookiesMu.Lock() + var prev string + for _, c := range w.cookies { + if c.cookie == cookie { + prev = c.path + break + } + } + w.cookiesMu.Unlock() + e.renamedFrom = prev + } + } + return e +} + +func (w *inotify) xSupports(op Op) bool { + return true // Supports everything. +} + +func (w *inotify) state() { + w.mu.Lock() + defer w.mu.Unlock() + for wd, ww := range w.watches.wd { + fmt.Fprintf(os.Stderr, "%4d: recurse=%t %q\n", wd, ww.recurse, ww.path) + } +} diff --git a/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go b/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go new file mode 100644 index 00000000..340aeec0 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/backend_kqueue.go @@ -0,0 +1,705 @@ +//go:build freebsd || openbsd || netbsd || dragonfly || darwin + +package fsnotify + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + "time" + + "github.com/fsnotify/fsnotify/internal" + "golang.org/x/sys/unix" +) + +type kqueue struct { + *shared + Events chan Event + Errors chan error + + kq int // File descriptor (as returned by the kqueue() syscall). + closepipe [2]int // Pipe used for closing kq. + watches *watches +} + +type ( + watches struct { + mu sync.RWMutex + wd map[int]watch // wd → watch + path map[string]int // pathname → wd + byDir map[string]map[int]struct{} // dirname(path) → wd + seen map[string]struct{} // Keep track of if we know this file exists. + byUser map[string]struct{} // Watches added with Watcher.Add() + } + watch struct { + wd int + name string + linkName string // In case of links; name is the target, and this is the link. + isDir bool + dirFlags uint32 + } +) + +func newWatches() *watches { + return &watches{ + wd: make(map[int]watch), + path: make(map[string]int), + byDir: make(map[string]map[int]struct{}), + seen: make(map[string]struct{}), + byUser: make(map[string]struct{}), + } +} + +func (w *watches) listPaths(userOnly bool) []string { + w.mu.RLock() + defer w.mu.RUnlock() + + if userOnly { + l := make([]string, 0, len(w.byUser)) + for p := range w.byUser { + l = append(l, p) + } + return l + } + + l := make([]string, 0, len(w.path)) + for p := range w.path { + l = append(l, p) + } + return l +} + +func (w *watches) watchesInDir(path string) []string { + w.mu.RLock() + defer w.mu.RUnlock() + + l := make([]string, 0, 4) + for fd := range w.byDir[path] { + info := w.wd[fd] + if _, ok := w.byUser[info.name]; !ok { + l = append(l, info.name) + } + } + return l +} + +// Mark path as added by the user. +func (w *watches) addUserWatch(path string) { + w.mu.Lock() + defer w.mu.Unlock() + w.byUser[path] = struct{}{} +} + +func (w *watches) addLink(path string, fd int) { + w.mu.Lock() + defer w.mu.Unlock() + + w.path[path] = fd + w.seen[path] = struct{}{} +} + +func (w *watches) add(path, linkPath string, fd int, isDir bool) { + w.mu.Lock() + defer w.mu.Unlock() + + w.path[path] = fd + w.wd[fd] = watch{wd: fd, name: path, linkName: linkPath, isDir: isDir} + + parent := filepath.Dir(path) + byDir, ok := w.byDir[parent] + if !ok { + byDir = make(map[int]struct{}, 1) + w.byDir[parent] = byDir + } + byDir[fd] = struct{}{} +} + +func (w *watches) byWd(fd int) (watch, bool) { + w.mu.RLock() + defer w.mu.RUnlock() + info, ok := w.wd[fd] + return info, ok +} + +func (w *watches) byPath(path string) (watch, bool) { + w.mu.RLock() + defer w.mu.RUnlock() + info, ok := w.wd[w.path[path]] + return info, ok +} + +func (w *watches) updateDirFlags(path string, flags uint32) bool { + w.mu.Lock() + defer w.mu.Unlock() + + fd, ok := w.path[path] + if !ok { // Already deleted: don't re-set it here. + return false + } + info := w.wd[fd] + info.dirFlags = flags + w.wd[fd] = info + return true +} + +func (w *watches) remove(fd int, path string) bool { + w.mu.Lock() + defer w.mu.Unlock() + + isDir := w.wd[fd].isDir + delete(w.path, path) + delete(w.byUser, path) + + parent := filepath.Dir(path) + delete(w.byDir[parent], fd) + + if len(w.byDir[parent]) == 0 { + delete(w.byDir, parent) + } + + delete(w.wd, fd) + delete(w.seen, path) + return isDir +} + +func (w *watches) markSeen(path string, exists bool) { + w.mu.Lock() + defer w.mu.Unlock() + if exists { + w.seen[path] = struct{}{} + } else { + delete(w.seen, path) + } +} + +func (w *watches) seenBefore(path string) bool { + w.mu.RLock() + defer w.mu.RUnlock() + _, ok := w.seen[path] + return ok +} + +var defaultBufferSize = 0 + +func newBackend(ev chan Event, errs chan error) (backend, error) { + kq, closepipe, err := newKqueue() + if err != nil { + return nil, err + } + + w := &kqueue{ + shared: newShared(ev, errs), + Events: ev, + Errors: errs, + kq: kq, + closepipe: closepipe, + watches: newWatches(), + } + + go w.readEvents() + return w, nil +} + +// newKqueue creates a new kernel event queue and returns a descriptor. +// +// This registers a new event on closepipe, which will trigger an event when +// it's closed. This way we can use kevent() without timeout/polling; without +// the closepipe, it would block forever and we wouldn't be able to stop it at +// all. +func newKqueue() (kq int, closepipe [2]int, err error) { + kq, err = unix.Kqueue() + if err != nil { + return kq, closepipe, err + } + + // Register the close pipe. + err = unix.Pipe(closepipe[:]) + if err != nil { + unix.Close(kq) + return kq, closepipe, err + } + unix.CloseOnExec(closepipe[0]) + unix.CloseOnExec(closepipe[1]) + + // Register changes to listen on the closepipe. + changes := make([]unix.Kevent_t, 1) + // SetKevent converts int to the platform-specific types. + unix.SetKevent(&changes[0], closepipe[0], unix.EVFILT_READ, + unix.EV_ADD|unix.EV_ENABLE|unix.EV_ONESHOT) + + ok, err := unix.Kevent(kq, changes, nil, nil) + if ok == -1 { + unix.Close(kq) + unix.Close(closepipe[0]) + unix.Close(closepipe[1]) + return kq, closepipe, err + } + return kq, closepipe, nil +} + +func (w *kqueue) Close() error { + if w.shared.close() { + return nil + } + + pathsToRemove := w.watches.listPaths(false) + for _, name := range pathsToRemove { + w.Remove(name) + } + + unix.Close(w.closepipe[1]) // Send "quit" message to readEvents + return nil +} + +func (w *kqueue) Add(name string) error { return w.AddWith(name) } + +func (w *kqueue) AddWith(name string, opts ...addOpt) error { + if debug { + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n", + time.Now().Format("15:04:05.000000000"), name) + } + + with := getOptions(opts...) + if !w.xSupports(with.op) { + return fmt.Errorf("%w: %s", xErrUnsupported, with.op) + } + + _, err := w.addWatch(name, noteAllEvents, false) + if err != nil { + return err + } + w.watches.addUserWatch(name) + return nil +} + +func (w *kqueue) Remove(name string) error { + if debug { + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n", + time.Now().Format("15:04:05.000000000"), name) + } + return w.remove(name, true) +} + +func (w *kqueue) remove(name string, unwatchFiles bool) error { + if w.isClosed() { + return nil + } + + name = filepath.Clean(name) + info, ok := w.watches.byPath(name) + if !ok { + return fmt.Errorf("%w: %s", ErrNonExistentWatch, name) + } + + err := w.register([]int{info.wd}, unix.EV_DELETE, 0) + if err != nil { + return err + } + + unix.Close(info.wd) + + isDir := w.watches.remove(info.wd, name) + + // Find all watched paths that are in this directory that are not external. + if unwatchFiles && isDir { + pathsToRemove := w.watches.watchesInDir(name) + for _, name := range pathsToRemove { + // Since these are internal, not much sense in propagating error to + // the user, as that will just confuse them with an error about a + // path they did not explicitly watch themselves. + w.Remove(name) + } + } + return nil +} + +func (w *kqueue) WatchList() []string { + if w.isClosed() { + return nil + } + return w.watches.listPaths(true) +} + +// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) +const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME + +// addWatch adds name to the watched file set; the flags are interpreted as +// described in kevent(2). +// +// Returns the real path to the file which was added, with symlinks resolved. +func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, error) { + if w.isClosed() { + return "", ErrClosed + } + + name = filepath.Clean(name) + + info, alreadyWatching := w.watches.byPath(name) + if !alreadyWatching { + fi, err := os.Lstat(name) + if err != nil { + return "", err + } + + // Don't watch sockets or named pipes. + if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) { + return "", nil + } + + // Follow symlinks, but only for paths added with Add(), and not paths + // we're adding from internalWatch from a listdir. + if !listDir && fi.Mode()&os.ModeSymlink == os.ModeSymlink { + link, err := os.Readlink(name) + if err != nil { + return "", err + } + if !filepath.IsAbs(link) { + link = filepath.Join(filepath.Dir(name), link) + } + + _, alreadyWatching = w.watches.byPath(link) + if alreadyWatching { + // Add to watches so we don't get spurious Create events later + // on when we diff the directories. + w.watches.addLink(name, 0) + return link, nil + } + + info.linkName = name + name = link + fi, err = os.Lstat(name) + if err != nil { + return "", err + } + } + + // Retry on EINTR; open() can return EINTR in practice on macOS. + // See #354, and Go issues 11180 and 39237. + for { + info.wd, err = unix.Open(name, openMode, 0) + if err == nil { + break + } + if errors.Is(err, unix.EINTR) { + continue + } + return "", err + } + + info.isDir = fi.IsDir() + } + + err := w.register([]int{info.wd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags) + if err != nil { + unix.Close(info.wd) + return "", err + } + + if !alreadyWatching { + w.watches.add(name, info.linkName, info.wd, info.isDir) + } + + // Watch the directory if it has not been watched before, or if it was + // watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) + if info.isDir { + watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE && + (!alreadyWatching || (info.dirFlags&unix.NOTE_WRITE) != unix.NOTE_WRITE) + if !w.watches.updateDirFlags(name, flags) { + return "", nil + } + + if watchDir { + d := name + if info.linkName != "" { + d = info.linkName + } + if err := w.watchDirectoryFiles(d); err != nil { + return "", err + } + } + } + return name, nil +} + +// readEvents reads from kqueue and converts the received kevents into +// Event values that it sends down the Events channel. +func (w *kqueue) readEvents() { + defer func() { + close(w.Events) + close(w.Errors) + _ = unix.Close(w.kq) + unix.Close(w.closepipe[0]) + }() + + eventBuffer := make([]unix.Kevent_t, 10) + for { + kevents, err := w.read(eventBuffer) + // EINTR is okay, the syscall was interrupted before timeout expired. + if err != nil && err != unix.EINTR { + if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) { + return + } + } + + for _, kevent := range kevents { + var ( + wd = int(kevent.Ident) + mask = uint32(kevent.Fflags) + ) + + // Shut down the loop when the pipe is closed, but only after all + // other events have been processed. + if wd == w.closepipe[0] { + return + } + + path, ok := w.watches.byWd(wd) + if debug { + internal.Debug(path.name, &kevent) + } + + // On macOS it seems that sometimes an event with Ident=0 is + // delivered, and no other flags/information beyond that, even + // though we never saw such a file descriptor. For example in + // TestWatchSymlink/277 (usually at the end, but sometimes sooner): + // + // fmt.Printf("READ: %2d %#v\n", kevent.Ident, kevent) + // unix.Kevent_t{Ident:0x2a, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)} + // unix.Kevent_t{Ident:0x0, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)} + // + // The first is a normal event, the second with Ident 0. No error + // flag, no data, no ... nothing. + // + // I read a bit through bsd/kern_event.c from the xnu source, but I + // don't really see an obvious location where this is triggered – + // this doesn't seem intentional, but idk... + // + // Technically fd 0 is a valid descriptor, so only skip it if + // there's no path, and if we're on macOS. + if !ok && kevent.Ident == 0 && runtime.GOOS == "darwin" { + continue + } + + event := w.newEvent(path.name, path.linkName, mask) + + if event.Has(Rename) || event.Has(Remove) { + w.remove(event.Name, false) + w.watches.markSeen(event.Name, false) + } + + if path.isDir && event.Has(Write) && !event.Has(Remove) { + w.dirChange(event.Name) + } else if !w.sendEvent(event) { + return + } + + if event.Has(Remove) { + // Look for a file that may have overwritten this; for example, + // mv f1 f2 will delete f2, then create f2. + if path.isDir { + fileDir := filepath.Clean(event.Name) + _, found := w.watches.byPath(fileDir) + if found { + // TODO: this branch is never triggered in any test. + // Added in d6220df (2012). + // isDir check added in 8611c35 (2016): https://github.com/fsnotify/fsnotify/pull/111 + // + // I don't really get how this can be triggered either. + // And it wasn't triggered in the patch that added it, + // either. + // + // Original also had a comment: + // make sure the directory exists before we watch for + // changes. When we do a recursive watch and perform + // rm -rf, the parent directory might have gone + // missing, ignore the missing directory and let the + // upcoming delete event remove the watch from the + // parent directory. + err := w.dirChange(fileDir) + if !w.sendError(err) { + return + } + } + } else { + path := filepath.Clean(event.Name) + if fi, err := os.Lstat(path); err == nil { + err := w.sendCreateIfNew(path, fi) + if !w.sendError(err) { + return + } + } + } + } + } + } +} + +// newEvent returns an platform-independent Event based on kqueue Fflags. +func (w *kqueue) newEvent(name, linkName string, mask uint32) Event { + e := Event{Name: name} + if linkName != "" { + // If the user watched "/path/link" then emit events as "/path/link" + // rather than "/path/target". + e.Name = linkName + } + + if mask&unix.NOTE_DELETE == unix.NOTE_DELETE { + e.Op |= Remove + } + if mask&unix.NOTE_WRITE == unix.NOTE_WRITE { + e.Op |= Write + } + if mask&unix.NOTE_RENAME == unix.NOTE_RENAME { + e.Op |= Rename + } + if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB { + e.Op |= Chmod + } + // No point sending a write and delete event at the same time: if it's gone, + // then it's gone. + if e.Op.Has(Write) && e.Op.Has(Remove) { + e.Op &^= Write + } + return e +} + +// watchDirectoryFiles to mimic inotify when adding a watch on a directory +func (w *kqueue) watchDirectoryFiles(dirPath string) error { + files, err := os.ReadDir(dirPath) + if err != nil { + return err + } + + for _, f := range files { + path := filepath.Join(dirPath, f.Name()) + + fi, err := f.Info() + if err != nil { + return fmt.Errorf("%q: %w", path, err) + } + + cleanPath, err := w.internalWatch(path, fi) + if err != nil { + // No permission to read the file; that's not a problem: just skip. + // But do add it to w.fileExists to prevent it from being picked up + // as a "new" file later (it still shows up in the directory + // listing). + switch { + case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM): + cleanPath = filepath.Clean(path) + default: + return fmt.Errorf("%q: %w", path, err) + } + } + + w.watches.markSeen(cleanPath, true) + } + + return nil +} + +// Search the directory for new files and send an event for them. +// +// This functionality is to have the BSD watcher match the inotify, which sends +// a create event for files created in a watched directory. +func (w *kqueue) dirChange(dir string) error { + files, err := os.ReadDir(dir) + if err != nil { + // Directory no longer exists: we can ignore this safely. kqueue will + // still give us the correct events. + if errors.Is(err, os.ErrNotExist) { + return nil + } + return fmt.Errorf("fsnotify.dirChange %q: %w", dir, err) + } + + for _, f := range files { + fi, err := f.Info() + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return fmt.Errorf("fsnotify.dirChange: %w", err) + } + + err = w.sendCreateIfNew(filepath.Join(dir, fi.Name()), fi) + if err != nil { + // Don't need to send an error if this file isn't readable. + if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) || errors.Is(err, os.ErrNotExist) { + return nil + } + return fmt.Errorf("fsnotify.dirChange: %w", err) + } + } + return nil +} + +// Send a create event if the file isn't already being tracked, and start +// watching this file. +func (w *kqueue) sendCreateIfNew(path string, fi os.FileInfo) error { + if !w.watches.seenBefore(path) { + if !w.sendEvent(Event{Name: path, Op: Create}) { + return nil + } + } + + // Like watchDirectoryFiles, but without doing another ReadDir. + path, err := w.internalWatch(path, fi) + if err != nil { + return err + } + w.watches.markSeen(path, true) + return nil +} + +func (w *kqueue) internalWatch(name string, fi os.FileInfo) (string, error) { + if fi.IsDir() { + // mimic Linux providing delete events for subdirectories, but preserve + // the flags used if currently watching subdirectory + info, _ := w.watches.byPath(name) + return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME, true) + } + + // Watch file to mimic Linux inotify. + return w.addWatch(name, noteAllEvents, true) +} + +// Register events with the queue. +func (w *kqueue) register(fds []int, flags int, fflags uint32) error { + changes := make([]unix.Kevent_t, len(fds)) + for i, fd := range fds { + // SetKevent converts int to the platform-specific types. + unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags) + changes[i].Fflags = fflags + } + + // Register the events. + success, err := unix.Kevent(w.kq, changes, nil, nil) + if success == -1 { + return err + } + return nil +} + +// read retrieves pending events, or waits until an event occurs. +func (w *kqueue) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) { + n, err := unix.Kevent(w.kq, nil, events, nil) + if err != nil { + return nil, err + } + return events[0:n], nil +} + +func (w *kqueue) xSupports(op Op) bool { + //if runtime.GOOS == "freebsd" { + // return true // Supports everything. + //} + if op.Has(xUnportableOpen) || op.Has(xUnportableRead) || + op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) { + return false + } + return true +} diff --git a/vendor/github.com/fsnotify/fsnotify/backend_other.go b/vendor/github.com/fsnotify/fsnotify/backend_other.go new file mode 100644 index 00000000..b8c0ad72 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/backend_other.go @@ -0,0 +1,22 @@ +//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows) + +package fsnotify + +import "errors" + +type other struct { + Events chan Event + Errors chan error +} + +var defaultBufferSize = 0 + +func newBackend(ev chan Event, errs chan error) (backend, error) { + return nil, errors.New("fsnotify not supported on the current platform") +} +func (w *other) Close() error { return nil } +func (w *other) WatchList() []string { return nil } +func (w *other) Add(name string) error { return nil } +func (w *other) AddWith(name string, opts ...addOpt) error { return nil } +func (w *other) Remove(name string) error { return nil } +func (w *other) xSupports(op Op) bool { return false } diff --git a/vendor/github.com/fsnotify/fsnotify/backend_windows.go b/vendor/github.com/fsnotify/fsnotify/backend_windows.go new file mode 100644 index 00000000..3433642d --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/backend_windows.go @@ -0,0 +1,680 @@ +//go:build windows + +// Windows backend based on ReadDirectoryChangesW() +// +// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw + +package fsnotify + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "sync" + "time" + "unsafe" + + "github.com/fsnotify/fsnotify/internal" + "golang.org/x/sys/windows" +) + +type readDirChangesW struct { + Events chan Event + Errors chan error + + port windows.Handle // Handle to completion port + input chan *input // Inputs to the reader are sent on this channel + done chan chan<- error + + mu sync.Mutex // Protects access to watches, closed + watches watchMap // Map of watches (key: i-number) + closed bool // Set to true when Close() is first called +} + +var defaultBufferSize = 50 + +func newBackend(ev chan Event, errs chan error) (backend, error) { + port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0) + if err != nil { + return nil, os.NewSyscallError("CreateIoCompletionPort", err) + } + w := &readDirChangesW{ + Events: ev, + Errors: errs, + port: port, + watches: make(watchMap), + input: make(chan *input, 1), + done: make(chan chan<- error, 1), + } + go w.readEvents() + return w, nil +} + +func (w *readDirChangesW) isClosed() bool { + w.mu.Lock() + defer w.mu.Unlock() + return w.closed +} + +func (w *readDirChangesW) sendEvent(name, renamedFrom string, mask uint64) bool { + if mask == 0 { + return false + } + + event := w.newEvent(name, uint32(mask)) + event.renamedFrom = renamedFrom + select { + case ch := <-w.done: + w.done <- ch + case w.Events <- event: + } + return true +} + +// Returns true if the error was sent, or false if watcher is closed. +func (w *readDirChangesW) sendError(err error) bool { + if err == nil { + return true + } + select { + case <-w.done: + return false + case w.Errors <- err: + return true + } +} + +func (w *readDirChangesW) Close() error { + if w.isClosed() { + return nil + } + + w.mu.Lock() + w.closed = true + w.mu.Unlock() + + // Send "done" message to the reader goroutine + ch := make(chan error) + w.done <- ch + if err := w.wakeupReader(); err != nil { + return err + } + return <-ch +} + +func (w *readDirChangesW) Add(name string) error { return w.AddWith(name) } + +func (w *readDirChangesW) AddWith(name string, opts ...addOpt) error { + if w.isClosed() { + return ErrClosed + } + if debug { + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n", + time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name)) + } + + with := getOptions(opts...) + if !w.xSupports(with.op) { + return fmt.Errorf("%w: %s", xErrUnsupported, with.op) + } + if with.bufsize < 4096 { + return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes") + } + + in := &input{ + op: opAddWatch, + path: filepath.Clean(name), + flags: sysFSALLEVENTS, + reply: make(chan error), + bufsize: with.bufsize, + } + w.input <- in + if err := w.wakeupReader(); err != nil { + return err + } + return <-in.reply +} + +func (w *readDirChangesW) Remove(name string) error { + if w.isClosed() { + return nil + } + if debug { + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n", + time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name)) + } + + in := &input{ + op: opRemoveWatch, + path: filepath.Clean(name), + reply: make(chan error), + } + w.input <- in + if err := w.wakeupReader(); err != nil { + return err + } + return <-in.reply +} + +func (w *readDirChangesW) WatchList() []string { + if w.isClosed() { + return nil + } + + w.mu.Lock() + defer w.mu.Unlock() + + entries := make([]string, 0, len(w.watches)) + for _, entry := range w.watches { + for _, watchEntry := range entry { + for name := range watchEntry.names { + entries = append(entries, filepath.Join(watchEntry.path, name)) + } + // the directory itself is being watched + if watchEntry.mask != 0 { + entries = append(entries, watchEntry.path) + } + } + } + + return entries +} + +// These options are from the old golang.org/x/exp/winfsnotify, where you could +// add various options to the watch. This has long since been removed. +// +// The "sys" in the name is misleading as they're not part of any "system". +// +// This should all be removed at some point, and just use windows.FILE_NOTIFY_* +const ( + sysFSALLEVENTS = 0xfff + sysFSCREATE = 0x100 + sysFSDELETE = 0x200 + sysFSDELETESELF = 0x400 + sysFSMODIFY = 0x2 + sysFSMOVE = 0xc0 + sysFSMOVEDFROM = 0x40 + sysFSMOVEDTO = 0x80 + sysFSMOVESELF = 0x800 + sysFSIGNORED = 0x8000 +) + +func (w *readDirChangesW) newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO { + e.Op |= Create + } + if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF { + e.Op |= Remove + } + if mask&sysFSMODIFY == sysFSMODIFY { + e.Op |= Write + } + if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM { + e.Op |= Rename + } + return e +} + +const ( + opAddWatch = iota + opRemoveWatch +) + +const ( + provisional uint64 = 1 << (32 + iota) +) + +type input struct { + op int + path string + flags uint32 + bufsize int + reply chan error +} + +type inode struct { + handle windows.Handle + volume uint32 + index uint64 +} + +type watch struct { + ov windows.Overlapped + ino *inode // i-number + recurse bool // Recursive watch? + path string // Directory path + mask uint64 // Directory itself is being watched with these notify flags + names map[string]uint64 // Map of names being watched and their notify flags + rename string // Remembers the old name while renaming a file + buf []byte // buffer, allocated later +} + +type ( + indexMap map[uint64]*watch + watchMap map[uint32]indexMap +) + +func (w *readDirChangesW) wakeupReader() error { + err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil) + if err != nil { + return os.NewSyscallError("PostQueuedCompletionStatus", err) + } + return nil +} + +func (w *readDirChangesW) getDir(pathname string) (dir string, err error) { + attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname)) + if err != nil { + return "", os.NewSyscallError("GetFileAttributes", err) + } + if attr&windows.FILE_ATTRIBUTE_DIRECTORY != 0 { + dir = pathname + } else { + dir, _ = filepath.Split(pathname) + dir = filepath.Clean(dir) + } + return +} + +func (w *readDirChangesW) getIno(path string) (ino *inode, err error) { + h, err := windows.CreateFile(windows.StringToUTF16Ptr(path), + windows.FILE_LIST_DIRECTORY, + windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, + nil, windows.OPEN_EXISTING, + windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED, 0) + if err != nil { + return nil, os.NewSyscallError("CreateFile", err) + } + + var fi windows.ByHandleFileInformation + err = windows.GetFileInformationByHandle(h, &fi) + if err != nil { + windows.CloseHandle(h) + return nil, os.NewSyscallError("GetFileInformationByHandle", err) + } + ino = &inode{ + handle: h, + volume: fi.VolumeSerialNumber, + index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), + } + return ino, nil +} + +// Must run within the I/O thread. +func (m watchMap) get(ino *inode) *watch { + if i := m[ino.volume]; i != nil { + return i[ino.index] + } + return nil +} + +// Must run within the I/O thread. +func (m watchMap) set(ino *inode, watch *watch) { + i := m[ino.volume] + if i == nil { + i = make(indexMap) + m[ino.volume] = i + } + i[ino.index] = watch +} + +// Must run within the I/O thread. +func (w *readDirChangesW) addWatch(pathname string, flags uint64, bufsize int) error { + pathname, recurse := recursivePath(pathname) + + dir, err := w.getDir(pathname) + if err != nil { + return err + } + + ino, err := w.getIno(dir) + if err != nil { + return err + } + w.mu.Lock() + watchEntry := w.watches.get(ino) + w.mu.Unlock() + if watchEntry == nil { + _, err := windows.CreateIoCompletionPort(ino.handle, w.port, 0, 0) + if err != nil { + windows.CloseHandle(ino.handle) + return os.NewSyscallError("CreateIoCompletionPort", err) + } + watchEntry = &watch{ + ino: ino, + path: dir, + names: make(map[string]uint64), + recurse: recurse, + buf: make([]byte, bufsize), + } + w.mu.Lock() + w.watches.set(ino, watchEntry) + w.mu.Unlock() + flags |= provisional + } else { + windows.CloseHandle(ino.handle) + } + if pathname == dir { + watchEntry.mask |= flags + } else { + watchEntry.names[filepath.Base(pathname)] |= flags + } + + err = w.startRead(watchEntry) + if err != nil { + return err + } + + if pathname == dir { + watchEntry.mask &= ^provisional + } else { + watchEntry.names[filepath.Base(pathname)] &= ^provisional + } + return nil +} + +// Must run within the I/O thread. +func (w *readDirChangesW) remWatch(pathname string) error { + pathname, recurse := recursivePath(pathname) + + dir, err := w.getDir(pathname) + if err != nil { + return err + } + ino, err := w.getIno(dir) + if err != nil { + return err + } + + w.mu.Lock() + watch := w.watches.get(ino) + w.mu.Unlock() + + if recurse && !watch.recurse { + return fmt.Errorf("can't use \\... with non-recursive watch %q", pathname) + } + + err = windows.CloseHandle(ino.handle) + if err != nil { + w.sendError(os.NewSyscallError("CloseHandle", err)) + } + if watch == nil { + return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname) + } + if pathname == dir { + w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED) + watch.mask = 0 + } else { + name := filepath.Base(pathname) + w.sendEvent(filepath.Join(watch.path, name), "", watch.names[name]&sysFSIGNORED) + delete(watch.names, name) + } + + return w.startRead(watch) +} + +// Must run within the I/O thread. +func (w *readDirChangesW) deleteWatch(watch *watch) { + for name, mask := range watch.names { + if mask&provisional == 0 { + w.sendEvent(filepath.Join(watch.path, name), "", mask&sysFSIGNORED) + } + delete(watch.names, name) + } + if watch.mask != 0 { + if watch.mask&provisional == 0 { + w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED) + } + watch.mask = 0 + } +} + +// Must run within the I/O thread. +func (w *readDirChangesW) startRead(watch *watch) error { + err := windows.CancelIo(watch.ino.handle) + if err != nil { + w.sendError(os.NewSyscallError("CancelIo", err)) + w.deleteWatch(watch) + } + mask := w.toWindowsFlags(watch.mask) + for _, m := range watch.names { + mask |= w.toWindowsFlags(m) + } + if mask == 0 { + err := windows.CloseHandle(watch.ino.handle) + if err != nil { + w.sendError(os.NewSyscallError("CloseHandle", err)) + } + w.mu.Lock() + delete(w.watches[watch.ino.volume], watch.ino.index) + w.mu.Unlock() + return nil + } + + // We need to pass the array, rather than the slice. + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&watch.buf)) + rdErr := windows.ReadDirectoryChanges(watch.ino.handle, + (*byte)(unsafe.Pointer(hdr.Data)), uint32(hdr.Len), + watch.recurse, mask, nil, &watch.ov, 0) + if rdErr != nil { + err := os.NewSyscallError("ReadDirectoryChanges", rdErr) + if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { + // Watched directory was probably removed + w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF) + err = nil + } + w.deleteWatch(watch) + w.startRead(watch) + return err + } + return nil +} + +// readEvents reads from the I/O completion port, converts the +// received events into Event objects and sends them via the Events channel. +// Entry point to the I/O thread. +func (w *readDirChangesW) readEvents() { + var ( + n uint32 + key uintptr + ov *windows.Overlapped + ) + runtime.LockOSThread() + + for { + // This error is handled after the watch == nil check below. + qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE) + + watch := (*watch)(unsafe.Pointer(ov)) + if watch == nil { + select { + case ch := <-w.done: + w.mu.Lock() + var indexes []indexMap + for _, index := range w.watches { + indexes = append(indexes, index) + } + w.mu.Unlock() + for _, index := range indexes { + for _, watch := range index { + w.deleteWatch(watch) + w.startRead(watch) + } + } + + err := windows.CloseHandle(w.port) + if err != nil { + err = os.NewSyscallError("CloseHandle", err) + } + close(w.Events) + close(w.Errors) + ch <- err + return + case in := <-w.input: + switch in.op { + case opAddWatch: + in.reply <- w.addWatch(in.path, uint64(in.flags), in.bufsize) + case opRemoveWatch: + in.reply <- w.remWatch(in.path) + } + default: + } + continue + } + + switch qErr { + case nil: + // No error + case windows.ERROR_MORE_DATA: + if watch == nil { + w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")) + } else { + // The i/o succeeded but the buffer is full. + // In theory we should be building up a full packet. + // In practice we can get away with just carrying on. + n = uint32(unsafe.Sizeof(watch.buf)) + } + case windows.ERROR_ACCESS_DENIED: + // Watched directory was probably removed + w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF) + w.deleteWatch(watch) + w.startRead(watch) + continue + case windows.ERROR_OPERATION_ABORTED: + // CancelIo was called on this handle + continue + default: + w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr)) + continue + } + + var offset uint32 + for { + if n == 0 { + w.sendError(ErrEventOverflow) + break + } + + // Point "raw" to the event in the buffer + raw := (*windows.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) + + // Create a buf that is the size of the path name + size := int(raw.FileNameLength / 2) + var buf []uint16 + // TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973 + sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) + sh.Data = uintptr(unsafe.Pointer(&raw.FileName)) + sh.Len = size + sh.Cap = size + name := windows.UTF16ToString(buf) + fullname := filepath.Join(watch.path, name) + + if debug { + internal.Debug(fullname, raw.Action) + } + + var mask uint64 + switch raw.Action { + case windows.FILE_ACTION_REMOVED: + mask = sysFSDELETESELF + case windows.FILE_ACTION_MODIFIED: + mask = sysFSMODIFY + case windows.FILE_ACTION_RENAMED_OLD_NAME: + watch.rename = name + case windows.FILE_ACTION_RENAMED_NEW_NAME: + // Update saved path of all sub-watches. + old := filepath.Join(watch.path, watch.rename) + w.mu.Lock() + for _, watchMap := range w.watches { + for _, ww := range watchMap { + if strings.HasPrefix(ww.path, old) { + ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old)) + } + } + } + w.mu.Unlock() + + if watch.names[watch.rename] != 0 { + watch.names[name] |= watch.names[watch.rename] + delete(watch.names, watch.rename) + mask = sysFSMOVESELF + } + } + + if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME { + w.sendEvent(fullname, "", watch.names[name]&mask) + } + if raw.Action == windows.FILE_ACTION_REMOVED { + w.sendEvent(fullname, "", watch.names[name]&sysFSIGNORED) + delete(watch.names, name) + } + + if watch.rename != "" && raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME { + w.sendEvent(fullname, filepath.Join(watch.path, watch.rename), watch.mask&w.toFSnotifyFlags(raw.Action)) + } else { + w.sendEvent(fullname, "", watch.mask&w.toFSnotifyFlags(raw.Action)) + } + + if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME { + w.sendEvent(filepath.Join(watch.path, watch.rename), "", watch.names[name]&mask) + } + + // Move to the next event in the buffer + if raw.NextEntryOffset == 0 { + break + } + offset += raw.NextEntryOffset + + // Error! + if offset >= n { + //lint:ignore ST1005 Windows should be capitalized + w.sendError(errors.New("Windows system assumed buffer larger than it is, events have likely been missed")) + break + } + } + + if err := w.startRead(watch); err != nil { + w.sendError(err) + } + } +} + +func (w *readDirChangesW) toWindowsFlags(mask uint64) uint32 { + var m uint32 + if mask&sysFSMODIFY != 0 { + m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE + } + if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 { + m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME + } + return m +} + +func (w *readDirChangesW) toFSnotifyFlags(action uint32) uint64 { + switch action { + case windows.FILE_ACTION_ADDED: + return sysFSCREATE + case windows.FILE_ACTION_REMOVED: + return sysFSDELETE + case windows.FILE_ACTION_MODIFIED: + return sysFSMODIFY + case windows.FILE_ACTION_RENAMED_OLD_NAME: + return sysFSMOVEDFROM + case windows.FILE_ACTION_RENAMED_NEW_NAME: + return sysFSMOVEDTO + } + return 0 +} + +func (w *readDirChangesW) xSupports(op Op) bool { + if op.Has(xUnportableOpen) || op.Has(xUnportableRead) || + op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) { + return false + } + return true +} diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify.go b/vendor/github.com/fsnotify/fsnotify/fsnotify.go new file mode 100644 index 00000000..f64be4bf --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/fsnotify.go @@ -0,0 +1,496 @@ +// Package fsnotify provides a cross-platform interface for file system +// notifications. +// +// Currently supported systems: +// +// - Linux via inotify +// - BSD, macOS via kqueue +// - Windows via ReadDirectoryChangesW +// - illumos via FEN +// +// # FSNOTIFY_DEBUG +// +// Set the FSNOTIFY_DEBUG environment variable to "1" to print debug messages to +// stderr. This can be useful to track down some problems, especially in cases +// where fsnotify is used as an indirect dependency. +// +// Every event will be printed as soon as there's something useful to print, +// with as little processing from fsnotify. +// +// Example output: +// +// FSNOTIFY_DEBUG: 11:34:23.633087586 256:IN_CREATE → "/tmp/file-1" +// FSNOTIFY_DEBUG: 11:34:23.633202319 4:IN_ATTRIB → "/tmp/file-1" +// FSNOTIFY_DEBUG: 11:34:28.989728764 512:IN_DELETE → "/tmp/file-1" +package fsnotify + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" +) + +// Watcher watches a set of paths, delivering events on a channel. +// +// A watcher should not be copied (e.g. pass it by pointer, rather than by +// value). +// +// # Linux notes +// +// When a file is removed a Remove event won't be emitted until all file +// descriptors are closed, and deletes will always emit a Chmod. For example: +// +// fp := os.Open("file") +// os.Remove("file") // Triggers Chmod +// fp.Close() // Triggers Remove +// +// This is the event that inotify sends, so not much can be changed about this. +// +// The fs.inotify.max_user_watches sysctl variable specifies the upper limit +// for the number of watches per user, and fs.inotify.max_user_instances +// specifies the maximum number of inotify instances per user. Every Watcher you +// create is an "instance", and every path you add is a "watch". +// +// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and +// /proc/sys/fs/inotify/max_user_instances +// +// To increase them you can use sysctl or write the value to the /proc file: +// +// # Default values on Linux 5.18 +// sysctl fs.inotify.max_user_watches=124983 +// sysctl fs.inotify.max_user_instances=128 +// +// To make the changes persist on reboot edit /etc/sysctl.conf or +// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check +// your distro's documentation): +// +// fs.inotify.max_user_watches=124983 +// fs.inotify.max_user_instances=128 +// +// Reaching the limit will result in a "no space left on device" or "too many open +// files" error. +// +// # kqueue notes (macOS, BSD) +// +// kqueue requires opening a file descriptor for every file that's being watched; +// so if you're watching a directory with five files then that's six file +// descriptors. You will run in to your system's "max open files" limit faster on +// these platforms. +// +// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to +// control the maximum number of open files, as well as /etc/login.conf on BSD +// systems. +// +// # Windows notes +// +// Paths can be added as "C:\\path\\to\\dir", but forward slashes +// ("C:/path/to/dir") will also work. +// +// When a watched directory is removed it will always send an event for the +// directory itself, but may not send events for all files in that directory. +// Sometimes it will send events for all files, sometimes it will send no +// events, and often only for some files. +// +// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest +// value that is guaranteed to work with SMB filesystems. If you have many +// events in quick succession this may not be enough, and you will have to use +// [WithBufferSize] to increase the value. +type Watcher struct { + b backend + + // Events sends the filesystem change events. + // + // fsnotify can send the following events; a "path" here can refer to a + // file, directory, symbolic link, or special file like a FIFO. + // + // fsnotify.Create A new path was created; this may be followed by one + // or more Write events if data also gets written to a + // file. + // + // fsnotify.Remove A path was removed. + // + // fsnotify.Rename A path was renamed. A rename is always sent with the + // old path as Event.Name, and a Create event will be + // sent with the new name. Renames are only sent for + // paths that are currently watched; e.g. moving an + // unmonitored file into a monitored directory will + // show up as just a Create. Similarly, renaming a file + // to outside a monitored directory will show up as + // only a Rename. + // + // fsnotify.Write A file or named pipe was written to. A Truncate will + // also trigger a Write. A single "write action" + // initiated by the user may show up as one or multiple + // writes, depending on when the system syncs things to + // disk. For example when compiling a large Go program + // you may get hundreds of Write events, and you may + // want to wait until you've stopped receiving them + // (see the dedup example in cmd/fsnotify). + // + // Some systems may send Write event for directories + // when the directory content changes. + // + // fsnotify.Chmod Attributes were changed. On Linux this is also sent + // when a file is removed (or more accurately, when a + // link to an inode is removed). On kqueue it's sent + // when a file is truncated. On Windows it's never + // sent. + Events chan Event + + // Errors sends any errors. + Errors chan error +} + +// Event represents a file system notification. +type Event struct { + // Path to the file or directory. + // + // Paths are relative to the input; for example with Add("dir") the Name + // will be set to "dir/file" if you create that file, but if you use + // Add("/path/to/dir") it will be "/path/to/dir/file". + Name string + + // File operation that triggered the event. + // + // This is a bitmask and some systems may send multiple operations at once. + // Use the Event.Has() method instead of comparing with ==. + Op Op + + // Create events will have this set to the old path if it's a rename. This + // only works when both the source and destination are watched. It's not + // reliable when watching individual files, only directories. + // + // For example "mv /tmp/file /tmp/rename" will emit: + // + // Event{Op: Rename, Name: "/tmp/file"} + // Event{Op: Create, Name: "/tmp/rename", RenamedFrom: "/tmp/file"} + renamedFrom string +} + +// Op describes a set of file operations. +type Op uint32 + +// The operations fsnotify can trigger; see the documentation on [Watcher] for a +// full description, and check them with [Event.Has]. +const ( + // A new pathname was created. + Create Op = 1 << iota + + // The pathname was written to; this does *not* mean the write has finished, + // and a write can be followed by more writes. + Write + + // The path was removed; any watches on it will be removed. Some "remove" + // operations may trigger a Rename if the file is actually moved (for + // example "remove to trash" is often a rename). + Remove + + // The path was renamed to something else; any watches on it will be + // removed. + Rename + + // File attributes were changed. + // + // It's generally not recommended to take action on this event, as it may + // get triggered very frequently by some software. For example, Spotlight + // indexing on macOS, anti-virus software, backup software, etc. + Chmod + + // File descriptor was opened. + // + // Only works on Linux and FreeBSD. + xUnportableOpen + + // File was read from. + // + // Only works on Linux and FreeBSD. + xUnportableRead + + // File opened for writing was closed. + // + // Only works on Linux and FreeBSD. + // + // The advantage of using this over Write is that it's more reliable than + // waiting for Write events to stop. It's also faster (if you're not + // listening to Write events): copying a file of a few GB can easily + // generate tens of thousands of Write events in a short span of time. + xUnportableCloseWrite + + // File opened for reading was closed. + // + // Only works on Linux and FreeBSD. + xUnportableCloseRead +) + +var ( + // ErrNonExistentWatch is used when Remove() is called on a path that's not + // added. + ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch") + + // ErrClosed is used when trying to operate on a closed Watcher. + ErrClosed = errors.New("fsnotify: watcher already closed") + + // ErrEventOverflow is reported from the Errors channel when there are too + // many events: + // + // - inotify: inotify returns IN_Q_OVERFLOW – because there are too + // many queued events (the fs.inotify.max_queued_events + // sysctl can be used to increase this). + // - windows: The buffer size is too small; WithBufferSize() can be used to increase it. + // - kqueue, fen: Not used. + ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow") + + // ErrUnsupported is returned by AddWith() when WithOps() specified an + // Unportable event that's not supported on this platform. + //lint:ignore ST1012 not relevant + xErrUnsupported = errors.New("fsnotify: not supported with this backend") +) + +// NewWatcher creates a new Watcher. +func NewWatcher() (*Watcher, error) { + ev, errs := make(chan Event, defaultBufferSize), make(chan error) + b, err := newBackend(ev, errs) + if err != nil { + return nil, err + } + return &Watcher{b: b, Events: ev, Errors: errs}, nil +} + +// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events +// channel. +// +// The main use case for this is situations with a very large number of events +// where the kernel buffer size can't be increased (e.g. due to lack of +// permissions). An unbuffered Watcher will perform better for almost all use +// cases, and whenever possible you will be better off increasing the kernel +// buffers instead of adding a large userspace buffer. +func NewBufferedWatcher(sz uint) (*Watcher, error) { + ev, errs := make(chan Event, sz), make(chan error) + b, err := newBackend(ev, errs) + if err != nil { + return nil, err + } + return &Watcher{b: b, Events: ev, Errors: errs}, nil +} + +// Add starts monitoring the path for changes. +// +// A path can only be watched once; watching it more than once is a no-op and will +// not return an error. Paths that do not yet exist on the filesystem cannot be +// watched. +// +// A watch will be automatically removed if the watched path is deleted or +// renamed. The exception is the Windows backend, which doesn't remove the +// watcher on renames. +// +// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special +// filesystems (/proc, /sys, etc.) generally don't work. +// +// Returns [ErrClosed] if [Watcher.Close] was called. +// +// See [Watcher.AddWith] for a version that allows adding options. +// +// # Watching directories +// +// All files in a directory are monitored, including new files that are created +// after the watcher is started. Subdirectories are not watched (i.e. it's +// non-recursive). +// +// # Watching files +// +// Watching individual files (rather than directories) is generally not +// recommended as many programs (especially editors) update files atomically: it +// will write to a temporary file which is then moved to destination, +// overwriting the original (or some variant thereof). The watcher on the +// original file is now lost, as that no longer exists. +// +// The upshot of this is that a power failure or crash won't leave a +// half-written file. +// +// Watch the parent directory and use Event.Name to filter out files you're not +// interested in. There is an example of this in cmd/fsnotify/file.go. +func (w *Watcher) Add(path string) error { return w.b.Add(path) } + +// AddWith is like [Watcher.Add], but allows adding options. When using Add() +// the defaults described below are used. +// +// Possible options are: +// +// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on +// other platforms. The default is 64K (65536 bytes). +func (w *Watcher) AddWith(path string, opts ...addOpt) error { return w.b.AddWith(path, opts...) } + +// Remove stops monitoring the path for changes. +// +// Directories are always removed non-recursively. For example, if you added +// /tmp/dir and /tmp/dir/subdir then you will need to remove both. +// +// Removing a path that has not yet been added returns [ErrNonExistentWatch]. +// +// Returns nil if [Watcher.Close] was called. +func (w *Watcher) Remove(path string) error { return w.b.Remove(path) } + +// Close removes all watches and closes the Events channel. +func (w *Watcher) Close() error { return w.b.Close() } + +// WatchList returns all paths explicitly added with [Watcher.Add] (and are not +// yet removed). +// +// The order is undefined, and may differ per call. Returns nil if +// [Watcher.Close] was called. +func (w *Watcher) WatchList() []string { return w.b.WatchList() } + +// Supports reports if all the listed operations are supported by this platform. +// +// Create, Write, Remove, Rename, and Chmod are always supported. It can only +// return false for an Op starting with Unportable. +func (w *Watcher) xSupports(op Op) bool { return w.b.xSupports(op) } + +func (o Op) String() string { + var b strings.Builder + if o.Has(Create) { + b.WriteString("|CREATE") + } + if o.Has(Remove) { + b.WriteString("|REMOVE") + } + if o.Has(Write) { + b.WriteString("|WRITE") + } + if o.Has(xUnportableOpen) { + b.WriteString("|OPEN") + } + if o.Has(xUnportableRead) { + b.WriteString("|READ") + } + if o.Has(xUnportableCloseWrite) { + b.WriteString("|CLOSE_WRITE") + } + if o.Has(xUnportableCloseRead) { + b.WriteString("|CLOSE_READ") + } + if o.Has(Rename) { + b.WriteString("|RENAME") + } + if o.Has(Chmod) { + b.WriteString("|CHMOD") + } + if b.Len() == 0 { + return "[no events]" + } + return b.String()[1:] +} + +// Has reports if this operation has the given operation. +func (o Op) Has(h Op) bool { return o&h != 0 } + +// Has reports if this event has the given operation. +func (e Event) Has(op Op) bool { return e.Op.Has(op) } + +// String returns a string representation of the event with their path. +func (e Event) String() string { + if e.renamedFrom != "" { + return fmt.Sprintf("%-13s %q ← %q", e.Op.String(), e.Name, e.renamedFrom) + } + return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name) +} + +type ( + backend interface { + Add(string) error + AddWith(string, ...addOpt) error + Remove(string) error + WatchList() []string + Close() error + xSupports(Op) bool + } + addOpt func(opt *withOpts) + withOpts struct { + bufsize int + op Op + noFollow bool + sendCreate bool + } +) + +var debug = func() bool { + // Check for exactly "1" (rather than mere existence) so we can add + // options/flags in the future. I don't know if we ever want that, but it's + // nice to leave the option open. + return os.Getenv("FSNOTIFY_DEBUG") == "1" +}() + +var defaultOpts = withOpts{ + bufsize: 65536, // 64K + op: Create | Write | Remove | Rename | Chmod, +} + +func getOptions(opts ...addOpt) withOpts { + with := defaultOpts + for _, o := range opts { + if o != nil { + o(&with) + } + } + return with +} + +// WithBufferSize sets the [ReadDirectoryChangesW] buffer size. +// +// This only has effect on Windows systems, and is a no-op for other backends. +// +// The default value is 64K (65536 bytes) which is the highest value that works +// on all filesystems and should be enough for most applications, but if you +// have a large burst of events it may not be enough. You can increase it if +// you're hitting "queue or buffer overflow" errors ([ErrEventOverflow]). +// +// [ReadDirectoryChangesW]: https://learn.microsoft.com/en-gb/windows/win32/api/winbase/nf-winbase-readdirectorychangesw +func WithBufferSize(bytes int) addOpt { + return func(opt *withOpts) { opt.bufsize = bytes } +} + +// WithOps sets which operations to listen for. The default is [Create], +// [Write], [Remove], [Rename], and [Chmod]. +// +// Excluding operations you're not interested in can save quite a bit of CPU +// time; in some use cases there may be hundreds of thousands of useless Write +// or Chmod operations per second. +// +// This can also be used to add unportable operations not supported by all +// platforms; unportable operations all start with "Unportable": +// [UnportableOpen], [UnportableRead], [UnportableCloseWrite], and +// [UnportableCloseRead]. +// +// AddWith returns an error when using an unportable operation that's not +// supported. Use [Watcher.Support] to check for support. +func withOps(op Op) addOpt { + return func(opt *withOpts) { opt.op = op } +} + +// WithNoFollow disables following symlinks, so the symlinks themselves are +// watched. +func withNoFollow() addOpt { + return func(opt *withOpts) { opt.noFollow = true } +} + +// "Internal" option for recursive watches on inotify. +func withCreate() addOpt { + return func(opt *withOpts) { opt.sendCreate = true } +} + +var enableRecurse = false + +// Check if this path is recursive (ends with "/..." or "\..."), and return the +// path with the /... stripped. +func recursivePath(path string) (string, bool) { + path = filepath.Clean(path) + if !enableRecurse { // Only enabled in tests for now. + return path, false + } + if filepath.Base(path) == "..." { + return filepath.Dir(path), true + } + return path, false +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/darwin.go b/vendor/github.com/fsnotify/fsnotify/internal/darwin.go new file mode 100644 index 00000000..0b01bc18 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/darwin.go @@ -0,0 +1,39 @@ +//go:build darwin + +package internal + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +var ( + ErrSyscallEACCES = syscall.EACCES + ErrUnixEACCES = unix.EACCES +) + +var maxfiles uint64 + +func SetRlimit() { + // Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/ + var l syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l) + if err == nil && l.Cur != l.Max { + l.Cur = l.Max + syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l) + } + maxfiles = l.Cur + + if n, err := syscall.SysctlUint32("kern.maxfiles"); err == nil && uint64(n) < maxfiles { + maxfiles = uint64(n) + } + + if n, err := syscall.SysctlUint32("kern.maxfilesperproc"); err == nil && uint64(n) < maxfiles { + maxfiles = uint64(n) + } +} + +func Maxfiles() uint64 { return maxfiles } +func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) } +func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) } diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go new file mode 100644 index 00000000..928319fb --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go @@ -0,0 +1,57 @@ +package internal + +import "golang.org/x/sys/unix" + +var names = []struct { + n string + m uint32 +}{ + {"NOTE_ABSOLUTE", unix.NOTE_ABSOLUTE}, + {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, + {"NOTE_BACKGROUND", unix.NOTE_BACKGROUND}, + {"NOTE_CHILD", unix.NOTE_CHILD}, + {"NOTE_CRITICAL", unix.NOTE_CRITICAL}, + {"NOTE_DELETE", unix.NOTE_DELETE}, + {"NOTE_EXEC", unix.NOTE_EXEC}, + {"NOTE_EXIT", unix.NOTE_EXIT}, + {"NOTE_EXITSTATUS", unix.NOTE_EXITSTATUS}, + {"NOTE_EXIT_CSERROR", unix.NOTE_EXIT_CSERROR}, + {"NOTE_EXIT_DECRYPTFAIL", unix.NOTE_EXIT_DECRYPTFAIL}, + {"NOTE_EXIT_DETAIL", unix.NOTE_EXIT_DETAIL}, + {"NOTE_EXIT_DETAIL_MASK", unix.NOTE_EXIT_DETAIL_MASK}, + {"NOTE_EXIT_MEMORY", unix.NOTE_EXIT_MEMORY}, + {"NOTE_EXIT_REPARENTED", unix.NOTE_EXIT_REPARENTED}, + {"NOTE_EXTEND", unix.NOTE_EXTEND}, + {"NOTE_FFAND", unix.NOTE_FFAND}, + {"NOTE_FFCOPY", unix.NOTE_FFCOPY}, + {"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK}, + {"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK}, + {"NOTE_FFNOP", unix.NOTE_FFNOP}, + {"NOTE_FFOR", unix.NOTE_FFOR}, + {"NOTE_FORK", unix.NOTE_FORK}, + {"NOTE_FUNLOCK", unix.NOTE_FUNLOCK}, + {"NOTE_LEEWAY", unix.NOTE_LEEWAY}, + {"NOTE_LINK", unix.NOTE_LINK}, + {"NOTE_LOWAT", unix.NOTE_LOWAT}, + {"NOTE_MACHTIME", unix.NOTE_MACHTIME}, + {"NOTE_MACH_CONTINUOUS_TIME", unix.NOTE_MACH_CONTINUOUS_TIME}, + {"NOTE_NONE", unix.NOTE_NONE}, + {"NOTE_NSECONDS", unix.NOTE_NSECONDS}, + {"NOTE_OOB", unix.NOTE_OOB}, + //{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, -0x100000 (?!) + {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, + {"NOTE_REAP", unix.NOTE_REAP}, + {"NOTE_RENAME", unix.NOTE_RENAME}, + {"NOTE_REVOKE", unix.NOTE_REVOKE}, + {"NOTE_SECONDS", unix.NOTE_SECONDS}, + {"NOTE_SIGNAL", unix.NOTE_SIGNAL}, + {"NOTE_TRACK", unix.NOTE_TRACK}, + {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, + {"NOTE_TRIGGER", unix.NOTE_TRIGGER}, + {"NOTE_USECONDS", unix.NOTE_USECONDS}, + {"NOTE_VM_ERROR", unix.NOTE_VM_ERROR}, + {"NOTE_VM_PRESSURE", unix.NOTE_VM_PRESSURE}, + {"NOTE_VM_PRESSURE_SUDDEN_TERMINATE", unix.NOTE_VM_PRESSURE_SUDDEN_TERMINATE}, + {"NOTE_VM_PRESSURE_TERMINATE", unix.NOTE_VM_PRESSURE_TERMINATE}, + {"NOTE_WRITE", unix.NOTE_WRITE}, +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go new file mode 100644 index 00000000..3186b0c3 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go @@ -0,0 +1,33 @@ +package internal + +import "golang.org/x/sys/unix" + +var names = []struct { + n string + m uint32 +}{ + {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, + {"NOTE_CHILD", unix.NOTE_CHILD}, + {"NOTE_DELETE", unix.NOTE_DELETE}, + {"NOTE_EXEC", unix.NOTE_EXEC}, + {"NOTE_EXIT", unix.NOTE_EXIT}, + {"NOTE_EXTEND", unix.NOTE_EXTEND}, + {"NOTE_FFAND", unix.NOTE_FFAND}, + {"NOTE_FFCOPY", unix.NOTE_FFCOPY}, + {"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK}, + {"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK}, + {"NOTE_FFNOP", unix.NOTE_FFNOP}, + {"NOTE_FFOR", unix.NOTE_FFOR}, + {"NOTE_FORK", unix.NOTE_FORK}, + {"NOTE_LINK", unix.NOTE_LINK}, + {"NOTE_LOWAT", unix.NOTE_LOWAT}, + {"NOTE_OOB", unix.NOTE_OOB}, + {"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, + {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, + {"NOTE_RENAME", unix.NOTE_RENAME}, + {"NOTE_REVOKE", unix.NOTE_REVOKE}, + {"NOTE_TRACK", unix.NOTE_TRACK}, + {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, + {"NOTE_TRIGGER", unix.NOTE_TRIGGER}, + {"NOTE_WRITE", unix.NOTE_WRITE}, +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go new file mode 100644 index 00000000..f69fdb93 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go @@ -0,0 +1,42 @@ +package internal + +import "golang.org/x/sys/unix" + +var names = []struct { + n string + m uint32 +}{ + {"NOTE_ABSTIME", unix.NOTE_ABSTIME}, + {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, + {"NOTE_CHILD", unix.NOTE_CHILD}, + {"NOTE_CLOSE", unix.NOTE_CLOSE}, + {"NOTE_CLOSE_WRITE", unix.NOTE_CLOSE_WRITE}, + {"NOTE_DELETE", unix.NOTE_DELETE}, + {"NOTE_EXEC", unix.NOTE_EXEC}, + {"NOTE_EXIT", unix.NOTE_EXIT}, + {"NOTE_EXTEND", unix.NOTE_EXTEND}, + {"NOTE_FFAND", unix.NOTE_FFAND}, + {"NOTE_FFCOPY", unix.NOTE_FFCOPY}, + {"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK}, + {"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK}, + {"NOTE_FFNOP", unix.NOTE_FFNOP}, + {"NOTE_FFOR", unix.NOTE_FFOR}, + {"NOTE_FILE_POLL", unix.NOTE_FILE_POLL}, + {"NOTE_FORK", unix.NOTE_FORK}, + {"NOTE_LINK", unix.NOTE_LINK}, + {"NOTE_LOWAT", unix.NOTE_LOWAT}, + {"NOTE_MSECONDS", unix.NOTE_MSECONDS}, + {"NOTE_NSECONDS", unix.NOTE_NSECONDS}, + {"NOTE_OPEN", unix.NOTE_OPEN}, + {"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, + {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, + {"NOTE_READ", unix.NOTE_READ}, + {"NOTE_RENAME", unix.NOTE_RENAME}, + {"NOTE_REVOKE", unix.NOTE_REVOKE}, + {"NOTE_SECONDS", unix.NOTE_SECONDS}, + {"NOTE_TRACK", unix.NOTE_TRACK}, + {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, + {"NOTE_TRIGGER", unix.NOTE_TRIGGER}, + {"NOTE_USECONDS", unix.NOTE_USECONDS}, + {"NOTE_WRITE", unix.NOTE_WRITE}, +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go new file mode 100644 index 00000000..607e683b --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go @@ -0,0 +1,32 @@ +//go:build freebsd || openbsd || netbsd || dragonfly || darwin + +package internal + +import ( + "fmt" + "os" + "strings" + "time" + + "golang.org/x/sys/unix" +) + +func Debug(name string, kevent *unix.Kevent_t) { + mask := uint32(kevent.Fflags) + + var ( + l []string + unknown = mask + ) + for _, n := range names { + if mask&n.m == n.m { + l = append(l, n.n) + unknown ^= n.m + } + } + if unknown > 0 { + l = append(l, fmt.Sprintf("0x%x", unknown)) + } + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-60s → %q\n", + time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name) +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go new file mode 100644 index 00000000..35c734be --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go @@ -0,0 +1,56 @@ +package internal + +import ( + "fmt" + "os" + "strings" + "time" + + "golang.org/x/sys/unix" +) + +func Debug(name string, mask, cookie uint32) { + names := []struct { + n string + m uint32 + }{ + {"IN_ACCESS", unix.IN_ACCESS}, + {"IN_ATTRIB", unix.IN_ATTRIB}, + {"IN_CLOSE", unix.IN_CLOSE}, + {"IN_CLOSE_NOWRITE", unix.IN_CLOSE_NOWRITE}, + {"IN_CLOSE_WRITE", unix.IN_CLOSE_WRITE}, + {"IN_CREATE", unix.IN_CREATE}, + {"IN_DELETE", unix.IN_DELETE}, + {"IN_DELETE_SELF", unix.IN_DELETE_SELF}, + {"IN_IGNORED", unix.IN_IGNORED}, + {"IN_ISDIR", unix.IN_ISDIR}, + {"IN_MODIFY", unix.IN_MODIFY}, + {"IN_MOVE", unix.IN_MOVE}, + {"IN_MOVED_FROM", unix.IN_MOVED_FROM}, + {"IN_MOVED_TO", unix.IN_MOVED_TO}, + {"IN_MOVE_SELF", unix.IN_MOVE_SELF}, + {"IN_OPEN", unix.IN_OPEN}, + {"IN_Q_OVERFLOW", unix.IN_Q_OVERFLOW}, + {"IN_UNMOUNT", unix.IN_UNMOUNT}, + } + + var ( + l []string + unknown = mask + ) + for _, n := range names { + if mask&n.m == n.m { + l = append(l, n.n) + unknown ^= n.m + } + } + if unknown > 0 { + l = append(l, fmt.Sprintf("0x%x", unknown)) + } + var c string + if cookie > 0 { + c = fmt.Sprintf("(cookie: %d) ", cookie) + } + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-30s → %s%q\n", + time.Now().Format("15:04:05.000000000"), strings.Join(l, "|"), c, name) +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go new file mode 100644 index 00000000..e5b3b6f6 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go @@ -0,0 +1,25 @@ +package internal + +import "golang.org/x/sys/unix" + +var names = []struct { + n string + m uint32 +}{ + {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, + {"NOTE_CHILD", unix.NOTE_CHILD}, + {"NOTE_DELETE", unix.NOTE_DELETE}, + {"NOTE_EXEC", unix.NOTE_EXEC}, + {"NOTE_EXIT", unix.NOTE_EXIT}, + {"NOTE_EXTEND", unix.NOTE_EXTEND}, + {"NOTE_FORK", unix.NOTE_FORK}, + {"NOTE_LINK", unix.NOTE_LINK}, + {"NOTE_LOWAT", unix.NOTE_LOWAT}, + {"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, + {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, + {"NOTE_RENAME", unix.NOTE_RENAME}, + {"NOTE_REVOKE", unix.NOTE_REVOKE}, + {"NOTE_TRACK", unix.NOTE_TRACK}, + {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, + {"NOTE_WRITE", unix.NOTE_WRITE}, +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go new file mode 100644 index 00000000..1dd455bc --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go @@ -0,0 +1,28 @@ +package internal + +import "golang.org/x/sys/unix" + +var names = []struct { + n string + m uint32 +}{ + {"NOTE_ATTRIB", unix.NOTE_ATTRIB}, + // {"NOTE_CHANGE", unix.NOTE_CHANGE}, // Not on 386? + {"NOTE_CHILD", unix.NOTE_CHILD}, + {"NOTE_DELETE", unix.NOTE_DELETE}, + {"NOTE_EOF", unix.NOTE_EOF}, + {"NOTE_EXEC", unix.NOTE_EXEC}, + {"NOTE_EXIT", unix.NOTE_EXIT}, + {"NOTE_EXTEND", unix.NOTE_EXTEND}, + {"NOTE_FORK", unix.NOTE_FORK}, + {"NOTE_LINK", unix.NOTE_LINK}, + {"NOTE_LOWAT", unix.NOTE_LOWAT}, + {"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, + {"NOTE_PDATAMASK", unix.NOTE_PDATAMASK}, + {"NOTE_RENAME", unix.NOTE_RENAME}, + {"NOTE_REVOKE", unix.NOTE_REVOKE}, + {"NOTE_TRACK", unix.NOTE_TRACK}, + {"NOTE_TRACKERR", unix.NOTE_TRACKERR}, + {"NOTE_TRUNCATE", unix.NOTE_TRUNCATE}, + {"NOTE_WRITE", unix.NOTE_WRITE}, +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go new file mode 100644 index 00000000..f1b2e73b --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go @@ -0,0 +1,45 @@ +package internal + +import ( + "fmt" + "os" + "strings" + "time" + + "golang.org/x/sys/unix" +) + +func Debug(name string, mask int32) { + names := []struct { + n string + m int32 + }{ + {"FILE_ACCESS", unix.FILE_ACCESS}, + {"FILE_MODIFIED", unix.FILE_MODIFIED}, + {"FILE_ATTRIB", unix.FILE_ATTRIB}, + {"FILE_TRUNC", unix.FILE_TRUNC}, + {"FILE_NOFOLLOW", unix.FILE_NOFOLLOW}, + {"FILE_DELETE", unix.FILE_DELETE}, + {"FILE_RENAME_TO", unix.FILE_RENAME_TO}, + {"FILE_RENAME_FROM", unix.FILE_RENAME_FROM}, + {"UNMOUNTED", unix.UNMOUNTED}, + {"MOUNTEDOVER", unix.MOUNTEDOVER}, + {"FILE_EXCEPTION", unix.FILE_EXCEPTION}, + } + + var ( + l []string + unknown = mask + ) + for _, n := range names { + if mask&n.m == n.m { + l = append(l, n.n) + unknown ^= n.m + } + } + if unknown > 0 { + l = append(l, fmt.Sprintf("0x%x", unknown)) + } + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-30s → %q\n", + time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name) +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go b/vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go new file mode 100644 index 00000000..52bf4ce5 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go @@ -0,0 +1,40 @@ +package internal + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "golang.org/x/sys/windows" +) + +func Debug(name string, mask uint32) { + names := []struct { + n string + m uint32 + }{ + {"FILE_ACTION_ADDED", windows.FILE_ACTION_ADDED}, + {"FILE_ACTION_REMOVED", windows.FILE_ACTION_REMOVED}, + {"FILE_ACTION_MODIFIED", windows.FILE_ACTION_MODIFIED}, + {"FILE_ACTION_RENAMED_OLD_NAME", windows.FILE_ACTION_RENAMED_OLD_NAME}, + {"FILE_ACTION_RENAMED_NEW_NAME", windows.FILE_ACTION_RENAMED_NEW_NAME}, + } + + var ( + l []string + unknown = mask + ) + for _, n := range names { + if mask&n.m == n.m { + l = append(l, n.n) + unknown ^= n.m + } + } + if unknown > 0 { + l = append(l, fmt.Sprintf("0x%x", unknown)) + } + fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-65s → %q\n", + time.Now().Format("15:04:05.000000000"), strings.Join(l, " | "), filepath.ToSlash(name)) +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/freebsd.go b/vendor/github.com/fsnotify/fsnotify/internal/freebsd.go new file mode 100644 index 00000000..5ac8b507 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/freebsd.go @@ -0,0 +1,31 @@ +//go:build freebsd + +package internal + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +var ( + ErrSyscallEACCES = syscall.EACCES + ErrUnixEACCES = unix.EACCES +) + +var maxfiles uint64 + +func SetRlimit() { + // Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/ + var l syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l) + if err == nil && l.Cur != l.Max { + l.Cur = l.Max + syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l) + } + maxfiles = uint64(l.Cur) +} + +func Maxfiles() uint64 { return maxfiles } +func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) } +func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, uint64(dev)) } diff --git a/vendor/github.com/fsnotify/fsnotify/internal/internal.go b/vendor/github.com/fsnotify/fsnotify/internal/internal.go new file mode 100644 index 00000000..7daa45e1 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/internal.go @@ -0,0 +1,2 @@ +// Package internal contains some helpers. +package internal diff --git a/vendor/github.com/fsnotify/fsnotify/internal/unix.go b/vendor/github.com/fsnotify/fsnotify/internal/unix.go new file mode 100644 index 00000000..b251fb80 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/unix.go @@ -0,0 +1,31 @@ +//go:build !windows && !darwin && !freebsd && !plan9 + +package internal + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +var ( + ErrSyscallEACCES = syscall.EACCES + ErrUnixEACCES = unix.EACCES +) + +var maxfiles uint64 + +func SetRlimit() { + // Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/ + var l syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l) + if err == nil && l.Cur != l.Max { + l.Cur = l.Max + syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l) + } + maxfiles = uint64(l.Cur) +} + +func Maxfiles() uint64 { return maxfiles } +func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) } +func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) } diff --git a/vendor/github.com/fsnotify/fsnotify/internal/unix2.go b/vendor/github.com/fsnotify/fsnotify/internal/unix2.go new file mode 100644 index 00000000..37dfeddc --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/unix2.go @@ -0,0 +1,7 @@ +//go:build !windows + +package internal + +func HasPrivilegesForSymlink() bool { + return true +} diff --git a/vendor/github.com/fsnotify/fsnotify/internal/windows.go b/vendor/github.com/fsnotify/fsnotify/internal/windows.go new file mode 100644 index 00000000..896bc2e5 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/internal/windows.go @@ -0,0 +1,41 @@ +//go:build windows + +package internal + +import ( + "errors" + + "golang.org/x/sys/windows" +) + +// Just a dummy. +var ( + ErrSyscallEACCES = errors.New("dummy") + ErrUnixEACCES = errors.New("dummy") +) + +func SetRlimit() {} +func Maxfiles() uint64 { return 1<<64 - 1 } +func Mkfifo(path string, mode uint32) error { return errors.New("no FIFOs on Windows") } +func Mknod(path string, mode uint32, dev int) error { return errors.New("no device nodes on Windows") } + +func HasPrivilegesForSymlink() bool { + var sid *windows.SID + err := windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, + 2, + windows.SECURITY_BUILTIN_DOMAIN_RID, + windows.DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid) + if err != nil { + return false + } + defer windows.FreeSid(sid) + token := windows.Token(0) + member, err := token.IsMember(sid) + if err != nil { + return false + } + return member || token.IsElevated() +} diff --git a/vendor/github.com/fsnotify/fsnotify/shared.go b/vendor/github.com/fsnotify/fsnotify/shared.go new file mode 100644 index 00000000..3ee9b58f --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/shared.go @@ -0,0 +1,64 @@ +package fsnotify + +import "sync" + +type shared struct { + Events chan Event + Errors chan error + done chan struct{} + mu sync.Mutex +} + +func newShared(ev chan Event, errs chan error) *shared { + return &shared{ + Events: ev, + Errors: errs, + done: make(chan struct{}), + } +} + +// Returns true if the event was sent, or false if watcher is closed. +func (w *shared) sendEvent(e Event) bool { + if e.Op == 0 { + return true + } + select { + case <-w.done: + return false + case w.Events <- e: + return true + } +} + +// Returns true if the error was sent, or false if watcher is closed. +func (w *shared) sendError(err error) bool { + if err == nil { + return true + } + select { + case <-w.done: + return false + case w.Errors <- err: + return true + } +} + +func (w *shared) isClosed() bool { + select { + case <-w.done: + return true + default: + return false + } +} + +// Mark as closed; returns true if it was already closed. +func (w *shared) close() bool { + w.mu.Lock() + defer w.mu.Unlock() + if w.isClosed() { + return true + } + close(w.done) + return false +} diff --git a/vendor/github.com/fsnotify/fsnotify/staticcheck.conf b/vendor/github.com/fsnotify/fsnotify/staticcheck.conf new file mode 100644 index 00000000..8fa7351f --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/staticcheck.conf @@ -0,0 +1,3 @@ +checks = ['all', + '-U1000', # Don't complain about unused functions. +] diff --git a/vendor/github.com/fsnotify/fsnotify/system_bsd.go b/vendor/github.com/fsnotify/fsnotify/system_bsd.go new file mode 100644 index 00000000..f65e8fe3 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/system_bsd.go @@ -0,0 +1,7 @@ +//go:build freebsd || openbsd || netbsd || dragonfly + +package fsnotify + +import "golang.org/x/sys/unix" + +const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/fsnotify/fsnotify/system_darwin.go b/vendor/github.com/fsnotify/fsnotify/system_darwin.go new file mode 100644 index 00000000..a29fc7aa --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/system_darwin.go @@ -0,0 +1,8 @@ +//go:build darwin + +package fsnotify + +import "golang.org/x/sys/unix" + +// note: this constant is not defined on BSD +const openMode = unix.O_EVTONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/fsouza/go-dockerclient/.gitattributes b/vendor/github.com/fsouza/go-dockerclient/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/vendor/github.com/fsouza/go-dockerclient/.gitignore b/vendor/github.com/fsouza/go-dockerclient/.gitignore new file mode 100644 index 00000000..5f6b48ea --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/.gitignore @@ -0,0 +1,2 @@ +# temporary symlink for testing +testing/data/symlink diff --git a/vendor/github.com/fsouza/go-dockerclient/.golangci.yaml b/vendor/github.com/fsouza/go-dockerclient/.golangci.yaml new file mode 100644 index 00000000..f6e588a6 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/.golangci.yaml @@ -0,0 +1,24 @@ +version: "2" +linters: + default: none + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/vendor/github.com/fsouza/go-dockerclient/DOCKER-LICENSE b/vendor/github.com/fsouza/go-dockerclient/DOCKER-LICENSE new file mode 100644 index 00000000..db092935 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/DOCKER-LICENSE @@ -0,0 +1,6 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +You can find the Docker license at the following link: +https://raw.githubusercontent.com/docker/docker/HEAD/LICENSE diff --git a/vendor/github.com/fsouza/go-dockerclient/LICENSE b/vendor/github.com/fsouza/go-dockerclient/LICENSE new file mode 100644 index 00000000..20837167 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) go-dockerclient authors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/fsouza/go-dockerclient/Makefile b/vendor/github.com/fsouza/go-dockerclient/Makefile new file mode 100644 index 00000000..2f5d9fcc --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/Makefile @@ -0,0 +1,30 @@ +ifeq "$(strip $(shell go env GOARCH))" "amd64" +RACE_FLAG := -race +endif + +.PHONY: test +test: pretest gotest + +.PHONY: golangci-lint +golangci-lint: + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + golangci-lint run + +.PHONY: staticcheck +staticcheck: + go install honnef.co/go/tools/cmd/staticcheck@master + staticcheck ./... + +.PHONY: lint +lint: golangci-lint staticcheck + +.PHONY: pretest +pretest: lint + +.PHONY: gotest +gotest: + go test $(RACE_FLAG) -vet all ./... + +.PHONY: integration +integration: + go test -tags docker_integration -run TestIntegration -v diff --git a/vendor/github.com/fsouza/go-dockerclient/README.md b/vendor/github.com/fsouza/go-dockerclient/README.md new file mode 100644 index 00000000..2323d89b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/README.md @@ -0,0 +1,122 @@ +# go-dockerclient + +[![Build Status](https://github.com/fsouza/go-dockerclient/workflows/Build/badge.svg)](https://github.com/fsouza/go-dockerclient/actions?query=branch:main+workflow:Build) +[![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/fsouza/go-dockerclient) + +This package presents a client for the Docker remote API. It also provides +support for the extensions in the [Swarm API](https://docs.docker.com/swarm/swarm-api/). + +This package also provides support for docker's network API, which is a simple +passthrough to the libnetwork remote API. + +For more details, check the [remote API +documentation](https://docs.docker.com/engine/api/latest/). + +## Difference between go-dockerclient and the official SDK + +Link for the official SDK: https://docs.docker.com/develop/sdk/ + +go-dockerclient was created before Docker had an official Go SDK and is +still maintained and active because it's still used out there. New features in +the Docker API do not get automatically implemented here: it's based on demand, +if someone wants it, they can file an issue or a PR and the feature may get +implemented/merged. + +For new projects, using the official SDK is probably more appropriate as +go-dockerclient lags behind the official SDK. + +## Example + +```go +package main + +import ( + "fmt" + + docker "github.com/fsouza/go-dockerclient" +) + +func main() { + client, err := docker.NewClientFromEnv() + if err != nil { + panic(err) + } + imgs, err := client.ListImages(docker.ListImagesOptions{All: false}) + if err != nil { + panic(err) + } + for _, img := range imgs { + fmt.Println("ID: ", img.ID) + fmt.Println("RepoTags: ", img.RepoTags) + fmt.Println("Created: ", img.Created) + fmt.Println("Size: ", img.Size) + fmt.Println("VirtualSize: ", img.VirtualSize) + fmt.Println("ParentId: ", img.ParentID) + } +} +``` + +## Using with TLS + +In order to instantiate the client for a TLS-enabled daemon, you should use +NewTLSClient, passing the endpoint and path for key and certificates as +parameters. + +```go +package main + +import ( + "fmt" + + docker "github.com/fsouza/go-dockerclient" +) + +func main() { + const endpoint = "tcp://[ip]:[port]" + path := os.Getenv("DOCKER_CERT_PATH") + ca := fmt.Sprintf("%s/ca.pem", path) + cert := fmt.Sprintf("%s/cert.pem", path) + key := fmt.Sprintf("%s/key.pem", path) + client, _ := docker.NewTLSClient(endpoint, cert, key, ca) + // use client +} +``` + +If using [docker-machine](https://docs.docker.com/machine/), or another +application that exports environment variables `DOCKER_HOST`, +`DOCKER_TLS_VERIFY`, `DOCKER_CERT_PATH`, `DOCKER_API_VERSION`, you can use +NewClientFromEnv. + + +```go +package main + +import ( + "fmt" + + docker "github.com/fsouza/go-dockerclient" +) + +func main() { + client, err := docker.NewClientFromEnv() + if err != nil { + // handle err + } + // use client +} +``` + +See the documentation for more details. + +## Developing + +All development commands can be seen in the [Makefile](Makefile). + +Committed code must pass: + +* [golangci-lint](https://github.com/golangci/golangci-lint) +* [go test](https://golang.org/cmd/go/#hdr-Test_packages) +* [staticcheck](https://staticcheck.io/) + +Running ``make test`` will run all checks, as well as install any required +dependencies. diff --git a/vendor/github.com/fsouza/go-dockerclient/auth.go b/vendor/github.com/fsouza/go-dockerclient/auth.go new file mode 100644 index 00000000..37c1cbe2 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/auth.go @@ -0,0 +1,395 @@ +// Copyright 2015 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "errors" + "io" + "net/http" + "os" + "os/exec" + "path" + "strings" +) + +// ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed. +var ErrCannotParseDockercfg = errors.New("failed to read authentication from dockercfg") + +// AuthConfiguration represents authentication options to use in the PushImage +// method. It represents the authentication in the Docker index server. +type AuthConfiguration struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Email string `json:"email,omitempty"` + ServerAddress string `json:"serveraddress,omitempty"` + + // IdentityToken can be supplied with the identitytoken response of the AuthCheck call + // see https://pkg.go.dev/github.com/docker/docker/api/types?tab=doc#AuthConfig + // It can be used in place of password not in conjunction with it + IdentityToken string `json:"identitytoken,omitempty"` + + // RegistryToken can be supplied with the registrytoken + RegistryToken string `json:"registrytoken,omitempty"` +} + +func (c AuthConfiguration) isEmpty() bool { + return c == AuthConfiguration{} +} + +func (c AuthConfiguration) headerKey() string { + return "X-Registry-Auth" +} + +// AuthConfigurations represents authentication options to use for the +// PushImage method accommodating the new X-Registry-Config header +type AuthConfigurations struct { + Configs map[string]AuthConfiguration `json:"configs"` +} + +func (c AuthConfigurations) isEmpty() bool { + return len(c.Configs) == 0 +} + +func (AuthConfigurations) headerKey() string { + return "X-Registry-Config" +} + +// merge updates the configuration. If a key is defined in both maps, the one +// in c.Configs takes precedence. +func (c *AuthConfigurations) merge(other AuthConfigurations) { + for k, v := range other.Configs { + if c.Configs == nil { + c.Configs = make(map[string]AuthConfiguration) + } + if _, ok := c.Configs[k]; !ok { + c.Configs[k] = v + } + } +} + +// AuthConfigurations119 is used to serialize a set of AuthConfigurations +// for Docker API >= 1.19. +type AuthConfigurations119 map[string]AuthConfiguration + +func (c AuthConfigurations119) isEmpty() bool { + return len(c) == 0 +} + +func (c AuthConfigurations119) headerKey() string { + return "X-Registry-Config" +} + +// dockerConfig represents a registry authentation configuration from the +// .dockercfg file. +type dockerConfig struct { + Auth string `json:"auth"` + Email string `json:"email"` + IdentityToken string `json:"identitytoken"` + RegistryToken string `json:"registrytoken"` +} + +// NewAuthConfigurationsFromFile returns AuthConfigurations from a path containing JSON +// in the same format as the .dockercfg file. +func NewAuthConfigurationsFromFile(path string) (*AuthConfigurations, error) { + r, err := os.Open(path) + if err != nil { + return nil, err + } + return NewAuthConfigurations(r) +} + +func cfgPaths(dockerConfigEnv string, homeEnv string) []string { + if dockerConfigEnv != "" { + return []string{ + path.Join(dockerConfigEnv, "plaintext-passwords.json"), + path.Join(dockerConfigEnv, "config.json"), + } + } + if homeEnv != "" { + return []string{ + path.Join(homeEnv, ".docker", "plaintext-passwords.json"), + path.Join(homeEnv, ".docker", "config.json"), + path.Join(homeEnv, ".dockercfg"), + } + } + return nil +} + +// NewAuthConfigurationsFromDockerCfg returns AuthConfigurations from system +// config files. The following files are checked in the order listed: +// +// If the environment variable DOCKER_CONFIG is set to a non-empty string: +// +// - $DOCKER_CONFIG/plaintext-passwords.json +// - $DOCKER_CONFIG/config.json +// +// Otherwise, it looks for files in the $HOME directory and the legacy +// location: +// +// - $HOME/.docker/plaintext-passwords.json +// - $HOME/.docker/config.json +// - $HOME/.dockercfg +func NewAuthConfigurationsFromDockerCfg() (*AuthConfigurations, error) { + pathsToTry := cfgPaths(os.Getenv("DOCKER_CONFIG"), os.Getenv("HOME")) + if len(pathsToTry) < 1 { + return nil, errors.New("no docker configuration found") + } + return newAuthConfigurationsFromDockerCfg(pathsToTry) +} + +func newAuthConfigurationsFromDockerCfg(pathsToTry []string) (*AuthConfigurations, error) { + var result *AuthConfigurations + var auths *AuthConfigurations + var err error + for _, path := range pathsToTry { + auths, err = NewAuthConfigurationsFromFile(path) + if err != nil { + continue + } + + if result == nil { + result = auths + } else { + result.merge(*auths) + } + } + + if result != nil { + return result, nil + } + return result, err +} + +// NewAuthConfigurations returns AuthConfigurations from a JSON encoded string in the +// same format as the .dockercfg file. +func NewAuthConfigurations(r io.Reader) (*AuthConfigurations, error) { + var auth *AuthConfigurations + confs, err := parseDockerConfig(r) + if err != nil { + return nil, err + } + auth, err = authConfigs(confs) + if err != nil { + return nil, err + } + return auth, nil +} + +func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) { + buf := new(bytes.Buffer) + buf.ReadFrom(r) + byteData := buf.Bytes() + + confsWrapper := struct { + Auths map[string]dockerConfig `json:"auths"` + }{} + if err := json.Unmarshal(byteData, &confsWrapper); err == nil { + if len(confsWrapper.Auths) > 0 { + return confsWrapper.Auths, nil + } + } + + var confs map[string]dockerConfig + if err := json.Unmarshal(byteData, &confs); err != nil { + return nil, err + } + return confs, nil +} + +// authConfigs converts a dockerConfigs map to a AuthConfigurations object. +func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) { + c := &AuthConfigurations{ + Configs: make(map[string]AuthConfiguration), + } + + for reg, conf := range confs { + if conf.Auth == "" { + continue + } + + // support both padded and unpadded encoding + data, err := base64.StdEncoding.DecodeString(conf.Auth) + if err != nil { + data, err = base64.StdEncoding.WithPadding(base64.NoPadding).DecodeString(conf.Auth) + } + if err != nil { + return nil, errors.New("error decoding plaintext credentials") + } + + userpass := strings.SplitN(string(data), ":", 2) + if len(userpass) != 2 { + return nil, ErrCannotParseDockercfg + } + + authConfig := AuthConfiguration{ + Email: conf.Email, + Username: userpass[0], + Password: userpass[1], + ServerAddress: reg, + } + + // if identitytoken provided then zero the password and set it + if conf.IdentityToken != "" { + authConfig.Password = "" + authConfig.IdentityToken = conf.IdentityToken + } + + // if registrytoken provided then zero the password and set it + if conf.RegistryToken != "" { + authConfig.Password = "" + authConfig.RegistryToken = conf.RegistryToken + } + c.Configs[reg] = authConfig + } + + return c, nil +} + +// AuthStatus returns the authentication status for Docker API versions >= 1.23. +type AuthStatus struct { + Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` + IdentityToken string `json:"IdentityToken,omitempty" yaml:"IdentityToken,omitempty" toml:"IdentityToken,omitempty"` +} + +// AuthCheck validates the given credentials. It returns nil if successful. +// +// For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty.` +// +// See https://goo.gl/6nsZkH for more details. +func (c *Client) AuthCheck(conf *AuthConfiguration) (AuthStatus, error) { + return c.AuthCheckWithContext(conf, context.TODO()) +} + +// AuthCheckWithContext validates the given credentials. It returns nil if successful. The context object +// can be used to cancel the request. +// +// For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty. +// +// See https://goo.gl/6nsZkH for more details. +func (c *Client) AuthCheckWithContext(conf *AuthConfiguration, ctx context.Context) (AuthStatus, error) { + var authStatus AuthStatus + if conf == nil { + return authStatus, errors.New("conf is nil") + } + resp, err := c.do(http.MethodPost, "/auth", doOptions{data: conf, context: ctx}) + if err != nil { + return authStatus, err + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return authStatus, err + } + if len(data) == 0 { + return authStatus, nil + } + if err := json.Unmarshal(data, &authStatus); err != nil { + return authStatus, err + } + return authStatus, nil +} + +// helperCredentials represents credentials commit from an helper +type helperCredentials struct { + Username string `json:"Username,omitempty"` + Secret string `json:"Secret,omitempty"` +} + +// NewAuthConfigurationsFromCredsHelpers returns AuthConfigurations from +// installed credentials helpers +func NewAuthConfigurationsFromCredsHelpers(registry string) (*AuthConfiguration, error) { + // Load docker configuration file in order to find a possible helper provider + pathsToTry := cfgPaths(os.Getenv("DOCKER_CONFIG"), os.Getenv("HOME")) + if len(pathsToTry) < 1 { + return nil, errors.New("no docker configuration found") + } + + provider, err := getHelperProviderFromDockerCfg(pathsToTry, registry) + if err != nil { + return nil, err + } + + c, err := getCredentialsFromHelper(provider, registry) + if err != nil { + return nil, err + } + + creds := new(AuthConfiguration) + creds.Username = c.Username + creds.Password = c.Secret + return creds, nil +} + +func getHelperProviderFromDockerCfg(pathsToTry []string, registry string) (string, error) { + for _, path := range pathsToTry { + content, err := os.ReadFile(path) + if err != nil { + // if we can't read the file keep going + continue + } + + provider, err := parseCredsDockerConfig(content, registry) + if err != nil { + continue + } + if provider != "" { + return provider, nil + } + } + return "", errors.New("no docker credentials provider found") +} + +func parseCredsDockerConfig(config []byte, registry string) (string, error) { + creds := struct { + CredsStore string `json:"credsStore,omitempty"` + CredHelpers map[string]string `json:"credHelpers,omitempty"` + }{} + err := json.Unmarshal(config, &creds) + if err != nil { + return "", err + } + + provider, ok := creds.CredHelpers[registry] + if ok { + return provider, nil + } + return creds.CredsStore, nil +} + +// Run and parse the found credential helper +func getCredentialsFromHelper(provider string, registry string) (*helperCredentials, error) { + helpercreds, err := runDockerCredentialsHelper(provider, registry) + if err != nil { + return nil, err + } + + c := new(helperCredentials) + err = json.Unmarshal(helpercreds, c) + if err != nil { + return nil, err + } + + return c, nil +} + +func runDockerCredentialsHelper(provider string, registry string) ([]byte, error) { + cmd := exec.Command("docker-credential-"+provider, "get") + + var stdout bytes.Buffer + + cmd.Stdin = bytes.NewBuffer([]byte(registry)) + cmd.Stdout = &stdout + + err := cmd.Run() + if err != nil { + return nil, err + } + + return stdout.Bytes(), nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/change.go b/vendor/github.com/fsouza/go-dockerclient/change.go new file mode 100644 index 00000000..3f936b22 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/change.go @@ -0,0 +1,43 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import "fmt" + +// ChangeType is a type for constants indicating the type of change +// in a container +type ChangeType int + +const ( + // ChangeModify is the ChangeType for container modifications + ChangeModify ChangeType = iota + + // ChangeAdd is the ChangeType for additions to a container + ChangeAdd + + // ChangeDelete is the ChangeType for deletions from a container + ChangeDelete +) + +// Change represents a change in a container. +// +// See https://goo.gl/Wo0JJp for more details. +type Change struct { + Path string + Kind ChangeType +} + +func (change *Change) String() string { + var kind string + switch change.Kind { + case ChangeModify: + kind = "C" + case ChangeAdd: + kind = "A" + case ChangeDelete: + kind = "D" + } + return fmt.Sprintf("%s %s", kind, change.Path) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/client.go b/vendor/github.com/fsouza/go-dockerclient/client.go new file mode 100644 index 00000000..e1a42001 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/client.go @@ -0,0 +1,1155 @@ +// Copyright 2013 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package docker provides a client for the Docker remote API. +// +// See https://goo.gl/o2v3rk for more details on the remote API. +package docker + +import ( + "bufio" + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "reflect" + "runtime" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/docker/docker/pkg/homedir" + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/stdcopy" +) + +const ( + userAgent = "go-dockerclient" + + unixProtocol = "unix" + namedPipeProtocol = "npipe" +) + +var ( + // ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL. + ErrInvalidEndpoint = errors.New("invalid endpoint") + + // ErrConnectionRefused is returned when the client cannot connect to the given endpoint. + ErrConnectionRefused = errors.New("cannot connect to Docker endpoint") + + // ErrInactivityTimeout is returned when a streamable call has been inactive for some time. + ErrInactivityTimeout = errors.New("inactivity time exceeded timeout") + + apiVersion112, _ = NewAPIVersion("1.12") + apiVersion118, _ = NewAPIVersion("1.18") + apiVersion119, _ = NewAPIVersion("1.19") + apiVersion121, _ = NewAPIVersion("1.21") + apiVersion124, _ = NewAPIVersion("1.24") + apiVersion125, _ = NewAPIVersion("1.25") + apiVersion135, _ = NewAPIVersion("1.35") +) + +// APIVersion is an internal representation of a version of the Remote API. +type APIVersion []int + +// NewAPIVersion returns an instance of APIVersion for the given string. +// +// The given string must be in the form .., where , +// and are integer numbers. +func NewAPIVersion(input string) (APIVersion, error) { + if !strings.Contains(input, ".") { + return nil, fmt.Errorf("unable to parse version %q", input) + } + raw := strings.Split(input, "-") + arr := strings.Split(raw[0], ".") + ret := make(APIVersion, len(arr)) + var err error + for i, val := range arr { + ret[i], err = strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("unable to parse version %q: %q is not an integer", input, val) + } + } + return ret, nil +} + +func (version APIVersion) String() string { + parts := make([]string, len(version)) + for i, val := range version { + parts[i] = strconv.Itoa(val) + } + return strings.Join(parts, ".") +} + +// LessThan is a function for comparing APIVersion structs. +func (version APIVersion) LessThan(other APIVersion) bool { + return version.compare(other) < 0 +} + +// LessThanOrEqualTo is a function for comparing APIVersion structs. +func (version APIVersion) LessThanOrEqualTo(other APIVersion) bool { + return version.compare(other) <= 0 +} + +// GreaterThan is a function for comparing APIVersion structs. +func (version APIVersion) GreaterThan(other APIVersion) bool { + return version.compare(other) > 0 +} + +// GreaterThanOrEqualTo is a function for comparing APIVersion structs. +func (version APIVersion) GreaterThanOrEqualTo(other APIVersion) bool { + return version.compare(other) >= 0 +} + +func (version APIVersion) compare(other APIVersion) int { + for i, v := range version { + if i <= len(other)-1 { + otherVersion := other[i] + + if v < otherVersion { + return -1 + } else if v > otherVersion { + return 1 + } + } + } + if len(version) > len(other) { + return 1 + } + if len(version) < len(other) { + return -1 + } + return 0 +} + +// Client is the basic type of this package. It provides methods for +// interaction with the API. +type Client struct { + SkipServerVersionCheck bool + HTTPClient *http.Client + TLSConfig *tls.Config + Dialer Dialer + + endpoint string + endpointURL *url.URL + eventMonitor *eventMonitoringState + requestedAPIVersion APIVersion + serverAPIVersion APIVersion + expectedAPIVersion APIVersion +} + +// Dialer is an interface that allows network connections to be dialed +// (net.Dialer fulfills this interface) and named pipes (a shim using +// winio.DialPipe) +type Dialer interface { + Dial(network, address string) (net.Conn, error) +} + +// NewClient returns a Client instance ready for communication with the given +// server endpoint. It will use the latest remote API version available in the +// server. +func NewClient(endpoint string) (*Client, error) { + client, err := NewVersionedClient(endpoint, "") + if err != nil { + return nil, err + } + client.SkipServerVersionCheck = true + return client, nil +} + +// NewTLSClient returns a Client instance ready for TLS communications with the givens +// server endpoint, key and certificates . It will use the latest remote API version +// available in the server. +func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) { + client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "") + if err != nil { + return nil, err + } + client.SkipServerVersionCheck = true + return client, nil +} + +// NewTLSClientFromBytes returns a Client instance ready for TLS communications with the givens +// server endpoint, key and certificates (passed inline to the function as opposed to being +// read from a local file). It will use the latest remote API version available in the server. +func NewTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte) (*Client, error) { + client, err := NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, "") + if err != nil { + return nil, err + } + client.SkipServerVersionCheck = true + return client, nil +} + +// NewVersionedClient returns a Client instance ready for communication with +// the given server endpoint, using a specific remote API version. +func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) { + u, err := parseEndpoint(endpoint, false) + if err != nil { + return nil, err + } + var requestedAPIVersion APIVersion + if strings.Contains(apiVersionString, ".") { + requestedAPIVersion, err = NewAPIVersion(apiVersionString) + if err != nil { + return nil, err + } + } + c := &Client{ + HTTPClient: defaultClient(), + Dialer: &net.Dialer{}, + endpoint: endpoint, + endpointURL: u, + eventMonitor: new(eventMonitoringState), + requestedAPIVersion: requestedAPIVersion, + } + c.initializeNativeClient(defaultTransport) + return c, nil +} + +// WithTransport replaces underlying HTTP client of Docker Client by accepting +// a function that returns pointer to a transport object. +func (c *Client) WithTransport(trFunc func() *http.Transport) { + c.initializeNativeClient(trFunc) +} + +// NewVersionnedTLSClient is like NewVersionedClient, but with ann extra n. +// +// Deprecated: Use NewVersionedTLSClient instead. +func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { + return NewVersionedTLSClient(endpoint, cert, key, ca, apiVersionString) +} + +// NewVersionedTLSClient returns a Client instance ready for TLS communications with the givens +// server endpoint, key and certificates, using a specific remote API version. +func NewVersionedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { + var certPEMBlock []byte + var keyPEMBlock []byte + var caPEMCert []byte + if _, err := os.Stat(cert); !os.IsNotExist(err) { + certPEMBlock, err = os.ReadFile(cert) + if err != nil { + return nil, err + } + } + if _, err := os.Stat(key); !os.IsNotExist(err) { + keyPEMBlock, err = os.ReadFile(key) + if err != nil { + return nil, err + } + } + if _, err := os.Stat(ca); !os.IsNotExist(err) { + caPEMCert, err = os.ReadFile(ca) + if err != nil { + return nil, err + } + } + return NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, apiVersionString) +} + +// NewClientFromEnv returns a Client instance ready for communication created from +// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH, +// and DOCKER_API_VERSION. +// +// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68. +// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7. +// See https://github.com/moby/moby/blob/28d7dba41d0c0d9c7f0dafcc79d3c59f2b3f5dc3/client/options.go#L51 +func NewClientFromEnv() (*Client, error) { + apiVersionString := os.Getenv("DOCKER_API_VERSION") + client, err := NewVersionedClientFromEnv(apiVersionString) + if err != nil { + return nil, err + } + client.SkipServerVersionCheck = apiVersionString == "" + return client, nil +} + +// NewVersionedClientFromEnv returns a Client instance ready for TLS communications created from +// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH, +// and using a specific remote API version. +// +// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68. +// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7. +func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) { + dockerEnv, err := getDockerEnv() + if err != nil { + return nil, err + } + dockerHost := dockerEnv.dockerHost + if dockerEnv.dockerTLSVerify { + parts := strings.SplitN(dockerEnv.dockerHost, "://", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("could not split %s into two parts by ://", dockerHost) + } + cert := filepath.Join(dockerEnv.dockerCertPath, "cert.pem") + key := filepath.Join(dockerEnv.dockerCertPath, "key.pem") + ca := filepath.Join(dockerEnv.dockerCertPath, "ca.pem") + return NewVersionedTLSClient(dockerEnv.dockerHost, cert, key, ca, apiVersionString) + } + return NewVersionedClient(dockerEnv.dockerHost, apiVersionString) +} + +// NewVersionedTLSClientFromBytes returns a Client instance ready for TLS communications with the givens +// server endpoint, key and certificates (passed inline to the function as opposed to being +// read from a local file), using a specific remote API version. +func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte, apiVersionString string) (*Client, error) { + u, err := parseEndpoint(endpoint, true) + if err != nil { + return nil, err + } + var requestedAPIVersion APIVersion + if strings.Contains(apiVersionString, ".") { + requestedAPIVersion, err = NewAPIVersion(apiVersionString) + if err != nil { + return nil, err + } + } + tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} + if certPEMBlock != nil && keyPEMBlock != nil { + tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{tlsCert} + } + if caPEMCert == nil { + tlsConfig.InsecureSkipVerify = true + } else { + caPool := x509.NewCertPool() + if !caPool.AppendCertsFromPEM(caPEMCert) { + return nil, errors.New("could not add RootCA pem") + } + tlsConfig.RootCAs = caPool + } + tr := defaultTransport() + tr.TLSClientConfig = tlsConfig + if err != nil { + return nil, err + } + c := &Client{ + HTTPClient: &http.Client{Transport: tr}, + TLSConfig: tlsConfig, + Dialer: &net.Dialer{}, + endpoint: endpoint, + endpointURL: u, + eventMonitor: new(eventMonitoringState), + requestedAPIVersion: requestedAPIVersion, + } + c.initializeNativeClient(defaultTransport) + return c, nil +} + +// SetTimeout takes a timeout and applies it to the HTTPClient. It should not +// be called concurrently with any other Client methods. +func (c *Client) SetTimeout(t time.Duration) { + if c.HTTPClient != nil { + c.HTTPClient.Timeout = t + } +} + +func (c *Client) checkAPIVersion() error { + serverAPIVersionString, err := c.getServerAPIVersionString() + if err != nil { + return err + } + c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString) + if err != nil { + return err + } + if c.requestedAPIVersion == nil { + c.expectedAPIVersion = c.serverAPIVersion + } else { + c.expectedAPIVersion = c.requestedAPIVersion + } + return nil +} + +// Endpoint returns the current endpoint. It's useful for getting the endpoint +// when using functions that get this data from the environment (like +// NewClientFromEnv. +func (c *Client) Endpoint() string { + return c.endpoint +} + +// Ping pings the docker server +// +// See https://goo.gl/wYfgY1 for more details. +func (c *Client) Ping() error { + return c.PingWithContext(context.TODO()) +} + +// PingWithContext pings the docker server +// The context object can be used to cancel the ping request. +// +// See https://goo.gl/wYfgY1 for more details. +func (c *Client) PingWithContext(ctx context.Context) error { + path := "/_ping" + resp, err := c.do(http.MethodGet, path, doOptions{context: ctx}) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return newError(resp) + } + resp.Body.Close() + return nil +} + +func (c *Client) getServerAPIVersionString() (version string, err error) { + resp, err := c.do(http.MethodGet, "/version", doOptions{}) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("received unexpected status %d while trying to retrieve the server version", resp.StatusCode) + } + var versionResponse map[string]any + if err := json.NewDecoder(resp.Body).Decode(&versionResponse); err != nil { + return "", err + } + if version, ok := (versionResponse["ApiVersion"]).(string); ok { + return version, nil + } + return "", nil +} + +type doOptions struct { + data any + forceJSON bool + headers map[string]string + context context.Context +} + +func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, error) { + var params io.Reader + if doOptions.data != nil || doOptions.forceJSON { + buf, err := json.Marshal(doOptions.data) + if err != nil { + return nil, err + } + params = bytes.NewBuffer(buf) + } + if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { + err := c.checkAPIVersion() + if err != nil { + return nil, err + } + } + protocol := c.endpointURL.Scheme + var u string + switch protocol { + case unixProtocol, namedPipeProtocol: + u = c.getFakeNativeURL(path) + default: + u = c.getURL(path) + } + + req, err := http.NewRequest(method, u, params) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", userAgent) + if doOptions.data != nil { + req.Header.Set("Content-Type", "application/json") + } else if method == http.MethodPost { + req.Header.Set("Content-Type", "plain/text") + } + + for k, v := range doOptions.headers { + req.Header.Set(k, v) + } + + ctx := doOptions.context + if ctx == nil { + ctx = context.Background() + } + + resp, err := c.HTTPClient.Do(req.WithContext(ctx)) + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return nil, ErrConnectionRefused + } + + return nil, chooseError(ctx, err) + } + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest { + return nil, newError(resp) + } + return resp, nil +} + +type streamOptions struct { + setRawTerminal bool + rawJSONStream bool + useJSONDecoder bool + headers map[string]string + in io.Reader + stdout io.Writer + stderr io.Writer + reqSent chan struct{} + // timeout is the initial connection timeout + timeout time.Duration + // Timeout with no data is received, it's reset every time new data + // arrives + inactivityTimeout time.Duration + context context.Context +} + +func chooseError(ctx context.Context, err error) error { + select { + case <-ctx.Done(): + return context.Cause(ctx) + default: + return err + } +} + +func (c *Client) stream(method, path string, streamOptions streamOptions) error { + if (method == http.MethodPost || method == http.MethodPut) && streamOptions.in == nil { + streamOptions.in = bytes.NewReader(nil) + } + if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { + err := c.checkAPIVersion() + if err != nil { + return err + } + } + return c.streamURL(method, c.getURL(path), streamOptions) +} + +func (c *Client) streamURL(method, url string, streamOptions streamOptions) error { + if (method == http.MethodPost || method == http.MethodPut) && streamOptions.in == nil { + streamOptions.in = bytes.NewReader(nil) + } + if !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { + err := c.checkAPIVersion() + if err != nil { + return err + } + } + + // make a sub-context so that our active cancellation does not affect parent + ctx := streamOptions.context + if ctx == nil { + ctx = context.Background() + } + subCtx, cancelRequest := context.WithCancel(ctx) + defer cancelRequest() + + req, err := http.NewRequestWithContext(ctx, method, url, streamOptions.in) + if err != nil { + return err + } + req.Header.Set("User-Agent", userAgent) + if method == http.MethodPost { + req.Header.Set("Content-Type", "plain/text") + } + for key, val := range streamOptions.headers { + req.Header.Set(key, val) + } + var resp *http.Response + protocol := c.endpointURL.Scheme + address := c.endpointURL.Path + if streamOptions.stdout == nil { + streamOptions.stdout = io.Discard + } + if streamOptions.stderr == nil { + streamOptions.stderr = io.Discard + } + + if protocol == unixProtocol || protocol == namedPipeProtocol { + var dial net.Conn + dial, err = c.Dialer.Dial(protocol, address) + if err != nil { + return err + } + go func() { + <-subCtx.Done() + dial.Close() + }() + breader := bufio.NewReader(dial) + err = req.Write(dial) + if err != nil { + return chooseError(subCtx, err) + } + + // ReadResponse may hang if server does not replay + if streamOptions.timeout > 0 { + dial.SetDeadline(time.Now().Add(streamOptions.timeout)) + } + + if streamOptions.reqSent != nil { + close(streamOptions.reqSent) + } + if resp, err = http.ReadResponse(breader, req); err != nil { + // Cancel timeout for future I/O operations + if streamOptions.timeout > 0 { + dial.SetDeadline(time.Time{}) + } + if strings.Contains(err.Error(), "connection refused") { + return ErrConnectionRefused + } + + return chooseError(subCtx, err) + } + defer resp.Body.Close() + } else { + if resp, err = c.HTTPClient.Do(req.WithContext(subCtx)); err != nil { + if strings.Contains(err.Error(), "connection refused") { + return ErrConnectionRefused + } + return chooseError(subCtx, err) + } + defer resp.Body.Close() + if streamOptions.reqSent != nil { + close(streamOptions.reqSent) + } + } + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + return newError(resp) + } + var canceled uint32 + if streamOptions.inactivityTimeout > 0 { + var ch chan<- struct{} + resp.Body, ch = handleInactivityTimeout(resp.Body, streamOptions.inactivityTimeout, cancelRequest, &canceled) + defer close(ch) + } + err = handleStreamResponse(resp, &streamOptions) + if err != nil { + if atomic.LoadUint32(&canceled) != 0 { + return ErrInactivityTimeout + } + return chooseError(subCtx, err) + } + return nil +} + +func handleStreamResponse(resp *http.Response, streamOptions *streamOptions) error { + var err error + if !streamOptions.useJSONDecoder && resp.Header.Get("Content-Type") != "application/json" { + if streamOptions.setRawTerminal { + _, err = io.Copy(streamOptions.stdout, resp.Body) + } else { + _, err = stdcopy.StdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body) + } + return err + } + // if we want to get raw json stream, just copy it back to output + // without decoding it + if streamOptions.rawJSONStream { + _, err = io.Copy(streamOptions.stdout, resp.Body) + return err + } + if st, ok := streamOptions.stdout.(stream); ok { + err = jsonmessage.DisplayJSONMessagesToStream(resp.Body, st, nil) + } else { + err = jsonmessage.DisplayJSONMessagesStream(resp.Body, streamOptions.stdout, 0, false, nil) + } + return err +} + +type stream interface { + io.Writer + FD() uintptr + IsTerminal() bool +} + +type proxyReader struct { + io.ReadCloser + calls uint64 +} + +func (p *proxyReader) callCount() uint64 { + return atomic.LoadUint64(&p.calls) +} + +func (p *proxyReader) Read(data []byte) (int, error) { + atomic.AddUint64(&p.calls, 1) + return p.ReadCloser.Read(data) +} + +func handleInactivityTimeout(reader io.ReadCloser, timeout time.Duration, cancelRequest func(), canceled *uint32) (io.ReadCloser, chan<- struct{}) { + done := make(chan struct{}) + proxyReader := &proxyReader{ReadCloser: reader} + go func() { + var lastCallCount uint64 + for { + select { + case <-time.After(timeout): + case <-done: + return + } + curCallCount := proxyReader.callCount() + if curCallCount == lastCallCount { + atomic.AddUint32(canceled, 1) + cancelRequest() + return + } + lastCallCount = curCallCount + } + }() + return proxyReader, done +} + +type hijackOptions struct { + success chan struct{} + setRawTerminal bool + in io.Reader + stdout io.Writer + stderr io.Writer + data any +} + +// CloseWaiter is an interface with methods for closing the underlying resource +// and then waiting for it to finish processing. +type CloseWaiter interface { + io.Closer + Wait() error +} + +type waiterFunc func() error + +func (w waiterFunc) Wait() error { return w() } + +type closerFunc func() error + +func (c closerFunc) Close() error { return c() } + +func (c *Client) hijack(method, path string, hijackOptions hijackOptions) (CloseWaiter, error) { + if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { + err := c.checkAPIVersion() + if err != nil { + return nil, err + } + } + var params io.Reader + if hijackOptions.data != nil { + buf, err := json.Marshal(hijackOptions.data) + if err != nil { + return nil, err + } + params = bytes.NewBuffer(buf) + } + req, err := http.NewRequest(method, c.getURL(path), params) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Connection", "Upgrade") + req.Header.Set("Upgrade", "tcp") + protocol := c.endpointURL.Scheme + address := c.endpointURL.Path + if protocol != unixProtocol && protocol != namedPipeProtocol { + protocol = "tcp" + address = c.endpointURL.Host + } + var dial net.Conn + if c.TLSConfig != nil && protocol != unixProtocol && protocol != namedPipeProtocol { + netDialer, ok := c.Dialer.(*net.Dialer) + if !ok { + return nil, ErrTLSNotSupported + } + dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig) + if err != nil { + return nil, err + } + } else { + dial, err = c.Dialer.Dial(protocol, address) + if err != nil { + return nil, err + } + } + + errs := make(chan error, 1) + quit := make(chan struct{}) + go func() { + //lint:ignore SA1019 the alternative doesn't quite work, so keep using the deprecated thing. + clientconn := httputil.NewClientConn(dial, nil) + defer clientconn.Close() + clientconn.Do(req) + if hijackOptions.success != nil { + hijackOptions.success <- struct{}{} + <-hijackOptions.success + } + rwc, br := clientconn.Hijack() + defer rwc.Close() + + errChanOut := make(chan error, 1) + errChanIn := make(chan error, 2) + if hijackOptions.stdout == nil && hijackOptions.stderr == nil { + close(errChanOut) + } else { + // Only copy if hijackOptions.stdout and/or hijackOptions.stderr is actually set. + // Otherwise, if the only stream you care about is stdin, your attach session + // will "hang" until the container terminates, even though you're not reading + // stdout/stderr + if hijackOptions.stdout == nil { + hijackOptions.stdout = io.Discard + } + if hijackOptions.stderr == nil { + hijackOptions.stderr = io.Discard + } + + go func() { + defer func() { + if hijackOptions.in != nil { + if closer, ok := hijackOptions.in.(io.Closer); ok { + closer.Close() + } + errChanIn <- nil + } + }() + + var err error + if hijackOptions.setRawTerminal { + _, err = io.Copy(hijackOptions.stdout, br) + } else { + _, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br) + } + errChanOut <- err + }() + } + + go func() { + var err error + if hijackOptions.in != nil { + _, err = io.Copy(rwc, hijackOptions.in) + } + errChanIn <- err + rwc.(interface { + CloseWrite() error + }).CloseWrite() + }() + + var errIn error + select { + case errIn = <-errChanIn: + case <-quit: + } + + var errOut error + select { + case errOut = <-errChanOut: + case <-quit: + } + + if errIn != nil { + errs <- errIn + } else { + errs <- errOut + } + }() + + return struct { + closerFunc + waiterFunc + }{ + closerFunc(func() error { close(quit); return nil }), + waiterFunc(func() error { return <-errs }), + }, nil +} + +func (c *Client) getURL(path string) string { + urlStr := strings.TrimRight(c.endpointURL.String(), "/") + if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol { + urlStr = "" + } + if c.requestedAPIVersion != nil { + return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path) + } + return fmt.Sprintf("%s%s", urlStr, path) +} + +func (c *Client) getPath(basepath string, opts any) (string, error) { + queryStr, requiredAPIVersion := queryStringVersion(opts) + return c.pathVersionCheck(basepath, queryStr, requiredAPIVersion) +} + +func (c *Client) pathVersionCheck(basepath, queryStr string, requiredAPIVersion APIVersion) (string, error) { + urlStr := strings.TrimRight(c.endpointURL.String(), "/") + if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol { + urlStr = "" + } + if c.requestedAPIVersion != nil { + if c.requestedAPIVersion.GreaterThanOrEqualTo(requiredAPIVersion) { + return fmt.Sprintf("%s/v%s%s?%s", urlStr, c.requestedAPIVersion, basepath, queryStr), nil + } + return "", fmt.Errorf("API %s requires version %s, requested version %s is insufficient", + basepath, requiredAPIVersion, c.requestedAPIVersion) + } + if requiredAPIVersion != nil { + return fmt.Sprintf("%s/v%s%s?%s", urlStr, requiredAPIVersion, basepath, queryStr), nil + } + return fmt.Sprintf("%s%s?%s", urlStr, basepath, queryStr), nil +} + +// getFakeNativeURL returns the URL needed to make an HTTP request over a UNIX +// domain socket to the given path. +func (c *Client) getFakeNativeURL(path string) string { + u := *c.endpointURL // Copy. + + // Override URL so that net/http will not complain. + u.Scheme = "http" + u.Host = "unix.sock" // Doesn't matter what this is - it's not used. + u.Path = "" + urlStr := strings.TrimRight(u.String(), "/") + if c.requestedAPIVersion != nil { + return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path) + } + return fmt.Sprintf("%s%s", urlStr, path) +} + +func queryStringVersion(opts any) (string, APIVersion) { + if opts == nil { + return "", nil + } + value := reflect.ValueOf(opts) + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + if value.Kind() != reflect.Struct { + return "", nil + } + var apiVersion APIVersion + items := url.Values(map[string][]string{}) + for i := 0; i < value.NumField(); i++ { + field := value.Type().Field(i) + if field.PkgPath != "" { + continue + } + key := field.Tag.Get("qs") + if key == "" { + key = strings.ToLower(field.Name) + } else if key == "-" { + continue + } + if addQueryStringValue(items, key, value.Field(i)) { + verstr := field.Tag.Get("ver") + if verstr != "" { + ver, _ := NewAPIVersion(verstr) + if apiVersion == nil { + apiVersion = ver + } else if ver.GreaterThan(apiVersion) { + apiVersion = ver + } + } + } + } + return items.Encode(), apiVersion +} + +func queryString(opts any) string { + s, _ := queryStringVersion(opts) + return s +} + +func addQueryStringValue(items url.Values, key string, v reflect.Value) bool { + switch v.Kind() { + case reflect.Bool: + if v.Bool() { + items.Add(key, "1") + return true + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if v.Int() > 0 { + items.Add(key, strconv.FormatInt(v.Int(), 10)) + return true + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if v.Uint() > 0 { + items.Add(key, strconv.FormatUint(v.Uint(), 10)) + return true + } + case reflect.Float32, reflect.Float64: + if v.Float() > 0 { + items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64)) + return true + } + case reflect.String: + if v.String() != "" { + items.Add(key, v.String()) + return true + } + case reflect.Ptr: + if !v.IsNil() { + if b, err := json.Marshal(v.Interface()); err == nil { + items.Add(key, string(b)) + return true + } + } + case reflect.Map: + if len(v.MapKeys()) > 0 { + if b, err := json.Marshal(v.Interface()); err == nil { + items.Add(key, string(b)) + return true + } + } + case reflect.Array, reflect.Slice: + vLen := v.Len() + var valuesAdded int + if vLen > 0 { + for i := 0; i < vLen; i++ { + if addQueryStringValue(items, key, v.Index(i)) { + valuesAdded++ + } + } + } + return valuesAdded > 0 + } + return false +} + +// Error represents failures in the API. It represents a failure from the API. +type Error struct { + Status int + Message string +} + +func newError(resp *http.Response) *Error { + type ErrMsg struct { + Message string `json:"message"` + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)} + } + var emsg ErrMsg + err = json.Unmarshal(data, &emsg) + if err != nil { + return &Error{Status: resp.StatusCode, Message: string(data)} + } + return &Error{Status: resp.StatusCode, Message: emsg.Message} +} + +func (e *Error) Error() string { + return fmt.Sprintf("API error (%d): %s", e.Status, e.Message) +} + +func parseEndpoint(endpoint string, tls bool) (*url.URL, error) { + if endpoint != "" && !strings.Contains(endpoint, "://") { + endpoint = "tcp://" + endpoint + } + u, err := url.Parse(endpoint) + if err != nil { + return nil, ErrInvalidEndpoint + } + if tls && u.Scheme != "unix" { + u.Scheme = "https" + } + switch u.Scheme { + case unixProtocol, namedPipeProtocol: + return u, nil + case "http", "https", "tcp": + _, port, err := net.SplitHostPort(u.Host) + if err != nil { + var e *net.AddrError + if errors.As(err, &e) { + if e.Err == "missing port in address" { + return u, nil + } + } + return nil, ErrInvalidEndpoint + } + number, err := strconv.ParseInt(port, 10, 64) + if err == nil && number > 0 && number < 65536 { + if u.Scheme == "tcp" { + if tls { + u.Scheme = "https" + } else { + u.Scheme = "http" + } + } + return u, nil + } + return nil, ErrInvalidEndpoint + default: + return nil, ErrInvalidEndpoint + } +} + +type dockerEnv struct { + dockerHost string + dockerTLSVerify bool + dockerCertPath string +} + +func getDockerEnv() (*dockerEnv, error) { + dockerHost := os.Getenv("DOCKER_HOST") + var err error + if dockerHost == "" { + dockerHost = defaultHost + } + dockerTLSVerify := os.Getenv("DOCKER_TLS_VERIFY") != "" + var dockerCertPath string + if dockerTLSVerify { + dockerCertPath = os.Getenv("DOCKER_CERT_PATH") + if dockerCertPath == "" { + home := homedir.Get() + if home == "" { + return nil, errors.New("environment variable HOME must be set if DOCKER_CERT_PATH is not set") + } + dockerCertPath = filepath.Join(home, ".docker") + dockerCertPath, err = filepath.Abs(dockerCertPath) + if err != nil { + return nil, err + } + } + } + return &dockerEnv{ + dockerHost: dockerHost, + dockerTLSVerify: dockerTLSVerify, + dockerCertPath: dockerCertPath, + }, nil +} + +// defaultTransport returns a new http.Transport with similar default values to +// http.DefaultTransport, but with idle connections and keepalives disabled. +func defaultTransport() *http.Transport { + transport := defaultPooledTransport() + transport.DisableKeepAlives = true + transport.MaxIdleConnsPerHost = -1 + return transport +} + +// defaultPooledTransport returns a new http.Transport with similar default +// values to http.DefaultTransport. Do not use this for transient transports as +// it can leak file descriptors over time. Only use this for transports that +// will be re-used for the same host(s). +func defaultPooledTransport() *http.Transport { + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1, + } + return transport +} + +// defaultClient returns a new http.Client with similar default values to +// http.Client, but with a non-shared Transport, idle connections disabled, and +// keepalives disabled. +func defaultClient() *http.Client { + return &http.Client{ + Transport: defaultTransport(), + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/client_unix.go b/vendor/github.com/fsouza/go-dockerclient/client_unix.go new file mode 100644 index 00000000..ee4dba2a --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/client_unix.go @@ -0,0 +1,31 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows + +package docker + +import ( + "context" + "net" + "net/http" +) + +const defaultHost = "unix:///var/run/docker.sock" + +// initializeNativeClient initializes the native Unix domain socket client on +// Unix-style operating systems +func (c *Client) initializeNativeClient(trFunc func() *http.Transport) { + if c.endpointURL.Scheme != unixProtocol { + return + } + sockPath := c.endpointURL.Path + + tr := trFunc() + tr.Proxy = nil + tr.DialContext = func(_ context.Context, network, addr string) (net.Conn, error) { + return c.Dialer.Dial(unixProtocol, sockPath) + } + c.HTTPClient.Transport = tr +} diff --git a/vendor/github.com/fsouza/go-dockerclient/client_windows.go b/vendor/github.com/fsouza/go-dockerclient/client_windows.go new file mode 100644 index 00000000..d35f401a --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/client_windows.go @@ -0,0 +1,46 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "net" + "net/http" + "time" + + winio "github.com/Microsoft/go-winio" +) + +const ( + defaultHost = "npipe:////./pipe/docker_engine" + namedPipeConnectTimeout = 2 * time.Second +) + +type pipeDialer struct { + dialFunc func(network, addr string) (net.Conn, error) +} + +func (p pipeDialer) Dial(network, address string) (net.Conn, error) { + return p.dialFunc(network, address) +} + +// initializeNativeClient initializes the native Named Pipe client for Windows +func (c *Client) initializeNativeClient(trFunc func() *http.Transport) { + if c.endpointURL.Scheme != namedPipeProtocol { + return + } + namedPipePath := c.endpointURL.Path + dialFunc := func(_, addr string) (net.Conn, error) { + timeout := namedPipeConnectTimeout + return winio.DialPipe(namedPipePath, &timeout) + } + tr := trFunc() + tr.Proxy = nil + tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialFunc(network, addr) + } + c.Dialer = &pipeDialer{dialFunc} + c.HTTPClient.Transport = tr +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container.go b/vendor/github.com/fsouza/go-dockerclient/container.go new file mode 100644 index 00000000..e5dafe0f --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container.go @@ -0,0 +1,600 @@ +// Copyright 2013 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "fmt" + "strconv" + "strings" + "time" + + units "github.com/docker/go-units" +) + +// APIPort is a type that represents a port mapping returned by the Docker API +type APIPort struct { + PrivatePort int64 `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty" toml:"PrivatePort,omitempty"` + PublicPort int64 `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty" toml:"PublicPort,omitempty"` + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` + IP string `json:"IP,omitempty" yaml:"IP,omitempty" toml:"IP,omitempty"` +} + +// APIMount represents a mount point for a container. +type APIMount struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Source string `json:"Source,omitempty" yaml:"Source,omitempty" toml:"Source,omitempty"` + Destination string `json:"Destination,omitempty" yaml:"Destination,omitempty" toml:"Destination,omitempty"` + Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` + Mode string `json:"Mode,omitempty" yaml:"Mode,omitempty" toml:"Mode,omitempty"` + RW bool `json:"RW,omitempty" yaml:"RW,omitempty" toml:"RW,omitempty"` + Propagation string `json:"Propagation,omitempty" yaml:"Propagation,omitempty" toml:"Propagation,omitempty"` + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` +} + +// APIContainers represents each container in the list returned by +// ListContainers. +type APIContainers struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` + Command string `json:"Command,omitempty" yaml:"Command,omitempty" toml:"Command,omitempty"` + Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` + State string `json:"State,omitempty" yaml:"State,omitempty" toml:"State,omitempty"` + Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` + Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty" toml:"Ports,omitempty"` + SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty" toml:"SizeRw,omitempty"` + SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty" toml:"SizeRootFs,omitempty"` + Names []string `json:"Names,omitempty" yaml:"Names,omitempty" toml:"Names,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` + Networks NetworkList `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty" toml:"NetworkSettings,omitempty"` + Mounts []APIMount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` +} + +// NetworkList encapsulates a map of networks, as returned by the Docker API in +// ListContainers. +type NetworkList struct { + Networks map[string]ContainerNetwork `json:"Networks" yaml:"Networks,omitempty" toml:"Networks,omitempty"` +} + +// Port represents the port number and the protocol, in the form +// /. For example: 80/tcp. +type Port string + +// Port returns the number of the port. +func (p Port) Port() string { + return strings.Split(string(p), "/")[0] +} + +// Proto returns the name of the protocol. +func (p Port) Proto() string { + parts := strings.Split(string(p), "/") + if len(parts) == 1 { + return "tcp" + } + return parts[1] +} + +// HealthCheck represents one check of health. +type HealthCheck struct { + Start time.Time `json:"Start,omitempty" yaml:"Start,omitempty" toml:"Start,omitempty"` + End time.Time `json:"End,omitempty" yaml:"End,omitempty" toml:"End,omitempty"` + ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` + Output string `json:"Output,omitempty" yaml:"Output,omitempty" toml:"Output,omitempty"` +} + +// Health represents the health of a container. +type Health struct { + Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` + FailingStreak int `json:"FailingStreak,omitempty" yaml:"FailingStreak,omitempty" toml:"FailingStreak,omitempty"` + Log []HealthCheck `json:"Log,omitempty" yaml:"Log,omitempty" toml:"Log,omitempty"` +} + +// State represents the state of a container. +type State struct { + Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` + Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"` + Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty" toml:"Paused,omitempty"` + Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty" toml:"Restarting,omitempty"` + OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty" toml:"OOMKilled,omitempty"` + RemovalInProgress bool `json:"RemovalInProgress,omitempty" yaml:"RemovalInProgress,omitempty" toml:"RemovalInProgress,omitempty"` + Dead bool `json:"Dead,omitempty" yaml:"Dead,omitempty" toml:"Dead,omitempty"` + Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty" toml:"Pid,omitempty"` + ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` + Error string `json:"Error,omitempty" yaml:"Error,omitempty" toml:"Error,omitempty"` + StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty" toml:"StartedAt,omitempty"` + FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty" toml:"FinishedAt,omitempty"` + Health Health `json:"Health,omitempty" yaml:"Health,omitempty" toml:"Health,omitempty"` +} + +// String returns a human-readable description of the state +func (s *State) String() string { + if s.Running { + if s.Paused { + return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + } + if s.Restarting { + return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) + } + + return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + } + + if s.RemovalInProgress { + return "Removal In Progress" + } + + if s.Dead { + return "Dead" + } + + if s.StartedAt.IsZero() { + return "Created" + } + + if s.FinishedAt.IsZero() { + return "" + } + + return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) +} + +// StateString returns a single string to describe state +func (s *State) StateString() string { + if s.Running { + if s.Paused { + return "paused" + } + if s.Restarting { + return "restarting" + } + return "running" + } + + if s.Dead { + return "dead" + } + + if s.StartedAt.IsZero() { + return "created" + } + + return "exited" +} + +// PortBinding represents the host/container port mapping as returned in the +// `docker inspect` json +type PortBinding struct { + HostIP string `json:"HostIp,omitempty" yaml:"HostIp,omitempty" toml:"HostIp,omitempty"` + HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty" toml:"HostPort,omitempty"` +} + +// PortMapping represents a deprecated field in the `docker inspect` output, +// and its value as found in NetworkSettings should always be nil +type PortMapping map[string]string + +// ContainerNetwork represents the networking settings of a container per network. +type ContainerNetwork struct { + Aliases []string `json:"Aliases,omitempty" yaml:"Aliases,omitempty" toml:"Aliases,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"` + GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"` + IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"` + IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"` + IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"` + Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"` + EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"` + NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"` +} + +// NetworkSettings contains network-related information about a container +type NetworkSettings struct { + Networks map[string]ContainerNetwork `json:"Networks,omitempty" yaml:"Networks,omitempty" toml:"Networks,omitempty"` + IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"` + IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` + Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"` + Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty" toml:"Bridge,omitempty"` + PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty" toml:"PortMapping,omitempty"` + Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty" toml:"Ports,omitempty"` + NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"` + EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"` + SandboxKey string `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty" toml:"SandboxKey,omitempty"` + GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"` + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"` + IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"` + LinkLocalIPv6Address string `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty" toml:"LinkLocalIPv6Address,omitempty"` + LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty" toml:"LinkLocalIPv6PrefixLen,omitempty"` + SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty" toml:"SecondaryIPAddresses,omitempty"` + SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty" toml:"SecondaryIPv6Addresses,omitempty"` +} + +// PortMappingAPI translates the port mappings as contained in NetworkSettings +// into the format in which they would appear when returned by the API +func (settings *NetworkSettings) PortMappingAPI() []APIPort { + var mapping []APIPort + for port, bindings := range settings.Ports { + p, _ := parsePort(port.Port()) + if len(bindings) == 0 { + mapping = append(mapping, APIPort{ + PrivatePort: int64(p), + Type: port.Proto(), + }) + continue + } + for _, binding := range bindings { + p, _ := parsePort(port.Port()) + h, _ := parsePort(binding.HostPort) + mapping = append(mapping, APIPort{ + PrivatePort: int64(p), + PublicPort: int64(h), + Type: port.Proto(), + IP: binding.HostIP, + }) + } + } + return mapping +} + +func parsePort(rawPort string) (int, error) { + port, err := strconv.ParseUint(rawPort, 10, 16) + if err != nil { + return 0, err + } + return int(port), nil +} + +// Config is the list of configuration options used when creating a container. +// Config does not contain the options that are specific to starting a container on a +// given host. Those are contained in HostConfig +type Config struct { + Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty" toml:"Hostname,omitempty"` + Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty" toml:"Domainname,omitempty"` + User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` + Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"` + MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty" toml:"MemorySwap,omitempty"` + MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty" toml:"MemoryReservation,omitempty"` + KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty" toml:"KernelMemory,omitempty"` + CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty" toml:"CpuShares,omitempty"` + CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty" toml:"Cpuset,omitempty"` + PortSpecs []string `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty" toml:"PortSpecs,omitempty"` + ExposedPorts map[Port]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty" toml:"ExposedPorts,omitempty"` + PublishService string `json:"PublishService,omitempty" yaml:"PublishService,omitempty" toml:"PublishService,omitempty"` + StopSignal string `json:"StopSignal,omitempty" yaml:"StopSignal,omitempty" toml:"StopSignal,omitempty"` + StopTimeout int `json:"StopTimeout,omitempty" yaml:"StopTimeout,omitempty" toml:"StopTimeout,omitempty"` + Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` + Cmd []string `json:"Cmd" yaml:"Cmd" toml:"Cmd"` + Shell []string `json:"Shell,omitempty" yaml:"Shell,omitempty" toml:"Shell,omitempty"` + Healthcheck *HealthConfig `json:"Healthcheck,omitempty" yaml:"Healthcheck,omitempty" toml:"Healthcheck,omitempty"` + DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty" toml:"Dns,omitempty"` // For Docker API v1.9 and below only + Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` + Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty" toml:"Volumes,omitempty"` + VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty" toml:"VolumeDriver,omitempty"` + WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty" toml:"WorkingDir,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` + Entrypoint []string `json:"Entrypoint" yaml:"Entrypoint" toml:"Entrypoint"` + SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty" toml:"SecurityOpts,omitempty"` + OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty" toml:"OnBuild,omitempty"` + Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` + AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"` + AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"` + AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"` + ArgsEscaped bool `json:"ArgsEscaped,omitempty" yaml:"ArgsEscaped,omitempty" toml:"ArgsEscaped,omitempty"` + Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` + OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"` + StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty" toml:"StdinOnce,omitempty"` + NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty" toml:"NetworkDisabled,omitempty"` + + // This is no longer used and has been kept here for backward + // compatibility, please use HostConfig.VolumesFrom. + VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty" toml:"VolumesFrom,omitempty"` +} + +// HostMount represents a mount point in the container in HostConfig. +// +// It has been added in the version 1.25 of the Docker API +type HostMount struct { + Target string `json:"Target,omitempty" yaml:"Target,omitempty" toml:"Target,omitempty"` + Source string `json:"Source,omitempty" yaml:"Source,omitempty" toml:"Source,omitempty"` + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` + ReadOnly bool `json:"ReadOnly,omitempty" yaml:"ReadOnly,omitempty" toml:"ReadOnly,omitempty"` + BindOptions *BindOptions `json:"BindOptions,omitempty" yaml:"BindOptions,omitempty" toml:"BindOptions,omitempty"` + VolumeOptions *VolumeOptions `json:"VolumeOptions,omitempty" yaml:"VolumeOptions,omitempty" toml:"VolumeOptions,omitempty"` + TempfsOptions *TempfsOptions `json:"TmpfsOptions,omitempty" yaml:"TmpfsOptions,omitempty" toml:"TmpfsOptions,omitempty"` +} + +// BindOptions contains optional configuration for the bind type +type BindOptions struct { + Propagation string `json:"Propagation,omitempty" yaml:"Propagation,omitempty" toml:"Propagation,omitempty"` +} + +// VolumeOptions contains optional configuration for the volume type +type VolumeOptions struct { + NoCopy bool `json:"NoCopy,omitempty" yaml:"NoCopy,omitempty" toml:"NoCopy,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` + DriverConfig VolumeDriverConfig `json:"DriverConfig,omitempty" yaml:"DriverConfig,omitempty" toml:"DriverConfig,omitempty"` +} + +// TempfsOptions contains optional configuration for the tempfs type +type TempfsOptions struct { + SizeBytes int64 `json:"SizeBytes,omitempty" yaml:"SizeBytes,omitempty" toml:"SizeBytes,omitempty"` + Mode int `json:"Mode,omitempty" yaml:"Mode,omitempty" toml:"Mode,omitempty"` + Options [][]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"` +} + +// VolumeDriverConfig holds a map of volume driver specific options +type VolumeDriverConfig struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Options map[string]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"` +} + +// Mount represents a mount point in the container. +// +// It has been added in the version 1.20 of the Docker API, available since +// Docker 1.8. +type Mount struct { + Name string + Source string + Destination string + Driver string + Mode string + RW bool +} + +// LogConfig defines the log driver type and the configuration for it. +type LogConfig struct { + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` + Config map[string]string `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` +} + +// ULimit defines system-wide resource limitations This can help a lot in +// system administration, e.g. when a user starts too many processes and +// therefore makes the system unresponsive for other users. +type ULimit struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Soft int64 `json:"Soft,omitempty" yaml:"Soft,omitempty" toml:"Soft,omitempty"` + Hard int64 `json:"Hard,omitempty" yaml:"Hard,omitempty" toml:"Hard,omitempty"` +} + +// SwarmNode containers information about which Swarm node the container is on. +type SwarmNode struct { + ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"` + IP string `json:"IP,omitempty" yaml:"IP,omitempty" toml:"IP,omitempty"` + Addr string `json:"Addr,omitempty" yaml:"Addr,omitempty" toml:"Addr,omitempty"` + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + CPUs int64 `json:"CPUs,omitempty" yaml:"CPUs,omitempty" toml:"CPUs,omitempty"` + Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` +} + +// GraphDriver contains information about the GraphDriver used by the +// container. +type GraphDriver struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Data map[string]string `json:"Data,omitempty" yaml:"Data,omitempty" toml:"Data,omitempty"` +} + +// HealthConfig holds configuration settings for the HEALTHCHECK feature +// +// It has been added in the version 1.24 of the Docker API, available since +// Docker 1.12. +type HealthConfig struct { + // Test is the test to perform to check that the container is healthy. + // An empty slice means to inherit the default. + // The options are: + // {} : inherit healthcheck + // {"NONE"} : disable healthcheck + // {"CMD", args...} : exec arguments directly + // {"CMD-SHELL", command} : run command with system's default shell + Test []string `json:"Test,omitempty" yaml:"Test,omitempty" toml:"Test,omitempty"` + + // Zero means to inherit. Durations are expressed as integer nanoseconds. + Interval time.Duration `json:"Interval,omitempty" yaml:"Interval,omitempty" toml:"Interval,omitempty"` // Interval is the time to wait between checks. + Timeout time.Duration `json:"Timeout,omitempty" yaml:"Timeout,omitempty" toml:"Timeout,omitempty"` // Timeout is the time to wait before considering the check to have hung. + StartPeriod time.Duration `json:"StartPeriod,omitempty" yaml:"StartPeriod,omitempty" toml:"StartPeriod,omitempty"` // The start period for the container to initialize before the retries starts to count down. + StartInterval time.Duration `json:"StartInterval,omitempty" yaml:"StartInterval,omitempty" toml:"StartInterval,omitempty"` // The start interval is the time to wait between checks during the start period. + + // Retries is the number of consecutive failures needed to consider a container as unhealthy. + // Zero means inherit. + Retries int `json:"Retries,omitempty" yaml:"Retries,omitempty" toml:"Retries,omitempty"` +} + +// Container is the type encompasing everything about a container - its config, +// hostconfig, etc. +type Container struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + + Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` + + Path string `json:"Path,omitempty" yaml:"Path,omitempty" toml:"Path,omitempty"` + Args []string `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"` + + Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` + State State `json:"State,omitempty" yaml:"State,omitempty" toml:"State,omitempty"` + Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` + + Node *SwarmNode `json:"Node,omitempty" yaml:"Node,omitempty" toml:"Node,omitempty"` + + NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty" toml:"NetworkSettings,omitempty"` + + SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty" toml:"SysInitPath,omitempty"` + ResolvConfPath string `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty" toml:"ResolvConfPath,omitempty"` + HostnamePath string `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty" toml:"HostnamePath,omitempty"` + HostsPath string `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty" toml:"HostsPath,omitempty"` + LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty" toml:"LogPath,omitempty"` + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` + Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` + + Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty" toml:"Volumes,omitempty"` + VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty" toml:"VolumesRW,omitempty"` + HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty" toml:"HostConfig,omitempty"` + ExecIDs []string `json:"ExecIDs,omitempty" yaml:"ExecIDs,omitempty" toml:"ExecIDs,omitempty"` + GraphDriver *GraphDriver `json:"GraphDriver,omitempty" yaml:"GraphDriver,omitempty" toml:"GraphDriver,omitempty"` + + RestartCount int `json:"RestartCount,omitempty" yaml:"RestartCount,omitempty" toml:"RestartCount,omitempty"` + + AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty" toml:"AppArmorProfile,omitempty"` + + MountLabel string `json:"MountLabel,omitempty" yaml:"MountLabel,omitempty" toml:"MountLabel,omitempty"` + ProcessLabel string `json:"ProcessLabel,omitempty" yaml:"ProcessLabel,omitempty" toml:"ProcessLabel,omitempty"` + Platform string `json:"Platform,omitempty" yaml:"Platform,omitempty" toml:"Platform,omitempty"` + SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty" toml:"SizeRw,omitempty"` + SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty" toml:"SizeRootFs,omitempty"` +} + +// KeyValuePair is a type for generic key/value pairs as used in the Lxc +// configuration +type KeyValuePair struct { + Key string `json:"Key,omitempty" yaml:"Key,omitempty" toml:"Key,omitempty"` + Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// Device represents a device mapping between the Docker host and the +// container. +type Device struct { + PathOnHost string `json:"PathOnHost,omitempty" yaml:"PathOnHost,omitempty" toml:"PathOnHost,omitempty"` + PathInContainer string `json:"PathInContainer,omitempty" yaml:"PathInContainer,omitempty" toml:"PathInContainer,omitempty"` + CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty" toml:"CgroupPermissions,omitempty"` +} + +// DeviceRequest represents a request for device that's sent to device drivers. +type DeviceRequest struct { + Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` + Count int `json:"Count,omitempty" yaml:"Count,omitempty" toml:"Count,omitempty"` + DeviceIDs []string `json:"DeviceIDs,omitempty" yaml:"DeviceIDs,omitempty" toml:"DeviceIDs,omitempty"` + Capabilities [][]string `json:"Capabilities,omitempty" yaml:"Capabilities,omitempty" toml:"Capabilities,omitempty"` + Options map[string]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"` +} + +// BlockWeight represents a relative device weight for an individual device inside +// of a container +type BlockWeight struct { + Path string `json:"Path,omitempty"` + Weight string `json:"Weight,omitempty"` +} + +// BlockLimit represents a read/write limit in IOPS or Bandwidth for a device +// inside of a container +type BlockLimit struct { + Path string `json:"Path,omitempty"` + Rate int64 `json:"Rate,omitempty"` +} + +// HostConfig contains the container options related to starting a container on +// a given host +type HostConfig struct { + Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty" toml:"Binds,omitempty"` + CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty" toml:"CapAdd,omitempty"` + CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty" toml:"CapDrop,omitempty"` + Capabilities []string `json:"Capabilities,omitempty" yaml:"Capabilities,omitempty" toml:"Capabilities,omitempty"` // Mutually exclusive w.r.t. CapAdd and CapDrop API v1.40 + GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty" toml:"GroupAdd,omitempty"` + ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty" toml:"ContainerIDFile,omitempty"` + LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty" toml:"LxcConf,omitempty"` + PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty" toml:"PortBindings,omitempty"` + Links []string `json:"Links,omitempty" yaml:"Links,omitempty" toml:"Links,omitempty"` + DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty" toml:"Dns,omitempty"` // For Docker API v1.10 and above only + DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty" toml:"DnsOptions,omitempty"` + DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty" toml:"DnsSearch,omitempty"` + ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty" toml:"ExtraHosts,omitempty"` + VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty" toml:"VolumesFrom,omitempty"` + UsernsMode string `json:"UsernsMode,omitempty" yaml:"UsernsMode,omitempty" toml:"UsernsMode,omitempty"` + NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty" toml:"NetworkMode,omitempty"` + IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty" toml:"IpcMode,omitempty"` + Isolation string `json:"Isolation,omitempty" yaml:"Isolation,omitempty" toml:"Isolation,omitempty"` // Windows only + ConsoleSize [2]int `json:"ConsoleSize,omitempty" yaml:"ConsoleSize,omitempty" toml:"ConsoleSize,omitempty"` // Windows only height x width + PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty" toml:"PidMode,omitempty"` + UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty" toml:"UTSMode,omitempty"` + RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty" toml:"RestartPolicy,omitempty"` + Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` + DeviceCgroupRules []string `json:"DeviceCgroupRules,omitempty" yaml:"DeviceCgroupRules,omitempty" toml:"DeviceCgroupRules,omitempty"` + DeviceRequests []DeviceRequest `json:"DeviceRequests,omitempty" yaml:"DeviceRequests,omitempty" toml:"DeviceRequests,omitempty"` + LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty" toml:"LogConfig,omitempty"` + SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty" toml:"SecurityOpt,omitempty"` + CgroupnsMode string `json:"CgroupnsMode,omitempty" yaml:"CgroupnsMode,omitempty" toml:"CgroupnsMode,omitempty"` // v1.40+ + Cgroup string `json:"Cgroup,omitempty" yaml:"Cgroup,omitempty" toml:"Cgroup,omitempty"` + CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty" toml:"CgroupParent,omitempty"` + Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"` + MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty" toml:"MemoryReservation,omitempty"` + KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty" toml:"KernelMemory,omitempty"` + MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty" toml:"MemorySwap,omitempty"` + CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty" toml:"CpuShares,omitempty"` + CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty" toml:"Cpuset,omitempty"` + CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty" toml:"CpusetCpus,omitempty"` + CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty" toml:"CpusetMems,omitempty"` + CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty" toml:"CpuQuota,omitempty"` + CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty" toml:"CpuPeriod,omitempty"` + CPURealtimePeriod int64 `json:"CpuRealtimePeriod,omitempty" yaml:"CpuRealtimePeriod,omitempty" toml:"CpuRealtimePeriod,omitempty"` + CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime,omitempty" yaml:"CpuRealtimeRuntime,omitempty" toml:"CpuRealtimeRuntime,omitempty"` + NanoCPUs int64 `json:"NanoCpus,omitempty" yaml:"NanoCpus,omitempty" toml:"NanoCpus,omitempty"` + BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight,omitempty" toml:"BlkioWeight,omitempty"` + BlkioWeightDevice []BlockWeight `json:"BlkioWeightDevice,omitempty" yaml:"BlkioWeightDevice,omitempty" toml:"BlkioWeightDevice,omitempty"` + BlkioDeviceReadBps []BlockLimit `json:"BlkioDeviceReadBps,omitempty" yaml:"BlkioDeviceReadBps,omitempty" toml:"BlkioDeviceReadBps,omitempty"` + BlkioDeviceReadIOps []BlockLimit `json:"BlkioDeviceReadIOps,omitempty" yaml:"BlkioDeviceReadIOps,omitempty" toml:"BlkioDeviceReadIOps,omitempty"` + BlkioDeviceWriteBps []BlockLimit `json:"BlkioDeviceWriteBps,omitempty" yaml:"BlkioDeviceWriteBps,omitempty" toml:"BlkioDeviceWriteBps,omitempty"` + BlkioDeviceWriteIOps []BlockLimit `json:"BlkioDeviceWriteIOps,omitempty" yaml:"BlkioDeviceWriteIOps,omitempty" toml:"BlkioDeviceWriteIOps,omitempty"` + Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty" toml:"Ulimits,omitempty"` + VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty" toml:"VolumeDriver,omitempty"` + OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty" toml:"OomScoreAdj,omitempty"` + MemorySwappiness *int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty" toml:"MemorySwappiness,omitempty"` + PidsLimit *int64 `json:"PidsLimit,omitempty" yaml:"PidsLimit,omitempty" toml:"PidsLimit,omitempty"` + OOMKillDisable *bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable,omitempty" toml:"OomKillDisable,omitempty"` + ShmSize int64 `json:"ShmSize,omitempty" yaml:"ShmSize,omitempty" toml:"ShmSize,omitempty"` + Tmpfs map[string]string `json:"Tmpfs,omitempty" yaml:"Tmpfs,omitempty" toml:"Tmpfs,omitempty"` + StorageOpt map[string]string `json:"StorageOpt,omitempty" yaml:"StorageOpt,omitempty" toml:"StorageOpt,omitempty"` + Sysctls map[string]string `json:"Sysctls,omitempty" yaml:"Sysctls,omitempty" toml:"Sysctls,omitempty"` + CPUCount int64 `json:"CpuCount,omitempty" yaml:"CpuCount,omitempty"` + CPUPercent int64 `json:"CpuPercent,omitempty" yaml:"CpuPercent,omitempty"` + IOMaximumBandwidth int64 `json:"IOMaximumBandwidth,omitempty" yaml:"IOMaximumBandwidth,omitempty"` + IOMaximumIOps int64 `json:"IOMaximumIOps,omitempty" yaml:"IOMaximumIOps,omitempty"` + Mounts []HostMount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` + MaskedPaths []string `json:"MaskedPaths,omitempty" yaml:"MaskedPaths,omitempty" toml:"MaskedPaths,omitempty"` + ReadonlyPaths []string `json:"ReadonlyPaths,omitempty" yaml:"ReadonlyPaths,omitempty" toml:"ReadonlyPaths,omitempty"` + Runtime string `json:"Runtime,omitempty" yaml:"Runtime,omitempty" toml:"Runtime,omitempty"` + Init bool `json:",omitempty" yaml:",omitempty"` + Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"` + PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty" toml:"PublishAllPorts,omitempty"` + ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty" toml:"ReadonlyRootfs,omitempty"` + AutoRemove bool `json:"AutoRemove,omitempty" yaml:"AutoRemove,omitempty" toml:"AutoRemove,omitempty"` + Annotations map[string]string `json:"Annotations,omitempty" yaml:"Annotations,omitempty" toml:"Annotations,omitempty"` +} + +// NetworkingConfig represents the container's networking configuration for each of its interfaces +// Carries the networking configs specified in the `docker run` and `docker network connect` commands +type NetworkingConfig struct { + EndpointsConfig map[string]*EndpointConfig `json:"EndpointsConfig" yaml:"EndpointsConfig" toml:"EndpointsConfig"` // Endpoint configs for each connecting network +} + +// NoSuchContainer is the error returned when a given container does not exist. +type NoSuchContainer struct { + ID string + Err error +} + +func (err *NoSuchContainer) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such container: " + err.ID +} + +// ContainerAlreadyRunning is the error returned when a given container is +// already running. +type ContainerAlreadyRunning struct { + ID string +} + +func (err *ContainerAlreadyRunning) Error() string { + return "Container already running: " + err.ID +} + +// ContainerNotRunning is the error returned when a given container is not +// running. +type ContainerNotRunning struct { + ID string +} + +func (err *ContainerNotRunning) Error() string { + return "Container not running: " + err.ID +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_archive.go b/vendor/github.com/fsouza/go-dockerclient/container_archive.go new file mode 100644 index 00000000..6c7e61c7 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_archive.go @@ -0,0 +1,58 @@ +package docker + +import ( + "context" + "fmt" + "io" + "net/http" + "time" +) + +// UploadToContainerOptions is the set of options that can be used when +// uploading an archive into a container. +// +// See https://goo.gl/g25o7u for more details. +type UploadToContainerOptions struct { + InputStream io.Reader `json:"-" qs:"-"` + Path string `qs:"path"` + NoOverwriteDirNonDir bool `qs:"noOverwriteDirNonDir"` + Context context.Context +} + +// UploadToContainer uploads a tar archive to be extracted to a path in the +// filesystem of the container. +// +// See https://goo.gl/g25o7u for more details. +func (c *Client) UploadToContainer(id string, opts UploadToContainerOptions) error { + url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) + + return c.stream(http.MethodPut, url, streamOptions{ + in: opts.InputStream, + context: opts.Context, + }) +} + +// DownloadFromContainerOptions is the set of options that can be used when +// downloading resources from a container. +// +// See https://goo.gl/W49jxK for more details. +type DownloadFromContainerOptions struct { + OutputStream io.Writer `json:"-" qs:"-"` + Path string `qs:"path"` + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// DownloadFromContainer downloads a tar archive of files or folders in a container. +// +// See https://goo.gl/W49jxK for more details. +func (c *Client) DownloadFromContainer(id string, opts DownloadFromContainerOptions) error { + url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) + + return c.stream(http.MethodGet, url, streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_attach.go b/vendor/github.com/fsouza/go-dockerclient/container_attach.go new file mode 100644 index 00000000..e6742d00 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_attach.go @@ -0,0 +1,74 @@ +package docker + +import ( + "io" + "net/http" +) + +// AttachToContainerOptions is the set of options that can be used when +// attaching to a container. +// +// See https://goo.gl/JF10Zk for more details. +type AttachToContainerOptions struct { + Container string `qs:"-"` + InputStream io.Reader `qs:"-"` + OutputStream io.Writer `qs:"-"` + ErrorStream io.Writer `qs:"-"` + + // If set, after a successful connect, a sentinel will be sent and then the + // client will block on receive before continuing. + // + // It must be an unbuffered channel. Using a buffered channel can lead + // to unexpected behavior. + Success chan struct{} + + // Override the key sequence for detaching a container. + DetachKeys string + + // Use raw terminal? Usually true when the container contains a TTY. + RawTerminal bool `qs:"-"` + + // Get container logs, sending it to OutputStream. + Logs bool + + // Stream the response? + Stream bool + + // Attach to stdin, and use InputStream. + Stdin bool + + // Attach to stdout, and use OutputStream. + Stdout bool + + // Attach to stderr, and use ErrorStream. + Stderr bool +} + +// AttachToContainer attaches to a container, using the given options. +// +// See https://goo.gl/JF10Zk for more details. +func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { + cw, err := c.AttachToContainerNonBlocking(opts) + if err != nil { + return err + } + return cw.Wait() +} + +// AttachToContainerNonBlocking attaches to a container, using the given options. +// This function does not block. +// +// See https://goo.gl/NKpkFk for more details. +func (c *Client) AttachToContainerNonBlocking(opts AttachToContainerOptions) (CloseWaiter, error) { + if opts.Container == "" { + return nil, &NoSuchContainer{ID: opts.Container} + } + path := "/containers/" + opts.Container + "/attach?" + queryString(opts) + return c.hijack(http.MethodPost, path, hijackOptions{ + success: opts.Success, + setRawTerminal: opts.RawTerminal, + in: opts.InputStream, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + }) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_changes.go b/vendor/github.com/fsouza/go-dockerclient/container_changes.go new file mode 100644 index 00000000..48835e23 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_changes.go @@ -0,0 +1,28 @@ +package docker + +import ( + "encoding/json" + "errors" + "net/http" +) + +// ContainerChanges returns changes in the filesystem of the given container. +// +// See https://goo.gl/15KKzh for more details. +func (c *Client) ContainerChanges(id string) ([]Change, error) { + path := "/containers/" + id + "/changes" + resp, err := c.do(http.MethodGet, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var changes []Change + if err := json.NewDecoder(resp.Body).Decode(&changes); err != nil { + return nil, err + } + return changes, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_commit.go b/vendor/github.com/fsouza/go-dockerclient/container_commit.go new file mode 100644 index 00000000..902ba645 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_commit.go @@ -0,0 +1,46 @@ +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" +) + +// CommitContainerOptions aggregates parameters to the CommitContainer method. +// +// See https://goo.gl/CzIguf for more details. +type CommitContainerOptions struct { + Container string + Repository string `qs:"repo"` + Tag string + Message string `qs:"comment"` + Author string + Changes []string `qs:"changes"` + Run *Config `qs:"-"` + Context context.Context +} + +// CommitContainer creates a new image from a container's changes. +// +// See https://goo.gl/CzIguf for more details. +func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { + path := "/commit?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{ + data: opts.Run, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: opts.Container} + } + return nil, err + } + defer resp.Body.Close() + var image Image + if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { + return nil, err + } + return &image, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_copy.go b/vendor/github.com/fsouza/go-dockerclient/container_copy.go new file mode 100644 index 00000000..c8ffb85c --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_copy.go @@ -0,0 +1,50 @@ +package docker + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" +) + +// CopyFromContainerOptions contains the set of options used for copying +// files from a container. +// +// Deprecated: Use DownloadFromContainerOptions and DownloadFromContainer instead. +type CopyFromContainerOptions struct { + OutputStream io.Writer `json:"-"` + Container string `json:"-"` + Resource string + Context context.Context `json:"-"` +} + +// CopyFromContainer copies files from a container. +// +// Deprecated: Use DownloadFromContainer and DownloadFromContainer instead. +func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { + if opts.Container == "" { + return &NoSuchContainer{ID: opts.Container} + } + if c.serverAPIVersion == nil { + c.checkAPIVersion() + } + if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion124) { + return errors.New("go-dockerclient: CopyFromContainer is no longer available in Docker >= 1.12, use DownloadFromContainer instead") + } + url := fmt.Sprintf("/containers/%s/copy", opts.Container) + resp, err := c.do(http.MethodPost, url, doOptions{ + data: opts, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: opts.Container} + } + return err + } + defer resp.Body.Close() + _, err = io.Copy(opts.OutputStream, resp.Body) + return err +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_create.go b/vendor/github.com/fsouza/go-dockerclient/container_create.go new file mode 100644 index 00000000..36bcbfc0 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_create.go @@ -0,0 +1,80 @@ +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "strings" +) + +// ErrContainerAlreadyExists is the error returned by CreateContainer when the +// container already exists. +var ErrContainerAlreadyExists = errors.New("container already exists") + +// CreateContainerOptions specify parameters to the CreateContainer function. +// +// See https://goo.gl/tyzwVM for more details. +type CreateContainerOptions struct { + Name string + Platform string + Config *Config `qs:"-"` + HostConfig *HostConfig `qs:"-"` + NetworkingConfig *NetworkingConfig `qs:"-"` + Context context.Context +} + +// CreateContainer creates a new container, returning the container instance, +// or an error in case of failure. +// +// The returned container instance contains only the container ID. To get more +// details about the container after creating it, use InspectContainer. +// +// See https://goo.gl/tyzwVM for more details. +func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) { + path := "/containers/create?" + queryString(opts) + resp, err := c.do( + http.MethodPost, + path, + doOptions{ + data: struct { + *Config + HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty" toml:"HostConfig,omitempty"` + NetworkingConfig *NetworkingConfig `json:"NetworkingConfig,omitempty" yaml:"NetworkingConfig,omitempty" toml:"NetworkingConfig,omitempty"` + }{ + opts.Config, + opts.HostConfig, + opts.NetworkingConfig, + }, + context: opts.Context, + }, + ) + + var e *Error + if errors.As(err, &e) { + if e.Status == http.StatusNotFound && strings.Contains(e.Message, "No such image") { + return nil, ErrNoSuchImage + } + if e.Status == http.StatusConflict { + return nil, ErrContainerAlreadyExists + } + // Workaround for 17.09 bug returning 400 instead of 409. + // See https://github.com/moby/moby/issues/35021 + if e.Status == http.StatusBadRequest && strings.Contains(e.Message, "Conflict.") { + return nil, ErrContainerAlreadyExists + } + } + + if err != nil { + return nil, err + } + defer resp.Body.Close() + var container Container + if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { + return nil, err + } + + container.Name = opts.Name + + return &container, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_export.go b/vendor/github.com/fsouza/go-dockerclient/container_export.go new file mode 100644 index 00000000..312f8cf1 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_export.go @@ -0,0 +1,37 @@ +package docker + +import ( + "context" + "fmt" + "io" + "net/http" + "time" +) + +// ExportContainerOptions is the set of parameters to the ExportContainer +// method. +// +// See https://goo.gl/yGJCIh for more details. +type ExportContainerOptions struct { + ID string + OutputStream io.Writer + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// ExportContainer export the contents of container id as tar archive +// and prints the exported contents to stdout. +// +// See https://goo.gl/yGJCIh for more details. +func (c *Client) ExportContainer(opts ExportContainerOptions) error { + if opts.ID == "" { + return &NoSuchContainer{ID: opts.ID} + } + url := fmt.Sprintf("/containers/%s/export", opts.ID) + return c.stream(http.MethodGet, url, streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_inspect.go b/vendor/github.com/fsouza/go-dockerclient/container_inspect.go new file mode 100644 index 00000000..48c1e8ea --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_inspect.go @@ -0,0 +1,55 @@ +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" +) + +// InspectContainer returns information about a container by its ID. +// +// Deprecated: Use InspectContainerWithOptions instead. +func (c *Client) InspectContainer(id string) (*Container, error) { + return c.InspectContainerWithOptions(InspectContainerOptions{ID: id}) +} + +// InspectContainerWithContext returns information about a container by its ID. +// The context object can be used to cancel the inspect request. +// +// Deprecated: Use InspectContainerWithOptions instead. +func (c *Client) InspectContainerWithContext(id string, ctx context.Context) (*Container, error) { + return c.InspectContainerWithOptions(InspectContainerOptions{ID: id, Context: ctx}) +} + +// InspectContainerWithOptions returns information about a container by its ID. +// +// See https://goo.gl/FaI5JT for more details. +func (c *Client) InspectContainerWithOptions(opts InspectContainerOptions) (*Container, error) { + path := "/containers/" + opts.ID + "/json?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{ + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: opts.ID} + } + return nil, err + } + defer resp.Body.Close() + var container Container + if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { + return nil, err + } + return &container, nil +} + +// InspectContainerOptions specifies parameters for InspectContainerWithOptions. +// +// See https://goo.gl/FaI5JT for more details. +type InspectContainerOptions struct { + Context context.Context + ID string `qs:"-"` + Size bool +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_kill.go b/vendor/github.com/fsouza/go-dockerclient/container_kill.go new file mode 100644 index 00000000..600c58f1 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_kill.go @@ -0,0 +1,46 @@ +package docker + +import ( + "context" + "errors" + "net/http" +) + +// KillContainerOptions represents the set of options that can be used in a +// call to KillContainer. +// +// See https://goo.gl/JnTxXZ for more details. +type KillContainerOptions struct { + // The ID of the container. + ID string `qs:"-"` + + // The signal to send to the container. When omitted, Docker server + // will assume SIGKILL. + Signal Signal + Context context.Context +} + +// KillContainer sends a signal to a container, returning an error in case of +// failure. +// +// See https://goo.gl/JnTxXZ for more details. +func (c *Client) KillContainer(opts KillContainerOptions) error { + path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) + if err != nil { + var e *Error + if !errors.As(err, &e) { + return err + } + switch e.Status { + case http.StatusNotFound: + return &NoSuchContainer{ID: opts.ID} + case http.StatusConflict: + return &ContainerNotRunning{ID: opts.ID} + default: + return err + } + } + resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_list.go b/vendor/github.com/fsouza/go-dockerclient/container_list.go new file mode 100644 index 00000000..1dec0e91 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_list.go @@ -0,0 +1,37 @@ +package docker + +import ( + "context" + "encoding/json" + "net/http" +) + +// ListContainersOptions specify parameters to the ListContainers function. +// +// See https://goo.gl/kaOHGw for more details. +type ListContainersOptions struct { + All bool + Size bool + Limit int + Since string + Before string + Filters map[string][]string + Context context.Context +} + +// ListContainers returns a slice of containers matching the given criteria. +// +// See https://goo.gl/kaOHGw for more details. +func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { + path := "/containers/json?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var containers []APIContainers + if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil { + return nil, err + } + return containers, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_logs.go b/vendor/github.com/fsouza/go-dockerclient/container_logs.go new file mode 100644 index 00000000..0e3f1199 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_logs.go @@ -0,0 +1,58 @@ +package docker + +import ( + "context" + "io" + "net/http" + "time" +) + +// LogsOptions represents the set of options used when getting logs from a +// container. +// +// See https://goo.gl/krK0ZH for more details. +type LogsOptions struct { + Context context.Context + Container string `qs:"-"` + OutputStream io.Writer `qs:"-"` + ErrorStream io.Writer `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + Tail string + + Since int64 + Follow bool + Stdout bool + Stderr bool + Timestamps bool + + // Use raw terminal? Usually true when the container contains a TTY. + RawTerminal bool `qs:"-"` +} + +// Logs gets stdout and stderr logs from the specified container. +// +// When LogsOptions.RawTerminal is set to false, go-dockerclient will multiplex +// the streams and send the containers stdout to LogsOptions.OutputStream, and +// stderr to LogsOptions.ErrorStream. +// +// When LogsOptions.RawTerminal is true, callers will get the raw stream on +// LogsOptions.OutputStream. The caller can use libraries such as dlog +// (github.com/ahmetalpbalkan/dlog). +// +// See https://goo.gl/krK0ZH for more details. +func (c *Client) Logs(opts LogsOptions) error { + if opts.Container == "" { + return &NoSuchContainer{ID: opts.Container} + } + if opts.Tail == "" { + opts.Tail = "all" + } + path := "/containers/" + opts.Container + "/logs?" + queryString(opts) + return c.stream(http.MethodGet, path, streamOptions{ + setRawTerminal: opts.RawTerminal, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_pause.go b/vendor/github.com/fsouza/go-dockerclient/container_pause.go new file mode 100644 index 00000000..7d18b32f --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_pause.go @@ -0,0 +1,24 @@ +package docker + +import ( + "errors" + "fmt" + "net/http" +) + +// PauseContainer pauses the given container. +// +// See https://goo.gl/D1Yaii for more details. +func (c *Client) PauseContainer(id string) error { + path := fmt.Sprintf("/containers/%s/pause", id) + resp, err := c.do(http.MethodPost, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + return err + } + resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_prune.go b/vendor/github.com/fsouza/go-dockerclient/container_prune.go new file mode 100644 index 00000000..3f2bdc6a --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_prune.go @@ -0,0 +1,40 @@ +package docker + +import ( + "context" + "encoding/json" + "net/http" +) + +// PruneContainersOptions specify parameters to the PruneContainers function. +// +// See https://goo.gl/wnkgDT for more details. +type PruneContainersOptions struct { + Filters map[string][]string + Context context.Context +} + +// PruneContainersResults specify results from the PruneContainers function. +// +// See https://goo.gl/wnkgDT for more details. +type PruneContainersResults struct { + ContainersDeleted []string + SpaceReclaimed int64 +} + +// PruneContainers deletes containers which are stopped. +// +// See https://goo.gl/wnkgDT for more details. +func (c *Client) PruneContainers(opts PruneContainersOptions) (*PruneContainersResults, error) { + path := "/containers/prune?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var results PruneContainersResults + if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { + return nil, err + } + return &results, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_remove.go b/vendor/github.com/fsouza/go-dockerclient/container_remove.go new file mode 100644 index 00000000..dbe0907f --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_remove.go @@ -0,0 +1,41 @@ +package docker + +import ( + "context" + "errors" + "net/http" +) + +// RemoveContainerOptions encapsulates options to remove a container. +// +// See https://goo.gl/hL5IPC for more details. +type RemoveContainerOptions struct { + // The ID of the container. + ID string `qs:"-"` + + // A flag that indicates whether Docker should remove the volumes + // associated to the container. + RemoveVolumes bool `qs:"v"` + + // A flag that indicates whether Docker should remove the container + // even if it is currently running. + Force bool + Context context.Context +} + +// RemoveContainer removes a container, returning an error in case of failure. +// +// See https://goo.gl/hL5IPC for more details. +func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { + path := "/containers/" + opts.ID + "?" + queryString(opts) + resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_rename.go b/vendor/github.com/fsouza/go-dockerclient/container_rename.go new file mode 100644 index 00000000..893c423b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_rename.go @@ -0,0 +1,33 @@ +package docker + +import ( + "context" + "fmt" + "net/http" +) + +// RenameContainerOptions specify parameters to the RenameContainer function. +// +// See https://goo.gl/46inai for more details. +type RenameContainerOptions struct { + // ID of container to rename + ID string `qs:"-"` + + // New name + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Context context.Context +} + +// RenameContainer updates and existing containers name +// +// See https://goo.gl/46inai for more details. +func (c *Client) RenameContainer(opts RenameContainerOptions) error { + resp, err := c.do(http.MethodPost, fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{ + context: opts.Context, + }) + if err != nil { + return err + } + resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_resize.go b/vendor/github.com/fsouza/go-dockerclient/container_resize.go new file mode 100644 index 00000000..3445be6b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_resize.go @@ -0,0 +1,22 @@ +package docker + +import ( + "net/http" + "net/url" + "strconv" +) + +// ResizeContainerTTY resizes the terminal to the given height and width. +// +// See https://goo.gl/FImjeq for more details. +func (c *Client) ResizeContainerTTY(id string, height, width int) error { + params := make(url.Values) + params.Set("h", strconv.Itoa(height)) + params.Set("w", strconv.Itoa(width)) + resp, err := c.do(http.MethodPost, "/containers/"+id+"/resize?"+params.Encode(), doOptions{}) + if err != nil { + return err + } + resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_restart.go b/vendor/github.com/fsouza/go-dockerclient/container_restart.go new file mode 100644 index 00000000..64bb1a09 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_restart.go @@ -0,0 +1,64 @@ +package docker + +import ( + "errors" + "fmt" + "net/http" +) + +// RestartPolicy represents the policy for automatically restarting a container. +// +// Possible values are: +// +// - always: the docker daemon will always restart the container +// - on-failure: the docker daemon will restart the container on failures, at +// most MaximumRetryCount times +// - unless-stopped: the docker daemon will always restart the container except +// when user has manually stopped the container +// - no: the docker daemon will not restart the container automatically +type RestartPolicy struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + MaximumRetryCount int `json:"MaximumRetryCount,omitempty" yaml:"MaximumRetryCount,omitempty" toml:"MaximumRetryCount,omitempty"` +} + +// AlwaysRestart returns a restart policy that tells the Docker daemon to +// always restart the container. +func AlwaysRestart() RestartPolicy { + return RestartPolicy{Name: "always"} +} + +// RestartOnFailure returns a restart policy that tells the Docker daemon to +// restart the container on failures, trying at most maxRetry times. +func RestartOnFailure(maxRetry int) RestartPolicy { + return RestartPolicy{Name: "on-failure", MaximumRetryCount: maxRetry} +} + +// RestartUnlessStopped returns a restart policy that tells the Docker daemon to +// always restart the container except when user has manually stopped the container. +func RestartUnlessStopped() RestartPolicy { + return RestartPolicy{Name: "unless-stopped"} +} + +// NeverRestart returns a restart policy that tells the Docker daemon to never +// restart the container on failures. +func NeverRestart() RestartPolicy { + return RestartPolicy{Name: "no"} +} + +// RestartContainer stops a container, killing it after the given timeout (in +// seconds), during the stop process. +// +// See https://goo.gl/MrAKQ5 for more details. +func (c *Client) RestartContainer(id string, timeout uint) error { + path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout) + resp, err := c.do(http.MethodPost, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + return err + } + resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_start.go b/vendor/github.com/fsouza/go-dockerclient/container_start.go new file mode 100644 index 00000000..0911eaab --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_start.go @@ -0,0 +1,57 @@ +package docker + +import ( + "context" + "errors" + "net/http" +) + +// StartContainer starts a container, returning an error in case of failure. +// +// Passing the HostConfig to this method has been deprecated in Docker API 1.22 +// (Docker Engine 1.10.x) and totally removed in Docker API 1.24 (Docker Engine +// 1.12.x). The client will ignore the parameter when communicating with Docker +// API 1.24 or greater. +// +// See https://goo.gl/fbOSZy for more details. +func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { + return c.startContainer(id, hostConfig, doOptions{}) +} + +// StartContainerWithContext starts a container, returning an error in case of +// failure. The context can be used to cancel the outstanding start container +// request. +// +// Passing the HostConfig to this method has been deprecated in Docker API 1.22 +// (Docker Engine 1.10.x) and totally removed in Docker API 1.24 (Docker Engine +// 1.12.x). The client will ignore the parameter when communicating with Docker +// API 1.24 or greater. +// +// See https://goo.gl/fbOSZy for more details. +func (c *Client) StartContainerWithContext(id string, hostConfig *HostConfig, ctx context.Context) error { + return c.startContainer(id, hostConfig, doOptions{context: ctx}) +} + +func (c *Client) startContainer(id string, hostConfig *HostConfig, opts doOptions) error { + path := "/containers/" + id + "/start" + if c.serverAPIVersion == nil { + c.checkAPIVersion() + } + if c.serverAPIVersion != nil && c.serverAPIVersion.LessThan(apiVersion124) { + opts.data = hostConfig + opts.forceJSON = true + } + resp, err := c.do(http.MethodPost, path, opts) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id, Err: err} + } + return err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNotModified { + return &ContainerAlreadyRunning{ID: id} + } + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_stats.go b/vendor/github.com/fsouza/go-dockerclient/container_stats.go new file mode 100644 index 00000000..99d9faa3 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_stats.go @@ -0,0 +1,239 @@ +package docker + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "time" +) + +// Stats represents container statistics, returned by /containers//stats. +// +// See https://goo.gl/Dk3Xio for more details. +type Stats struct { + Read time.Time `json:"read,omitempty" yaml:"read,omitempty" toml:"read,omitempty"` + PreRead time.Time `json:"preread,omitempty" yaml:"preread,omitempty" toml:"preread,omitempty"` + NumProcs uint32 `json:"num_procs" yaml:"num_procs" toml:"num_procs"` + PidsStats struct { + Current uint64 `json:"current,omitempty" yaml:"current,omitempty"` + } `json:"pids_stats,omitempty" yaml:"pids_stats,omitempty" toml:"pids_stats,omitempty"` + Network NetworkStats `json:"network,omitempty" yaml:"network,omitempty" toml:"network,omitempty"` + Networks map[string]NetworkStats `json:"networks,omitempty" yaml:"networks,omitempty" toml:"networks,omitempty"` + MemoryStats struct { + Stats struct { + TotalPgmafault uint64 `json:"total_pgmafault,omitempty" yaml:"total_pgmafault,omitempty" toml:"total_pgmafault,omitempty"` + Cache uint64 `json:"cache,omitempty" yaml:"cache,omitempty" toml:"cache,omitempty"` + MappedFile uint64 `json:"mapped_file,omitempty" yaml:"mapped_file,omitempty" toml:"mapped_file,omitempty"` + TotalInactiveFile uint64 `json:"total_inactive_file,omitempty" yaml:"total_inactive_file,omitempty" toml:"total_inactive_file,omitempty"` + Pgpgout uint64 `json:"pgpgout,omitempty" yaml:"pgpgout,omitempty" toml:"pgpgout,omitempty"` + Rss uint64 `json:"rss,omitempty" yaml:"rss,omitempty" toml:"rss,omitempty"` + TotalMappedFile uint64 `json:"total_mapped_file,omitempty" yaml:"total_mapped_file,omitempty" toml:"total_mapped_file,omitempty"` + Writeback uint64 `json:"writeback,omitempty" yaml:"writeback,omitempty" toml:"writeback,omitempty"` + Unevictable uint64 `json:"unevictable,omitempty" yaml:"unevictable,omitempty" toml:"unevictable,omitempty"` + Pgpgin uint64 `json:"pgpgin,omitempty" yaml:"pgpgin,omitempty" toml:"pgpgin,omitempty"` + TotalUnevictable uint64 `json:"total_unevictable,omitempty" yaml:"total_unevictable,omitempty" toml:"total_unevictable,omitempty"` + Pgmajfault uint64 `json:"pgmajfault,omitempty" yaml:"pgmajfault,omitempty" toml:"pgmajfault,omitempty"` + TotalRss uint64 `json:"total_rss,omitempty" yaml:"total_rss,omitempty" toml:"total_rss,omitempty"` + TotalRssHuge uint64 `json:"total_rss_huge,omitempty" yaml:"total_rss_huge,omitempty" toml:"total_rss_huge,omitempty"` + TotalWriteback uint64 `json:"total_writeback,omitempty" yaml:"total_writeback,omitempty" toml:"total_writeback,omitempty"` + TotalInactiveAnon uint64 `json:"total_inactive_anon,omitempty" yaml:"total_inactive_anon,omitempty" toml:"total_inactive_anon,omitempty"` + RssHuge uint64 `json:"rss_huge,omitempty" yaml:"rss_huge,omitempty" toml:"rss_huge,omitempty"` + HierarchicalMemoryLimit uint64 `json:"hierarchical_memory_limit,omitempty" yaml:"hierarchical_memory_limit,omitempty" toml:"hierarchical_memory_limit,omitempty"` + TotalPgfault uint64 `json:"total_pgfault,omitempty" yaml:"total_pgfault,omitempty" toml:"total_pgfault,omitempty"` + TotalActiveFile uint64 `json:"total_active_file,omitempty" yaml:"total_active_file,omitempty" toml:"total_active_file,omitempty"` + ActiveAnon uint64 `json:"active_anon,omitempty" yaml:"active_anon,omitempty" toml:"active_anon,omitempty"` + TotalActiveAnon uint64 `json:"total_active_anon,omitempty" yaml:"total_active_anon,omitempty" toml:"total_active_anon,omitempty"` + TotalPgpgout uint64 `json:"total_pgpgout,omitempty" yaml:"total_pgpgout,omitempty" toml:"total_pgpgout,omitempty"` + TotalCache uint64 `json:"total_cache,omitempty" yaml:"total_cache,omitempty" toml:"total_cache,omitempty"` + InactiveAnon uint64 `json:"inactive_anon,omitempty" yaml:"inactive_anon,omitempty" toml:"inactive_anon,omitempty"` + ActiveFile uint64 `json:"active_file,omitempty" yaml:"active_file,omitempty" toml:"active_file,omitempty"` + Pgfault uint64 `json:"pgfault,omitempty" yaml:"pgfault,omitempty" toml:"pgfault,omitempty"` + InactiveFile uint64 `json:"inactive_file,omitempty" yaml:"inactive_file,omitempty" toml:"inactive_file,omitempty"` + TotalPgpgin uint64 `json:"total_pgpgin,omitempty" yaml:"total_pgpgin,omitempty" toml:"total_pgpgin,omitempty"` + HierarchicalMemswLimit uint64 `json:"hierarchical_memsw_limit,omitempty" yaml:"hierarchical_memsw_limit,omitempty" toml:"hierarchical_memsw_limit,omitempty"` + Swap uint64 `json:"swap,omitempty" yaml:"swap,omitempty" toml:"swap,omitempty"` + Anon uint64 `json:"anon,omitempty" yaml:"anon,omitempty" toml:"anon,omitempty"` + AnonThp uint64 `json:"anon_thp,omitempty" yaml:"anon_thp,omitempty" toml:"anon_thp,omitempty"` + File uint64 `json:"file,omitempty" yaml:"file,omitempty" toml:"file,omitempty"` + FileDirty uint64 `json:"file_dirty,omitempty" yaml:"file_dirty,omitempty" toml:"file_dirty,omitempty"` + FileMapped uint64 `json:"file_mapped,omitempty" yaml:"file_mapped,omitempty" toml:"file_mapped,omitempty"` + FileWriteback uint64 `json:"file_writeback,omitempty" yaml:"file_writeback,omitempty" toml:"file_writeback,omitempty"` + KernelStack uint64 `json:"kernel_stack,omitempty" yaml:"kernel_stack,omitempty" toml:"kernel_stack,omitempty"` + Pgactivate uint64 `json:"pgactivate,omitempty" yaml:"pgactivate,omitempty" toml:"pgactivate,omitempty"` + Pgdeactivate uint64 `json:"pgdeactivate,omitempty" yaml:"pgdeactivate,omitempty" toml:"pgdeactivate,omitempty"` + Pglazyfree uint64 `json:"pglazyfree,omitempty" yaml:"pglazyfree,omitempty" toml:"pglazyfree,omitempty"` + Pglazyfreed uint64 `json:"pglazyfreed,omitempty" yaml:"pglazyfreed,omitempty" toml:"pglazyfreed,omitempty"` + Pgrefill uint64 `json:"pgrefill,omitempty" yaml:"pgrefill,omitempty" toml:"pgrefill,omitempty"` + Pgscan uint64 `json:"pgscan,omitempty" yaml:"pgscan,omitempty" toml:"pgscan,omitempty"` + Pgsteal uint64 `json:"pgsteal,omitempty" yaml:"pgsteal,omitempty" toml:"pgsteal,omitempty"` + Shmem uint64 `json:"shmem,omitempty" yaml:"shmem,omitempty" toml:"shmem,omitempty"` + Slab uint64 `json:"slab,omitempty" yaml:"slab,omitempty" toml:"slab,omitempty"` + SlabReclaimable uint64 `json:"slab_reclaimable,omitempty" yaml:"slab_reclaimable,omitempty" toml:"slab_reclaimable,omitempty"` + SlabUnreclaimable uint64 `json:"slab_unreclaimable,omitempty" yaml:"slab_unreclaimable,omitempty" toml:"slab_unreclaimable,omitempty"` + Sock uint64 `json:"sock,omitempty" yaml:"sock,omitempty" toml:"sock,omitempty"` + ThpCollapseAlloc uint64 `json:"thp_collapse_alloc,omitempty" yaml:"thp_collapse_alloc,omitempty" toml:"thp_collapse_alloc,omitempty"` + ThpFaultAlloc uint64 `json:"thp_fault_alloc,omitempty" yaml:"thp_fault_alloc,omitempty" toml:"thp_fault_alloc,omitempty"` + WorkingsetActivate uint64 `json:"workingset_activate,omitempty" yaml:"workingset_activate,omitempty" toml:"workingset_activate,omitempty"` + WorkingsetNodereclaim uint64 `json:"workingset_nodereclaim,omitempty" yaml:"workingset_nodereclaim,omitempty" toml:"workingset_nodereclaim,omitempty"` + WorkingsetRefault uint64 `json:"workingset_refault,omitempty" yaml:"workingset_refault,omitempty" toml:"workingset_refault,omitempty"` + } `json:"stats,omitempty" yaml:"stats,omitempty" toml:"stats,omitempty"` + MaxUsage uint64 `json:"max_usage,omitempty" yaml:"max_usage,omitempty" toml:"max_usage,omitempty"` + Usage uint64 `json:"usage,omitempty" yaml:"usage,omitempty" toml:"usage,omitempty"` + Failcnt uint64 `json:"failcnt,omitempty" yaml:"failcnt,omitempty" toml:"failcnt,omitempty"` + Limit uint64 `json:"limit,omitempty" yaml:"limit,omitempty" toml:"limit,omitempty"` + Commit uint64 `json:"commitbytes,omitempty" yaml:"commitbytes,omitempty" toml:"privateworkingset,omitempty"` + CommitPeak uint64 `json:"commitpeakbytes,omitempty" yaml:"commitpeakbytes,omitempty" toml:"commitpeakbytes,omitempty"` + PrivateWorkingSet uint64 `json:"privateworkingset,omitempty" yaml:"privateworkingset,omitempty" toml:"privateworkingset,omitempty"` + } `json:"memory_stats,omitempty" yaml:"memory_stats,omitempty" toml:"memory_stats,omitempty"` + BlkioStats struct { + IOServiceBytesRecursive []BlkioStatsEntry `json:"io_service_bytes_recursive,omitempty" yaml:"io_service_bytes_recursive,omitempty" toml:"io_service_bytes_recursive,omitempty"` + IOServicedRecursive []BlkioStatsEntry `json:"io_serviced_recursive,omitempty" yaml:"io_serviced_recursive,omitempty" toml:"io_serviced_recursive,omitempty"` + IOQueueRecursive []BlkioStatsEntry `json:"io_queue_recursive,omitempty" yaml:"io_queue_recursive,omitempty" toml:"io_queue_recursive,omitempty"` + IOServiceTimeRecursive []BlkioStatsEntry `json:"io_service_time_recursive,omitempty" yaml:"io_service_time_recursive,omitempty" toml:"io_service_time_recursive,omitempty"` + IOWaitTimeRecursive []BlkioStatsEntry `json:"io_wait_time_recursive,omitempty" yaml:"io_wait_time_recursive,omitempty" toml:"io_wait_time_recursive,omitempty"` + IOMergedRecursive []BlkioStatsEntry `json:"io_merged_recursive,omitempty" yaml:"io_merged_recursive,omitempty" toml:"io_merged_recursive,omitempty"` + IOTimeRecursive []BlkioStatsEntry `json:"io_time_recursive,omitempty" yaml:"io_time_recursive,omitempty" toml:"io_time_recursive,omitempty"` + SectorsRecursive []BlkioStatsEntry `json:"sectors_recursive,omitempty" yaml:"sectors_recursive,omitempty" toml:"sectors_recursive,omitempty"` + } `json:"blkio_stats,omitempty" yaml:"blkio_stats,omitempty" toml:"blkio_stats,omitempty"` + CPUStats CPUStats `json:"cpu_stats,omitempty" yaml:"cpu_stats,omitempty" toml:"cpu_stats,omitempty"` + PreCPUStats CPUStats `json:"precpu_stats,omitempty"` + StorageStats struct { + ReadCountNormalized uint64 `json:"read_count_normalized,omitempty" yaml:"read_count_normalized,omitempty" toml:"read_count_normalized,omitempty"` + ReadSizeBytes uint64 `json:"read_size_bytes,omitempty" yaml:"read_size_bytes,omitempty" toml:"read_size_bytes,omitempty"` + WriteCountNormalized uint64 `json:"write_count_normalized,omitempty" yaml:"write_count_normalized,omitempty" toml:"write_count_normalized,omitempty"` + WriteSizeBytes uint64 `json:"write_size_bytes,omitempty" yaml:"write_size_bytes,omitempty" toml:"write_size_bytes,omitempty"` + } `json:"storage_stats,omitempty" yaml:"storage_stats,omitempty" toml:"storage_stats,omitempty"` +} + +// NetworkStats is a stats entry for network stats +type NetworkStats struct { + RxDropped uint64 `json:"rx_dropped,omitempty" yaml:"rx_dropped,omitempty" toml:"rx_dropped,omitempty"` + RxBytes uint64 `json:"rx_bytes,omitempty" yaml:"rx_bytes,omitempty" toml:"rx_bytes,omitempty"` + RxErrors uint64 `json:"rx_errors,omitempty" yaml:"rx_errors,omitempty" toml:"rx_errors,omitempty"` + TxPackets uint64 `json:"tx_packets,omitempty" yaml:"tx_packets,omitempty" toml:"tx_packets,omitempty"` + TxDropped uint64 `json:"tx_dropped,omitempty" yaml:"tx_dropped,omitempty" toml:"tx_dropped,omitempty"` + RxPackets uint64 `json:"rx_packets,omitempty" yaml:"rx_packets,omitempty" toml:"rx_packets,omitempty"` + TxErrors uint64 `json:"tx_errors,omitempty" yaml:"tx_errors,omitempty" toml:"tx_errors,omitempty"` + TxBytes uint64 `json:"tx_bytes,omitempty" yaml:"tx_bytes,omitempty" toml:"tx_bytes,omitempty"` +} + +// CPUStats is a stats entry for cpu stats +type CPUStats struct { + CPUUsage struct { + PercpuUsage []uint64 `json:"percpu_usage,omitempty" yaml:"percpu_usage,omitempty" toml:"percpu_usage,omitempty"` + UsageInUsermode uint64 `json:"usage_in_usermode,omitempty" yaml:"usage_in_usermode,omitempty" toml:"usage_in_usermode,omitempty"` + TotalUsage uint64 `json:"total_usage,omitempty" yaml:"total_usage,omitempty" toml:"total_usage,omitempty"` + UsageInKernelmode uint64 `json:"usage_in_kernelmode,omitempty" yaml:"usage_in_kernelmode,omitempty" toml:"usage_in_kernelmode,omitempty"` + } `json:"cpu_usage,omitempty" yaml:"cpu_usage,omitempty" toml:"cpu_usage,omitempty"` + SystemCPUUsage uint64 `json:"system_cpu_usage,omitempty" yaml:"system_cpu_usage,omitempty" toml:"system_cpu_usage,omitempty"` + OnlineCPUs uint64 `json:"online_cpus,omitempty" yaml:"online_cpus,omitempty" toml:"online_cpus,omitempty"` + ThrottlingData struct { + Periods uint64 `json:"periods,omitempty"` + ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` + ThrottledTime uint64 `json:"throttled_time,omitempty"` + } `json:"throttling_data,omitempty" yaml:"throttling_data,omitempty" toml:"throttling_data,omitempty"` +} + +// BlkioStatsEntry is a stats entry for blkio_stats +type BlkioStatsEntry struct { + Major uint64 `json:"major,omitempty" yaml:"major,omitempty" toml:"major,omitempty"` + Minor uint64 `json:"minor,omitempty" yaml:"minor,omitempty" toml:"minor,omitempty"` + Op string `json:"op,omitempty" yaml:"op,omitempty" toml:"op,omitempty"` + Value uint64 `json:"value,omitempty" yaml:"value,omitempty" toml:"value,omitempty"` +} + +// StatsOptions specify parameters to the Stats function. +// +// See https://goo.gl/Dk3Xio for more details. +type StatsOptions struct { + ID string + Stats chan<- *Stats + Stream bool + // A flag that enables stopping the stats operation + Done <-chan bool + // Initial connection timeout + Timeout time.Duration + // Timeout with no data is received, it's reset every time new data + // arrives + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// Stats sends container statistics for the given container to the given channel. +// +// This function is blocking, similar to a streaming call for logs, and should be run +// on a separate goroutine from the caller. Note that this function will block until +// the given container is removed, not just exited. When finished, this function +// will close the given channel. Alternatively, function can be stopped by +// signaling on the Done channel. +// +// See https://goo.gl/Dk3Xio for more details. +func (c *Client) Stats(opts StatsOptions) (retErr error) { + errC := make(chan error, 1) + readCloser, writeCloser := io.Pipe() + + defer func() { + close(opts.Stats) + + if err := <-errC; err != nil && retErr == nil { + retErr = err + } + + if err := readCloser.Close(); err != nil && retErr == nil { + retErr = err + } + }() + + reqSent := make(chan struct{}) + go func() { + defer close(errC) + err := c.stream(http.MethodGet, fmt.Sprintf("/containers/%s/stats?stream=%v", opts.ID, opts.Stream), streamOptions{ + rawJSONStream: true, + useJSONDecoder: true, + stdout: writeCloser, + timeout: opts.Timeout, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + reqSent: reqSent, + }) + if err != nil { + var dockerError *Error + if errors.As(err, &dockerError) { + if dockerError.Status == http.StatusNotFound { + err = &NoSuchContainer{ID: opts.ID} + } + } + } + if closeErr := writeCloser.Close(); closeErr != nil && err == nil { + err = closeErr + } + errC <- err + }() + + quit := make(chan struct{}) + defer close(quit) + go func() { + // block here waiting for the signal to stop function + select { + case <-opts.Done: + readCloser.Close() + case <-quit: + return + } + }() + + decoder := json.NewDecoder(readCloser) + stats := new(Stats) + <-reqSent + for err := decoder.Decode(stats); !errors.Is(err, io.EOF); err = decoder.Decode(stats) { + if err != nil { + return err + } + opts.Stats <- stats + stats = new(Stats) + } + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_stop.go b/vendor/github.com/fsouza/go-dockerclient/container_stop.go new file mode 100644 index 00000000..43d98987 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_stop.go @@ -0,0 +1,42 @@ +package docker + +import ( + "context" + "errors" + "fmt" + "net/http" +) + +// StopContainer stops a container, killing it after the given timeout (in +// seconds). +// +// See https://goo.gl/R9dZcV for more details. +func (c *Client) StopContainer(id string, timeout uint) error { + return c.stopContainer(id, timeout, doOptions{}) +} + +// StopContainerWithContext stops a container, killing it after the given +// timeout (in seconds). The context can be used to cancel the stop +// container request. +// +// See https://goo.gl/R9dZcV for more details. +func (c *Client) StopContainerWithContext(id string, timeout uint, ctx context.Context) error { + return c.stopContainer(id, timeout, doOptions{context: ctx}) +} + +func (c *Client) stopContainer(id string, timeout uint, opts doOptions) error { + path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout) + resp, err := c.do(http.MethodPost, path, opts) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + return err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNotModified { + return &ContainerNotRunning{ID: id} + } + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_top.go b/vendor/github.com/fsouza/go-dockerclient/container_top.go new file mode 100644 index 00000000..0aec655f --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_top.go @@ -0,0 +1,40 @@ +package docker + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" +) + +// TopResult represents the list of processes running in a container, as +// returned by /containers//top. +// +// See https://goo.gl/FLwpPl for more details. +type TopResult struct { + Titles []string + Processes [][]string +} + +// TopContainer returns processes running inside a container +// +// See https://goo.gl/FLwpPl for more details. +func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) { + var args string + var result TopResult + if psArgs != "" { + args = fmt.Sprintf("?ps_args=%s", psArgs) + } + path := fmt.Sprintf("/containers/%s/top%s", id, args) + resp, err := c.do(http.MethodGet, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return result, &NoSuchContainer{ID: id} + } + return result, err + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&result) + return result, err +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_unpause.go b/vendor/github.com/fsouza/go-dockerclient/container_unpause.go new file mode 100644 index 00000000..8f3adc34 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_unpause.go @@ -0,0 +1,24 @@ +package docker + +import ( + "errors" + "fmt" + "net/http" +) + +// UnpauseContainer unpauses the given container. +// +// See https://goo.gl/sZ2faO for more details. +func (c *Client) UnpauseContainer(id string) error { + path := fmt.Sprintf("/containers/%s/unpause", id) + resp, err := c.do(http.MethodPost, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + return err + } + resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_update.go b/vendor/github.com/fsouza/go-dockerclient/container_update.go new file mode 100644 index 00000000..d2116dbd --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_update.go @@ -0,0 +1,43 @@ +package docker + +import ( + "context" + "fmt" + "net/http" +) + +// UpdateContainerOptions specify parameters to the UpdateContainer function. +// +// See https://goo.gl/Y6fXUy for more details. +type UpdateContainerOptions struct { + BlkioWeight int `json:"BlkioWeight"` + CPUShares int `json:"CpuShares"` + CPUPeriod int `json:"CpuPeriod"` + CPURealtimePeriod int64 `json:"CpuRealtimePeriod"` + CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime"` + CPUQuota int `json:"CpuQuota"` + CpusetCpus string `json:"CpusetCpus"` + CpusetMems string `json:"CpusetMems"` + Memory int `json:"Memory"` + MemorySwap int `json:"MemorySwap"` + MemoryReservation int `json:"MemoryReservation"` + KernelMemory int `json:"KernelMemory"` + RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty"` + Context context.Context +} + +// UpdateContainer updates the container at ID with the options +// +// See https://goo.gl/Y6fXUy for more details. +func (c *Client) UpdateContainer(id string, opts UpdateContainerOptions) error { + resp, err := c.do(http.MethodPost, fmt.Sprintf("/containers/%s/update", id), doOptions{ + data: opts, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + return err + } + defer resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container_wait.go b/vendor/github.com/fsouza/go-dockerclient/container_wait.go new file mode 100644 index 00000000..96f0c25f --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container_wait.go @@ -0,0 +1,42 @@ +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" +) + +// WaitContainer blocks until the given container stops, return the exit code +// of the container status. +// +// See https://goo.gl/4AGweZ for more details. +func (c *Client) WaitContainer(id string) (int, error) { + return c.waitContainer(id, doOptions{}) +} + +// WaitContainerWithContext blocks until the given container stops, return the exit code +// of the container status. The context object can be used to cancel the +// inspect request. +// +// See https://goo.gl/4AGweZ for more details. +func (c *Client) WaitContainerWithContext(id string, ctx context.Context) (int, error) { + return c.waitContainer(id, doOptions{context: ctx}) +} + +func (c *Client) waitContainer(id string, opts doOptions) (int, error) { + resp, err := c.do(http.MethodPost, "/containers/"+id+"/wait", opts) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return 0, &NoSuchContainer{ID: id} + } + return 0, err + } + defer resp.Body.Close() + var r struct{ StatusCode int } + if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { + return 0, err + } + return r.StatusCode, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/distribution.go b/vendor/github.com/fsouza/go-dockerclient/distribution.go new file mode 100644 index 00000000..6e5e12f7 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/distribution.go @@ -0,0 +1,27 @@ +// Copyright 2017 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "net/http" + + "github.com/docker/docker/api/types/registry" +) + +// InspectDistribution returns image digest and platform information by contacting the registry +func (c *Client) InspectDistribution(name string) (*registry.DistributionInspect, error) { + path := "/distribution/" + name + "/json" + resp, err := c.do(http.MethodGet, path, doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var distributionInspect registry.DistributionInspect + if err := json.NewDecoder(resp.Body).Decode(&distributionInspect); err != nil { + return nil, err + } + return &distributionInspect, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/env.go b/vendor/github.com/fsouza/go-dockerclient/env.go new file mode 100644 index 00000000..67bb99f5 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/env.go @@ -0,0 +1,172 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package docker + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" +) + +// Env represents a list of key-pair represented in the form KEY=VALUE. +type Env []string + +// Get returns the string value of the given key. +func (env *Env) Get(key string) (value string) { + return env.Map()[key] +} + +// Exists checks whether the given key is defined in the internal Env +// representation. +func (env *Env) Exists(key string) bool { + _, exists := env.Map()[key] + return exists +} + +// GetBool returns a boolean representation of the given key. The key is false +// whenever its value if 0, no, false, none or an empty string. Any other value +// will be interpreted as true. +func (env *Env) GetBool(key string) (value bool) { + s := strings.ToLower(strings.Trim(env.Get(key), " \t")) + if s == "" || s == "0" || s == "no" || s == "false" || s == "none" { + return false + } + return true +} + +// SetBool defines a boolean value to the given key. +func (env *Env) SetBool(key string, value bool) { + if value { + env.Set(key, "1") + } else { + env.Set(key, "0") + } +} + +// GetInt returns the value of the provided key, converted to int. +// +// It the value cannot be represented as an integer, it returns -1. +func (env *Env) GetInt(key string) int { + return int(env.GetInt64(key)) +} + +// SetInt defines an integer value to the given key. +func (env *Env) SetInt(key string, value int) { + env.Set(key, strconv.Itoa(value)) +} + +// GetInt64 returns the value of the provided key, converted to int64. +// +// It the value cannot be represented as an integer, it returns -1. +func (env *Env) GetInt64(key string) int64 { + s := strings.Trim(env.Get(key), " \t") + val, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return -1 + } + return val +} + +// SetInt64 defines an integer (64-bit wide) value to the given key. +func (env *Env) SetInt64(key string, value int64) { + env.Set(key, strconv.FormatInt(value, 10)) +} + +// GetJSON unmarshals the value of the provided key in the provided iface. +// +// iface is a value that can be provided to the json.Unmarshal function. +func (env *Env) GetJSON(key string, iface any) error { + sval := env.Get(key) + if sval == "" { + return nil + } + return json.Unmarshal([]byte(sval), iface) +} + +// SetJSON marshals the given value to JSON format and stores it using the +// provided key. +func (env *Env) SetJSON(key string, value any) error { + sval, err := json.Marshal(value) + if err != nil { + return err + } + env.Set(key, string(sval)) + return nil +} + +// GetList returns a list of strings matching the provided key. It handles the +// list as a JSON representation of a list of strings. +// +// If the given key matches to a single string, it will return a list +// containing only the value that matches the key. +func (env *Env) GetList(key string) []string { + sval := env.Get(key) + if sval == "" { + return nil + } + var l []string + if err := json.Unmarshal([]byte(sval), &l); err != nil { + l = append(l, sval) + } + return l +} + +// SetList stores the given list in the provided key, after serializing it to +// JSON format. +func (env *Env) SetList(key string, value []string) error { + return env.SetJSON(key, value) +} + +// Set defines the value of a key to the given string. +func (env *Env) Set(key, value string) { + *env = append(*env, key+"="+value) +} + +// Decode decodes `src` as a json dictionary, and adds each decoded key-value +// pair to the environment. +// +// If `src` cannot be decoded as a json dictionary, an error is returned. +func (env *Env) Decode(src io.Reader) error { + m := make(map[string]any) + if err := json.NewDecoder(src).Decode(&m); err != nil { + return err + } + for k, v := range m { + env.SetAuto(k, v) + } + return nil +} + +// SetAuto will try to define the Set* method to call based on the given value. +func (env *Env) SetAuto(key string, value any) { + if fval, ok := value.(float64); ok { + env.SetInt64(key, int64(fval)) + } else if sval, ok := value.(string); ok { + env.Set(key, sval) + } else if val, err := json.Marshal(value); err == nil { + env.Set(key, string(val)) + } else { + env.Set(key, fmt.Sprintf("%v", value)) + } +} + +// Map returns the map representation of the env. +func (env *Env) Map() map[string]string { + if env == nil || len(*env) == 0 { + return nil + } + m := make(map[string]string) + for _, kv := range *env { + parts := strings.SplitN(kv, "=", 2) + if len(parts) == 1 { + m[parts[0]] = "" + } else { + m[parts[0]] = parts[1] + } + } + return m +} diff --git a/vendor/github.com/fsouza/go-dockerclient/event.go b/vendor/github.com/fsouza/go-dockerclient/event.go new file mode 100644 index 00000000..22d64f6a --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/event.go @@ -0,0 +1,465 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "errors" + "io" + "math" + "net" + "net/http" + "net/http/httputil" + "strconv" + "sync" + "sync/atomic" + "time" +) + +// EventsOptions to filter events +// See https://docs.docker.com/engine/api/v1.41/#operation/SystemEvents for more details. +type EventsOptions struct { + // Show events created since this timestamp then stream new events. + Since string + + // Show events created until this timestamp then stop streaming. + Until string + + // Filter for events. For example: + // map[string][]string{"type": {"container"}, "event": {"start", "die"}} + // will return events when container was started and stopped or killed + // + // Available filters: + // config= config name or ID + // container= container name or ID + // daemon= daemon name or ID + // event= event type + // image= image name or ID + // label= image or container label + // network= network name or ID + // node= node ID + // plugin= plugin name or ID + // scope= local or swarm + // secret= secret name or ID + // service= service name or ID + // type= container, image, volume, network, daemon, plugin, node, service, secret or config + // volume= volume name + Filters map[string][]string +} + +// APIEvents represents events coming from the Docker API +// The fields in the Docker API changed in API version 1.22, and +// events for more than images and containers are now fired off. +// To maintain forward and backward compatibility, go-dockerclient +// replicates the event in both the new and old format as faithfully as possible. +// +// For events that only exist in 1.22 in later, `Status` is filled in as +// `"Type:Action"` instead of just `Action` to allow for older clients to +// differentiate and not break if they rely on the pre-1.22 Status types. +// +// The transformEvent method can be consulted for more information about how +// events are translated from new/old API formats +type APIEvents struct { + // New API Fields in 1.22 + Action string `json:"action,omitempty"` + Type string `json:"type,omitempty"` + Actor APIActor `json:"actor,omitempty"` + + // Old API fields for < 1.22 + Status string `json:"status,omitempty"` + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + + // Fields in both + Time int64 `json:"time,omitempty"` + TimeNano int64 `json:"timeNano,omitempty"` +} + +// APIActor represents an actor that accomplishes something for an event +type APIActor struct { + ID string `json:"id,omitempty"` + Attributes map[string]string `json:"attributes,omitempty"` +} + +type eventMonitoringState struct { + // `sync/atomic` expects the first word in an allocated struct to be 64-bit + // aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details. + lastSeen int64 + sync.RWMutex + sync.WaitGroup + enabled bool + C chan *APIEvents + errC chan error + listeners []chan<- *APIEvents + closeConn func() +} + +const ( + maxMonitorConnRetries = 5 + retryInitialWaitTime = 10. +) + +var ( + // ErrNoListeners is the error returned when no listeners are available + // to receive an event. + ErrNoListeners = errors.New("no listeners present to receive event") + + // ErrListenerAlreadyExists is the error returned when the listerner already + // exists. + ErrListenerAlreadyExists = errors.New("listener already exists for docker events") + + // ErrTLSNotSupported is the error returned when the client does not support + // TLS (this applies to the Windows named pipe client). + ErrTLSNotSupported = errors.New("tls not supported by this client") + + // EOFEvent is sent when the event listener receives an EOF error. + EOFEvent = &APIEvents{ + Type: "EOF", + Status: "EOF", + } +) + +// AddEventListener adds a new listener to container events in the Docker API. +// +// The parameter is a channel through which events will be sent. +func (c *Client) AddEventListener(listener chan<- *APIEvents) error { + return c.AddEventListenerWithOptions(EventsOptions{}, listener) +} + +// AddEventListener adds a new listener to container events in the Docker API. +// See https://docs.docker.com/engine/api/v1.41/#operation/SystemEvents for more details. +// +// The listener parameter is a channel through which events will be sent. +func (c *Client) AddEventListenerWithOptions(options EventsOptions, listener chan<- *APIEvents) error { + var err error + if !c.eventMonitor.isEnabled() { + err = c.eventMonitor.enableEventMonitoring(c, options) + if err != nil { + return err + } + } + return c.eventMonitor.addListener(listener) +} + +// RemoveEventListener removes a listener from the monitor. +func (c *Client) RemoveEventListener(listener chan *APIEvents) error { + err := c.eventMonitor.removeListener(listener) + if err != nil { + return err + } + if c.eventMonitor.listernersCount() == 0 { + c.eventMonitor.disableEventMonitoring() + } + return nil +} + +func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error { + eventState.Lock() + defer eventState.Unlock() + if listenerExists(listener, &eventState.listeners) { + return ErrListenerAlreadyExists + } + eventState.Add(1) + eventState.listeners = append(eventState.listeners, listener) + return nil +} + +func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error { + eventState.Lock() + defer eventState.Unlock() + if listenerExists(listener, &eventState.listeners) { + var newListeners []chan<- *APIEvents + for _, l := range eventState.listeners { + if l != listener { + newListeners = append(newListeners, l) + } + } + eventState.listeners = newListeners + eventState.Add(-1) + } + return nil +} + +func (eventState *eventMonitoringState) closeListeners() { + for _, l := range eventState.listeners { + close(l) + eventState.Add(-1) + } + eventState.listeners = nil +} + +func (eventState *eventMonitoringState) listernersCount() int { + eventState.RLock() + defer eventState.RUnlock() + return len(eventState.listeners) +} + +func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool { + for _, b := range *list { + if b == a { + return true + } + } + return false +} + +func (eventState *eventMonitoringState) enableEventMonitoring(c *Client, opts EventsOptions) error { + eventState.Lock() + defer eventState.Unlock() + if !eventState.enabled { + eventState.enabled = true + atomic.StoreInt64(&eventState.lastSeen, 0) + eventState.C = make(chan *APIEvents, 100) + eventState.errC = make(chan error, 1) + go eventState.monitorEvents(c, opts) + } + return nil +} + +func (eventState *eventMonitoringState) disableEventMonitoring() { + eventState.Lock() + defer eventState.Unlock() + + eventState.closeListeners() + + eventState.Wait() + + if eventState.enabled { + eventState.enabled = false + close(eventState.C) + close(eventState.errC) + + if eventState.closeConn != nil { + eventState.closeConn() + eventState.closeConn = nil + } + } +} + +func (eventState *eventMonitoringState) monitorEvents(c *Client, opts EventsOptions) { + const ( + noListenersTimeout = 5 * time.Second + noListenersInterval = 10 * time.Millisecond + noListenersMaxTries = noListenersTimeout / noListenersInterval + ) + + var err error + for i := time.Duration(0); i < noListenersMaxTries && eventState.noListeners(); i++ { + time.Sleep(10 * time.Millisecond) + } + + if eventState.noListeners() { + // terminate if no listener is available after 5 seconds. + // Prevents goroutine leak when RemoveEventListener is called + // right after AddEventListener. + eventState.disableEventMonitoring() + return + } + + if err = eventState.connectWithRetry(c, opts); err != nil { + // terminate if connect failed + eventState.disableEventMonitoring() + return + } + for eventState.isEnabled() { + timeout := time.After(100 * time.Millisecond) + select { + case ev, ok := <-eventState.C: + if !ok { + return + } + if ev == EOFEvent { + go eventState.disableEventMonitoring() + return + } + go func(ev *APIEvents) { + eventState.updateLastSeen(ev) + eventState.sendEvent(ev) + }(ev) + case err = <-eventState.errC: + if errors.Is(err, ErrNoListeners) { + eventState.disableEventMonitoring() + return + } else if err != nil { + defer func() { go eventState.monitorEvents(c, opts) }() + return + } + case <-timeout: + continue + } + } +} + +func (eventState *eventMonitoringState) connectWithRetry(c *Client, opts EventsOptions) error { + var retries int + eventState.RLock() + eventChan := eventState.C + errChan := eventState.errC + eventState.RUnlock() + closeConn, err := c.eventHijack(opts, atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan) + for ; err != nil && retries < maxMonitorConnRetries; retries++ { + waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries))) + time.Sleep(time.Duration(waitTime) * time.Millisecond) + eventState.RLock() + eventChan = eventState.C + errChan = eventState.errC + eventState.RUnlock() + closeConn, err = c.eventHijack(opts, atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan) + } + eventState.Lock() + defer eventState.Unlock() + eventState.closeConn = closeConn + return err +} + +func (eventState *eventMonitoringState) noListeners() bool { + eventState.RLock() + defer eventState.RUnlock() + return len(eventState.listeners) == 0 +} + +func (eventState *eventMonitoringState) isEnabled() bool { + eventState.RLock() + defer eventState.RUnlock() + return eventState.enabled +} + +func (eventState *eventMonitoringState) sendEvent(event *APIEvents) { + eventState.RLock() + defer eventState.RUnlock() + eventState.Add(1) + defer eventState.Done() + if eventState.enabled { + if len(eventState.listeners) == 0 { + eventState.errC <- ErrNoListeners + return + } + + for _, listener := range eventState.listeners { + select { + case listener <- event: + default: + } + } + } +} + +func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) { + eventState.Lock() + defer eventState.Unlock() + if atomic.LoadInt64(&eventState.lastSeen) < e.Time { + atomic.StoreInt64(&eventState.lastSeen, e.Time) + } +} + +func (c *Client) eventHijack(opts EventsOptions, startTime int64, eventChan chan *APIEvents, errChan chan error) (closeConn func(), err error) { + // on reconnect override initial Since with last event seen time + if startTime != 0 { + opts.Since = strconv.FormatInt(startTime, 10) + } + uri := "/events?" + queryString(opts) + protocol := c.endpointURL.Scheme + address := c.endpointURL.Path + if protocol != "unix" && protocol != "npipe" { + protocol = "tcp" + address = c.endpointURL.Host + } + var dial net.Conn + if c.TLSConfig == nil { + dial, err = c.Dialer.Dial(protocol, address) + } else { + netDialer, ok := c.Dialer.(*net.Dialer) + if !ok { + return nil, ErrTLSNotSupported + } + dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig) + } + if err != nil { + return nil, err + } + //lint:ignore SA1019 the alternative doesn't quite work, so keep using the deprecated thing. + conn := httputil.NewClientConn(dial, nil) + req, err := http.NewRequest(http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + res, err := conn.Do(req) + if err != nil { + return nil, err + } + + keepRunning := int32(1) + //lint:ignore SA1019 the alternative doesn't quite work, so keep using the deprecated thing. + go func(res *http.Response, conn *httputil.ClientConn) { + defer conn.Close() + defer res.Body.Close() + decoder := json.NewDecoder(res.Body) + for atomic.LoadInt32(&keepRunning) == 1 { + var event APIEvents + if err := decoder.Decode(&event); err != nil { + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { + c.eventMonitor.RLock() + if c.eventMonitor.enabled && c.eventMonitor.C == eventChan { + // Signal that we're exiting. + eventChan <- EOFEvent + } + c.eventMonitor.RUnlock() + break + } + errChan <- err + } + if event.Time == 0 { + continue + } + transformEvent(&event) + c.eventMonitor.RLock() + if c.eventMonitor.enabled && c.eventMonitor.C == eventChan { + eventChan <- &event + } + c.eventMonitor.RUnlock() + } + }(res, conn) + return func() { + atomic.StoreInt32(&keepRunning, 0) + }, nil +} + +// transformEvent takes an event and determines what version it is from +// then populates both versions of the event +func transformEvent(event *APIEvents) { + // if event version is <= 1.21 there will be no Action and no Type + if event.Action == "" && event.Type == "" { + event.Action = event.Status + event.Actor.ID = event.ID + event.Actor.Attributes = map[string]string{} + switch event.Status { + case "delete", "import", "pull", "push", "tag", "untag": + event.Type = "image" + default: + event.Type = "container" + if event.From != "" { + event.Actor.Attributes["image"] = event.From + } + } + } else { + if event.Status == "" { + if event.Type == "image" || event.Type == "container" { + event.Status = event.Action + } else { + // Because just the Status has been overloaded with different Types + // if an event is not for an image or a container, we prepend the type + // to avoid problems for people relying on actions being only for + // images and containers + event.Status = event.Type + ":" + event.Action + } + } + if event.ID == "" { + event.ID = event.Actor.ID + } + if event.From == "" { + event.From = event.Actor.Attributes["image"] + } + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/exec.go b/vendor/github.com/fsouza/go-dockerclient/exec.go new file mode 100644 index 00000000..c8399b0b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/exec.go @@ -0,0 +1,224 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" +) + +// Exec is the type representing a `docker exec` instance and containing the +// instance ID +type Exec struct { + ID string `json:"Id,omitempty" yaml:"Id,omitempty"` +} + +// CreateExecOptions specify parameters to the CreateExecContainer function. +// +// See https://goo.gl/60TeBP for more details +type CreateExecOptions struct { + Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` + Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty" toml:"Cmd,omitempty"` + Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"` + User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` + WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty" toml:"WorkingDir,omitempty"` + DetachKeys string `json:"DetachKeys,omitempty" yaml:"DetachKeys,omitempty" toml:"DetachKeys,omitempty"` + Context context.Context `json:"-"` + AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"` + AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"` + AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"` + Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` + Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"` +} + +// CreateExec sets up an exec instance in a running container `id`, returning the exec +// instance, or an error in case of failure. +// +// See https://goo.gl/60TeBP for more details +func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { + if c.serverAPIVersion == nil { + c.checkAPIVersion() + } + if len(opts.Env) > 0 && c.serverAPIVersion.LessThan(apiVersion125) { + return nil, errors.New("exec configuration Env is only supported in API#1.25 and above") + } + if len(opts.WorkingDir) > 0 && c.serverAPIVersion.LessThan(apiVersion135) { + return nil, errors.New("exec configuration WorkingDir is only supported in API#1.35 and above") + } + path := fmt.Sprintf("/containers/%s/exec", opts.Container) + resp, err := c.do(http.MethodPost, path, doOptions{data: opts, context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: opts.Container} + } + return nil, err + } + defer resp.Body.Close() + var exec Exec + if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { + return nil, err + } + + return &exec, nil +} + +// StartExecOptions specify parameters to the StartExecContainer function. +// +// See https://goo.gl/1EeDWi for more details +type StartExecOptions struct { + InputStream io.Reader `qs:"-"` + OutputStream io.Writer `qs:"-"` + ErrorStream io.Writer `qs:"-"` + + Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty" toml:"Detach,omitempty"` + Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` + + // Use raw terminal? Usually true when the container contains a TTY. + RawTerminal bool `qs:"-"` + + // If set, after a successful connect, a sentinel will be sent and then the + // client will block on receive before continuing. + // + // It must be an unbuffered channel. Using a buffered channel can lead + // to unexpected behavior. + Success chan struct{} `json:"-"` + + Context context.Context `json:"-"` +} + +// StartExec starts a previously set up exec instance id. If opts.Detach is +// true, it returns after starting the exec command. Otherwise, it sets up an +// interactive session with the exec command. +// +// See https://goo.gl/1EeDWi for more details +func (c *Client) StartExec(id string, opts StartExecOptions) error { + cw, err := c.StartExecNonBlocking(id, opts) + if err != nil { + return err + } + if cw != nil { + return cw.Wait() + } + return nil +} + +// StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is +// true, it returns after starting the exec command. Otherwise, it sets up an +// interactive session with the exec command. +// +// See https://goo.gl/1EeDWi for more details +func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) { + if id == "" { + return nil, &NoSuchExec{ID: id} + } + + path := fmt.Sprintf("/exec/%s/start", id) + + if opts.Detach { + resp, err := c.do(http.MethodPost, path, doOptions{data: opts, context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchExec{ID: id} + } + return nil, err + } + defer resp.Body.Close() + return nil, nil + } + + return c.hijack(http.MethodPost, path, hijackOptions{ + success: opts.Success, + setRawTerminal: opts.RawTerminal, + in: opts.InputStream, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + data: opts, + }) +} + +// ResizeExecTTY resizes the tty session used by the exec command id. This API +// is valid only if Tty was specified as part of creating and starting the exec +// command. +// +// See https://goo.gl/Mo5bxx for more details +func (c *Client) ResizeExecTTY(id string, height, width int) error { + params := make(url.Values) + params.Set("h", strconv.Itoa(height)) + params.Set("w", strconv.Itoa(width)) + + path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode()) + resp, err := c.do(http.MethodPost, path, doOptions{}) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + +// ExecProcessConfig is a type describing the command associated to a Exec +// instance. It's used in the ExecInspect type. +type ExecProcessConfig struct { + User string `json:"user,omitempty" yaml:"user,omitempty" toml:"user,omitempty"` + Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty" toml:"privileged,omitempty"` + Tty bool `json:"tty,omitempty" yaml:"tty,omitempty" toml:"tty,omitempty"` + EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty" toml:"entrypoint,omitempty"` + Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty" toml:"arguments,omitempty"` +} + +// ExecInspect is a type with details about a exec instance, including the +// exit code if the command has finished running. It's returned by a api +// call to /exec/(id)/json +// +// See https://goo.gl/ctMUiW for more details +type ExecInspect struct { + ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"` + ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` + ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty" toml:"ProcessConfig,omitempty"` + ContainerID string `json:"ContainerID,omitempty" yaml:"ContainerID,omitempty" toml:"ContainerID,omitempty"` + DetachKeys string `json:"DetachKeys,omitempty" yaml:"DetachKeys,omitempty" toml:"DetachKeys,omitempty"` + Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"` + OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"` + OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty" toml:"OpenStderr,omitempty"` + OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty" toml:"OpenStdout,omitempty"` + CanRemove bool `json:"CanRemove,omitempty" yaml:"CanRemove,omitempty" toml:"CanRemove,omitempty"` +} + +// InspectExec returns low-level information about the exec command id. +// +// See https://goo.gl/ctMUiW for more details +func (c *Client) InspectExec(id string) (*ExecInspect, error) { + path := fmt.Sprintf("/exec/%s/json", id) + resp, err := c.do(http.MethodGet, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchExec{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var exec ExecInspect + if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { + return nil, err + } + return &exec, nil +} + +// NoSuchExec is the error returned when a given exec instance does not exist. +type NoSuchExec struct { + ID string +} + +func (err *NoSuchExec) Error() string { + return "No such exec instance: " + err.ID +} diff --git a/vendor/github.com/fsouza/go-dockerclient/image.go b/vendor/github.com/fsouza/go-dockerclient/image.go new file mode 100644 index 00000000..614908c6 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/image.go @@ -0,0 +1,770 @@ +// Copyright 2013 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strings" + "time" +) + +// APIImages represent an image returned in the ListImages call. +type APIImages struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"` + Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` + Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"` + VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"` + ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty" toml:"ParentId,omitempty"` + RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` +} + +// RootFS represents the underlying layers used by an image +type RootFS struct { + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` + Layers []string `json:"Layers,omitempty" yaml:"Layers,omitempty" toml:"Layers,omitempty"` +} + +// Image is the type representing a docker image and its various properties +type Image struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"` + Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty" toml:"Parent,omitempty"` + Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty" toml:"Comment,omitempty"` + Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` + Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"` + ContainerConfig Config `json:"ContainerConfig,omitempty" yaml:"ContainerConfig,omitempty" toml:"ContainerConfig,omitempty"` + DockerVersion string `json:"DockerVersion,omitempty" yaml:"DockerVersion,omitempty" toml:"DockerVersion,omitempty"` + Author string `json:"Author,omitempty" yaml:"Author,omitempty" toml:"Author,omitempty"` + Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` + Architecture string `json:"Architecture,omitempty" yaml:"Architecture,omitempty"` + Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"` + VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"` + RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"` + RootFS *RootFS `json:"RootFS,omitempty" yaml:"RootFS,omitempty" toml:"RootFS,omitempty"` + OS string `json:"Os,omitempty" yaml:"Os,omitempty" toml:"Os,omitempty"` +} + +// ImagePre012 serves the same purpose as the Image type except that it is for +// earlier versions of the Docker API (pre-012 to be specific) +type ImagePre012 struct { + ID string `json:"id"` + Parent string `json:"parent,omitempty"` + Comment string `json:"comment,omitempty"` + Created time.Time `json:"created"` + Container string `json:"container,omitempty"` + ContainerConfig Config `json:"container_config,omitempty"` + DockerVersion string `json:"docker_version,omitempty"` + Author string `json:"author,omitempty"` + Config *Config `json:"config,omitempty"` + Architecture string `json:"architecture,omitempty"` + Size int64 `json:"size,omitempty"` +} + +var ( + // ErrNoSuchImage is the error returned when the image does not exist. + ErrNoSuchImage = errors.New("no such image") + + // ErrMissingRepo is the error returned when the remote repository is + // missing. + ErrMissingRepo = errors.New("missing remote repository e.g. 'github.com/user/repo'") + + // ErrMissingOutputStream is the error returned when no output stream + // is provided to some calls, like BuildImage. + ErrMissingOutputStream = errors.New("missing output stream") + + // ErrMultipleContexts is the error returned when both a ContextDir and + // InputStream are provided in BuildImageOptions + ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream") + + // ErrMustSpecifyNames is the error returned when the Names field on + // ExportImagesOptions is nil or empty + ErrMustSpecifyNames = errors.New("must specify at least one name to export") +) + +// ListImagesOptions specify parameters to the ListImages function. +// +// See https://goo.gl/BVzauZ for more details. +type ListImagesOptions struct { + Filters map[string][]string + All bool + Digests bool + Filter string + Context context.Context +} + +// ListImages returns the list of available images in the server. +// +// See https://goo.gl/BVzauZ for more details. +func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { + path := "/images/json?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var images []APIImages + if err := json.NewDecoder(resp.Body).Decode(&images); err != nil { + return nil, err + } + return images, nil +} + +// ImageHistory represent a layer in an image's history returned by the +// ImageHistory call. +type ImageHistory struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty" toml:"Tags,omitempty"` + Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Tags,omitempty"` + CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty" toml:"CreatedBy,omitempty"` + Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"` + Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty" toml:"Comment,omitempty"` +} + +// ImageHistory returns the history of the image by its name or ID. +// +// See https://goo.gl/fYtxQa for more details. +func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { + resp, err := c.do(http.MethodGet, "/images/"+name+"/history", doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, ErrNoSuchImage + } + return nil, err + } + defer resp.Body.Close() + var history []ImageHistory + if err := json.NewDecoder(resp.Body).Decode(&history); err != nil { + return nil, err + } + return history, nil +} + +// RemoveImage removes an image by its name or ID. +// +// See https://goo.gl/Vd2Pck for more details. +func (c *Client) RemoveImage(name string) error { + resp, err := c.do(http.MethodDelete, "/images/"+name, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return ErrNoSuchImage + } + return err + } + resp.Body.Close() + return nil +} + +// RemoveImageOptions present the set of options available for removing an image +// from a registry. +// +// See https://goo.gl/Vd2Pck for more details. +type RemoveImageOptions struct { + Force bool `qs:"force"` + NoPrune bool `qs:"noprune"` + Context context.Context +} + +// RemoveImageExtended removes an image by its name or ID. +// Extra params can be passed, see RemoveImageOptions +// +// See https://goo.gl/Vd2Pck for more details. +func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error { + uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts)) + resp, err := c.do(http.MethodDelete, uri, doOptions{context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return ErrNoSuchImage + } + return err + } + resp.Body.Close() + return nil +} + +// InspectImage returns an image by its name or ID. +// +// See https://goo.gl/ncLTG8 for more details. +func (c *Client) InspectImage(name string) (*Image, error) { + resp, err := c.do(http.MethodGet, "/images/"+name+"/json", doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, ErrNoSuchImage + } + return nil, err + } + defer resp.Body.Close() + + var image Image + + // if the caller elected to skip checking the server's version, assume it's the latest + if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) { + if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { + return nil, err + } + } else { + var imagePre012 ImagePre012 + if err := json.NewDecoder(resp.Body).Decode(&imagePre012); err != nil { + return nil, err + } + + image.ID = imagePre012.ID + image.Parent = imagePre012.Parent + image.Comment = imagePre012.Comment + image.Created = imagePre012.Created + image.Container = imagePre012.Container + image.ContainerConfig = imagePre012.ContainerConfig + image.DockerVersion = imagePre012.DockerVersion + image.Author = imagePre012.Author + image.Config = imagePre012.Config + image.Architecture = imagePre012.Architecture + image.Size = imagePre012.Size + } + + return &image, nil +} + +// PushImageOptions represents options to use in the PushImage method. +// +// See https://goo.gl/BZemGg for more details. +type PushImageOptions struct { + // Name of the image + Name string + + // Tag of the image + Tag string + + // Registry server to push the image + Registry string + + OutputStream io.Writer `qs:"-"` + RawJSONStream bool `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + + Context context.Context +} + +// PushImage pushes an image to a remote registry, logging progress to w. +// +// An empty instance of AuthConfiguration may be used for unauthenticated +// pushes. +// +// See https://goo.gl/BZemGg for more details. +func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error { + if opts.Name == "" { + return ErrNoSuchImage + } + headers, err := headersWithAuth(auth) + if err != nil { + return err + } + name := opts.Name + opts.Name = "" + path := "/images/" + name + "/push?" + queryString(&opts) + return c.stream(http.MethodPost, path, streamOptions{ + setRawTerminal: true, + rawJSONStream: opts.RawJSONStream, + headers: headers, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} + +// PullImageOptions present the set of options available for pulling an image +// from a registry. +// +// See https://goo.gl/qkoSsn for more details. +type PullImageOptions struct { + All bool + Repository string `qs:"fromImage"` + Tag string + Platform string `ver:"1.32"` + + // Only required for Docker Engine 1.9 or 1.10 w/ Remote API < 1.21 + // and Docker Engine < 1.9 + // This parameter was removed in Docker Engine 1.11 + Registry string + + OutputStream io.Writer `qs:"-"` + RawJSONStream bool `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// PullImage pulls an image from a remote registry, logging progress to +// opts.OutputStream. +// +// See https://goo.gl/qkoSsn for more details. +func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error { + if opts.Repository == "" { + return ErrNoSuchImage + } + + headers, err := headersWithAuth(auth) + if err != nil { + return err + } + if opts.Tag == "" && strings.Contains(opts.Repository, "@") { + parts := strings.SplitN(opts.Repository, "@", 2) + opts.Repository = parts[0] + opts.Tag = parts[1] + } + return c.createImage(&opts, headers, nil, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context) +} + +func (c *Client) createImage(opts any, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool, timeout time.Duration, context context.Context) error { + url, err := c.getPath("/images/create", opts) + if err != nil { + return err + } + return c.streamURL(http.MethodPost, url, streamOptions{ + setRawTerminal: true, + headers: headers, + in: in, + stdout: w, + rawJSONStream: rawJSONStream, + inactivityTimeout: timeout, + context: context, + }) +} + +// LoadImageOptions represents the options for LoadImage Docker API Call +// +// See https://goo.gl/rEsBV3 for more details. +type LoadImageOptions struct { + InputStream io.Reader + OutputStream io.Writer + Context context.Context +} + +// LoadImage imports a tarball docker image +// +// See https://goo.gl/rEsBV3 for more details. +func (c *Client) LoadImage(opts LoadImageOptions) error { + return c.stream(http.MethodPost, "/images/load", streamOptions{ + setRawTerminal: true, + in: opts.InputStream, + stdout: opts.OutputStream, + context: opts.Context, + }) +} + +// ExportImageOptions represent the options for ExportImage Docker API call. +// +// See https://goo.gl/AuySaA for more details. +type ExportImageOptions struct { + Name string + OutputStream io.Writer + InactivityTimeout time.Duration + Context context.Context +} + +// ExportImage exports an image (as a tar file) into the stream. +// +// See https://goo.gl/AuySaA for more details. +func (c *Client) ExportImage(opts ExportImageOptions) error { + return c.stream(http.MethodGet, fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} + +// ExportImagesOptions represent the options for ExportImages Docker API call +// +// See https://goo.gl/N9XlDn for more details. +type ExportImagesOptions struct { + Names []string + OutputStream io.Writer `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// ExportImages exports one or more images (as a tar file) into the stream +// +// See https://goo.gl/N9XlDn for more details. +func (c *Client) ExportImages(opts ExportImagesOptions) error { + if len(opts.Names) == 0 { + return ErrMustSpecifyNames + } + // API < 1.25 allows multiple name values + // 1.25 says name must be a comma separated list + var err error + var exporturl string + if c.requestedAPIVersion.GreaterThanOrEqualTo(apiVersion125) { + str := opts.Names[0] + for _, val := range opts.Names[1:] { + str += "," + val + } + exporturl, err = c.getPath("/images/get", ExportImagesOptions{ + Names: []string{str}, + OutputStream: opts.OutputStream, + InactivityTimeout: opts.InactivityTimeout, + Context: opts.Context, + }) + } else { + exporturl, err = c.getPath("/images/get", &opts) + } + if err != nil { + return err + } + return c.streamURL(http.MethodGet, exporturl, streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + }) +} + +// ImportImageOptions present the set of informations available for importing +// an image from a source file or the stdin. +// +// See https://goo.gl/qkoSsn for more details. +type ImportImageOptions struct { + Repository string `qs:"repo"` + Source string `qs:"fromSrc"` + Tag string `qs:"tag"` + + InputStream io.Reader `qs:"-"` + OutputStream io.Writer `qs:"-"` + RawJSONStream bool `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// ImportImage imports an image from a url, a file or stdin +// +// See https://goo.gl/qkoSsn for more details. +func (c *Client) ImportImage(opts ImportImageOptions) error { + if opts.Repository == "" { + return ErrNoSuchImage + } + if opts.Source != "-" { + opts.InputStream = nil + } + if opts.Source != "-" && !isURL(opts.Source) { + f, err := os.Open(opts.Source) + if err != nil { + return err + } + opts.InputStream = f + opts.Source = "-" + } + return c.createImage(&opts, nil, opts.InputStream, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context) +} + +// BuilderVersion represents either the BuildKit or V1 ("classic") builder. +type BuilderVersion string + +const ( + BuilderV1 BuilderVersion = "1" + BuilderBuildKit BuilderVersion = "2" +) + +// BuildImageOptions present the set of informations available for building an +// image from a tarfile with a Dockerfile in it. +// +// For more details about the Docker building process, see +// https://goo.gl/4nYHwV. +type BuildImageOptions struct { + Context context.Context + Name string `qs:"t"` + Dockerfile string `ver:"1.25"` + ExtraHosts string `ver:"1.28"` + CacheFrom []string `qs:"-" ver:"1.25"` + Memory int64 + Memswap int64 + ShmSize int64 + CPUShares int64 + CPUQuota int64 `ver:"1.21"` + CPUPeriod int64 `ver:"1.21"` + CPUSetCPUs string + Labels map[string]string + InputStream io.Reader `qs:"-"` + OutputStream io.Writer `qs:"-"` + Remote string + Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header + AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header + ContextDir string `qs:"-"` + Ulimits []ULimit `qs:"-" ver:"1.18"` + BuildArgs []BuildArg `qs:"-" ver:"1.21"` + NetworkMode string `ver:"1.25"` + Platform string `ver:"1.32"` + InactivityTimeout time.Duration `qs:"-"` + CgroupParent string + SecurityOpt []string + Target string + Outputs string `ver:"1.40"` + NoCache bool + SuppressOutput bool `qs:"q"` + Pull bool `ver:"1.16"` + RmTmpContainer bool `qs:"rm"` + ForceRmTmpContainer bool `qs:"forcerm" ver:"1.12"` + RawJSONStream bool `qs:"-"` + Version BuilderVersion `qs:"version" ver:"1.39"` +} + +// BuildArg represents arguments that can be passed to the image when building +// it from a Dockerfile. +// +// For more details about the Docker building process, see +// https://goo.gl/4nYHwV. +type BuildArg struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// BuildImage builds an image from a tarball's url or a Dockerfile in the input +// stream. +// +// See https://goo.gl/4nYHwV for more details. +func (c *Client) BuildImage(opts BuildImageOptions) error { + if opts.OutputStream == nil { + return ErrMissingOutputStream + } + headers, err := headersWithAuth(opts.Auth, c.versionedAuthConfigs(opts.AuthConfigs)) + if err != nil { + return err + } + + if opts.Remote != "" && opts.Name == "" { + opts.Name = opts.Remote + } + if opts.InputStream != nil || opts.ContextDir != "" { + headers["Content-Type"] = "application/tar" + } else if opts.Remote == "" { + return ErrMissingRepo + } + if opts.ContextDir != "" { + if opts.InputStream != nil { + return ErrMultipleContexts + } + var err error + if opts.InputStream, err = createTarStream(opts.ContextDir, opts.Dockerfile); err != nil { + return err + } + } + qs, ver := queryStringVersion(&opts) + + if len(opts.CacheFrom) > 0 { + if b, err := json.Marshal(opts.CacheFrom); err == nil { + item := url.Values(map[string][]string{}) + item.Add("cachefrom", string(b)) + qs = fmt.Sprintf("%s&%s", qs, item.Encode()) + if ver == nil || apiVersion125.GreaterThan(ver) { + ver = apiVersion125 + } + } + } + + if len(opts.Ulimits) > 0 { + if b, err := json.Marshal(opts.Ulimits); err == nil { + item := url.Values(map[string][]string{}) + item.Add("ulimits", string(b)) + qs = fmt.Sprintf("%s&%s", qs, item.Encode()) + if ver == nil || apiVersion118.GreaterThan(ver) { + ver = apiVersion118 + } + } + } + + if len(opts.BuildArgs) > 0 { + v := make(map[string]string) + for _, arg := range opts.BuildArgs { + v[arg.Name] = arg.Value + } + if b, err := json.Marshal(v); err == nil { + item := url.Values(map[string][]string{}) + item.Add("buildargs", string(b)) + qs = fmt.Sprintf("%s&%s", qs, item.Encode()) + if ver == nil || apiVersion121.GreaterThan(ver) { + ver = apiVersion121 + } + } + } + + buildURL, err := c.pathVersionCheck("/build", qs, ver) + if err != nil { + return err + } + + return c.streamURL(http.MethodPost, buildURL, streamOptions{ + setRawTerminal: true, + rawJSONStream: opts.RawJSONStream, + headers: headers, + in: opts.InputStream, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} + +func (c *Client) versionedAuthConfigs(authConfigs AuthConfigurations) registryAuth { + if c.serverAPIVersion == nil { + c.checkAPIVersion() + } + if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion119) { + return AuthConfigurations119(authConfigs.Configs) + } + return authConfigs +} + +// TagImageOptions present the set of options to tag an image. +// +// See https://goo.gl/prHrvo for more details. +type TagImageOptions struct { + Repo string + Tag string + Force bool + Context context.Context +} + +// TagImage adds a tag to the image identified by the given name. +// +// See https://goo.gl/prHrvo for more details. +func (c *Client) TagImage(name string, opts TagImageOptions) error { + if name == "" { + return ErrNoSuchImage + } + resp, err := c.do(http.MethodPost, "/images/"+name+"/tag?"+queryString(&opts), doOptions{ + context: opts.Context, + }) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return ErrNoSuchImage + } + + return err +} + +func isURL(u string) bool { + p, err := url.Parse(u) + if err != nil { + return false + } + return p.Scheme == "http" || p.Scheme == "https" +} + +func headersWithAuth(auths ...registryAuth) (map[string]string, error) { + headers := make(map[string]string) + + for _, auth := range auths { + if auth.isEmpty() { + continue + } + data, err := json.Marshal(auth) + if err != nil { + return nil, err + } + headers[auth.headerKey()] = base64.URLEncoding.EncodeToString(data) + } + + return headers, nil +} + +// APIImageSearch reflect the result of a search on the Docker Hub. +// +// See https://goo.gl/KLO9IZ for more details. +type APIImageSearch struct { + Description string `json:"description,omitempty" yaml:"description,omitempty" toml:"description,omitempty"` + IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty" toml:"is_official,omitempty"` + IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty" toml:"is_automated,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty" toml:"name,omitempty"` + StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty" toml:"star_count,omitempty"` +} + +// SearchImages search the docker hub with a specific given term. +// +// See https://goo.gl/KLO9IZ for more details. +func (c *Client) SearchImages(term string) ([]APIImageSearch, error) { + resp, err := c.do(http.MethodGet, "/images/search?term="+term, doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var searchResult []APIImageSearch + if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil { + return nil, err + } + return searchResult, nil +} + +// SearchImagesEx search the docker hub with a specific given term and authentication. +// +// See https://goo.gl/KLO9IZ for more details. +func (c *Client) SearchImagesEx(term string, auth AuthConfiguration) ([]APIImageSearch, error) { + headers, err := headersWithAuth(auth) + if err != nil { + return nil, err + } + + resp, err := c.do(http.MethodGet, "/images/search?term="+term, doOptions{ + headers: headers, + }) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + var searchResult []APIImageSearch + if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil { + return nil, err + } + + return searchResult, nil +} + +// PruneImagesOptions specify parameters to the PruneImages function. +// +// See https://goo.gl/qfZlbZ for more details. +type PruneImagesOptions struct { + Filters map[string][]string + Context context.Context +} + +// PruneImagesResults specify results from the PruneImages function. +// +// See https://goo.gl/qfZlbZ for more details. +type PruneImagesResults struct { + ImagesDeleted []struct{ Untagged, Deleted string } + SpaceReclaimed int64 +} + +// PruneImages deletes images which are unused. +// +// See https://goo.gl/qfZlbZ for more details. +func (c *Client) PruneImages(opts PruneImagesOptions) (*PruneImagesResults, error) { + path := "/images/prune?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var results PruneImagesResults + if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { + return nil, err + } + return &results, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/misc.go b/vendor/github.com/fsouza/go-dockerclient/misc.go new file mode 100644 index 00000000..2fa19c67 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/misc.go @@ -0,0 +1,196 @@ +// Copyright 2013 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "net" + "net/http" + "strings" + + "github.com/docker/docker/api/types/swarm" +) + +// Version returns version information about the docker server. +// +// See https://goo.gl/mU7yje for more details. +func (c *Client) Version() (*Env, error) { + return c.VersionWithContext(context.TODO()) +} + +// VersionWithContext returns version information about the docker server. +func (c *Client) VersionWithContext(ctx context.Context) (*Env, error) { + resp, err := c.do(http.MethodGet, "/version", doOptions{context: ctx}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var env Env + if err := env.Decode(resp.Body); err != nil { + return nil, err + } + return &env, nil +} + +// DockerInfo contains information about the Docker server +// +// See https://goo.gl/bHUoz9 for more details. +type DockerInfo struct { + ID string + Containers int + ContainersRunning int + ContainersPaused int + ContainersStopped int + Images int + Driver string + DriverStatus [][2]string + SystemStatus [][2]string + Plugins PluginsInfo + NFd int + NGoroutines int + SystemTime string + ExecutionDriver string + LoggingDriver string + CgroupDriver string + NEventsListener int + KernelVersion string + OperatingSystem string + OSType string + Architecture string + IndexServerAddress string + RegistryConfig *ServiceConfig + SecurityOptions []string + NCPU int + MemTotal int64 + DockerRootDir string + HTTPProxy string `json:"HttpProxy"` + HTTPSProxy string `json:"HttpsProxy"` + NoProxy string + Name string + Labels []string + ServerVersion string + ClusterStore string + Runtimes map[string]Runtime + ClusterAdvertise string + Isolation string + InitBinary string + DefaultRuntime string + Swarm swarm.Info + LiveRestoreEnabled bool + MemoryLimit bool + SwapLimit bool + KernelMemory bool + CPUCfsPeriod bool `json:"CpuCfsPeriod"` + CPUCfsQuota bool `json:"CpuCfsQuota"` + CPUShares bool + CPUSet bool + IPv4Forwarding bool + BridgeNfIptables bool + BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` + Debug bool + OomKillDisable bool + ExperimentalBuild bool +} + +// Runtime describes an OCI runtime +// +// for more information, see: https://dockr.ly/2NKM8qq +type Runtime struct { + Path string + Args []string `json:"runtimeArgs"` +} + +// PluginsInfo is a struct with the plugins registered with the docker daemon +// +// for more information, see: https://goo.gl/bHUoz9 +type PluginsInfo struct { + // List of Volume plugins registered + Volume []string + // List of Network plugins registered + Network []string + // List of Authorization plugins registered + Authorization []string +} + +// ServiceConfig stores daemon registry services configuration. +// +// for more information, see: https://goo.gl/7iFFDz +type ServiceConfig struct { + InsecureRegistryCIDRs []*NetIPNet + IndexConfigs map[string]*IndexInfo + Mirrors []string +} + +// NetIPNet is the net.IPNet type, which can be marshalled and +// unmarshalled to JSON. +// +// for more information, see: https://goo.gl/7iFFDz +type NetIPNet net.IPNet + +// MarshalJSON returns the JSON representation of the IPNet. +func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) { + return json.Marshal((*net.IPNet)(ipnet).String()) +} + +// UnmarshalJSON sets the IPNet from a byte array of JSON. +func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) { + var ipnetStr string + if err = json.Unmarshal(b, &ipnetStr); err == nil { + var cidr *net.IPNet + if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil { + *ipnet = NetIPNet(*cidr) + } + } + return +} + +// IndexInfo contains information about a registry. +// +// for more information, see: https://goo.gl/7iFFDz +type IndexInfo struct { + Name string + Mirrors []string + Secure bool + Official bool +} + +// Info returns system-wide information about the Docker server. +// +// See https://goo.gl/ElTHi2 for more details. +func (c *Client) Info() (*DockerInfo, error) { + resp, err := c.do(http.MethodGet, "/info", doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var info DockerInfo + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { + return nil, err + } + return &info, nil +} + +// ParseRepositoryTag gets the name of the repository and returns it splitted +// in two parts: the repository and the tag. It ignores the digest when it is +// present. +// +// Some examples: +// +// localhost.localdomain:5000/samalba/hipache:latest -> localhost.localdomain:5000/samalba/hipache, latest +// localhost.localdomain:5000/samalba/hipache -> localhost.localdomain:5000/samalba/hipache, "" +// busybox:latest@sha256:4a731fb46adc5cefe3ae374a8b6020fc1b6ad667a279647766e9a3cd89f6fa92 -> busybox, latest +func ParseRepositoryTag(repoTag string) (repository string, tag string) { + parts := strings.SplitN(repoTag, "@", 2) + repoTag = parts[0] + n := strings.LastIndex(repoTag, ":") + if n < 0 { + return repoTag, "" + } + if tag := repoTag[n+1:]; !strings.Contains(tag, "/") { + return repoTag[:n], tag + } + return repoTag, "" +} diff --git a/vendor/github.com/fsouza/go-dockerclient/network.go b/vendor/github.com/fsouza/go-dockerclient/network.go new file mode 100644 index 00000000..6932c8d8 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/network.go @@ -0,0 +1,340 @@ +// Copyright 2015 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" +) + +// ErrNetworkAlreadyExists is the error returned by CreateNetwork when the +// network already exists. +var ErrNetworkAlreadyExists = errors.New("network already exists") + +// Network represents a network. +// +// See https://goo.gl/6GugX3 for more details. +type Network struct { + Name string + ID string `json:"Id"` + Scope string + Driver string + IPAM IPAMOptions + Containers map[string]Endpoint + Options map[string]string + Internal bool + EnableIPv6 bool `json:"EnableIPv6"` + Labels map[string]string +} + +// Endpoint contains network resources allocated and used for a container in a network +// +// See https://goo.gl/6GugX3 for more details. +type Endpoint struct { + Name string + ID string `json:"EndpointID"` + MacAddress string + IPv4Address string + IPv6Address string +} + +// ListNetworks returns all networks. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) ListNetworks() ([]Network, error) { + resp, err := c.do(http.MethodGet, "/networks", doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var networks []Network + if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil { + return nil, err + } + return networks, nil +} + +// NetworkFilterOpts is an aggregation of key=value that Docker +// uses to filter networks +type NetworkFilterOpts map[string]map[string]bool + +// FilteredListNetworks returns all networks with the filters applied +// +// See goo.gl/zd2mx4 for more details. +func (c *Client) FilteredListNetworks(opts NetworkFilterOpts) ([]Network, error) { + params, err := json.Marshal(opts) + if err != nil { + return nil, err + } + qs := make(url.Values) + qs.Add("filters", string(params)) + path := "/networks?" + qs.Encode() + resp, err := c.do(http.MethodGet, path, doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var networks []Network + if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil { + return nil, err + } + return networks, nil +} + +// NetworkInfo returns information about a network by its ID. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) NetworkInfo(id string) (*Network, error) { + path := "/networks/" + id + resp, err := c.do(http.MethodGet, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchNetwork{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var network Network + if err := json.NewDecoder(resp.Body).Decode(&network); err != nil { + return nil, err + } + return &network, nil +} + +// CreateNetworkOptions specify parameters to the CreateNetwork function and +// (for now) is the expected body of the "create network" http request message +// +// See https://goo.gl/6GugX3 for more details. +type CreateNetworkOptions struct { + Name string `json:"Name" yaml:"Name" toml:"Name"` + Driver string `json:"Driver" yaml:"Driver" toml:"Driver"` + Scope string `json:"Scope" yaml:"Scope" toml:"Scope"` + IPAM *IPAMOptions `json:"IPAM,omitempty" yaml:"IPAM" toml:"IPAM"` + ConfigFrom *NetworkConfigFrom `json:"ConfigFrom,omitempty" yaml:"ConfigFrom" toml:"ConfigFrom"` + Options map[string]any `json:"Options" yaml:"Options" toml:"Options"` + Labels map[string]string `json:"Labels" yaml:"Labels" toml:"Labels"` + CheckDuplicate bool `json:"CheckDuplicate" yaml:"CheckDuplicate" toml:"CheckDuplicate"` + Internal bool `json:"Internal" yaml:"Internal" toml:"Internal"` + EnableIPv6 bool `json:"EnableIPv6" yaml:"EnableIPv6" toml:"EnableIPv6"` + Attachable bool `json:"Attachable" yaml:"Attachable" toml:"Attachable"` + ConfigOnly bool `json:"ConfigOnly" yaml:"ConfigOnly" toml:"ConfigOnly"` + Ingress bool `json:"Ingress" yaml:"Ingress" toml:"Ingress"` + Context context.Context `json:"-"` +} + +// NetworkConfigFrom is used in network creation for specifying the source of a +// network configuration. +type NetworkConfigFrom struct { + Network string `json:"Network" yaml:"Network" toml:"Network"` +} + +// IPAMOptions controls IP Address Management when creating a network +// +// See https://goo.gl/T8kRVH for more details. +type IPAMOptions struct { + Driver string `json:"Driver" yaml:"Driver" toml:"Driver"` + Config []IPAMConfig `json:"Config" yaml:"Config" toml:"Config"` + Options map[string]string `json:"Options" yaml:"Options" toml:"Options"` +} + +// IPAMConfig represents IPAM configurations +// +// See https://goo.gl/T8kRVH for more details. +type IPAMConfig struct { + Subnet string `json:",omitempty"` + IPRange string `json:",omitempty"` + Gateway string `json:",omitempty"` + AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` +} + +// CreateNetwork creates a new network, returning the network instance, +// or an error in case of failure. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) { + resp, err := c.do( + http.MethodPost, + "/networks/create", + doOptions{ + data: opts, + context: opts.Context, + }, + ) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + type createNetworkResponse struct { + ID string + } + var ( + network Network + cnr createNetworkResponse + ) + if err := json.NewDecoder(resp.Body).Decode(&cnr); err != nil { + return nil, err + } + + network.Name = opts.Name + network.ID = cnr.ID + network.Driver = opts.Driver + + return &network, nil +} + +// RemoveNetwork removes a network or returns an error in case of failure. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) RemoveNetwork(id string) error { + resp, err := c.do(http.MethodDelete, "/networks/"+id, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchNetwork{ID: id} + } + return err + } + resp.Body.Close() + return nil +} + +// NetworkConnectionOptions specify parameters to the ConnectNetwork and +// DisconnectNetwork function. +// +// See https://goo.gl/RV7BJU for more details. +type NetworkConnectionOptions struct { + Container string + + // EndpointConfig is only applicable to the ConnectNetwork call + EndpointConfig *EndpointConfig `json:"EndpointConfig,omitempty"` + + // Force is only applicable to the DisconnectNetwork call + Force bool + + Context context.Context `json:"-"` +} + +// EndpointConfig stores network endpoint details +// +// See https://goo.gl/RV7BJU for more details. +type EndpointConfig struct { + IPAMConfig *EndpointIPAMConfig `json:"IPAMConfig,omitempty" yaml:"IPAMConfig,omitempty" toml:"IPAMConfig,omitempty"` + Links []string `json:"Links,omitempty" yaml:"Links,omitempty" toml:"Links,omitempty"` + Aliases []string `json:"Aliases,omitempty" yaml:"Aliases,omitempty" toml:"Aliases,omitempty"` + NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"` + EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"` + Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"` + IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"` + IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"` + IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"` + GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"` + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` + DriverOpts map[string]string `json:"DriverOpts,omitempty" yaml:"DriverOpts,omitempty" toml:"DriverOpts,omitempty"` +} + +// EndpointIPAMConfig represents IPAM configurations for an +// endpoint +// +// See https://goo.gl/RV7BJU for more details. +type EndpointIPAMConfig struct { + IPv4Address string `json:",omitempty" yaml:"IPv4Address,omitempty"` + IPv6Address string `json:",omitempty" yaml:"IPv6Address,omitempty"` +} + +// ConnectNetwork adds a container to a network or returns an error in case of +// failure. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) ConnectNetwork(id string, opts NetworkConnectionOptions) error { + resp, err := c.do(http.MethodPost, "/networks/"+id+"/connect", doOptions{ + data: opts, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container} + } + return err + } + resp.Body.Close() + return nil +} + +// DisconnectNetwork removes a container from a network or returns an error in +// case of failure. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) DisconnectNetwork(id string, opts NetworkConnectionOptions) error { + resp, err := c.do(http.MethodPost, "/networks/"+id+"/disconnect", doOptions{data: opts}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container} + } + return err + } + resp.Body.Close() + return nil +} + +// PruneNetworksOptions specify parameters to the PruneNetworks function. +// +// See https://goo.gl/kX0S9h for more details. +type PruneNetworksOptions struct { + Filters map[string][]string + Context context.Context +} + +// PruneNetworksResults specify results from the PruneNetworks function. +// +// See https://goo.gl/kX0S9h for more details. +type PruneNetworksResults struct { + NetworksDeleted []string +} + +// PruneNetworks deletes networks which are unused. +// +// See https://goo.gl/kX0S9h for more details. +func (c *Client) PruneNetworks(opts PruneNetworksOptions) (*PruneNetworksResults, error) { + path := "/networks/prune?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var results PruneNetworksResults + if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { + return nil, err + } + return &results, nil +} + +// NoSuchNetwork is the error returned when a given network does not exist. +type NoSuchNetwork struct { + ID string +} + +func (err *NoSuchNetwork) Error() string { + return fmt.Sprintf("No such network: %s", err.ID) +} + +// NoSuchNetworkOrContainer is the error returned when a given network or +// container does not exist. +type NoSuchNetworkOrContainer struct { + NetworkID string + ContainerID string +} + +func (err *NoSuchNetworkOrContainer) Error() string { + return fmt.Sprintf("No such network (%s) or container (%s)", err.NetworkID, err.ContainerID) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/plugin.go b/vendor/github.com/fsouza/go-dockerclient/plugin.go new file mode 100644 index 00000000..7951e362 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/plugin.go @@ -0,0 +1,461 @@ +// Copyright 2018 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" +) + +// PluginPrivilege represents a privilege for a plugin. +type PluginPrivilege struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` + Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// InstallPluginOptions specify parameters to the InstallPlugins function. +// +// See https://goo.gl/C4t7Tz for more details. +type InstallPluginOptions struct { + Remote string + Name string + Plugins []PluginPrivilege `qs:"-"` + + Auth AuthConfiguration + + Context context.Context +} + +// InstallPlugins installs a plugin or returns an error in case of failure. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) InstallPlugins(opts InstallPluginOptions) error { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return err + } + + path := "/plugins/pull?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{ + data: opts.Plugins, + context: opts.Context, + headers: headers, + }) + if err != nil { + return err + } + defer resp.Body.Close() + // PullPlugin streams back the progress of the pull, we must consume the whole body + // otherwise the pull will be canceled on the engine. + if _, err := io.ReadAll(resp.Body); err != nil { + return err + } + return nil +} + +// PluginSettings stores plugin settings. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginSettings struct { + Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` + Args []string `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"` + Devices []string `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` +} + +// PluginInterface stores plugin interface. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginInterface struct { + Types []string `json:"Types,omitempty" yaml:"Types,omitempty" toml:"Types,omitempty"` + Socket string `json:"Socket,omitempty" yaml:"Socket,omitempty" toml:"Socket,omitempty"` +} + +// PluginNetwork stores plugin network type. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginNetwork struct { + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` +} + +// PluginLinux stores plugin linux setting. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginLinux struct { + Capabilities []string `json:"Capabilities,omitempty" yaml:"Capabilities,omitempty" toml:"Capabilities,omitempty"` + AllowAllDevices bool `json:"AllowAllDevices,omitempty" yaml:"AllowAllDevices,omitempty" toml:"AllowAllDevices,omitempty"` + Devices []PluginLinuxDevices `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` +} + +// PluginLinuxDevices stores plugin linux device setting. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginLinuxDevices struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Description string `json:"Documentation,omitempty" yaml:"Documentation,omitempty" toml:"Documentation,omitempty"` + Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"` + Path string `json:"Path,omitempty" yaml:"Path,omitempty" toml:"Path,omitempty"` +} + +// PluginEnv stores plugin environment. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginEnv struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` + Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"` + Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// PluginArgs stores plugin arguments. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginArgs struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` + Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"` + Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// PluginUser stores plugin user. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginUser struct { + UID int32 `json:"UID,omitempty" yaml:"UID,omitempty" toml:"UID,omitempty"` + GID int32 `json:"GID,omitempty" yaml:"GID,omitempty" toml:"GID,omitempty"` +} + +// PluginConfig stores plugin config. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginConfig struct { + Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` + Documentation string + Interface PluginInterface `json:"Interface,omitempty" yaml:"Interface,omitempty" toml:"Interface,omitempty"` + Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty" toml:"Entrypoint,omitempty"` + WorkDir string `json:"WorkDir,omitempty" yaml:"WorkDir,omitempty" toml:"WorkDir,omitempty"` + User PluginUser `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` + Network PluginNetwork `json:"Network,omitempty" yaml:"Network,omitempty" toml:"Network,omitempty"` + Linux PluginLinux `json:"Linux,omitempty" yaml:"Linux,omitempty" toml:"Linux,omitempty"` + PropagatedMount string `json:"PropagatedMount,omitempty" yaml:"PropagatedMount,omitempty" toml:"PropagatedMount,omitempty"` + Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` + Env []PluginEnv `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` + Args PluginArgs `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"` +} + +// PluginDetail specify results from the ListPlugins function. +// +// See https://goo.gl/C4t7Tz for more details. +type PluginDetail struct { + ID string `json:"Id,omitempty" yaml:"Id,omitempty" toml:"Id,omitempty"` + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Tag string `json:"Tag,omitempty" yaml:"Tag,omitempty" toml:"Tag,omitempty"` + Active bool `json:"Enabled,omitempty" yaml:"Active,omitempty" toml:"Active,omitempty"` + Settings PluginSettings `json:"Settings,omitempty" yaml:"Settings,omitempty" toml:"Settings,omitempty"` + Config PluginConfig `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` +} + +// ListPlugins returns pluginDetails or an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) ListPlugins(ctx context.Context) ([]PluginDetail, error) { + resp, err := c.do(http.MethodGet, "/plugins", doOptions{ + context: ctx, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + pluginDetails := make([]PluginDetail, 0) + if err := json.NewDecoder(resp.Body).Decode(&pluginDetails); err != nil { + return nil, err + } + return pluginDetails, nil +} + +// ListFilteredPluginsOptions specify parameters to the ListFilteredPlugins function. +// +// See https://goo.gl/C4t7Tz for more details. +type ListFilteredPluginsOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListFilteredPlugins returns pluginDetails or an error. +// +// See https://goo.gl/rmdmWg for more details. +func (c *Client) ListFilteredPlugins(opts ListFilteredPluginsOptions) ([]PluginDetail, error) { + path := "/plugins/json?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{ + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + pluginDetails := make([]PluginDetail, 0) + if err := json.NewDecoder(resp.Body).Decode(&pluginDetails); err != nil { + return nil, err + } + return pluginDetails, nil +} + +// GetPluginPrivileges returns pluginPrivileges or an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) GetPluginPrivileges(remote string, ctx context.Context) ([]PluginPrivilege, error) { + return c.GetPluginPrivilegesWithOptions( + GetPluginPrivilegesOptions{ + Remote: remote, + Context: ctx, + }) +} + +// GetPluginPrivilegesOptions specify parameters to the GetPluginPrivilegesWithOptions function. +// +// See https://goo.gl/C4t7Tz for more details. +type GetPluginPrivilegesOptions struct { + Remote string + Auth AuthConfiguration + Context context.Context +} + +// GetPluginPrivilegesWithOptions returns pluginPrivileges or an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) GetPluginPrivilegesWithOptions(opts GetPluginPrivilegesOptions) ([]PluginPrivilege, error) { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return nil, err + } + + path := "/plugins/privileges?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{ + context: opts.Context, + headers: headers, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var pluginPrivileges []PluginPrivilege + if err := json.NewDecoder(resp.Body).Decode(&pluginPrivileges); err != nil { + return nil, err + } + return pluginPrivileges, nil +} + +// InspectPlugins returns a pluginDetail or an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) InspectPlugins(name string, ctx context.Context) (*PluginDetail, error) { + resp, err := c.do(http.MethodGet, "/plugins/"+name+"/json", doOptions{ + context: ctx, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchPlugin{ID: name} + } + return nil, err + } + defer resp.Body.Close() + var pluginDetail PluginDetail + if err := json.NewDecoder(resp.Body).Decode(&pluginDetail); err != nil { + return nil, err + } + return &pluginDetail, nil +} + +// RemovePluginOptions specify parameters to the RemovePlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type RemovePluginOptions struct { + // The Name of the plugin. + Name string `qs:"-"` + + Force bool `qs:"force"` + Context context.Context +} + +// RemovePlugin returns a PluginDetail or an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) RemovePlugin(opts RemovePluginOptions) (*PluginDetail, error) { + path := "/plugins/" + opts.Name + "?" + queryString(opts) + resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchPlugin{ID: opts.Name} + } + return nil, err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if len(body) == 0 { + // Seems like newer docker versions won't return the plugindetail after removal + return nil, nil + } + + var pluginDetail PluginDetail + if err := json.Unmarshal(body, &pluginDetail); err != nil { + return nil, err + } + return &pluginDetail, nil +} + +// EnablePluginOptions specify parameters to the EnablePlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type EnablePluginOptions struct { + // The Name of the plugin. + Name string `qs:"-"` + Timeout int64 `qs:"timeout"` + + Context context.Context +} + +// EnablePlugin enables plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) EnablePlugin(opts EnablePluginOptions) error { + path := "/plugins/" + opts.Name + "/enable?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + +// DisablePluginOptions specify parameters to the DisablePlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type DisablePluginOptions struct { + // The Name of the plugin. + Name string `qs:"-"` + + Context context.Context +} + +// DisablePlugin disables plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) DisablePlugin(opts DisablePluginOptions) error { + path := "/plugins/" + opts.Name + "/disable" + resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + +// CreatePluginOptions specify parameters to the CreatePlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type CreatePluginOptions struct { + // The Name of the plugin. + Name string `qs:"name"` + // Path to tar containing plugin + Path string `qs:"-"` + + Context context.Context +} + +// CreatePlugin creates plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) CreatePlugin(opts CreatePluginOptions) (string, error) { + path := "/plugins/create?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{ + data: opts.Path, + context: opts.Context, + }) + if err != nil { + return "", err + } + defer resp.Body.Close() + containerNameBytes, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(containerNameBytes), nil +} + +// PushPluginOptions specify parameters to PushPlugin function. +// +// See https://goo.gl/C4t7Tz for more details. +type PushPluginOptions struct { + // The Name of the plugin. + Name string + + Context context.Context +} + +// PushPlugin pushes plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) PushPlugin(opts PushPluginOptions) error { + path := "/plugins/" + opts.Name + "/push" + resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + +// ConfigurePluginOptions specify parameters to the ConfigurePlugin +// +// See https://goo.gl/C4t7Tz for more details. +type ConfigurePluginOptions struct { + // The Name of the plugin. + Name string `qs:"name"` + Envs []string + + Context context.Context +} + +// ConfigurePlugin configures plugin that opts point or returns an error. +// +// See https://goo.gl/C4t7Tz for more details. +func (c *Client) ConfigurePlugin(opts ConfigurePluginOptions) error { + path := "/plugins/" + opts.Name + "/set" + resp, err := c.do(http.MethodPost, path, doOptions{ + data: opts.Envs, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchPlugin{ID: opts.Name} + } + return err + } + resp.Body.Close() + return nil +} + +// NoSuchPlugin is the error returned when a given plugin does not exist. +type NoSuchPlugin struct { + ID string + Err error +} + +func (err *NoSuchPlugin) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such plugin: " + err.ID +} diff --git a/vendor/github.com/fsouza/go-dockerclient/registry_auth.go b/vendor/github.com/fsouza/go-dockerclient/registry_auth.go new file mode 100644 index 00000000..1f60d1e8 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/registry_auth.go @@ -0,0 +1,10 @@ +// Copyright 2013 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +type registryAuth interface { + isEmpty() bool + headerKey() string +} diff --git a/vendor/github.com/fsouza/go-dockerclient/signal.go b/vendor/github.com/fsouza/go-dockerclient/signal.go new file mode 100644 index 00000000..16aa0038 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/signal.go @@ -0,0 +1,49 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +// Signal represents a signal that can be send to the container on +// KillContainer call. +type Signal int + +// These values represent all signals available on Linux, where containers will +// be running. +const ( + SIGABRT = Signal(0x6) + SIGALRM = Signal(0xe) + SIGBUS = Signal(0x7) + SIGCHLD = Signal(0x11) + SIGCLD = Signal(0x11) + SIGCONT = Signal(0x12) + SIGFPE = Signal(0x8) + SIGHUP = Signal(0x1) + SIGILL = Signal(0x4) + SIGINT = Signal(0x2) + SIGIO = Signal(0x1d) + SIGIOT = Signal(0x6) + SIGKILL = Signal(0x9) + SIGPIPE = Signal(0xd) + SIGPOLL = Signal(0x1d) + SIGPROF = Signal(0x1b) + SIGPWR = Signal(0x1e) + SIGQUIT = Signal(0x3) + SIGSEGV = Signal(0xb) + SIGSTKFLT = Signal(0x10) + SIGSTOP = Signal(0x13) + SIGSYS = Signal(0x1f) + SIGTERM = Signal(0xf) + SIGTRAP = Signal(0x5) + SIGTSTP = Signal(0x14) + SIGTTIN = Signal(0x15) + SIGTTOU = Signal(0x16) + SIGUNUSED = Signal(0x1f) + SIGURG = Signal(0x17) + SIGUSR1 = Signal(0xa) + SIGUSR2 = Signal(0xc) + SIGVTALRM = Signal(0x1a) + SIGWINCH = Signal(0x1c) + SIGXCPU = Signal(0x18) + SIGXFSZ = Signal(0x19) +) diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm.go b/vendor/github.com/fsouza/go-dockerclient/swarm.go new file mode 100644 index 00000000..ae37cd1e --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm.go @@ -0,0 +1,161 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" +) + +var ( + // ErrNodeAlreadyInSwarm is the error returned by InitSwarm and JoinSwarm + // when the node is already part of a Swarm. + ErrNodeAlreadyInSwarm = errors.New("node already in a Swarm") + + // ErrNodeNotInSwarm is the error returned by LeaveSwarm and UpdateSwarm + // when the node is not part of a Swarm. + ErrNodeNotInSwarm = errors.New("node is not in a Swarm") +) + +// InitSwarmOptions specify parameters to the InitSwarm function. +// See https://goo.gl/hzkgWu for more details. +type InitSwarmOptions struct { + swarm.InitRequest + Context context.Context +} + +// InitSwarm initializes a new Swarm and returns the node ID. +// See https://goo.gl/ZWyG1M for more details. +func (c *Client) InitSwarm(opts InitSwarmOptions) (string, error) { + path := "/swarm/init" + resp, err := c.do(http.MethodPost, path, doOptions{ + data: opts.InitRequest, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return "", ErrNodeAlreadyInSwarm + } + return "", err + } + defer resp.Body.Close() + var response string + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return "", err + } + return response, nil +} + +// JoinSwarmOptions specify parameters to the JoinSwarm function. +// See https://goo.gl/TdhJWU for more details. +type JoinSwarmOptions struct { + swarm.JoinRequest + Context context.Context +} + +// JoinSwarm joins an existing Swarm. +// See https://goo.gl/N59IP1 for more details. +func (c *Client) JoinSwarm(opts JoinSwarmOptions) error { + path := "/swarm/join" + resp, err := c.do(http.MethodPost, path, doOptions{ + data: opts.JoinRequest, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return ErrNodeAlreadyInSwarm + } + } + resp.Body.Close() + return err +} + +// LeaveSwarmOptions specify parameters to the LeaveSwarm function. +// See https://goo.gl/UWDlLg for more details. +type LeaveSwarmOptions struct { + Force bool + Context context.Context +} + +// LeaveSwarm leaves a Swarm. +// See https://goo.gl/FTX1aD for more details. +func (c *Client) LeaveSwarm(opts LeaveSwarmOptions) error { + params := make(url.Values) + params.Set("force", strconv.FormatBool(opts.Force)) + path := "/swarm/leave?" + params.Encode() + resp, err := c.do(http.MethodPost, path, doOptions{ + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return ErrNodeNotInSwarm + } + } + resp.Body.Close() + return err +} + +// UpdateSwarmOptions specify parameters to the UpdateSwarm function. +// See https://goo.gl/vFbq36 for more details. +type UpdateSwarmOptions struct { + Version int + RotateWorkerToken bool + RotateManagerToken bool + Swarm swarm.Spec + Context context.Context +} + +// UpdateSwarm updates a Swarm. +// See https://goo.gl/iJFnsw for more details. +func (c *Client) UpdateSwarm(opts UpdateSwarmOptions) error { + params := make(url.Values) + params.Set("version", strconv.Itoa(opts.Version)) + params.Set("rotateWorkerToken", strconv.FormatBool(opts.RotateWorkerToken)) + params.Set("rotateManagerToken", strconv.FormatBool(opts.RotateManagerToken)) + path := "/swarm/update?" + params.Encode() + resp, err := c.do(http.MethodPost, path, doOptions{ + data: opts.Swarm, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return ErrNodeNotInSwarm + } + } + resp.Body.Close() + return err +} + +// InspectSwarm inspects a Swarm. +// See https://goo.gl/MFwgX9 for more details. +func (c *Client) InspectSwarm(ctx context.Context) (swarm.Swarm, error) { + response := swarm.Swarm{} + resp, err := c.do(http.MethodGet, "/swarm", doOptions{ + context: ctx, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return response, ErrNodeNotInSwarm + } + return response, err + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&response) + return response, err +} diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_configs.go b/vendor/github.com/fsouza/go-dockerclient/swarm_configs.go new file mode 100644 index 00000000..055e9954 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_configs.go @@ -0,0 +1,175 @@ +// Copyright 2017 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" +) + +// NoSuchConfig is the error returned when a given config does not exist. +type NoSuchConfig struct { + ID string + Err error +} + +func (err *NoSuchConfig) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such config: " + err.ID +} + +// CreateConfigOptions specify parameters to the CreateConfig function. +// +// See https://goo.gl/KrVjHz for more details. +type CreateConfigOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.ConfigSpec + Context context.Context +} + +// CreateConfig creates a new config, returning the config instance +// or an error in case of failure. +// +// See https://goo.gl/KrVjHz for more details. +func (c *Client) CreateConfig(opts CreateConfigOptions) (*swarm.Config, error) { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return nil, err + } + path := "/configs/create?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{ + headers: headers, + data: opts.ConfigSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var config swarm.Config + if err := json.NewDecoder(resp.Body).Decode(&config); err != nil { + return nil, err + } + return &config, nil +} + +// RemoveConfigOptions encapsulates options to remove a config. +// +// See https://goo.gl/Tqrtya for more details. +type RemoveConfigOptions struct { + ID string `qs:"-"` + Context context.Context +} + +// RemoveConfig removes a config, returning an error in case of failure. +// +// See https://goo.gl/Tqrtya for more details. +func (c *Client) RemoveConfig(opts RemoveConfigOptions) error { + path := "/configs/" + opts.ID + resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchConfig{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} + +// UpdateConfigOptions specify parameters to the UpdateConfig function. +// +// See https://goo.gl/wu3MmS for more details. +type UpdateConfigOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.ConfigSpec + Context context.Context + Version uint64 +} + +// UpdateConfig updates the config at ID with the options +// +// Only label can be updated +// https://docs.docker.com/engine/api/v1.33/#operation/ConfigUpdate +// See https://goo.gl/wu3MmS for more details. +func (c *Client) UpdateConfig(id string, opts UpdateConfigOptions) error { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return err + } + params := make(url.Values) + params.Set("version", strconv.FormatUint(opts.Version, 10)) + resp, err := c.do(http.MethodPost, "/configs/"+id+"/update?"+params.Encode(), doOptions{ + headers: headers, + data: opts.ConfigSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchConfig{ID: id} + } + return err + } + defer resp.Body.Close() + return nil +} + +// InspectConfig returns information about a config by its ID. +// +// See https://goo.gl/dHmr75 for more details. +func (c *Client) InspectConfig(id string) (*swarm.Config, error) { + path := "/configs/" + id + resp, err := c.do(http.MethodGet, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchConfig{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var config swarm.Config + if err := json.NewDecoder(resp.Body).Decode(&config); err != nil { + return nil, err + } + return &config, nil +} + +// ListConfigsOptions specify parameters to the ListConfigs function. +// +// See https://goo.gl/DwvNMd for more details. +type ListConfigsOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListConfigs returns a slice of configs matching the given criteria. +// +// See https://goo.gl/DwvNMd for more details. +func (c *Client) ListConfigs(opts ListConfigsOptions) ([]swarm.Config, error) { + path := "/configs?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var configs []swarm.Config + if err := json.NewDecoder(resp.Body).Decode(&configs); err != nil { + return nil, err + } + return configs, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_node.go b/vendor/github.com/fsouza/go-dockerclient/swarm_node.go new file mode 100644 index 00000000..8538a167 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_node.go @@ -0,0 +1,134 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" +) + +// NoSuchNode is the error returned when a given node does not exist. +type NoSuchNode struct { + ID string + Err error +} + +func (err *NoSuchNode) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such node: " + err.ID +} + +// ListNodesOptions specify parameters to the ListNodes function. +// +// See http://goo.gl/3K4GwU for more details. +type ListNodesOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListNodes returns a slice of nodes matching the given criteria. +// +// See http://goo.gl/3K4GwU for more details. +func (c *Client) ListNodes(opts ListNodesOptions) ([]swarm.Node, error) { + path := "/nodes?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var nodes []swarm.Node + if err := json.NewDecoder(resp.Body).Decode(&nodes); err != nil { + return nil, err + } + return nodes, nil +} + +// InspectNode returns information about a node by its ID. +// +// See http://goo.gl/WjkTOk for more details. +func (c *Client) InspectNode(id string) (*swarm.Node, error) { + resp, err := c.do(http.MethodGet, "/nodes/"+id, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchNode{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var node swarm.Node + if err := json.NewDecoder(resp.Body).Decode(&node); err != nil { + return nil, err + } + return &node, nil +} + +// UpdateNodeOptions specify parameters to the NodeUpdate function. +// +// See http://goo.gl/VPBFgA for more details. +type UpdateNodeOptions struct { + swarm.NodeSpec + Version uint64 + Context context.Context +} + +// UpdateNode updates a node. +// +// See http://goo.gl/VPBFgA for more details. +func (c *Client) UpdateNode(id string, opts UpdateNodeOptions) error { + params := make(url.Values) + params.Set("version", strconv.FormatUint(opts.Version, 10)) + path := "/nodes/" + id + "/update?" + params.Encode() + resp, err := c.do(http.MethodPost, path, doOptions{ + context: opts.Context, + forceJSON: true, + data: opts.NodeSpec, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchNode{ID: id} + } + return err + } + resp.Body.Close() + return nil +} + +// RemoveNodeOptions specify parameters to the RemoveNode function. +// +// See http://goo.gl/0SNvYg for more details. +type RemoveNodeOptions struct { + ID string + Force bool + Context context.Context +} + +// RemoveNode removes a node. +// +// See http://goo.gl/0SNvYg for more details. +func (c *Client) RemoveNode(opts RemoveNodeOptions) error { + params := make(url.Values) + params.Set("force", strconv.FormatBool(opts.Force)) + path := "/nodes/" + opts.ID + "?" + params.Encode() + resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchNode{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_secrets.go b/vendor/github.com/fsouza/go-dockerclient/swarm_secrets.go new file mode 100644 index 00000000..375e6e5b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_secrets.go @@ -0,0 +1,175 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" +) + +// NoSuchSecret is the error returned when a given secret does not exist. +type NoSuchSecret struct { + ID string + Err error +} + +func (err *NoSuchSecret) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such secret: " + err.ID +} + +// CreateSecretOptions specify parameters to the CreateSecret function. +// +// See https://goo.gl/KrVjHz for more details. +type CreateSecretOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.SecretSpec + Context context.Context +} + +// CreateSecret creates a new secret, returning the secret instance +// or an error in case of failure. +// +// See https://goo.gl/KrVjHz for more details. +func (c *Client) CreateSecret(opts CreateSecretOptions) (*swarm.Secret, error) { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return nil, err + } + path := "/secrets/create?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{ + headers: headers, + data: opts.SecretSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var secret swarm.Secret + if err := json.NewDecoder(resp.Body).Decode(&secret); err != nil { + return nil, err + } + return &secret, nil +} + +// RemoveSecretOptions encapsulates options to remove a secret. +// +// See https://goo.gl/Tqrtya for more details. +type RemoveSecretOptions struct { + ID string `qs:"-"` + Context context.Context +} + +// RemoveSecret removes a secret, returning an error in case of failure. +// +// See https://goo.gl/Tqrtya for more details. +func (c *Client) RemoveSecret(opts RemoveSecretOptions) error { + path := "/secrets/" + opts.ID + resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchSecret{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} + +// UpdateSecretOptions specify parameters to the UpdateSecret function. +// +// Only label can be updated +// See https://docs.docker.com/engine/api/v1.33/#operation/SecretUpdate +// See https://goo.gl/wu3MmS for more details. +type UpdateSecretOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.SecretSpec + Context context.Context + Version uint64 +} + +// UpdateSecret updates the secret at ID with the options +// +// See https://goo.gl/wu3MmS for more details. +func (c *Client) UpdateSecret(id string, opts UpdateSecretOptions) error { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return err + } + params := make(url.Values) + params.Set("version", strconv.FormatUint(opts.Version, 10)) + resp, err := c.do(http.MethodPost, "/secrets/"+id+"/update?"+params.Encode(), doOptions{ + headers: headers, + data: opts.SecretSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchSecret{ID: id} + } + return err + } + defer resp.Body.Close() + return nil +} + +// InspectSecret returns information about a secret by its ID. +// +// See https://goo.gl/dHmr75 for more details. +func (c *Client) InspectSecret(id string) (*swarm.Secret, error) { + path := "/secrets/" + id + resp, err := c.do(http.MethodGet, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchSecret{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var secret swarm.Secret + if err := json.NewDecoder(resp.Body).Decode(&secret); err != nil { + return nil, err + } + return &secret, nil +} + +// ListSecretsOptions specify parameters to the ListSecrets function. +// +// See https://goo.gl/DwvNMd for more details. +type ListSecretsOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListSecrets returns a slice of secrets matching the given criteria. +// +// See https://goo.gl/DwvNMd for more details. +func (c *Client) ListSecrets(opts ListSecretsOptions) ([]swarm.Secret, error) { + path := "/secrets?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var secrets []swarm.Secret + if err := json.NewDecoder(resp.Body).Decode(&secrets); err != nil { + return nil, err + } + return secrets, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_service.go b/vendor/github.com/fsouza/go-dockerclient/swarm_service.go new file mode 100644 index 00000000..0d0f007b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_service.go @@ -0,0 +1,218 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "time" + + "github.com/docker/docker/api/types/swarm" +) + +// NoSuchService is the error returned when a given service does not exist. +type NoSuchService struct { + ID string + Err error +} + +func (err *NoSuchService) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such service: " + err.ID +} + +// CreateServiceOptions specify parameters to the CreateService function. +// +// See https://goo.gl/KrVjHz for more details. +type CreateServiceOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.ServiceSpec + Context context.Context +} + +// CreateService creates a new service, returning the service instance +// or an error in case of failure. +// +// See https://goo.gl/KrVjHz for more details. +func (c *Client) CreateService(opts CreateServiceOptions) (*swarm.Service, error) { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return nil, err + } + path := "/services/create?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{ + headers: headers, + data: opts.ServiceSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var service swarm.Service + if err := json.NewDecoder(resp.Body).Decode(&service); err != nil { + return nil, err + } + return &service, nil +} + +// RemoveServiceOptions encapsulates options to remove a service. +// +// See https://goo.gl/Tqrtya for more details. +type RemoveServiceOptions struct { + ID string `qs:"-"` + Context context.Context +} + +// RemoveService removes a service, returning an error in case of failure. +// +// See https://goo.gl/Tqrtya for more details. +func (c *Client) RemoveService(opts RemoveServiceOptions) error { + path := "/services/" + opts.ID + resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchService{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} + +// UpdateServiceOptions specify parameters to the UpdateService function. +// +// See https://goo.gl/wu3MmS for more details. +type UpdateServiceOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.ServiceSpec `qs:"-"` + Context context.Context + Version uint64 + Rollback string +} + +// UpdateService updates the service at ID with the options +// +// See https://goo.gl/wu3MmS for more details. +func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return err + } + resp, err := c.do(http.MethodPost, "/services/"+id+"/update?"+queryString(opts), doOptions{ + headers: headers, + data: opts.ServiceSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return &NoSuchService{ID: id} + } + return err + } + defer resp.Body.Close() + return nil +} + +// InspectService returns information about a service by its ID. +// +// See https://goo.gl/dHmr75 for more details. +func (c *Client) InspectService(id string) (*swarm.Service, error) { + path := "/services/" + id + resp, err := c.do(http.MethodGet, path, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchService{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var service swarm.Service + if err := json.NewDecoder(resp.Body).Decode(&service); err != nil { + return nil, err + } + return &service, nil +} + +// ListServicesOptions specify parameters to the ListServices function. +// +// See https://goo.gl/DwvNMd for more details. +type ListServicesOptions struct { + Filters map[string][]string + Status bool + Context context.Context +} + +// ListServices returns a slice of services matching the given criteria. +// +// See https://goo.gl/DwvNMd for more details. +func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error) { + path := "/services?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var services []swarm.Service + if err := json.NewDecoder(resp.Body).Decode(&services); err != nil { + return nil, err + } + return services, nil +} + +// LogsServiceOptions represents the set of options used when getting logs from a +// service. +type LogsServiceOptions struct { + Context context.Context + Service string `qs:"-"` + OutputStream io.Writer `qs:"-"` + ErrorStream io.Writer `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + Tail string + Since int64 + + // Use raw terminal? Usually true when the container contains a TTY. + RawTerminal bool `qs:"-"` + Follow bool + Stdout bool + Stderr bool + Timestamps bool + Details bool +} + +// GetServiceLogs gets stdout and stderr logs from the specified service. +// +// When LogsServiceOptions.RawTerminal is set to false, go-dockerclient will multiplex +// the streams and send the containers stdout to LogsServiceOptions.OutputStream, and +// stderr to LogsServiceOptions.ErrorStream. +// +// When LogsServiceOptions.RawTerminal is true, callers will get the raw stream on +// LogsServiceOptions.OutputStream. +func (c *Client) GetServiceLogs(opts LogsServiceOptions) error { + if opts.Service == "" { + return &NoSuchService{ID: opts.Service} + } + if opts.Tail == "" { + opts.Tail = "all" + } + path := "/services/" + opts.Service + "/logs?" + queryString(opts) + return c.stream(http.MethodGet, path, streamOptions{ + setRawTerminal: opts.RawTerminal, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm_task.go b/vendor/github.com/fsouza/go-dockerclient/swarm_task.go new file mode 100644 index 00000000..9321368d --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm_task.go @@ -0,0 +1,72 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" + + "github.com/docker/docker/api/types/swarm" +) + +// NoSuchTask is the error returned when a given task does not exist. +type NoSuchTask struct { + ID string + Err error +} + +func (err *NoSuchTask) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such task: " + err.ID +} + +// ListTasksOptions specify parameters to the ListTasks function. +// +// See http://goo.gl/rByLzw for more details. +type ListTasksOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListTasks returns a slice of tasks matching the given criteria. +// +// See http://goo.gl/rByLzw for more details. +func (c *Client) ListTasks(opts ListTasksOptions) ([]swarm.Task, error) { + path := "/tasks?" + queryString(opts) + resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var tasks []swarm.Task + if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil { + return nil, err + } + return tasks, nil +} + +// InspectTask returns information about a task by its ID. +// +// See http://goo.gl/kyziuq for more details. +func (c *Client) InspectTask(id string) (*swarm.Task, error) { + resp, err := c.do(http.MethodGet, "/tasks/"+id, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, &NoSuchTask{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var task swarm.Task + if err := json.NewDecoder(resp.Body).Decode(&task); err != nil { + return nil, err + } + return &task, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/system.go b/vendor/github.com/fsouza/go-dockerclient/system.go new file mode 100644 index 00000000..f1791f6c --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/system.go @@ -0,0 +1,72 @@ +package docker + +import ( + "context" + "encoding/json" + "net/http" +) + +// VolumeUsageData represents usage data from the docker system api +// More Info Here https://dockr.ly/2PNzQyO +type VolumeUsageData struct { + // The number of containers referencing this volume. This field + // is set to `-1` if the reference-count is not available. + // + // Required: true + RefCount int64 `json:"RefCount"` + + // Amount of disk space used by the volume (in bytes). This information + // is only available for volumes created with the `"local"` volume + // driver. For volumes created with other volume drivers, this field + // is set to `-1` ("not available") + // + // Required: true + Size int64 `json:"Size"` +} + +// ImageSummary represents data about what images are +// currently known to docker +// More Info Here https://dockr.ly/2PNzQyO +type ImageSummary struct { + Containers int64 `json:"Containers"` + Created int64 `json:"Created"` + ID string `json:"Id"` + Labels map[string]string `json:"Labels"` + ParentID string `json:"ParentId"` + RepoDigests []string `json:"RepoDigests"` + RepoTags []string `json:"RepoTags"` + SharedSize int64 `json:"SharedSize"` + Size int64 `json:"Size"` + VirtualSize int64 `json:"VirtualSize"` +} + +// DiskUsage holds information about what docker is using disk space on. +// More Info Here https://dockr.ly/2PNzQyO +type DiskUsage struct { + LayersSize int64 + Images []*ImageSummary + Containers []*APIContainers + Volumes []*Volume +} + +// DiskUsageOptions only contains a context for canceling. +type DiskUsageOptions struct { + Context context.Context +} + +// DiskUsage returns a *DiskUsage describing what docker is using disk on. +// +// More Info Here https://dockr.ly/2PNzQyO +func (c *Client) DiskUsage(opts DiskUsageOptions) (*DiskUsage, error) { + path := "/system/df" + resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var du *DiskUsage + if err := json.NewDecoder(resp.Body).Decode(&du); err != nil { + return nil, err + } + return du, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/tar.go b/vendor/github.com/fsouza/go-dockerclient/tar.go new file mode 100644 index 00000000..0d40c644 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/tar.go @@ -0,0 +1,121 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + + "github.com/moby/go-archive" + "github.com/moby/patternmatcher" +) + +func createTarStream(srcPath, dockerfilePath string) (io.ReadCloser, error) { + srcPath, err := filepath.Abs(srcPath) + if err != nil { + return nil, err + } + + excludes, err := parseDockerignore(srcPath) + if err != nil { + return nil, err + } + + includes := []string{"."} + + // If .dockerignore mentions .dockerignore or the Dockerfile + // then make sure we send both files over to the daemon + // because Dockerfile is, obviously, needed no matter what, and + // .dockerignore is needed to know if either one needs to be + // removed. The deamon will remove them for us, if needed, after it + // parses the Dockerfile. + // + // https://github.com/docker/docker/issues/8330 + // + forceIncludeFiles := []string{".dockerignore", dockerfilePath} + + for _, includeFile := range forceIncludeFiles { + if includeFile == "" { + continue + } + keepThem, err := patternmatcher.Matches(includeFile, excludes) + if err != nil { + return nil, fmt.Errorf("cannot match .dockerfileignore: '%s', error: %w", includeFile, err) + } + if keepThem { + includes = append(includes, includeFile) + } + } + + if err := validateContextDirectory(srcPath, excludes); err != nil { + return nil, err + } + tarOpts := &archive.TarOptions{ + ExcludePatterns: excludes, + IncludeFiles: includes, + Compression: archive.Uncompressed, + NoLchown: true, + } + return archive.TarWithOptions(srcPath, tarOpts) +} + +// validateContextDirectory checks if all the contents of the directory +// can be read and returns an error if some files can't be read. +// Symlinks which point to non-existing files don't trigger an error +func validateContextDirectory(srcPath string, excludes []string) error { + return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error { + // skip this directory/file if it's not in the path, it won't get added to the context + if relFilePath, relErr := filepath.Rel(srcPath, filePath); relErr != nil { + return relErr + } else if skip, matchErr := patternmatcher.Matches(relFilePath, excludes); matchErr != nil { + return matchErr + } else if skip { + if f.IsDir() { + return filepath.SkipDir + } + return nil + } + + if err != nil { + if os.IsPermission(err) { + return fmt.Errorf("cannot stat %q: %w", filePath, err) + } + if os.IsNotExist(err) { + return nil + } + return err + } + + // skip checking if symlinks point to non-existing files, such symlinks can be useful + // also skip named pipes, because they hanging on open + if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 { + return nil + } + + if !f.IsDir() { + currentFile, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("cannot open %q for reading: %w", filePath, err) + } + currentFile.Close() + } + return nil + }) +} + +func parseDockerignore(root string) ([]string, error) { + var excludes []string + ignore, err := os.ReadFile(path.Join(root, ".dockerignore")) + if err != nil && !os.IsNotExist(err) { + return excludes, fmt.Errorf("error reading .dockerignore: %w", err) + } + excludes = strings.Split(string(ignore), "\n") + + return excludes, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/tls.go b/vendor/github.com/fsouza/go-dockerclient/tls.go new file mode 100644 index 00000000..15d5e266 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/tls.go @@ -0,0 +1,115 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// The content is borrowed from Docker's own source code to provide a simple +// tls based dialer + +package docker + +import ( + "crypto/tls" + "errors" + "net" + "strings" + "time" +) + +type tlsClientCon struct { + *tls.Conn + rawConn net.Conn +} + +func (c *tlsClientCon) CloseWrite() error { + // Go standard tls.Conn doesn't provide the CloseWrite() method so we do it + // on its underlying connection. + if cwc, ok := c.rawConn.(interface { + CloseWrite() error + }); ok { + return cwc.CloseWrite() + } + return nil +} + +func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) { + // We want the Timeout and Deadline values from dialer to cover the + // whole process: TCP connection and TLS handshake. This means that we + // also need to start our own timers now. + timeout := dialer.Timeout + + if !dialer.Deadline.IsZero() { + deadlineTimeout := time.Until(dialer.Deadline) + if timeout == 0 || deadlineTimeout < timeout { + timeout = deadlineTimeout + } + } + + var errChannel chan error + + if timeout != 0 { + errChannel = make(chan error, 2) + time.AfterFunc(timeout, func() { + errChannel <- errors.New("") + }) + } + + rawConn, err := dialer.Dial(network, addr) + if err != nil { + return nil, err + } + + colonPos := strings.LastIndex(addr, ":") + if colonPos == -1 { + colonPos = len(addr) + } + hostname := addr[:colonPos] + + // If no ServerName is set, infer the ServerName + // from the hostname we're connecting to. + if config.ServerName == "" { + // Make a copy to avoid polluting argument or default. + config = copyTLSConfig(config) + config.ServerName = hostname + } + + conn := tls.Client(rawConn, config) + + if timeout == 0 { + err = conn.Handshake() + } else { + go func() { + errChannel <- conn.Handshake() + }() + + err = <-errChannel + } + + if err != nil { + rawConn.Close() + return nil, err + } + + // This is Docker difference with standard's crypto/tls package: returned a + // wrapper which holds both the TLS and raw connections. + return &tlsClientCon{conn, rawConn}, nil +} + +// this exists to silent an error message in go vet +func copyTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Certificates: cfg.Certificates, + CipherSuites: cfg.CipherSuites, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + ClientSessionCache: cfg.ClientSessionCache, + CurvePreferences: cfg.CurvePreferences, + InsecureSkipVerify: cfg.InsecureSkipVerify, + MaxVersion: cfg.MaxVersion, + MinVersion: cfg.MinVersion, + NextProtos: cfg.NextProtos, + Rand: cfg.Rand, + RootCAs: cfg.RootCAs, + ServerName: cfg.ServerName, + SessionTicketsDisabled: cfg.SessionTicketsDisabled, + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/volume.go b/vendor/github.com/fsouza/go-dockerclient/volume.go new file mode 100644 index 00000000..92dcb71b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/volume.go @@ -0,0 +1,194 @@ +// Copyright 2015 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "time" +) + +var ( + // ErrNoSuchVolume is the error returned when the volume does not exist. + ErrNoSuchVolume = errors.New("no such volume") + + // ErrVolumeInUse is the error returned when the volume requested to be removed is still in use. + ErrVolumeInUse = errors.New("volume in use and cannot be removed") +) + +// Volume represents a volume. +// +// See https://goo.gl/3wgTsd for more details. +type Volume struct { + Name string `json:"Name" yaml:"Name" toml:"Name"` + Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` + Mountpoint string `json:"Mountpoint,omitempty" yaml:"Mountpoint,omitempty" toml:"Mountpoint,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` + Options map[string]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"` + CreatedAt time.Time `json:"CreatedAt,omitempty" yaml:"CreatedAt,omitempty" toml:"CreatedAt,omitempty"` +} + +// ListVolumesOptions specify parameters to the ListVolumes function. +// +// See https://goo.gl/3wgTsd for more details. +type ListVolumesOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListVolumes returns a list of available volumes in the server. +// +// See https://goo.gl/3wgTsd for more details. +func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) { + resp, err := c.do(http.MethodGet, "/volumes?"+queryString(opts), doOptions{ + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + m := make(map[string]any) + if err = json.NewDecoder(resp.Body).Decode(&m); err != nil { + return nil, err + } + var volumes []Volume + volumesJSON, ok := m["Volumes"] + if !ok { + return volumes, nil + } + data, err := json.Marshal(volumesJSON) + if err != nil { + return nil, err + } + if err := json.Unmarshal(data, &volumes); err != nil { + return nil, err + } + return volumes, nil +} + +// CreateVolumeOptions specify parameters to the CreateVolume function. +// +// See https://goo.gl/qEhmEC for more details. +type CreateVolumeOptions struct { + Name string + Driver string + DriverOpts map[string]string + Context context.Context `json:"-"` + Labels map[string]string +} + +// CreateVolume creates a volume on the server. +// +// See https://goo.gl/qEhmEC for more details. +func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) { + resp, err := c.do(http.MethodPost, "/volumes/create", doOptions{ + data: opts, + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var volume Volume + if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil { + return nil, err + } + return &volume, nil +} + +// InspectVolume returns a volume by its name. +// +// See https://goo.gl/GMjsMc for more details. +func (c *Client) InspectVolume(name string) (*Volume, error) { + resp, err := c.do(http.MethodGet, "/volumes/"+name, doOptions{}) + if err != nil { + var e *Error + if errors.As(err, &e) && e.Status == http.StatusNotFound { + return nil, ErrNoSuchVolume + } + return nil, err + } + defer resp.Body.Close() + var volume Volume + if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil { + return nil, err + } + return &volume, nil +} + +// RemoveVolume removes a volume by its name. +// +// Deprecated: Use RemoveVolumeWithOptions instead. +func (c *Client) RemoveVolume(name string) error { + return c.RemoveVolumeWithOptions(RemoveVolumeOptions{Name: name}) +} + +// RemoveVolumeOptions specify parameters to the RemoveVolumeWithOptions +// function. +// +// See https://goo.gl/nvd6qj for more details. +type RemoveVolumeOptions struct { + Context context.Context + Name string `qs:"-"` + Force bool +} + +// RemoveVolumeWithOptions removes a volume by its name and takes extra +// parameters. +// +// See https://goo.gl/nvd6qj for more details. +func (c *Client) RemoveVolumeWithOptions(opts RemoveVolumeOptions) error { + path := "/volumes/" + opts.Name + resp, err := c.do(http.MethodDelete, path+"?"+queryString(opts), doOptions{context: opts.Context}) + if err != nil { + var e *Error + if errors.As(err, &e) { + if e.Status == http.StatusNotFound { + return ErrNoSuchVolume + } + if e.Status == http.StatusConflict { + return ErrVolumeInUse + } + } + return err + } + defer resp.Body.Close() + return nil +} + +// PruneVolumesOptions specify parameters to the PruneVolumes function. +// +// See https://goo.gl/f9XDem for more details. +type PruneVolumesOptions struct { + Filters map[string][]string + Context context.Context +} + +// PruneVolumesResults specify results from the PruneVolumes function. +// +// See https://goo.gl/f9XDem for more details. +type PruneVolumesResults struct { + VolumesDeleted []string + SpaceReclaimed int64 +} + +// PruneVolumes deletes volumes which are unused. +// +// See https://goo.gl/f9XDem for more details. +func (c *Client) PruneVolumes(opts PruneVolumesOptions) (*PruneVolumesResults, error) { + path := "/volumes/prune?" + queryString(opts) + resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var results PruneVolumesResults + if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { + return nil, err + } + return &results, nil +} diff --git a/vendor/github.com/fvbommel/sortorder/README.md b/vendor/github.com/fvbommel/sortorder/README.md deleted file mode 100644 index 06779c88..00000000 --- a/vendor/github.com/fvbommel/sortorder/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# sortorder [![PkgGoDev](https://pkg.go.dev/badge/github.com/fvbommel/sortorder)](https://pkg.go.dev/github.com/fvbommel/sortorder) - - import "github.com/fvbommel/sortorder" - -Sort orders and comparison functions. - -Case-insensitive sort orders are in the `casefolded` sub-package -because it pulls in the Unicode tables in the standard library, -which can add significantly to the size of binaries. diff --git a/vendor/github.com/fvbommel/sortorder/doc.go b/vendor/github.com/fvbommel/sortorder/doc.go deleted file mode 100644 index a7dd9585..00000000 --- a/vendor/github.com/fvbommel/sortorder/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package sortorder implements sort orders and comparison functions. -// -// Currently, it only implements so-called "natural order", where integers -// embedded in strings are compared by value. -package sortorder // import "github.com/fvbommel/sortorder" diff --git a/vendor/github.com/fvbommel/sortorder/natsort.go b/vendor/github.com/fvbommel/sortorder/natsort.go deleted file mode 100644 index e4f15110..00000000 --- a/vendor/github.com/fvbommel/sortorder/natsort.go +++ /dev/null @@ -1,76 +0,0 @@ -package sortorder - -// Natural implements sort.Interface to sort strings in natural order. This -// means that e.g. "abc2" < "abc12". -// -// Non-digit sequences and numbers are compared separately. The former are -// compared bytewise, while digits are compared numerically (except that -// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") -// -// Limitation: only ASCII digits (0-9) are considered. -type Natural []string - -func (n Natural) Len() int { return len(n) } -func (n Natural) Swap(i, j int) { n[i], n[j] = n[j], n[i] } -func (n Natural) Less(i, j int) bool { return NaturalLess(n[i], n[j]) } - -func isDigit(b byte) bool { return '0' <= b && b <= '9' } - -// NaturalLess compares two strings using natural ordering. This means that e.g. -// "abc2" < "abc12". -// -// Non-digit sequences and numbers are compared separately. The former are -// compared bytewise, while digits are compared numerically (except that -// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") -// -// Limitation: only ASCII digits (0-9) are considered. -func NaturalLess(str1, str2 string) bool { - idx1, idx2 := 0, 0 - for idx1 < len(str1) && idx2 < len(str2) { - c1, c2 := str1[idx1], str2[idx2] - dig1, dig2 := isDigit(c1), isDigit(c2) - switch { - case dig1 != dig2: // Digits before other characters. - return dig1 // True if LHS is a digit, false if the RHS is one. - case !dig1: // && !dig2, because dig1 == dig2 - // UTF-8 compares bytewise-lexicographically, no need to decode - // codepoints. - if c1 != c2 { - return c1 < c2 - } - idx1++ - idx2++ - default: // Digits - // Eat zeros. - for ; idx1 < len(str1) && str1[idx1] == '0'; idx1++ { - } - for ; idx2 < len(str2) && str2[idx2] == '0'; idx2++ { - } - // Eat all digits. - nonZero1, nonZero2 := idx1, idx2 - for ; idx1 < len(str1) && isDigit(str1[idx1]); idx1++ { - } - for ; idx2 < len(str2) && isDigit(str2[idx2]); idx2++ { - } - // If lengths of numbers with non-zero prefix differ, the shorter - // one is less. - if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 { - return len1 < len2 - } - // If they're equally long, string comparison is correct. - if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 { - return nr1 < nr2 - } - // Otherwise, the one with less zeros is less. - // Because everything up to the number is equal, comparing the index - // after the zeros is sufficient. - if nonZero1 != nonZero2 { - return nonZero1 < nonZero2 - } - } - // They're identical so far, so continue comparing. - } - // So far they are identical. At least one is ended. If the other continues, - // it sorts last. - return len(str1) < len(str2) -} diff --git a/vendor/github.com/go-jose/go-jose/v4/.gitignore b/vendor/github.com/go-jose/go-jose/v4/.gitignore new file mode 100644 index 00000000..eb29ebae --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/.gitignore @@ -0,0 +1,2 @@ +jose-util/jose-util +jose-util.t.err \ No newline at end of file diff --git a/vendor/github.com/go-jose/go-jose/v4/.golangci.yml b/vendor/github.com/go-jose/go-jose/v4/.golangci.yml new file mode 100644 index 00000000..2a577a8f --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/.golangci.yml @@ -0,0 +1,53 @@ +# https://github.com/golangci/golangci-lint + +run: + skip-files: + - doc_test.go + modules-download-mode: readonly + +linters: + enable-all: true + disable: + - gochecknoglobals + - goconst + - lll + - maligned + - nakedret + - scopelint + - unparam + - funlen # added in 1.18 (requires go-jose changes before it can be enabled) + +linters-settings: + gocyclo: + min-complexity: 35 + +issues: + exclude-rules: + - text: "don't use ALL_CAPS in Go names" + linters: + - golint + - text: "hardcoded credentials" + linters: + - gosec + - text: "weak cryptographic primitive" + linters: + - gosec + - path: json/ + linters: + - dupl + - errcheck + - gocritic + - gocyclo + - golint + - govet + - ineffassign + - staticcheck + - structcheck + - stylecheck + - unused + - path: _test\.go + linters: + - scopelint + - path: jwk.go + linters: + - gocyclo diff --git a/vendor/github.com/go-jose/go-jose/v4/.travis.yml b/vendor/github.com/go-jose/go-jose/v4/.travis.yml new file mode 100644 index 00000000..48de631b --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/.travis.yml @@ -0,0 +1,33 @@ +language: go + +matrix: + fast_finish: true + allow_failures: + - go: tip + +go: + - "1.13.x" + - "1.14.x" + - tip + +before_script: + - export PATH=$HOME/.local/bin:$PATH + +before_install: + - go get -u github.com/mattn/goveralls github.com/wadey/gocovmerge + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0 + - pip install cram --user + +script: + - go test -v -covermode=count -coverprofile=profile.cov . + - go test -v -covermode=count -coverprofile=cryptosigner/profile.cov ./cryptosigner + - go test -v -covermode=count -coverprofile=cipher/profile.cov ./cipher + - go test -v -covermode=count -coverprofile=jwt/profile.cov ./jwt + - go test -v ./json # no coverage for forked encoding/json package + - golangci-lint run + - cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util + - cd .. + +after_success: + - gocovmerge *.cov */*.cov > merged.coverprofile + - goveralls -coverprofile merged.coverprofile -service=travis-ci diff --git a/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md b/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md new file mode 100644 index 00000000..6f717dbd --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md @@ -0,0 +1,96 @@ +# v4.0.4 + +## Fixed + + - Reverted "Allow unmarshalling JSONWebKeySets with unsupported key types" as a + breaking change. See #136 / #137. + +# v4.0.3 + +## Changed + + - Allow unmarshalling JSONWebKeySets with unsupported key types (#130) + - Document that OpaqueKeyEncrypter can't be implemented (for now) (#129) + - Dependency updates + +# v4.0.2 + +## Changed + + - Improved documentation of Verify() to note that JSONWebKeySet is a supported + argument type (#104) + - Defined exported error values for missing x5c header and unsupported elliptic + curves error cases (#117) + +# v4.0.1 + +## Fixed + + - An attacker could send a JWE containing compressed data that used large + amounts of memory and CPU when decompressed by `Decrypt` or `DecryptMulti`. + Those functions now return an error if the decompressed data would exceed + 250kB or 10x the compressed size (whichever is larger). Thanks to + Enze Wang@Alioth and Jianjun Chen@Zhongguancun Lab (@zer0yu and @chenjj) + for reporting. + +# v4.0.0 + +This release makes some breaking changes in order to more thoroughly +address the vulnerabilities discussed in [Three New Attacks Against JSON Web +Tokens][1], "Sign/encrypt confusion", "Billion hash attack", and "Polyglot +token". + +## Changed + + - Limit JWT encryption types (exclude password or public key types) (#78) + - Enforce minimum length for HMAC keys (#85) + - jwt: match any audience in a list, rather than requiring all audiences (#81) + - jwt: accept only Compact Serialization (#75) + - jws: Add expected algorithms for signatures (#74) + - Require specifying expected algorithms for ParseEncrypted, + ParseSigned, ParseDetached, jwt.ParseEncrypted, jwt.ParseSigned, + jwt.ParseSignedAndEncrypted (#69, #74) + - Usually there is a small, known set of appropriate algorithms for a program + to use and it's a mistake to allow unexpected algorithms. For instance the + "billion hash attack" relies in part on programs accepting the PBES2 + encryption algorithm and doing the necessary work even if they weren't + specifically configured to allow PBES2. + - Revert "Strip padding off base64 strings" (#82) + - The specs require base64url encoding without padding. + - Minimum supported Go version is now 1.21 + +## Added + + - ParseSignedCompact, ParseSignedJSON, ParseEncryptedCompact, ParseEncryptedJSON. + - These allow parsing a specific serialization, as opposed to ParseSigned and + ParseEncrypted, which try to automatically detect which serialization was + provided. It's common to require a specific serialization for a specific + protocol - for instance JWT requires Compact serialization. + +[1]: https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf + +# v3.0.2 + +## Fixed + + - DecryptMulti: handle decompression error (#19) + +## Changed + + - jwe/CompactSerialize: improve performance (#67) + - Increase the default number of PBKDF2 iterations to 600k (#48) + - Return the proper algorithm for ECDSA keys (#45) + +## Added + + - Add Thumbprint support for opaque signers (#38) + +# v3.0.1 + +## Fixed + + - Security issue: an attacker specifying a large "p2c" value can cause + JSONWebEncryption.Decrypt and JSONWebEncryption.DecryptMulti to consume large + amounts of CPU, causing a DoS. Thanks to Matt Schwager (@mschwager) for the + disclosure and to Tom Tervoort for originally publishing the category of attack. + https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf diff --git a/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md b/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md new file mode 100644 index 00000000..4b4805ad --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Contributing + +If you would like to contribute code to go-jose you can do so through GitHub by +forking the repository and sending a pull request. + +When submitting code, please make every effort to follow existing conventions +and style in order to keep the code as readable as possible. Please also make +sure all tests pass by running `go test`, and format your code with `go fmt`. +We also recommend using `golint` and `errcheck`. diff --git a/vendor/github.com/go-jose/go-jose/v4/LICENSE b/vendor/github.com/go-jose/go-jose/v4/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/go-jose/go-jose/v4/README.md b/vendor/github.com/go-jose/go-jose/v4/README.md new file mode 100644 index 00000000..02b57495 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/README.md @@ -0,0 +1,106 @@ +# Go JOSE + +[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4) +[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt) +[![license](https://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE) +[![test](https://img.shields.io/github/checks-status/go-jose/go-jose/v4)](https://github.com/go-jose/go-jose/actions) + +Package jose aims to provide an implementation of the Javascript Object Signing +and Encryption set of standards. This includes support for JSON Web Encryption, +JSON Web Signature, and JSON Web Token standards. + +## Overview + +The implementation follows the +[JSON Web Encryption](https://dx.doi.org/10.17487/RFC7516) (RFC 7516), +[JSON Web Signature](https://dx.doi.org/10.17487/RFC7515) (RFC 7515), and +[JSON Web Token](https://dx.doi.org/10.17487/RFC7519) (RFC 7519) specifications. +Tables of supported algorithms are shown below. The library supports both +the compact and JWS/JWE JSON Serialization formats, and has optional support for +multiple recipients. It also comes with a small command-line utility +([`jose-util`](https://pkg.go.dev/github.com/go-jose/go-jose/jose-util)) +for dealing with JOSE messages in a shell. + +**Note**: We use a forked version of the `encoding/json` package from the Go +standard library which uses case-sensitive matching for member names (instead +of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)). +This is to avoid differences in interpretation of messages between go-jose and +libraries in other languages. + +### Versions + +[Version 4](https://github.com/go-jose/go-jose) +([branch](https://github.com/go-jose/go-jose/tree/main), +[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v4), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version: + + import "github.com/go-jose/go-jose/v4" + +The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which +are still useable but not actively developed anymore. + +Version 3, in this repo, is still receiving security fixes but not functionality +updates. + +### Supported algorithms + +See below for a table of supported algorithms. Algorithm identifiers match +the names in the [JSON Web Algorithms](https://dx.doi.org/10.17487/RFC7518) +standard where possible. The Godoc reference has a list of constants. + + Key encryption | Algorithm identifier(s) + :------------------------- | :------------------------------ + RSA-PKCS#1v1.5 | RSA1_5 + RSA-OAEP | RSA-OAEP, RSA-OAEP-256 + AES key wrap | A128KW, A192KW, A256KW + AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW + ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW + ECDH-ES (direct) | ECDH-ES1 + Direct encryption | dir1 + +1. Not supported in multi-recipient mode + + Signing / MAC | Algorithm identifier(s) + :------------------------- | :------------------------------ + RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 + RSASSA-PSS | PS256, PS384, PS512 + HMAC | HS256, HS384, HS512 + ECDSA | ES256, ES384, ES512 + Ed25519 | EdDSA2 + +2. Only available in version 2 of the package + + Content encryption | Algorithm identifier(s) + :------------------------- | :------------------------------ + AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 + AES-GCM | A128GCM, A192GCM, A256GCM + + Compression | Algorithm identifiers(s) + :------------------------- | ------------------------------- + DEFLATE (RFC 1951) | DEF + +### Supported key types + +See below for a table of supported key types. These are understood by the +library, and can be passed to corresponding functions such as `NewEncrypter` or +`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which +allows attaching a key id. + + Algorithm(s) | Corresponding types + :------------------------- | ------------------------------- + RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) + ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) + EdDSA1 | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) + AES, HMAC | []byte + +1. Only available in version 2 or later of the package + +## Examples + +[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4) +[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt) + +Examples can be found in the Godoc +reference for this package. The +[`jose-util`](https://github.com/go-jose/go-jose/tree/main/jose-util) +subdirectory also contains a small command-line utility which might be useful +as an example as well. diff --git a/vendor/github.com/go-jose/go-jose/v4/SECURITY.md b/vendor/github.com/go-jose/go-jose/v4/SECURITY.md new file mode 100644 index 00000000..2f18a75a --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy +This document explains how to contact the Let's Encrypt security team to report security vulnerabilities. + +## Supported Versions +| Version | Supported | +| ------- | ----------| +| >= v3 | ✓ | +| v2 | ✗ | +| v1 | ✗ | + +## Reporting a vulnerability + +Please see [https://letsencrypt.org/contact/#security](https://letsencrypt.org/contact/#security) for the email address to report a vulnerability. Ensure that the subject line for your report contains the word `vulnerability` and is descriptive. Your email should be acknowledged within 24 hours. If you do not receive a response within 24 hours, please follow-up again with another email. diff --git a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go b/vendor/github.com/go-jose/go-jose/v4/asymmetric.go new file mode 100644 index 00000000..f8d5774e --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/asymmetric.go @@ -0,0 +1,595 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +import ( + "crypto" + "crypto/aes" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "errors" + "fmt" + "math/big" + + josecipher "github.com/go-jose/go-jose/v4/cipher" + "github.com/go-jose/go-jose/v4/json" +) + +// A generic RSA-based encrypter/verifier +type rsaEncrypterVerifier struct { + publicKey *rsa.PublicKey +} + +// A generic RSA-based decrypter/signer +type rsaDecrypterSigner struct { + privateKey *rsa.PrivateKey +} + +// A generic EC-based encrypter/verifier +type ecEncrypterVerifier struct { + publicKey *ecdsa.PublicKey +} + +type edEncrypterVerifier struct { + publicKey ed25519.PublicKey +} + +// A key generator for ECDH-ES +type ecKeyGenerator struct { + size int + algID string + publicKey *ecdsa.PublicKey +} + +// A generic EC-based decrypter/signer +type ecDecrypterSigner struct { + privateKey *ecdsa.PrivateKey +} + +type edDecrypterSigner struct { + privateKey ed25519.PrivateKey +} + +// newRSARecipient creates recipientKeyInfo based on the given key. +func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) { + // Verify that key management algorithm is supported by this encrypter + switch keyAlg { + case RSA1_5, RSA_OAEP, RSA_OAEP_256: + default: + return recipientKeyInfo{}, ErrUnsupportedAlgorithm + } + + if publicKey == nil { + return recipientKeyInfo{}, errors.New("invalid public key") + } + + return recipientKeyInfo{ + keyAlg: keyAlg, + keyEncrypter: &rsaEncrypterVerifier{ + publicKey: publicKey, + }, + }, nil +} + +// newRSASigner creates a recipientSigInfo based on the given key. +func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipientSigInfo, error) { + // Verify that key management algorithm is supported by this encrypter + switch sigAlg { + case RS256, RS384, RS512, PS256, PS384, PS512: + default: + return recipientSigInfo{}, ErrUnsupportedAlgorithm + } + + if privateKey == nil { + return recipientSigInfo{}, errors.New("invalid private key") + } + + return recipientSigInfo{ + sigAlg: sigAlg, + publicKey: staticPublicKey(&JSONWebKey{ + Key: privateKey.Public(), + }), + signer: &rsaDecrypterSigner{ + privateKey: privateKey, + }, + }, nil +} + +func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey) (recipientSigInfo, error) { + if sigAlg != EdDSA { + return recipientSigInfo{}, ErrUnsupportedAlgorithm + } + + if privateKey == nil { + return recipientSigInfo{}, errors.New("invalid private key") + } + return recipientSigInfo{ + sigAlg: sigAlg, + publicKey: staticPublicKey(&JSONWebKey{ + Key: privateKey.Public(), + }), + signer: &edDecrypterSigner{ + privateKey: privateKey, + }, + }, nil +} + +// newECDHRecipient creates recipientKeyInfo based on the given key. +func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) { + // Verify that key management algorithm is supported by this encrypter + switch keyAlg { + case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW: + default: + return recipientKeyInfo{}, ErrUnsupportedAlgorithm + } + + if publicKey == nil || !publicKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) { + return recipientKeyInfo{}, errors.New("invalid public key") + } + + return recipientKeyInfo{ + keyAlg: keyAlg, + keyEncrypter: &ecEncrypterVerifier{ + publicKey: publicKey, + }, + }, nil +} + +// newECDSASigner creates a recipientSigInfo based on the given key. +func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (recipientSigInfo, error) { + // Verify that key management algorithm is supported by this encrypter + switch sigAlg { + case ES256, ES384, ES512: + default: + return recipientSigInfo{}, ErrUnsupportedAlgorithm + } + + if privateKey == nil { + return recipientSigInfo{}, errors.New("invalid private key") + } + + return recipientSigInfo{ + sigAlg: sigAlg, + publicKey: staticPublicKey(&JSONWebKey{ + Key: privateKey.Public(), + }), + signer: &ecDecrypterSigner{ + privateKey: privateKey, + }, + }, nil +} + +// Encrypt the given payload and update the object. +func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) { + encryptedKey, err := ctx.encrypt(cek, alg) + if err != nil { + return recipientInfo{}, err + } + + return recipientInfo{ + encryptedKey: encryptedKey, + header: &rawHeader{}, + }, nil +} + +// Encrypt the given payload. Based on the key encryption algorithm, +// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256). +func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) { + switch alg { + case RSA1_5: + return rsa.EncryptPKCS1v15(RandReader, ctx.publicKey, cek) + case RSA_OAEP: + return rsa.EncryptOAEP(sha1.New(), RandReader, ctx.publicKey, cek, []byte{}) + case RSA_OAEP_256: + return rsa.EncryptOAEP(sha256.New(), RandReader, ctx.publicKey, cek, []byte{}) + } + + return nil, ErrUnsupportedAlgorithm +} + +// Decrypt the given payload and return the content encryption key. +func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { + return ctx.decrypt(recipient.encryptedKey, headers.getAlgorithm(), generator) +} + +// Decrypt the given payload. Based on the key encryption algorithm, +// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256). +func (ctx rsaDecrypterSigner) decrypt(jek []byte, alg KeyAlgorithm, generator keyGenerator) ([]byte, error) { + // Note: The random reader on decrypt operations is only used for blinding, + // so stubbing is meanlingless (hence the direct use of rand.Reader). + switch alg { + case RSA1_5: + defer func() { + // DecryptPKCS1v15SessionKey sometimes panics on an invalid payload + // because of an index out of bounds error, which we want to ignore. + // This has been fixed in Go 1.3.1 (released 2014/08/13), the recover() + // only exists for preventing crashes with unpatched versions. + // See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k + // See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33 + _ = recover() + }() + + // Perform some input validation. + keyBytes := ctx.privateKey.PublicKey.N.BitLen() / 8 + if keyBytes != len(jek) { + // Input size is incorrect, the encrypted payload should always match + // the size of the public modulus (e.g. using a 2048 bit key will + // produce 256 bytes of output). Reject this since it's invalid input. + return nil, ErrCryptoFailure + } + + cek, _, err := generator.genKey() + if err != nil { + return nil, ErrCryptoFailure + } + + // When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to + // prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing + // the Million Message Attack on Cryptographic Message Syntax". We are + // therefore deliberately ignoring errors here. + _ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, ctx.privateKey, jek, cek) + + return cek, nil + case RSA_OAEP: + // Use rand.Reader for RSA blinding + return rsa.DecryptOAEP(sha1.New(), rand.Reader, ctx.privateKey, jek, []byte{}) + case RSA_OAEP_256: + // Use rand.Reader for RSA blinding + return rsa.DecryptOAEP(sha256.New(), rand.Reader, ctx.privateKey, jek, []byte{}) + } + + return nil, ErrUnsupportedAlgorithm +} + +// Sign the given payload +func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { + var hash crypto.Hash + + switch alg { + case RS256, PS256: + hash = crypto.SHA256 + case RS384, PS384: + hash = crypto.SHA384 + case RS512, PS512: + hash = crypto.SHA512 + default: + return Signature{}, ErrUnsupportedAlgorithm + } + + hasher := hash.New() + + // According to documentation, Write() on hash never fails + _, _ = hasher.Write(payload) + hashed := hasher.Sum(nil) + + var out []byte + var err error + + switch alg { + case RS256, RS384, RS512: + // TODO(https://github.com/go-jose/go-jose/issues/40): As of go1.20, the + // random parameter is legacy and ignored, and it can be nil. + // https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/rsa/pkcs1v15.go;l=263;bpv=0;bpt=1 + out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed) + case PS256, PS384, PS512: + out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + }) + } + + if err != nil { + return Signature{}, err + } + + return Signature{ + Signature: out, + protected: &rawHeader{}, + }, nil +} + +// Verify the given payload +func (ctx rsaEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { + var hash crypto.Hash + + switch alg { + case RS256, PS256: + hash = crypto.SHA256 + case RS384, PS384: + hash = crypto.SHA384 + case RS512, PS512: + hash = crypto.SHA512 + default: + return ErrUnsupportedAlgorithm + } + + hasher := hash.New() + + // According to documentation, Write() on hash never fails + _, _ = hasher.Write(payload) + hashed := hasher.Sum(nil) + + switch alg { + case RS256, RS384, RS512: + return rsa.VerifyPKCS1v15(ctx.publicKey, hash, hashed, signature) + case PS256, PS384, PS512: + return rsa.VerifyPSS(ctx.publicKey, hash, hashed, signature, nil) + } + + return ErrUnsupportedAlgorithm +} + +// Encrypt the given payload and update the object. +func (ctx ecEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) { + switch alg { + case ECDH_ES: + // ECDH-ES mode doesn't wrap a key, the shared secret is used directly as the key. + return recipientInfo{ + header: &rawHeader{}, + }, nil + case ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW: + default: + return recipientInfo{}, ErrUnsupportedAlgorithm + } + + generator := ecKeyGenerator{ + algID: string(alg), + publicKey: ctx.publicKey, + } + + switch alg { + case ECDH_ES_A128KW: + generator.size = 16 + case ECDH_ES_A192KW: + generator.size = 24 + case ECDH_ES_A256KW: + generator.size = 32 + } + + kek, header, err := generator.genKey() + if err != nil { + return recipientInfo{}, err + } + + block, err := aes.NewCipher(kek) + if err != nil { + return recipientInfo{}, err + } + + jek, err := josecipher.KeyWrap(block, cek) + if err != nil { + return recipientInfo{}, err + } + + return recipientInfo{ + encryptedKey: jek, + header: &header, + }, nil +} + +// Get key size for EC key generator +func (ctx ecKeyGenerator) keySize() int { + return ctx.size +} + +// Get a content encryption key for ECDH-ES +func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) { + priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, RandReader) + if err != nil { + return nil, rawHeader{}, err + } + + out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size) + + b, err := json.Marshal(&JSONWebKey{ + Key: &priv.PublicKey, + }) + if err != nil { + return nil, nil, err + } + + headers := rawHeader{ + headerEPK: makeRawMessage(b), + } + + return out, headers, nil +} + +// Decrypt the given payload and return the content encryption key. +func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { + epk, err := headers.getEPK() + if err != nil { + return nil, errors.New("go-jose/go-jose: invalid epk header") + } + if epk == nil { + return nil, errors.New("go-jose/go-jose: missing epk header") + } + + publicKey, ok := epk.Key.(*ecdsa.PublicKey) + if publicKey == nil || !ok { + return nil, errors.New("go-jose/go-jose: invalid epk header") + } + + if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) { + return nil, errors.New("go-jose/go-jose: invalid public key in epk header") + } + + apuData, err := headers.getAPU() + if err != nil { + return nil, errors.New("go-jose/go-jose: invalid apu header") + } + apvData, err := headers.getAPV() + if err != nil { + return nil, errors.New("go-jose/go-jose: invalid apv header") + } + + deriveKey := func(algID string, size int) []byte { + return josecipher.DeriveECDHES(algID, apuData.bytes(), apvData.bytes(), ctx.privateKey, publicKey, size) + } + + var keySize int + + algorithm := headers.getAlgorithm() + switch algorithm { + case ECDH_ES: + // ECDH-ES uses direct key agreement, no key unwrapping necessary. + return deriveKey(string(headers.getEncryption()), generator.keySize()), nil + case ECDH_ES_A128KW: + keySize = 16 + case ECDH_ES_A192KW: + keySize = 24 + case ECDH_ES_A256KW: + keySize = 32 + default: + return nil, ErrUnsupportedAlgorithm + } + + key := deriveKey(string(algorithm), keySize) + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + return josecipher.KeyUnwrap(block, recipient.encryptedKey) +} + +func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { + if alg != EdDSA { + return Signature{}, ErrUnsupportedAlgorithm + } + + sig, err := ctx.privateKey.Sign(RandReader, payload, crypto.Hash(0)) + if err != nil { + return Signature{}, err + } + + return Signature{ + Signature: sig, + protected: &rawHeader{}, + }, nil +} + +func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { + if alg != EdDSA { + return ErrUnsupportedAlgorithm + } + ok := ed25519.Verify(ctx.publicKey, payload, signature) + if !ok { + return errors.New("go-jose/go-jose: ed25519 signature failed to verify") + } + return nil +} + +// Sign the given payload +func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { + var expectedBitSize int + var hash crypto.Hash + + switch alg { + case ES256: + expectedBitSize = 256 + hash = crypto.SHA256 + case ES384: + expectedBitSize = 384 + hash = crypto.SHA384 + case ES512: + expectedBitSize = 521 + hash = crypto.SHA512 + } + + curveBits := ctx.privateKey.Curve.Params().BitSize + if expectedBitSize != curveBits { + return Signature{}, fmt.Errorf("go-jose/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits) + } + + hasher := hash.New() + + // According to documentation, Write() on hash never fails + _, _ = hasher.Write(payload) + hashed := hasher.Sum(nil) + + r, s, err := ecdsa.Sign(RandReader, ctx.privateKey, hashed) + if err != nil { + return Signature{}, err + } + + keyBytes := curveBits / 8 + if curveBits%8 > 0 { + keyBytes++ + } + + // We serialize the outputs (r and s) into big-endian byte arrays and pad + // them with zeros on the left to make sure the sizes work out. Both arrays + // must be keyBytes long, and the output must be 2*keyBytes long. + rBytes := r.Bytes() + rBytesPadded := make([]byte, keyBytes) + copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) + + sBytes := s.Bytes() + sBytesPadded := make([]byte, keyBytes) + copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) + + out := append(rBytesPadded, sBytesPadded...) + + return Signature{ + Signature: out, + protected: &rawHeader{}, + }, nil +} + +// Verify the given payload +func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { + var keySize int + var hash crypto.Hash + + switch alg { + case ES256: + keySize = 32 + hash = crypto.SHA256 + case ES384: + keySize = 48 + hash = crypto.SHA384 + case ES512: + keySize = 66 + hash = crypto.SHA512 + default: + return ErrUnsupportedAlgorithm + } + + if len(signature) != 2*keySize { + return fmt.Errorf("go-jose/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize) + } + + hasher := hash.New() + + // According to documentation, Write() on hash never fails + _, _ = hasher.Write(payload) + hashed := hasher.Sum(nil) + + r := big.NewInt(0).SetBytes(signature[:keySize]) + s := big.NewInt(0).SetBytes(signature[keySize:]) + + match := ecdsa.Verify(ctx.publicKey, hashed, r, s) + if !match { + return errors.New("go-jose/go-jose: ecdsa signature failed to verify") + } + + return nil +} diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go b/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go new file mode 100644 index 00000000..af029cec --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go @@ -0,0 +1,196 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package josecipher + +import ( + "bytes" + "crypto/cipher" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "crypto/subtle" + "encoding/binary" + "errors" + "hash" +) + +const ( + nonceBytes = 16 +) + +// NewCBCHMAC instantiates a new AEAD based on CBC+HMAC. +func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (cipher.AEAD, error) { + keySize := len(key) / 2 + integrityKey := key[:keySize] + encryptionKey := key[keySize:] + + blockCipher, err := newBlockCipher(encryptionKey) + if err != nil { + return nil, err + } + + var hash func() hash.Hash + switch keySize { + case 16: + hash = sha256.New + case 24: + hash = sha512.New384 + case 32: + hash = sha512.New + } + + return &cbcAEAD{ + hash: hash, + blockCipher: blockCipher, + authtagBytes: keySize, + integrityKey: integrityKey, + }, nil +} + +// An AEAD based on CBC+HMAC +type cbcAEAD struct { + hash func() hash.Hash + authtagBytes int + integrityKey []byte + blockCipher cipher.Block +} + +func (ctx *cbcAEAD) NonceSize() int { + return nonceBytes +} + +func (ctx *cbcAEAD) Overhead() int { + // Maximum overhead is block size (for padding) plus auth tag length, where + // the length of the auth tag is equivalent to the key size. + return ctx.blockCipher.BlockSize() + ctx.authtagBytes +} + +// Seal encrypts and authenticates the plaintext. +func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte { + // Output buffer -- must take care not to mangle plaintext input. + ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)] + copy(ciphertext, plaintext) + ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize()) + + cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce) + + cbc.CryptBlocks(ciphertext, ciphertext) + authtag := ctx.computeAuthTag(data, nonce, ciphertext) + + ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag))) + copy(out, ciphertext) + copy(out[len(ciphertext):], authtag) + + return ret +} + +// Open decrypts and authenticates the ciphertext. +func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + if len(ciphertext) < ctx.authtagBytes { + return nil, errors.New("go-jose/go-jose: invalid ciphertext (too short)") + } + + offset := len(ciphertext) - ctx.authtagBytes + expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset]) + match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:]) + if match != 1 { + return nil, errors.New("go-jose/go-jose: invalid ciphertext (auth tag mismatch)") + } + + cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce) + + // Make copy of ciphertext buffer, don't want to modify in place + buffer := append([]byte{}, ciphertext[:offset]...) + + if len(buffer)%ctx.blockCipher.BlockSize() > 0 { + return nil, errors.New("go-jose/go-jose: invalid ciphertext (invalid length)") + } + + cbc.CryptBlocks(buffer, buffer) + + // Remove padding + plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize()) + if err != nil { + return nil, err + } + + ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext))) + copy(out, plaintext) + + return ret, nil +} + +// Compute an authentication tag +func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte { + buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8) + n := 0 + n += copy(buffer, aad) + n += copy(buffer[n:], nonce) + n += copy(buffer[n:], ciphertext) + binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8) + + // According to documentation, Write() on hash.Hash never fails. + hmac := hmac.New(ctx.hash, ctx.integrityKey) + _, _ = hmac.Write(buffer) + + return hmac.Sum(nil)[:ctx.authtagBytes] +} + +// resize ensures that the given slice has a capacity of at least n bytes. +// If the capacity of the slice is less than n, a new slice is allocated +// and the existing data will be copied. +func resize(in []byte, n uint64) (head, tail []byte) { + if uint64(cap(in)) >= n { + head = in[:n] + } else { + head = make([]byte, n) + copy(head, in) + } + + tail = head[len(in):] + return +} + +// Apply padding +func padBuffer(buffer []byte, blockSize int) []byte { + missing := blockSize - (len(buffer) % blockSize) + ret, out := resize(buffer, uint64(len(buffer))+uint64(missing)) + padding := bytes.Repeat([]byte{byte(missing)}, missing) + copy(out, padding) + return ret +} + +// Remove padding +func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) { + if len(buffer)%blockSize != 0 { + return nil, errors.New("go-jose/go-jose: invalid padding") + } + + last := buffer[len(buffer)-1] + count := int(last) + + if count == 0 || count > blockSize || count > len(buffer) { + return nil, errors.New("go-jose/go-jose: invalid padding") + } + + padding := bytes.Repeat([]byte{last}, count) + if !bytes.HasSuffix(buffer, padding) { + return nil, errors.New("go-jose/go-jose: invalid padding") + } + + return buffer[:len(buffer)-count], nil +} diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go b/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go new file mode 100644 index 00000000..f62c3bdb --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go @@ -0,0 +1,75 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package josecipher + +import ( + "crypto" + "encoding/binary" + "hash" + "io" +) + +type concatKDF struct { + z, info []byte + i uint32 + cache []byte + hasher hash.Hash +} + +// NewConcatKDF builds a KDF reader based on the given inputs. +func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader { + buffer := make([]byte, uint64(len(algID))+uint64(len(ptyUInfo))+uint64(len(ptyVInfo))+uint64(len(supPubInfo))+uint64(len(supPrivInfo))) + n := 0 + n += copy(buffer, algID) + n += copy(buffer[n:], ptyUInfo) + n += copy(buffer[n:], ptyVInfo) + n += copy(buffer[n:], supPubInfo) + copy(buffer[n:], supPrivInfo) + + hasher := hash.New() + + return &concatKDF{ + z: z, + info: buffer, + hasher: hasher, + cache: []byte{}, + i: 1, + } +} + +func (ctx *concatKDF) Read(out []byte) (int, error) { + copied := copy(out, ctx.cache) + ctx.cache = ctx.cache[copied:] + + for copied < len(out) { + ctx.hasher.Reset() + + // Write on a hash.Hash never fails + _ = binary.Write(ctx.hasher, binary.BigEndian, ctx.i) + _, _ = ctx.hasher.Write(ctx.z) + _, _ = ctx.hasher.Write(ctx.info) + + hash := ctx.hasher.Sum(nil) + chunkCopied := copy(out[copied:], hash) + copied += chunkCopied + ctx.cache = hash[chunkCopied:] + + ctx.i++ + } + + return copied, nil +} diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go b/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go new file mode 100644 index 00000000..093c6467 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go @@ -0,0 +1,86 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package josecipher + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "encoding/binary" +) + +// DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA. +// It is an error to call this function with a private/public key that are not on the same +// curve. Callers must ensure that the keys are valid before calling this function. Output +// size may be at most 1<<16 bytes (64 KiB). +func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte { + if size > 1<<16 { + panic("ECDH-ES output size too large, must be less than or equal to 1<<16") + } + + // algId, partyUInfo, partyVInfo inputs must be prefixed with the length + algID := lengthPrefixed([]byte(alg)) + ptyUInfo := lengthPrefixed(apuData) + ptyVInfo := lengthPrefixed(apvData) + + // suppPubInfo is the encoded length of the output size in bits + supPubInfo := make([]byte, 4) + binary.BigEndian.PutUint32(supPubInfo, uint32(size)*8) + + if !priv.PublicKey.Curve.IsOnCurve(pub.X, pub.Y) { + panic("public key not on same curve as private key") + } + + z, _ := priv.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes()) + zBytes := z.Bytes() + + // Note that calling z.Bytes() on a big.Int may strip leading zero bytes from + // the returned byte array. This can lead to a problem where zBytes will be + // shorter than expected which breaks the key derivation. Therefore we must pad + // to the full length of the expected coordinate here before calling the KDF. + octSize := dSize(priv.Curve) + if len(zBytes) != octSize { + zBytes = append(bytes.Repeat([]byte{0}, octSize-len(zBytes)), zBytes...) + } + + reader := NewConcatKDF(crypto.SHA256, zBytes, algID, ptyUInfo, ptyVInfo, supPubInfo, []byte{}) + key := make([]byte, size) + + // Read on the KDF will never fail + _, _ = reader.Read(key) + + return key +} + +// dSize returns the size in octets for a coordinate on a elliptic curve. +func dSize(curve elliptic.Curve) int { + order := curve.Params().P + bitLen := order.BitLen() + size := bitLen / 8 + if bitLen%8 != 0 { + size++ + } + return size +} + +func lengthPrefixed(data []byte) []byte { + out := make([]byte, len(data)+4) + binary.BigEndian.PutUint32(out, uint32(len(data))) + copy(out[4:], data) + return out +} diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go b/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go new file mode 100644 index 00000000..b9effbca --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go @@ -0,0 +1,109 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package josecipher + +import ( + "crypto/cipher" + "crypto/subtle" + "encoding/binary" + "errors" +) + +var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6} + +// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher. +func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) { + if len(cek)%8 != 0 { + return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks") + } + + n := len(cek) / 8 + r := make([][]byte, n) + + for i := range r { + r[i] = make([]byte, 8) + copy(r[i], cek[i*8:]) + } + + buffer := make([]byte, 16) + tBytes := make([]byte, 8) + copy(buffer, defaultIV) + + for t := 0; t < 6*n; t++ { + copy(buffer[8:], r[t%n]) + + block.Encrypt(buffer, buffer) + + binary.BigEndian.PutUint64(tBytes, uint64(t+1)) + + for i := 0; i < 8; i++ { + buffer[i] ^= tBytes[i] + } + copy(r[t%n], buffer[8:]) + } + + out := make([]byte, (n+1)*8) + copy(out, buffer[:8]) + for i := range r { + copy(out[(i+1)*8:], r[i]) + } + + return out, nil +} + +// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher. +func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) { + if len(ciphertext)%8 != 0 { + return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks") + } + + n := (len(ciphertext) / 8) - 1 + r := make([][]byte, n) + + for i := range r { + r[i] = make([]byte, 8) + copy(r[i], ciphertext[(i+1)*8:]) + } + + buffer := make([]byte, 16) + tBytes := make([]byte, 8) + copy(buffer[:8], ciphertext[:8]) + + for t := 6*n - 1; t >= 0; t-- { + binary.BigEndian.PutUint64(tBytes, uint64(t+1)) + + for i := 0; i < 8; i++ { + buffer[i] ^= tBytes[i] + } + copy(buffer[8:], r[t%n]) + + block.Decrypt(buffer, buffer) + + copy(r[t%n], buffer[8:]) + } + + if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 { + return nil, errors.New("go-jose/go-jose: failed to unwrap key") + } + + out := make([]byte, n*8) + for i := range r { + copy(out[i*8:], r[i]) + } + + return out, nil +} diff --git a/vendor/github.com/go-jose/go-jose/v4/crypter.go b/vendor/github.com/go-jose/go-jose/v4/crypter.go new file mode 100644 index 00000000..d81b03b4 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/crypter.go @@ -0,0 +1,599 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +import ( + "crypto/ecdsa" + "crypto/rsa" + "errors" + "fmt" + + "github.com/go-jose/go-jose/v4/json" +) + +// Encrypter represents an encrypter which produces an encrypted JWE object. +type Encrypter interface { + Encrypt(plaintext []byte) (*JSONWebEncryption, error) + EncryptWithAuthData(plaintext []byte, aad []byte) (*JSONWebEncryption, error) + Options() EncrypterOptions +} + +// A generic content cipher +type contentCipher interface { + keySize() int + encrypt(cek []byte, aad, plaintext []byte) (*aeadParts, error) + decrypt(cek []byte, aad []byte, parts *aeadParts) ([]byte, error) +} + +// A key generator (for generating/getting a CEK) +type keyGenerator interface { + keySize() int + genKey() ([]byte, rawHeader, error) +} + +// A generic key encrypter +type keyEncrypter interface { + encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) // Encrypt a key +} + +// A generic key decrypter +type keyDecrypter interface { + decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) // Decrypt a key +} + +// A generic encrypter based on the given key encrypter and content cipher. +type genericEncrypter struct { + contentAlg ContentEncryption + compressionAlg CompressionAlgorithm + cipher contentCipher + recipients []recipientKeyInfo + keyGenerator keyGenerator + extraHeaders map[HeaderKey]interface{} +} + +type recipientKeyInfo struct { + keyID string + keyAlg KeyAlgorithm + keyEncrypter keyEncrypter +} + +// EncrypterOptions represents options that can be set on new encrypters. +type EncrypterOptions struct { + Compression CompressionAlgorithm + + // Optional map of name/value pairs to be inserted into the protected + // header of a JWS object. Some specifications which make use of + // JWS require additional values here. + // + // Values will be serialized by [json.Marshal] and must be valid inputs to + // that function. + // + // [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal + ExtraHeaders map[HeaderKey]interface{} +} + +// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it +// if necessary, and returns the updated EncrypterOptions. +// +// The v parameter will be serialized by [json.Marshal] and must be a valid +// input to that function. +// +// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal +func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions { + if eo.ExtraHeaders == nil { + eo.ExtraHeaders = map[HeaderKey]interface{}{} + } + eo.ExtraHeaders[k] = v + return eo +} + +// WithContentType adds a content type ("cty") header and returns the updated +// EncrypterOptions. +func (eo *EncrypterOptions) WithContentType(contentType ContentType) *EncrypterOptions { + return eo.WithHeader(HeaderContentType, contentType) +} + +// WithType adds a type ("typ") header and returns the updated EncrypterOptions. +func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions { + return eo.WithHeader(HeaderType, typ) +} + +// Recipient represents an algorithm/key to encrypt messages to. +// +// PBES2Count and PBES2Salt correspond with the "p2c" and "p2s" headers used +// on the password-based encryption algorithms PBES2-HS256+A128KW, +// PBES2-HS384+A192KW, and PBES2-HS512+A256KW. If they are not provided a safe +// default of 100000 will be used for the count and a 128-bit random salt will +// be generated. +type Recipient struct { + Algorithm KeyAlgorithm + // Key must have one of these types: + // - ed25519.PublicKey + // - *ecdsa.PublicKey + // - *rsa.PublicKey + // - *JSONWebKey + // - JSONWebKey + // - []byte (a symmetric key) + // - Any type that satisfies the OpaqueKeyEncrypter interface + // + // The type of Key must match the value of Algorithm. + Key interface{} + KeyID string + PBES2Count int + PBES2Salt []byte +} + +// NewEncrypter creates an appropriate encrypter based on the key type +func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions) (Encrypter, error) { + encrypter := &genericEncrypter{ + contentAlg: enc, + recipients: []recipientKeyInfo{}, + cipher: getContentCipher(enc), + } + if opts != nil { + encrypter.compressionAlg = opts.Compression + encrypter.extraHeaders = opts.ExtraHeaders + } + + if encrypter.cipher == nil { + return nil, ErrUnsupportedAlgorithm + } + + var keyID string + var rawKey interface{} + switch encryptionKey := rcpt.Key.(type) { + case JSONWebKey: + keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key + case *JSONWebKey: + keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key + case OpaqueKeyEncrypter: + keyID, rawKey = encryptionKey.KeyID(), encryptionKey + default: + rawKey = encryptionKey + } + + switch rcpt.Algorithm { + case DIRECT: + // Direct encryption mode must be treated differently + keyBytes, ok := rawKey.([]byte) + if !ok { + return nil, ErrUnsupportedKeyType + } + if encrypter.cipher.keySize() != len(keyBytes) { + return nil, ErrInvalidKeySize + } + encrypter.keyGenerator = staticKeyGenerator{ + key: keyBytes, + } + recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, keyBytes) + recipientInfo.keyID = keyID + if rcpt.KeyID != "" { + recipientInfo.keyID = rcpt.KeyID + } + encrypter.recipients = []recipientKeyInfo{recipientInfo} + return encrypter, nil + case ECDH_ES: + // ECDH-ES (w/o key wrapping) is similar to DIRECT mode + keyDSA, ok := rawKey.(*ecdsa.PublicKey) + if !ok { + return nil, ErrUnsupportedKeyType + } + encrypter.keyGenerator = ecKeyGenerator{ + size: encrypter.cipher.keySize(), + algID: string(enc), + publicKey: keyDSA, + } + recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, keyDSA) + recipientInfo.keyID = keyID + if rcpt.KeyID != "" { + recipientInfo.keyID = rcpt.KeyID + } + encrypter.recipients = []recipientKeyInfo{recipientInfo} + return encrypter, nil + default: + // Can just add a standard recipient + encrypter.keyGenerator = randomKeyGenerator{ + size: encrypter.cipher.keySize(), + } + err := encrypter.addRecipient(rcpt) + return encrypter, err + } +} + +// NewMultiEncrypter creates a multi-encrypter based on the given parameters +func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *EncrypterOptions) (Encrypter, error) { + cipher := getContentCipher(enc) + + if cipher == nil { + return nil, ErrUnsupportedAlgorithm + } + if len(rcpts) == 0 { + return nil, fmt.Errorf("go-jose/go-jose: recipients is nil or empty") + } + + encrypter := &genericEncrypter{ + contentAlg: enc, + recipients: []recipientKeyInfo{}, + cipher: cipher, + keyGenerator: randomKeyGenerator{ + size: cipher.keySize(), + }, + } + + if opts != nil { + encrypter.compressionAlg = opts.Compression + encrypter.extraHeaders = opts.ExtraHeaders + } + + for _, recipient := range rcpts { + err := encrypter.addRecipient(recipient) + if err != nil { + return nil, err + } + } + + return encrypter, nil +} + +func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) { + var recipientInfo recipientKeyInfo + + switch recipient.Algorithm { + case DIRECT, ECDH_ES: + return fmt.Errorf("go-jose/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm) + } + + recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key) + if recipient.KeyID != "" { + recipientInfo.keyID = recipient.KeyID + } + + switch recipient.Algorithm { + case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW: + if sr, ok := recipientInfo.keyEncrypter.(*symmetricKeyCipher); ok { + sr.p2c = recipient.PBES2Count + sr.p2s = recipient.PBES2Salt + } + } + + if err == nil { + ctx.recipients = append(ctx.recipients, recipientInfo) + } + return err +} + +func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) { + switch encryptionKey := encryptionKey.(type) { + case *rsa.PublicKey: + return newRSARecipient(alg, encryptionKey) + case *ecdsa.PublicKey: + return newECDHRecipient(alg, encryptionKey) + case []byte: + return newSymmetricRecipient(alg, encryptionKey) + case string: + return newSymmetricRecipient(alg, []byte(encryptionKey)) + case *JSONWebKey: + recipient, err := makeJWERecipient(alg, encryptionKey.Key) + recipient.keyID = encryptionKey.KeyID + return recipient, err + case OpaqueKeyEncrypter: + return newOpaqueKeyEncrypter(alg, encryptionKey) + } + return recipientKeyInfo{}, ErrUnsupportedKeyType +} + +// newDecrypter creates an appropriate decrypter based on the key type +func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) { + switch decryptionKey := decryptionKey.(type) { + case *rsa.PrivateKey: + return &rsaDecrypterSigner{ + privateKey: decryptionKey, + }, nil + case *ecdsa.PrivateKey: + return &ecDecrypterSigner{ + privateKey: decryptionKey, + }, nil + case []byte: + return &symmetricKeyCipher{ + key: decryptionKey, + }, nil + case string: + return &symmetricKeyCipher{ + key: []byte(decryptionKey), + }, nil + case JSONWebKey: + return newDecrypter(decryptionKey.Key) + case *JSONWebKey: + return newDecrypter(decryptionKey.Key) + case OpaqueKeyDecrypter: + return &opaqueKeyDecrypter{decrypter: decryptionKey}, nil + default: + return nil, ErrUnsupportedKeyType + } +} + +// Implementation of encrypt method producing a JWE object. +func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JSONWebEncryption, error) { + return ctx.EncryptWithAuthData(plaintext, nil) +} + +// Implementation of encrypt method producing a JWE object. +func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWebEncryption, error) { + obj := &JSONWebEncryption{} + obj.aad = aad + + obj.protected = &rawHeader{} + err := obj.protected.set(headerEncryption, ctx.contentAlg) + if err != nil { + return nil, err + } + + obj.recipients = make([]recipientInfo, len(ctx.recipients)) + + if len(ctx.recipients) == 0 { + return nil, fmt.Errorf("go-jose/go-jose: no recipients to encrypt to") + } + + cek, headers, err := ctx.keyGenerator.genKey() + if err != nil { + return nil, err + } + + obj.protected.merge(&headers) + + for i, info := range ctx.recipients { + recipient, err := info.keyEncrypter.encryptKey(cek, info.keyAlg) + if err != nil { + return nil, err + } + + err = recipient.header.set(headerAlgorithm, info.keyAlg) + if err != nil { + return nil, err + } + + if info.keyID != "" { + err = recipient.header.set(headerKeyID, info.keyID) + if err != nil { + return nil, err + } + } + obj.recipients[i] = recipient + } + + if len(ctx.recipients) == 1 { + // Move per-recipient headers into main protected header if there's + // only a single recipient. + obj.protected.merge(obj.recipients[0].header) + obj.recipients[0].header = nil + } + + if ctx.compressionAlg != NONE { + plaintext, err = compress(ctx.compressionAlg, plaintext) + if err != nil { + return nil, err + } + + err = obj.protected.set(headerCompression, ctx.compressionAlg) + if err != nil { + return nil, err + } + } + + for k, v := range ctx.extraHeaders { + b, err := json.Marshal(v) + if err != nil { + return nil, err + } + (*obj.protected)[k] = makeRawMessage(b) + } + + authData := obj.computeAuthData() + parts, err := ctx.cipher.encrypt(cek, authData, plaintext) + if err != nil { + return nil, err + } + + obj.iv = parts.iv + obj.ciphertext = parts.ciphertext + obj.tag = parts.tag + + return obj, nil +} + +func (ctx *genericEncrypter) Options() EncrypterOptions { + return EncrypterOptions{ + Compression: ctx.compressionAlg, + ExtraHeaders: ctx.extraHeaders, + } +} + +// Decrypt and validate the object and return the plaintext. This +// function does not support multi-recipient. If you desire multi-recipient +// decryption use DecryptMulti instead. +// +// The decryptionKey argument must contain a private or symmetric key +// and must have one of these types: +// - *ecdsa.PrivateKey +// - *rsa.PrivateKey +// - *JSONWebKey +// - JSONWebKey +// - *JSONWebKeySet +// - JSONWebKeySet +// - []byte (a symmetric key) +// - string (a symmetric key) +// - Any type that satisfies the OpaqueKeyDecrypter interface. +// +// Note that ed25519 is only available for signatures, not encryption, so is +// not an option here. +// +// Automatically decompresses plaintext, but returns an error if the decompressed +// data would be >250kB or >10x the size of the compressed data, whichever is larger. +func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) { + headers := obj.mergedHeaders(nil) + + if len(obj.recipients) > 1 { + return nil, errors.New("go-jose/go-jose: too many recipients in payload; expecting only one") + } + + critical, err := headers.getCritical() + if err != nil { + return nil, fmt.Errorf("go-jose/go-jose: invalid crit header") + } + + if len(critical) > 0 { + return nil, fmt.Errorf("go-jose/go-jose: unsupported crit header") + } + + key, err := tryJWKS(decryptionKey, obj.Header) + if err != nil { + return nil, err + } + decrypter, err := newDecrypter(key) + if err != nil { + return nil, err + } + + cipher := getContentCipher(headers.getEncryption()) + if cipher == nil { + return nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(headers.getEncryption())) + } + + generator := randomKeyGenerator{ + size: cipher.keySize(), + } + + parts := &aeadParts{ + iv: obj.iv, + ciphertext: obj.ciphertext, + tag: obj.tag, + } + + authData := obj.computeAuthData() + + var plaintext []byte + recipient := obj.recipients[0] + recipientHeaders := obj.mergedHeaders(&recipient) + + cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator) + if err == nil { + // Found a valid CEK -- let's try to decrypt. + plaintext, err = cipher.decrypt(cek, authData, parts) + } + + if plaintext == nil { + return nil, ErrCryptoFailure + } + + // The "zip" header parameter may only be present in the protected header. + if comp := obj.protected.getCompression(); comp != "" { + plaintext, err = decompress(comp, plaintext) + if err != nil { + return nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err) + } + } + + return plaintext, nil +} + +// DecryptMulti decrypts and validates the object and returns the plaintexts, +// with support for multiple recipients. It returns the index of the recipient +// for which the decryption was successful, the merged headers for that recipient, +// and the plaintext. +// +// The decryptionKey argument must have one of the types allowed for the +// decryptionKey argument of Decrypt(). +// +// Automatically decompresses plaintext, but returns an error if the decompressed +// data would be >250kB or >3x the size of the compressed data, whichever is larger. +func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) { + globalHeaders := obj.mergedHeaders(nil) + + critical, err := globalHeaders.getCritical() + if err != nil { + return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: invalid crit header") + } + + if len(critical) > 0 { + return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported crit header") + } + + key, err := tryJWKS(decryptionKey, obj.Header) + if err != nil { + return -1, Header{}, nil, err + } + decrypter, err := newDecrypter(key) + if err != nil { + return -1, Header{}, nil, err + } + + encryption := globalHeaders.getEncryption() + cipher := getContentCipher(encryption) + if cipher == nil { + return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(encryption)) + } + + generator := randomKeyGenerator{ + size: cipher.keySize(), + } + + parts := &aeadParts{ + iv: obj.iv, + ciphertext: obj.ciphertext, + tag: obj.tag, + } + + authData := obj.computeAuthData() + + index := -1 + var plaintext []byte + var headers rawHeader + + for i, recipient := range obj.recipients { + recipientHeaders := obj.mergedHeaders(&recipient) + + cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator) + if err == nil { + // Found a valid CEK -- let's try to decrypt. + plaintext, err = cipher.decrypt(cek, authData, parts) + if err == nil { + index = i + headers = recipientHeaders + break + } + } + } + + if plaintext == nil { + return -1, Header{}, nil, ErrCryptoFailure + } + + // The "zip" header parameter may only be present in the protected header. + if comp := obj.protected.getCompression(); comp != "" { + plaintext, err = decompress(comp, plaintext) + if err != nil { + return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err) + } + } + + sanitized, err := headers.sanitized() + if err != nil { + return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to sanitize header: %v", err) + } + + return index, sanitized, plaintext, err +} diff --git a/vendor/github.com/go-jose/go-jose/v4/doc.go b/vendor/github.com/go-jose/go-jose/v4/doc.go new file mode 100644 index 00000000..0ad40ca0 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/doc.go @@ -0,0 +1,25 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* +Package jose aims to provide an implementation of the Javascript Object Signing +and Encryption set of standards. It implements encryption and signing based on +the JSON Web Encryption and JSON Web Signature standards, with optional JSON Web +Token support available in a sub-package. The library supports both the compact +and JWS/JWE JSON Serialization formats, and has optional support for multiple +recipients. +*/ +package jose diff --git a/vendor/github.com/go-jose/go-jose/v4/encoding.go b/vendor/github.com/go-jose/go-jose/v4/encoding.go new file mode 100644 index 00000000..4f6e0d4a --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/encoding.go @@ -0,0 +1,228 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +import ( + "bytes" + "compress/flate" + "encoding/base64" + "encoding/binary" + "fmt" + "io" + "math/big" + "strings" + "unicode" + + "github.com/go-jose/go-jose/v4/json" +) + +// Helper function to serialize known-good objects. +// Precondition: value is not a nil pointer. +func mustSerializeJSON(value interface{}) []byte { + out, err := json.Marshal(value) + if err != nil { + panic(err) + } + // We never want to serialize the top-level value "null," since it's not a + // valid JOSE message. But if a caller passes in a nil pointer to this method, + // MarshalJSON will happily serialize it as the top-level value "null". If + // that value is then embedded in another operation, for instance by being + // base64-encoded and fed as input to a signing algorithm + // (https://github.com/go-jose/go-jose/issues/22), the result will be + // incorrect. Because this method is intended for known-good objects, and a nil + // pointer is not a known-good object, we are free to panic in this case. + // Note: It's not possible to directly check whether the data pointed at by an + // interface is a nil pointer, so we do this hacky workaround. + // https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I + if string(out) == "null" { + panic("Tried to serialize a nil pointer.") + } + return out +} + +// Strip all newlines and whitespace +func stripWhitespace(data string) string { + buf := strings.Builder{} + buf.Grow(len(data)) + for _, r := range data { + if !unicode.IsSpace(r) { + buf.WriteRune(r) + } + } + return buf.String() +} + +// Perform compression based on algorithm +func compress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) { + switch algorithm { + case DEFLATE: + return deflate(input) + default: + return nil, ErrUnsupportedAlgorithm + } +} + +// Perform decompression based on algorithm +func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) { + switch algorithm { + case DEFLATE: + return inflate(input) + default: + return nil, ErrUnsupportedAlgorithm + } +} + +// deflate compresses the input. +func deflate(input []byte) ([]byte, error) { + output := new(bytes.Buffer) + + // Writing to byte buffer, err is always nil + writer, _ := flate.NewWriter(output, 1) + _, _ = io.Copy(writer, bytes.NewBuffer(input)) + + err := writer.Close() + return output.Bytes(), err +} + +// inflate decompresses the input. +// +// Errors if the decompressed data would be >250kB or >10x the size of the +// compressed data, whichever is larger. +func inflate(input []byte) ([]byte, error) { + output := new(bytes.Buffer) + reader := flate.NewReader(bytes.NewBuffer(input)) + + maxCompressedSize := max(250_000, 10*int64(len(input))) + + limit := maxCompressedSize + 1 + n, err := io.CopyN(output, reader, limit) + if err != nil && err != io.EOF { + return nil, err + } + if n == limit { + return nil, fmt.Errorf("uncompressed data would be too large (>%d bytes)", maxCompressedSize) + } + + err = reader.Close() + return output.Bytes(), err +} + +// byteBuffer represents a slice of bytes that can be serialized to url-safe base64. +type byteBuffer struct { + data []byte +} + +func newBuffer(data []byte) *byteBuffer { + if data == nil { + return nil + } + return &byteBuffer{ + data: data, + } +} + +func newFixedSizeBuffer(data []byte, length int) *byteBuffer { + if len(data) > length { + panic("go-jose/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)") + } + pad := make([]byte, length-len(data)) + return newBuffer(append(pad, data...)) +} + +func newBufferFromInt(num uint64) *byteBuffer { + data := make([]byte, 8) + binary.BigEndian.PutUint64(data, num) + return newBuffer(bytes.TrimLeft(data, "\x00")) +} + +func (b *byteBuffer) MarshalJSON() ([]byte, error) { + return json.Marshal(b.base64()) +} + +func (b *byteBuffer) UnmarshalJSON(data []byte) error { + var encoded string + err := json.Unmarshal(data, &encoded) + if err != nil { + return err + } + + if encoded == "" { + return nil + } + + decoded, err := base64.RawURLEncoding.DecodeString(encoded) + if err != nil { + return err + } + + *b = *newBuffer(decoded) + + return nil +} + +func (b *byteBuffer) base64() string { + return base64.RawURLEncoding.EncodeToString(b.data) +} + +func (b *byteBuffer) bytes() []byte { + // Handling nil here allows us to transparently handle nil slices when serializing. + if b == nil { + return nil + } + return b.data +} + +func (b byteBuffer) bigInt() *big.Int { + return new(big.Int).SetBytes(b.data) +} + +func (b byteBuffer) toInt() int { + return int(b.bigInt().Int64()) +} + +func base64EncodeLen(sl []byte) int { + return base64.RawURLEncoding.EncodedLen(len(sl)) +} + +func base64JoinWithDots(inputs ...[]byte) string { + if len(inputs) == 0 { + return "" + } + + // Count of dots. + totalCount := len(inputs) - 1 + + for _, input := range inputs { + totalCount += base64EncodeLen(input) + } + + out := make([]byte, totalCount) + startEncode := 0 + for i, input := range inputs { + base64.RawURLEncoding.Encode(out[startEncode:], input) + + if i == len(inputs)-1 { + continue + } + + startEncode += base64EncodeLen(input) + out[startEncode] = '.' + startEncode++ + } + + return string(out) +} diff --git a/vendor/github.com/go-jose/go-jose/v4/json/LICENSE b/vendor/github.com/go-jose/go-jose/v4/json/LICENSE new file mode 100644 index 00000000..74487567 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/json/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/go-jose/go-jose/v4/json/README.md b/vendor/github.com/go-jose/go-jose/v4/json/README.md new file mode 100644 index 00000000..86de5e55 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/json/README.md @@ -0,0 +1,13 @@ +# Safe JSON + +This repository contains a fork of the `encoding/json` package from Go 1.6. + +The following changes were made: + +* Object deserialization uses case-sensitive member name matching instead of + [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html). + This is to avoid differences in the interpretation of JOSE messages between + go-jose and libraries written in other languages. +* When deserializing a JSON object, we check for duplicate keys and reject the + input whenever we detect a duplicate. Rather than trying to work with malformed + data, we prefer to reject it right away. diff --git a/vendor/github.com/go-jose/go-jose/v4/json/decode.go b/vendor/github.com/go-jose/go-jose/v4/json/decode.go new file mode 100644 index 00000000..50634dd8 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/json/decode.go @@ -0,0 +1,1216 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Represents JSON data structure using native Go types: booleans, floats, +// strings, arrays, and maps. + +package json + +import ( + "bytes" + "encoding" + "encoding/base64" + "errors" + "fmt" + "math" + "reflect" + "runtime" + "strconv" + "unicode" + "unicode/utf16" + "unicode/utf8" +) + +// Unmarshal parses the JSON-encoded data and stores the result +// in the value pointed to by v. +// +// Unmarshal uses the inverse of the encodings that +// Marshal uses, allocating maps, slices, and pointers as necessary, +// with the following additional rules: +// +// To unmarshal JSON into a pointer, Unmarshal first handles the case of +// the JSON being the JSON literal null. In that case, Unmarshal sets +// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into +// the value pointed at by the pointer. If the pointer is nil, Unmarshal +// allocates a new value for it to point to. +// +// To unmarshal JSON into a struct, Unmarshal matches incoming object +// keys to the keys used by Marshal (either the struct field name or its tag), +// preferring an exact match but also accepting a case-insensitive match. +// Unmarshal will only set exported fields of the struct. +// +// To unmarshal JSON into an interface value, +// Unmarshal stores one of these in the interface value: +// +// bool, for JSON booleans +// float64, for JSON numbers +// string, for JSON strings +// []interface{}, for JSON arrays +// map[string]interface{}, for JSON objects +// nil for JSON null +// +// To unmarshal a JSON array into a slice, Unmarshal resets the slice length +// to zero and then appends each element to the slice. +// As a special case, to unmarshal an empty JSON array into a slice, +// Unmarshal replaces the slice with a new empty slice. +// +// To unmarshal a JSON array into a Go array, Unmarshal decodes +// JSON array elements into corresponding Go array elements. +// If the Go array is smaller than the JSON array, +// the additional JSON array elements are discarded. +// If the JSON array is smaller than the Go array, +// the additional Go array elements are set to zero values. +// +// To unmarshal a JSON object into a string-keyed map, Unmarshal first +// establishes a map to use, If the map is nil, Unmarshal allocates a new map. +// Otherwise Unmarshal reuses the existing map, keeping existing entries. +// Unmarshal then stores key-value pairs from the JSON object into the map. +// +// If a JSON value is not appropriate for a given target type, +// or if a JSON number overflows the target type, Unmarshal +// skips that field and completes the unmarshaling as best it can. +// If no more serious errors are encountered, Unmarshal returns +// an UnmarshalTypeError describing the earliest such error. +// +// The JSON null value unmarshals into an interface, map, pointer, or slice +// by setting that Go value to nil. Because null is often used in JSON to mean +// “not present,” unmarshaling a JSON null into any other Go type has no effect +// on the value and produces no error. +// +// When unmarshaling quoted strings, invalid UTF-8 or +// invalid UTF-16 surrogate pairs are not treated as an error. +// Instead, they are replaced by the Unicode replacement +// character U+FFFD. +func Unmarshal(data []byte, v interface{}) error { + // Check for well-formedness. + // Avoids filling out half a data structure + // before discovering a JSON syntax error. + var d decodeState + err := checkValid(data, &d.scan) + if err != nil { + return err + } + + d.init(data) + return d.unmarshal(v) +} + +// Unmarshaler is the interface implemented by objects +// that can unmarshal a JSON description of themselves. +// The input can be assumed to be a valid encoding of +// a JSON value. UnmarshalJSON must copy the JSON data +// if it wishes to retain the data after returning. +type Unmarshaler interface { + UnmarshalJSON([]byte) error +} + +// An UnmarshalTypeError describes a JSON value that was +// not appropriate for a value of a specific Go type. +type UnmarshalTypeError struct { + Value string // description of JSON value - "bool", "array", "number -5" + Type reflect.Type // type of Go value it could not be assigned to + Offset int64 // error occurred after reading Offset bytes +} + +func (e *UnmarshalTypeError) Error() string { + return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() +} + +// An UnmarshalFieldError describes a JSON object key that +// led to an unexported (and therefore unwritable) struct field. +// (No longer used; kept for compatibility.) +type UnmarshalFieldError struct { + Key string + Type reflect.Type + Field reflect.StructField +} + +func (e *UnmarshalFieldError) Error() string { + return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() +} + +// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. +// (The argument to Unmarshal must be a non-nil pointer.) +type InvalidUnmarshalError struct { + Type reflect.Type +} + +func (e *InvalidUnmarshalError) Error() string { + if e.Type == nil { + return "json: Unmarshal(nil)" + } + + if e.Type.Kind() != reflect.Ptr { + return "json: Unmarshal(non-pointer " + e.Type.String() + ")" + } + return "json: Unmarshal(nil " + e.Type.String() + ")" +} + +func (d *decodeState) unmarshal(v interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return &InvalidUnmarshalError{reflect.TypeOf(v)} + } + + d.scan.reset() + // We decode rv not rv.Elem because the Unmarshaler interface + // test must be applied at the top level of the value. + d.value(rv) + return d.savedError +} + +// A Number represents a JSON number literal. +type Number string + +// String returns the literal text of the number. +func (n Number) String() string { return string(n) } + +// Float64 returns the number as a float64. +func (n Number) Float64() (float64, error) { + return strconv.ParseFloat(string(n), 64) +} + +// Int64 returns the number as an int64. +func (n Number) Int64() (int64, error) { + return strconv.ParseInt(string(n), 10, 64) +} + +// isValidNumber reports whether s is a valid JSON number literal. +func isValidNumber(s string) bool { + // This function implements the JSON numbers grammar. + // See https://tools.ietf.org/html/rfc7159#section-6 + // and http://json.org/number.gif + + if s == "" { + return false + } + + // Optional - + if s[0] == '-' { + s = s[1:] + if s == "" { + return false + } + } + + // Digits + switch { + default: + return false + + case s[0] == '0': + s = s[1:] + + case '1' <= s[0] && s[0] <= '9': + s = s[1:] + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + } + } + + // . followed by 1 or more digits. + if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' { + s = s[2:] + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + } + } + + // e or E followed by an optional - or + and + // 1 or more digits. + if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { + s = s[1:] + if s[0] == '+' || s[0] == '-' { + s = s[1:] + if s == "" { + return false + } + } + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + } + } + + // Make sure we are at the end. + return s == "" +} + +type NumberUnmarshalType int + +const ( + // unmarshal a JSON number into an interface{} as a float64 + UnmarshalFloat NumberUnmarshalType = iota + // unmarshal a JSON number into an interface{} as a `json.Number` + UnmarshalJSONNumber + // unmarshal a JSON number into an interface{} as a int64 + // if value is an integer otherwise float64 + UnmarshalIntOrFloat +) + +// decodeState represents the state while decoding a JSON value. +type decodeState struct { + data []byte + off int // read offset in data + scan scanner + nextscan scanner // for calls to nextValue + savedError error + numberType NumberUnmarshalType +} + +// errPhase is used for errors that should not happen unless +// there is a bug in the JSON decoder or something is editing +// the data slice while the decoder executes. +var errPhase = errors.New("JSON decoder out of sync - data changing underfoot?") + +func (d *decodeState) init(data []byte) *decodeState { + d.data = data + d.off = 0 + d.savedError = nil + return d +} + +// error aborts the decoding by panicking with err. +func (d *decodeState) error(err error) { + panic(err) +} + +// saveError saves the first err it is called with, +// for reporting at the end of the unmarshal. +func (d *decodeState) saveError(err error) { + if d.savedError == nil { + d.savedError = err + } +} + +// next cuts off and returns the next full JSON value in d.data[d.off:]. +// The next value is known to be an object or array, not a literal. +func (d *decodeState) next() []byte { + c := d.data[d.off] + item, rest, err := nextValue(d.data[d.off:], &d.nextscan) + if err != nil { + d.error(err) + } + d.off = len(d.data) - len(rest) + + // Our scanner has seen the opening brace/bracket + // and thinks we're still in the middle of the object. + // invent a closing brace/bracket to get it out. + if c == '{' { + d.scan.step(&d.scan, '}') + } else { + d.scan.step(&d.scan, ']') + } + + return item +} + +// scanWhile processes bytes in d.data[d.off:] until it +// receives a scan code not equal to op. +// It updates d.off and returns the new scan code. +func (d *decodeState) scanWhile(op int) int { + var newOp int + for { + if d.off >= len(d.data) { + newOp = d.scan.eof() + d.off = len(d.data) + 1 // mark processed EOF with len+1 + } else { + c := d.data[d.off] + d.off++ + newOp = d.scan.step(&d.scan, c) + } + if newOp != op { + break + } + } + return newOp +} + +// value decodes a JSON value from d.data[d.off:] into the value. +// it updates d.off to point past the decoded value. +func (d *decodeState) value(v reflect.Value) { + if !v.IsValid() { + _, rest, err := nextValue(d.data[d.off:], &d.nextscan) + if err != nil { + d.error(err) + } + d.off = len(d.data) - len(rest) + + // d.scan thinks we're still at the beginning of the item. + // Feed in an empty string - the shortest, simplest value - + // so that it knows we got to the end of the value. + if d.scan.redo { + // rewind. + d.scan.redo = false + d.scan.step = stateBeginValue + } + d.scan.step(&d.scan, '"') + d.scan.step(&d.scan, '"') + + n := len(d.scan.parseState) + if n > 0 && d.scan.parseState[n-1] == parseObjectKey { + // d.scan thinks we just read an object key; finish the object + d.scan.step(&d.scan, ':') + d.scan.step(&d.scan, '"') + d.scan.step(&d.scan, '"') + d.scan.step(&d.scan, '}') + } + + return + } + + switch op := d.scanWhile(scanSkipSpace); op { + default: + d.error(errPhase) + + case scanBeginArray: + d.array(v) + + case scanBeginObject: + d.object(v) + + case scanBeginLiteral: + d.literal(v) + } +} + +type unquotedValue struct{} + +// valueQuoted is like value but decodes a +// quoted string literal or literal null into an interface value. +// If it finds anything other than a quoted string literal or null, +// valueQuoted returns unquotedValue{}. +func (d *decodeState) valueQuoted() interface{} { + switch op := d.scanWhile(scanSkipSpace); op { + default: + d.error(errPhase) + + case scanBeginArray: + d.array(reflect.Value{}) + + case scanBeginObject: + d.object(reflect.Value{}) + + case scanBeginLiteral: + switch v := d.literalInterface().(type) { + case nil, string: + return v + } + } + return unquotedValue{} +} + +// indirect walks down v allocating pointers as needed, +// until it gets to a non-pointer. +// if it encounters an Unmarshaler, indirect stops and returns that. +// if decodingNull is true, indirect stops at the last pointer so it can be set to nil. +func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { + // If v is a named type and is addressable, + // start with its address, so that if the type has pointer methods, + // we find them. + if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { + v = v.Addr() + } + for { + // Load value from interface, but only if the result will be + // usefully addressable. + if v.Kind() == reflect.Interface && !v.IsNil() { + e := v.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { + v = e + continue + } + } + + if v.Kind() != reflect.Ptr { + break + } + + if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { + break + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + if v.Type().NumMethod() > 0 { + if u, ok := v.Interface().(Unmarshaler); ok { + return u, nil, reflect.Value{} + } + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return nil, u, reflect.Value{} + } + } + v = v.Elem() + } + return nil, nil, v +} + +// array consumes an array from d.data[d.off-1:], decoding into the value v. +// the first byte of the array ('[') has been read already. +func (d *decodeState) array(v reflect.Value) { + // Check for unmarshaler. + u, ut, pv := d.indirect(v, false) + if u != nil { + d.off-- + err := u.UnmarshalJSON(d.next()) + if err != nil { + d.error(err) + } + return + } + if ut != nil { + d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)}) + d.off-- + d.next() + return + } + + v = pv + + // Check type of target. + switch v.Kind() { + case reflect.Interface: + if v.NumMethod() == 0 { + // Decoding into nil interface? Switch to non-reflect code. + v.Set(reflect.ValueOf(d.arrayInterface())) + return + } + // Otherwise it's invalid. + fallthrough + default: + d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)}) + d.off-- + d.next() + return + case reflect.Array: + case reflect.Slice: + break + } + + i := 0 + for { + // Look ahead for ] - can only happen on first iteration. + op := d.scanWhile(scanSkipSpace) + if op == scanEndArray { + break + } + + // Back up so d.value can have the byte we just read. + d.off-- + d.scan.undo(op) + + // Get element of array, growing if necessary. + if v.Kind() == reflect.Slice { + // Grow slice if necessary + if i >= v.Cap() { + newcap := v.Cap() + v.Cap()/2 + if newcap < 4 { + newcap = 4 + } + newv := reflect.MakeSlice(v.Type(), v.Len(), newcap) + reflect.Copy(newv, v) + v.Set(newv) + } + if i >= v.Len() { + v.SetLen(i + 1) + } + } + + if i < v.Len() { + // Decode into element. + d.value(v.Index(i)) + } else { + // Ran out of fixed array: skip. + d.value(reflect.Value{}) + } + i++ + + // Next token must be , or ]. + op = d.scanWhile(scanSkipSpace) + if op == scanEndArray { + break + } + if op != scanArrayValue { + d.error(errPhase) + } + } + + if i < v.Len() { + if v.Kind() == reflect.Array { + // Array. Zero the rest. + z := reflect.Zero(v.Type().Elem()) + for ; i < v.Len(); i++ { + v.Index(i).Set(z) + } + } else { + v.SetLen(i) + } + } + if i == 0 && v.Kind() == reflect.Slice { + v.Set(reflect.MakeSlice(v.Type(), 0, 0)) + } +} + +var nullLiteral = []byte("null") + +// object consumes an object from d.data[d.off-1:], decoding into the value v. +// the first byte ('{') of the object has been read already. +func (d *decodeState) object(v reflect.Value) { + // Check for unmarshaler. + u, ut, pv := d.indirect(v, false) + if u != nil { + d.off-- + err := u.UnmarshalJSON(d.next()) + if err != nil { + d.error(err) + } + return + } + if ut != nil { + d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) + d.off-- + d.next() // skip over { } in input + return + } + v = pv + + // Decoding into nil interface? Switch to non-reflect code. + if v.Kind() == reflect.Interface && v.NumMethod() == 0 { + v.Set(reflect.ValueOf(d.objectInterface())) + return + } + + // Check type of target: struct or map[string]T + switch v.Kind() { + case reflect.Map: + // map must have string kind + t := v.Type() + if t.Key().Kind() != reflect.String { + d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) + d.off-- + d.next() // skip over { } in input + return + } + if v.IsNil() { + v.Set(reflect.MakeMap(t)) + } + case reflect.Struct: + + default: + d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) + d.off-- + d.next() // skip over { } in input + return + } + + var mapElem reflect.Value + keys := map[string]bool{} + + for { + // Read opening " of string key or closing }. + op := d.scanWhile(scanSkipSpace) + if op == scanEndObject { + // closing } - can only happen on first iteration. + break + } + if op != scanBeginLiteral { + d.error(errPhase) + } + + // Read key. + start := d.off - 1 + op = d.scanWhile(scanContinue) + item := d.data[start : d.off-1] + key, ok := unquote(item) + if !ok { + d.error(errPhase) + } + + // Check for duplicate keys. + _, ok = keys[key] + if !ok { + keys[key] = true + } else { + d.error(fmt.Errorf("json: duplicate key '%s' in object", key)) + } + + // Figure out field corresponding to key. + var subv reflect.Value + destring := false // whether the value is wrapped in a string to be decoded first + + if v.Kind() == reflect.Map { + elemType := v.Type().Elem() + if !mapElem.IsValid() { + mapElem = reflect.New(elemType).Elem() + } else { + mapElem.Set(reflect.Zero(elemType)) + } + subv = mapElem + } else { + var f *field + fields := cachedTypeFields(v.Type()) + for i := range fields { + ff := &fields[i] + if bytes.Equal(ff.nameBytes, []byte(key)) { + f = ff + break + } + } + if f != nil { + subv = v + destring = f.quoted + for _, i := range f.index { + if subv.Kind() == reflect.Ptr { + if subv.IsNil() { + subv.Set(reflect.New(subv.Type().Elem())) + } + subv = subv.Elem() + } + subv = subv.Field(i) + } + } + } + + // Read : before value. + if op == scanSkipSpace { + op = d.scanWhile(scanSkipSpace) + } + if op != scanObjectKey { + d.error(errPhase) + } + + // Read value. + if destring { + switch qv := d.valueQuoted().(type) { + case nil: + d.literalStore(nullLiteral, subv, false) + case string: + d.literalStore([]byte(qv), subv, true) + default: + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type())) + } + } else { + d.value(subv) + } + + // Write value back to map; + // if using struct, subv points into struct already. + if v.Kind() == reflect.Map { + kv := reflect.ValueOf(key).Convert(v.Type().Key()) + v.SetMapIndex(kv, subv) + } + + // Next token must be , or }. + op = d.scanWhile(scanSkipSpace) + if op == scanEndObject { + break + } + if op != scanObjectValue { + d.error(errPhase) + } + } +} + +// literal consumes a literal from d.data[d.off-1:], decoding into the value v. +// The first byte of the literal has been read already +// (that's how the caller knows it's a literal). +func (d *decodeState) literal(v reflect.Value) { + // All bytes inside literal return scanContinue op code. + start := d.off - 1 + op := d.scanWhile(scanContinue) + + // Scan read one byte too far; back up. + d.off-- + d.scan.undo(op) + + d.literalStore(d.data[start:d.off], v, false) +} + +// convertNumber converts the number literal s to a float64, int64 or a Number +// depending on d.numberDecodeType. +func (d *decodeState) convertNumber(s string) (interface{}, error) { + switch d.numberType { + + case UnmarshalJSONNumber: + return Number(s), nil + case UnmarshalIntOrFloat: + v, err := strconv.ParseInt(s, 10, 64) + if err == nil { + return v, nil + } + + // tries to parse integer number in scientific notation + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)} + } + + // if it has no decimal value use int64 + if fi, fd := math.Modf(f); fd == 0.0 { + return int64(fi), nil + } + return f, nil + default: + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)} + } + return f, nil + } + +} + +var numberType = reflect.TypeOf(Number("")) + +// literalStore decodes a literal stored in item into v. +// +// fromQuoted indicates whether this literal came from unwrapping a +// string from the ",string" struct tag option. this is used only to +// produce more helpful error messages. +func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) { + // Check for unmarshaler. + if len(item) == 0 { + //Empty string given + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + return + } + wantptr := item[0] == 'n' // null + u, ut, pv := d.indirect(v, wantptr) + if u != nil { + err := u.UnmarshalJSON(item) + if err != nil { + d.error(err) + } + return + } + if ut != nil { + if item[0] != '"' { + if fromQuoted { + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) + } + return + } + s, ok := unquoteBytes(item) + if !ok { + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(errPhase) + } + } + err := ut.UnmarshalText(s) + if err != nil { + d.error(err) + } + return + } + + v = pv + + switch c := item[0]; c { + case 'n': // null + switch v.Kind() { + case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + v.Set(reflect.Zero(v.Type())) + // otherwise, ignore null for primitives/string + } + case 't', 'f': // true, false + value := c == 't' + switch v.Kind() { + default: + if fromQuoted { + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)}) + } + case reflect.Bool: + v.SetBool(value) + case reflect.Interface: + if v.NumMethod() == 0 { + v.Set(reflect.ValueOf(value)) + } else { + d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)}) + } + } + + case '"': // string + s, ok := unquoteBytes(item) + if !ok { + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(errPhase) + } + } + switch v.Kind() { + default: + d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) + case reflect.Slice: + if v.Type().Elem().Kind() != reflect.Uint8 { + d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) + break + } + b := make([]byte, base64.StdEncoding.DecodedLen(len(s))) + n, err := base64.StdEncoding.Decode(b, s) + if err != nil { + d.saveError(err) + break + } + v.SetBytes(b[:n]) + case reflect.String: + v.SetString(string(s)) + case reflect.Interface: + if v.NumMethod() == 0 { + v.Set(reflect.ValueOf(string(s))) + } else { + d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) + } + } + + default: // number + if c != '-' && (c < '0' || c > '9') { + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(errPhase) + } + } + s := string(item) + switch v.Kind() { + default: + if v.Kind() == reflect.String && v.Type() == numberType { + v.SetString(s) + if !isValidNumber(s) { + d.error(fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item)) + } + break + } + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(&UnmarshalTypeError{"number", v.Type(), int64(d.off)}) + } + case reflect.Interface: + n, err := d.convertNumber(s) + if err != nil { + d.saveError(err) + break + } + if v.NumMethod() != 0 { + d.saveError(&UnmarshalTypeError{"number", v.Type(), int64(d.off)}) + break + } + v.Set(reflect.ValueOf(n)) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + n, err := strconv.ParseInt(s, 10, 64) + if err != nil || v.OverflowInt(n) { + d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)}) + break + } + v.SetInt(n) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || v.OverflowUint(n) { + d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)}) + break + } + v.SetUint(n) + + case reflect.Float32, reflect.Float64: + n, err := strconv.ParseFloat(s, v.Type().Bits()) + if err != nil || v.OverflowFloat(n) { + d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)}) + break + } + v.SetFloat(n) + } + } +} + +// The xxxInterface routines build up a value to be stored +// in an empty interface. They are not strictly necessary, +// but they avoid the weight of reflection in this common case. + +// valueInterface is like value but returns interface{} +func (d *decodeState) valueInterface() interface{} { + switch d.scanWhile(scanSkipSpace) { + default: + d.error(errPhase) + panic("unreachable") + case scanBeginArray: + return d.arrayInterface() + case scanBeginObject: + return d.objectInterface() + case scanBeginLiteral: + return d.literalInterface() + } +} + +// arrayInterface is like array but returns []interface{}. +func (d *decodeState) arrayInterface() []interface{} { + var v = make([]interface{}, 0) + for { + // Look ahead for ] - can only happen on first iteration. + op := d.scanWhile(scanSkipSpace) + if op == scanEndArray { + break + } + + // Back up so d.value can have the byte we just read. + d.off-- + d.scan.undo(op) + + v = append(v, d.valueInterface()) + + // Next token must be , or ]. + op = d.scanWhile(scanSkipSpace) + if op == scanEndArray { + break + } + if op != scanArrayValue { + d.error(errPhase) + } + } + return v +} + +// objectInterface is like object but returns map[string]interface{}. +func (d *decodeState) objectInterface() map[string]interface{} { + m := make(map[string]interface{}) + keys := map[string]bool{} + + for { + // Read opening " of string key or closing }. + op := d.scanWhile(scanSkipSpace) + if op == scanEndObject { + // closing } - can only happen on first iteration. + break + } + if op != scanBeginLiteral { + d.error(errPhase) + } + + // Read string key. + start := d.off - 1 + op = d.scanWhile(scanContinue) + item := d.data[start : d.off-1] + key, ok := unquote(item) + if !ok { + d.error(errPhase) + } + + // Check for duplicate keys. + _, ok = keys[key] + if !ok { + keys[key] = true + } else { + d.error(fmt.Errorf("json: duplicate key '%s' in object", key)) + } + + // Read : before value. + if op == scanSkipSpace { + op = d.scanWhile(scanSkipSpace) + } + if op != scanObjectKey { + d.error(errPhase) + } + + // Read value. + m[key] = d.valueInterface() + + // Next token must be , or }. + op = d.scanWhile(scanSkipSpace) + if op == scanEndObject { + break + } + if op != scanObjectValue { + d.error(errPhase) + } + } + return m +} + +// literalInterface is like literal but returns an interface value. +func (d *decodeState) literalInterface() interface{} { + // All bytes inside literal return scanContinue op code. + start := d.off - 1 + op := d.scanWhile(scanContinue) + + // Scan read one byte too far; back up. + d.off-- + d.scan.undo(op) + item := d.data[start:d.off] + + switch c := item[0]; c { + case 'n': // null + return nil + + case 't', 'f': // true, false + return c == 't' + + case '"': // string + s, ok := unquote(item) + if !ok { + d.error(errPhase) + } + return s + + default: // number + if c != '-' && (c < '0' || c > '9') { + d.error(errPhase) + } + n, err := d.convertNumber(string(item)) + if err != nil { + d.saveError(err) + } + return n + } +} + +// getu4 decodes \uXXXX from the beginning of s, returning the hex value, +// or it returns -1. +func getu4(s []byte) rune { + if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { + return -1 + } + r, err := strconv.ParseUint(string(s[2:6]), 16, 64) + if err != nil { + return -1 + } + return rune(r) +} + +// unquote converts a quoted JSON string literal s into an actual string t. +// The rules are different than for Go, so cannot use strconv.Unquote. +func unquote(s []byte) (t string, ok bool) { + s, ok = unquoteBytes(s) + t = string(s) + return +} + +func unquoteBytes(s []byte) (t []byte, ok bool) { + if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { + return + } + s = s[1 : len(s)-1] + + // Check for unusual characters. If there are none, + // then no unquoting is needed, so return a slice of the + // original bytes. + r := 0 + for r < len(s) { + c := s[r] + if c == '\\' || c == '"' || c < ' ' { + break + } + if c < utf8.RuneSelf { + r++ + continue + } + rr, size := utf8.DecodeRune(s[r:]) + if rr == utf8.RuneError && size == 1 { + break + } + r += size + } + if r == len(s) { + return s, true + } + + b := make([]byte, len(s)+2*utf8.UTFMax) + w := copy(b, s[0:r]) + for r < len(s) { + // Out of room? Can only happen if s is full of + // malformed UTF-8 and we're replacing each + // byte with RuneError. + if w >= len(b)-2*utf8.UTFMax { + nb := make([]byte, (len(b)+utf8.UTFMax)*2) + copy(nb, b[0:w]) + b = nb + } + switch c := s[r]; { + case c == '\\': + r++ + if r >= len(s) { + return + } + switch s[r] { + default: + return + case '"', '\\', '/', '\'': + b[w] = s[r] + r++ + w++ + case 'b': + b[w] = '\b' + r++ + w++ + case 'f': + b[w] = '\f' + r++ + w++ + case 'n': + b[w] = '\n' + r++ + w++ + case 'r': + b[w] = '\r' + r++ + w++ + case 't': + b[w] = '\t' + r++ + w++ + case 'u': + r-- + rr := getu4(s[r:]) + if rr < 0 { + return + } + r += 6 + if utf16.IsSurrogate(rr) { + rr1 := getu4(s[r:]) + if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { + // A valid pair; consume. + r += 6 + w += utf8.EncodeRune(b[w:], dec) + break + } + // Invalid surrogate; fall back to replacement rune. + rr = unicode.ReplacementChar + } + w += utf8.EncodeRune(b[w:], rr) + } + + // Quote, control characters are invalid. + case c == '"', c < ' ': + return + + // ASCII + case c < utf8.RuneSelf: + b[w] = c + r++ + w++ + + // Coerce to well-formed UTF-8. + default: + rr, size := utf8.DecodeRune(s[r:]) + r += size + w += utf8.EncodeRune(b[w:], rr) + } + } + return b[0:w], true +} diff --git a/vendor/github.com/go-jose/go-jose/v4/json/encode.go b/vendor/github.com/go-jose/go-jose/v4/json/encode.go new file mode 100644 index 00000000..98de68ce --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/json/encode.go @@ -0,0 +1,1197 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package json implements encoding and decoding of JSON objects as defined in +// RFC 4627. The mapping between JSON objects and Go values is described +// in the documentation for the Marshal and Unmarshal functions. +// +// See "JSON and Go" for an introduction to this package: +// https://golang.org/doc/articles/json_and_go.html +package json + +import ( + "bytes" + "encoding" + "encoding/base64" + "fmt" + "math" + "reflect" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +// Marshal returns the JSON encoding of v. +// +// Marshal traverses the value v recursively. +// If an encountered value implements the Marshaler interface +// and is not a nil pointer, Marshal calls its MarshalJSON method +// to produce JSON. If no MarshalJSON method is present but the +// value implements encoding.TextMarshaler instead, Marshal calls +// its MarshalText method. +// The nil pointer exception is not strictly necessary +// but mimics a similar, necessary exception in the behavior of +// UnmarshalJSON. +// +// Otherwise, Marshal uses the following type-dependent default encodings: +// +// Boolean values encode as JSON booleans. +// +// Floating point, integer, and Number values encode as JSON numbers. +// +// String values encode as JSON strings coerced to valid UTF-8, +// replacing invalid bytes with the Unicode replacement rune. +// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e" +// to keep some browsers from misinterpreting JSON output as HTML. +// Ampersand "&" is also escaped to "\u0026" for the same reason. +// +// Array and slice values encode as JSON arrays, except that +// []byte encodes as a base64-encoded string, and a nil slice +// encodes as the null JSON object. +// +// Struct values encode as JSON objects. Each exported struct field +// becomes a member of the object unless +// - the field's tag is "-", or +// - the field is empty and its tag specifies the "omitempty" option. +// +// The empty values are false, 0, any +// nil pointer or interface value, and any array, slice, map, or string of +// length zero. The object's default key string is the struct field name +// but can be specified in the struct field's tag value. The "json" key in +// the struct field's tag value is the key name, followed by an optional comma +// and options. Examples: +// +// // Field is ignored by this package. +// Field int `json:"-"` +// +// // Field appears in JSON as key "myName". +// Field int `json:"myName"` +// +// // Field appears in JSON as key "myName" and +// // the field is omitted from the object if its value is empty, +// // as defined above. +// Field int `json:"myName,omitempty"` +// +// // Field appears in JSON as key "Field" (the default), but +// // the field is skipped if empty. +// // Note the leading comma. +// Field int `json:",omitempty"` +// +// The "string" option signals that a field is stored as JSON inside a +// JSON-encoded string. It applies only to fields of string, floating point, +// integer, or boolean types. This extra level of encoding is sometimes used +// when communicating with JavaScript programs: +// +// Int64String int64 `json:",string"` +// +// The key name will be used if it's a non-empty string consisting of +// only Unicode letters, digits, dollar signs, percent signs, hyphens, +// underscores and slashes. +// +// Anonymous struct fields are usually marshaled as if their inner exported fields +// were fields in the outer struct, subject to the usual Go visibility rules amended +// as described in the next paragraph. +// An anonymous struct field with a name given in its JSON tag is treated as +// having that name, rather than being anonymous. +// An anonymous struct field of interface type is treated the same as having +// that type as its name, rather than being anonymous. +// +// The Go visibility rules for struct fields are amended for JSON when +// deciding which field to marshal or unmarshal. If there are +// multiple fields at the same level, and that level is the least +// nested (and would therefore be the nesting level selected by the +// usual Go rules), the following extra rules apply: +// +// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered, +// even if there are multiple untagged fields that would otherwise conflict. +// 2) If there is exactly one field (tagged or not according to the first rule), that is selected. +// 3) Otherwise there are multiple fields, and all are ignored; no error occurs. +// +// Handling of anonymous struct fields is new in Go 1.1. +// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of +// an anonymous struct field in both current and earlier versions, give the field +// a JSON tag of "-". +// +// Map values encode as JSON objects. +// The map's key type must be string; the map keys are used as JSON object +// keys, subject to the UTF-8 coercion described for string values above. +// +// Pointer values encode as the value pointed to. +// A nil pointer encodes as the null JSON object. +// +// Interface values encode as the value contained in the interface. +// A nil interface value encodes as the null JSON object. +// +// Channel, complex, and function values cannot be encoded in JSON. +// Attempting to encode such a value causes Marshal to return +// an UnsupportedTypeError. +// +// JSON cannot represent cyclic data structures and Marshal does not +// handle them. Passing cyclic structures to Marshal will result in +// an infinite recursion. +func Marshal(v interface{}) ([]byte, error) { + e := &encodeState{} + err := e.marshal(v) + if err != nil { + return nil, err + } + return e.Bytes(), nil +} + +// MarshalIndent is like Marshal but applies Indent to format the output. +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { + b, err := Marshal(v) + if err != nil { + return nil, err + } + var buf bytes.Buffer + err = Indent(&buf, b, prefix, indent) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 +// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 +// so that the JSON will be safe to embed inside HTML