Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
275 lines (236 loc) · 12.2 KB
/
Copy pathMakefile
File metadata and controls
275 lines (236 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# Repo automation. Chart targets take CHART=<name>; without it they cover every chart.
CHARTS_DIR ?= charts
CHART ?=
LEVEL ?= patch
# Repo-local CR schemas consulted by `validate` before the remote datree
# catalog, so a kind the catalog has not published yet still gets validated.
# Populate with scripts/validate/vendor-crd-schema.sh.
SCHEMAS_DIR ?= schemas
# chart-testing knobs, mirroring ct's defaults so a fork / other remote works unchanged.
# ct's default scope is changed charts only — it lints NOTHING on a clean tree, unlike
# lint/template/validate. `ALL=1 make ct-lint` covers every chart.
CT_REMOTE ?= origin
CT_TARGET_BRANCH ?= main
# kubeconform downloads every schema over HTTPS. Without a cache an offline or
# rate-limited run resolves nothing, and -ignore-missing-schemas would turn that
# into "everything skipped, all green" — the exact blind spot check-skips.sh
# exists to catch. Cached under a gitignored dir; `make clean` clears it.
KUBECONFORM_CACHE ?= .kubeconform-cache
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec
ALL_CHARTS := $(shell find $(CHARTS_DIR) -mindepth 1 -maxdepth 1 -type d -exec basename {} \; 2>/dev/null | sort)
ifeq ($(CHART),)
TARGET_CHARTS := $(ALL_CHARTS)
else
TARGET_CHARTS := $(CHART)
endif
# Shared by `template` and `validate`: one helm template per charts/<c>/ci/*.yaml
# (pure defaults when there is none), matching ct lint. Not one render with merged
# -f flags: keycloak-cr's routing fixtures contradict each other on purpose.
# Expanded inside `for c in $(TARGET_CHARTS)`, so $$c is the chart.
# Brace-grouped so `$(HELM_TEMPLATE_CMD) | kubeconform` pipes the whole loop —
# without braces `|` binds tighter than `||` and pipes only the fallback render.
HELM_TEMPLATE_CMD = { rendered=0; \
for f in $(CHARTS_DIR)/$$c/ci/*.yaml; do \
[ -f "$$f" ] || continue; \
rendered=1; \
helm template ci $(CHARTS_DIR)/$$c -f "$$f" || exit 1; \
done; \
[ "$$rendered" = "1" ] || helm template ci $(CHARTS_DIR)/$$c; \
}
# Guards for chart-specific targets. Usage at recipe start:
# $(call require_chart,make bump CHART=<name> LEVEL=patch)
# Emits a single line per error; example string is the user-facing hint.
define require_chart
@if [ -z "$(CHART)" ]; then \
echo "ERROR: CHART is required (e.g. $(1))"; exit 1; \
fi
@if [ ! -d "$(CHARTS_DIR)/$(CHART)" ]; then \
echo "ERROR: chart not found: $(CHARTS_DIR)/$(CHART)"; exit 1; \
fi
endef
.PHONY: all
all: lint template ## Default: lint + template all charts.
##@ General
.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m [VAR=value ...]\n\nVariables:\n \033[36mCHART\033[0m target a single chart (default: all charts under $(CHARTS_DIR)/)\n \033[36mLEVEL\033[0m patch | minor | major (used by bump, default: patch)\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-18s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
.PHONY: charts
charts: ## List all charts and their current versions.
@for c in $(ALL_CHARTS); do \
v=$$(awk '/^version:/ {print $$2}' $(CHARTS_DIR)/$$c/Chart.yaml); \
printf " %-30s %s\n" "$$c" "$$v"; \
done
.PHONY: version
version: ## Show CHART current version. Usage: make version CHART=<name>
$(call require_chart,make version CHART=<name>)
@awk '/^version:/ {print $$2}' $(CHARTS_DIR)/$(CHART)/Chart.yaml
##@ Development
.PHONY: scaffold
scaffold: ## Copy an existing chart as starting point. Usage: make scaffold CHART=<new> FROM=<existing>
@if [ -z "$(CHART)" ] || [ -z "$(FROM)" ]; then echo "Usage: make scaffold CHART=<new> FROM=<existing>"; exit 1; fi
@if [ -d "$(CHARTS_DIR)/$(CHART)" ]; then echo "ERROR: chart already exists: $(CHARTS_DIR)/$(CHART)"; exit 1; fi
@if [ ! -d "$(CHARTS_DIR)/$(FROM)" ]; then echo "ERROR: source chart not found: $(CHARTS_DIR)/$(FROM)"; exit 1; fi
cp -r $(CHARTS_DIR)/$(FROM) $(CHARTS_DIR)/$(CHART)
@sed -i.bak "s/^name:.*/name: $(CHART)/" $(CHARTS_DIR)/$(CHART)/Chart.yaml
@sed -i.bak "s/^version:.*/version: 0.1.0/" $(CHARTS_DIR)/$(CHART)/Chart.yaml
@rm $(CHARTS_DIR)/$(CHART)/Chart.yaml.bak
@echo "Scaffolded: $(CHARTS_DIR)/$(CHART) (copied from $(FROM), name renamed, version reset to 0.1.0)"
@echo ""
@echo "NEXT STEPS:"
@echo " 1. Edit $(CHARTS_DIR)/$(CHART)/Chart.yaml — description, keywords, artifacthub.io/changes"
@echo " 2. Replace $(CHARTS_DIR)/$(CHART)/values.yaml and values.schema.json with the new chart's schema"
@echo " 3. Replace templates/ with the new chart's manifests"
@echo " 4. Rewrite $(CHARTS_DIR)/$(CHART)/README.md"
@echo " 5. make lint CHART=$(CHART) && make template CHART=$(CHART)"
.PHONY: lint
lint: ## helm lint each chart (CHART=name to limit).
@for c in $(TARGET_CHARTS); do \
echo "==> helm lint $$c"; \
helm lint $(CHARTS_DIR)/$$c; \
done
.PHONY: ct-lint
ct-lint: ## chart-testing lint (matches CI behavior). ALL=1 lints every chart instead of only changed ones.
@command -v ct >/dev/null 2>&1 || { echo "chart-testing required: brew install chart-testing"; exit 1; }
@scope="--target-branch $(CT_TARGET_BRANCH) --check-version-increment=false"; \
if [ -n "$(ALL)" ]; then scope="--all"; fi; \
extra=""; \
host=$$(git remote get-url $(CT_REMOTE) 2>/dev/null \
| sed -E 's|^[a-z]+://||; s|^[^@]*@||; s|[:/].*$$||'); \
if [ -n "$$host" ] && ! python3 -c 'import socket,sys; socket.gethostbyname(sys.argv[1])' "$$host" >/dev/null 2>&1; then \
echo "NOTE: git remote host '$$host' does not resolve — skipping ct's maintainer validation."; \
echo " (a multi-account SSH alias such as git@github.com-<account>: is not a real host;"; \
echo " CI checks out over HTTPS, so maintainers are still validated there)"; \
extra="--validate-maintainers=false"; \
fi; \
echo "==> ct lint $$scope $$extra"; \
ct lint $$scope $$extra
.PHONY: template
template: ## helm template render smoke test (CHART=name to limit). Picks up charts/<name>/ci/*.yaml if present.
@for c in $(TARGET_CHARTS); do \
echo "==> helm template $$c"; \
$(HELM_TEMPLATE_CMD) > /dev/null; \
done
.PHONY: validate
validate: ## kubeconform validation against k8s + CRD schemas, plus RBAC parity with charts/<name>/upstream/ when present (CHART=name to limit). Picks up charts/<name>/ci/*.yaml if present.
@command -v kubeconform >/dev/null 2>&1 || { echo "kubeconform required: brew install kubeconform"; exit 1; }
@mkdir -p $(KUBECONFORM_CACHE)
@for c in $(TARGET_CHARTS); do \
echo "==> kubeconform $$c"; \
$(HELM_TEMPLATE_CMD) | kubeconform \
-strict \
-ignore-missing-schemas \
-cache $(KUBECONFORM_CACHE) \
-schema-location default \
-schema-location '$(SCHEMAS_DIR)/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-output json -verbose \
| scripts/validate/check-skips.sh $$c; \
scripts/validate/check-upstream-rbac.sh $$c; \
done
.PHONY: ci
ci: lint ct-lint template validate ## Run the full CI suite locally.
##@ Packaging
.PHONY: package
package: ## helm package each chart into ./.cr-release-packages/ (CHART=name to limit).
@mkdir -p .cr-release-packages
@for c in $(TARGET_CHARTS); do \
echo "==> helm package $$c"; \
helm package $(CHARTS_DIR)/$$c -d .cr-release-packages; \
done
.PHONY: clean
clean: ## Remove generated tarballs, helm artifacts, and the kubeconform schema cache.
rm -rf .cr-release-packages $(KUBECONFORM_CACHE)
##@ Release
.PHONY: bump
bump: ## Bump CHART version. Usage: make bump CHART=<name> LEVEL=patch|minor|major
$(call require_chart,make bump CHART=<name> LEVEL=patch)
@case "$(LEVEL)" in patch|minor|major) ;; *) echo "ERROR: LEVEL must be patch, minor, or major (got: $(LEVEL))"; exit 1 ;; esac
@CURRENT=$$(awk '/^version:/ {print $$2}' $(CHARTS_DIR)/$(CHART)/Chart.yaml | tr -d '"'); \
IFS='.' read -r MAJ MIN PAT <<< "$$CURRENT"; \
if [[ -z "$$MAJ" || -z "$$MIN" || -z "$$PAT" ]]; then \
echo "ERROR: cannot parse current version '$$CURRENT' as semver"; exit 1; \
fi; \
case "$(LEVEL)" in \
patch) NEW="$$MAJ.$$MIN.$$((PAT+1))" ;; \
minor) NEW="$$MAJ.$$((MIN+1)).0" ;; \
major) NEW="$$((MAJ+1)).0.0" ;; \
esac; \
echo "Bumping $(CHART): $$CURRENT -> $$NEW"; \
sed -i.bak "s/^version:.*/version: $$NEW/" $(CHARTS_DIR)/$(CHART)/Chart.yaml; \
rm $(CHARTS_DIR)/$(CHART)/Chart.yaml.bak; \
echo ""; \
echo "NEXT STEPS:"; \
echo " 1. Replace the 'artifacthub.io/changes' entries in $(CHARTS_DIR)/$(CHART)/Chart.yaml with this release's changes (do not append)"; \
echo " 2. Run 'make changelog CHART=$(CHART)' to update CHANGELOG.md"; \
echo " 3. Update $(CHARTS_DIR)/$(CHART)/README.md if values/behavior changed"; \
echo " 4. git add $(CHARTS_DIR)/$(CHART) && git commit -m 'feat($(CHART)): describe change'"; \
echo " 5. git push (release.yml auto-triggers chart-releaser + GHCR push)"
.PHONY: changelog
changelog: ## Sync CHART CHANGELOG.md from Chart.yaml. Usage: make changelog CHART=<name> [DRY_RUN=1]
$(call require_chart,make changelog CHART=<name>)
@./scripts/changelog/sync-changelog.sh $(if $(DRY_RUN),--dry-run) $(CHARTS_DIR)/$(CHART)
.PHONY: changelog-all
changelog-all: ## Sync CHANGELOG.md for every chart (used for backfill / CI sanity).
@./scripts/changelog/sync-changelog.sh $(if $(DRY_RUN),--dry-run) --all
##@ Upgrade-sync (chart upgrade.sh body propagation)
.PHONY: sync-check
sync-check: ## Verify each chart's upgrade.sh body matches its declared template (CI guard).
@./scripts/upgrade-sync/sync.sh --check
.PHONY: sync-apply
sync-apply: ## Rewrite each chart's upgrade.sh body from its template. Refuses on dirty tree.
@./scripts/upgrade-sync/sync.sh --apply
.PHONY: sync-list
sync-list: ## List managed charts and their templates.
@./scripts/upgrade-sync/sync.sh --list
.PHONY: sync-status
sync-status: ## List every chart with classification (managed / unmanaged / missing-template).
@./scripts/upgrade-sync/sync.sh --status
##@ Version drift (upstream tracking + auto-bump)
.PHONY: version-check
version-check: ## Drift-only report (read-only, no file or branch changes).
@./scripts/check-version/check-version.sh --check
.PHONY: version-apply
version-apply: ## For each drifted chart: bump + ci + branch + commit + push + open PR.
@./scripts/check-version/check-version.sh --apply
##@ Shell scripts
# SYNTAX_TARGETS (bash -n + zsh -n) adds the per-chart upgrade.sh copies;
# SHELLCHECK_TARGETS skips them since they are sync output of
# scripts/upgrade-sync/templates/. Fails at --severity=error; warnings print as
# advisory, info is not shown. STRICT=1 makes a missing shellcheck fatal (e.g. CI).
SYNTAX_TARGETS := $(shell find scripts -type f -name '*.sh' 2>/dev/null | sort) $(wildcard $(CHARTS_DIR)/*/upgrade.sh)
SHELLCHECK_TARGETS := $(shell find scripts -type f -name '*.sh' 2>/dev/null | sort)
.PHONY: shell-lint
shell-lint: ## Lint repo shell scripts: bash -n + zsh -n on every script, shellcheck (--severity=error) on scripts/ only, then the zsh-portability guards. STRICT=1 to require shellcheck.
@if [ -z "$(SYNTAX_TARGETS)" ]; then \
echo "shell-lint: no scripts found"; exit 0; \
fi; \
rc=0; \
echo "==> bash -n ($(words $(SYNTAX_TARGETS)) files)"; \
for f in $(SYNTAX_TARGETS); do \
bash -n "$$f" || { echo "FAIL bash -n: $$f"; rc=1; }; \
done; \
if command -v zsh >/dev/null 2>&1; then \
echo "==> zsh -n ($(words $(SYNTAX_TARGETS)) files)"; \
for f in $(SYNTAX_TARGETS); do \
zsh -n "$$f" || { echo "FAIL zsh -n: $$f"; rc=1; }; \
done; \
else \
echo "==> zsh -n skipped (zsh not installed)"; \
fi; \
if command -v shellcheck >/dev/null 2>&1; then \
echo "==> shellcheck --severity=error ($(words $(SHELLCHECK_TARGETS)) files; advisory output for warning/info)"; \
shellcheck --severity=error $(SHELLCHECK_TARGETS) || rc=1; \
echo "--- advisory shellcheck output (warning/info, not fail) ---"; \
shellcheck --severity=warning $(SHELLCHECK_TARGETS) || true; \
else \
msg="shellcheck not installed (brew install shellcheck)"; \
if [ -n "$(STRICT)" ]; then \
echo "==> shellcheck FAIL: $$msg (STRICT=1)"; rc=1; \
else \
echo "==> shellcheck skipped: $$msg"; \
fi; \
fi; \
echo "==> zsh-portability guards ($(words $(SYNTAX_TARGETS)) files)"; \
./scripts/shell-guards/shell-guards.sh $(SYNTAX_TARGETS) || rc=1; \
exit $$rc