ci-pipeline.yml 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. # Copyright Broadcom, Inc. All Rights Reserved.
  2. # SPDX-License-Identifier: APACHE-2.0
  3. name: '[CI/CD] CI Pipeline'
  4. on: # rebuild any PRs and main branch changes
  5. pull_request_target:
  6. types:
  7. - opened
  8. - reopened
  9. - synchronize
  10. - labeled
  11. branches:
  12. - main
  13. - bitnami:main
  14. # Remove all permissions by default
  15. permissions: {}
  16. # Avoid concurrency over the same PR
  17. concurrency:
  18. group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  19. jobs:
  20. get-chart:
  21. runs-on: ubuntu-latest
  22. name: Get modified charts
  23. permissions:
  24. pull-requests: read
  25. outputs:
  26. chart: ${{ steps.get-chart.outputs.chart }}
  27. result: ${{ steps.get-chart.outputs.result }}
  28. values-updated: ${{ steps.get-chart.outputs.values-updated }}
  29. steps:
  30. - id: get-chart
  31. name: Get modified charts
  32. env:
  33. PULL_REQUEST_NUMBER: "${{ github.event.pull_request.number }}"
  34. PULL_REQUEST_URL: "${{ github.event.pull_request.url }}"
  35. GITHUB_TOKEN: "${{ github.token }}"
  36. run: |
  37. # Using the Github API to detect the files changed as git merge-base stops working when the branch is behind
  38. files_changed_data="$(gh api --paginate "/repos/${GITHUB_REPOSITORY}/pulls/${PULL_REQUEST_NUMBER}/files")"
  39. files_changed="$(echo "$files_changed_data" | jq -r '.[] | .filename')"
  40. # Adding || true to avoid "Process exited with code 1" errors
  41. charts_dirs_changed="$(echo "$files_changed" | xargs dirname | grep -o "bitnami/[^/]*" | sort | uniq || true)"
  42. # Using grep -c as a better alternative to wc -l when dealing with empty strings."
  43. num_charts_changed="$(echo "$charts_dirs_changed" | grep -c "bitnami" || true)"
  44. num_version_bumps="$(echo "$files_changed_data" | jq -r '[.[] | select(.filename|match("bitnami/[^/]+/Chart.yaml")) | select(.patch|contains("+version")) ] | length' )"
  45. non_readme_files=$(echo "$files_changed" | grep -vc "\.md" || true)
  46. if [[ $(curl -Lks "${PULL_REQUEST_URL}" | jq '.state | index("closed")') != *null* ]]; then
  47. # The PR for which this workflow run was launched is now closed -> SKIP
  48. echo "error=The PR for which this workflow run was launched is now closed. The tests will be skipped." >> "$GITHUB_OUTPUT"
  49. echo "result=skip" >> "$GITHUB_OUTPUT"
  50. elif [[ "$non_readme_files" -le "0" ]]; then
  51. # The only changes are .md files -> SKIP
  52. echo "result=skip" >> "$GITHUB_OUTPUT"
  53. elif [[ "$num_charts_changed" -ne "$num_version_bumps" ]]; then
  54. # Changes done in charts but version not bumped -> ERROR
  55. echo "error=Detected changes in charts without version bump in Chart.yaml. Charts changed: ${num_charts_changed}. Version bumps detected: ${num_version_bumps}" >> "$GITHUB_OUTPUT"
  56. echo "result=fail" >> "$GITHUB_OUTPUT"
  57. elif [[ "$num_charts_changed" -eq "1" ]]; then
  58. # Changes done in only one chart -> OK
  59. echo "result=ok" >> "$GITHUB_OUTPUT"
  60. # Extra output: chart name
  61. chart_name="${charts_dirs_changed//bitnami\/}"
  62. echo "chart=${chart_name}" >> "$GITHUB_OUTPUT"
  63. # Extra output: values-updated
  64. # shellcheck disable=SC2076
  65. if [[ "${files_changed[*]}" =~ "bitnami/${chart_name}/values.yaml" ]]; then
  66. echo "values-updated=true" >> "$GITHUB_OUTPUT"
  67. fi
  68. elif [[ "$num_charts_changed" -le "0" ]]; then
  69. # Changes done in the bitnami/ folder but not inside a chart subfolder -> SKIP
  70. echo "error=No changes detected in charts. The rest of the tests will be skipped." >> "$GITHUB_OUTPUT"
  71. echo "result=skip" >> "$GITHUB_OUTPUT"
  72. else
  73. # Changes done in more than chart -> SKIP
  74. echo "error=Changes detected in more than one chart directory. It is strongly advised to change only one chart in a PR. The rest of the tests will be skipped." >> "$GITHUB_OUTPUT"
  75. echo "result=skip" >> "$GITHUB_OUTPUT"
  76. fi
  77. # Using actions/github-scripts because using exit 1 in the script above would not provide any output
  78. # Source: https://github.community/t/no-output-on-process-completed-with-exit-code-1/123821/3
  79. - id: show-error
  80. name: Show error
  81. if: ${{ steps.get-chart.outputs.result != 'ok' }}
  82. uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
  83. with:
  84. script: |
  85. let message='${{ steps.get-chart.outputs.error }}';
  86. if ('${{ steps.get-chart.outputs.result }}' === 'fail' ) {
  87. core.setFailed(message);
  88. } else {
  89. core.warning(message);
  90. }
  91. chart-tests:
  92. runs-on: ubuntu-latest
  93. needs: [get-chart]
  94. name: Look for hardcoded images
  95. if: needs.get-chart.outputs.result == 'ok'
  96. steps:
  97. - name: Checkout bitnami/charts
  98. uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
  99. with:
  100. ref: ${{github.event.pull_request.head.ref}}
  101. repository: ${{github.event.pull_request.head.repo.full_name}}
  102. path: charts
  103. - id: check-hardcoded-images
  104. name: Look for hardcoded images
  105. env:
  106. CHART: ${{ needs.get-chart.outputs.chart }}
  107. run: |
  108. cd "${GITHUB_WORKSPACE}/charts" || exit 1
  109. hardcoded_images=()
  110. while read -r image; do
  111. if [[ -n "$image" && $image != {{*}} ]]; then
  112. hardcoded_images+=("${image}")
  113. fi
  114. done <<< "$(grep --exclude "NOTES.txt" -REoh "\s*image:\s+[\"']*.+[\"']*\s*$" "bitnami/${CHART}/templates" | sed "s/image: [\"']*//" | sed "s/[\"']*$//")"
  115. echo "${hardcoded_images[@]}"
  116. if [[ ${#hardcoded_images[@]} -gt 0 ]] ; then
  117. echo "error=Found hardcoded images in the chart templates: ${hardcoded_images[*]}"
  118. exit 1
  119. fi
  120. - id: check-image-warning-list
  121. name: Check image warning list
  122. env:
  123. CHART: ${{ needs.get-chart.outputs.chart }}
  124. run: |
  125. cd "${GITHUB_WORKSPACE}/charts" || exit 1
  126. if [[ "$CHART" != "common" && "$CHART" != "fluentd" ]]; then
  127. readarray -t tag_paths < <(yq e '.. | (path | join("."))' "bitnami/${CHART}/values.yaml" | grep -E '\.tag$' | sed 's/.tag$//g' | sort -u)
  128. readarray -t registry_paths < <(yq e '.. | (path | join("."))' "bitnami/${CHART}/values.yaml" | grep '\.registry$' | sed 's/.registry$//g' | sort -u)
  129. # We assume that image objects are those that contain both keys 'tag' and 'registry'
  130. images_paths=()
  131. for path in "${tag_paths[@]}"; do
  132. if echo "${registry_paths[@]}" | grep -w -q "$path"; then
  133. [[ -n "$path" ]] && images_paths+=("$path")
  134. fi
  135. done
  136. # Get the images defined in the image warning helper
  137. readarray -d ' ' -t images_list_tmp < <(grep -E 'common.warnings.modifiedImages' "bitnami/${CHART}/templates/NOTES.txt" | sed -E 's/.*\(list (.+)\) "context".*/\1/' | sed 's/.Values.//g')
  138. # Remove any empty element from the array
  139. images_list=()
  140. for i in "${images_list_tmp[@]}"; do
  141. if echo "$i" | grep -q -E "\S+"; then
  142. images_list+=("$i")
  143. fi
  144. done
  145. # Compare the image objects and the image warning list
  146. if [[ ${#images_list[@]} -eq ${#images_paths[@]} ]]; then
  147. for path in "${images_list[@]}"; do
  148. if ! echo "${images_paths[*]}" | grep -w -q "$path"; then
  149. echo "Found inconsistencies in the images warning list: '${images_list[*]}' should be equal to '${images_paths[*]}'"
  150. exit 1
  151. fi
  152. done
  153. else
  154. echo "Found inconsistencies in the images warning list: '${images_list[*]}' should be equal to '${images_paths[*]}'"
  155. exit 1
  156. fi
  157. fi
  158. update-pr:
  159. runs-on: ubuntu-latest
  160. needs: [get-chart]
  161. name: Automatically update README, CRDs and CHANGELOG
  162. permissions:
  163. contents: write
  164. outputs:
  165. result: ${{ steps.update-pr.outputs.result }}
  166. if: needs.get-chart.outputs.result == 'ok'
  167. steps:
  168. - name: Checkout bitnami/charts
  169. uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
  170. with:
  171. ref: ${{github.event.pull_request.head.ref}}
  172. repository: ${{github.event.pull_request.head.repo.full_name}}
  173. token: ${{ secrets.BITNAMI_BOT_TOKEN }}
  174. path: charts
  175. - name: Clone upstream bitnami/charts repository
  176. uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
  177. with:
  178. path: upstream-charts
  179. - name: Setup git configuration
  180. run: |
  181. cd $GITHUB_WORKSPACE/charts
  182. git config user.name "Bitnami Bot"
  183. git config user.email "bitnami.bot@broadcom.com"
  184. # In order to avoid doing a full clone (which would fetch the index branch), we
  185. # unshallow the clone only using the main branch. We need to get the tags to
  186. # regenerate the changelog too
  187. - name: Unshallow main branch and get tags
  188. run: |
  189. cd $GITHUB_WORKSPACE/upstream-charts
  190. git fetch origin main --unshallow
  191. git fetch --tags
  192. - name: Install conventional-changelog-cli
  193. run: npm install -g conventional-changelog-cli
  194. - id: generate-changelog
  195. name: Generate changelog
  196. env:
  197. PULL_REQUEST_NUMBER: "${{ github.event.pull_request.number }}"
  198. PULL_REQUEST_URL: "${{ github.server_url }}/${{ github.repository }}/pull/${{ github.event.number }}"
  199. GITHUB_TOKEN: "${{ github.token }}"
  200. CHART: ${{ needs.get-chart.outputs.chart }}
  201. run: |
  202. cd "${GITHUB_WORKSPACE}/upstream-charts" || exit 1
  203. # Get PR title using the API to avoid malicious string substitutions
  204. pr_title="$(gh api "/repos/${GITHUB_REPOSITORY}/pulls/${PULL_REQUEST_NUMBER}" | jq -r '.title')"
  205. # The generator needs the file to exist
  206. chart_version="$(yq e '.version' "${GITHUB_WORKSPACE}/charts/bitnami/${CHART}/Chart.yaml")"
  207. changelog_file="${GITHUB_WORKSPACE}/charts/bitnami/${CHART}/CHANGELOG.md"
  208. changelog_tmp="${GITHUB_WORKSPACE}/charts/bitnami/${CHART}/CHANGELOG.md.tmp"
  209. touch "$changelog_file"
  210. npx conventional-changelog-cli -i "$changelog_file" -s -t "${CHART}/" -r 0 --commit-path "bitnami/${CHART}"
  211. # The tool uses short sha to generate commit links. Sometimes, Github does not offer links with the short sha, so we change all commit links to use the full sha instead
  212. for short_sha in $(grep -Eo "/commit/[a-z0-9]+" "$changelog_file" | awk -F/ '{print $3}'); do
  213. long_sha="$(git rev-list @ | grep "^$short_sha" | head -n 1)";
  214. sed -i "s%/commit/$short_sha%/commit/$long_sha%g" "$changelog_file";
  215. done
  216. cd "${GITHUB_WORKSPACE}/charts" || exit 1
  217. # Remove unreleased section (includes all intermediate commits in the branch) and create future entry based on PR title
  218. # The unreleased section looks like this "## (YYYY-MM-DD)" whereas a released section looks like this "## 0.0.1 (YYYY-MM-DD)"
  219. # So we only need to find a released section to start printing in the awk script below
  220. awk '/^##[^(]*[0-9]/ {flag=1} flag {print}' "$changelog_file" > "$changelog_tmp"
  221. # Remove extra newlines so the changelog file passes the markdown linter
  222. sed -i -E -e '/^$/d' "$changelog_tmp" && sed -i -E -e 's/(##.*)/\n\1\n/g' "$changelog_tmp"
  223. # Include h1 heading and add entry for the current version. There is no tag for the current version (this will be created once merged), so we need to manually add it.
  224. # We know the final squashed commit title, which will be the PR title. We cannot add a link to the commit in the main branch because it has not been
  225. # merged yet (this will be corrected once a new version regenerates the changelog). Instead, we add the PR url which contains the exact same information.
  226. echo -e -n "# Changelog\n\n## $chart_version ($(date +'%Y-%m-%d'))\n\n* ${pr_title} ([#${PULL_REQUEST_NUMBER}](${PULL_REQUEST_URL}))\n" > "$changelog_file"
  227. cat "$changelog_tmp" >> "$changelog_file"
  228. rm "$changelog_tmp"
  229. # Commit all changes, if any
  230. if git status -s | grep "bitnami/${CHART}/CHANGELOG.md"; then
  231. git add "bitnami/${CHART}/CHANGELOG.md"
  232. git commit -m "Update CHANGELOG.md" --signoff
  233. fi
  234. - name: Install readme-generator-for-helm
  235. if: needs.get-chart.outputs.values-updated == 'true'
  236. run: npm install -g @bitnami/readme-generator-for-helm
  237. - id: update-readme
  238. name: 'Update README'
  239. if: needs.get-chart.outputs.values-updated == 'true'
  240. env:
  241. CHART: ${{ needs.get-chart.outputs.chart }}
  242. run: |
  243. exit_code=0
  244. cd "${GITHUB_WORKSPACE}/charts" || exit 1
  245. echo "Validating README.md for bitnami/${CHART}"
  246. # Validating *.registry parameters
  247. while read -r line; do
  248. echo "$line" | grep --quiet "\[default: \(REGISTRY_NAME\|\"\"\)\]" || exit_code=$?
  249. done < <(grep "@param\s\+[A-Za-z\.]\+\.registry\s\+" "bitnami/${CHART}/values.yaml")
  250. if [[ $exit_code -ne 0 ]]; then
  251. echo "error=Please ensure all *.registry params include the [default: REGISTRY_NAME] modifier in the chart bitnami/${CHART}/values.yaml file"
  252. exit "$exit_code"
  253. fi
  254. # Validating *.repository parameters
  255. while read -r line; do
  256. param=$(echo "$line" | awk '{print $3}')
  257. # Checking if it's a image's registry-related param
  258. registry_param="${param//.repository/.registry}"
  259. grep --quiet "@param\s\+${registry_param}" "bitnami/${CHART}/values.yaml" && ( echo "$line" | grep --quiet "\[default: \(REPOSITORY_NAME/.*\|\"\"\)\]" || exit_code=$? )
  260. done < <(grep "@param\s\+[A-Za-z\.]\+\.repository\s\+" "bitnami/${CHART}/values.yaml")
  261. if [[ $exit_code -ne 0 ]]; then
  262. echo "error=Please ensure all *.repository params include the [default: REPOSITORY_NAME] modifier the in the chart bitnami/${CHART}/values.yaml file"
  263. exit "$exit_code"
  264. fi
  265. # Validating *.tag parameters
  266. grep -v --quiet "@param\s\+[A-Za-z\.]\+\.tag\s\+" "bitnami/${CHART}/values.yaml" || exit_code=$?
  267. if [[ $exit_code -ne 0 ]]; then
  268. echo "error=Please ensure all *.tag params are skipped (@skip) in the bitnami/${CHART}/values.yaml file"
  269. exit "$exit_code"
  270. fi
  271. echo "Updating README.md for bitnami/${CHART}"
  272. readme-generator --values "bitnami/${CHART}/values.yaml" --readme "bitnami/${CHART}/README.md" --schema "/tmp/schema.json"
  273. # Commit all changes, if any
  274. if git status -s | grep "bitnami/${CHART}"; then
  275. git add "bitnami/${CHART}"
  276. git commit -m "Update README.md with readme-generator-for-helm" --signoff
  277. fi
  278. - id: update-crds
  279. name: 'Update CRDs'
  280. # To avoid malicious executions, only PRs performed by the bitnami-bot will perform the CRDs update
  281. if: github.event.pull_request.user.login == 'bitnami-bot'
  282. env:
  283. CHART: ${{ needs.get-chart.outputs.chart }}
  284. run: |
  285. cd "${GITHUB_WORKSPACE}/charts" || exit 1
  286. # Updating CRDs stored at 'bitnami/$CHART/crds', 'bitnami/$CHART/templates/crds', and "bitnami/${CHART}/charts/${CHART}-crds/crds"
  287. mapfile -t crd_files < <(find "bitnami/${CHART}/crds" "bitnami/${CHART}/templates/crds" "bitnami/${CHART}/charts/${CHART}-crds/crds" -name "*.yaml" -o -name "*.yml" 2>/dev/null || true)
  288. for file in "${crd_files[@]}"; do
  289. # Automatically update CRDs that use the '# Source' header
  290. source_url_tpl="$(head -n 1 "$file" | grep -E "^# ?Source: ?" | sed -E 's|^# ?Source: ?||' || true)"
  291. if [[ -n "$source_url_tpl" ]]; then
  292. # Validate the second line of the CRD file includes the version of the CRD
  293. crd_version="$(head -n 2 $file | tail -n 1 | grep -E "^# ?Version: ?" | sed -E 's|^# ?Version: ?||' || true)"
  294. if [[ -z "$crd_version" ]]; then
  295. echo "error=CRD file '${file}' does not include the '#Version: <version> header'"
  296. exit 1
  297. fi
  298. # Additional headers may be used for extra features
  299. # Conditional - Adds a conditional {{if}}/{{end}} to the downloaded upstream CRD
  300. # VersionOf - Name of a subcomponent, its version will be used for CRD tracking instead of the main component version
  301. # UseKustomize - If set to true, uses Kustomize to render the CRDs
  302. # RequiresFilter - If set to true, uses yq to filter resources having 'kind: CustomResourceDefinition', useful when using 'install.yaml' file as upstream source
  303. continue=true
  304. line_n=2
  305. extra_headers=""
  306. CONDITIONAL=""
  307. SUBCOMPONENT=""
  308. USE_KUSTOMIZE=""
  309. REQUIRES_FILTER=""
  310. while [ "$continue" = true ]; do
  311. line_n=$((line_n+1))
  312. line="$(head -n $line_n $file | tail -n 1)"
  313. if [[ $line =~ ^#\ ?[a-zA-Z]+:\ ? ]]; then
  314. if [[ $line =~ ^#\ ?Conditional:\ ? ]]; then
  315. CONDITIONAL="$(echo $line | sed -E 's|^# ?Conditional: ?||')"
  316. CONDITIONAL="{{- if ${CONDITIONAL} }}\n"
  317. elif [[ $line =~ ^#\ ?VersionOf:\ ? ]]; then
  318. SUBCOMPONENT="$(echo $line | sed -E 's|^# ?VersionOf: ?||')"
  319. elif [[ $line =~ ^#\ ?UseKustomize:\ ? ]]; then
  320. USE_KUSTOMIZE="$(echo $line | sed -E 's|^# ?UseKustomize: ?||' || true)"
  321. elif [[ $line =~ ^#\ ?RequiresFilter:\ ? ]]; then
  322. REQUIRES_FILTER="$(echo $line | sed -E 's|^# ?RequiresFilter: ?||' || true)"
  323. else
  324. echo "error=Header ${line} not recognized'"
  325. exit 1
  326. fi
  327. extra_headers="${extra_headers}${line}\n"
  328. else
  329. continue=false
  330. fi
  331. done
  332. # Obtain the version of the subcomponent if provided, otherwise use the main component version
  333. if [[ -n "$SUBCOMPONENT" ]]; then
  334. APP_VERSION="$(cat bitnami/${CHART}/Chart.yaml | grep -E "image: \S+${SUBCOMPONENT}:" | sed -E "s|.*${SUBCOMPONENT}:([0-9\.]+)-.*|\1|")"
  335. else
  336. APP_VERSION="$(yq e '.appVersion' bitnami/${CHART}/Chart.yaml)"
  337. fi
  338. # Replace version placeholder, if present
  339. source_url=$(echo "$source_url_tpl" | sed "s/{version}/${APP_VERSION}/")
  340. # If the application version is newer, automatically update the CRD file
  341. if [[ "$APP_VERSION" != "$crd_version" ]]; then
  342. if [[ "$USE_KUSTOMIZE" = "true" ]]; then
  343. kubectl kustomize "$source_url" > $file
  344. else
  345. curl -Lks --fail -o $file "$source_url"
  346. fi
  347. if [[ "$REQUIRES_FILTER" = "true" ]]; then
  348. yq -i e '. | select(.kind == "CustomResourceDefinition") | ... head_comment=""' $file
  349. fi
  350. sed -i "1s|^|# Source: ${source_url_tpl}\n# Version: ${APP_VERSION}\n${extra_headers}${CONDITIONAL}|" $file
  351. if [[ -n "$CONDITIONAL" ]]; then
  352. echo -E "{{- end }}" >> $file
  353. fi
  354. echo "info=CRD file '${file}' automatically updated using source '$source_url'"
  355. fi
  356. else
  357. echo "info=CRD file '$file' does not contain the '#Source' header. Skipping..."
  358. fi
  359. done
  360. # Commit all changes, if any
  361. if git status -s | grep "bitnami/${CHART}"; then
  362. git add "bitnami/${CHART}"
  363. git commit -m "Update CRDs automatically" --signoff
  364. fi
  365. - id: update-prometheus-rules
  366. name: Update Prometheus rules on kube-prometheus based on upstream
  367. # To avoid malicious executions, only PRs performed by the bitnami-bot will perform the CRDs update
  368. if: github.event.pull_request.user.login == 'bitnami-bot' && needs.get-chart.outputs.chart == 'kube-prometheus'
  369. run: |
  370. cd "${GITHUB_WORKSPACE}/charts" || exit 1
  371. # This function returns the chart parameter name based on the rule name
  372. rule_parameter_from_rule_name() {
  373. local -r rule_name="${1:-missing rule_name}"
  374. kebab_to_camel() {
  375. IFS='-' read -r -a parts <<< "$1"
  376. for i in "${!parts[@]}"; do
  377. if [[ $i -eq 0 ]]; then
  378. camelCase="${parts[i]}"
  379. else
  380. camelCase+=$(tr '[:lower:]' '[:upper:]' <<< ${parts[i]:0:1})${parts[i]:1}
  381. fi
  382. done
  383. echo "$camelCase"
  384. }
  385. case "$rule_name" in
  386. "config-reloaders" | "etcd" | "kube-state-metrics" | "kubernetes-apps" | "kubernetes-resources" | "kubernetes-storage" | "kube-apiserver-slos" | "prometheus" | "prometheus-operator") kebab_to_camel "$rule_name" ;;
  387. "alertmanager.rules" | "general.rules" | "kubelet.rules" | "node.rules" | "kube-apiserver-availability.rules" | "kube-apiserver-burnrate.rules" | "kube-apiserver-histogram.rules" | "kube-prometheus-general.rules" | "kube-prometheus-node-recording.rules") kebab_to_camel "${rule_name%.rules}" ;;
  388. "k8s.rules.container-cpu-usage-seconds-total" | "k8s.rules.container-memory-cache" | "k8s.rules.container-memory-rss" | "k8s.rules.container-memory-swap" | "k8s.rules.container-memory-working-set-bytes" | "k8s.rules.container-resource" | "k8s.rules.pod-owner") kebab_to_camel "${rule_name//.rules./-}" ;;
  389. "kubernetes-system-apiserver" | "kubernetes-system-kubelet" | "kubernetes-system") echo "kubernetesSystem" ;;
  390. "kube-scheduler.rules") echo "kubeSchedulerAlerting" ;;
  391. "kubernetes-system-controller-manager") echo "kubeControllerManager" ;;
  392. "kubernetes-system-kube-proxy") echo "kubeProxy" ;;
  393. "kubernetes-system-scheduler") echo "kubeSchedulerRecording" ;;
  394. "node-exporter") echo "nodeExporterAlerting" ;;
  395. "node-exporter.rules") echo "nodeExporterRecording" ;;
  396. "node-network") echo "network" ;;
  397. *) echo "" ;;
  398. esac
  399. }
  400. mkdir -p "bitnami/kube-prometheus/templates/prometheus/rules"
  401. cd "bitnami/kube-prometheus/templates/prometheus/rules"
  402. helm repo add prometheus-community https://prometheus-community.github.io/helm-charts 2>&1 >/dev/null 2>&1
  403. helm repo update >/dev/null 2>&1
  404. helm template prometheus-community/kube-prometheus-stack \
  405. --set fullnameOverride=foo \
  406. --set defaultRules.create=true \
  407. --show-only templates/prometheus/rules-1.14/* \
  408. | awk 'NF' | yq --no-doc -s '.metadata.name | sub("^foo-"; "") + ".yaml"'
  409. for m in *.yaml; do
  410. rule_name="${m%.yaml}"
  411. rule_parameter="$(rule_parameter_from_rule_name "$rule_name")"
  412. # We're just interested in the .spec given we build apiVersion, kind and metadata
  413. # based in Bitnami standards
  414. spec="$(yq '{"spec": .spec}' "$m")"
  415. # We need to escape curly braces to avoid issues with Go templates
  416. spec=$(echo "$spec" | sed -E 's/\{\{/__OPEN__/g' | sed -E 's/\}\}/__CLOSE__/g' | sed -E 's/__OPEN__/{{\`{{\`}}/g' | sed -E 's/__CLOSE__/{{\`}}\`}}/g')
  417. cat > "$m" << EOF
  418. {{- /*
  419. Copyright Broadcom, Inc. All Rights Reserved.
  420. SPDX-License-Identifier: APACHE-2.0
  421. */}}
  422. {{- if and .Values.prometheus.enabled .Values.prometheus.defaultRules.create .Values.prometheus.defaultRules.rules.$rule_parameter }}
  423. apiVersion: monitoring.coreos.com/v1
  424. kind: PrometheusRule
  425. metadata:
  426. name: {{ printf "%s-$rule_name" (include "kube-prometheus.prometheus.fullname" .) }}
  427. namespace: {{ include "common.names.namespace" . | quote }}
  428. labels: {{ include "kube-prometheus.prometheus.labels" . | nindent 4 }}
  429. {{- if .Values.commonAnnotations }}
  430. annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" . ) | nindent 4 }}
  431. {{- end }}
  432. $spec
  433. {{- end }}
  434. EOF
  435. done
  436. cd "${GITHUB_WORKSPACE}/charts"
  437. if git status -s | grep "bitnami/kube-prometheus/templates/prometheus/rules"; then
  438. git add "bitnami/kube-prometheus/templates/prometheus/rules"
  439. git commit -m "Update Prometheus rules" --signoff
  440. fi
  441. - id: update-pr
  442. name: Push changes
  443. run: |
  444. cd $GITHUB_WORKSPACE/charts
  445. # Push all the new commits, if any
  446. if [[ $(git cherry -v) ]]; then
  447. git push
  448. echo "result=ok" >> $GITHUB_OUTPUT
  449. else
  450. echo "result=skip" >> $GITHUB_OUTPUT
  451. fi
  452. chart-score:
  453. runs-on: ubuntu-latest
  454. needs: [get-chart]
  455. name: Get chart latest Kubescape score
  456. permissions:
  457. contents: read
  458. outputs:
  459. threshold: ${{ steps.set-output.outputs.threshold }}
  460. if: needs.get-chart.outputs.result == 'ok'
  461. steps:
  462. - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
  463. name: Checkout 'main' branch
  464. with:
  465. path: charts-main
  466. repository: bitnami/charts
  467. fetch-depth: 1
  468. - name: Install helm
  469. run: |
  470. HELM_TARBALL="helm-v3.8.1-linux-amd64.tar.gz"
  471. curl -SsLfO "https://get.helm.sh/${HELM_TARBALL}" && sudo tar xf "$HELM_TARBALL" --strip-components 1 -C /usr/local/bin
  472. - name: Run helm-dep-build
  473. env:
  474. CHART: ${{ needs.get-chart.outputs.chart }}
  475. run: |
  476. if [ -d "charts-main/bitnami/${CHART}" ]; then
  477. helm dep build charts-main/bitnami/${CHART}
  478. if [ -d "charts-main/bitnami/${CHART}/charts" ]; then
  479. cd charts-main/bitnami/${CHART}/charts
  480. for filename in *.tgz; do
  481. tar -xf "$filename"
  482. rm -f "$filename"
  483. done
  484. fi
  485. fi
  486. - id: get-chart-score
  487. uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185
  488. name: Get main branch kubescape score
  489. # Skip step when user is bitnami-bot or 'skip-score' label is used
  490. if: |
  491. !(
  492. github.event.pull_request.user.login == 'bitnami-bot' ||
  493. contains(github.event.pull_request.labels.*.name, 'skip-score') ||
  494. (github.event.action == 'labeled' && github.event.label.name == 'skip-score')
  495. )
  496. env:
  497. CHART: ${{ needs.get-chart.outputs.chart }}
  498. with:
  499. image: bitnami/kubescape:3.0.37
  500. options: -v /home/runner/work/charts/charts/charts-main:/charts -v /tmp:/out -e CHART
  501. run: |
  502. if [ -d "/charts/bitnami/${CHART}" ]; then
  503. kubescape scan framework MITRE,NSA,SOC2,cis-v1.10.0 /charts/bitnami/${CHART} --format json -o /out/report.json
  504. # Truncate score to 2 decimals
  505. printf "%s.%.2s" $(echo "$(jq .summaryDetails.complianceScore /out/report.json)" | tr '.' ' ') | sed 's/\.$//' > /out/score
  506. else
  507. echo "Chart not found at /charts/bitnami/${CHART}. It will be assumed that the upstream chart does not exist."
  508. fi
  509. - id: set-output
  510. name: Set threshold score
  511. run: |
  512. if [[ -f "/tmp/score" ]]; then
  513. score="$(cat /tmp/score)"
  514. else
  515. echo "Skipping Kubescape score check."
  516. score="0"
  517. fi
  518. echo "Using threshold score: ${score}"
  519. echo "threshold=${score}" >> $GITHUB_OUTPUT
  520. vib-verify:
  521. runs-on: ubuntu-latest
  522. needs: [get-chart, update-pr, chart-score]
  523. permissions:
  524. contents: read
  525. # Given performance issues of the action feature on GH's side, we need to be very restrictive in the job's triggers:
  526. # -> The 'Get modified charts' job suceededs AND
  527. # -> The 'Update PR' job did not push any new changes AND
  528. # ( ---> The pipeline was triggered due to a label addition and said label was the 'verify' one OR
  529. # ---> the PR already contains the 'verify' label )
  530. if: |
  531. needs.get-chart.outputs.result == 'ok' &&
  532. needs.update-pr.outputs.result == 'skip' &&
  533. (
  534. contains(github.event.pull_request.labels.*.name, 'verify') || (github.event.action == 'labeled' && github.event.label.name == 'verify')
  535. )
  536. name: VIB Verify
  537. steps:
  538. - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
  539. name: Checkout Repository
  540. with:
  541. ref: ${{ github.event.pull_request.head.ref }}
  542. repository: ${{ github.event.pull_request.head.repo.full_name }}
  543. - id: log-chart-info
  544. name: Get chart version and app version
  545. env:
  546. CHART: ${{ needs.get-chart.outputs.chart }}
  547. run: |
  548. # Log chart info
  549. chart_version="$(yq e '.version' bitnami/${CHART}/Chart.yaml)"
  550. app_version="$(yq e '.appVersion' bitnami/${CHART}/Chart.yaml)"
  551. echo "Chart: ${CHART} ChartVersion: ${chart_version} AppVersion: ${app_version}"
  552. - id: get-asset-vib-config
  553. name: Get asset-specific configuration for VIB action
  554. run: |
  555. config_file=".vib/${{ needs.get-chart.outputs.chart }}/vib-action.config"
  556. # Supported configuration customizations and default values
  557. verification_mode="PARALLEL"
  558. if [[ -f $config_file ]]; then
  559. verification_mode="$(cat $config_file | grep 'verification-mode' | cut -d'=' -f2)"
  560. fi
  561. runtime_parameters_file=""
  562. if [[ -f ".vib/${{ needs.get-chart.outputs.chart }}/runtime-parameters.yaml" ]]; then
  563. # The path is relative to the .vib folder
  564. runtime_parameters_file="${{ needs.get-chart.outputs.chart }}/runtime-parameters.yaml"
  565. fi
  566. echo "verification_mode=${verification_mode}" >> $GITHUB_OUTPUT
  567. echo "runtime_parameters_file=${runtime_parameters_file}" >> $GITHUB_OUTPUT
  568. - uses: vmware-labs/vmware-image-builder-action@v0
  569. name: Verify ${{ needs.get-chart.outputs.chart }}
  570. with:
  571. pipeline: ${{ needs.get-chart.outputs.chart }}/vib-verify.json
  572. verification-mode: ${{ steps.get-asset-vib-config.outputs.verification_mode }}
  573. runtime-parameters-file: ${{ steps.get-asset-vib-config.outputs.runtime_parameters_file }}
  574. env:
  575. CSP_API_URL: https://console.tanzu.broadcom.com
  576. CSP_API_TOKEN: ${{ secrets.CSP_API_TOKEN }}
  577. VIB_PUBLIC_URL: ${{ vars.VIB_PUBLIC_URL }}
  578. # Target-Platform used by default
  579. VIB_ENV_TARGET_PLATFORM: ${{ secrets.VIB_ENV_TARGET_PLATFORM }}
  580. # Alternative Target-Platform to be used in case of incompatibilities
  581. VIB_ENV_ALTERNATIVE_TARGET_PLATFORM: ${{ secrets.VIB_ENV_ALTERNATIVE_TARGET_PLATFORM }}
  582. # Set kubescape score threshold
  583. VIB_ENV_KUBESCAPE_SCORE_THRESHOLD: ${{ needs.chart-score.outputs.threshold }}
  584. # Set docker credentials
  585. VIB_ENV_CHARTS_REGISTRY: oci://registry-1.docker.io/bitnamicharts
  586. VIB_ENV_CHARTS_REGISTRY_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
  587. VIB_ENV_CHARTS_REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
  588. auto-pr-review:
  589. runs-on: ubuntu-latest
  590. needs: vib-verify
  591. name: Reviewal for automated PRs
  592. permissions:
  593. pull-requests: write
  594. # Job to be run only when the triage for automated PRs did as well,
  595. # not taking into account whether 'VIB Verify' succeeded
  596. if: |
  597. always() &&
  598. contains(github.event.pull_request.labels.*.name, 'auto-merge') &&
  599. github.event.pull_request.user.login == 'bitnami-bot'
  600. steps:
  601. # Approve the CI's PR if the 'VIB Verify' job succeeded
  602. # Approved by the 'github-actions' user; a PR can't be approved by its author
  603. - name: PR approval
  604. if: ${{ needs.vib-verify.result == 'success' }}
  605. uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
  606. with:
  607. result-encoding: string
  608. retries: 3
  609. script: |
  610. github.rest.pulls.createReview({
  611. owner: context.repo.owner,
  612. repo: context.repo.repo,
  613. pull_number: context.issue.number,
  614. event: 'APPROVE',
  615. });
  616. - name: Merge
  617. id: merge
  618. if: ${{ needs.vib-verify.result == 'success' }}
  619. uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
  620. with:
  621. result-encoding: string
  622. retries: 3
  623. github-token: ${{ secrets.BITNAMI_BOT_TOKEN }}
  624. script: |
  625. github.rest.pulls.merge({
  626. pull_number: context.issue.number,
  627. owner: context.repo.owner,
  628. repo: context.repo.repo,
  629. merge_method: 'squash'
  630. })
  631. # If the CI did not succeed ('VIB Verify' failed or skipped),
  632. # post a comment on the PR and assign a maintainer agent to review it
  633. - name: Manual review required
  634. if: ${{ always() && github.run_attempt >= vars.MAX_RUNS_ATTEMPTS && needs.vib-verify.result != 'skipped' && (needs.vib-verify.result != 'success' || steps.merge.outcome != 'success' ) }}
  635. uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
  636. env:
  637. BODY: |
  638. There has been an error during the automated release process. Manual revision is now required.
  639. Please check the related [action_run#${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for more information.
  640. with:
  641. retries: 3
  642. # Necessary to trigger support workflows
  643. github-token: ${{ secrets.BITNAMI_BOT_TOKEN }}
  644. script: |
  645. const {BODY} = process.env
  646. github.rest.issues.createComment({
  647. issue_number: context.issue.number,
  648. owner: context.repo.owner,
  649. repo: context.repo.repo,
  650. body: `${BODY}`
  651. })