@hybridlabor-api/aos 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/.agents/agents.md +1 -1
  2. package/.agents/nodes.json +1 -1
  3. package/.agents/vendor-manifest.json +23 -1
  4. package/.claude/agents/godmode-media-eventtech.md +1 -1
  5. package/.claude/hooks/conventional-commits.mjs +125 -0
  6. package/.claude/hooks/env-file-protection.mjs +105 -0
  7. package/.claude/hooks/go-gate.mjs +101 -81
  8. package/.claude/settings.json +13 -0
  9. package/.opencode/agents/godmode-media-eventtech.md +1 -1
  10. package/.opencode/plugins/bdb-aos.js +31 -4
  11. package/README.de.md +1 -1
  12. package/README.md +1 -1
  13. package/README.pt.md +1 -1
  14. package/THIRD_PARTY_NOTICES.md +88 -0
  15. package/docs/skills_table.md +1 -1
  16. package/installer.js +174 -46
  17. package/package.json +6 -2
  18. package/packages/aos-cli/README.md +80 -0
  19. package/packages/aos-cli/bin/aos-cli.mjs +134 -0
  20. package/packages/aos-cli/core-skills.json +12 -0
  21. package/packages/aos-cli/extensions/aos.ts +321 -0
  22. package/packages/aos-cli/package-lock.json +1923 -0
  23. package/packages/aos-cli/package.json +29 -0
  24. package/packages/aos-cli/scripts/check-theme.mjs +63 -0
  25. package/packages/aos-cli/themes/aos.json +97 -0
  26. package/scripts/build-plugin-manifest.mjs +131 -0
  27. package/scripts/validate-skills.mjs +81 -6
  28. package/skills/basic/ao-orchestrator/SKILL.md +116 -0
  29. package/skills/global_config/agenttrail/SKILL.md +6 -1
  30. package/skills/global_config/ask-tim/SKILL.md +4 -4
  31. package/skills/global_config/bash-script-generator/SKILL.md +201 -0
  32. package/skills/global_config/bash-script-generator/assets/templates/standard-template.sh +96 -0
  33. package/skills/global_config/bash-script-generator/docs/bash-scripting-guide.md +729 -0
  34. package/skills/global_config/bash-script-generator/docs/generation-best-practices.md +193 -0
  35. package/skills/global_config/bash-script-generator/docs/script-patterns.md +566 -0
  36. package/skills/global_config/bash-script-generator/docs/text-processing-guide.md +437 -0
  37. package/skills/global_config/bash-script-generator/examples/log-analyzer.sh +92 -0
  38. package/skills/global_config/bash-script-generator/scripts/generate_script_template.sh +123 -0
  39. package/skills/global_config/bash-script-generator/scripts/run_ci_checks.sh +172 -0
  40. package/skills/global_config/bash-script-generator/scripts/test_generator.sh +413 -0
  41. package/skills/global_config/bash-script-validator/SKILL.md +249 -0
  42. package/skills/global_config/bash-script-validator/docs/awk-reference.md +449 -0
  43. package/skills/global_config/bash-script-validator/docs/bash-reference.md +468 -0
  44. package/skills/global_config/bash-script-validator/docs/common-mistakes.md +623 -0
  45. package/skills/global_config/bash-script-validator/docs/grep-reference.md +395 -0
  46. package/skills/global_config/bash-script-validator/docs/regex-reference.md +391 -0
  47. package/skills/global_config/bash-script-validator/docs/sed-reference.md +454 -0
  48. package/skills/global_config/bash-script-validator/docs/shell-reference.md +463 -0
  49. package/skills/global_config/bash-script-validator/docs/shellcheck-reference.md +399 -0
  50. package/skills/global_config/bash-script-validator/examples/bad-bash.sh +55 -0
  51. package/skills/global_config/bash-script-validator/examples/bad-shell.sh +54 -0
  52. package/skills/global_config/bash-script-validator/examples/good-bash.sh +71 -0
  53. package/skills/global_config/bash-script-validator/examples/good-shell.sh +69 -0
  54. package/skills/global_config/bash-script-validator/scripts/run_ci_checks.sh +23 -0
  55. package/skills/global_config/bash-script-validator/scripts/shellcheck_wrapper.sh +174 -0
  56. package/skills/global_config/bash-script-validator/scripts/test_validate.sh +446 -0
  57. package/skills/global_config/bash-script-validator/scripts/validate.sh +512 -0
  58. package/skills/global_config/ci-pipeline/SKILL.md +135 -0
  59. package/skills/global_config/dispatching-parallel-agents/SKILL.md +170 -0
  60. package/skills/global_config/dockerfile-generator/SKILL.md +1038 -0
  61. package/skills/global_config/dockerfile-generator/examples/example.dockerignore +95 -0
  62. package/skills/global_config/dockerfile-generator/examples/golang-distroless.Dockerfile +34 -0
  63. package/skills/global_config/dockerfile-generator/examples/java-springboot.Dockerfile +45 -0
  64. package/skills/global_config/dockerfile-generator/examples/nextjs-production.Dockerfile +49 -0
  65. package/skills/global_config/dockerfile-generator/examples/nodejs-multistage.Dockerfile +55 -0
  66. package/skills/global_config/dockerfile-generator/examples/python-fastapi.Dockerfile +48 -0
  67. package/skills/global_config/dockerfile-generator/references/language_specific_guides.md +510 -0
  68. package/skills/global_config/dockerfile-generator/references/multistage_builds.md +570 -0
  69. package/skills/global_config/dockerfile-generator/references/optimization_patterns.md +492 -0
  70. package/skills/global_config/dockerfile-generator/references/security_best_practices.md +375 -0
  71. package/skills/global_config/dockerfile-generator/scripts/generate_dockerignore.sh +199 -0
  72. package/skills/global_config/dockerfile-generator/scripts/generate_golang.sh +172 -0
  73. package/skills/global_config/dockerfile-generator/scripts/generate_java.sh +185 -0
  74. package/skills/global_config/dockerfile-generator/scripts/generate_nodejs.sh +279 -0
  75. package/skills/global_config/dockerfile-generator/scripts/generate_python.sh +218 -0
  76. package/skills/global_config/dockerfile-generator/scripts/test_generator.sh +210 -0
  77. package/skills/global_config/dockerfile-validator/SKILL.md +300 -0
  78. package/skills/global_config/dockerfile-validator/examples/.dockerignore.example +34 -0
  79. package/skills/global_config/dockerfile-validator/examples/bad-example.Dockerfile +37 -0
  80. package/skills/global_config/dockerfile-validator/examples/golang-distroless.Dockerfile +50 -0
  81. package/skills/global_config/dockerfile-validator/examples/good-example.Dockerfile +52 -0
  82. package/skills/global_config/dockerfile-validator/examples/python-optimized.Dockerfile +53 -0
  83. package/skills/global_config/dockerfile-validator/examples/security-issues.Dockerfile +42 -0
  84. package/skills/global_config/dockerfile-validator/references/docker_best_practices.md +348 -0
  85. package/skills/global_config/dockerfile-validator/references/optimization_guide.md +473 -0
  86. package/skills/global_config/dockerfile-validator/references/security_checklist.md +208 -0
  87. package/skills/global_config/dockerfile-validator/scripts/dockerfile-validate.sh +699 -0
  88. package/skills/global_config/dockerfile-validator/scripts/test_validate.sh +35 -0
  89. package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn-lock-read.Dockerfile +6 -0
  90. package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn.Dockerfile +6 -0
  91. package/skills/global_config/dockerfile-validator/tests/fixtures/from-platform-nonroot.Dockerfile +7 -0
  92. package/skills/global_config/dockerfile-validator/tests/test_regression.sh +164 -0
  93. package/skills/global_config/finishing-a-development-branch/SKILL.md +228 -0
  94. package/skills/global_config/github-actions-generator/SKILL.md +353 -0
  95. package/skills/global_config/github-actions-generator/assets/templates/action/composite/action.yml +82 -0
  96. package/skills/global_config/github-actions-generator/assets/templates/action/docker/Dockerfile +25 -0
  97. package/skills/global_config/github-actions-generator/assets/templates/action/docker/action.yml +42 -0
  98. package/skills/global_config/github-actions-generator/assets/templates/action/docker/entrypoint.sh +27 -0
  99. package/skills/global_config/github-actions-generator/assets/templates/action/javascript/action.yml +33 -0
  100. package/skills/global_config/github-actions-generator/assets/templates/action/javascript/index.js +50 -0
  101. package/skills/global_config/github-actions-generator/assets/templates/action/javascript/package.json +27 -0
  102. package/skills/global_config/github-actions-generator/assets/templates/workflow/basic_workflow.yml +242 -0
  103. package/skills/global_config/github-actions-generator/assets/templates/workflow/reusable_workflow.yml +106 -0
  104. package/skills/global_config/github-actions-generator/examples/README.md +147 -0
  105. package/skills/global_config/github-actions-generator/examples/actions/setup-node-cached/action.yml +93 -0
  106. package/skills/global_config/github-actions-generator/examples/caching/docker-buildkit.yml +256 -0
  107. package/skills/global_config/github-actions-generator/examples/security/dependency-review.yml +62 -0
  108. package/skills/global_config/github-actions-generator/examples/security/sbom-attestation.yml +119 -0
  109. package/skills/global_config/github-actions-generator/examples/triggers/chatops-commands.yml +475 -0
  110. package/skills/global_config/github-actions-generator/examples/triggers/repository-dispatch.yml +418 -0
  111. package/skills/global_config/github-actions-generator/examples/triggers/workflow-orchestration.yml +404 -0
  112. package/skills/global_config/github-actions-generator/examples/workflows/docker-build-push.yml +68 -0
  113. package/skills/global_config/github-actions-generator/examples/workflows/go-ci.yml +161 -0
  114. package/skills/global_config/github-actions-generator/examples/workflows/monorepo-ci.yml +340 -0
  115. package/skills/global_config/github-actions-generator/examples/workflows/multi-environment-deploy.yml +406 -0
  116. package/skills/global_config/github-actions-generator/examples/workflows/nodejs-ci.yml +122 -0
  117. package/skills/global_config/github-actions-generator/examples/workflows/python-ci.yml +157 -0
  118. package/skills/global_config/github-actions-generator/examples/workflows/scheduled-tasks.yml +376 -0
  119. package/skills/global_config/github-actions-generator/references/advanced-triggers.md +917 -0
  120. package/skills/global_config/github-actions-generator/references/best-practices.md +755 -0
  121. package/skills/global_config/github-actions-generator/references/common-actions.md +715 -0
  122. package/skills/global_config/github-actions-generator/references/custom-actions.md +320 -0
  123. package/skills/global_config/github-actions-generator/references/expressions-and-contexts.md +688 -0
  124. package/skills/global_config/github-actions-generator/references/modern-features.md +421 -0
  125. package/skills/global_config/github-actions-generator/scripts/test_generator.sh +344 -0
  126. package/skills/global_config/github-actions-templates/SKILL.md +7 -0
  127. package/skills/global_config/github-actions-validator/SKILL.md +576 -0
  128. package/skills/global_config/github-actions-validator/examples/README.md +88 -0
  129. package/skills/global_config/github-actions-validator/examples/outdated-versions.yml +76 -0
  130. package/skills/global_config/github-actions-validator/examples/valid-ci.yml +79 -0
  131. package/skills/global_config/github-actions-validator/examples/with-errors.yml +47 -0
  132. package/skills/global_config/github-actions-validator/references/act_usage.md +233 -0
  133. package/skills/global_config/github-actions-validator/references/action_versions.md +122 -0
  134. package/skills/global_config/github-actions-validator/references/actionlint_usage.md +343 -0
  135. package/skills/global_config/github-actions-validator/references/common_errors.md +512 -0
  136. package/skills/global_config/github-actions-validator/references/modern_features.md +384 -0
  137. package/skills/global_config/github-actions-validator/references/runners.md +317 -0
  138. package/skills/global_config/github-actions-validator/scripts/install_tools.sh +113 -0
  139. package/skills/global_config/github-actions-validator/scripts/validate_workflow.sh +910 -0
  140. package/skills/global_config/github-actions-validator/tests/test_validate_workflow.sh +237 -0
  141. package/skills/global_config/makefile-generator/SKILL.md +614 -0
  142. package/skills/global_config/makefile-generator/assets/templates/.gitkeep +1 -0
  143. package/skills/global_config/makefile-generator/docs/makefile-structure.md +530 -0
  144. package/skills/global_config/makefile-generator/docs/optimization-guide.md +784 -0
  145. package/skills/global_config/makefile-generator/docs/patterns-guide.md +642 -0
  146. package/skills/global_config/makefile-generator/docs/security-guide.md +361 -0
  147. package/skills/global_config/makefile-generator/docs/targets-guide.md +642 -0
  148. package/skills/global_config/makefile-generator/docs/variables-guide.md +596 -0
  149. package/skills/global_config/makefile-generator/scripts/add_standard_targets.sh +539 -0
  150. package/skills/global_config/makefile-generator/scripts/generate_makefile_template.sh +690 -0
  151. package/skills/global_config/makefile-generator/test/test_helper_scripts.sh +190 -0
  152. package/skills/global_config/makefile-validator/SKILL.md +244 -0
  153. package/skills/global_config/makefile-validator/docs/bake-tool.md +1000 -0
  154. package/skills/global_config/makefile-validator/docs/best-practices.md +858 -0
  155. package/skills/global_config/makefile-validator/docs/common-mistakes.md +944 -0
  156. package/skills/global_config/makefile-validator/examples/bad-makefile.mk +77 -0
  157. package/skills/global_config/makefile-validator/examples/good-makefile.mk +103 -0
  158. package/skills/global_config/makefile-validator/scripts/test_validate.sh +382 -0
  159. package/skills/global_config/makefile-validator/scripts/validate_makefile.sh +712 -0
  160. package/skills/global_config/{MCP_Manage → mcp-manage}/SKILL.md +3 -3
  161. package/skills/global_config/requesting-code-review/SKILL.md +98 -0
  162. package/skills/global_config/requesting-code-review/code-reviewer.md +198 -0
  163. package/skills/global_config/using-git-worktrees/SKILL.md +170 -0
  164. package/skills/global_config/verification-before-completion/SKILL.md +123 -0
  165. package/skills/global_config/writing-plans/SKILL.md +126 -46
  166. package/skills/global_config/writing-plans-legacy/SKILL.md +139 -0
