Skip to content

Commit 4fc2e55

Browse files
authored
chore(ci): manage Sonar projects from Github actions (testcontainers#3039)
* chore(ci): manage Sonar projects from Github actions * fix: eof
1 parent 2df38dd commit 4fc2e55

File tree

6 files changed

+338
-1
lines changed

6 files changed

+338
-1
lines changed

.github/scripts/sonar-manager.sh

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
#!/bin/bash
2+
3+
# Prevent command echoing and exit on any error
4+
set +x -e
5+
6+
# Clear sensitive environment variables on exit
7+
trap 'unset SONAR_TOKEN' EXIT
8+
9+
# sonar-manager.sh
10+
# ---------------
11+
# Manages SonarCloud projects for the testcontainers-go repository
12+
#
13+
# Usage:
14+
# ./.github/scripts/sonar-manager.sh -h # Show help message
15+
# ./.github/scripts/sonar-manager.sh -a create -p <project> # Create a single project
16+
# ./.github/scripts/sonar-manager.sh -a delete -p <project> # Delete a single project
17+
# ./.github/scripts/sonar-manager.sh -a createAll # Create all projects
18+
# ./.github/scripts/sonar-manager.sh -a deleteAll # Delete all projects
19+
#
20+
# Project name format:
21+
# - For modules: modules_<module-name>
22+
# - For examples: examples_<example-name>
23+
# - For modulegen: modulegen
24+
#
25+
# Examples:
26+
# ./.github/scripts/sonar-manager.sh -a create -p modulegen
27+
# ./.github/scripts/sonar-manager.sh -a create -p modules_mysql
28+
# ./.github/scripts/sonar-manager.sh -a create -p examples_redis
29+
# ./.github/scripts/sonar-manager.sh -a delete -p modules_mysql
30+
# ./.github/scripts/sonar-manager.sh -a createAll
31+
# ./.github/scripts/sonar-manager.sh -a deleteAll
32+
#
33+
# Environment variables:
34+
# SONAR_TOKEN - Required. The SonarCloud authentication token.
35+
36+
SONAR_TOKEN=${SONAR_TOKEN:-}
37+
ORGANIZATION="testcontainers"
38+
39+
# list all directories in the modules and examples directories
40+
EXAMPLES=$(ls -1d examples/*/ | cut -d'/' -f2)
41+
MODULES=$(ls -1d modules/*/ | cut -d'/' -f2)
42+
43+
# Delete all the projects in SonarCloud, starting with the modulegen module.
44+
# Modules and examples need a prefix with the module type (module or example).
45+
deleteAll() {
46+
delete "modulegen"
47+
48+
for MODULE in $MODULES; do
49+
delete "modules_$MODULE"
50+
done
51+
52+
for EXAMPLE in $EXAMPLES; do
53+
delete "examples_$EXAMPLE"
54+
done
55+
}
56+
57+
# Helper function to print success message
58+
print_success() {
59+
local action=$1
60+
local module=$2
61+
echo -e "\033[32mOK\033[0m: $action $([ ! -z "$module" ] && echo "for $module")"
62+
}
63+
64+
# Helper function to print failure message
65+
print_failure() {
66+
local action=$1
67+
local module=$2
68+
local status=$3
69+
echo -e "\033[31mFAIL\033[0m: Failed to $action $([ ! -z "$module" ] && echo "for $module") (HTTP status: $status)"
70+
exit 1
71+
}
72+
73+
# Helper function to handle curl responses
74+
handle_curl_response() {
75+
local response_code=$1
76+
local action=$2
77+
local module=$3
78+
local allow_404=${4:-false} # Optional parameter to allow 404 responses
79+
80+
if [ $response_code -eq 200 ] || [ $response_code -eq 204 ] || ([ "$allow_404" = "true" ] && [ $response_code -eq 404 ]); then
81+
return
82+
fi
83+
print_failure "$action" "$module" "$response_code"
84+
}
85+
86+
# Delete a project in SonarCloud
87+
delete() {
88+
MODULE=$1
89+
NAME=$(echo $MODULE | tr '_' '-')
90+
response=$(curl -s -w "%{http_code}" -X POST https://${SONAR_TOKEN}@sonarcloud.io/api/projects/delete \
91+
-d "name=testcontainers-go-${NAME}&project=testcontainers_testcontainers-go_${MODULE}&organization=testcontainers" 2>/dev/null)
92+
status_code=${response: -3}
93+
handle_curl_response $status_code "delete" "$MODULE"
94+
print_success "Deleted project" "$MODULE"
95+
}
96+
97+
# Create all the projects in SonarCloud, starting with the modulegen module.
98+
# Modules and examples need a prefix with the module type (module or example).
99+
createAll() {
100+
create "modulegen"
101+
102+
for MODULE in $MODULES; do
103+
create "modules_$MODULE"
104+
done
105+
106+
for EXAMPLE in $EXAMPLES; do
107+
create "examples_$EXAMPLE"
108+
done
109+
}
110+
111+
# create a new project in SonarCloud
112+
create() {
113+
MODULE=$1
114+
NAME=$(echo $MODULE | tr '_' '-')
115+
response=$(curl -s -w "%{http_code}" -X POST https://${SONAR_TOKEN}@sonarcloud.io/api/projects/create \
116+
-d "name=testcontainers-go-${NAME}&project=testcontainers_testcontainers-go_${MODULE}&organization=testcontainers" 2>/dev/null)
117+
status_code=${response: -3}
118+
handle_curl_response $status_code "create" "$MODULE"
119+
print_success "Created project" "$MODULE"
120+
}
121+
122+
# Rename all the main branches to the new name, starting with the modulegen module.
123+
# Modules and examples need a prefix with the module type (module or example).
124+
renameAllMainBranches() {
125+
rename_main_branch "modulegen"
126+
127+
for MODULE in $MODULES; do
128+
rename_main_branch "modules_$MODULE"
129+
done
130+
131+
for EXAMPLE in $EXAMPLES; do
132+
rename_main_branch "examples_$EXAMPLE"
133+
done
134+
}
135+
136+
# rename the main branch to the new name: they originally have the name "master"
137+
rename_main_branch() {
138+
MODULE=$1
139+
NAME=$(echo $MODULE | tr '_' '-')
140+
141+
# Delete main branch (404 is acceptable here)
142+
response=$(curl -s -w "%{http_code}" -X POST https://${SONAR_TOKEN}@sonarcloud.io/api/project_branches/delete \
143+
-d "branch=main&project=testcontainers_testcontainers-go_${MODULE}&organization=testcontainers" 2>/dev/null)
144+
status_code=${response: -3}
145+
handle_curl_response $status_code "delete main branch" "$MODULE" true
146+
147+
# Rename master to main
148+
response=$(curl -s -w "%{http_code}" -X POST https://${SONAR_TOKEN}@sonarcloud.io/api/project_branches/rename \
149+
-d "name=main&project=testcontainers_testcontainers-go_${MODULE}&organization=testcontainers" 2>/dev/null)
150+
status_code=${response: -3}
151+
handle_curl_response $status_code "rename branch to main" "$MODULE"
152+
print_success "Renamed branch to main" "$MODULE"
153+
}
154+
155+
show_help() {
156+
echo "Usage: $0 [-h] [-a ACTION] [-p PROJECT_NAME]"
157+
echo
158+
echo "Options:"
159+
echo " -h Show this help message"
160+
echo " -a ACTION Action to perform (create, delete, createAll, deleteAll)"
161+
echo " -p PROJECT Project name to operate on (required for create/delete)"
162+
echo
163+
echo "Actions:"
164+
echo " create Creates a new SonarCloud project and sets up the main branch"
165+
echo " delete Deletes an existing SonarCloud project"
166+
echo " createAll Creates all projects and sets up their main branches"
167+
echo " deleteAll Deletes all existing projects"
168+
echo
169+
echo "Examples:"
170+
echo " $0 -a create -p modules_mymodule # Create a new project"
171+
echo " $0 -a delete -p modules_mymodule # Delete an existing project"
172+
echo " $0 -a createAll # Create all projects"
173+
echo " $0 -a deleteAll # Delete all projects"
174+
}
175+
176+
validate_action() {
177+
local action=$1
178+
case $action in
179+
create|delete|createAll|deleteAll)
180+
return 0
181+
;;
182+
*)
183+
echo "Error: Invalid action '$action'. Valid actions are: create, delete, createAll, deleteAll"
184+
return 1
185+
;;
186+
esac
187+
}
188+
189+
validate_project() {
190+
local action=$1
191+
local project=$2
192+
193+
# Skip project validation for "All" actions
194+
if [[ "$action" == *"All" ]]; then
195+
return 0
196+
fi
197+
198+
if [ -z "$project" ]; then
199+
echo "Error: Project name is required for action '$action'. Use -p to specify a project name"
200+
return 1
201+
fi
202+
return 0
203+
}
204+
205+
main() {
206+
local project_name=""
207+
local action=""
208+
209+
# Handle flags
210+
while getopts "ha:p:" opt; do
211+
case $opt in
212+
h)
213+
show_help
214+
exit 0
215+
;;
216+
a)
217+
action="$OPTARG"
218+
if ! validate_action "$action"; then
219+
exit 1
220+
fi
221+
;;
222+
p)
223+
project_name="$OPTARG"
224+
;;
225+
\?)
226+
echo "Invalid option: -$OPTARG" >&2
227+
show_help
228+
exit 1
229+
;;
230+
esac
231+
done
232+
233+
# Validate SONAR_TOKEN is set (except for help)
234+
if [ -z "${SONAR_TOKEN}" ]; then
235+
echo "Error: SONAR_TOKEN environment variable is not set"
236+
exit 1
237+
fi
238+
239+
# Validate project name is provided for non-All actions
240+
if ! validate_project "$action" "$project_name"; then
241+
exit 1
242+
fi
243+
244+
case $action in
245+
create)
246+
create "$project_name"
247+
rename_main_branch "$project_name"
248+
;;
249+
delete)
250+
delete "$project_name"
251+
;;
252+
createAll)
253+
createAll
254+
renameAllMainBranches
255+
;;
256+
deleteAll)
257+
deleteAll
258+
;;
259+
*)
260+
echo "No valid action specified. Use -h for help"
261+
;;
262+
esac
263+
}
264+
265+
main "$@"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Sonar Bulk Operations
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
operation:
7+
description: 'Operation to perform'
8+
required: true
9+
type: choice
10+
options:
11+
- createAll
12+
- deleteAll
13+
14+
jobs:
15+
bulk-operation:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Execute Bulk Operation
21+
env:
22+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
23+
run: |
24+
chmod +x .github/scripts/.sonar-manager.sh
25+
.github/scripts/.sonar-manager.sh -a "${{ inputs.operation }}"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Create Sonar Project
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
project_name:
7+
description: 'Name of the project (without type prefix)'
8+
required: true
9+
type: string
10+
11+
jobs:
12+
create-project:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Create Sonar Project for module
18+
env:
19+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
20+
run: |
21+
chmod +x .github/scripts/sonar-manager.sh
22+
.github/scripts/sonar-manager.sh -a "create" -p "modules_${{ inputs.project_name }}"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Delete Sonar Project
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
project_name:
7+
description: 'Name of the project (without type prefix)'
8+
required: true
9+
type: string
10+
11+
jobs:
12+
delete-project:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Delete Sonar Project for module
18+
env:
19+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
20+
run: |
21+
chmod +x .github/scripts/.sonar-manager.sh
22+
.github/scripts/.sonar-manager.sh -a "delete" -p "modules_${{ inputs.project_name }}"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ tcvenv
1919

2020
# VS Code settings
2121
.vscode
22+
23+
# Environment variables
24+
.env

scripts/changed-modules.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ readonly ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
7474
readonly excluded_modules=(".devcontainer" ".vscode" "docs")
7575

7676
# define an array of files that won't be included in the list
77-
readonly excluded_files=("mkdocs.yml" ".github/dependabot.yml")
77+
readonly excluded_files=("mkdocs.yml" ".github/dependabot.yml" ".github/workflows/sonar-*.yml")
7878

7979
# define an array of modules that won't be part of the build
8080
readonly no_build_modules=("modules/k6")

0 commit comments

Comments
 (0)