@hybridlabor-api/aos 4.8.0 → 4.10.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.
- package/.agents/{agents.md → AGENTS.md} +3 -1
- package/.agents/nodes.json +3 -1
- package/.agents/vendor-manifest.json +23 -1
- package/.claude/agents/godmode-media-eventtech.md +1 -1
- package/.claude/hooks/conventional-commits.mjs +125 -0
- package/.claude/hooks/env-file-protection.mjs +105 -0
- package/.claude/hooks/go-gate.mjs +101 -81
- package/.claude/hooks/memb-inject.mjs +29 -1
- package/.claude/settings.json +13 -0
- package/.claude/workflows/startcycle-dispatch.mjs +23 -1
- package/.opencode/agents/godmode-media-eventtech.md +1 -1
- package/.opencode/plugins/bdb-aos.js +31 -4
- package/CLAUDE.md +0 -571
- package/README.de.md +1 -1
- package/README.md +1 -1
- package/README.pt.md +1 -1
- package/THIRD_PARTY_NOTICES.md +126 -0
- package/bin/aos-doctor.mjs +1 -1
- package/docs/skills_table.md +1 -1
- package/installer.js +187 -55
- package/package.json +7 -3
- package/packages/aos-cli/README.md +80 -0
- package/packages/aos-cli/bin/aos-cli.mjs +134 -0
- package/packages/aos-cli/core-skills.json +12 -0
- package/packages/aos-cli/extensions/aos.ts +321 -0
- package/packages/aos-cli/package-lock.json +1923 -0
- package/packages/aos-cli/package.json +29 -0
- package/packages/aos-cli/scripts/check-theme.mjs +63 -0
- package/packages/aos-cli/themes/aos.json +97 -0
- package/scripts/build-plugin-manifest.mjs +131 -0
- package/scripts/validate-skills.mjs +81 -6
- package/skills/basic/ao-orchestrator/SKILL.md +116 -0
- package/skills/basic/bdb-eventagency-skill/SKILL.md +252 -0
- package/skills/basic/bdb-shipping-skill/SKILL.md +161 -0
- package/skills/basic/godmode-eventtech/SKILL.md +4 -1
- package/skills/global_config/agenttrail/SKILL.md +6 -1
- package/skills/global_config/aos-project-init/SKILL.md +2 -0
- package/skills/global_config/aos-project-init/assets/AGENTS.template.md +1 -1
- package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +1 -1
- package/skills/global_config/aos-setup/SKILL.md +1 -1
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
- package/skills/global_config/ask-tim/SKILL.md +7 -7
- package/skills/global_config/bash-script-generator/SKILL.md +201 -0
- package/skills/global_config/bash-script-generator/assets/templates/standard-template.sh +96 -0
- package/skills/global_config/bash-script-generator/docs/bash-scripting-guide.md +729 -0
- package/skills/global_config/bash-script-generator/docs/generation-best-practices.md +193 -0
- package/skills/global_config/bash-script-generator/docs/script-patterns.md +566 -0
- package/skills/global_config/bash-script-generator/docs/text-processing-guide.md +437 -0
- package/skills/global_config/bash-script-generator/examples/log-analyzer.sh +92 -0
- package/skills/global_config/bash-script-generator/scripts/generate_script_template.sh +123 -0
- package/skills/global_config/bash-script-generator/scripts/run_ci_checks.sh +172 -0
- package/skills/global_config/bash-script-generator/scripts/test_generator.sh +413 -0
- package/skills/global_config/bash-script-validator/SKILL.md +249 -0
- package/skills/global_config/bash-script-validator/docs/awk-reference.md +449 -0
- package/skills/global_config/bash-script-validator/docs/bash-reference.md +468 -0
- package/skills/global_config/bash-script-validator/docs/common-mistakes.md +623 -0
- package/skills/global_config/bash-script-validator/docs/grep-reference.md +395 -0
- package/skills/global_config/bash-script-validator/docs/regex-reference.md +391 -0
- package/skills/global_config/bash-script-validator/docs/sed-reference.md +454 -0
- package/skills/global_config/bash-script-validator/docs/shell-reference.md +463 -0
- package/skills/global_config/bash-script-validator/docs/shellcheck-reference.md +399 -0
- package/skills/global_config/bash-script-validator/examples/bad-bash.sh +55 -0
- package/skills/global_config/bash-script-validator/examples/bad-shell.sh +54 -0
- package/skills/global_config/bash-script-validator/examples/good-bash.sh +71 -0
- package/skills/global_config/bash-script-validator/examples/good-shell.sh +69 -0
- package/skills/global_config/bash-script-validator/scripts/run_ci_checks.sh +23 -0
- package/skills/global_config/bash-script-validator/scripts/shellcheck_wrapper.sh +174 -0
- package/skills/global_config/bash-script-validator/scripts/test_validate.sh +446 -0
- package/skills/global_config/bash-script-validator/scripts/validate.sh +512 -0
- package/skills/global_config/ci-pipeline/SKILL.md +135 -0
- package/skills/global_config/deja-memory/SKILL.md +3 -1
- package/skills/global_config/dispatching-parallel-agents/SKILL.md +170 -0
- package/skills/global_config/dockerfile-generator/SKILL.md +1038 -0
- package/skills/global_config/dockerfile-generator/examples/example.dockerignore +95 -0
- package/skills/global_config/dockerfile-generator/examples/golang-distroless.Dockerfile +34 -0
- package/skills/global_config/dockerfile-generator/examples/java-springboot.Dockerfile +45 -0
- package/skills/global_config/dockerfile-generator/examples/nextjs-production.Dockerfile +49 -0
- package/skills/global_config/dockerfile-generator/examples/nodejs-multistage.Dockerfile +55 -0
- package/skills/global_config/dockerfile-generator/examples/python-fastapi.Dockerfile +48 -0
- package/skills/global_config/dockerfile-generator/references/language_specific_guides.md +510 -0
- package/skills/global_config/dockerfile-generator/references/multistage_builds.md +570 -0
- package/skills/global_config/dockerfile-generator/references/optimization_patterns.md +492 -0
- package/skills/global_config/dockerfile-generator/references/security_best_practices.md +375 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_dockerignore.sh +199 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_golang.sh +172 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_java.sh +185 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_nodejs.sh +279 -0
- package/skills/global_config/dockerfile-generator/scripts/generate_python.sh +218 -0
- package/skills/global_config/dockerfile-generator/scripts/test_generator.sh +210 -0
- package/skills/global_config/dockerfile-validator/SKILL.md +300 -0
- package/skills/global_config/dockerfile-validator/examples/.dockerignore.example +34 -0
- package/skills/global_config/dockerfile-validator/examples/bad-example.Dockerfile +37 -0
- package/skills/global_config/dockerfile-validator/examples/golang-distroless.Dockerfile +50 -0
- package/skills/global_config/dockerfile-validator/examples/good-example.Dockerfile +52 -0
- package/skills/global_config/dockerfile-validator/examples/python-optimized.Dockerfile +53 -0
- package/skills/global_config/dockerfile-validator/examples/security-issues.Dockerfile +42 -0
- package/skills/global_config/dockerfile-validator/references/docker_best_practices.md +348 -0
- package/skills/global_config/dockerfile-validator/references/optimization_guide.md +473 -0
- package/skills/global_config/dockerfile-validator/references/security_checklist.md +208 -0
- package/skills/global_config/dockerfile-validator/scripts/dockerfile-validate.sh +699 -0
- package/skills/global_config/dockerfile-validator/scripts/test_validate.sh +35 -0
- package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn-lock-read.Dockerfile +6 -0
- package/skills/global_config/dockerfile-validator/tests/fixtures/copy-before-yarn.Dockerfile +6 -0
- package/skills/global_config/dockerfile-validator/tests/fixtures/from-platform-nonroot.Dockerfile +7 -0
- package/skills/global_config/dockerfile-validator/tests/test_regression.sh +164 -0
- package/skills/global_config/finishing-a-development-branch/SKILL.md +228 -0
- package/skills/global_config/github-actions-generator/SKILL.md +353 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/composite/action.yml +82 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/docker/Dockerfile +25 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/docker/action.yml +42 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/docker/entrypoint.sh +27 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/javascript/action.yml +33 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/javascript/index.js +50 -0
- package/skills/global_config/github-actions-generator/assets/templates/action/javascript/package.json +27 -0
- package/skills/global_config/github-actions-generator/assets/templates/workflow/basic_workflow.yml +242 -0
- package/skills/global_config/github-actions-generator/assets/templates/workflow/reusable_workflow.yml +106 -0
- package/skills/global_config/github-actions-generator/examples/README.md +147 -0
- package/skills/global_config/github-actions-generator/examples/actions/setup-node-cached/action.yml +93 -0
- package/skills/global_config/github-actions-generator/examples/caching/docker-buildkit.yml +256 -0
- package/skills/global_config/github-actions-generator/examples/security/dependency-review.yml +62 -0
- package/skills/global_config/github-actions-generator/examples/security/sbom-attestation.yml +119 -0
- package/skills/global_config/github-actions-generator/examples/triggers/chatops-commands.yml +475 -0
- package/skills/global_config/github-actions-generator/examples/triggers/repository-dispatch.yml +418 -0
- package/skills/global_config/github-actions-generator/examples/triggers/workflow-orchestration.yml +404 -0
- package/skills/global_config/github-actions-generator/examples/workflows/docker-build-push.yml +68 -0
- package/skills/global_config/github-actions-generator/examples/workflows/go-ci.yml +161 -0
- package/skills/global_config/github-actions-generator/examples/workflows/monorepo-ci.yml +340 -0
- package/skills/global_config/github-actions-generator/examples/workflows/multi-environment-deploy.yml +406 -0
- package/skills/global_config/github-actions-generator/examples/workflows/nodejs-ci.yml +122 -0
- package/skills/global_config/github-actions-generator/examples/workflows/python-ci.yml +157 -0
- package/skills/global_config/github-actions-generator/examples/workflows/scheduled-tasks.yml +376 -0
- package/skills/global_config/github-actions-generator/references/advanced-triggers.md +917 -0
- package/skills/global_config/github-actions-generator/references/best-practices.md +755 -0
- package/skills/global_config/github-actions-generator/references/common-actions.md +715 -0
- package/skills/global_config/github-actions-generator/references/custom-actions.md +320 -0
- package/skills/global_config/github-actions-generator/references/expressions-and-contexts.md +688 -0
- package/skills/global_config/github-actions-generator/references/modern-features.md +421 -0
- package/skills/global_config/github-actions-generator/scripts/test_generator.sh +344 -0
- package/skills/global_config/github-actions-templates/SKILL.md +7 -0
- package/skills/global_config/github-actions-validator/SKILL.md +576 -0
- package/skills/global_config/github-actions-validator/examples/README.md +88 -0
- package/skills/global_config/github-actions-validator/examples/outdated-versions.yml +76 -0
- package/skills/global_config/github-actions-validator/examples/valid-ci.yml +79 -0
- package/skills/global_config/github-actions-validator/examples/with-errors.yml +47 -0
- package/skills/global_config/github-actions-validator/references/act_usage.md +233 -0
- package/skills/global_config/github-actions-validator/references/action_versions.md +122 -0
- package/skills/global_config/github-actions-validator/references/actionlint_usage.md +343 -0
- package/skills/global_config/github-actions-validator/references/common_errors.md +512 -0
- package/skills/global_config/github-actions-validator/references/modern_features.md +384 -0
- package/skills/global_config/github-actions-validator/references/runners.md +317 -0
- package/skills/global_config/github-actions-validator/scripts/install_tools.sh +113 -0
- package/skills/global_config/github-actions-validator/scripts/validate_workflow.sh +910 -0
- package/skills/global_config/github-actions-validator/tests/test_validate_workflow.sh +237 -0
- package/skills/global_config/makefile-generator/SKILL.md +614 -0
- package/skills/global_config/makefile-generator/assets/templates/.gitkeep +1 -0
- package/skills/global_config/makefile-generator/docs/makefile-structure.md +530 -0
- package/skills/global_config/makefile-generator/docs/optimization-guide.md +784 -0
- package/skills/global_config/makefile-generator/docs/patterns-guide.md +642 -0
- package/skills/global_config/makefile-generator/docs/security-guide.md +361 -0
- package/skills/global_config/makefile-generator/docs/targets-guide.md +642 -0
- package/skills/global_config/makefile-generator/docs/variables-guide.md +596 -0
- package/skills/global_config/makefile-generator/scripts/add_standard_targets.sh +539 -0
- package/skills/global_config/makefile-generator/scripts/generate_makefile_template.sh +690 -0
- package/skills/global_config/makefile-generator/test/test_helper_scripts.sh +190 -0
- package/skills/global_config/makefile-validator/SKILL.md +244 -0
- package/skills/global_config/makefile-validator/docs/bake-tool.md +1000 -0
- package/skills/global_config/makefile-validator/docs/best-practices.md +858 -0
- package/skills/global_config/makefile-validator/docs/common-mistakes.md +944 -0
- package/skills/global_config/makefile-validator/examples/bad-makefile.mk +77 -0
- package/skills/global_config/makefile-validator/examples/good-makefile.mk +103 -0
- package/skills/global_config/makefile-validator/scripts/test_validate.sh +382 -0
- package/skills/global_config/makefile-validator/scripts/validate_makefile.sh +712 -0
- package/skills/global_config/{MCP_Manage → mcp-manage}/SKILL.md +3 -3
- package/skills/global_config/plan-canvas/SKILL.md +9 -2
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +10 -2
- package/skills/global_config/plan-canvas/scripts/plan-canvas.js +1 -1
- package/skills/global_config/read-the-damn-docs/SKILL.md +175 -0
- package/skills/global_config/requesting-code-review/SKILL.md +98 -0
- package/skills/global_config/requesting-code-review/code-reviewer.md +198 -0
- package/skills/global_config/using-git-worktrees/SKILL.md +170 -0
- package/skills/global_config/verification-before-completion/SKILL.md +123 -0
- package/skills/global_config/writing-plans/SKILL.md +126 -46
- package/skills/global_config/writing-plans-legacy/SKILL.md +152 -0
- package/.claude/CLAUDE.md +0 -12
- package/mcps/RhinoMCP/docs/content/docs/getting-started/gemini.md +0 -61
- /package/{GEMINI.md → RULES.md} +0 -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
|