@@ -0,0 +1,699 @@
1
+ #!/bin/bash
2
+
3
+ ################################################################################
4
+ # Dockerfile Validator - Complete Lifecycle Management
5
+ #
6
+ # Single self-contained script that handles:
7
+ # - Tool installation (hadolint + Checkov in Python venvs)
8
+ # - Syntax validation (hadolint)
9
+ # - Security scanning (Checkov)
10
+ # - Best practices validation (custom checks)
11
+ # - Optimization analysis (custom checks)
12
+ # - Automatic cleanup on exit (success or failure)
13
+ #
14
+ # Usage: ./dockerfile-validate.sh [Dockerfile]
15
+ ################################################################################
16
+
17
+ set -euo pipefail
18
+
19
+ # Colors
20
+ RED='\033[0;31m'
21
+ GREEN='\033[0;32m'
22
+ YELLOW='\033[1;33m'
23
+ BLUE='\033[0;34m'
24
+ CYAN='\033[0;36m'
25
+ PURPLE='\033[0;35m'
26
+ BOLD='\033[1m'
27
+ NC='\033[0m'
28
+
29
+ # Configuration
30
+ DOCKERFILE="${1:-Dockerfile}"
31
+ VENV_BASE_DIR=""
32
+ HADOLINT_VENV=""
33
+ CHECKOV_VENV=""
34
+ TEMP_INSTALL=false
35
+ HADOLINT_CMD=""
36
+ CHECKOV_CMD=""
37
+ HADOLINT_MISSING=false
38
+ CHECKOV_MISSING=false
39
+
40
+ # Environment variable to force temporary installation (for testing cleanup)
41
+ # Usage: FORCE_TEMP_INSTALL=true bash scripts/dockerfile-validate.sh Dockerfile
42
+ FORCE_TEMP_INSTALL="${FORCE_TEMP_INSTALL:-false}"
43
+
44
+ # Exit codes
45
+ EXIT_CODE=0
46
+
47
+ # Counters for custom checks
48
+ BP_ERRORS=0
49
+ BP_WARNINGS=0
50
+ BP_INFO=0
51
+ BP_HAS_WARNINGS=false # set true when BP has warnings but no errors (drives WARN summary state)
52
+ RUN_COUNT=0
53
+
54
+ ################################################################################
55
+ # Cleanup Function - Called on EXIT
56
+ ################################################################################
57
+ cleanup() {
58
+ local exit_code=$?
59
+
60
+ if [ "$TEMP_INSTALL" = true ] && [ -n "$VENV_BASE_DIR" ]; then
61
+ echo ""
62
+ echo -e "${YELLOW}Cleaning up temporary installation...${NC}"
63
+
64
+ if [ -d "$VENV_BASE_DIR" ]; then
65
+ rm -rf "$VENV_BASE_DIR"
66
+ echo -e "${GREEN}✓ Removed temporary venvs${NC}"
67
+ fi
68
+
69
+ echo -e "${GREEN}✓ Cleanup complete${NC}"
70
+ fi
71
+
72
+ exit $exit_code
73
+ }
74
+
75
+ # Set trap for cleanup on any exit
76
+ trap cleanup EXIT INT TERM
77
+
78
+ ################################################################################
79
+ # Tool Installation Functions
80
+ ################################################################################
81
+
82
+ check_python() {
83
+ if command -v python3 &> /dev/null; then
84
+ PYTHON_CMD="python3"
85
+ elif command -v python &> /dev/null; then
86
+ PYTHON_CMD="python"
87
+ else
88
+ echo -e "${RED}ERROR: Python 3 is required but not installed${NC}" >&2
89
+ exit 2
90
+ fi
91
+
92
+ # Verify Python version (need 3.8+)
93
+ PYTHON_VERSION=$($PYTHON_CMD --version 2>&1 | awk '{print $2}')
94
+ MAJOR=$(echo $PYTHON_VERSION | cut -d. -f1)
95
+ MINOR=$(echo $PYTHON_VERSION | cut -d. -f2)
96
+
97
+ if [ "$MAJOR" -lt 3 ] || ([ "$MAJOR" -eq 3 ] && [ "$MINOR" -lt 8 ]); then
98
+ echo -e "${RED}ERROR: Python 3.8+ required (found $PYTHON_VERSION)${NC}" >&2
99
+ exit 2
100
+ fi
101
+ }
102
+
103
+ check_tools() {
104
+ # If FORCE_TEMP_INSTALL is set, skip tool check and force installation
105
+ if [ "$FORCE_TEMP_INSTALL" = "true" ]; then
106
+ echo -e "${YELLOW}FORCE_TEMP_INSTALL=true: Forcing temporary tool installation for testing${NC}"
107
+ HADOLINT_MISSING=true
108
+ CHECKOV_MISSING=true
109
+ return 1
110
+ fi
111
+
112
+ HADOLINT_MISSING=false
113
+ CHECKOV_MISSING=false
114
+
115
+ # Check for hadolint (system-installed)
116
+ if command -v hadolint &> /dev/null; then
117
+ HADOLINT_CMD="hadolint"
118
+ else
119
+ HADOLINT_MISSING=true
120
+ fi
121
+
122
+ # Check for Checkov (system-installed)
123
+ if command -v checkov &> /dev/null; then
124
+ CHECKOV_CMD="checkov"
125
+ else
126
+ CHECKOV_MISSING=true
127
+ fi
128
+
129
+ # Return 0 if both found, 1 if installation needed
130
+ [ "$HADOLINT_MISSING" = false ] && [ "$CHECKOV_MISSING" = false ]
131
+ }
132
+
133
+ install_hadolint() {
134
+ echo -e "${BLUE}Installing hadolint...${NC}"
135
+
136
+ mkdir -p "$HADOLINT_VENV"
137
+ $PYTHON_CMD -m venv "$HADOLINT_VENV" 2>&1 | grep -v "upgrade pip" || true
138
+
139
+ "$HADOLINT_VENV/bin/pip" install --quiet --upgrade pip
140
+ "$HADOLINT_VENV/bin/pip" install --quiet hadolint-bin
141
+
142
+ if "$HADOLINT_VENV/bin/hadolint" --version &> /dev/null; then
143
+ HADOLINT_CMD="$HADOLINT_VENV/bin/hadolint"
144
+ VERSION=$("$HADOLINT_VENV/bin/hadolint" --version | head -n1)
145
+ echo -e "${GREEN}✓ hadolint installed: $VERSION${NC}"
146
+ else
147
+ echo -e "${RED}✗ hadolint installation failed${NC}" >&2
148
+ exit 2
149
+ fi
150
+ }
151
+
152
+ install_checkov() {
153
+ echo -e "${BLUE}Installing Checkov...${NC}"
154
+
155
+ mkdir -p "$CHECKOV_VENV"
156
+ $PYTHON_CMD -m venv "$CHECKOV_VENV" 2>&1 | grep -v "upgrade pip" || true
157
+
158
+ "$CHECKOV_VENV/bin/pip" install --quiet --upgrade pip
159
+ "$CHECKOV_VENV/bin/pip" install --quiet checkov
160
+
161
+ if "$CHECKOV_VENV/bin/checkov" --version &> /dev/null; then
162
+ CHECKOV_CMD="$CHECKOV_VENV/bin/checkov"
163
+ VERSION=$("$CHECKOV_VENV/bin/checkov" --version 2>&1)
164
+ echo -e "${GREEN}✓ Checkov installed: $VERSION${NC}"
165
+ else
166
+ echo -e "${RED}✗ Checkov installation failed${NC}" >&2
167
+ exit 2
168
+ fi
169
+ }
170
+
171
+ install_tools() {
172
+ if [ "$HADOLINT_MISSING" = false ] && [ "$CHECKOV_MISSING" = false ]; then
173
+ return 0
174
+ fi
175
+
176
+ echo -e "${YELLOW}${BOLD}Installing validation tools...${NC}"
177
+ echo ""
178
+
179
+ TEMP_INSTALL=true
180
+ VENV_BASE_DIR=$(mktemp -d "${TMPDIR:-/tmp}/dockerfile-validator.XXXXXX")
181
+ HADOLINT_VENV="${VENV_BASE_DIR}/hadolint-venv"
182
+ CHECKOV_VENV="${VENV_BASE_DIR}/checkov-venv"
183
+
184
+ check_python
185
+
186
+ if [ "$HADOLINT_MISSING" = true ]; then
187
+ install_hadolint
188
+ fi
189
+
190
+ if [ "$CHECKOV_MISSING" = true ]; then
191
+ install_checkov
192
+ fi
193
+
194
+ echo ""
195
+ }
196
+
197
+ ################################################################################
198
+ # Dockerfile Preprocessing - Handle Multi-line Instructions
199
+ ################################################################################
200
+
201
+ # Normalize Dockerfile by joining continuation lines (lines ending with \)
202
+ # This allows accurate counting of multi-line instructions
203
+ normalize_dockerfile() {
204
+ local dockerfile="$1"
205
+ # Use awk to join lines that end with backslash
206
+ awk '
207
+ /\\$/ {
208
+ sub(/\\$/, "")
209
+ printf "%s", $0
210
+ next
211
+ }
212
+ { print }
213
+ ' "$dockerfile"
214
+ }
215
+
216
+ count_instruction() {
217
+ local content="$1"
218
+ local instruction="$2"
219
+
220
+ printf '%s\n' "$content" | awk -v instruction="$instruction" '
221
+ BEGIN { IGNORECASE=1 }
222
+ $0 ~ "^[[:space:]]*" instruction "[[:space:]]" { count++ }
223
+ END { print count + 0 }
224
+ '
225
+ }
226
+
227
+ from_images() {
228
+ local content="$1"
229
+
230
+ printf '%s\n' "$content" | awk '
231
+ BEGIN { IGNORECASE=1 }
232
+ function parse_from_image(line, n, token, i) {
233
+ sub(/^[[:space:]]*FROM[[:space:]]+/, "", line)
234
+ sub(/[[:space:]]+#.*/, "", line)
235
+ n = split(line, token, /[[:space:]]+/)
236
+
237
+ i = 1
238
+ while (i <= n && token[i] ~ /^--/) {
239
+ # Handle both --platform=<value> and --platform <value> forms.
240
+ if (token[i] == "--platform" && i < n) {
241
+ i += 2
242
+ continue
243
+ }
244
+ i++
245
+ }
246
+
247
+ if (i <= n) {
248
+ return token[i]
249
+ }
250
+
251
+ return ""
252
+ }
253
+
254
+ /^[[:space:]]*FROM[[:space:]]+/ {
255
+ image = parse_from_image($0)
256
+ if (image != "") {
257
+ print image
258
+ }
259
+ }
260
+ '
261
+ }
262
+
263
+ final_from_image() {
264
+ local content="$1"
265
+
266
+ from_images "$content" | tail -n1
267
+ }
268
+
269
+ is_nonroot_base_image() {
270
+ local image="$1"
271
+
272
+ if echo "$image" | grep -qiE 'distroless[^[:space:]]*:nonroot|:nonroot$'; then
273
+ return 0
274
+ fi
275
+
276
+ return 1
277
+ }
278
+
279
+ ################################################################################
280
+ # Validation Functions
281
+ ################################################################################
282
+
283
+ run_hadolint() {
284
+ echo -e "${CYAN}${BOLD}[1/4] Syntax Validation (hadolint)${NC}"
285
+ echo "====================================="
286
+ echo ""
287
+
288
+ if "$HADOLINT_CMD" "$DOCKERFILE" 2>&1; then
289
+ echo ""
290
+ echo -e "${GREEN}✓ Syntax validation passed${NC}"
291
+ return 0
292
+ else
293
+ local hadolint_exit=$?
294
+ echo ""
295
+ echo -e "${YELLOW}⚠ Syntax issues found${NC}"
296
+ EXIT_CODE=1
297
+ return $hadolint_exit
298
+ fi
299
+ }
300
+
301
+ run_checkov() {
302
+ echo -e "${CYAN}${BOLD}[2/4] Security Scan (Checkov)${NC}"
303
+ echo "================================"
304
+ echo ""
305
+
306
+ if "$CHECKOV_CMD" -f "$DOCKERFILE" --framework dockerfile --compact 2>&1; then
307
+ echo ""
308
+ echo -e "${GREEN}✓ Security scan passed${NC}"
309
+ return 0
310
+ else
311
+ local checkov_exit=$?
312
+ echo ""
313
+ echo -e "${YELLOW}⚠ Security issues found${NC}"
314
+ EXIT_CODE=1
315
+ return $checkov_exit
316
+ fi
317
+ }
318
+
319
+ run_best_practices() {
320
+ echo -e "${CYAN}${BOLD}[3/4] Best Practices Validation${NC}"
321
+ echo "===================================="
322
+ echo ""
323
+
324
+ # Reset counters
325
+ BP_ERRORS=0
326
+ BP_WARNINGS=0
327
+ BP_INFO=0
328
+
329
+ # Create normalized version for accurate multi-line instruction counting
330
+ local normalized_content
331
+ normalized_content=$(normalize_dockerfile "$DOCKERFILE")
332
+ local final_image
333
+ final_image=$(final_from_image "$normalized_content")
334
+
335
+ # Check for :latest tag
336
+ if grep -qiE "^[[:space:]]*FROM[[:space:]]+[^[:space:]]+:latest([[:space:]]|$)" "$DOCKERFILE"; then
337
+ echo -e "${YELLOW}[WARNING] Base image using :latest tag${NC}"
338
+ echo " → Use specific version tags for reproducibility"
339
+ ((BP_WARNINGS++))
340
+ fi
341
+
342
+ # Check for USER directive
343
+ if ! grep -qiE "^[[:space:]]*USER[[:space:]]+" "$DOCKERFILE"; then
344
+ if is_nonroot_base_image "$final_image"; then
345
+ echo -e "${PURPLE}[INFO] No USER directive, but final base image is non-root: $final_image${NC}"
346
+ echo " → Confirm runtime user requirements for your platform"
347
+ ((BP_INFO++))
348
+ else
349
+ echo -e "${YELLOW}[WARNING] No USER directive - container will run as root${NC}"
350
+ echo " → Add 'USER <non-root-user>' before CMD/ENTRYPOINT"
351
+ ((BP_WARNINGS++))
352
+ fi
353
+ else
354
+ LAST_USER=$(grep -iE "^[[:space:]]*USER[[:space:]]+" "$DOCKERFILE" | tail -n1 | awk '{print $2}')
355
+ LAST_USER_LOWER=$(echo "$LAST_USER" | tr '[:upper:]' '[:lower:]')
356
+ if [[ "$LAST_USER_LOWER" == "root" ]] || [[ "$LAST_USER" == "0" ]] || [[ "$LAST_USER" == "0:0" ]]; then
357
+ echo -e "${RED}[ERROR] Last USER directive sets user to root${NC}"
358
+ echo " → Container should not run as root user"
359
+ ((BP_ERRORS++))
360
+ EXIT_CODE=1
361
+ fi
362
+ fi
363
+
364
+ # Check for HEALTHCHECK
365
+ if ! grep -qiE "^[[:space:]]*HEALTHCHECK[[:space:]]+" "$DOCKERFILE"; then
366
+ if grep -qiE "^[[:space:]]*EXPOSE[[:space:]]+|^[[:space:]]*(CMD|ENTRYPOINT)[[:space:]].*server" "$DOCKERFILE"; then
367
+ echo -e "${PURPLE}[INFO] No HEALTHCHECK defined for service container${NC}"
368
+ echo " → Consider adding HEALTHCHECK for monitoring"
369
+ ((BP_INFO++))
370
+ fi
371
+ fi
372
+
373
+ # Check RUN command efficiency (using normalized content for accurate counting)
374
+ RUN_COUNT=$(count_instruction "$normalized_content" "RUN")
375
+ if [ "$RUN_COUNT" -gt "5" ]; then
376
+ echo -e "${PURPLE}[INFO] High number of RUN commands ($RUN_COUNT)${NC}"
377
+ echo " → Consider combining related commands to reduce layers"
378
+ ((BP_INFO++))
379
+ fi
380
+
381
+ # Check for apt-get cache cleanup (must happen in same RUN instruction)
382
+ APT_INSTALL_WITHOUT_CLEAN=$(printf '%s\n' "$normalized_content" | awk '
383
+ BEGIN { IGNORECASE=1 }
384
+ /^[[:space:]]*RUN[[:space:]]+/ && /apt-get[[:space:]]+install/ {
385
+ has_clean = ($0 ~ /rm[[:space:]]+-rf[[:space:]]+\/var\/lib\/apt\/lists/ || $0 ~ /apt-get[[:space:]]+clean/)
386
+ if (!has_clean) { count++ }
387
+ }
388
+ END { print count + 0 }
389
+ ')
390
+
391
+ if [ "$APT_INSTALL_WITHOUT_CLEAN" -gt 0 ]; then
392
+ echo -e "${YELLOW}[WARNING] apt-get install found without same-layer cache cleanup${NC}"
393
+ echo " → Add '&& rm -rf /var/lib/apt/lists/*' to the same RUN instruction"
394
+ ((BP_WARNINGS++))
395
+ fi
396
+
397
+ # Check for apk --no-cache (using normalized content)
398
+ APK_ADD_WITHOUT_NOCACHE=$(printf '%s\n' "$normalized_content" | awk '
399
+ BEGIN { IGNORECASE=1 }
400
+ /^[[:space:]]*RUN[[:space:]]+/ && /apk[[:space:]]+add/ {
401
+ has_no_cache = ($0 ~ /apk[[:space:]]+add[^#]*--no-cache/)
402
+ has_manual_cleanup = ($0 ~ /rm[[:space:]]+-rf[[:space:]]+\/var\/cache\/apk/)
403
+ if (!has_no_cache && !has_manual_cleanup) { count++ }
404
+ }
405
+ END { print count + 0 }
406
+ ')
407
+
408
+ if [ "$APK_ADD_WITHOUT_NOCACHE" -gt 0 ]; then
409
+ echo -e "${YELLOW}[WARNING] apk add without --no-cache or manual cache cleanup${NC}"
410
+ echo " → Use 'apk add --no-cache' to avoid cache in image"
411
+ ((BP_WARNINGS++))
412
+ fi
413
+
414
+ # Check for hardcoded secrets (ignore comments)
415
+ # Use tolower() instead of IGNORECASE=1 for ~ operator: BSD awk (macOS) does not
416
+ # honour IGNORECASE when using the dynamic ~ operator, only for literal /patterns/.
417
+ # Dockerfile convention is UPPERCASE variable names (ENV PASSWORD=, ENV API_KEY=),
418
+ # so without tolower() all secrets are silently missed on macOS.
419
+ if awk '
420
+ /^[[:space:]]*#/ { next }
421
+ /^[[:space:]]*(ENV|ARG)[[:space:]]/ {
422
+ lower = tolower($0)
423
+ if (lower ~ /(password|secret|api_key|api-key|apikey|token)[[:space:]]*=/) {
424
+ found=1
425
+ }
426
+ }
427
+ END { exit found ? 0 : 1 }
428
+ ' "$DOCKERFILE"; then
429
+ echo -e "${RED}[ERROR] Potential hardcoded secrets in ENV/ARG${NC}"
430
+ echo " → Never hardcode secrets in Dockerfiles"
431
+ ((BP_ERRORS++))
432
+ EXIT_CODE=1
433
+ fi
434
+
435
+ # Check for poor COPY ordering (COPY . before dependency installation)
436
+ # This hurts build cache efficiency - dependencies should be copied first.
437
+ # Stage-aware: resets tracking on each FROM so a COPY . in a builder stage
438
+ # does not produce a false positive against installs in a separate runtime stage.
439
+ COPY_ORDER_ISSUE=$(printf '%s\n' "$normalized_content" | awk '
440
+ {
441
+ lower = tolower($0)
442
+
443
+ # New build stage — reset per-stage COPY . tracking
444
+ if (lower ~ /^[[:space:]]*from[[:space:]]+/) {
445
+ stage_copy_line = 0
446
+ next
447
+ }
448
+
449
+ # Skip comment lines
450
+ if (lower ~ /^[[:space:]]*#/) { next }
451
+
452
+ # Record the first COPY . in the current stage (ignore COPY --from=)
453
+ if (stage_copy_line == 0 && lower ~ /^[[:space:]]*copy[[:space:]]+/) {
454
+ stripped = lower
455
+ sub(/^[[:space:]]*copy[[:space:]]+/, "", stripped)
456
+ while (stripped ~ /^--[^[:space:]]+[[:space:]]+/) {
457
+ sub(/^--[^[:space:]]+[[:space:]]+/, "", stripped)
458
+ }
459
+ split(stripped, parts, /[[:space:]]+/)
460
+ if (parts[1] == "." || parts[1] == "./") {
461
+ stage_copy_line = NR
462
+ }
463
+ }
464
+
465
+ # Flag if a package install follows COPY . within the same stage
466
+ if (stage_copy_line > 0 && NR > stage_copy_line && lower ~ /^[[:space:]]*run[[:space:]]+/) {
467
+ if (lower ~ /pip[[:space:]]+install|npm[[:space:]]+(install|ci)|yarn([[:space:]]|$)|go[[:space:]]+mod[[:space:]]|apt-get[[:space:]]+install|apk[[:space:]]+add/) {
468
+ found = 1
469
+ exit
470
+ }
471
+ }
472
+ }
473
+ END { print found + 0 }
474
+ ')
475
+
476
+ if [ "$COPY_ORDER_ISSUE" -gt 0 ]; then
477
+ echo -e "${YELLOW}[WARNING] COPY . appears before dependency installation${NC}"
478
+ echo " → Copy dependency files (package.json, requirements.txt) first for better cache"
479
+ echo " → Then install dependencies, then COPY . for source code"
480
+ ((BP_WARNINGS++))
481
+ fi
482
+
483
+ echo ""
484
+ echo "Best Practices Summary:"
485
+ echo -e " Errors: ${RED}$BP_ERRORS${NC}"
486
+ echo -e " Warnings: ${YELLOW}$BP_WARNINGS${NC}"
487
+ echo -e " Info: ${PURPLE}$BP_INFO${NC}"
488
+ echo ""
489
+
490
+ if [ $BP_ERRORS -eq 0 ] && [ $BP_WARNINGS -eq 0 ]; then
491
+ echo -e "${GREEN}✓ Best practices validation passed${NC}"
492
+ return 0
493
+ elif [ $BP_ERRORS -eq 0 ]; then
494
+ echo -e "${YELLOW}⚠ Best practices completed with warnings${NC}"
495
+ BP_HAS_WARNINGS=true
496
+ return 0
497
+ else
498
+ echo -e "${RED}✗ Best practices validation failed${NC}"
499
+ return 1
500
+ fi
501
+ }
502
+
503
+ run_optimization() {
504
+ echo -e "${CYAN}${BOLD}[4/4] Optimization Analysis${NC}"
505
+ echo "=============================="
506
+ echo ""
507
+
508
+ # Create normalized version for accurate multi-line instruction counting
509
+ local normalized_content
510
+ normalized_content=$(normalize_dockerfile "$DOCKERFILE")
511
+
512
+ # Analyze base images
513
+ BASE_IMAGES=$(from_images "$normalized_content")
514
+
515
+ echo -e "${BLUE}Base Image Analysis:${NC}"
516
+ for image in $BASE_IMAGES; do
517
+ if echo "$image" | grep -qi "distroless"; then
518
+ continue
519
+ fi
520
+
521
+ if echo "$image" | grep -qiE "ubuntu|debian|centos|fedora"; then
522
+ echo -e " ${PURPLE}[OPTIMIZATION] Consider Alpine alternative for: $image${NC}"
523
+ echo " → Alpine images are 10-100x smaller"
524
+ fi
525
+ done
526
+ echo ""
527
+
528
+ # Multi-stage analysis (using normalized content)
529
+ FROM_COUNT=$(count_instruction "$normalized_content" "FROM")
530
+
531
+ echo -e "${BLUE}Build Structure:${NC}"
532
+ if [ "$FROM_COUNT" -eq "1" ]; then
533
+ if echo "$normalized_content" | grep -qiE "apt-get install.*(gcc|make|build)" || \
534
+ echo "$normalized_content" | grep -qiE "apk add.*(gcc|make|build)"; then
535
+ echo -e " ${PURPLE}[OPTIMIZATION] Build tools detected in single-stage build${NC}"
536
+ echo " → Consider multi-stage build to exclude build tools from final image"
537
+ fi
538
+ else
539
+ FINAL_FROM=$(final_from_image "$normalized_content")
540
+ if echo "$FINAL_FROM" | grep -qiE "distroless|alpine|scratch"; then
541
+ echo -e " ${GREEN}✓ Using minimal base for final stage: $FINAL_FROM${NC}"
542
+ else
543
+ echo -e " ${PURPLE}[OPTIMIZATION] Final stage could use smaller base image${NC}"
544
+ echo " → Consider: alpine, distroless, or scratch"
545
+ fi
546
+ fi
547
+ echo ""
548
+
549
+ # Layer count (reuse RUN_COUNT from best practices if available, otherwise calculate)
550
+ RUN_COUNT=$(count_instruction "$normalized_content" "RUN")
551
+
552
+ echo -e "${BLUE}Layer Optimization:${NC}"
553
+ echo " RUN commands: $RUN_COUNT"
554
+ if [ "$RUN_COUNT" -gt "7" ]; then
555
+ echo -e " ${PURPLE}[OPTIMIZATION] Consider combining RUN commands${NC}"
556
+ echo " → Reduces layer count and image size"
557
+ fi
558
+ echo ""
559
+
560
+ # .dockerignore check
561
+ DOCKERFILE_DIR=$(dirname "$DOCKERFILE")
562
+ if [ ! -f "$DOCKERFILE_DIR/.dockerignore" ]; then
563
+ echo -e "${YELLOW}[INFO] No .dockerignore file found${NC}"
564
+ echo " → Create .dockerignore to optimize build context"
565
+ echo ""
566
+ fi
567
+
568
+ echo -e "${GREEN}✓ Optimization analysis complete${NC}"
569
+ return 0
570
+ }
571
+
572
+ ################################################################################
573
+ # Main Execution
574
+ ################################################################################
575
+
576
+ show_help() {
577
+ # Use echo -e so that BOLD/NC variables (which hold \033[...m escape sequences)
578
+ # are interpreted correctly. cat << EOF expands variables but does not process
579
+ # \033 escape sequences, causing raw escape codes to appear in the output.
580
+ echo -e "${BOLD}Dockerfile Validator - Complete Lifecycle${NC}"
581
+ echo ""
582
+ echo "Validates Dockerfiles with automatic tool management and cleanup."
583
+ echo ""
584
+ echo -e "${BOLD}Usage:${NC}"
585
+ echo " $(basename "$0") [Dockerfile]"
586
+ echo ""
587
+ echo -e "${BOLD}Validation Stages:${NC}"
588
+ echo " 1. Syntax validation (hadolint)"
589
+ echo " 2. Security scanning (Checkov)"
590
+ echo " 3. Best practices validation"
591
+ echo " 4. Optimization analysis"
592
+ echo ""
593
+ echo -e "${BOLD}Features:${NC}"
594
+ echo " • Auto-installs tools if not found"
595
+ echo " • Runs all validation stages"
596
+ echo " • Auto-cleanup on exit"
597
+ echo ""
598
+ echo -e "${BOLD}Examples:${NC}"
599
+ echo " $(basename "$0") # Validate ./Dockerfile"
600
+ echo " $(basename "$0") Dockerfile.prod # Validate specific file"
601
+ echo ""
602
+ echo -e "${BOLD}Exit Codes:${NC}"
603
+ echo " 0 All validations passed"
604
+ echo " 1 One or more validations failed"
605
+ echo " 2 Critical error"
606
+ echo ""
607
+ }
608
+
609
+ # Check for help
610
+ ARG1="${1:-}"
611
+ if [[ "$ARG1" == "-h" ]] || [[ "$ARG1" == "--help" ]]; then
612
+ show_help
613
+ exit 0
614
+ fi
615
+
616
+ # Validate input
617
+ if [ ! -f "$DOCKERFILE" ]; then
618
+ echo -e "${RED}ERROR: Dockerfile not found: $DOCKERFILE${NC}" >&2
619
+ echo ""
620
+ echo "Usage: $(basename "$0") [Dockerfile]"
621
+ exit 2
622
+ fi
623
+
624
+ # Print header
625
+ echo ""
626
+ echo -e "${CYAN}${BOLD}========================================${NC}"
627
+ echo -e "${CYAN}${BOLD} Dockerfile Validator${NC}"
628
+ echo -e "${CYAN}${BOLD}========================================${NC}"
629
+ echo ""
630
+ echo -e "${BOLD}Target:${NC} $DOCKERFILE"
631
+ echo -e "${BOLD}Date:${NC} $(date '+%Y-%m-%d %H:%M:%S')"
632
+ echo ""
633
+
634
+ # Check and install tools if needed
635
+ if ! check_tools; then
636
+ install_tools
637
+ fi
638
+
639
+ echo -e "${CYAN}${BOLD}Running Validations...${NC}"
640
+ echo ""
641
+
642
+ # Track results
643
+ HADOLINT_RESULT="SKIP"
644
+ CHECKOV_RESULT="SKIP"
645
+ BEST_PRACTICES_RESULT="SKIP"
646
+ OPTIMIZATION_RESULT="SKIP"
647
+
648
+ # Run all validations
649
+ run_hadolint && HADOLINT_RESULT="PASS" || HADOLINT_RESULT="FAIL"
650
+ echo ""
651
+
652
+ run_checkov && CHECKOV_RESULT="PASS" || CHECKOV_RESULT="FAIL"
653
+ echo ""
654
+
655
+ run_best_practices && {
656
+ [ "$BP_HAS_WARNINGS" = true ] && BEST_PRACTICES_RESULT="WARN" || BEST_PRACTICES_RESULT="PASS"
657
+ } || BEST_PRACTICES_RESULT="FAIL"
658
+ echo ""
659
+
660
+ run_optimization && OPTIMIZATION_RESULT="INFO"
661
+ echo ""
662
+
663
+ # Print summary
664
+ echo -e "${CYAN}${BOLD}========================================${NC}"
665
+ echo -e "${CYAN}${BOLD} Validation Summary${NC}"
666
+ echo -e "${CYAN}${BOLD}========================================${NC}"
667
+ echo ""
668
+
669
+ # Print results
670
+ [ "$HADOLINT_RESULT" = "PASS" ] && echo -e " Syntax (hadolint): ${GREEN}✓ PASSED${NC}" || echo -e " Syntax (hadolint): ${RED}✗ FAILED${NC}"
671
+ [ "$CHECKOV_RESULT" = "PASS" ] && echo -e " Security (Checkov): ${GREEN}✓ PASSED${NC}" || echo -e " Security (Checkov): ${RED}✗ FAILED${NC}"
672
+ if [ "$BEST_PRACTICES_RESULT" = "PASS" ]; then
673
+ echo -e " Best Practices: ${GREEN}✓ PASSED${NC}"
674
+ elif [ "$BEST_PRACTICES_RESULT" = "WARN" ]; then
675
+ echo -e " Best Practices: ${YELLOW}⚠ WARNED${NC}"
676
+ else
677
+ echo -e " Best Practices: ${RED}✗ FAILED${NC}"
678
+ fi
679
+ echo -e " Optimization: ${BLUE}ℹ INFORMATIONAL${NC}"
680
+
681
+ echo ""
682
+ echo -e "${CYAN}${BOLD}========================================${NC}"
683
+ echo ""
684
+
685
+ # Overall result
686
+ if [ $EXIT_CODE -eq 0 ]; then
687
+ echo -e "${GREEN}${BOLD}✓ Overall Result: PASSED${NC}"
688
+ echo ""
689
+ echo "Your Dockerfile meets validation requirements."
690
+ else
691
+ echo -e "${RED}${BOLD}✗ Overall Result: FAILED${NC}"
692
+ echo ""
693
+ echo "Please address the issues identified above."
694
+ fi
695
+
696
+ echo ""
697
+
698
+ # Exit (cleanup trap will run automatically)
699
+ exit $EXIT_CODE