{"id":294816,"date":"2019-11-18T21:00:25","date_gmt":"2019-11-18T21:00:25","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=294816"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=294816","title":{"rendered":"\u0421\u0442\u0440\u043e\u0438\u043c \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u0439 CI\/CD \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 GitHub Actions \u0438 Python"},"content":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\" id=\"post-content-body\">\n<p>\u041a\u0430\u043a \u0442\u043e \u0432\u0435\u0447\u0435\u0440\u043e\u043c, \u043f\u0440\u0438\u0434\u044f \u0434\u043e\u043c\u043e\u0439 \u0441 \u0440\u0430\u0431\u043e\u0442\u044b, \u044f \u0440\u0435\u0448\u0438\u043b \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0437\u0430\u043d\u0438\u043c\u0430\u0442\u044c\u0441\u044f \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c. \u042f \u0441\u0434\u0435\u043b\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0430\u0432\u043e\u043a \u0438 \u0441\u0440\u0430\u0437\u0443 \u0437\u0430\u0445\u043e\u0442\u0435\u043b \u043f\u043e\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 \u043d\u0438\u043c\u0438. \u041d\u043e \u0434\u043e \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u043e\u0432 \u043c\u043d\u0435 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0437\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u043d\u0430 VPS, \u043f\u0443\u043b\u0438\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u043f\u0435\u0440\u0435\u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0435\u0433\u043e. \u0422\u0443\u0442 \u044f \u0438 \u0440\u0435\u0448\u0438\u043b, \u0447\u0442\u043e \u043f\u043e\u0440\u0430 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0441 \u043d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u043e\u0439 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u043e\u0439.<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/xa\/l4\/dm\/xal4dmowtxwpuoxxrutujwwy4fm.jpeg\"><\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<p>\u0418\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u043e \u043c\u043d\u043e\u0439 \u0441\u0442\u043e\u044f\u043b \u0432\u044b\u0431\u043e\u0440 \u043c\u0435\u0436\u0434\u0443 Circle CI, Travis \u0438\u043b\u0438 Jenkins. Jenkins \u044f \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u0440\u0430\u0437\u0443 \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u043b \u0438\u0437-\u0437\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 \u043d\u0430\u0441\u0442\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u0449\u043d\u043e\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0435. \u0411\u0435\u0433\u043b\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0432 \u043f\u0440\u043e Travis \u043f\u0440\u0438\u0448\u0435\u043b \u043a \u0432\u044b\u0432\u043e\u0434\u0443, \u0447\u0442\u043e \u0432 \u043d\u0435\u043c \u0443\u0434\u043e\u0431\u043d\u043e \u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043d\u043e \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0443 \u0441 \u043d\u0438\u043c \u043e\u0441\u043e\u0431\u043e \u043d\u0435 \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u0435\u0448\u044c. \u041e Circle CI \u044f \u0443\u0437\u043d\u0430\u043b \u0438\u0437 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043d\u0430\u0432\u044f\u0437\u0447\u0438\u0432\u043e\u0439 \u0440\u0435\u043a\u043b\u0430\u043c\u044b \u043d\u0430 youtube. \u041d\u0430\u0447\u0430\u043b \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u043c\u0438, \u043d\u043e \u0432 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u044f \u043e\u0448\u0438\u0431\u0441\u044f \u0438 \u0443 \u043c\u0435\u043d\u044f \u0431\u044b\u043b\u0430 \u0432\u0435\u0447\u043d\u043e\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0443 \u043c\u0435\u043d\u044f \u0443\u0448\u043b\u043e \u043c\u043d\u043e\u0433\u043e \u0434\u0440\u0430\u0433\u043e\u0446\u0435\u043d\u043d\u044b\u0445 \u043c\u0438\u043d\u0443\u0442\u044b \u0441\u0431\u043e\u0440\u043a\u0438 (\u0412 \u043e\u0431\u0449\u0435\u043c \u0442\u043e \u0442\u0430\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u044b\u0439 \u043b\u0438\u043c\u0438\u0442, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0431\u0435\u0441\u043f\u043e\u043a\u043e\u0438\u0442\u044c\u0441\u044f, \u043d\u043e \u043c\u0435\u043d\u044f \u044d\u0442\u043e \u0437\u0430\u0434\u0435\u043b\u043e). \u0421\u043d\u043e\u0432\u0430 \u0432\u0437\u044f\u0432\u0448\u0438\u0441\u044c \u0437\u0430 \u043f\u043e\u0438\u0441\u043a\u0438 \u044f \u043d\u0430\u0442\u043a\u043d\u0443\u043b\u0441\u044f \u043d\u0430 Github Actions. \u041f\u043e\u0438\u0433\u0440\u0430\u0432 \u0441 Get Started \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u043c\u0438 \u0443 \u043c\u0435\u043d\u044f \u0441\u043b\u043e\u0436\u0438\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u0435\u043d\u0438\u0435, \u0430 \u043f\u043e\u0441\u043b\u0435 \u0431\u0435\u0433\u043b\u043e\u0433\u043e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439, \u043f\u0440\u0438\u0448\u0435\u043b \u043a \u0432\u044b\u0432\u043e\u0434\u0443 \u0447\u0442\u043e \u044d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u043a\u0440\u0443\u0442\u043e \u0447\u0442\u043e \u044f \u043c\u043e\u0433\u0443 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0435\u043a\u0440\u0435\u0442\u044b \u0434\u043b\u044f \u0441\u0431\u043e\u0440\u043a\u0438, \u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u0438 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u044b \u0432 \u043e\u0434\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u0435. \u0421 \u0433\u043e\u0440\u044f\u0449\u0438\u043c\u0438 \u0433\u043b\u0430\u0437\u0430\u043c\u0438 \u0431\u044b\u0441\u0442\u0440\u043e \u043d\u0430\u0440\u0438\u0441\u043e\u0432\u0430\u043b \u0436\u0435\u043b\u0430\u0435\u043c\u0443\u044e \u0441\u0445\u0435\u043c\u0443, \u0438 \u0448\u0435\u0441\u0442\u0435\u0440\u0435\u043d\u043a\u0438 \u0437\u0430\u043a\u0440\u0443\u0442\u0438\u043b\u0438\u0441\u044c.<\/p>\n<p>  <\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/h1\/fp\/rv\/h1fprvm3jru4em3vj0ae6xruu5i.jpeg\" alt=\"plan\"><\/div>\n<p>  <\/p>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u0434\u043e\u043f\u044b\u0442\u043d\u043e\u0433\u043e \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0432\u0435\u0431 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430 Flask \u0441 2\u043c\u044f \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430\u043c\u0438:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041b\u0438\u0441\u0442\u0438\u043d\u0433 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u0432\u0435\u0431 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">from flask import Flask from flask import request, jsonify  app = Flask(__name__)  def validate_post_data(data: dict) -&gt; bool:     if not isinstance(data, dict):         return False     if not data.get('name') or not isinstance(data['name'], str):         return False     if data.get('age') and not isinstance(data['age'], int):         return False     return True  @app.route('\/', methods=['GET']) def hello():     return 'Hello World!'  @app.route('\/api', methods=['GET', 'POST']) def api():     \"\"\"     \/api entpoint     GET - returns json= {'status': 'test'}     POST -  {             name - str not null             age - int optional             }     :return:     \"\"\"     if request.method == 'GET':         return jsonify({'status': 'test'})     elif request.method == 'POST':         if validate_post_data(request.json):             return jsonify({'status': 'OK'})         else:             return jsonify({'status': 'bad input'}), 400  def main():     app.run(host='0.0.0.0', port=8080)  if __name__ == '__main__':     main()<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0418 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u0441\u0442\u043e\u0432:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041b\u0438\u0441\u0442\u0438\u043d\u0433 \u0442\u0435\u0441\u0442\u043e\u0432 \u0434\u043b\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\">import unittest import app as tested_app import json  class FlaskAppTests(unittest.TestCase):      def setUp(self):         tested_app.app.config['TESTING'] = True         self.app = tested_app.app.test_client()      def test_get_hello_endpoint(self):         r = self.app.get('\/')         self.assertEqual(r.data, b'Hello World!')      def test_post_hello_endpoint(self):         r = self.app.post('\/')         self.assertEqual(r.status_code, 405)      def test_get_api_endpoint(self):         r = self.app.get('\/api')         self.assertEqual(r.json, {'status': 'test'})      def test_correct_post_api_endpoint(self):         r = self.app.post('\/api',                           content_type='application\/json',                           data=json.dumps({'name': 'Den', 'age': 100}))         self.assertEqual(r.json, {'status': 'OK'})         self.assertEqual(r.status_code, 200)          r = self.app.post('\/api',                           content_type='application\/json',                           data=json.dumps({'name': 'Den'}))         self.assertEqual(r.json, {'status': 'OK'})         self.assertEqual(r.status_code, 200)      def test_not_dict_post_api_endpoint(self):         r = self.app.post('\/api',                           content_type='application\/json',                           data=json.dumps([{'name': 'Den'}]))         self.assertEqual(r.json, {'status': 'bad input'})         self.assertEqual(r.status_code, 400)      def test_no_name_post_api_endpoint(self):         r = self.app.post('\/api',                           content_type='application\/json',                           data=json.dumps({'age': 100}))         self.assertEqual(r.json, {'status': 'bad input'})         self.assertEqual(r.status_code, 400)      def test_bad_age_post_api_endpoint(self):         r = self.app.post('\/api',                           content_type='application\/json',                           data=json.dumps({'name': 'Den', 'age': '100'}))         self.assertEqual(r.json, {'status': 'bad input'})         self.assertEqual(r.status_code, 400)  if __name__ == '__main__':     unittest.main()<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0412\u044b\u0432\u043e\u0434 \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">coverage report Name                                           Stmts   Miss  Cover ------------------------------------------------------------------ src\/app.py                                        28      2    93% src\/tests.py                                      37      0   100% ------------------------------------------------------------------ TOTAL                                             65      2    96%<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0430\u0448 \u043f\u0435\u0440\u0432\u044b\u0439 action, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b. \u0421\u043e\u0433\u043b\u0430\u0441\u043d\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0432\u0441\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ mkdir -p .github\/workflows $ touch .github\/workflows\/test_on_push.yaml<\/code><\/pre>\n<p>  <\/p>\n<p>\u042f \u0445\u043e\u0447\u0443, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u043e\u0442 \u044d\u043a\u0448\u0435\u043d \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0441\u044f \u043f\u0440\u0438 \u043b\u044e\u0431\u043e\u043c \u043f\u0443\u0448 \u044d\u0432\u0435\u043d\u0442\u0435 \u0432 \u043b\u044e\u0431\u043e\u0439 \u0432\u0435\u0442\u043a\u0435, \u0437\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u0440\u0435\u043b\u0438\u0437\u043e\u0432(\u0442\u044d\u0433\u043e\u0432, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u0430\u043c \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435):<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">on:  push:    tags:      - '!refs\/tags\/*'    branches:      - '*'<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0430\u0442\u0435\u043c \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0437\u0430\u0434\u0430\u0447\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u0440\u0435\u0434\u0435 Ubuntu \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">jobs:  run_tests:    runs-on: [ubuntu-latest]<\/code><\/pre>\n<p>  <\/p>\n<p>\u0428\u0430\u0433\u0430\u043c\u0438 \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0443\u0442 \u0447\u0435\u043a\u0430\u0443\u0442 \u043a\u043e\u0434\u0430, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u0438\u0442\u043e\u043d\u0430, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u0432\u044b\u0432\u043e\u0434 \u043f\u043e\u043a\u0440\u044b\u0442\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">steps:   # \u0427\u0435\u043a\u0430\u0443\u0442\u0438\u043c \u043a\u043e\u0434  - uses: actions\/checkout@master    # \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c python \u043d\u0443\u0436\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438  - uses: actions\/setup-python@v1    with:      python-version: '3.8'      architecture: 'x64'  - name: Install requirements    # \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438    run: pip install -r requirements.txt  - name: Run tests    run: coverage run src\/tests.py  - name: Tests report    run: coverage report<\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0412\u0441\u0435 \u0432\u043c\u0435\u0441\u0442\u0435<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\">name: Run tests on any Push event # \u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438 \u043b\u044e\u0431\u043e\u043c push \u0435\u0432\u0435\u043d\u0442\u0435 \u0432 \u043b\u044e\u0431\u043e\u0439 \u0432\u0435\u0442\u043a\u0435, \u0437\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u0440\u0435\u043b\u0438\u0437\u043d\u044b\u0445 \u0442\u044d\u0433\u043e\u0432. # \u041e\u043d\u0438 \u0431\u0443\u0434\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0435\u0440\u0435\u0434 \u0441\u0431\u043e\u0440\u043a\u043e\u0439 on:   push:     tags:       - '!refs\/tags\/*'     branches:       - '*' jobs:   run_tests:     runs-on: [ubuntu-latest]     steps:       # \u0427\u0435\u043a\u0430\u0443\u0442\u0438\u043c \u043a\u043e\u0434       - uses: actions\/checkout@master       # \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c python \u043d\u0443\u0436\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438       - uses: actions\/setup-python@v1         with:           python-version: '3.8'           architecture: 'x64'       - name: Install requirements         # \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438         run: pip install -r requirements.txt       - name: Run tests         run: coverage run src\/tests.py       - name: Tests report         run: coverage report<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043e\u043c\u043c\u0438\u0442 \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430\u0448\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435.<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/qy\/v7\/by\/qyv7byybfnvpnkurriqvydsp_xk.png\"><br \/>  <em>\u041f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 Actions<\/em><\/p>\n<p>  <\/p>\n<p>\u0423\u0440\u0430, \u0443 \u043d\u0430\u0441 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0435\u0440\u0432\u044b\u0439 \u044d\u043a\u0448\u0435\u043d \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0435\u0433\u043e! \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0441\u043b\u043e\u043c\u0430\u0442\u044c \u043a\u0430\u043a\u043e\u0439 \u043d\u0438\u0431\u0443\u0434\u044c \u0442\u0435\u0441\u0442 \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0432\u044b\u0432\u043e\u0434:<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/dr\/vn\/_s\/drvn_sgrq-esw6sgxjiylhhy02w.png\"><br \/>  <em>\u041f\u0430\u0434\u0435\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 Actions<\/em> <\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u0441\u0442\u044b \u043f\u0440\u043e\u0432\u0430\u043b\u0438\u043b\u0438\u0441\u044c. \u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u0433\u043e\u0440\u0435\u043b\u0441\u044f \u043a\u0440\u0430\u0441\u043d\u044b\u043c \u0438 \u0434\u0430\u0436\u0435 \u043f\u0440\u0438\u0448\u043b\u043e \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 \u043f\u043e\u0447\u0442\u0443. \u0422\u043e \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e! 3 \u0438\u0437 8 \u043f\u0443\u043d\u043a\u0442\u043e\u0432 \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u0441\u0445\u0435\u043c\u044b \u043c\u043e\u0436\u043d\u043e \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u044b\u043c\u0438. \u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0441\u043e \u0441\u0431\u043e\u0440\u043a\u043e\u0439, \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u043d\u0430\u0448\u0438\u0445 docker images. <\/p>\n<p>  <\/p>\n<p><strong>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435! \u0414\u0430\u043b\u0435\u0435 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0432 <a href=\"https:\/\/hub.docker.com\">\u0434\u043e\u043a\u0435\u0440\u0435<\/a><\/strong><\/p>\n<p>  <\/p>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 Dockerfile \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0442\u0441\u044f \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. <\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Dockerfile<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\"># \u0411\u0435\u0440\u0435\u043c \u043d\u0443\u0436\u043d\u044b\u0439 \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043e\u0431\u0440\u0430\u0437 FROM python:3.8-alpine # \u041a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0432\u0441\u0435 \u0444\u0430\u0439\u043b\u044b \u0438\u0437 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0432 \/app \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 COPY .\/ \/app # \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0432\u0441\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 RUN apk update &amp;&amp; pip install -r \/app\/requirements.txt --no-cache-dir # \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 (\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0441\u043c\u043e\u0442\u0440\u0438 Distutils) RUN pip install -e \/app # \u0413\u043e\u0432\u043e\u0440\u0438\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0443 \u043a\u0430\u043a\u043e\u0439 \u043f\u043e\u0440\u0442 \u0441\u043b\u0443\u0448\u0430\u0439 EXPOSE 8080 # \u0417\u0430\u043f\u0443\u0441\u043a \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 CMD web_server  # \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b distutils \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0447\u0442\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c #CMD python \/app\/src\/app.py<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0432 \u0445\u0430\u0431 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043b\u043e\u0433\u0438\u043d\u0438\u0442\u0441\u044f \u0432 \u0434\u043e\u043a\u0435\u0440\u0435, \u043d\u043e \u0442\u0430\u043a \u043a\u0430\u043a \u044f \u043d\u0435 \u0445\u043e\u0447\u0443 \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0441\u044c \u043c\u0438\u0440 \u0443\u0437\u043d\u0430\u043b \u043f\u0430\u0440\u043e\u043b\u044c \u043e\u0442 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 \u044f \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0441\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u043c\u0438 \u0432 GitHub \u0441\u0435\u043a\u0440\u0435\u0442\u0430\u043c\u0438. \u0412\u043e\u043e\u0431\u0449\u0435 \u0432 \u0441\u0435\u043a\u0440\u0435\u0442\u044b \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0430\u0440\u043e\u043b\u0438, \u0430 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u0437\u0430\u0445\u0430\u0440\u0434\u043a\u043e\u0434\u0438\u0442\u044c \u0432 *.yaml, \u0438 \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u041d\u043e \u043c\u043d\u0435 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u043a\u043e\u043f\u0438\u043f\u0430\u0441\u0442\u0438\u0442\u044c \u043c\u043e\u0438 \u044d\u043a\u0448\u0435\u043d\u044b \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439, \u0430 \u0432\u0441\u044e \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0442\u044c \u0438\u0437 \u0441\u0435\u043a\u0440\u0435\u0442\u043e\u0432.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/2c\/h8\/5t\/2ch85tdbuovsc7ywejh5gmrmvgw.png\"><br \/>  <em>\u0421\u0435\u043a\u0440\u0435\u0442\u044b \u0432 GitHub<\/em> <\/p>\n<p>  <\/p>\n<p>DOCKER_LOGIN \u2014 \u043b\u043e\u0433\u0438\u043d \u0432 hub.docker.com<br \/>  DOCKER_PWD \u2014 \u043f\u0430\u0440\u043e\u043b\u044c<br \/>  DOCKER_NAME \u2014 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0434\u043e\u043a\u0435\u0440 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 (\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0437\u0430\u0440\u0430\u043d\u0435\u0435)<\/p>\n<p>  <\/p>\n<p>\u041e\u043a\u0435\u0439, \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430, \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0430\u0448 \u0432\u0442\u043e\u0440\u043e\u0439 action:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ touch .github\/workflows\/pub_on_release.yaml<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0438\u0437 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e \u044d\u043a\u0448\u0435\u043d\u0430 \u0437\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 (\u044f \u043d\u0435 \u043d\u0430\u0448\u0435\u043b \u043a\u0430\u043a \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u043a\u0448\u0435\u043d\u044b). \u0415\u0433\u043e \u043c\u044b \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c \u043d\u0430 \u201c\u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438 \u0440\u0435\u043b\u0438\u0437\u0435\u201d:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">on:  release:    types: [published]<\/code><\/pre>\n<p>  <\/p>\n<p><strong>\u0412\u0410\u0416\u041d\u041e! \u041e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u0443\u0441\u043b\u043e\u0432\u0438\u0435 on.event<\/strong><br \/>  \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0435\u0441\u043b\u0438 on.release \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c types, \u0442\u043e \u044d\u0442\u043e\u0442 \u044d\u0432\u0435\u043d\u0442 \u0442\u0440\u0438\u0433\u0433\u0435\u0440\u0438\u0442 \u043a\u0430\u043a \u043c\u0438\u043d\u0438\u043c\u0443\u043c 2 \u0441\u043e\u0431\u044b\u0442\u0438\u044f: published \u0438 created. \u0422\u043e \u0435\u0441\u0442\u044c \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e \u0441\u0440\u0430\u0437\u0443 2 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0441\u0431\u043e\u0440\u043a\u0438.<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432 \u044d\u0442\u043e\u043c \u0436\u0435 \u0444\u0430\u0439\u043b\u0435 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0435\u0449\u0435 \u043e\u0434\u043d\u0443 \u0437\u0430\u0434\u0430\u0447\u0443 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u0443\u044e \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0439:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">build_and_pub:   needs: [run_tests]<\/code><\/pre>\n<p>  <\/p>\n<p>needs \u2014 \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u044d\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043d\u0435 \u043d\u0430\u0447\u043d\u0435\u0442\u0441\u044f, \u043f\u043e\u043a\u0430 \u043d\u0435 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u0441\u044f run_tests<\/p>\n<p>  <\/p>\n<p><strong>\u0412\u0410\u0416\u041d\u041e! \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0430\u0439\u043b\u043e\u0432 \u0441 \u044d\u043a\u0448\u0435\u043d\u0430\u043c\u0438, \u0438 \u0432\u043d\u0443\u0442\u0440\u0438 \u043d\u0438\u0445 \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0434\u0430\u0447 \u0442\u043e \u0432\u0441\u0435 \u043e\u043d\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0440\u0435\u0434\u0430\u0445.<\/strong> \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0441\u0440\u0435\u0434\u0430, \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u0430\u044f \u043e\u0442 \u0434\u0440\u0443\u0433\u0438\u0445 \u0437\u0430\u0434\u0430\u0447. \u0435\u0441\u043b\u0438 \u043d\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c needs \u0442\u043e \u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0441\u0431\u043e\u0440\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u044b \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0438 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430.<\/p>\n<p>  <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0448\u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">env:  LOGIN: ${{ secrets.DOCKER_LOGIN }}  NAME: ${{ secrets.DOCKER_NAME }}<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0448\u0430\u0433\u0438 \u043d\u0430\u0448\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438, \u0432 \u043d\u0438\u0445 \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0437\u0430\u043b\u043e\u0433\u0438\u043d\u0438\u0442\u0441\u044f \u0432 \u0434\u043e\u043a\u0435\u0440, \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0432 registry:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">steps:  - name: Login to docker.io    run:  echo ${{ secrets.DOCKER_PWD }} | docker login -u ${{ secrets.DOCKER_LOGIN }} --password-stdin  - uses: actions\/checkout@master  - name: Build image    run: docker build -t $LOGIN\/$NAME:${GITHUB_REF:11} -f Dockerfile .  - name: Push image to docker.io    run: docker push $LOGIN\/$NAME:${GITHUB_REF:11}<\/code><\/pre>\n<p>  <\/p>\n<p>${GITHUB_REF:11} \u2014 \u044d\u0442\u043e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f \u0433\u0438\u0442\u0445\u0430\u0431\u0430, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0441\u0442\u0440\u043e\u043a\u0430 \u0441 \u0440\u0435\u0444\u0435\u0440\u0435\u043d\u0441\u043e\u043c \u043d\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u043b \u0442\u0440\u0438\u0433\u0433\u0435\u0440(\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0432\u0435\u0442\u043a\u0438, \u0442\u044d\u0433 \u0438 \u0442.\u0434.), \u0435\u0441\u043b\u0438 \u0443 \u043d\u0430\u0441 \u0442\u044d\u0433\u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u0430 &#171;v0.0.0&#187; \u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u0440\u0435\u0437\u0430\u0442\u044c \u043f\u0435\u0440\u0432\u044b\u0435 11 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432, \u0442\u043e\u0433\u0434\u0430 \u043e\u0441\u0442\u0430\u043d\u0435\u0442\u0441\u044f &#171;0.0.0&#187;.<\/p>\n<p>  <\/p>\n<p>\u041f\u0443\u0448\u0438\u043c \u043a\u043e\u0434 \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u0442\u044d\u0433. \u0418 \u043c\u044b \u0432\u0438\u0434\u0438\u043c \u0442\u043e, \u0447\u0442\u043e \u043d\u0430\u0448 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0431\u044b\u043b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0431\u0440\u0430\u043d \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d \u0432 registry, \u0438 \u043c\u044b \u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u0437\u0430\u0441\u0432\u0435\u0442\u0438\u043b\u0438 \u0441\u0432\u043e\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/mg\/yt\/i-\/mgyti-mww_fcqsmbiqk6zli7swo.png\"><br \/>  <em>\u0421\u0431\u043e\u0440\u043a\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 Actions<\/em> <\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0445\u0430\u0431:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/ut\/nw\/vd\/utnwvdtcprbqfpavnnirqmqim0s.png\"><br \/>  <em>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0439 \u0432 docker hub \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440<\/em> <\/p>\n<p>  <\/p>\n<p>\u0412\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043d\u043e \u043e\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0441\u0430\u043c\u0430\u044f \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 deployment. \u0422\u0443\u0442 \u0443\u0436\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f VPS \u0438 \u0431\u0435\u043b\u044b\u0439 IP \u0430\u0434\u0440\u0435\u0441 \u043a\u0443\u0434\u0430 \u0431\u0443\u0434\u0435\u043c \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438 \u043a\u0443\u0434\u0430 \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0445\u0443\u043a. \u0412 \u0442\u0435\u043e\u0440\u0438\u0438 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 VPS \u0438\u043b\u0438 \u0434\u043e\u043c\u0430\u0448\u043d\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u043e \u043a\u0440\u043e\u043d\u0443 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043d\u0430\u043b\u0438\u0447\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0437\u0430 \u043f\u0443\u043b\u0438\u043b \u0431\u044b \u0435\u0433\u043e, \u0438\u043b\u0438 \u043a\u0430\u043a \u0442\u043e \u043f\u043e\u0438\u0433\u0440\u0430\u0442\u044c\u0441\u044f \u0441 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u0431\u043e\u0442\u043e\u043c. \u041d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430 \u0435\u0441\u0442\u044c \u043a\u0443\u0447\u0430 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c. \u041d\u043e \u044f \u0431\u0443\u0434\u0443 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c ip. \u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u043c\u0443\u0434\u0440\u0438\u0442\u044c, \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0432\u0435\u0431-\u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 Flask c \u043f\u0440\u043e\u0441\u0442\u044b\u043c API.<br \/>  \u0415\u0441\u043b\u0438 \u043a\u043e\u0440\u043e\u0442\u043a\u043e, \u0442\u043e \u0435\u0441\u0442\u044c 1 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 \u201c\/\u201d.<br \/>  GET \u0437\u0430\u043f\u0440\u043e\u0441 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 json \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u043c\u0438 \u043d\u0430 \u0445\u043e\u0441\u0442\u0435.<br \/>  POST \u2014 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435:<\/p>\n<p>  <\/p>\n<pre><code class=\"json\">{     \"owner\": \"\u043b\u043e\u0433\u0438\u043d \u0434\u043e\u043a\u0435\u0440 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430\",     \"repository\": \"\u0438\u043c\u044f \u0434\u043e\u043a\u0435\u0440 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f\",     \"tag\": \"v0.0.1\",  #\u0442\u044d\u0433 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0434\u043e \u0437\u0430\u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c     \"ports\": {\"8080\": 8080, \u201c443\u201d: 443} #\u043c\u0430\u043f\u0438\u043d\u0433 \u043f\u043e\u0440\u0442\u043e\u0432 \u043c\u0435\u0436\u0434\u0443 \u0445\u043e\u0441\u0442\u043e\u043c \u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u043c }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0427\u0442\u043e \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0445\u043e\u0441\u0442\u0435:<\/p>\n<p>  <\/p>\n<ol>\n<li>\u0438\u0437 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0433\u043e json \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0438\u043c\u044f \u043d\u043e\u0432\u043e\u0433\u043e image<\/li>\n<li>\u043f\u0443\u043b\u0438\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0439 \u043e\u0431\u0440\u0430\u0437<\/li>\n<li>\u0435\u0441\u043b\u0438 \u043e\u0431\u0440\u0430\u0437 \u0431\u044b\u043b \u0441\u043a\u0430\u0447\u0430\u043d, \u0442\u043e \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438 \u0443\u0434\u0430\u043b\u044f\u0435\u0442\u0441\u044f<\/li>\n<li>\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043d\u043e\u0432\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u0441 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0435\u0439 \u043f\u043e\u0440\u0442\u043e\u0432 (\u0444\u043b\u0430\u0433 -p)<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0412\u0441\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 \u0434\u043e\u043a\u0435\u0440\u043e\u043c \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u0435\u043d\u0430 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 <a href=\"https:\/\/docker-py.readthedocs.io\/en\/stable\/\">docker-py<\/a><\/p>\n<p>  <\/p>\n<p>\u0411\u044b\u043b\u043e \u0431\u044b \u043e\u0447\u0435\u043d\u044c \u043d\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0431\u0435\u0437 \u043a\u0430\u043a\u043e\u0439 \u043b\u0438\u0431\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0439 \u0437\u0430\u0449\u0438\u0442\u044b, \u0438 \u042f \u0441\u0434\u0435\u043b\u0430\u043b \u043f\u043e\u0434\u043e\u0431\u0438\u0435 API-KEY, \u0441\u0435\u0440\u0432\u0438\u0441 \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0438 \u043f\u043e\u0442\u043e\u043c \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0435\u0442 \u0435\u0433\u043e header\u2019\u043e\u043c {Authorization: CI_TOKEN}<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041b\u0438\u0441\u0442\u0438\u043d\u0433 \u0432\u0435\u0431-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\"># coding=utf-8 import os import sys import logging import logging.config import logging.handlers  from flask import Flask from flask import request, jsonify import docker  log = logging.getLogger(__name__) app = Flask(__name__) docker_client = docker.from_env() MY_AUTH_TOKEN = os.getenv('CI_TOKEN', None)  # \u0411\u0435\u0440\u0435\u043c \u043d\u0430\u0448 \u0442\u043e\u043a\u0435\u043d \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f  def init_logging():     \"\"\"     \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043b\u043e\u0433\u0433\u0435\u0440\u0430     :return:     \"\"\"     log_format = f\"[%(asctime)s] [ CI\/CD server ] [%(levelname)s]:%(name)s:%(message)s\"     formatters = {'basic': {'format': log_format}}     handlers = {'stdout': {'class': 'logging.StreamHandler',                            'formatter': 'basic'}}     level = 'INFO'     handlers_names = ['stdout']     loggers = {         '': {             'level': level,             'propagate': False,             'handlers': handlers_names         },     }     logging.basicConfig(level='INFO', format=log_format)     log_config = {         'version': 1,         'disable_existing_loggers': False,         'formatters': formatters,         'handlers': handlers,         'loggers': loggers     }     logging.config.dictConfig(log_config)  def get_active_containers():     \"\"\"     \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432     :return:     \"\"\"     containers = docker_client.containers.list()     result = []     for container in containers:         result.append({             'short_id': container.short_id,             'container_name': container.name,             'image_name': container.image.tags,             'created':  container.attrs['Created'],             'status':  container.status,             'ports':  container.ports,         })     return result  def get_container_name(item: dict) -&gt; [str, str]:     \"\"\"     \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0438\u043c\u0435\u043d\u0438 image \u0438\u0437 POST \u0437\u0430\u043f\u0440\u043e\u0441\u0430     :param item:     :return:     \"\"\"     if not isinstance(item, dict):         return ''     owner = item.get('owner')     repository = item.get('repository')     tag = item.get('tag', 'latest').replace('v', '')     if owner and repository and tag:         return f'{owner}\/{repository}:{tag}', repository     if repository and tag:         return f'{repository}:{tag}', repository     return '', ''  def kill_old_container(container_name: str) -&gt; bool:     \"\"\"     \u041f\u0435\u0440\u0435\u0434 \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u043c \u043d\u043e\u0432\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430, \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0441\u0442\u0430\u0440\u044b\u0439     :param container_name:     :return:     \"\"\"     try:         # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430         container = docker_client.containers.get(container_name)         # \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430         container.kill()     except Exception as e:         # \u041d\u0430 \u0441\u043b\u0443\u0447\u0430\u0439 \u0435\u0441\u043b\u0438 \u0442\u0430\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 \u043d\u0435\u0431\u044b\u043b\u043e         log.warning(f'Error while delete container {container_name}, {e}')         return False     finally:         # \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u044b\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u0430 \u0438\u043c\u0435\u043d         log.debug(docker_client.containers.prune())     log.info(f'Container deleted. container_name = {container_name}')     return True  def deploy_new_container(image_name: str, container_name: str, ports: dict = None):     try:         # \u041f\u0443\u043b \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e image \u0438\u0437 docker hub'a         log.info(f'pull {image_name}, name={container_name}')         docker_client.images.pull(image_name)         log.debug('Success')         kill_old_container(container_name)         log.debug('Old killed')         # \u0417\u0430\u043f\u0443\u0441\u043a \u043d\u043e\u0432\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430         docker_client.containers.run(image=image_name, name=container_name, detach=True, ports=ports)     except Exception as e:         log.error(f'Error while deploy container {container_name}, \\n{e}')         return {'status': False, 'error': str(e)}, 400     log.info(f'Container deployed. container_name = {container_name}')     return {'status': True}, 200  @app.route('\/', methods=['GET', 'POST']) def MainHandler():     \"\"\"     GET - \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0432\u0441\u0435\u0445 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432     POST - \u0434\u0435\u043f\u043b\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430     \u041f\u0440\u0438\u043c\u0435\u0440 \u0442\u0435\u043b\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430:     {         \"owner\": \"gonfff\",         \"repository\": \"ci_example\",         \"tag\": \"v0.0.1\",          \"ports\": {\"8080\": 8080}     }     :return:     \"\"\"     if request.headers.get('Authorization') != MY_AUTH_TOKEN:         return jsonify({'message': 'Bad token'}), 401     if request.method == 'GET':         return jsonify(get_active_containers())     elif request.method == 'POST':         log.debug(f'Recieved {request.data}')         image_name, container_name = get_container_name(request.json)         ports = request.json.get('ports') if request.json.get('ports') else None         result, status = deploy_new_container(image_name, container_name, ports)         return jsonify(result), status  def main():     init_logging()     if not MY_AUTH_TOKEN:         log.error('There is no auth token in env')         sys.exit(1)     app.run(host='0.0.0.0', port=5000)  if __name__ == '__main__':     main()<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u044f \u0442\u0430\u043a \u0436\u0435 \u0441\u0434\u0435\u043b\u0430\u043b setup.py \u0447\u0442\u043e\u0431\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0435\u0433\u043e \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ python3 setup.sy install<\/code><\/pre>\n<p>  <\/p>\n<p>\u043f\u0440\u0438 \u0443\u0441\u043b\u043e\u0432\u0438\u0438 \u0447\u0442\u043e \u0432\u044b \u0441\u043a\u0430\u0447\u0430\u043b\u0438 \u0444\u0430\u0439\u043b\u044b \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0435\u0441\u044c \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<br \/>  \u041f\u043e\u0441\u043b\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u043a \u0441\u0435\u0440\u0432\u0438\u0441 \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u043e\u0441\u044c \u0441\u0430\u043c\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f <a href=\"https:\/\/wiki.debian.org\/systemd\/Services\">systemd<\/a><br \/>  \u0412\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440 \u0444\u0430\u0439\u043b\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">[Unit] Description=Deployment web server After=network-online.target  [Service] Type=simple RestartSec=3 ExecStart=\/usr\/local\/bin\/ci_example Environment=CI_TOKEN=#&lt;I generate it with $(openssl rand -hex 20)&gt;  [Install] WantedBy=multi-user.target <\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0435\u0433\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ sudo systemctl daemon-reload $ sudo systemctl enable ci_example.service $ sudo systemctl start ci_example.service<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043b\u043e\u0433 \u0432\u0435\u0431 \u0441\u0435\u0440\u0432\u0435\u0432\u0440\u0430 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b <\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ sudo systemctl status ci_example.service<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u0430, \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0445\u0443\u043a \u043d\u0430\u0448\u0435\u043c\u0443 \u044d\u043a\u0448\u0435\u043d\u0443. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u0441\u0435\u043a\u0440\u0435\u0442\u044b ip-\u0430\u0434\u0440\u0435\u0441 \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 CI_TOKEN \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u043a\u043e\u0433\u0434\u0430 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.<br \/>  \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044f \u0445\u043e\u0442\u0435\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0443\u0436\u0435 \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u044d\u043a\u0448\u0435\u043d \u0434\u043b\u044f curl \u0438\u0437 \u043c\u0430\u0440\u043a\u0435\u0442\u043f\u043b\u044d\u0439\u0441\u0430 github, \u043d\u043e \u043a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u043e\u043d \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u043a\u0430\u0432\u044b\u0447\u043a\u0438 \u0438\u0437 \u0442\u0435\u043b\u0430 POST \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0447\u0442\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u043b\u043e \u043a \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0430 json. \u041c\u0435\u043d\u044f \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u043e \u044d\u0442\u043e \u043d\u0435 \u0443\u0441\u0442\u0440\u043e\u0438\u043b\u043e, \u0438 \u044f \u0440\u0435\u0448\u0438\u043b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u043c \u0432 ubuntu (\u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u044f \u0441\u043e\u0431\u0438\u0440\u0430\u044e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u044b) curl\u2019\u043e\u043c, \u0447\u0442\u043e \u043a\u0441\u0442\u0430\u0442\u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043d\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0441\u0431\u043e\u0440\u043a\u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">deploy:   needs: [build_and_pub]   runs-on: [ubuntu-latest]   steps:     - name: Set tag to env        run: echo ::set-env name=TAG::$(echo ${GITHUB_REF:11})     - name: Send webhook for deploy run: \"curl --silent --show-error --fail -X POST ${{ secrets.DEPLOYMENT_SERVER }} -H 'Authorization: ${{ secrets.DEPLOYMENT_TOKEN }}' -H 'Content-Type: application\/json' -d '{\\\"owner\\\": \\\"${{ secrets.DOCKER_LOGIN }}\\\", \\\"repository\\\": \\\"${{ secrets.DOCKER_NAME }}\\\", \\\"tag\\\": \\\"${{ env.TAG }}\\\", \\\"ports\\\": {\\\"8080\\\": 8080}}'\" <\/code><\/pre>\n<p>  <\/p>\n<p><strong>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435: \u043e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u043b\u044e\u0447 &#8212;fail, \u0438\u043d\u0430\u0447\u0435 \u043b\u044e\u0431\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u0432 \u043e\u0442\u0432\u0435\u0442 \u0431\u044b\u043b\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0430 \u043e\u0448\u0438\u0431\u043a\u0430. <\/strong><br \/>  \u0422\u0430\u043a\u0436\u0435 \u0441\u0442\u043e\u0438\u0442 \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0435, \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u043d\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u0430 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0437\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c GITHUB_REF \u0438\u0437-\u0437\u0430 \u0447\u0435\u0433\u043e \u044f \u0434\u043e\u043b\u0433\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0435 \u043c\u043e\u0433 \u043f\u043e\u043d\u044f\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0435 \u0432\u0435\u0440\u043d\u043e. \u041d\u043e \u0441\u0434\u0435\u043b\u0430\u0432 \u0438\u0437 \u043d\u0435\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u0432\u0441\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c.<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Action \u0441\u0431\u043e\u0440\u043a\u0438 \u0438 \u0434\u0435\u043f\u043b\u043e\u044f<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\">name: Publish on Docker Hub and Deploy  on:   release:     types: [published]   # \u0417\u0430\u043f\u0443\u0441\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d\u0438\u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u0440\u0435\u043b\u0438\u0437\u0430  jobs:   run_tests:     # \u041f\u0435\u0440\u0432\u0443\u044e \u0434\u0436\u043e\u0431\u0443 \u0441\u043c\u0435\u043b\u043e \u043c\u043e\u0436\u0435\u043c \u043a\u043e\u043f\u0438\u043f\u0430\u0441\u0442\u0438\u0442\u044c \u0438\u0437 \u044d\u043a\u0448\u0435\u043d\u0430 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f     runs-on: [ubuntu-latest]     steps:       # \u0427\u0435\u043a\u0430\u0443\u0442\u0438\u043c \u043a\u043e\u0434       - uses: actions\/checkout@master       # \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c python \u043d\u0443\u0436\u043d\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438       - uses: actions\/setup-python@v1         with:           python-version: '3.8'           architecture: 'x64'       - name: Install requirements         # \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438         run: pip install -r requirements.txt       - name: Run tests         # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0442\u0435\u0441\u0442\u044b         run: coverage run src\/tests.py       - name: Tests report         run: coverage report    build_and_pub:     # \u0415\u0441\u043b\u0438 \u0442\u0435\u0441\u0442\u044b \u0431\u044b\u043b\u0438 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e     needs: [run_tests]     runs-on: [ubuntu-latest]     env:       LOGIN: ${{ secrets.DOCKER_LOGIN }}       NAME: ${{ secrets.DOCKER_NAME }}     steps:       - name: Login to docker.io         # \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u043b\u043e\u0433\u0438\u043d\u0438\u043c\u0441\u044f \u0432 docker.io         run:  echo ${{ secrets.DOCKER_PWD }} | docker login -u ${{ secrets.DOCKER_LOGIN }} --password-stdin         # \u0427\u0435\u043a\u0430\u0443\u0442\u0438\u043c \u043a\u043e\u0434       - uses: actions\/checkout@master       - name: Build image         # \u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c image \u0438 \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u0442\u0430\u043a \u043a\u0430\u043a \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u0432 hub.docker \u0442.\u0435. login\/repository:version         run: docker build -t $LOGIN\/$NAME:${GITHUB_REF:11} -f Dockerfile .       - name: Push image to docker.io         # \u041f\u0443\u0448\u0438\u043c \u043e\u0431\u0440\u0430\u0437 \u0432 registry         run: docker push $LOGIN\/$NAME:${GITHUB_REF:11}    deploy:     # \u0415\u0441\u043b\u0438 \u043c\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0431\u0440\u0430\u043b\u0438 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043b\u0438 \u0432 registry, \u0442\u043e \u0434\u0435\u043b\u0430\u0435\u043c \u0445\u0443\u043a \u0434\u0435\u043f\u043b\u043e\u0439\u043c\u0435\u043d\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0443     # \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u044d\u043a\u0448\u0435\u043d curl \u0438\u0437 \u043c\u0430\u0440\u043a\u0435\u0442\u043f\u043b\u044d\u0439\u0441\u0430     needs: [build_and_pub]     runs-on: [ubuntu-latest]     steps:       - name: Set tag to env         run: echo ::set-env name=RELEASE_VERSION::$(echo ${GITHUB_REF:11})       - name: Send webhook for deploy         run: \"curl --silent --show-error --fail -X POST ${{ secrets.DEPLOYMENT_SERVER }} -H 'Authorization: ${{ secrets.DEPLOYMENT_TOKEN }}' -H 'Content-Type: application\/json' -d '{\\\"owner\\\": \\\"${{ secrets.DOCKER_LOGIN }}\\\", \\\"repository\\\": \\\"${{ secrets.DOCKER_NAME }}\\\", \\\"tag\\\": \\\"${{ env.RELEASE_VERSION }}\\\", \\\"ports\\\": {\\\"8080\\\": 8080}}'\"<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u041e\u043a\u0435\u0439, \u043c\u044b \u0441\u043e\u0431\u0440\u0430\u043b\u0438 \u0432\u0441\u0435 \u0432\u043c\u0435\u0441\u0442\u0435, \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u0440\u0435\u043b\u0438\u0437 \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u044d\u043a\u0448\u0435\u043d\u044b.<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/07\/dn\/5q\/07dn5qrqodyyfaulm6gpey7w8my.png\"><br \/>  <em>\u0412\u0435\u0431\u0445\u0443\u043a \u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u0434\u0435\u043f\u043b\u043e\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 Actions<\/em> <\/p>\n<p>  <\/p>\n<p>\u0412\u0441\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0435\u043c GET \u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u0434\u0435\u043f\u043b\u043e\u044f (\u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0432\u0441\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u044b \u043d\u0430 \u0445\u043e\u0441\u0442\u0435):<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/gq\/1w\/yg\/gq1wygbewjefugj8itjchn2ucki.png\"><br \/>  <em>\u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044b\u0439 \u043d\u0430 VPS \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440<\/em> <\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043a \u043d\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u0434\u0435\u043f\u043b\u043e\u0435\u043d\u043e\u043c\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e:<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/hs\/9x\/hz\/hs9xhzawrwliyoptcccjd-48bco.png\"><br \/>  <em>GET \u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0443<\/em> <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/jf\/fy\/xc\/jffyxcdf8zyzfylylyufv8wlcp0.png\" alt=\"POST \u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0443\">  <\/p>\n<p><em>POST \u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0443<\/em><\/p>\n<p>  <\/p>\n<p><strong>\u0412\u044b\u0432\u043e\u0434\u044b<\/strong><br \/>  GitHub Actions \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u044b\u0439 \u0438 \u0433\u0438\u0431\u043a\u0438\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u0432\u0435\u0449\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0447\u0435\u043d\u044c \u0441\u0438\u043b\u044c\u043d\u043e \u043c\u043e\u0433\u0443\u0442 \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u0436\u0438\u0437\u043d\u044c. \u0412\u0441\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442 \u0444\u0430\u043d\u0442\u0430\u0437\u0438\u0438.<br \/>  \u041e\u043d\u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 services.<br \/>  \u041a\u0430\u043a \u043b\u043e\u0433\u0438\u0447\u043d\u043e\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0435\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 webhook \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430.<br \/>  \u0412 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u044f \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u044e \u0432\u0437\u044f\u0442\u044c \u0437\u0430 \u043e\u0441\u043d\u043e\u0432\u0443 \u044d\u0442\u043e\u0442 \u043f\u0440\u043e\u0435\u043a\u0442 \u0434\u043b\u044f \u0434\u0435\u043f\u043b\u043e\u044f helm charts \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0443 \u0438\u0437\u0443\u0447\u0430\u0442\u044c \u0438 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 k8s<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u043a\u0430\u043a\u043e\u0439 \u0442\u043e \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u0439 \u043f\u0440\u043e\u0435\u043a\u0442, \u0442\u043e GitHub Actions \u043c\u043e\u0436\u0435\u0442 \u0441\u0438\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u043c .<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u0430 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 <a href=\"https:\/\/help.github.com\/en\/actions\/automating-your-workflow-with-github-actions\/workflow-syntax-for-github-actions\">\u0442\u0443\u0442<\/a><br \/>  \u0412\u0441\u0435 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 <a href=\"https:\/\/github.com\/dementevda\/actions_ci_example\">\u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a><\/p>\n<p><cut text=\"\u0427\u0438\u0442\u0430\u0442\u044c \u0434\u0430\u043b\u044c\u0448\u0435\"><\/cut><\/div>\n<p>               <script class=\"js-mediator-script\">!function(e){function t(t,n){if(!(n in e)){for(var r,a=e.document,i=a.scripts,o=i.length;o--;)if(-1!==i[o].src.indexOf(t)){r=i[o];break}if(!r){r=a.createElement(\"script\"),r.type=\"text\/javascript\",r.async=!0,r.defer=!0,r.src=t,r.charset=\"UTF-8\";var d=function(){var e=a.getElementsByTagName(\"script\")[0];e.parentNode.insertBefore(r,e)};\"[object Opera]\"==e.opera?a.addEventListener?a.addEventListener(\"DOMContentLoaded\",d,!1):e.attachEvent(\"onload\",d):d() } } }t(\"\/\/mediator.mail.ru\/script\/2820404\/\",\"_mediator\")}(window);<\/script>      <br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/476368\/\"> https:\/\/habr.com\/ru\/post\/476368\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\" id=\"post-content-body\">\n<p>\u041a\u0430\u043a \u0442\u043e \u0432\u0435\u0447\u0435\u0440\u043e\u043c, \u043f\u0440\u0438\u0434\u044f \u0434\u043e\u043c\u043e\u0439 \u0441 \u0440\u0430\u0431\u043e\u0442\u044b, \u044f \u0440\u0435\u0448\u0438\u043b \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0437\u0430\u043d\u0438\u043c\u0430\u0442\u044c\u0441\u044f \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c. \u042f \u0441\u0434\u0435\u043b\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0430\u0432\u043e\u043a \u0438 \u0441\u0440\u0430\u0437\u0443 \u0437\u0430\u0445\u043e\u0442\u0435\u043b \u043f\u043e\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 \u043d\u0438\u043c\u0438. \u041d\u043e \u0434\u043e \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u043e\u0432 \u043c\u043d\u0435 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0437\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u043d\u0430 VPS, \u043f\u0443\u043b\u0438\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u043f\u0435\u0440\u0435\u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0435\u0433\u043e. \u0422\u0443\u0442 \u044f \u0438 \u0440\u0435\u0448\u0438\u043b, \u0447\u0442\u043e \u043f\u043e\u0440\u0430 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0441 \u043d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u043e\u0439 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u043e\u0439.<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/xa\/l4\/dm\/xal4dmowtxwpuoxxrutujwwy4fm.jpeg\"><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-294816","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/294816","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=294816"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/294816\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=294816"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=294816"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=294816"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}