āĻ•ā§āĻŦāĻžāĻ°āĻ¨ā§‡āĻŸāĻ¸ā§‡āĻ° āĻ¸āĻžāĻĨā§‡ āĻ—āĻŋāĻŸāĻ˛ā§āĻ¯āĻžāĻŦ āĻ¸āĻŋāĻ†āĻ‡-āĻ JUnit

āĻ¯āĻĻāĻŋāĻ“ āĻ¸āĻŦāĻžāĻ‡ āĻ­āĻžāĻ˛āĻ­āĻžāĻŦā§‡ āĻœāĻžāĻ¨ā§‡ āĻ¯ā§‡ āĻ†āĻĒāĻ¨āĻžāĻ° āĻ¸āĻĢā§āĻŸāĻ“āĻ¯āĻŧā§āĻ¯āĻžāĻ° āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻ•āĻ°āĻž āĻ—ā§āĻ°ā§āĻ¤ā§āĻŦāĻĒā§‚āĻ°ā§āĻŖ āĻāĻŦāĻ‚ āĻĒā§āĻ°āĻ¯āĻŧā§‹āĻœāĻ¨ā§€āĻ¯āĻŧ, āĻāĻŦāĻ‚ āĻ…āĻ¨ā§‡āĻ•ā§‡āĻ‡ āĻāĻŸāĻŋ āĻ¸ā§āĻŦāĻ¯āĻŧāĻ‚āĻ•ā§āĻ°āĻŋāĻ¯āĻŧāĻ­āĻžāĻŦā§‡ āĻĻā§€āĻ°ā§āĻ˜āĻĻāĻŋāĻ¨ āĻ§āĻ°ā§‡ āĻ•āĻ°ā§‡ āĻ†āĻ¸āĻ›ā§‡, āĻšāĻžāĻŦāĻ°ā§‡āĻ° āĻŦāĻŋāĻļāĻžāĻ˛āĻ¤āĻžāĻ¯āĻŧ āĻāĻ‡ āĻœāĻžāĻ¤ā§€āĻ¯āĻŧ āĻœāĻ¨āĻĒā§āĻ°āĻŋāĻ¯āĻŧ āĻĒāĻŖā§āĻ¯āĻ—ā§āĻ˛āĻŋāĻ° āĻ¸āĻ‚āĻŽāĻŋāĻļā§āĻ°āĻŖ āĻ¸ā§‡āĻŸ āĻ†āĻĒ āĻ•āĻ°āĻžāĻ° āĻœāĻ¨ā§āĻ¯ āĻāĻ•āĻŸāĻŋ āĻ°ā§‡āĻ¸āĻŋāĻĒāĻŋ āĻ›āĻŋāĻ˛ āĻ¨āĻžāĨ¤ (āĻ†āĻŽāĻžāĻĻā§‡āĻ° āĻĒā§āĻ°āĻŋāĻ¯āĻŧ) GitLab āĻāĻŦāĻ‚ JUnit āĻšāĻŋāĻ¸āĻžāĻŦā§‡ āĻāĻ‡ āĻ•ā§āĻ˛ā§āĻ™ā§āĻ—āĻŋāĨ¤ āĻāĻ° āĻāĻ‡ āĻļā§‚āĻ¨ā§āĻ¯āĻ¸ā§āĻĨāĻžāĻ¨ āĻĒā§‚āĻ°āĻŖ āĻ•āĻ°āĻž āĻ¯āĻžāĻ•!

āĻ•ā§āĻŦāĻžāĻ°āĻ¨ā§‡āĻŸāĻ¸ā§‡āĻ° āĻ¸āĻžāĻĨā§‡ āĻ—āĻŋāĻŸāĻ˛ā§āĻ¯āĻžāĻŦ āĻ¸āĻŋāĻ†āĻ‡-āĻ JUnit

āĻĒāĻ°āĻŋāĻšāĻžāĻ¯āĻŧāĻ•

āĻĒā§āĻ°āĻĨāĻŽā§‡, āĻ†āĻŽāĻžāĻ•ā§‡ āĻ•āĻŋāĻ›ā§ āĻĒā§āĻ°āĻ¸āĻ™ā§āĻ— āĻĻāĻŋāĻ¤ā§‡ āĻĻāĻŋāĻ¨:

  • āĻ¯ā§‡āĻšā§‡āĻ¤ā§ āĻ†āĻŽāĻžāĻĻā§‡āĻ° āĻ¸āĻŽāĻ¸ā§āĻ¤ āĻ…ā§āĻ¯āĻžāĻĒā§āĻ˛āĻŋāĻ•ā§‡āĻļāĻ¨ āĻ•ā§āĻŦāĻžāĻ°āĻ¨ā§‡āĻŸā§‡ āĻšāĻ˛ā§‡, āĻ¤āĻžāĻ‡ āĻ†āĻŽāĻ°āĻž āĻ‰āĻĒāĻ¯ā§āĻ•ā§āĻ¤ āĻĒāĻ°āĻŋāĻ•āĻžāĻ āĻžāĻŽā§‹āĻ¤ā§‡ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻšāĻžāĻ˛āĻžāĻ¨ā§‹āĻ° āĻ•āĻĨāĻž āĻŦāĻŋāĻŦā§‡āĻšāĻ¨āĻž āĻ•āĻ°āĻŦāĨ¤
  • āĻ¸āĻŽāĻžāĻŦā§‡āĻļ āĻāĻŦāĻ‚ āĻ¸ā§āĻĨāĻžāĻĒāĻ¨āĻžāĻ° āĻœāĻ¨ā§āĻ¯ āĻ†āĻŽāĻ°āĻž āĻŦā§āĻ¯āĻŦāĻšāĻžāĻ° āĻ•āĻ°āĻŋ werf (āĻ…āĻŦāĻ•āĻžāĻ āĻžāĻŽā§‹āĻ—āĻ¤ āĻ‰āĻĒāĻžāĻĻāĻžāĻ¨āĻ—ā§āĻ˛āĻŋāĻ° āĻĒāĻ°āĻŋāĻĒā§āĻ°ā§‡āĻ•ā§āĻˇāĻŋāĻ¤ā§‡, āĻāĻŸāĻŋ āĻ¸ā§āĻŦāĻ¯āĻŧāĻ‚āĻ•ā§āĻ°āĻŋāĻ¯āĻŧāĻ­āĻžāĻŦā§‡ āĻŦā§‹āĻāĻžāĻ¯āĻŧ āĻ¯ā§‡ āĻšā§‡āĻ˛āĻŽ āĻœāĻĄāĻŧāĻŋāĻ¤)āĨ¤
  • āĻ†āĻŽāĻŋ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻžāĻ° āĻĒā§āĻ°āĻ•ā§ƒāĻ¤ āĻ¸ā§ƒāĻˇā§āĻŸāĻŋāĻ° āĻŦāĻŋāĻŦāĻ°āĻŖā§‡ āĻ¯āĻžāĻŦ āĻ¨āĻž: āĻ†āĻŽāĻžāĻĻā§‡āĻ° āĻ•ā§āĻˇā§‡āĻ¤ā§āĻ°ā§‡, āĻ•ā§āĻ˛āĻžāĻ¯āĻŧā§‡āĻ¨ā§āĻŸ āĻ¨āĻŋāĻœā§‡āĻ‡ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻžāĻ—ā§āĻ˛āĻŋ āĻ˛ā§‡āĻ–ā§‡āĻ¨ āĻāĻŦāĻ‚ āĻ†āĻŽāĻ°āĻž āĻ•ā§‡āĻŦāĻ˛ āĻ¤āĻžāĻĻā§‡āĻ° āĻ˛āĻžā§āĻš (āĻāĻŦāĻ‚ āĻŽāĻžāĻ°ā§āĻœ āĻ…āĻ¨ā§āĻ°ā§‹āĻ§ā§‡ āĻāĻ•āĻŸāĻŋ āĻ¸āĻ‚āĻļā§āĻ˛āĻŋāĻˇā§āĻŸ āĻĒā§āĻ°āĻ¤āĻŋāĻŦā§‡āĻĻāĻ¨ā§‡āĻ° āĻ‰āĻĒāĻ¸ā§āĻĨāĻŋāĻ¤āĻŋ) āĻ¨āĻŋāĻļā§āĻšāĻŋāĻ¤ āĻ•āĻ°āĻŋāĨ¤


