ЁЯзй рдзрдбрд╛ 02 тАФ pipeline рдЪреА рд░рдЪрдирд╛: рдШрдВрдЯрд╛, рдбреЗрд╕реНрдХ, рдХрд╛рд░рдХреВрди
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 02 ┬╖ рдорд╛рдЧреЗ: lesson-01-why-cicd ┬╖ рдкреБрдвреЗ: lesson-03-first-pipeline
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбрд╛ 01, рдЖрдгрд┐ рдХреЛрдгрддреАрд╣реА pipeline file рд╡рд╛рдЪрд╛рдпрд▓рд╛ рд▓рд╛рдЧрдгрд╛рд░реЗ рд╢рдмреНрдж тАФ рдЖрдгрд┐ рдкрд╣рд┐рд▓реА рдЦрд░реА pipeline, рд╡рд░рдкрд╛рд╕реВрди рдЦрд╛рд▓рдкрд░реНрдпрдВрдд рд╡рд╛рдЪрд▓реЗрд▓реА, рдЕрдЬреВрди рди рдЪрд╛рд▓рд╡рддрд╛. рдЦрд▒реНрдпрд╛ files:
- .github/workflows/ci.yml тАФ рддрдкрд╛рд╕рдгреА рдбреЗрд╕реНрдХрдЪрд╛ рдорд╛рд░реНрдЧ: рдПрдХ trigger, рддреАрди рджрд┐рд╢рд╛рдВрдирд╛ рдкрд╕рд░рдгрд╛рд░рд╛ рдПрдХ job, рд╕рд╣рд╛ steps (рдпрд╛ рдзрдбреНрдпрд╛рдд рдлрдХреНрдд рд╡рд╛рдЪрд╛рдпрдЪреЗ)
- .circleci/config.yml, .gitlab-ci.yml, Jenkinsfile тАФ рддреЛрдЪ рдорд╛рд░реНрдЧ рдЗрддрд░ рддреАрди рдмреЛрд▓реАрдВрдордзреНрдпреЗ; рдЖрдЬ рддреБрдореНрд╣реА рдлрдХреНрдд рддреНрдпрд╛рдВрдЪреЗ рд╢рдмреНрдж рдЙрд╕рдиреЗ рдШреЗрддрд╛
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рд╢рд╛рд│реЗрдЪреНрдпрд╛ mailroom ЁЯУо рдордзреНрдпреЗ рдЬрд╛ рдЖрдгрд┐ courier рдЪреА рдПрдХ рдлреЗрд░реА рдкрд╛рд╣рд╛. рдПрдХ рдШрдВрдЯрд╛ рд╡рд╛рдЬрддреЗ ЁЯФФ тАФ рдирд╡реЗ рдкрд╛рдХреАрдЯ рдЖрд▓реЗ. рддреЛ рдореНрд╣рдгрдЬреЗ trigger: push, pull request, timer, рдХрд┐рдВрд╡рд╛ рдХреЛрдгреАрддрд░реА рдмрдЯрдг рджрд╛рдмрдгреЗ.
рдШрдВрдЯрд╛ рдЕрдиреЗрдХ рдбреЗрд╕реНрдХ ЁЯкС рдЕрд╕рд▓реЗрд▓рд╛ рдПрдХ рдорд╛рд░реНрдЧ рд╕реБрд░реВ рдХрд░рддреЗ. рдкреНрд░рддреНрдпреЗрдХ рдбреЗрд╕реНрдХрд╡рд░ рдПрдХ рдХрд╛рд░рдХреВрди рдЫреЛрдЯреНрдпрд╛ рдХрд╛рдорд╛рдВрдЪреА рдпрд╛рджреА рдХреНрд░рдорд╛рдиреЗ рдХрд░рддреЛ: рдкрд╛рдХреАрдЯ рдЙрдШрдбрдгреЗ, рдкреЗрдиреНрд╕рд┐рд▓ рддрд╛рд╕рдгреЗ, рдЙрддреНрддрд░реЗ рддрдкрд╛рд╕рдгреЗ, рдХрд╛рдЧрджрд╛рд╡рд░ рд╢рд┐рдХреНрдХрд╛ рдорд╛рд░рдгреЗ. рдорд╛рд░реНрдЧ рдореНрд╣рдгрдЬреЗ workflow, рдкреНрд░рддреНрдпреЗрдХ рдбреЗрд╕реНрдХ рдПрдХ job, рдкреНрд░рддреНрдпреЗрдХ рдХрд╛рдо рдПрдХ step, рдЖрдгрд┐ рдХрд╛рд░рдХреВрди рдореНрд╣рдгрдЬреЗ runner тАФ рдкреНрд░рддреНрдпрдХреНрд╖ рдХрд╛рдо рдХрд░рдгрд╛рд░реА рдорд╢реАрди.
рдбреЗрд╕реНрдХ рд╡реЗрдЧрд╡реЗрдЧрд│реНрдпрд╛ рдЦреЛрд▓реНрдпрд╛рдВрдордзреНрдпреЗ рдЖрд╣реЗрдд, рдореНрд╣рдгреВрди рддреНрдпрд╛рдВрдЪреНрдпрд╛рдордзреНрдпреЗ рдЬреЗ рдХрд╛рд╣реА рдЬрд╛рдпрдЪреЗ рддреЗ рдкрд╛рдХрд┐рдЯрд╛рдд ЁЯУО рдЬрд╛рддреЗ (artifact). рддрд╛рд╕рд▓реЗрд▓реНрдпрд╛ рдкреЗрдиреНрд╕рд┐рд▓реАрдВрдЪрд╛ рдбреНрд░реЙрд╡рд░ ЁЯЧДя╕П рдлреЗрд▒реНрдпрд╛рдВрдордзреНрдпреЗ рд╡реЗрд│ рд╡рд╛рдЪрд╡рддреЛ (cache). рдХрд╛рд╣реА рдбреЗрд╕реНрдХрдирд╛ рдмреЕрдЬ ЁЯкк рд▓рд╛рдЧрддреЛ (secret). рд╕реВрдЪрдирд╛ рд╢реЗрд╡рдЯреА рд╕рд░рд╛рд╡ рдлрд▓рдХрд╛рд╡рд░ рдХрд┐рдВрд╡рд╛ рдореБрдЦреНрдп рдлрд▓рдХрд╛рд╡рд░ ЁЯУМ рд▓рд╛рдЧрддрд╛рдд (environments), рдЖрдгрд┐ рдореБрдЦреНрдп рдлрд▓рдХрд╛рдЖрдзреА рдкреНрд░рд╛рдЪрд╛рд░реНрдпрд╛рдВрдЪреА рд╕рд╣реА ЁЯЫС рдпреЗрддреЗ (approval). рдЪрд╛рд░ courier рдХрдВрдкрдиреНрдпрд╛ рд╣реАрдЪ mailroom рд╡рд╛рдкрд░рддрд╛рдд тАФ рддреНрдпрд╛рдВрдЪреНрдпрд╛ рдлреЙрд░реНрдорд╡рд░ рд╡реЗрдЧрд│реЗ рд╢рдмреНрдж рдЕрд╕рддрд╛рдд, рдЖрдгрд┐ рдЖрдЬрдЪрд╛ рддреЛрдЪ рдПрдХрдореЗрд╡ рдЕрд╡рдШрдб рднрд╛рдЧ рдЖрд╣реЗ.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart TB
bell["ЁЯФФ trigger<br/>on push and pull_request"]
wf["ЁЯУо workflow<br/>ci.yml"]
j1["ЁЯкС job test node 20"]
j2["ЁЯкС job test node 22"]
j3["ЁЯкС job test node 24"]
s["ЁЯУЭ steps in order<br/>checkout тЖТ setup-node тЖТ cache тЖТ prepare тЖТ test тЖТ upload"]
r["ЁЯзСтАНЁЯТ╝ runner<br/>ubuntu-latest, a fresh machine per job"]
bell -->|"1 rings"| wf
wf -->|"2 fans out"| j1
wf -->|"2 fans out"| j2
wf -->|"2 fans out"| j3
j2 -->|"3 one after another"| s
s -->|"4 executed by"| r
тЭУ рдХрд╛рдп
- Trigger / event = run рд╕реБрд░реВ рдХрд░рдгрд╛рд░реА рдЧреЛрд╖реНрдЯ: push, pull request,
schedule, рд╣рд╛рддрд╛рдиреЗ рджрд╛рдмрд╛рдпрдЪреЗ рдмрдЯрдг (
ship.ymlрдордзрд▓реЗworkflow_dispatch). - Workflow = рдПрдХ file, рдПрдХ рдорд╛рд░реНрдЧ (рдЗрдереЗ
ci.yml,ship.yml,deploy.yml). - Job = рд╕реНрд╡рддрдГрдЪреНрдпрд╛ рдирд╡реНрдпрд╛ runner рд╡рд░рдЪреЗ рдХрд╛рдорд╛рдЪреЗ рдПрдХрдХ. рдПрдХ job рджреБрд╕рд▒реНрдпрд╛рдЪреА
needs:рдХрд░рдд рдирд╛рд╣реА рддреЛрдкрд░реНрдпрдВрдд jobs parallel рдЪрд╛рд▓рддрд╛рдд. - Step = рдПрдХ command (
run:) рдХрд┐рдВрд╡рд╛ рдПрдХ рдкреБрдиреНрд╣рд╛ рд╡рд╛рдкрд░рддрд╛ рдпреЗрдгрд╛рд░реА action (uses:). Steps job рдЪреА рдорд╢реАрди рд╡рд╛рдЯреВрди рдШреЗрддрд╛рдд рдЖрдгрд┐ рдХреНрд░рдорд╛рдиреЗ рдЪрд╛рд▓рддрд╛рдд. - Runner = job рдЪрд╛рд▓рд╡рдгрд╛рд░реА рдорд╢реАрди, vendor рдЪреА рдХрд┐рдВрд╡рд╛ рддреБрдордЪреА рд╕реНрд╡рддрдГрдЪреА.
- Artifact ЁЯУО = рдорд╛рдгрд╕рд╛рд╕рд╛рдареА рдХрд┐рдВрд╡рд╛ рдирдВрддрд░рдЪреНрдпрд╛ job рд╕рд╛рдареА run рдордзреВрди рдареЗрд╡рд▓реЗрд▓реА file (рдзрдбрд╛
05). Cache ЁЯЧДя╕П = inputs рдЪреНрдпрд╛ hash рд╡рд░реВрди key рджрд┐рд▓реЗрд▓рд╛ рд╡реЗрдЧ рд╡рд╛рдврд╡рдгрд╛рд░рд╛ рд╕рд╛рдард╛ (рдзрдбрд╛ 04).
Secret ЁЯкк = encrypted рд╕реНрд╡рд░реВрдкрд╛рдд рдареЗрд╡рд▓реЗрд▓реЗ рдореВрд▓реНрдп, logs рдордзреНрдпреЗ рд▓рдкрд╡рд▓реЗрд▓реЗ (рдзрдбрд╛ 06).
Environment ЁЯУМ =
stagingрд╕рд╛рд░рдЦреЗ рд╕реНрд╡рддрдГрдЪреЗ рдирд┐рдпрдо рдЕрд╕рд▓реЗрд▓реЗ рдирд╛рд╡ рджрд┐рд▓реЗрд▓реЗ target; approval ЁЯЫС = рддреНрдпрд╛рдЪреНрдпрд╛рдкреБрдврдЪреЗ рдорд╛рдгрд╕рд╛рдЪреЗ рдлрд╛рдЯрдХ (рдзрдбрд╛ 08).
ЁЯза рдЪрд╛рд░ рдмреЛрд▓реА, рдПрдХрдЪ рд╢рдмреНрджрд╕рдВрдЧреНрд░рд╣
| ЁЯУо mailroom рдордзреНрдпреЗ | GitHub Actions | CircleCI | GitLab CI | Jenkins |
|---|---|---|---|---|
| рд╕рдВрдкреВрд░реНрдг рдорд╛рд░реНрдЧ | workflow | workflow | pipeline | pipeline |
| рдПрдХ рдбреЗрд╕реНрдХ (рд╕реНрд╡рддрдГрдЪреА рдорд╢реАрди) | job | job | stage + job | stage |
| рдбреЗрд╕реНрдХрд╡рд░рдЪреЗ рдПрдХ рдХрд╛рдо | step | step | script line | step |
| рдХрд╛рд░рдХреВрди (рдорд╢реАрди) | runner | executor | runner | agent |
GitLab рдЪреЗ stages рдХреНрд░рдорд╛рдиреЗ рдЪрд╛рд▓рддрд╛рдд, рддреНрдпрд╛рдВрдЪреЗ jobs рд╢реЗрдЬрд╛рд░реА рд╢реЗрдЬрд╛рд░реА; Jenkins рдЪрд╛ stage job рдЪреНрдпрд╛ рдЬрд╡рд│рдЪрд╛ рдЖрд╣реЗ.
ЁЯдФ рдХрд╛
рдЗрдереВрди рдкреБрдвреЗ рдкреНрд░рддреНрдпреЗрдХ run page, error message рдЖрдгрд┐ doc рд╣реЗрдЪ рд╢рдмреНрдж рд╡рд╛рдкрд░рддреЗ, рдЖрдгрд┐ рдЪреБрдХреАрдЪреНрдпрд╛ рдкрд╛рддрд│реАрд╡рд░рдЪреА рдЪреВрдХ рдорд╣рд╛рдЧ рдкрдбрддреЗ: step 3 рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рд▓реЗрд▓реА file step 4 рдордзреНрдпреЗ рдЕрд╕рддреЗ рдкрдг рдкреБрдврдЪреНрдпрд╛ job рдордзреНрдпреЗ рдирд╕рддреЗ, рдХрд╛рд░рдг рддреЛ job рд╡реЗрдЧрд│реА рдорд╢реАрди рдЖрд╣реЗ. рдорд╢реАрдирд╕рд╛рдареА "job" рдЖрдгрд┐ command рд╕рд╛рдареА "step" рдореНрд╣рдгрд╛, рдХреА рдзрдбреЗ 04тАУ07 рдордзрд▓реНрдпрд╛ рдЧреЛрдВрдзрд│рд╛рдд рдЯрд╛рдХрдгрд╛рд▒реНрдпрд╛ failures рдЪрд╛ рдПрдХ рд╕рдВрдкреВрд░реНрдг рд╡рд░реНрдЧ рдирд╛рд╣реАрд╕рд╛ рд╣реЛрддреЛ. рд╣реЗ рд╢рдмреНрдж рдкреБрдврдЪреНрдпрд╛ mailroom рдордзреНрдпреЗрд╣реА рдЪрд╛рд▓рддрд╛рдд, рддрд┐рдЪрд╛ logo рдХреЛрдгрддрд╛рд╣реА рдЕрд╕реЛ тАФ рдпрд╛ рдХреЛрд░реНрд╕рдЪреЗ рдЪрд╛рд░-tab pipeline page рдЖрдгрд┐ Docker рд╢рд╛рд│реЗрдЪрд╛ рдзрдбрд╛ 12 рдкрд╛рд╣рд╛.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
ci.yml рд╡рд░рдкрд╛рд╕реВрди рд╡рд╛рдЪрд╛. рдЖрдзреА name рдЖрдгрд┐ рдШрдВрдЯрд╛ тАФ рджреЛрди events, filters рдирд╛рд╣реАрдд, рдореНрд╣рдгреВрди рдХреЛрдгрддреАрд╣реА branch рддреА рд╡рд╛рдЬрд╡рддреЗ:
name: тЬЕ ci
on:
push:
pull_request:
рд╕рдВрдкреВрд░реНрдг workflow рд╕рд╛рдареА рджреЛрди settings: permissions: run рдЪрд╛ token рд▓рд╣рд╛рди рдХрд░рддреЗ (рдзрдбрд╛
06); concurrency: рдирд╡реНрдпрд╛ push рд▓рд╛ рдЬреБрдирд╛ run рд░рджреНрдж рдХрд░реВ рджреЗрддреЗ (рдзрдбрд╛ 03):
permissions:
contents: read # lesson 06: the job's own token gets the least it needs
concurrency: # a newer push cancels the older run of the same branch
group: ci-${{ github.ref }}
cancel-in-progress: true
рдордЧ jobs: рдПрдХрдЪ, test, рдЬреНрдпрд╛рд▓рд╛ matrix рддреАрди рдбреЗрд╕реНрдХрдордзреНрдпреЗ рдмрджрд▓рддреЗ
(рддреАрди рдлреВрдЯрдкрдЯреНрдЯреНрдпрд╛ ЁЯУП, рдзрдбрд╛ 04), рдкреНрд░рддреНрдпреЗрдХ рдирд╡реНрдпрд╛ runner рд╡рд░, рдкреНрд░рддреНрдпреЗрдХ app/ рдордзреНрдпреЗ:
jobs:
test:
name: test (node ${{ matrix.node }})
runs-on: ubuntu-latest
strategy:
fail-fast: false # lesson 04: let all three rulers finish, even if one fails
matrix:
node: [20, 22, 24] # three rulers тАФ the same homework checked three ways
defaults:
run:
working-directory: app
рд╢реЗрд╡рдЯреА steps, рдХреНрд░рдорд╛рдиреЗ. uses: рдкреНрд░рдХрд╛рд╢рд┐рдд action рдЙрд╕рдиреА рдШреЗрддреЗ; run:
shell command рдЪрд╛рд▓рд╡рддреЗ. рдкрд╣рд┐рд▓реНрдпрд╛ рджреЛрдиреНрд╣реА uses: рдЖрд╣реЗрдд:
steps:
- name: ЁЯУе checkout
uses: actions/checkout@v4
- name: ЁЯЯв node ${{ matrix.node }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
рдЖрдгрдЦреА рдЪрд╛рд░ рдпреЗрддрд╛рдд: cache step (id: cache), if: steps.cache.outputs.cache-hit != 'true'
рдиреЗ рд░рд╛рдЦрд▓реЗрд▓реА prepare step (рджреЛрдиреНрд╣реА рдзрдбрд╛ 04), ЁЯзк test step (рдзрдбрд╛ 03 рддреА рдЙрджреНрдзреГрдд рдХрд░рддреЛ),
рдЖрдгрд┐ if: always() рдЕрд╕рд▓реЗрд▓реА upload step (рдзрдбрд╛ 05).
рд╕реНрд╡рддрдГрдЪреА file рд▓рд┐рд╣рд┐рдгреНрдпрд╛рдЖрдзреА YAML рдЪреЗ рджреЛрди рд╕рд╛рдкрд│реЗ:
- YAML 1.1 рдордзреНрдпреЗ
on:рд╣рд╛ boolean рдЖрд╣реЗ. GitHub рддреЛonkey рдореНрд╣рдгреВрди рд╡рд╛рдЪрддреЛ, рдкрдг рдХрд╛рд╣реА linters рдЖрдгрд┐ libraries (yamllint рдЪрд╛ truthy рдирд┐рдпрдо, PyYAML, Ruby рдЪреЗ Psych) рддреНрдпрд╛рдЪреЗtrueрдХрд░рддрд╛рдд. рдПрдЦрд╛рджреЗ tool рддрдХреНрд░рд╛рд░ рдХрд░рдд рдЕрд╕реЗрд▓, рддрд░"on":рд▓рд┐рд╣рд╛. - Indentation рдЖрдгрд┐ quoting рдЕрд░реНрде рдард░рд╡рддрд╛рдд. Test step
run: >рд╡рд╛рдкрд░рддреЗ, рдПрдХ folded block тАФ рддреАрди рдУрд│реА рдПрдХ command рдмрдирддрд╛рдд;run: |рддреАрди рдЪрд╛рд▓рд╡реЗрд▓. Outputs strings рдЕрд╕рддрд╛рдд, рдореНрд╣рдгреВрди рдЦрд░реА filecache-hitрдЪреА рддреБрд▓рдирд╛'true'рд╢реА рдХрд░рддреЗ, quotes рдирд╕рд▓реЗрд▓реНрдпрд╛trueрд╢реА рдирд╛рд╣реА. рдПрдХ рдЬрд╛рдЧрд╛ рдЬрд╛рд╕реНрдд рдбрд╛рд╡реАрдХрдбреЗ рдЕрд╕рд▓реЗрд▓рд╛ list item list рд╕рдВрдкрд╡рддреЛ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
# 0) fork and clone (you will need this fork from lesson 03 onward)
gh repo fork BaluRaut/learn-cicd-school --clone && cd learn-cicd-school
# 1) read the file and point at each part of the anatomy
cat .github/workflows/ci.yml
grep -nE '^(on|permissions|concurrency|jobs):|runs-on|matrix:|- name:|uses:|run:' .github/workflows/ci.yml
ruby -ryaml -e 'p YAML.load_file(".github/workflows/ci.yml").keys' # optional, needs ruby: ["name", true, тАж] тАФ the on: gotcha
# 2) ask GitHub which workflows the fork knows about (enable Actions once in the fork's Actions tab if empty)
gh workflow list
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- "step" рдореНрд╣рдгрд╛рдпрдЪреЗ рдЕрд╕рддрд╛рдирд╛ "job" рдореНрд╣рдгрдгреЗ тАФ job рд▓рд╛ рдирд╡реА рдорд╢реАрди рдорд┐рд│рддреЗ, step рдПрдХрдЪ рдорд╢реАрди рд╡рд╛рдЯреВрди рдШреЗрддреЗ; step 3 рдордзреНрдпреЗ рдмрдирд▓реЗрд▓реНрдпрд╛ files step 4 рдордзреНрдпреЗ рдЕрд╕рддрд╛рдд рдкрдг рдкреБрдврдЪреНрдпрд╛ job рдордзреНрдпреЗ рдирд╕рддрд╛рдд
steps:рд▓рд╛runs-on:рдЦрд╛рд▓реА indent рдХрд░рдгреЗ (рдХрд┐рдВрд╡рд╛- name:рдПрдХ рдЬрд╛рдЧрд╛ рдЪреБрдХреАрдЪрд╛) тАФ YAML рдЕрдиреЗрдХ рдЪреБрдХреАрдЪреЗ рдЖрдХрд╛рд░ рдЧрдкрдЪреВрдк рд╕реНрд╡реАрдХрд╛рд░рддреЗ рдЖрдгрд┐ error рддреБрдордЪреНрдпрд╛ editor рдХрдбреВрди рдирд╛рд╣реА рддрд░ GitHub рдХрдбреВрди рдпреЗрддреЛ- output рдЪреА рддреБрд▓рдирд╛ quotes рдирд╕рд▓реЗрд▓реНрдпрд╛
trueрд╢реА рдХрд░рдгреЗ тАФ step outputs strings рдЕрд╕рддрд╛рдд, рдореНрд╣рдгреВрди рдЦрд░реА cache step рдХрд░рддреЗ рддрд╢реАрдЪ'true'рд╢реА рддреБрд▓рдирд╛ рдХрд░рд╛
тПня╕П рдкреБрдвреЗ
рд╡рд╛рдЪрди рдкреБрд░реЗ. Fork рдХрд░рд╛, push рдХрд░рд╛, рдЖрдгрд┐ рддрдкрд╛рд╕рдгреА рдбреЗрд╕реНрдХрд▓рд╛ тЬЕ рдЪрд╛ рд╢рд┐рдХреНрдХрд╛ рдорд╛рд░рддрд╛рдирд╛ рдкрд╛рд╣рд╛ тАФ рдордЧ рдореБрджреНрджрд╛рдо тЭМ рдЪрд╛ рд╢рд┐рдХреНрдХрд╛ рдорд╛рд░рд╛рдпрд▓рд╛ рд▓рд╛рд╡рд╛, рдореНрд╣рдгрдЬреЗ рд╡реЗрд│ рдпреЗрдгреНрдпрд╛рдЖрдзреАрдЪ рд▓рд╛рд▓ рдХрд╕реЗ рджрд┐рд╕рддреЗ рддреЗ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд│реЗрд▓.
git checkout lesson-03-first-pipeline