stages:
- build
- deploy
build:
# ...
deploy:
# ...
build:
image: node:8
stage: build
script:
- npm ci
- npm build
- docker build -t my-app:latest .
- docker push my-app:latest
deploy:
image: helm
stage: deploy
script:
- helm upgrade -i my-app ./my-app-chart
newIngress {
metadata {
name = "example-ingress"
}
spec {
backend {
serviceName = "example-service"
servicePort = IntOrString(8080)
}
}
}
plugins {
application
kotlin("jvm") version "1.2.61"
}
application {
mainClassName = "samples.HelloWorldKt"
}
dependencies {
compile(kotlin("stdlib"))
}
pipeline {
stages {
stage('Build') {
// ...
}
stage('Deploy') {
// ...
}
}
}
agent { docker { image 'node:8' } }
steps {
sh 'npm ci'
sh 'npm test'
sh 'npm build'
script {
docker.build("my-app:release").push()
}
}
agent {
docker { image 'helm' }
}
steps {
sh '''
helm upgrade -i my-app ./my-app-chart
'''
}
project {
buildType {
id = AbsoluteId("build")
}
buildType {
id = AbsoluteId("deploy")
}
}
params {
param("plugin.docker.imageId", "node:8")
}
steps {
script {
scriptContent = """npm ci"""
}
// ...
}
params { param("plugin.docker.imageId", "helm") }
steps {
script {
scriptContent = """
helm upgrade -i my-app ./my-app-chart
"""
}
}
dependencies {
dependency(AbsoluteId("build")) { snapshot() }
}
vars/npm.groovy
def installModules(String name) {
sh """
nvm use env.NODE_VERSION
npm ci
"""
}
vars/helmUp.groovy
def call(String release, String path) {
sh """
helm upgrade $release $path
"""
}
Jenkinsfile
@Library('common-steps') _
pipeline {
stages('Build') {
npm.installModules()
npm.test()
npm.build()
docker.build("my-app:release").push()
}
}
Jenkinsfile
@Library('pipelines') _
nodeJsApp()
kafkaTopic.kt
fun BuildSteps.kafkaTopic(topic: String) {
script {
name = "Create kafka topic"
scriptContent = """
kafka-topics --create --topic $topic
""".trimIndent()
}
}
settings.kts
import util.kafka.kafkaTopic
project {
buildType {
steps {
kafkaTopic("my-topic")
}
}
}
void should_make_kittens_cute() {
binding.setVariable('AWESOMENESS', '1')
def script = loadScript("Jenkinsfile")
script.execute()
}
@Test
fun `shell scripts have valid shebang`() {
allSteps.forEach {
if (it is ScriptBuildStep) {
assertThat(it.scriptContent,
startsWith("#!/bin/bash"))
}
}
}
object MasterVCS: GitVcsRoot({
name = "Master branch VCS root"
url = "git@server:apps/contact-api.git"
branch = "master"
branchSpec = "+:refs/heads/*"
authMethod = defaultPrivateKey {
}
})
fun Project.vcs(repository: String) {
vcsRoot(GitVcsRoot {
id("Master".toId(this@Project.toString()))
url = "git@server:$repository.git"
// ...
})
}
project {
vcs("apps/contact-api")
}
object PullRequestTrigger : VcsTrigger({
triggerRules = """
+:**
-:comment=^Merge pull request #.*
""".trimIndent()
branchFilter = """
+:*
-:
-:master
""".trimIndent()
})
buildType {
triggers {
PullRequestTrigger()
}
}
buildType {
triggers {
trigger(PullRequestTrigger())
}
}
buildType {
script {
scriptContent = """
IMAGE=%env.DOCKER_REGISTRY%/%env.DOCKER_IMAGE%
docker run ${'$'}IMAGE
""".trimIndent()
}
}
mvn test
mvn teamcity-configs:generate
> Changes from VCS are applied to project settings
buildType {
params {
param(
"env.DOCKER_IMAGE",
"%dep.ContactBuild.env.DOCKER_IMAGE%"
)
}
}
> +IMAGE="registry:5050/"
> docker: invalid reference format.
val vars = injectVars.joinToString("\n") {
"echo \\\"##teamcity[setParameter name='$it' value='%$it%']\\\""
}
scriptContent = """
#!/bin/bash
echo "$vars" > inject_file.sh
""".trimIndent()
password(
name = "env.DB_PASSWORD",
value = "credentialsJSON:112e157f-76e8-4e7d-b12f-33f35103eb15"
)
project {
subProject {
params {
password("env.DB_PASSWORD", "credentialsJSON:...")
}
}
subProject {
// Nope
}
}
project {
params {
password("env.DB_PASSWORD", "credentialsJSON:...")
}
subProject {
}
subProject {
// OK
}
}
project {
params {
password("env.DB_PASSWORD", "%vault:qa/db!password%")
}
}
fun apiBuild(parent: Project, apiTests: Boolean,
pr: Boolean) = BuildType({
steps {
if (pr) {
getJiraTaskNumber()
}
if (apiTests) {
e2eTest("")
}
}
})
Джек:
Написал сервис, ему нужна база данных и еще бы миграции накатить
Thingy:
Билды настроил, базу создал, миграции накатил
product("CRM") {
services {
backend {
name = "file-api"
resources { /* ... */ }
}
}
}
flywayMigrations {
db {
name = "file_storage"
schema = "files"
}
}
swagger {
name = "file-storage-service-swagger"
dockerFile = "swagger-public.Dockerfile"
}
Product
, Service
:arrow_right: Project
, BuildType
fun toProject(p: Product) = Project({
id = AbsoluteId("${p.id}")
p.services.each {
buildType {
// ...
}
}
})
backend {
name = "contact-api"
resources {
postgres {
name = "%env.CONTACT_API_DB_NAME%"
}
}
}
class Service(val name: String)
class Postgres(val dbName: String)
@DslMarker
annotation class TeamCityDslMarker
@TeamCityDslMarker
class Service(val name: String)
@TeamCityDslMarker
class Postgres(val dbName: String)
Custom DSL
Custom DSL Adapter
Helper Steps
TeamCity DSL
DSL.register {
deploy {
k8s()
}
teamcity {
v2017_2()
}
product(CRM)
}
Андрей Ермаков, Tinkoff.ru
AntiYAML: DSL is the new black