āĻ•āĻ°ā§āĻŽā§‡āĻ° āĻ¸āĻžāĻ§āĻžāĻ°āĻŖ āĻ•ā§āĻ°āĻŽ āĻ•ā§‡āĻŽāĻ¨ āĻšāĻŦā§‡?

  1. āĻ…ā§āĻ¯āĻžāĻĒā§āĻ˛āĻŋāĻ•ā§‡āĻļāĻ¨ āĻ¤ā§ˆāĻ°āĻŋ āĻ•āĻ°āĻž - āĻ†āĻŽāĻ°āĻž āĻāĻ‡ āĻĒāĻ°ā§āĻ¯āĻžāĻ¯āĻŧā§‡āĻ° āĻŦāĻ°ā§āĻŖāĻ¨āĻž āĻŦāĻžāĻĻ āĻĻā§‡āĻŦāĨ¤
  2. Kubernetes āĻ•ā§āĻ˛āĻžāĻ¸ā§āĻŸāĻžāĻ°ā§‡āĻ° āĻāĻ•āĻŸāĻŋ āĻĒā§ƒāĻĨāĻ• āĻ¨āĻžāĻŽāĻ¸ā§āĻĨāĻžāĻ¨ā§‡ āĻ…ā§āĻ¯āĻžāĻĒā§āĻ˛āĻŋāĻ•ā§‡āĻļāĻ¨āĻŸāĻŋ āĻ¸ā§āĻĨāĻžāĻĒāĻ¨ āĻ•āĻ°ā§āĻ¨ āĻāĻŦāĻ‚ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻļā§āĻ°ā§ āĻ•āĻ°ā§āĻ¨āĨ¤
  3. āĻ†āĻ°ā§āĻŸāĻŋāĻĢā§āĻ¯āĻžāĻ•ā§āĻŸ āĻ…āĻ¨ā§āĻ¸āĻ¨ā§āĻ§āĻžāĻ¨ āĻ•āĻ°āĻž āĻāĻŦāĻ‚ GitLab āĻāĻ° āĻ¸āĻžāĻĨā§‡ JUnit āĻ°āĻŋāĻĒā§‹āĻ°ā§āĻŸ āĻĒāĻžāĻ°ā§āĻ¸ āĻ•āĻ°āĻžāĨ¤
  4. āĻĒā§‚āĻ°ā§āĻŦā§‡ āĻ¤ā§ˆāĻ°āĻŋ āĻāĻ•āĻŸāĻŋ āĻ¨āĻžāĻŽāĻ¸ā§āĻĨāĻžāĻ¨ āĻŽā§āĻ›ā§‡ āĻĢā§‡āĻ˛āĻž āĻšāĻšā§āĻ›ā§‡āĨ¤

āĻāĻ–āĻ¨ - āĻŦāĻžāĻ¸ā§āĻ¤āĻŦāĻžāĻ¯āĻŧāĻ¨!

āĻ¸āĻŽāĻ¨ā§āĻŦāĻ¯āĻŧ

āĻ—āĻŋāĻŸāĻ˛ā§āĻ¯āĻžāĻŦ āĻ¸āĻŋāĻ†āĻ‡

āĻāĻ•āĻŸāĻŋ āĻ–āĻŖā§āĻĄ āĻĻāĻŋāĻ¯āĻŧā§‡ āĻļā§āĻ°ā§ āĻ•āĻ°āĻž āĻ¯āĻžāĻ• .gitlab-ci.yaml, āĻ¯āĻž āĻ…ā§āĻ¯āĻžāĻĒā§āĻ˛āĻŋāĻ•ā§‡āĻļāĻ¨ āĻ¸ā§āĻĨāĻžāĻĒāĻ¨ āĻāĻŦāĻ‚ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻšāĻžāĻ˛āĻžāĻ¨ā§‹āĻ° āĻŦāĻ°ā§āĻŖāĻ¨āĻž āĻĻā§‡āĻ¯āĻŧāĨ¤ āĻ¤āĻžāĻ˛āĻŋāĻ•āĻžāĻŸāĻŋ āĻŦā§‡āĻļ āĻŦāĻĄāĻŧāĻ¸āĻĄāĻŧ āĻšāĻ¯āĻŧā§‡ āĻ‰āĻ ā§‡āĻ›ā§‡, āĻ¤āĻžāĻ‡ āĻāĻŸāĻŋ āĻŽāĻ¨ā§āĻ¤āĻŦā§āĻ¯ā§‡āĻ° āĻ¸āĻžāĻĨā§‡ āĻĒā§āĻ™ā§āĻ–āĻžāĻ¨ā§āĻĒā§āĻ™ā§āĻ–āĻ­āĻžāĻŦā§‡ āĻĒāĻ°āĻŋāĻĒā§‚āĻ°āĻ• āĻ›āĻŋāĻ˛:

