SonarQube + Artifactory ์ ์๋ฒฝํ ์๋ํ ํ์ดํ๋ผ์ธ
์ด ๊ธ์ Fabrice Bellingard๊ฐ ์์ฑํ Fully Automated Promotion Pipelines with SonarQube and Artifactory๋ฅผ ๋ฒ์ญํ ๊ธ์ ๋๋ค.
์ด ๋ธ๋ก๊ทธ ํฌ์คํธ๋ Jonathan Roquelaure(JFrog)๊ณผ Fabrice Bellingard(SonarSource)๊ฐ ๊ณต๋ ์์ฑํ์ผ๋ฉฐ, JFrog blog์ ์ต์ด๋ก ๊ฒ์ฌํ์์ต๋๋ค.
JFrog ์น์ฌ์ดํธ์ ๊ฒ์ ํ ์ด์ ํฌ์คํธ๋ฅผ ํตํด Artifactory์ SonarQube๋ฅผ ์ฐ๋ํ๊ณ , ๋ ๋์ ํ์ง์ ์ํํธ์จ์ด์ ๋ํ ๊ฒฐ์ ์ ๋ด๋ฆฌ๋ ๋ฐ ํ์ฉํ ์ ์์์ ์ค๋ช ํ์ต๋๋ค. ๊ธฐ์กด ํ์ดํ๋ผ์ธ์ ๊ฐ๋จํ ์คํฌ๋ฆฝํธ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ๋ง์ผ๋ก ์์ฝ๊ฒ Artifactory์ SonarQube๋ฅผ ํตํฉํ ์ ์๊ณ , quality gate ๋ง์กฑ ์ฌ๋ถ์ ๋ฐ๋ผ ์ดํ์ ํ์ดํ๋ผ์ธ ์งํ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ ์ ์์ต๋๋ค.
์ด๊ฒ ์ ๋ถ์ผ๊น์?
์ฐ๋ฆฌ๋ ์ํฐํฉํธ๋ค์ quality gate๋ฅผ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค. ์กฐ๊ธ ๋ ์๊ฐํด๋ด ์๋ค. ๋จ์ํ ์ด๋ฅผ Artifactory์ ๋ฉํ๋ฐ์ดํฐ ์ ๋ณด๋ก ์ฌ์ฉํ๋ ๋์ , ์ด ๋ฐ์ดํฐ๋ฅผ ๊ธฐ์ด๋ก ์๋์ ์ผ๋ก ํ๋ก๋ชจ์ ์ ํธ๋ฆฌ๊ฑฐ๋งํ ์ (ํน์ ํ์ง ์์ ์) ์์ ๊ฒ์ ๋๋ค โ ์๋ฅผ ๋ค๋ฉด, ๋ค์ ์คํ ์ด์ง ํ์ด์ฆ์์ ์ํฐํฉํธ๋ค์ ์ฌ์ฉํ ์ ์๋๋ก ํด๋น ์ํฐํฉํธ๋ค์ ๋ณต์ฌํ๊ฑฐ๋ ์ด๋์์ผ ๋๋ ์์ ๋ค์ด ๋ ์ ์์ต๋๋ค. ๋ชจ๋ ํ์ฌ๋ค์ ๊ฐ๊ฐ ๊ณ ์ ์ ์ํฌํ๋ก์ฐ๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ์๋ํ๋ ํ๋ก๋ชจ์ ์ ์ํด ํ์ํ ๋ก์ง๋ค์ ๊ตฌํํ ์ ์์ด์ผ๋ง ํฉ๋๋ค.
๋ํ ์ค์ ํ๋ก์ ํธ๋ค์ ๋ณต์กํ ๋น๋ ํ์ดํ๋ผ์ธ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ, ๊ฐ๋ฐํ๋ค์ ๊ฐ๋ฅํ ๋น ๋ฅธ(Jez Humble๊ณผ Dave Farley๊ฐ Continuous Delivery ์ฑ ์์ ๋งํ ๊ฒ๊ณผ ๊ฐ์ ์ปค๋ฐ๋น ๋น๋๋ฅผ ์ํํ๋ ํ์ดํ๋ฆฌ์๊ณผ ๊ฐ์) ํผ๋๋ฐฑ์ ์ํฉ๋๋ค. SonarQube๋ ํ๋ก์ ํธ๋ฅผ ๋ถ์ํด quality gateย ์ํ๋ฅผ ์ ๊ณตํ๊ธฐ ์ํ ์ฝ๊ฐ์ ์๊ฐ์ด ํ์ํ๋ฐ Artifactory์ ํตํฉ์ ํ๋ ๊ณผ์ ์์ ํ์ดํ๋ผ์ธ์ ๋ธ๋ญ์ํค๋ฉด ์๋ฉ๋๋ค: ๋ํ ์ ์ฌ์ ์ธ ํ์ ์์ ๋ค ์ญ์ SonarQube์ ๋ถ์ ๋ณด๊ณ ์ ์ฒ๋ฆฌ์ ๊ด๋ จ์์ด ๋์์ ์ํ๋์ด์ผ ํฉ๋๋ค.
๊ทธ๋ผ, Artifactory์ SonarQube๋ฅผ ํ์ฉํด ์ด์ ํ์ดํ๋ผ์ธ์ ๋ธ๋กํนํ์ง ์๊ณ , ์ปค์คํฐ๋ง์ด์ด์ ์ด์ ๊ฐ๋ฅํ ์๋ํ ๋ ์๋ฃจ์ ์ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ๋ฐ๋ก ์ฌ๋ฌ๋ถ์ด ์ต๊ณ ํ์ง์ ์ํํธ์จ์ด๋ฅผ ์ ๋ฌํ ์ ์๋๋ก ๋ง์ ๋๋ค.
์ปค์คํฐ๋ง์ด์ ์ด์ ๊ฐ๋ฅํ ์๋ํ ๋ ํตํฉ
์ฌ๋ฌ๋ถ์ ์ด ๋จ์ด๋ค์ ๋ฃ๋ ์๊ฐ ์นํ (webhooks), API ๋ฐ ์ฌ์ฉ์ ํ๋ฌ๊ทธ์ธ๋ค์ ๋ ์ฌ๋ฆด ๊ฒ์ ๋๋ค.
SoanrQube Webhooks
CI/CD ํ๋ก์ธ์ค๋ฅผ ๊ตฌ์ฑํ๋ ๋ง์ ํ์ดํ๋ผ์ธ ์ค ํ๋๋ก, SonarQube๋ ์นํ (webhooks)์ ์ฌ์ฉํด ๋ค๋ฅธ ์๋น์ค๋ค์ ๋์์ผ๋ก ๋ถ์ ๋ณด๊ณ ์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋์์์ ์๋ฆฝ๋๋ค. HTTPS ์ฝ์ ์ฒ๋ฆฌ ํ์คํฌ์ ์ํ์ ๊ด๊ณ์์ด ์์ฑ๋๋ฉฐ, Artifactory์ ์ฌ์ฉ์ ํ๋ฌ๊ทธ์ธ์ ์ด ์ฝ์ ํ์ด๋ก๋์ ๋ด๊ธด ๋ง์ ์ ๋ณด๋ค์ ํ์ฉํด ์ง์ ๋ ์ํฐํฉํธ๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง ๊ฒฐ์ ํ ์ ์์ต๋๋ค. ์๋๋ SonarQube ์นํ ์ ํตํด ์ ๋ฌ๋๋ JSON ํ์ด๋ก๋ ์์์ ๋๋ค:
{
"analysedAt": "2016-11-18T10:46:28+0100",
"project": {
"key": "org.sonarqube:example",
"name": "Example"
},
"properties": {
},
"qualityGate": {
"conditions": [
{
"errorThreshold": "1",
"metric": "new_security_rating",
"onLeakPeriod": true,
"operator": "GREATER_THAN",
"status": "OK",
"value": "1"
},
{
"errorThreshold": "1",
"metric": "new_reliability_rating",
"onLeakPeriod": true,
"operator": "GREATER_THAN",
"status": "ERROR",
"value": "1"
},
...
],
"name": "SonarQube way",
"status": "ERROR"
},
"serverUrl": "http://localhost:9000",
"status": "SUCCESS",
"taskId": "AVh21JS2JepAEhwQ-b3u"
}
์ง๊ธ ์ฐ๋ฆฌ๊ฐ ์ด์ผ๊ธฐํ๊ณ ์๋ ์ปจํ ์คํธ์ ํด๋นํ๋ ์ ๋ณด๋ค์ ์ถ๋ ค๋ณด๋ฉด ์๋์ ๊ฐ์ต๋๋ค:
TaskId โ Artifactory์ ์ง์ ๋ ์ํฐํฉํธ๋ฅผ ์๋ณํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค
quality gate status โ ์ง์ ๋ ์ํฐํฉํธ์ ํ๋ก๋ชจ์ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๊ธฐ ์ํด ํ์ํ ๊ฐ์ฅ ์ค์ํ ์ ๋ณด์ ๋๋ค.
SonarQub์ ์นํ ์ ํ๋ก์ ํธ ๋จ์(project settings ๋ฉ๋ด) ํน์ ๊ธ๋ก๋ฒ ๋ ๋ฒจ์์ ์ค์ ํ ์ ์์ต๋๋ค. ๋๋ถ๋ถ์ ํ๋ก์ ํธ๋ฅผ SonarQube๋ก ๋ถ์ํ๊ณ Artifactory๋ฅผ ํตํด ๊ด๋ฆฌํ๋ ๊ฒฝ์ฐ์๋ ๊ธ๋ก๋ฒ ๋ ๋ฒจ์์ ์นํ ์ ์ค์ ํ๋ ๊ฒ์ด ๋ณด๋ค ํธ๋ฆฌํ ๊ฒ์ ๋๋ค.
Artifactory ์ฌ์ฉ์ ํ๋ฌ๊ทธ์ธ
Artifactory Pro ๋ฐ Enterprise๋ฅผ ์ฌ์ฉํ๋ฉด Groovy ์คํฌ๋ฆฝํธ๋ก ์์ฑํ ์ฌ์ฉ์ ํ๋ฌ๊ทธ์ธ์ผ๋ก ์์ฝ๊ฒ Artifactory์ ๊ธฐ๋ฅ์ ํ์ฅํ ์ ์์ต๋๋ค. ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ๋ฉด ์ ๊ธฐ์ ์ธ ํ์คํฌ(์. ํด๋ฆฐ์ ๋ฑ) ๋๋ ํน์ ํ ์ด๋ฒคํธ์ ์ฐ๋๋ ์ฌ์ฉ์ ์ ์ ํ์คํฌ(์: ๋ค์ด๋ก๋ ์๋ต์ ๋ณ๊ฒฝํ๊ฑฐ๋ ํน์ ํ ๋ณด์ ์ ์ฑ ์ ๋ณ๊ฒฝํ๋ ๋ฑ)๋ฅผ ์คํํ ์ ์๋ ๊ฒ์ ๋ฌผ๋ก ์๋ก์ด API ์๋ํฌ์ธํธ๋ฅผ ๋ ธ์ถ์ํฌ ์๋ ์์ต๋๋ค(์, SonarQube ์นํ ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ ํน์ ํ ์ํฌํ๋ก์ฐ๋ฅผ ๊ตฌํํ๋ ๋ฑ).
๋ค์ ์ฝ๋ ์ค๋ํซ์ ๋ณผ๊น์?
executions {
//Expose a new endpoint for sonarqube webhook
updateSonarTaskStatus(httpMethod: 'POST', users: ["admin"], groups: [], params:[targetRepo: '']) { params, ResourceStreamHandle body ->
targetRepo = getStringProperty(params, 'targetRepo', true)
bodyJson = new JsonSlurper().parse(body.inputStream)
sonarTaskId = bodyJson.taskId
//Implement your workflow based on SonarQube quality gate result
}
}
์ ์ฝ๋๋ฅผ ํตํด ์์ฑํ ์๋ก์ด ์๋ํฌ์ธํธ๋ SonarQube ์นํ ์์ ์๋ URL๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค:
http://admin:password@<ARTIFACTORY_URL>:8081/artifactory/api/plugins/execute/updateSonarTaskStatus?params=targetRepo=gradle-staging-local
Jenkins ํ์ฉํ๊ธฐ
์ฐ๋ฆฌ๋ Jenkins CI ์๋ฒ์ ์ปค๋ฐ ๋น๋๋ฅผ ๊ตฌํํด ์ปค๋ฏธํฐ์๊ฒ ๋น ๋ฅธ ํผ๋๋ฐฑ์ ์ ๊ณตํ๋ฉฐ, (์ด์ ํฌ์คํธ์ ๊ธฐ์ ํ๋ ๊ฒ ์ฒ๋ผ) ์๋ํ๋ธ ๋ถ์(taskId)๊ณผ Artifactory ๋น๋ ์ ๋ณด(๋ฐ ๊ด๋ จ ์ฐ์ถ๋ฌผ)์ ์ฐ๊ฒฐํฉ๋๋ค.
์ ์ฒด์ ์ธ ๊ทธ๋ฆผ
Jenkins ์๋ฒ์๋ ์ปค๋ฐ ๋น๋๊ฐ ๋งค ์ปค๋ฐ์ ๋ํ ๋น ๋ฅธ ํผ๋๋ฐฑ์ ์ ๊ณตํฉ๋๋ค(๋ด๊ฐ ์์ฑํ ์ฝ๋๊ฐ ๊ณต์ฉ ํ๊ฒฝ์์ ๋น๋๋์๋์ง, ๋ด ์ฝ๋๊ฐ ๊ธฐ์กด ์ํํธ์จ์ด๋ฅผ ๋ง๊ฐ๋จ๋ฆฌ์ง๋ ์์๋์ง, ๊ณ์ ์ฝ๋๋ฅผ ์์ฑํด๋ ๋๋์ง ๋ฑ).
์ปค๋ฐ ๋น๋ ์ํฌํ๋ก์ฐ(commit build workflow)๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
SonarQube์ quality gate ๊ฒฐ๊ณผ๊ณผ ๊ด๋ จ๋ ๋ก์ง๋ค์ ๋๋ฒ์งธ โstagingโ ์ํฌํ๋ก์ฐ์ ๊ตฌํ๋์ด ์์ต๋๋ค. ์ฌ๊ธฐ์์ ์ฌ๋ฌ๋ถ์ ๋น๋๋ฅผ ๊ณ์ ์งํํ๊ฑฐ๋(ํ๋ก๋ชจํธ) ๋ณด๋ค ๋์ ์์ค์ ํ ์คํธ, ํตํฉ, ๋ฐฐํฌ ๋ฑ์ ์ํํ๊ธฐ ์ํด ์ธ๋ถ ๋๊ตฌ๋ค์ ํธ๋ฆฌ๊ฑฐ๋ง ํ ์ ์์ต๋๋ค.
์คํ ์ด์ง ์ํฌํ๋ก์ฐ(staging workflow)๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
์๋ํด ๋ณด์ญ์์ค
SonarSource์ JFrog์ ํ์ ์ด ๊ฒฐ์ค์ ๋งบ๊ณ ์์ด์ ๋งค์ฐ ํฅ๋ถ๋ฉ๋๋ค. ์ด ํตํฉ ๊ณผ์ ์ ์๋ํด๋ณด๊ณ ์ฌ๋ฌ๋ถ์ด ๋๋ ๊ฒ๋ค์ ์๋ ค์ฃผ์ญ์์ค. Integration GitHub project์ ์ด์๋ฅผ ์์ฑํด์ ์ ํฌ์ ๋ํ๋ฅผ ์์ํ ์ ์์ต๋๋ค.
ํตํฉ ๊ด๋ จ ์จ๋น๋ ์ ์ฒญํ๊ธฐ (๊ธฐํ ๋ง๋ฃ)
SonarCloud ํธ๋ผ์ด์ผ ์์ํ๊ธฐ, SonarCloud๋ SonarQube as a Service์ ๋๋ค(์คํ์์ค ๋ถ์์ ๋ฌด๋ฃ)
ยฉ 2017-2018 Moses Kim.
๋ณ๋์ ์ธ๊ธ์ด ์๋ ํ, ์ด ์คํ์ด์ค์ ์ปจํ
์ธ ๋ ํฌ๋ฆฌ์์ดํฐ๋ธ ์ปค๋จผ์ฆ ์ ์์ํ์-๋น์๋ฆฌ-๋์ผ์กฐ๊ฑด๋ณ๊ฒฝํ๋ฝ 4.0 ๊ตญ์ ๋ผ์ด์ ์ค์ ๋ฐ๋ผ ์ด์ฉํ ์ ์์ต๋๋ค.
SONARQUBE๋ SonarSource SA์ ํธ๋ ์ด๋ ๋งํฌ์
๋๋ค. ๋ชจ๋ ํธ๋ ์ดํธ ๋งํฌ ๋ฐ ์ ์๊ถ์ ๊ฐ ์์ ์์ ์์ ๋ฌผ์
๋๋ค.
::: SonarQube ๊ด๋ จ ๋ฌธ์ : ์ด๋ฉ์ผ :::