Skip to content

Commit 3124f12

Browse files
ajaydevtronAjay Kumar
andauthored
feat: Integrate GKE provisioning into Devtron Plugin (#4406)
* add the gke devtron plugin * change the gke plugin migration number * update the docker image of gke plugin * update the sonarqube v1.1.0 plugin script --------- Co-authored-by: Ajay Kumar <[email protected]>
1 parent 229005e commit 3124f12

File tree

3 files changed

+282
-0
lines changed

3 files changed

+282
-0
lines changed

assets/gke-plugin-icon.png

6.8 KB
Loading

scripts/sql/203_gke_plugin.down.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
DELETE FROM plugin_step_variable WHERE plugin_step_id =(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false);
2+
DELETE FROM plugin_step WHERE plugin_id=(SELECT id FROM plugin_metadata WHERE name='GKE Provisioner v1.1.0');
3+
DELETE FROM plugin_stage_mapping WHERE plugin_id =(SELECT id FROM plugin_metadata WHERE name='GKE Provisioner v1.1.0');
4+
DELETE from pipeline_stage_step_variable where pipeline_stage_step_id =(select pipeline_stage_id from pipeline_stage_step where name='GKE Provisioner v1.1.0');
5+
DELETE from pipeline_stage_step where name ='GKE Provisioner v1.1.0';
6+
DELETE from plugin_tag_relation where plugin_id=(SELECT id FROM plugin_metadata WHERE name='GKE Provisioner v1.1.0');
7+
DELETE FROM plugin_metadata WHERE name ='GKE Provisioner v1.1.0';

scripts/sql/203_gke_plugin.up.sql

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
INSERT INTO plugin_metadata (id,name,description,type,icon,deleted,created_on,created_by,updated_on,updated_by)
2+
VALUES (nextval('id_seq_plugin_metadata'),'GKE Provisioner v1.1.0','Provision a Google Kubernetes Engine cluster within a Google Cloud Platform project. The cluster should be configured with an initial firewall setting designed to permit access only to SSH, ports 80 and 8080, and NodePorts','PRESET','https://raw.githubusercontent.com/ajaydevtron/devtron/main/assets/gke-plugin-icon.png',false,'now()',1,'now()',1);
3+
4+
INSERT INTO plugin_tag (id, name, deleted, created_on, created_by, updated_on, updated_by)
5+
SELECT
6+
nextval('id_seq_plugin_tag'),
7+
'Google Kubernetes Engine',
8+
false,
9+
'now()',
10+
1,
11+
'now()',
12+
1
13+
WHERE NOT EXISTS (
14+
SELECT 1
15+
FROM plugin_tag
16+
WHERE name = 'Google Kubernetes Engine'
17+
);
18+
19+
INSERT INTO plugin_tag (id, name, deleted, created_on, created_by, updated_on, updated_by)
20+
SELECT
21+
nextval('id_seq_plugin_tag'),
22+
'GCP',
23+
false,
24+
'now()',
25+
1,
26+
'now()',
27+
1
28+
WHERE NOT EXISTS (
29+
SELECT 1
30+
FROM plugin_tag
31+
WHERE name = 'GCP'
32+
);
33+
34+
INSERT INTO plugin_tag (id, name, deleted, created_on, created_by, updated_on, updated_by)
35+
SELECT
36+
nextval('id_seq_plugin_tag'),
37+
'Kubernetes',
38+
false,
39+
'now()',
40+
1,
41+
'now()',
42+
1
43+
WHERE NOT EXISTS (
44+
SELECT 1
45+
FROM plugin_tag
46+
WHERE name = 'Kubernetes'
47+
);
48+
49+
50+
INSERT INTO "plugin_tag_relation" ("id", "tag_id", "plugin_id", "created_on", "created_by", "updated_on", "updated_by") VALUES (nextval('id_seq_plugin_tag_relation'), (SELECT id FROM plugin_tag WHERE name='Google Kubernetes Engine'), (SELECT id FROM plugin_metadata WHERE name='GKE Provisioner v1.1.0'),'now()', 1, 'now()', 1);
51+
INSERT INTO "plugin_tag_relation" ("id", "tag_id", "plugin_id", "created_on", "created_by", "updated_on", "updated_by") VALUES (nextval('id_seq_plugin_tag_relation'), (SELECT id FROM plugin_tag WHERE name='Kubernetes'), (SELECT id FROM plugin_metadata WHERE name='GKE Provisioner v1.1.0'),'now()', 1, 'now()', 1);
52+
INSERT INTO "plugin_tag_relation" ("id", "tag_id", "plugin_id", "created_on", "created_by", "updated_on", "updated_by") VALUES (nextval('id_seq_plugin_tag_relation'), (SELECT id FROM plugin_tag WHERE name='GCP'), (SELECT id FROM plugin_metadata WHERE name='GKE Provisioner v1.1.0'),'now()', 1, 'now()', 1);
53+
54+
55+
INSERT INTO "plugin_pipeline_script" ("id", "script","type","deleted","created_on", "created_by", "updated_on", "updated_by")
56+
VALUES (
57+
nextval('id_seq_plugin_pipeline_script'),
58+
E'#!/bin/sh
59+
if [ -z $GcpServiceAccountEncodedCredential ]
60+
then
61+
echo -e "\\n****** The GCP service account has not been provided for provisioning a GKE cluster. Please provide the encoded format of the JSON file for the service account. For instructions on creating a service account and assigning the necessary permissions, refer to the following documentation : https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console"
62+
exit 0
63+
fi
64+
mkdir -p /GoogleCloudPlatform
65+
echo $GcpServiceAccountEncodedCredential | base64 -d > /GoogleCloudPlatform/serviceaccount.json
66+
67+
if [ -z $GkeMinNodes ]
68+
then
69+
GkeMinNodes=1
70+
fi
71+
if [ -z $GkeMaxNodes ]
72+
then
73+
GkeMaxNodes=3
74+
fi
75+
if [ -z $GkeRegion ]
76+
then
77+
GkeRegion="us-central1"
78+
fi
79+
if [ -z $GkeMachineType ]
80+
then
81+
GkeMachineType="n1-standard-4"
82+
fi
83+
if [ -z $GkeImageType ]
84+
then
85+
GkeImageType="cos"
86+
fi
87+
if [ -z $GkeClusterVersion ]
88+
then
89+
GkeClusterVersion="latest"
90+
fi
91+
92+
echo -e "\\n********** Provided Parameters to spin up GKE cluster *************"
93+
echo -e "\\n Project Name: $GcpProjectId\\n Identifier: $Identifier \\n Min Nodes : $GkeMinNodes \\n Max Nodes: $GkeMaxNodes \\n Region: $GkeRegion \\n Machine Type: $GkeMachineType \\n Image type: $GkeImageType \\n Cluster Version: $GkeClusterVersion"
94+
UNIQUE_STR=$(head /dev/urandom | tr -dc a-z0-9 | head -c 10 ; echo '''')
95+
export UNIQUE_NAME=$Identifier-$UNIQUE_STR
96+
echo "Unique name is : $UNIQUE_NAME"
97+
98+
GkeProvisionCmd="gcloud container clusters create $UNIQUE_NAME --quiet --enable-autoscaling --scopes=cloud-platform --project=$GcpProjectId --cluster-version=$GkeClusterVersion --min-nodes=$GkeMinNodes --max-nodes=$GkeMaxNodes --region=$GkeRegion --machine-type=$GkeMachineType --image-type=$GkeImageType --num-nodes=1 --network=$UNIQUE_NAME"
99+
if [ ! -z $GkeNodeServiceAccountName ]
100+
then
101+
GkeProvisionCmd="$GkeProvisionCmd --service-account=$GkeNodeServiceAccountName"
102+
fi
103+
104+
echo ''#!/bin/sh'' > /GoogleCloudPlatform/gke_provision.sh
105+
echo ''gcloud auth activate-service-account --key-file=/GoogleCloudPlatform/serviceaccount.json'' >> /GoogleCloudPlatform/gke_provision.sh
106+
echo ''
107+
if [ $? -eq 0 ]; then
108+
echo "Service account authenticated successfully."
109+
else
110+
echo "Service account authentication failed."
111+
exit
112+
fi'' >> /GoogleCloudPlatform/gke_provision.sh
113+
echo "gcloud compute networks create $UNIQUE_NAME --project $GcpProjectId --subnet-mode=auto" >> /GoogleCloudPlatform/gke_provision.sh
114+
echo "echo ''GKE cluster provision will take at least 5 mins, Please wait ....... ''" >> /GoogleCloudPlatform/gke_provision.sh
115+
echo $GkeProvisionCmd >> /GoogleCloudPlatform/gke_provision.sh
116+
echo "gcloud container clusters get-credentials --project=$GcpProjectId --region=$GkeRegion $UNIQUE_NAME" >> /GoogleCloudPlatform/gke_provision.sh
117+
echo "cat ~/.kube/config > /GoogleCloudPlatform/config" >> /GoogleCloudPlatform/gke_provision.sh
118+
echo "INSTANCE_TAG=\\$(gcloud compute instances list --project=$GcpProjectId --filter=metadata.cluster-name=$UNIQUE_NAME --limit=1 --format=get\\(tags.items\\) | tr -d ''\\n'')" >> /GoogleCloudPlatform/gke_provision.sh
119+
echo "gcloud compute firewall-rules create ports-$UNIQUE_NAME --project=$GcpProjectId --network=$UNIQUE_NAME --allow=tcp:22,tcp:80,tcp:8080,tcp:30000-32767,udp:30000-32767 --target-tags=\\$INSTANCE_TAG" >> /GoogleCloudPlatform/gke_provision.sh
120+
echo -e "\\n ********** Final script to provision GKE cluster ********** \\n"
121+
cat /GoogleCloudPlatform/gke_provision.sh
122+
docker run --rm -v "/GoogleCloudPlatform:/GoogleCloudPlatform" quay.io/devtron/gcloud-util:v1.1.0 sh /GoogleCloudPlatform/gke_provision.sh
123+
GKE_KUBECONFIG=$(cat /GoogleCloudPlatform/config)
124+
export GkeKubeconfigFilePath="/GoogleCloudPlatform/config"
125+
if [ -n "$DisplayGkeKubeConfig" ] && [ "$DisplayGkeKubeConfig" = true ];
126+
then
127+
echo "********* GKE kubeconfig ********* "
128+
cat /GoogleCloudPlatform/config
129+
echo "**********************************"
130+
fi',
131+
'SHELL',
132+
'f',
133+
'now()',
134+
1,
135+
'now()',
136+
1
137+
);
138+
139+
INSERT INTO "plugin_step" ("id", "plugin_id","name","description","index","step_type","script_id","deleted", "created_on", "created_by", "updated_on", "updated_by") VALUES (nextval('id_seq_plugin_step'), (SELECT id FROM plugin_metadata WHERE name='GKE Provisioner v1.1.0'),'Step 1','Step 1 - GKE Devtron plugin','1','INLINE',(SELECT last_value FROM id_seq_plugin_pipeline_script),'f','now()', 1, 'now()', 1);
140+
141+
INSERT INTO plugin_step_variable (id,plugin_step_id,name,format,description,is_exposed,allow_empty_value,default_value,value,variable_type,value_type,previous_step_index,variable_step_index,variable_step_index_in_plugin,reference_variable_name,deleted,created_on,created_by,updated_on,updated_by)
142+
VALUES (nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GcpServiceAccountEncodedCredential','STRING','GCP service account(base64 encoded) that to be used to create GKE cluster in the project','t','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
143+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GkeMinNodes','STRING','The minimum number of nodes in the cluster, default is 1','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
144+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'DisplayGkeKubeConfig','BOOL','Do we want to display the kubeconfig? Value either true or false.','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
145+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'Identifier','STRING','A string which identifies the purpose for which this cluster is being created. Used to name other resources created.','t','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
146+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GkeMaxNodes','STRING','The maximum number of nodes in the cluster, default is 3','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
147+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GkeNodeServiceAccountName','STRING','The Google Cloud Platform Service Account to be used by the node VMs, If no Service Account is specified, the project default service account is used.','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
148+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GkeRegion','STRING','The region to create the cluster in, default is us-central1 ','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
149+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GkeMachineType','STRING','The machine type to create, default is n1-standard-4 ','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
150+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GkeImageType','STRING','The type of image to create the nodes , default is cos','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
151+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GcpProjectId','STRING','The name of the GCP project in which to create the GKE cluster','t','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
152+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GkeClusterVersion','STRING','The GKE k8s version to install, default is latest','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
153+
(nextval('id_seq_plugin_step_variable'),(SELECT ps.id FROM plugin_metadata p inner JOIN plugin_step ps on ps.plugin_id=p.id WHERE p.name='GKE Provisioner v1.1.0' and ps."index"=1 and ps.deleted=false),'GkeKubeconfigFilePath','STRING','The kubeconfig path of gke cluster','t','f',false,null,'OUTPUT','NEW',null,1,null,null,'f','now()',1,'now()',1);
154+
155+
156+
INSERT INTO plugin_stage_mapping (id,plugin_id,stage_type,created_on,created_by,updated_on,updated_by)VALUES (nextval('id_seq_plugin_stage_mapping'),
157+
158+
(SELECT id from plugin_metadata where name='GKE Provisioner v1.1.0'), 0,'now()',1,'now()',1);
159+
160+
UPDATE plugin_pipeline_script SET script=
161+
'#!/bin/sh
162+
repoName=""
163+
branchName=""
164+
# Define the function to extract repoName and branchName
165+
FetchRepoBranchNameFunction() {
166+
CiMaterialsRequests=$GIT_MATERIAL_REQUEST
167+
materials=$(echo $CiMaterialsRequests | tr "|" "\n")
168+
for material in $materials
169+
do
170+
# echo "material : $material"
171+
data=$(echo $material | tr "," "\n")
172+
# echo "data: $data"
173+
repo_name=$(echo "$data" | sed -n ''1p'')
174+
branch_name=$(echo "$data" | sed -n ''3p'')
175+
# echo Reponame: $repo_name and branchName: $branch_name
176+
repoName="${repoName}-$repo_name"
177+
branchName="${branchName}-$branch_name"
178+
done
179+
repoName="${repoName#-}"
180+
branchName="${branchName#-}"
181+
}
182+
GlobalSonarqubeProjectName=""
183+
GlobalSonarqubeBranchName=""
184+
# Define sonarqube scan function
185+
SonarqubeScanFunction() {
186+
echo -e "\n********** Starting the scanning ************"
187+
docker run --rm -e SONAR_HOST_URL=$SonarqubeEndpoint -e SONAR_LOGIN=$SonarqubeApiKey -v "/$PWD:/usr/src" sonarsource/sonar-scanner-cli
188+
SonarScanStatusCode=$?
189+
echo -e "\nStatus code of sonarqube scanning command : $SonarScanStatusCode"
190+
if [ "$SonarScanStatusCode" -ne 0 ]; then
191+
echo -e "****** Sonarqube scanning command failed to run *********"
192+
exit 1
193+
fi
194+
if [[ $CheckForSonarAnalysisReport == true && ! -z "$CheckForSonarAnalysisReport" ]]
195+
then
196+
status=$(curl -u ${SonarqubeApiKey}: -sS ${SonarqubeEndpoint}/api/qualitygates/project_status?projectKey=$GlobalSonarqubeProjectName&branch=$SonarqubeBranchName)
197+
project_status=$(echo $status | jq -r ".projectStatus.status")
198+
export SonarqubeProjectStatus=$project_status
199+
echo "********* SonarQube Policy Report *********"
200+
echo $status
201+
if [[ $AbortPipelineOnPolicyCheckFailed == true && $project_status == "ERROR" ]]
202+
then
203+
echo "********* SonarQube Policy Violated *********"
204+
echo "********* Exiting Build *********"
205+
exit
206+
elif [[ $AbortPipelineOnPolicyCheckFailed == true && $project_status == "OK" ]]
207+
then
208+
echo "********* SonarQube Policy Passed *********"
209+
fi
210+
else
211+
echo -e "\nFinding the Vulnerabilities and High hotspots in source code ........\n"
212+
sleep 10
213+
export SonarqubeVulnerabilities=$(curl -u ${SonarqubeApiKey}: --location --request GET "$SonarqubeEndpoint/api/issues/search?componentKeys=$GlobalSonarqubeProjectName&types=VULNERABILITY" | jq ".issues | length")
214+
export SonarqubeHighHotspots=$(curl -u ${SonarqubeApiKey}: --location --request GET "$SonarqubeEndpoint/api/hotspots/search?projectKey=$GlobalSonarqubeProjectName" | jq ''.hotspots|[.[]|select(.vulnerabilityProbability=="HIGH")]|length'')
215+
echo "Total Sonarqube Vulnerability: $SonarqubeVulnerabilities"
216+
echo "Total High Hotspots: $SonarqubeHighHotspots"
217+
export TotalSonarqubeIssues=$((SonarqubeVulnerabilities + SonarqubeHighHotspots))
218+
echo "Total number of issues found by sonarqube scanner : $TotalSonarqubeIssues"
219+
echo -e "For analysis report please visit $SonarqubeEndpoint/dashboard?id=$GlobalSonarqubeProjectName"
220+
fi
221+
}
222+
223+
224+
FetchRepoBranchNameFunction
225+
if [ -z $SonarqubeProjectPrefixName ]
226+
then
227+
SonarqubeProjectPrefixName=$repoName
228+
fi
229+
if [ -z $SonarqubeBranchName ]
230+
then
231+
SonarqubeBranchName=$branchName
232+
fi
233+
234+
235+
PathToCodeDir=/devtroncd$CheckoutPath
236+
cd $PathToCodeDir
237+
if [ ! -z $SonarqubeProjectKey ]
238+
then
239+
GlobalSonarqubeProjectName=$SonarqubeProjectKey
240+
GlobalSonarqubeBranchName="master"
241+
else
242+
GlobalSonarqubeProjectName=$SonarqubeProjectPrefixName-$SonarqubeBranchName
243+
GlobalSonarqubeBranchName=$SonarqubeBranchName
244+
fi
245+
if [[ -z "$UsePropertiesFileFromProject" || $UsePropertiesFileFromProject == false ]]
246+
then
247+
echo "sonar.projectKey=$GlobalSonarqubeProjectName" > sonar-project.properties
248+
fi
249+
echo -e "\n********** Sonarqube Project Name : $GlobalSonarqubeProjectName , Sonarqube Branch name : $SonarqubeBranchName ***********"
250+
if [ -z "$GlobalSonarqubeProjectName" ] || [ -z "$SonarqubeBranchName" ]; then
251+
echo -e "\n****** Sonarqube Project Name and Sonarqube branch name should not be empty *********"
252+
exit 1
253+
fi
254+
255+
if [ -z $SonarqubeApiKey ]
256+
then
257+
echo "************* Sonarqube analysis api key has not been provided *************"
258+
exit 1
259+
fi
260+
if [ -z $SonarqubeEndpoint ]
261+
then
262+
echo "********** Sonarqube endpoint URL has not been provided ********* "
263+
exit 1
264+
fi
265+
266+
echo -e "\n*********Creating Sonarqube project **********"
267+
curl -u ${SonarqubeApiKey}: --location --request POST "$SonarqubeEndpoint/api/projects/create?name=$GlobalSonarqubeProjectName&mainBranch=$SonarqubeBranchName&project=$GlobalSonarqubeProjectName"
268+
CreateProjectStatusCode=$?
269+
if [ "$CreateProjectStatusCode" -ne 0 ]; then
270+
echo -e "****** Sonarqube project create command failed to run *********"
271+
exit 1
272+
else
273+
SonarqubeScanFunction
274+
fi'
275+
WHERE id=(select script_id from plugin_step where plugin_id =(select id from plugin_metadata where name='Sonarqube v1.1.0'));

0 commit comments

Comments
 (0)