variables:
# ОйŅŠŅĐ˛ĐģŅĐĩĐŧ вĐĩŅ€ŅĐ¸ŅŽ werf, ĐēĐžŅ‚ĐžŅ€ŅƒŅŽ ŅĐžĐąĐ¸Ņ€Đ°ĐĩĐŧŅŅ иŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ
  WERF_VERSION: "1.0 beta"

.base_deploy: &base_deploy
  script:
# ŅĐžĐˇĐ´Đ°ĐĩĐŧ namespace в K8s, ĐĩŅĐģи ĐĩĐŗĐž ĐŊĐĩŅ‚
    - kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# СаĐŗŅ€ŅƒĐļĐ°ĐĩĐŧ werf и Đ´ĐĩĐŋĐģОиĐŧ — ĐŋОдŅ€ĐžĐąĐŊĐĩĐĩ Ой ŅŅ‚ĐžĐŧ ŅĐŧ. в Đ´ĐžĐēŅƒĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸Đ¸
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#deploy-stage)
    - type multiwerf && source <(multiwerf use ${WERF_VERSION})
    - werf version
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - werf deploy --stages-storage :local
      --namespace ${CI_ENVIRONMENT_SLUG}
      --set "global.commit_ref_slug=${CI_COMMIT_REF_SLUG:-''}"
# ĐŋĐĩŅ€ĐĩĐ´Đ°ĐĩĐŧ ĐŋĐĩŅ€ĐĩĐŧĐĩĐŊĐŊŅƒŅŽ `run_tests`
# ĐžĐŊĐ° ĐąŅƒĐ´ĐĩŅ‚ иŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒŅŅ в Ņ€ĐĩĐŊĐ´ĐĩŅ€Đĩ Helm-Ņ€ĐĩĐģиСа
      --set "global.run_tests=${RUN_TESTS:-no}"
      --set "global.env=${CI_ENVIRONMENT_SLUG}"
# иСĐŧĐĩĐŊŅĐĩĐŧ timeout (ĐąŅ‹Đ˛Đ°ŅŽŅ‚ Đ´ĐžĐģĐŗиĐĩ Ņ‚ĐĩŅŅ‚Ņ‹) и ĐŋĐĩŅ€ĐĩĐ´Đ°ĐĩĐŧ ĐĩĐŗĐž в Ņ€ĐĩĐģиС
      --set "global.ci_timeout=${CI_TIMEOUT:-900}"
     --timeout ${CI_TIMEOUT:-900}
  dependencies:
    - Build

.test-base: &test-base
  extends: .base_deploy
  before_script:
# ŅĐžĐˇĐ´Đ°ĐĩĐŧ диŅ€ĐĩĐēŅ‚ĐžŅ€Đ¸ŅŽ Đ´ĐģŅ ĐąŅƒĐ´ŅƒŅ‰ĐĩĐŗĐž ĐžŅ‚Ņ‡ĐĩŅ‚Đ°, иŅŅ…ОдŅ иС $CI_COMMIT_REF_SLUG
    - mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# вŅ‹ĐŊŅƒĐļĐ´ĐĩĐŊĐŊŅ‹Đš ĐēĐžŅŅ‚Ņ‹ĐģŅŒ, Ņ‚.Đē. GitLab Ņ…ĐžŅ‡ĐĩŅ‚ ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚ŅŒ Đ°Ņ€Ņ‚ĐĩŅ„Đ°ĐēŅ‚Ņ‹ в ŅĐ˛ĐžĐĩĐŧ build-dir’Đĩ
    - mkdir ./tests || true
    - ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
  after_script:
# ĐŋĐžŅĐģĐĩ ĐžĐēĐžĐŊŅ‡Đ°ĐŊиŅ Ņ‚ĐĩŅŅ‚Ов ŅƒĐ´Đ°ĐģŅĐĩĐŧ Ņ€ĐĩĐģиС вĐŧĐĩŅŅ‚Đĩ Ņ Job’оĐŧ
# (и, вОСĐŧĐžĐļĐŊĐž, ĐĩĐŗĐž иĐŊŅ„Ņ€Đ°ŅŅ‚Ņ€ŅƒĐēŅ‚ŅƒŅ€ĐžĐš)
    - type multiwerf && source <(multiwerf use ${WERF_VERSION})
    - werf version
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - werf dismiss --namespace ${CI_ENVIRONMENT_SLUG} --with-namespace
# ĐŧŅ‹ Ņ€Đ°ĐˇŅ€ĐĩŅˆĐ°ĐĩĐŧ ĐŋĐ°Đ´ĐĩĐŊиŅ, ĐŊĐž вŅ‹ ĐŧĐžĐļĐĩŅ‚Đĩ ŅĐ´ĐĩĐģĐ°Ņ‚ŅŒ иĐŊĐ°Ņ‡Đĩ
  allow_failure: true
  variables:
    RUN_TESTS: 'yes'
# СадаĐĩĐŧ ĐēĐžĐŊŅ‚ĐĩĐēŅŅ‚ в werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
    WERF_KUBE_CONTEXT: 'admin@stage-cluster'
  tags:
# иŅĐŋĐžĐģŅŒĐˇŅƒĐĩĐŧ Ņ€Đ°ĐŊĐŊĐĩŅ€ Ņ Ņ‚ĐĩĐŗĐžĐŧ `werf-runner`
    - werf-runner
  artifacts:
