/
pipeline_generator.lib
294 lines (266 loc) · 17.3 KB
/
pipeline_generator.lib
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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#!/bin/bash
function help {
echo ""
echo "Generates a $pipeline_type on $provider based on the given definition."
echo ""
echo "Common flags:"
echo " -c, --config-file [Required] Configuration file containing $pipeline_type definition."
echo " -n, --pipeline-name [Required] Name that will be set to the $pipeline_type."
echo " -d, --local-directory [Required] Local directory of your project."
echo " -a, --artifact-path Path to be persisted as an artifact after $pipeline_type execution, e.g. where the application stores logs or any other blob on runtime."
echo " -b, --target-branch Name of the branch to which the Pull Request will target. PR is not created if the flag is not provided."
[ "$provider" != "gcloud" ] && echo " -w Open the Pull Request on the web browser if it cannot be automatically merged. Requires -b flag."
[ "$provider" == "gcloud" ] && echo " -m, --machine-type Machine type for pipeline runner. Accepted values: E2_HIGHCPU_8, E2_HIGHCPU_32, N1_HIGHCPU_8, N1_HIGHCPU_32."
[ "$provider" == "gcloud" ] && echo " --env-vars List of environment variables to be made available in pipeline. Syntax: \"var1=val1 var2=val2 ...\"."
[ "$provider" == "gcloud" ] && echo " --secret-vars List of environment variables (saved as secrets in Secret Manager) to be made available in pipeline. Syntax: \"var1=val1 var2=val2 ...\"."
echo ""
echo "Build $pipeline_type flags:"
echo " -l, --language [Required] Language or framework of the project."
echo " -t, --target-directory Target directory of build process. Takes precedence over the language/framework default one."
echo " --language-version [Required, if Flutter or Python] Language or framework version."
[ "$provider" == "gcloud" ] && echo " --registry-location [Required, if Flutter] Artifact registry location."
echo ""
echo "Test $pipeline_type flags:"
echo " -l, --language [Required] Language or framework of the project."
echo " --language-version [Required, if Flutter or Python] Language or framework version."
[ "$provider" == "gcloud" ] && echo " --registry-location [Required, if Flutter] Artifact registry location."
[ "$provider" == "azure-devops" ] || [ "$provider" == "gcloud" ] && echo " --build-pipeline-name [Required] Build $pipeline_type name."
echo ""
echo "Quality $pipeline_type flags:"
echo " -l, --language [Required] Language or framework of the project."
echo " --language-version [Required, if Flutter or Python] Language or framework version."
[ "$provider" == "gcloud" ] && echo " --registry-location [Required, if Flutter] Artifact registry location."
echo " --sonar-url [Required] Sonarqube URL."
echo " --sonar-token [Required] Sonarqube token."
[ "$provider" == "azure-devops" ] || [ "$provider" == "gcloud" ] && echo " --build-pipeline-name [Required] Build $pipeline_type name."
[ "$provider" == "azure-devops" ] && echo " --test-pipeline-name [Required] Test $pipeline_type name."
echo ""
[ "$provider" == "github" ] && echo ""
[ "$provider" == "github" ] && echo "CI pipeline flags"
[ "$provider" == "github" ] && echo " --build-pipeline-name [Required] Name of the job calling the build $pipeline_type."
[ "$provider" == "github" ] && echo " --test-pipeline-name Name of the job calling the test $pipeline_type."
[ "$provider" == "github" ] && echo " --quality-pipeline-name Name of the job calling the quality $pipeline_type."
[ "$provider" == "github" ] && echo ""
echo "Package $pipeline_type flags:"
echo " -l, --language [Required, if dockerfile not set] Language or framework of the project."
echo " --language-version [Required, if Flutter or Python] Language or framework version."
[ "$provider" == "gcloud" ] && echo " --registry-location [Required, if Flutter] Artifact registry location."
echo " --flutter-web-platform [Required, if Flutter] Compile Web platform."
echo " --flutter-android-platform [Required, if Flutter] Compile Android platform."
echo " --flutter-web-renderer Flutter web renderer. Accepted values: auto (default), canvaskit, html."
echo " --dockerfile [Required, if language not set] Path from the root of the project to its Dockerfile. Takes precedence over the language/framework default one."
[ "$provider" == "azure-devops" ] || [ "$provider" == "gcloud" ] && echo " --build-pipeline-name [Required] Build $pipeline_type name."
[ "$provider" == "azure-devops" ] && echo " --quality-pipeline-name [Required] Quality $pipeline_type name."
[ "$provider" == "github" ] && echo " --ci-pipeline-name [Required] CI $pipeline_type name."
echo " -i, --image-name [Required] Name (excluding tag) for the generated container image."
echo " -u, --registry-user [Required, unless AWS or GCP] Container registry login user."
echo " -p, --registry-password [Required, unless AWS or GCP] Container registry login password."
echo " --aws-access-key [Required, if AWS] AWS account access key ID. Takes precedence over registry credentials."
echo " --aws-secret-access-key [Required, if AWS] AWS account secret access key."
echo " --aws-region [Required, if AWS] AWS region for ECR."
echo ""
echo "Library package $pipeline_type flags:"
echo " -l, --language [Required] Language or framework of the project."
echo ""
echo "Azure AKS provisioning $pipeline_type flags:"
echo " --cluster-name [Required] Name for the cluster."
echo " --resource-group [Required] Name of the resource group for the cluster."
echo " --storage-account [Required] Name of the storage account for the cluster."
echo " --storage-container [Required] Name of the storage container where the Terraform state of the cluster will be stored."
echo " --rancher Install Rancher to manage the cluster."
echo " --setup-monitoring Install logging and monitoring stack (Prometheus, Grafana, Alertmanager, Loki). Default: true."
echo ""
echo "AWS EKS provisioning $pipeline_type flags:"
echo " --cluster-name [Required] Name for the cluster."
echo " --s3-bucket [Required] Name of the S3 bucket where the Terraform state of the cluster will be stored."
echo " --s3-key-path [Required] Path within the S3 bucket where the Terraform state of the cluster will be stored."
echo " --aws-access-key [Required, on first run] AWS account access key ID."
echo " --aws-secret-access-key [Required, on first run] AWS account secret access key."
echo " --aws-region [Required, on first run] AWS region for provisioning resources."
echo " --setup-monitoring Install logging and monitoring stack (Prometheus, Grafana, Alertmanager, Loki). Default: true."
echo " --rancher Install Rancher to manage the cluster."
echo ""
if [ "$provider" == "gcloud" ]
then
echo "GKE provisioning $pipeline_type flags:"
echo " --gcloud-region [Required] Google Cloud region for provisioning resources."
echo " --rancher Install Rancher to manage the cluster."
echo " --setup-monitoring Install logging and monitoring stack (Prometheus, Grafana, Alertmanager, Loki). Default: true."
echo ""
fi
echo "Deploy to Kubernetes $pipeline_type flags:"
echo ""
echo " --package-pipeline-name [Required] Package $pipeline_type name."
echo " --env-provision-pipeline-name [Required] Environment provisioning $pipeline_type name."
echo " --k8s-provider [Required] Kubernetes cluster provider name. Accepted values: EKS, AKS."
echo " --k8s-namespace [Required] Kubernetes namespace where the application will be deployed."
echo " --k8s-deploy-files-path [Required] Path from the root of the project to the YAML manifests directory."
echo " --k8s-image-pull-secret-name Name for the generated secret containing registry credentials. Required when using a private registry to host images."
echo ""
if [[ "$provider" == "gcloud" ]]
then
echo "Deploy to Cloud Run $pipeline_type flags:"
echo ""
echo " --service-name [Required] Name for the Cloud Run service."
echo " --gcloud-region [Required] Region where the service will be deployed."
echo " --port Listening port of the service. Default: 8080."
fi
exit
}
function validateRegistryLoginCredentials {
# if the user chose to push to a registry and the user has not already given a password
# then prompt the user
if [ -v dockerUser ] && [ ! -v dockerPassword ]
then
read -rsp "Please enter Docker registry password..." dockerPassword
fi
if [ -v awsRegion ] && [ -v awsAccessKey ] && [ ! -v awsSecretAccessKey ]
then
read -rsp "Please enter AWS secret access key..." awsSecretAccessKey
fi
}
function ensurePathFormat {
currentDirectory=$(pwd)
# When necessary, converts a relative path into an absolute path, and a Windows-style path (e.g. "C:\Users" or C:/Users) into a
# Unix-style path using forward slashes (e.g. "/c/Users").
localDirectory=${localDirectory//'\'/"/"}
localDirectory=${localDirectory//'C:'/"/c"}
cd "${localDirectory}" || { echo -e "${red}Error: Local directory '${localDirectory}' does not exist. Check provided path (missing quotes?)." >&2; echo -ne "${white}" >&2; exit 2; }
localDirectory=$(pwd)
# Return to initial directory
cd "$currentDirectory"
}
function importConfigFile {
# Import config file.
source $configFile
IFS=, read -ra flags <<< "$mandatoryFlags"
# Check if the config file was supplied.
if test -z "$configFile"
then
echo -e "${red}Error: $pipeline_type definition configuration file not specified." >&2
echo -ne "${white}" >&2
exit 2
fi
# Check if the required flags in the config file have been activated.
for flag in "${flags[@]}"
do
if test -z $flag
then
echo -e "${red}Error: Missing parameters, some flags are mandatory." >&2
echo -e "${red}Use -h or --help flag to display help." >&2
echo -ne "${white}" >&2
exit 2
fi
done
}
function checkInstallations {
# Check if Git is installed
if ! [ -x "$(command -v git)" ]; then
echo -e "${red}Error: Git is not installed." >&2
echo -ne "${white}" >&2
exit 127
fi
# Check if the CLI tool for the provider is installed
if ([ "$provider" == "github" ] && ! [ -x "$(command -v gh)" ]); then
echo -e "${red}Error: Github CLI is not installed." >&2
echo -ne "${white}" >&2
exit 127
elif ([ "$provider" == "azure-devops" ] && ! [ -x "$(command -v az)" ]); then
echo -e "${red}Error: Azure CLI is not installed." >&2
echo -ne "${white}" >&2
exit 127
elif ([ "$provider" == "gcloud" ] && ! [ -x "$(command -v gcloud)" ]); then
echo -e "${red}Error: gcloud CLI is not installed." >&2
exit 127
fi
# Check if Python is installed
if ! [ -x "$(command -v python)" ]; then
echo -e "${red}Error: Python is not installed." >&2
echo -ne "${white}" >&2
exit 127
fi
}
function createNewBranch {
echo -e "${green}Creating the new branch: ${sourceBranch}..."
echo -ne ${white}
# Create the new branch.
cd "${localDirectory}"
git checkout -b ${sourceBranch}
}
function copyYAMLFile {
echo -e "${green}Copying the corresponding files into your directory..."
echo -ne ${white}
# Create .pipelines and scripts if they do not exist.
mkdir -p "${localDirectory}/$scriptFilePath"
# Generate pipeline YAML from template and put it in the repository.
# We cannot use a variable in the definition of resource in the pipeline so we have to use a placeholder to replace it with the value we need
commonEnvSubstList='${buildPipelineName} ${testPipelineName} ${qualityPipelineName} ${pipelineName} ${ciPipelineName} ${packagePipelineName}'
envsubst "${commonEnvSubstList} ${specificEnvSubstList}" < "${hangarPath}/${templatesPath}/${yamlFile}.template" > "${localDirectory}/${pipelinePath}/${yamlFile}"
# Check if an extra artifact to store is supplied.
if test -n "$artifactPath"
then
# Add the extra step to the YAML.
if [ "$provider" == "azure-devops" ]
then
cat "${hangarPath}/${commonTemplatesPath}/store-extra-path.yml" >> "${localDirectory}/${pipelinePath}/${yamlFile}"
elif [ "$provider" == "github" ]
then
# (in case of -a flag set) Here we replace a comment inside the template file by a step in Github Actions to upload an artifact
storeExtraPathContent="\n - name: Publish Additional Output Artifact\n uses: actions\/upload-artifact@v3\n with:\n name: additional-pipeline-output\n path: \"\${{ env.artifactPath }}\""
sed -i "s/# mark to insert step for additional artifact #/$storeExtraPathContent\n/" "${localDirectory}/${pipelinePath}/${yamlFile}"
elif [ "$provider" == "gcloud" ]
then
# (in case of -a flag set) We first check if an artifact is already uploaded in this template,
# to know if we need to add an artifact to the list of uploaded artifact because there is/are already artifact(s) uploaded,
# or if we need to add the artifact section because there is not artifact uploaded yet
grep -e "^artifacts:" "${localDirectory}/${pipelinePath}/${yamlFile}" > /dev/null && storeExtraPathContent=" - additional_output_\${BUILD_ID}.tar" || storeExtraPathContent="\nartifacts:\n objects:\n location: gs:\/\/\${PROJECT_ID}_cloudbuild\/pipelinesArtifacts\/${pipelineName}\/commit_\${SHORT_SHA}\n paths:\n - additional_output_\${BUILD_ID}.tar"
# We replace the comment used to know where to add the lines defined is the line above
sed -i "s/# mark to insert entry in artifact for additional artifact #/$storeExtraPathContent\n/" "${localDirectory}/${pipelinePath}/${yamlFile}"
# Then we add a step to the pipeline to create a tar of the additional artiact, we create a tar for 2 reasons:
# - Keep the permissions of the files as they are
# - In Gcloud, if you try to upload a folder and not a file, it fails
stepCreateTarArtifact="- id: \"Create additional artifact\"\n name: \${_DOCKER_IMAGE_STEPS}\n args: ['tar', '-cf', 'additional_output_\${BUILD_ID}.tar', '\${_ARTIFACT_PATH}']"
sed -i "s/# mark to insert step for additional artifact #/$stepCreateTarArtifact\n/" "${localDirectory}/${pipelinePath}/${yamlFile}"
fi
else
if [ "$provider" == "github" ] || [ "$provider" == "gcloud" ]
then
# If no -a flag set, we just remove the mark used to place the additional entries and step in the yaml
sed -i '/# mark to insert step for additional artifact #/d' "${localDirectory}/${pipelinePath}/${yamlFile}"
sed -i '/# mark to insert entry in artifact for additional artifact #/d' "${localDirectory}/${pipelinePath}/${yamlFile}"
fi
fi
}
function copyCommonScript {
echo -e "${green}Copying the script(s) common to any $pipeline_type files into your directory..."
echo -ne ${white}
! (ls "${hangarPath}/${commonTemplatesPath}"/*.sh) &> /dev/null || cp "${hangarPath}/${commonTemplatesPath}"/*.sh "${localDirectory}/${scriptFilePath}"
}
function commitCommonFiles {
echo -e "${green}Commiting and pushing into Git remote..."
echo -ne ${white}
# Move into the project's directory and pushing the template into the remote repository.
cd "${localDirectory}"
# Add the YAML files.
git add "$pipelinePath" -f
# Git commit and push it into the repository.
# changing all files to be executable
find "$pipelinePath" -type f -name '*.sh' -exec git update-index --chmod=+x {} \;
git commit -m "[skip ci] Adding the source YAML"
git push -u origin ${sourceBranch}
}
function setTargetDirectory {
case $language in
node | angular | flutter) targetDirectory="./" ;;
quarkus*) targetDirectory="./target/" ;;
python) targetDirectory="./venv" ;;
*) echo -e "${red}Error: Specified language '${language}' is not supported." >&2; echo -ne "${white}" >&2; exit 2
esac
}
function languageVersionVerification {
if { [ "$language" == "flutter" ] || [ "$language" == "python" ] ; } && [ "" == "$languageVersion" ]
then
echo -e "${red}Error: Language given requires --language-version flag.${white}" >&2
exit 2
fi
}