-
Notifications
You must be signed in to change notification settings - Fork 6
/
github.jenkinsfile
189 lines (159 loc) · 10.2 KB
/
github.jenkinsfile
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
// Requires Pipeline Utility Steps plugin (https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/)
pipeline {
agent any
parameters {
string(name: 'REPO_NAME', description: 'GitHub repository name. E.g. "user/repo1"')
string(name: 'BRANCH_NAME', description: 'GitHub branch name. E.g. "my_branch"')
string(name: 'TF_VAR_FILE', defaultValue: '', description: 'Optional Terraform var file name, relative branch root. E.g. "path/to/my_var_file.tfvars"')
}
stages {
stage('infracost') {
agent {
docker {
// Always use the latest 0.10.x version to pick up bug fixes and new resources.
// See https://www.infracost.io/docs/integrations/cicd/#docker-images for other options
image 'infracost/infracost:ci-0.10'
args "--user=root --entrypoint='' --pull always"
}
}
steps {
script {
// Set up required credentials for posting the comment, e.g. GitHub token, Infracost API key
env.GITHUB_TOKEN = credentials('github-token')
env.INFRACOST_API_KEY = credentials('infracost-api-key')
env.LOG_LEVEL = 'warn'
env.GITHUB_URL = 'https://github.com'
env.GITHUB_API_URL = 'https://api.github.com'
env.REPO_NAME = "${params.REPO_NAME}"
env.BRANCH_NAME = "${params.BRANCH_NAME}"
env.TF_VAR_FILE = "${params.TF_VAR_FILE ?: ''}"
if (env.TF_VAR_FILE && !fileExists(env.TF_VAR_FILE)) {
error("TF_VAR_FILE '${env.TF_VAR_FILE}' was specified but does not exist")
}
// If you store Terraform variables or modules in a 3rd party such as Terraform Cloud or Spacelift,
// specify the following so Infracost can automatically retrieve them.
// See https://www.infracost.io/docs/features/terraform_modules/#registry-modules for details.
// env.INFRACOST_TERRAFORM_CLOUD_TOKEN = credentials('terraform-cloud-token')
// env.INFRACOST_TERRAFORM_CLOUD_HOST = 'app.terraform.io'
}
script {
// Get the default branch of the repository
def repoApiUrl = "${env.GITHUB_API_URL}/repos/${env.REPO_NAME}"
echo "Getting repo details from GitHub API: ${repoApiUrl}"
def repoOutput = sh(script: 'curl -s -H \"Authorization: token $GITHUB_TOKEN\" \"' + repoApiUrl + '\"', returnStdout: true).trim()
def repoDetails = readJSON text: repoOutput
def defaultBranch = repoDetails.default_branch
env.DEFAULT_BRANCH = defaultBranch
// Call GitHub API to get branch details
def branchApiUrl = "${env.GITHUB_API_URL}/repos/${env.REPO_NAME}/branches/${env.BRANCH_NAME}"
echo "Getting branch details from GitHub API: ${branchApiUrl}"
def output = sh(script: 'curl -s -H \"Authorization: token $GITHUB_TOKEN\" \"' + branchApiUrl + '\"', returnStdout: true).trim()
def branchDetails = readJSON text: output
// Set branch and repository environment variables
env.INFRACOST_VCS_REPOSITORY_URL = "${GITHUB_URL}/${env.REPO_NAME}"
env.INFRACOST_VCS_BRANCH = env.BRANCH_NAME
env.INFRACOST_VCS_COMMIT_SHA = branchDetails.commit.sha
env.INFRACOST_VCS_COMMIT_AUTHOR_NAME = branchDetails.commit.commit.author.name
env.INFRACOST_VCS_COMMIT_AUTHOR_EMAIL = branchDetails.commit.commit.author.email
env.INFRACOST_VCS_COMMIT_TIMESTAMP = branchDetails.commit.commit.author.date
env.INFRACOST_VCS_COMMIT_MESSAGE = branchDetails.commit.commit.message
echo 'Finished setting environment variables'
}
script {
if (env.BRANCH_NAME == env.DEFAULT_BRANCH) {
// don't create a diff, this is a default branch run
return
}
echo "Generating Infracost baseline from default branch"
def failuresList = null
try {
env.INFRACOST_VCS_BASE_BRANCH = env.DEFAULT_BRANCH
// Get the merge base SHA using GitHub API
def mergeBaseApiUrl = "${env.GITHUB_API_URL}/repos/${env.REPO_NAME}/compare/${env.DEFAULT_BRANCH}...${env.BRANCH_NAME}"
echo "Getting merge base details from GitHub API: ${mergeBaseApiUrl}"
def mergeBaseOutput = sh(script: 'curl -s -H \"Authorization: token $GITHUB_TOKEN\" \"' + mergeBaseApiUrl + '\"', returnStdout: true).trim()
def mergeBaseDetails = readJSON text: mergeBaseOutput
def mergeBaseSha = mergeBaseDetails.merge_base_commit.sha
echo "Found merge base SHA: ${mergeBaseSha}"
// Clone the repository to a temporary directory
sh "git clone --depth 1 -b ${env.INFRACOST_VCS_COMMIT_SHA} ${GITHUB_URL}/${env.REPO_NAME}.git /tmp/repo"
// Checkout the merge base commit
sh "cd /tmp/repo && git fetch --depth 1 origin ${mergeBaseSha} && git checkout -q ${mergeBaseSha}"
sh 'infracost breakdown --path=/tmp/repo \
--format=json \
--out-file=/tmp/infracost-base.json \
--log-level=$LOG_LEVEL \
--terraform-var-file=$TF_VAR_FILE'
echo "Generating Infracost diff"
sh "cd /tmp/repo && git checkout ${env.INFRACOST_VCS_COMMIT_SHA}"
// Generate an Infracost diff and save it to a JSON file.
sh 'infracost diff --path=/tmp/repo \
--format=json \
--compare-to=/tmp/infracost-base.json \
--out-file=/tmp/infracost.json \
--log-level=$LOG_LEVEL \
--terraform-var-file=$TF_VAR_FILE'
echo "Generating Infracost comment"
// Post PR comment and upload the infracost.json file to Infracost Cloud
def commentOutput = sh(
script: 'infracost comment github --path=/tmp/infracost.json \
--repo=$REPO_NAME \
--commit=$INFRACOST_VCS_COMMIT_SHA \
--github-api-url=$GITHUB_API_URL \
--github-token=$GITHUB_TOKEN \
--behavior=update \
--format=json \
2> commentErrOut.txt \
|| true',
returnStdout: true,
)
def commentErrOut = readFile('commentErrOut.txt').trim()
def commentUrl = commentErrOut.find(/[^\s]+#commitcomment[^\s]+/)
def commentDetails = readJSON text: commentOutput
def infracostReport = "########## See Infracost branch diff ##########\n${commentUrl}\n##########"
echo infracostReport
writeFile file: 'infracost.txt', text: infracostReport
archiveArtifacts artifacts: 'infracost.txt'
if (commentDetails.governanceFailures instanceof List && !commentDetails.governanceFailures.isEmpty()) {
failuresList = commentDetails.governanceFailures.collect { it }
}
} catch (Exception e) {
echo "Caught an error: ${e}"
}
if (failuresList != null) {
error("Governance check failed:\n- ${failuresList.join(' ')}\n")
}
}
script {
if (env.BRANCH_NAME != env.DEFAULT_BRANCH) {
// don't upload default branch breakdown, this is a feature branch run
return
}
try {
echo "Generating Infracost baseline for default branch"
// Clone the repository to a temporary directory
sh "git clone --depth 1 -b ${env.DEFAULT_BRANCH} ${GITHUB_URL}/${env.REPO_NAME}.git /tmp/repo && cd /tmp/repo"
// For cases where you use pull requests in your workflow, we need to add a step here
// that updates the PR status in Infracost Cloud based on the PR number from the git log.
// See the bitbucket.jenkinsfile for an example.
// Run Infracost breakdown, don't use the TF_VAR_FILE so all projects in the repo are evaluated
sh 'infracost breakdown --path=/tmp/repo \
--format=json \
--out-file=/tmp/infracost.json \
--log-level=$LOG_LEVEL'
// Run Infracost upload
def uploadOutput = sh( script: "infracost upload --path=/tmp/infracost.json --format=json || true", returnStdout: true )
def uploadDetails = readJSON text: uploadOutput
def repoUrl = uploadDetails.cloudUrl.find(/.*\/repos\/[^\/]+/)
def infracostReport = "########## See Infracost repo report ##########\n${repoUrl}\n##########"
echo infracostReport
writeFile file: 'infracost.txt', text: infracostReport
archiveArtifacts artifacts: 'infracost.txt'
} catch (Exception e) {
echo "Caught an error: ${e}"
}
}
}
}
}
}