# Ņ‚Ņ€ĐĩĐąŅƒĐĩŅ‚ŅŅ ŅĐžĐąŅ€Đ°Ņ‚ŅŒ Đ°Ņ€Ņ‚ĐĩŅ„Đ°ĐēŅ‚ Đ´ĐģŅ Ņ‚ĐžĐŗĐž, Ņ‡Ņ‚ОйŅ‹ ĐĩĐŗĐž ĐŧĐžĐļĐŊĐž ĐąŅ‹ĐģĐž ŅƒĐ˛Đ¸Đ´ĐĩŅ‚ŅŒ
# в ĐŋĐ°ĐšĐŋĐģĐ°ĐšĐŊĐĩ и ŅĐēĐ°Ņ‡Đ°Ņ‚ŅŒ — ĐŊĐ°ĐŋŅ€Đ¸ĐŧĐĩŅ€, Đ´ĐģŅ йОĐģĐĩĐĩ вдŅƒĐŧŅ‡Đ¸Đ˛ĐžĐŗĐž иСŅƒŅ‡ĐĩĐŊиŅ
    paths:
      - ./tests/${CI_COMMIT_REF_SLUG}/*
# Đ°Ņ€Ņ‚ĐĩŅ„Đ°ĐēŅ‚Ņ‹ ŅŅ‚Đ°Ņ€ŅˆĐĩ ĐŊĐĩĐ´ĐĩĐģи ĐąŅƒĐ´ŅƒŅ‚ ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹
    expire_in: 7 day
# ваĐļĐŊĐž: ŅŅ‚и ŅŅ‚Ņ€ĐžĐēи ĐžŅ‚вĐĩŅ‡Đ°ŅŽŅ‚ Са ĐŋĐ°Ņ€ŅĐ¸ĐŊĐŗ ĐžŅ‚Ņ‡ĐĩŅ‚Đ° GitLab’оĐŧ
    reports:
      junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml

# Đ´ĐģŅ ŅƒĐŋŅ€ĐžŅ‰ĐĩĐŊиŅ СдĐĩŅŅŒ ĐŋĐžĐēаСаĐŊŅ‹ вŅĐĩĐŗĐž двĐĩ ŅŅ‚адии
# в Ņ€ĐĩĐ°ĐģŅŒĐŊĐžŅŅ‚и ĐļĐĩ Ņƒ ваŅ иŅ… ĐąŅƒĐ´ĐĩŅ‚ йОĐģŅŒŅˆĐĩ — ĐēĐ°Đē ĐŧиĐŊиĐŧŅƒĐŧ иС-Са Đ´ĐĩĐŋĐģĐžŅ
stages:
  - build
  - tests

build:
  stage: build
  script:
# ŅĐąĐžŅ€ĐēĐ° — ŅĐŊОва ĐŋĐž Đ´ĐžĐēŅƒĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸Đ¸ ĐŋĐž werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#build-stage)
    - type multiwerf && source <(multiwerf use ${WERF_VERSION})
    - werf version
    - type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
    - werf build-and-publish --stages-storage :local
  tags:
    - werf-runner
  except:
    - schedules

run tests:
  <<: *test-base
  environment:
# "ŅĐ°ĐŧĐ° ŅĐžĐģŅŒ" иĐŧĐĩĐŊОваĐŊиŅ namespace’а
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
    name: tests-${CI_COMMIT_REF_SLUG}
  stage: tests
  except:
    - schedules

Kubernetes

āĻāĻ–āĻ¨ āĻĄāĻŋāĻ°ā§‡āĻ•ā§āĻŸāĻ°āĻŋāĻ¤ā§‡ .helm/templates āĻ•āĻžāĻœā§‡āĻ° āĻ¸āĻžāĻĨā§‡ YAML āĻ¤ā§ˆāĻ°āĻŋ āĻ•āĻ°āĻž āĻ¯āĻžāĻ• - tests-job.yaml — āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻšāĻžāĻ˛āĻžāĻ¨ā§‹āĻ° āĻœāĻ¨ā§āĻ¯ āĻāĻŦāĻ‚ Kubernetes āĻ¸āĻ‚āĻ¸ā§āĻĨāĻžāĻ¨āĻ—ā§āĻ˛āĻŋāĻ° āĻĒā§āĻ°āĻ¯āĻŧā§‹āĻœāĻ¨āĨ¤ āĻ¤āĻžāĻ˛āĻŋāĻ•āĻž āĻ•āĻ°āĻžāĻ° āĻĒāĻ°ā§‡ āĻŦā§āĻ¯āĻžāĻ–ā§āĻ¯āĻž āĻĻā§‡āĻ–ā§āĻ¨:

{{- if eq .Values.global.run_tests "yes" }}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: tests-script
data:
  tests.sh: |
    echo "======================"
    echo "${APP_NAME} TESTS"
    echo "======================"

    cd /app
    npm run test:ci
    cp report.xml /app/test_results/${CI_COMMIT_REF_SLUG}/

    echo ""
    echo ""
    echo ""

    chown -R 999:999 /app/test_results/${CI_COMMIT_REF_SLUG}
---
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Chart.Name }}-test
  annotations:
    "helm.sh/hook": post-install,post-upgrade
    "helm.sh/hook-weight": "2"
    "werf/watch-logs": "true"
spec:
  activeDeadlineSeconds: {{ .Values.global.ci_timeout }}
  backoffLimit: 1
  template:
    metadata:
      name: {{ .Chart.Name }}-test
    spec:
      containers:
      - name: test
        command: ['bash', '-c', '/app/tests.sh']
{{ tuple "application" . | include "werf_container_image" | indent 8 }}
        env:
        - name: env
          value: {{ .Values.global.env }}
        - name: CI_COMMIT_REF_SLUG
          value: {{ .Values.global.commit_ref_slug }}
       - name: APP_NAME
          value: {{ .Chart.Name }}
{{ tuple "application" . | include "werf_container_env" | indent 8 }}
        volumeMounts:
        - mountPath: /app/test_results/
          name: data
        - mountPath: /app/tests.sh
          name: tests-script
          subPath: tests.sh
      tolerations:
      - key: dedicated
        operator: Exists
      - key: node-role.kubernetes.io/master
        operator: Exists
      restartPolicy: OnFailure
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: {{ .Chart.Name }}-pvc
      - name: tests-script
        configMap:
          name: tests-script
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: {{ .Chart.Name }}-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Mi
  storageClassName: {{ .Chart.Name }}-{{ .Values.global.commit_ref_slug }}
  volumeName: {{ .Values.global.commit_ref_slug }}

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: {{ .Values.global.commit_ref_slug }}
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 10Mi
  local:
    path: /mnt/tests/
  nodeAffinity:
   required:
     nodeSelectorTerms:
     - matchExpressions:
       - key: kubernetes.io/hostname
         operator: In
         values:
         - kube-master
  persistentVolumeReclaimPolicy: Delete
  storageClassName: {{ .Chart.Name }}-{{ .Values.global.commit_ref_slug }}
{{- end }}

āĻ•āĻŋ āĻ§āĻ°āĻ¨ā§‡āĻ° āĻ¸āĻŽā§āĻĒāĻĻ āĻāĻ‡ āĻ•āĻ¨āĻĢāĻŋāĻ—āĻžāĻ°ā§‡āĻļāĻ¨ā§‡ āĻŦāĻ°ā§āĻŖāĻŋāĻ¤? āĻ¸ā§āĻĨāĻžāĻĒāĻ¨ āĻ•āĻ°āĻžāĻ° āĻ¸āĻŽāĻ¯āĻŧ, āĻ†āĻŽāĻ°āĻž āĻĒā§āĻ°āĻ•āĻ˛ā§āĻĒā§‡āĻ° āĻœāĻ¨ā§āĻ¯ āĻāĻ•āĻŸāĻŋ āĻ…āĻ¨āĻ¨ā§āĻ¯ āĻ¨āĻžāĻŽāĻ¸ā§āĻĨāĻžāĻ¨ āĻ¤ā§ˆāĻ°āĻŋ āĻ•āĻ°āĻŋ (āĻāĻŸāĻŋ āĻ¨āĻŋāĻ°ā§āĻĻā§‡āĻļāĻŋāĻ¤ āĻ†āĻ›ā§‡ .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) āĻāĻŦāĻ‚ āĻāĻŸāĻŋ āĻ°ā§‹āĻ˛ āĻ†āĻ‰āĻŸ āĻ•āĻ°ā§āĻ¨:

  1. āĻ•āĻ¨āĻĢāĻŋāĻ—āĻŽā§āĻ¯āĻžāĻĒ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻžāĻ° āĻ¸ā§āĻ•ā§āĻ°āĻŋāĻĒā§āĻŸ āĻ¸āĻš;
  2. āĻ•āĻžāĻœ āĻĒāĻĄā§‡āĻ° āĻŦāĻ°ā§āĻŖāĻ¨āĻž āĻāĻŦāĻ‚ āĻ¨āĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āĻŸ āĻ¨āĻŋāĻ°ā§āĻĻā§‡āĻļāĻ¨āĻž āĻ¸āĻš command, āĻ¯āĻž āĻļā§āĻ§ā§ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻšāĻžāĻ˛āĻžāĻ¯āĻŧ;
  3. āĻĒāĻŋāĻ­āĻŋ āĻāĻŦāĻ‚ āĻĒāĻŋāĻ­āĻŋāĻ¸āĻŋ, āĻ¯āĻž āĻ†āĻĒāĻ¨āĻžāĻ•ā§‡ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻžāĻ° āĻĄā§‡āĻŸāĻž āĻ¸āĻžā§āĻšāĻ¯āĻŧ āĻ•āĻ°āĻ¤ā§‡ āĻĻā§‡āĻ¯āĻŧāĨ¤

āĻ¸āĻ™ā§āĻ—ā§‡ āĻĒāĻ°āĻŋāĻšāĻžāĻ¯āĻŧāĻ• āĻ…āĻŦāĻ¸ā§āĻĨāĻž āĻŽāĻ¨ā§‹āĻ¯ā§‹āĻ— āĻĻāĻŋāĻ¨ if āĻŽā§āĻ¯āĻžāĻ¨āĻŋāĻĢā§‡āĻ¸ā§āĻŸā§‡āĻ° āĻļā§āĻ°ā§āĻ¤ā§‡ - āĻ¤āĻĻāĻ¨ā§āĻ¸āĻžāĻ°ā§‡, āĻ…ā§āĻ¯āĻžāĻĒā§āĻ˛āĻŋāĻ•ā§‡āĻļāĻ¨ āĻ¸āĻš āĻšā§‡āĻ˛āĻŽ āĻšāĻžāĻ°ā§āĻŸā§‡āĻ° āĻ…āĻ¨ā§āĻ¯āĻžāĻ¨ā§āĻ¯ YAML āĻĢāĻžāĻ‡āĻ˛āĻ—ā§āĻ˛āĻŋāĻ•ā§‡ āĻ†āĻŦā§ƒāĻ¤ āĻ•āĻ°āĻ¤ā§‡ āĻšāĻŦā§‡ āĻŦāĻŋāĻĒāĻ°ā§€āĻ¤ āĻĄāĻŋāĻœāĻžāĻ‡āĻ¨ āĻ¯āĻžāĻ¤ā§‡ āĻ¤āĻžāĻ°āĻž āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻžāĻ° āĻ¸āĻŽāĻ¯āĻŧ āĻŽā§‹āĻ¤āĻžāĻ¯āĻŧā§‡āĻ¨ āĻ¨āĻž āĻšāĻ¯āĻŧāĨ¤ āĻāĻŸāĻžāĻ‡:

{{- if ne .Values.global.run_tests "yes" }}
---
Ņ Đ´Ņ€ŅƒĐŗОК ŅĐŧĐģиĐē
{{- end }}

āĻ¤āĻŦā§‡ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻšāĻ˛ā§‡ āĻ•āĻŋāĻ›ā§ āĻ…āĻŦāĻ•āĻžāĻ āĻžāĻŽā§‹ āĻĒā§āĻ°āĻ¯āĻŧā§‹āĻœāĻ¨ (āĻ‰āĻĻāĻžāĻšāĻ°āĻŖāĻ¸ā§āĻŦāĻ°ā§‚āĻĒ, Redis, RabbitMQ, Mongo, PostgreSQL...) - āĻ¤āĻžāĻĻā§‡āĻ° YAML āĻšāĻ¤ā§‡ āĻĒāĻžāĻ°ā§‡ āĻ¨āĻž āĻŦāĻ¨ā§āĻ§ āĻ•āĻ°. āĻ¸ā§‡āĻ—ā§āĻ˛āĻŋāĻ•ā§‡ āĻāĻ•āĻŸāĻŋ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻžāĻ° āĻĒāĻ°āĻŋāĻŦā§‡āĻļā§‡ āĻ¸ā§āĻĨāĻžāĻĒāĻ¨ āĻ•āĻ°ā§āĻ¨... āĻ…āĻŦāĻļā§āĻ¯āĻ‡ āĻ†āĻĒāĻ¨āĻžāĻ° āĻ‰āĻĒāĻ¯ā§āĻ•ā§āĻ¤ āĻŽāĻ¨ā§‡ āĻšāĻ˛ā§‡ āĻ¸ā§‡āĻ—ā§āĻ˛āĻŋ āĻ¸āĻžāĻŽāĻžā§āĻœāĻ¸ā§āĻ¯ āĻ•āĻ°ā§āĻ¨ā§ˇ

āĻšā§‚āĻĄāĻŧāĻžāĻ¨ā§āĻ¤ āĻ¸ā§āĻĒāĻ°ā§āĻļ

āĻ•āĻžāĻ°āĻŖ werf āĻŦā§āĻ¯āĻŦāĻšāĻžāĻ° āĻ•āĻ°ā§‡ āĻ¸āĻŽāĻžāĻŦā§‡āĻļ āĻāĻŦāĻ‚ āĻ¸ā§āĻĨāĻžāĻĒāĻ¨āĻž āĻ†āĻĒāĻžāĻ¤āĻ¤ āĻ•āĻžāĻœ āĻ•āĻ°ā§‡ āĻļā§āĻ§ā§āĻŽāĻžāĻ¤ā§āĻ° āĻŦāĻŋāĻ˛ā§āĻĄ āĻ¸āĻžāĻ°ā§āĻ­āĻžāĻ°ā§‡ (āĻ—āĻŋāĻŸāĻ˛ā§āĻ¯āĻžāĻŦ-āĻ°āĻžāĻ¨āĻžāĻ° āĻ¸āĻš), āĻāĻŦāĻ‚ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻ¸āĻš āĻĒāĻĄāĻŸāĻŋ āĻŽāĻžāĻ¸ā§āĻŸāĻžāĻ°ā§‡ āĻšāĻžāĻ˛ā§ āĻšāĻ¯āĻŧ, āĻ†āĻĒāĻ¨āĻžāĻ•ā§‡ āĻāĻ•āĻŸāĻŋ āĻĄāĻŋāĻ°ā§‡āĻ•ā§āĻŸāĻ°āĻŋ āĻ¤ā§ˆāĻ°āĻŋ āĻ•āĻ°āĻ¤ā§‡ āĻšāĻŦā§‡ /mnt/tests āĻŽāĻžāĻ¸ā§āĻŸāĻžāĻ°ā§‡āĻ° āĻ‰āĻĒāĻ° āĻāĻŦāĻ‚ āĻāĻŸāĻŋ āĻ°āĻžāĻ¨āĻžāĻ°āĻ•ā§‡ āĻĻāĻŋāĻ¨, āĻ‰āĻĻāĻžāĻšāĻ°āĻŖāĻ¸ā§āĻŦāĻ°ā§‚āĻĒ, NFS āĻāĻ° āĻŽāĻžāĻ§ā§āĻ¯āĻŽā§‡. āĻŦā§āĻ¯āĻžāĻ–ā§āĻ¯āĻž āĻ¸āĻš āĻāĻ•āĻŸāĻŋ āĻŦāĻŋāĻ¸ā§āĻ¤āĻžāĻ°āĻŋāĻ¤ āĻ‰āĻĻāĻžāĻšāĻ°āĻŖ āĻĒāĻžāĻ“āĻ¯āĻŧāĻž āĻ¯āĻžāĻŦā§‡ K8s āĻĄāĻ•ā§āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāĻ¨.

āĻĢāĻ˛āĻžāĻĢāĻ˛ āĻšāĻŦā§‡:

user@kube-master:~$ cat /etc/exports | grep tests
/mnt/tests    IP_gitlab-builder/32(rw,nohide,insecure,no_subtree_check,sync,all_squash,anonuid=999,anongid=998)

user@gitlab-runner:~$ cat /etc/fstab | grep tests
IP_kube-master:/mnt/tests    /mnt/tests   nfs4    _netdev,auto  0       0

āĻ•ā§‡āĻ‰ āĻ¸āĻ°āĻžāĻ¸āĻ°āĻŋ āĻ—āĻŋāĻŸāĻ˛ā§āĻ¯āĻžāĻŦ-āĻ°āĻžāĻ¨āĻžāĻ°ā§‡ āĻāĻ•āĻŸāĻŋ NFS āĻļā§‡āĻ¯āĻŧāĻžāĻ° āĻ•āĻ°āĻ¤ā§‡ āĻāĻŦāĻ‚ āĻ¤āĻžāĻ°āĻĒāĻ° āĻāĻŸāĻŋāĻ•ā§‡ āĻĒāĻĄāĻ—ā§āĻ˛āĻŋāĻ¤ā§‡ āĻŽāĻžāĻ‰āĻ¨ā§āĻŸ āĻ•āĻ°āĻ¤ā§‡ āĻ¨āĻŋāĻˇā§‡āĻ§ āĻ•āĻ°ā§‡ āĻ¨āĻžāĨ¤

āĻŽāĻ¨ā§āĻ¤āĻŦā§āĻ¯

āĻ†āĻĒāĻ¨āĻŋ āĻšāĻ¯āĻŧāĻ¤ā§‹ āĻœāĻŋāĻœā§āĻžāĻžāĻ¸āĻž āĻ•āĻ°āĻ›ā§‡āĻ¨ āĻ¯ā§‡ āĻ†āĻĒāĻ¨āĻŋ āĻ¯āĻĻāĻŋ āĻļā§‡āĻ˛ āĻ°āĻžāĻ¨āĻžāĻ°ā§‡ āĻ¸āĻ°āĻžāĻ¸āĻ°āĻŋ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻ¸āĻš āĻāĻ•āĻŸāĻŋ āĻ¸ā§āĻ•ā§āĻ°āĻŋāĻĒā§āĻŸ āĻšāĻžāĻ˛āĻžāĻ¤ā§‡ āĻĒāĻžāĻ°ā§‡āĻ¨ āĻ¤āĻŦā§‡ āĻ•ā§‡āĻ¨ āĻāĻ•āĻŸāĻŋ āĻšāĻžāĻ•āĻ°āĻŋ āĻ¤ā§ˆāĻ°āĻŋ āĻ•āĻ°ā§‡ āĻ¸āĻŦāĻ•āĻŋāĻ›ā§āĻ•ā§‡ āĻœāĻŸāĻŋāĻ˛ āĻ•āĻ°āĻŦā§‡āĻ¨? āĻ‰āĻ¤ā§āĻ¤āĻ°āĻŸāĻž āĻ–ā§āĻŦāĻ‡ āĻ¤ā§āĻšā§āĻ›...

āĻ•āĻŋāĻ›ā§ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻžāĻ° āĻœāĻ¨ā§āĻ¯ āĻĒāĻ°āĻŋāĻ•āĻžāĻ āĻžāĻŽā§‹ (MongoDB, RabbitMQ, PostgreSQL, āĻ‡āĻ¤ā§āĻ¯āĻžāĻĻāĻŋ) āĻ¸āĻ āĻŋāĻ•āĻ­āĻžāĻŦā§‡ āĻ•āĻžāĻœ āĻ•āĻ°ā§‡ āĻ•āĻŋāĻ¨āĻž āĻ¤āĻž āĻ¯āĻžāĻšāĻžāĻ‡ āĻ•āĻ°āĻžāĻ° āĻœāĻ¨ā§āĻ¯ āĻ…ā§āĻ¯āĻžāĻ•ā§āĻ¸ā§‡āĻ¸ āĻĒā§āĻ°āĻ¯āĻŧā§‹āĻœāĻ¨āĨ¤ āĻ†āĻŽāĻ°āĻž āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻžāĻ•ā§‡ āĻāĻ•ā§€āĻ­ā§‚āĻ¤ āĻ•āĻ°āĻŋ - āĻāĻ‡ āĻĒāĻĻā§āĻ§āĻ¤āĻŋāĻ° āĻ¸āĻžāĻšāĻžāĻ¯ā§āĻ¯ā§‡ āĻāĻ‡ āĻ§āĻ°āĻ¨ā§‡āĻ° āĻ…āĻ¤āĻŋāĻ°āĻŋāĻ•ā§āĻ¤ āĻ¸āĻ¤ā§āĻ¤āĻž āĻ…āĻ¨ā§āĻ¤āĻ°ā§āĻ­ā§āĻ•ā§āĻ¤ āĻ•āĻ°āĻž āĻ¸āĻšāĻœ āĻšāĻ¯āĻŧā§‡ āĻ¯āĻžāĻ¯āĻŧāĨ¤ āĻāĻ‡ āĻ›āĻžāĻĄāĻŧāĻžāĻ“, āĻ†āĻŽāĻ°āĻž āĻĒā§‡āĻ¤ā§‡ āĻŽāĻžāĻ¨ āĻ¸ā§āĻĨāĻžāĻĒāĻ¨āĻž āĻĒāĻĻā§āĻ§āĻ¤āĻŋ (āĻāĻŽāĻ¨āĻ•āĻŋ NFS āĻŦā§āĻ¯āĻŦāĻšāĻžāĻ° āĻ•āĻ°āĻ˛ā§‡āĻ“, āĻĄāĻŋāĻ°ā§‡āĻ•ā§āĻŸāĻ°āĻŋāĻ° āĻ…āĻ¤āĻŋāĻ°āĻŋāĻ•ā§āĻ¤ āĻŽāĻžāĻ‰āĻ¨ā§āĻŸāĻŋāĻ‚)āĨ¤

āĻĢāĻ˛

āĻ†āĻŽāĻ°āĻž āĻ¯āĻ–āĻ¨ āĻĒā§āĻ°āĻ¸ā§āĻ¤ā§āĻ¤ āĻ•āĻ¨āĻĢāĻŋāĻ—āĻžāĻ°ā§‡āĻļāĻ¨ āĻĒā§āĻ°āĻ¯āĻŧā§‹āĻ— āĻ•āĻ°āĻŦ āĻ¤āĻ–āĻ¨ āĻ†āĻŽāĻ°āĻž āĻ•ā§€ āĻĻā§‡āĻ–āĻŦ?

āĻŽāĻžāĻ°ā§āĻœ āĻ…āĻ¨ā§āĻ°ā§‹āĻ§āĻŸāĻŋ āĻ¤āĻžāĻ° āĻ¸āĻ°ā§āĻŦāĻļā§‡āĻˇ āĻĒāĻžāĻ‡āĻĒāĻ˛āĻžāĻ‡āĻ¨ā§‡ āĻšāĻžāĻ˛āĻžāĻ¨ā§‹ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻžāĻ° āĻœāĻ¨ā§āĻ¯ āĻ¸āĻžāĻ°āĻ¸āĻ‚āĻ•ā§āĻˇā§‡āĻĒ āĻĒāĻ°āĻŋāĻ¸āĻ‚āĻ–ā§āĻ¯āĻžāĻ¨ āĻĻā§‡āĻ–āĻžāĻŦā§‡:

āĻ•ā§āĻŦāĻžāĻ°āĻ¨ā§‡āĻŸāĻ¸ā§‡āĻ° āĻ¸āĻžāĻĨā§‡ āĻ—āĻŋāĻŸāĻ˛ā§āĻ¯āĻžāĻŦ āĻ¸āĻŋāĻ†āĻ‡-āĻ JUnit

āĻĒā§āĻ°āĻ¤āĻŋāĻŸāĻŋ āĻ¤ā§āĻ°ā§āĻŸāĻŋ āĻŦāĻŋāĻ¸ā§āĻ¤āĻžāĻ°āĻŋāĻ¤ āĻœāĻžāĻ¨āĻžāĻ° āĻœāĻ¨ā§āĻ¯ āĻāĻ–āĻžāĻ¨ā§‡ āĻ•ā§āĻ˛āĻŋāĻ• āĻ•āĻ°āĻž āĻ¯ā§‡āĻ¤ā§‡ āĻĒāĻžāĻ°ā§‡:

āĻ•ā§āĻŦāĻžāĻ°āĻ¨ā§‡āĻŸāĻ¸ā§‡āĻ° āĻ¸āĻžāĻĨā§‡ āĻ—āĻŋāĻŸāĻ˛ā§āĻ¯āĻžāĻŦ āĻ¸āĻŋāĻ†āĻ‡-āĻ JUnit

NB: āĻŽāĻ¨ā§‹āĻ¯ā§‹āĻ—ā§€ āĻĒāĻžāĻ āĻ• āĻ˛āĻ•ā§āĻˇā§āĻ¯ āĻ•āĻ°āĻŦā§‡āĻ¨ āĻ¯ā§‡ āĻ†āĻŽāĻ°āĻž āĻāĻ•āĻŸāĻŋ NodeJS āĻ…ā§āĻ¯āĻžāĻĒā§āĻ˛āĻŋāĻ•ā§‡āĻļāĻ¨ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻ•āĻ°āĻ›āĻŋ, āĻāĻŦāĻ‚ āĻ¸ā§āĻ•ā§āĻ°āĻŋāĻ¨āĻļāĻŸāĻ—ā§āĻ˛āĻŋāĻ¤ā§‡ - .NET... āĻ…āĻŦāĻžāĻ• āĻšāĻŦā§‡āĻ¨ āĻ¨āĻž: āĻāĻŸāĻŋ āĻ āĻŋāĻ• āĻ¯ā§‡ āĻ¨āĻŋāĻŦāĻ¨ā§āĻ§āĻŸāĻŋ āĻĒā§āĻ°āĻ¸ā§āĻ¤ā§āĻ¤ āĻ•āĻ°āĻžāĻ° āĻ¸āĻŽāĻ¯āĻŧ, āĻĒā§āĻ°āĻĨāĻŽ āĻ…ā§āĻ¯āĻžāĻĒā§āĻ˛āĻŋāĻ•ā§‡āĻļāĻ¨āĻŸāĻŋ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻ•āĻ°āĻžāĻ° āĻ¸āĻŽāĻ¯āĻŧ āĻ•ā§‹āĻ¨āĻ“ āĻ¤ā§āĻ°ā§āĻŸāĻŋ āĻĒāĻžāĻ“āĻ¯āĻŧāĻž āĻ¯āĻžāĻ¯āĻŧāĻ¨āĻŋ, āĻ¤āĻŦā§‡ āĻ¤āĻžāĻ°āĻž āĻ…āĻ¨ā§āĻ¯āĻŸāĻŋāĻ¤ā§‡ āĻĒāĻžāĻ“āĻ¯āĻŧāĻž āĻ—ā§‡āĻ›ā§‡āĨ¤

āĻ‰āĻĒāĻ¸āĻ‚āĻšāĻžāĻ°

āĻ†āĻĒāĻ¨āĻŋ āĻĻā§‡āĻ–āĻ¤ā§‡ āĻĒāĻžāĻ°ā§‡āĻ¨, āĻ•āĻŋāĻ›ā§āĻ‡ āĻœāĻŸāĻŋāĻ˛!

āĻ¨ā§€āĻ¤āĻŋāĻ—āĻ¤āĻ­āĻžāĻŦā§‡, āĻ†āĻĒāĻ¨āĻžāĻ° āĻ¯āĻĻāĻŋ āĻ‡āĻ¤āĻŋāĻŽāĻ§ā§āĻ¯ā§‡ āĻāĻ•āĻŸāĻŋ āĻļā§‡āĻ˛ āĻ¸āĻ‚āĻ—ā§āĻ°āĻžāĻšāĻ• āĻĨāĻžāĻ•ā§‡ āĻāĻŦāĻ‚ āĻāĻŸāĻŋ āĻ•āĻžāĻœ āĻ•āĻ°ā§‡ āĻ¤āĻŦā§‡ āĻ†āĻĒāĻ¨āĻžāĻ° āĻ•ā§āĻŦāĻžāĻ°āĻ¨ā§‡āĻŸāĻ¸ā§‡āĻ° āĻĒā§āĻ°āĻ¯āĻŧā§‹āĻœāĻ¨ āĻ¨ā§‡āĻ‡, āĻāĻŸāĻŋāĻ° āĻ¸āĻžāĻĨā§‡ āĻĒāĻ°ā§€āĻ•ā§āĻˇāĻž āĻ¸āĻ‚āĻ¯ā§āĻ•ā§āĻ¤ āĻ•āĻ°āĻž āĻāĻ–āĻžāĻ¨ā§‡ āĻŦāĻ°ā§āĻŖāĻŋāĻ¤ āĻšāĻ“āĻ¯āĻŧāĻžāĻ° āĻšā§‡āĻ¯āĻŧā§‡ āĻ†āĻ°āĻ“ āĻ¸āĻšāĻœ āĻ•āĻžāĻœ āĻšāĻŦā§‡āĨ¤ āĻāĻŦāĻ‚ āĻ­āĻŋāĻ¤āĻ°ā§‡ āĻ—āĻŋāĻŸāĻ˛ā§āĻ¯āĻžāĻŦ āĻ¸āĻŋāĻ†āĻ‡ āĻĄāĻ•ā§āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāĻ¨ āĻ†āĻĒāĻ¨āĻŋ āĻ°ā§āĻŦāĻŋ, āĻ—ā§‹, āĻ—ā§āĻ°ā§‡āĻĄāĻ˛, āĻŽā§āĻ¯āĻžāĻ­ā§‡āĻ¨ āĻāĻŦāĻ‚ āĻ†āĻ°āĻ“ āĻ•āĻŋāĻ›ā§ āĻ‰āĻĻāĻžāĻšāĻ°āĻŖ āĻĒāĻžāĻŦā§‡āĻ¨āĨ¤

āĻĻā§āĻ°āĻˇā§āĻŸāĻŦā§āĻ¯

āĻ†āĻŽāĻžāĻĻā§‡āĻ° āĻŦā§āĻ˛āĻ—ā§‡āĻ“ āĻĒāĻĄāĻŧā§āĻ¨:

āĻ‰āĻ¤ā§āĻ¸: www.habr.com

āĻāĻ•āĻŸāĻŋ āĻŽāĻ¨ā§āĻ¤āĻŦā§āĻ¯ āĻœā§āĻĄāĻŧā§āĻ¨