{"id":453150,"date":"2025-03-24T15:02:28","date_gmt":"2025-03-24T15:02:28","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=453150"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=453150","title":{"rendered":"<span>\u0423\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0435\u0442\u0435\u0432\u044b\u043c\u0438 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0432 \u0441\u0442\u0438\u043b\u0435 \u00abNetwork as Code\u00bb. \u0427\u0430\u0441\u0442\u044c 2<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0418 \u0441\u043d\u043e\u0432\u0430 \u043f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u044e \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c, \u043a\u0430\u043a\u00a0\u043c\u044b \u0441\u00a0\u043a\u043e\u043b\u043b\u0435\u0433\u0430\u043c\u0438 \u0440\u0435\u0448\u0430\u043b\u0438 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443 \u043f\u043e\u00a0\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043f\u0438\u0441\u043a\u0430\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u0432 \u043d\u0430\u00a0\u043f\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u043d\u044b\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430\u0445. \u0412\u00a0\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0435\u0447\u044c \u0448\u043b\u0430 \u043e\u00a0\u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u0438 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445. \u0422\u0435\u043f\u0435\u0440\u044c\u00a0\u0436\u0435 \u043f\u043e\u0433\u0440\u0443\u0437\u0438\u043c\u0441\u044f \u0432\u00a0\u043f\u0443\u0447\u0438\u043d\u0443 \u043a\u043e\u0434\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430\u00a0\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u043f\u043e\u043b\u0438\u0442\u0438\u043a \u043d\u0430\u00a0\u0441\u0435\u0442\u0435\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/536\/db9\/38a\/536db938abb74c51005cb13381cf466e.jpg\" width=\"1388\" height=\"901\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/536\/db9\/38a\/536db938abb74c51005cb13381cf466e.jpg\" data-blurred=\"true\"\/><\/figure>\n<ul>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/893494\/\" rel=\"noopener noreferrer nofollow\">\u0427\u0430\u0441\u0442\u044c 1\u00a0\u2014 \u041a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p>\u0427\u0430\u0441\u0442\u044c 2\u00a0\u2014 \u041a\u043e\u0434 (\u0432\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0435\u0441\u044c \u0442\u0443\u0442)<\/p>\n<\/li>\n<\/ul>\n<h3>Disclaimer<\/h3>\n<p>\u0412\u00a0\u0441\u0438\u043b\u0443 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u043e\u0431\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u0441\u0442\u0432 \u043d\u0435\u00a0\u043c\u043e\u0433\u0443 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043a\u043e\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f. \u0412\u0441\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438, \u0430\u0434\u0440\u0435\u0441\u0430, \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432, \u043f\u043b\u043e\u0449\u0430\u0434\u043e\u043a \u0438 \u0434\u0435\u043f\u0430\u0440\u0442\u0430\u043c\u0435\u043d\u0442\u043e\u0432\u00a0\u2014 \u0432\u044b\u043c\u044b\u0448\u043b\u0435\u043d\u044b.<\/p>\n<h3>\u041a\u0440\u0430\u0442\u043a\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0435\u0440\u0438\u0438<\/h3>\n<p>\u0422\u0435, \u043a\u0442\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043b \u043f\u0435\u0440\u0432\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0441\u0442\u0430\u0442\u044c\u0438, \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u043c\u043d\u0438\u0442\u044c, \u0447\u0442\u043e\u00a0\u0434\u043b\u044f\u00a0\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043c\u044b \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043c\u0438 \u00abNetwork as Code\u00bb. \u0412\u00a0Gitlab \u043c\u044b \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b\u0438 \u043d\u0430\u0431\u043e\u0440 \u0444\u0430\u0439\u043b\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0442\u0438\u043f\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432,<\/p>\n<\/li>\n<li>\n<p>\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0445\u043e\u0441\u0442\u043e\u0432 \u0438 \u0441\u0435\u0442\u0435\u0439,<\/p>\n<\/li>\n<li>\n<p>\u0441\u043f\u0438\u0441\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430,<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u0424\u0430\u0439\u043b\u043e\u0432 \u0432\u00a0\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043c\u043d\u043e\u0433\u043e, \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437\u00a0\u043d\u0438\u0445 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u043a\u0430\u043a\u00a0\u0437\u0430\u00a0\u043e\u0434\u0438\u043d \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440, \u0442\u0430\u043a \u0438 \u0437\u0430\u00a0\u043e\u0434\u043d\u0443 \u0438\u043b\u0438\u00a0\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043b\u043e\u043a\u0430\u0446\u0438\u0439. \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043d\u0430\u0434\u043e \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u044d\u0442\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432\u00a0\u043a\u043e\u043c\u0430\u043d\u0434\u044b CLI \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0438\u0445 \u043d\u0430\u00a0\u0441\u0435\u0442\u0435\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.<\/p>\n<h3>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 <\/h3>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437, \u043a\u043e\u0433\u0434\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440 \u0432\u043d\u043e\u0441\u0438\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432\u00a0YAML\u2011\u0444\u0430\u0439\u043b\u044b, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0449\u0438\u0435 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u043d\u0435\u043d\u0443\u043b\u0435\u0432\u0430\u044f \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c, \u0447\u0442\u043e\u00a0\u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0439\u0442\u0438 \u043d\u0435\u00a0\u0442\u0430\u043a:<\/p>\n<ul>\n<li>\n<p>\u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 YAML,<\/p>\n<\/li>\n<li>\n<p>\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435,<\/p>\n<\/li>\n<li>\n<p>\u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0435\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u043c,<\/p>\n<\/li>\n<li>\n<p>\u0432\u00a0\u043f\u0440\u0430\u0432\u0438\u043b\u0430\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0445\u043e\u0441\u0442\u0430,<\/p>\n<\/li>\n<li>\n<p>\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438\u00a0\u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043d\u0435\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043f\u0440\u0438\u043d\u044f\u0442\u044b\u043c Naming conventions.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u043d\u0430 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0445 \u044d\u0442\u0430\u043f\u0430\u0445,\u00a0\u0431\u044b\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u043a\u0440\u0438\u043f\u0442 \u043d\u0430\u00a0Python. \u0418\u0437\u043e\u0431\u0440\u0435\u0442\u0430\u0442\u044c \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434 \u043c\u044b \u043d\u0435\u00a0\u0441\u0442\u0430\u043b\u0438, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0448\u0438\u0440\u043e\u043a\u043e \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u043e\u0439 <a href=\"https:\/\/github.com\/keleshev\/schema\" rel=\"noopener noreferrer nofollow\">schema<\/a>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0434\u0430\u043d\u043d\u044b\u0445. \u0421\u0445\u0435\u043c\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u0441\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0441\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0439 YAML\u2011\u0444\u0430\u0439\u043b, \u0434\u043b\u044f\u00a0\u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0432\u044b\u043d\u0435\u0441\u0435\u043d\u0430 \u0432\u00a0\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b. \u041f\u0440\u0438\u00a0\u043d\u0430\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441\u043a\u0440\u0438\u043f\u0442 \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0432\u00a0\u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f \u0441\u00a0\u043e\u0448\u0438\u0431\u043a\u043e\u0439.<\/p>\n<pre><code class=\"python\"># code\/lint_state.py  import yaml import os from schema import Schema, SchemaError from state_scheme import scheme_dict  # \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0444\u0430\u0439\u043b\u043e\u0432 state \u0438\u0437 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 intended_state state_files = [] for root, dirs, files in os.walk('.\/intended_state', topdown = False):    for name in files:         state_files.append(os.path.join(root, name))  # \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043c YAML, \u0437\u0430\u043e\u0434\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u0447\u0442\u043e \u043d\u0435\u0442 \u043e\u0448\u0438\u0431\u043e\u043a, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u043c yaml_dict = {} for filename in state_files:     try:         with open(filename, 'r') as file:             yaml_data = yaml.safe_load(file)             print(f'{filename} - OK')     except yaml.YAMLError as exc:         print (f'{filename} - \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0435 \u0444\u0430\u0439\u043b\u0430\\n')         if hasattr(exc, 'problem_mark'):             if exc.context != None:                 print (f'  ERROR:\\n  {exc.problem_mark}\\n  {exc.problem} {exc.context}')             else:                 print (f'  ERROR:\\n  {exc.problem_mark}\\n  {exc.problem}')         else:             print (f'{filename} - \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0435 \u0444\u0430\u0439\u043b\u0430')         exit(1)      yaml_dict[filename] = yaml_data  # \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043d\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0441\u0445\u0435\u043c\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 output = '' success = True  for filename, yaml_data in yaml_dict.items():     yaml_schema = yaml_data.get('_metadata',{}).get('schema')     if yaml_schema not in scheme_dict:         success = False         output += f'{filename} - \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0444\u0430\u0439\u043b\u0430\\n'         output += f'\u0412 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0438\u043b\u0438 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u0430 schema \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438\\n'     try:         config_schema = Schema(scheme_dict[yaml_schema])         config_schema.validate(yaml_data)         output += f'{filename} - OK\\n'     except SchemaError as exc:         success = False         output += f'{filename} - \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0444\u0430\u0439\u043b\u0430\\n'         output += f'{exc}\\n'  # \u0432\u044b\u0432\u0435\u0434\u0435\u043c \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 print(output) if not success:     # \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a, \u0432\u044b\u0439\u0434\u0435\u043c \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439     exit(1)<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u0421\u0445\u0435\u043c\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 &#8212; code\/state_scheme.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\"># code\/state_scheme.py  from schema import Or, Regex, Optional import datetime  scheme_dict = {}  # \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f IP4_REGEX = r'^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$' IP4_NETWORK_REGEX = r'^(?:[0-9]{1,3}\\.){3}[0-9]{1,3} (255)\\.(0|128|192|224|240|248|252|254|255)\\.(0|128|192|224|240|248|252|254|255)\\.(0|128|192|224|240|248|252|254|255)$ ' IP4_RANGE_REGEX = r'^(?:[0-9]{1,3}\\.){3}[0-9]{1,3} (?:[0-9]{1,3}\\.){3}[0-9]{1,3}$' PORTS_REGEX = r'^(range [0-9]{2,5} [0-9]{2,5})|(eq|lt|gt) [0-9]{1,5}$'  metadata_scheme = {     Optional('filter'): {         Optional('location_types'): [str],         Optional('locations'): [str],         Optional('hosts'): [str],     },     'weight': int,     'description': str,     'is_active': bool,     'schema': Or('network_groups',                  'service_groups',                  'rules',                  'policy'                 ), }  scheme_dict['network_groups'] = {     '_metadata': metadata_scheme,     'network_groups': {         str: {             Optional('description'): str,             Optional('extend'): bool,             Optional('fqdn'): str,             Optional('networks'): [Regex(IP4_NETWORK_REGEX)],             Optional('hosts'): [Regex(IP4_REGEX)],             Optional('ranges'): [Regex(IP4_RANGE_REGEX)],             Optional('groups'): [str],         },     } }  scheme_dict['service_groups'] = {     '_metadata': metadata_scheme,     'service_groups': {         str: {             Optional('extend'): bool,             Optional('tcp'): [Regex(PORTS_REGEX)],             Optional('udp'): [Regex(PORTS_REGEX)],         },     } }  scheme_dict['rules'] = {     '_metadata': metadata_scheme,     'rules': {         str: {             Optional('extend'): bool,             Optional('lines'): [{                 Optional('sequence'): int,                 'action': Or('permit', 'deny'),                 'source': str,                 Optional('source_type'): str,                 'destination': str,                 Optional('destination_type'): str,                 'services': {                     Optional('protocol'): Or('ip',                                              'icmp',                                              'tcp',                                              'udp'),                     Optional('ports'): [int],                     Optional('group'): str,                 },                 Optional('reason'): str,                 Optional('expiry'): Or(datetime.date,                                        datetime.datetime),             }]         },     } }  scheme_dict['policy'] = {     '_metadata': metadata_scheme,     'name': Or('RM_NAT', 'ACL_NAT'),     'type': Or('route-map', 'acl'),     Optional('lines'): {         int: {             'rule': str,             'action': Or('permit', 'deny'),         }     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0441\u043f\u0438\u0441\u043a\u0430 \u0437\u0430\u0442\u0440\u043e\u043d\u0443\u0442\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 <\/h3>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a\u00a0\u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u0438\u00a0\u043a\u0430\u0436\u0434\u043e\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u043f\u043e\u043b\u0438\u0442\u0438\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043d\u0430\u00a0\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u044b, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u0447\u0435\u043d\u044c \u0442\u0435\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432, \u043d\u0430\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044d\u0442\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0433\u043b\u0438 \u043f\u043e\u0432\u043b\u0438\u044f\u0442\u044c. \u0414\u043b\u044f\u00a0\u044d\u0442\u043e\u0433\u043e \u0441\u043f\u0435\u0440\u0432\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432. \u0423\u00a0\u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0445\u044d\u0448 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0438\u043c, \u0447\u0442\u043e\u0431\u044b \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0435\u0433\u043e \u043d\u0430\u00a0\u0434\u0438\u0441\u043a:<\/p>\n<pre><code class=\"python\"># code\/get_changes.py  import sys from git import Repo from pathlib import Path  # \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0438 Path(Path.cwd() \/ 'artifacts').mkdir(parents=True, exist_ok=True) ARTIFACTS_PATH = Path.cwd() \/ 'artifacts'  # \u0445\u044d\u0448 \u043a\u043e\u043c\u043c\u0438\u0442\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u044b\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c COMMIT_HASH = sys.argv[1]  # \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 repo = Repo.init(Path.cwd()) changed_files = '' for filename in repo.commit(COMMIT_HASH).stats.files:     if filename.split('\/')[0] == 'intended_state' and filename.split('.')[-1] == 'yaml':         changed_files += f'{filename}\\n'  if not changed_files:     print('State \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f! \u0417\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0443')     exit(1) with open(f'{ARTIFACTS_PATH}\/changed_files.txt', 'w') as file:     # \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u044b     file.write(changed_files)<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f API Nautobot, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0442\u044c \u0435\u0433\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u043c\u0438 \u043b\u043e\u043a\u0430\u0446\u0438\u044f\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0442\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u043a\u00a0\u0444\u0430\u0439\u043b\u0430\u043c, \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u043c \u0432\u00a0\u0442\u0435\u043a\u0443\u0449\u0435\u043c \u043a\u043e\u043c\u043c\u0438\u0442\u0435. \u0412\u00a0\u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 API \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c GraphQL, \u0430\u00a0\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0432\u00a0\u0444\u0430\u0439\u043b\u0435 <em>inventory.yaml<\/em><\/p>\n<pre><code class=\"python\"># code\/prepare_inventory.py # \u0447\u0430\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u043e\u043f\u0443\u0449\u0435\u043d\u0430, \u0442\u0430\u043a \u043a\u0430\u043a \u0441\u0435\u0439\u0447\u0430\u0441 \u043d\u0435 \u0432\u0430\u0436\u043d\u0430  affected_items = {     'location_types': set(),     'locations': set(),     'hosts': set(), } affected_all = False for filename in changed_files:     with open(filename.strip(), 'r') as file:         yaml_data = yaml.safe_load(file)         if 'filter' not in yaml_data['_metadata']:             # \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u0432\u0441\u0435\u0445 \u0431\u0435\u0437 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f             affected_all = True             # \u0434\u0430\u043b\u044c\u0448\u0435 \u0447\u0442\u043e-\u0442\u043e \u0438\u0441\u043a\u0430\u0442\u044c \u043d\u0435\u0442 \u0441\u043c\u044b\u0441\u043b\u0430             break         for filter_name, filter_data in yaml_data['_metadata']['filter'].items():             for item in filter_data:                 affected_items[filter_name].add(item)  # \u0432\u044b\u0442\u0430\u0449\u0438\u043c \u0438\u0437 Nautobot \u0441\u043f\u0438\u0441\u043e\u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 nautobot_health = requests.get(f'https:\/\/{NAUTOBOT_URL}\/health', verify=False) if nautobot_health.status_code != 200:     print(\"\u041e\u0448\u0438\u0431\u043a\u0430! Nautobot \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d\")     exit(1)  query = ''' query {     devices (role: ['Router'], status: 'Active') {         name         primary_ip4 {             address         }         location {             name             location_type {                 name             }         }         rel_device_soft {             version             device_platform {               name             }         }          tags {           name         }     } } ''' nb = pynautobot.api(     url = f'https:\/\/{NAUTOBOT_URL}',     token = NAUTOBOT_TOKEN,     threading=True ) graphql_response = nb.graphql.query(query=query) devices = graphql_response.json['data']['devices'] affected_devices = {} affected_devices_for_test = {}  # \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u041e\u0421 ANSIBLE_NETWORK_OS = {     'Cisco IOS': 'ios',     'Mikrotik RouterOS': 'routeros', }  for device in devices:     if not device['primary_ip4']:         # \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0435\u0437 IP \u0430\u0434\u0440\u0435\u0441\u0430         continue     if (not device['rel_device_soft'] or       device['rel_device_soft']['platform']['name'] not in ANSIBLE_NETWORK_OS     ):         # \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043c \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0435 \u0438 \u043d\u0435\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u041e\u0421         continue      if (         affected_all or         device['location']['location_type']['name'] in affected_items['location_types'] or         device['location']['name'] in affected_items['locations'] or         device['name'] in affected_items['hosts']     ):         affected_devices[device['name']] = {             'address': device['primary_ip4']['address'].split('\/')[0],             'location': device['location']['name'],             'location_type': device['location']['location_type']['name'],             'platform': device['rel_device_soft']['platform']['name'],             'software_version': device['rel_device_soft']['version']         }  # \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c inventory \u0434\u043b\u044f ansible inventory = {     'all': {         'hosts': {}     } } for host, data in affected_devices.items():     inventory['all']['hosts'][host] = {         'ansible_host': data['address'],         'location': data['location'],         'location_type': data['location_type'],         'software_version': data.get('software_version'),         'ansible_connection': 'network_cli',         'ansible_network_os': ANSIBLE_NETWORK_OS[data['platform']],     }  with open(f'{INVENTORY_PATH}\/inventory.yaml', 'w') as file:     # \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0438\u043d\u0432\u0435\u043d\u0442\u043e\u0440\u0438 \u0432 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u044b     yaml.dump(inventory, file, allow_unicode=True)<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435: \u0432\u00a0\u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0430 \u043c\u044b \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0432\u0435\u0440\u0441\u0438\u044e \u041e\u0421\u00a0\u2014 \u044d\u0442\u043e \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f \u043d\u0430\u043c \u0432\u00a0\u0431\u0443\u0434\u0443\u0449\u0435\u043c<\/p>\n<h3>\u0421\u0431\u043e\u0440\u043a\u0430 \u043f\u043e\u043b\u0438\u0442\u0438\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/h3>\n<p>\u041a\u0430\u043a\u00a0\u043f\u0440\u0430\u0432\u0438\u043b\u043e, \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0434\u043b\u044f\u00a0\u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0438\u0437\u00a0\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432: \u044d\u0442\u043e \u043c\u043e\u0433\u0443\u0442\u00a0\u0431\u044b\u0442\u044c \u043e\u0431\u0449\u0438\u0435 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438, \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u043f\u043b\u043e\u0449\u0430\u0434\u043a\u0438 \u0438\u043b\u0438\u00a0\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u0434\u043b\u044f\u00a0\u0441\u0430\u043c\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u0414\u043b\u044f \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0438\u0442\u0438\u043a \u0432\u00a0\u0435\u0434\u0438\u043d\u043e\u0435 \u0446\u0435\u043b\u043e\u0435 \u0431\u0443\u0434\u0435\u043c \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0432\u0435\u0441\u0430, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0432\u00a0\u0440\u0430\u0437\u0434\u0435\u043b\u0435 <em>_metadata<\/em>, \u0438 \u043d\u0435\u00a0\u0437\u0430\u0431\u044b\u0432\u0430\u0435\u043c \u043f\u0440\u043e\u00a0\u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0443 <em>extend<\/em>. \u0412\u043e\u0442 \u0442\u0430\u043a, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442 \u0441\u043a\u0440\u0438\u043f\u0442\u0430 <em>code\/prepare_configs.py<\/em>, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0439 \u0437\u0430\u00a0\u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u0430\u0432\u0438\u043b (rules):<\/p>\n<pre><code class=\"python\"># code\/prepare_configs.py (\u043d\u0435 \u0432\u0435\u0441\u044c)  # \u0432\u044b\u0442\u0430\u0449\u0438\u043c \u0438\u0437 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u043e\u0432 \u0441\u043f\u0438\u0441\u043e\u043a  \u0444\u0430\u0439\u043b\u043e\u0432 all_files = [] for root, dirs, files in os.walk('intended_state', topdown = False):     for name in files:         all_files.append(os.path.join(root, name))  # \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0441\u043b\u043e\u0432\u0430\u0440\u044c \u043f\u043e\u043b\u0438\u0442\u0438\u043a intended_schema = defaultdict(dict) for filename in all_files:     with open(filename.strip(), 'r') as file:         yaml_data = yaml.safe_load(file)         if yaml_data['_metadata'].get('is_active'):             intended_schema[yaml_data['_metadata']['schema']][filename.strip()] = yaml_data  # \u0441\u043e\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0430 if 'rules' in intended_schema:     for host, host_data in inventory['all']['hosts'].items():         location = host_data['location']          # \u043e\u0442\u0441\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u043f\u043e \u0432\u0435\u0441\u0430\u043c         weighted_dict = {}         for current_state in intended_schema['rules'].values():             if (                 host_data[\u2018location_type\u2019] in current_state['_metadata'].get('location_types',[]) or                 host in current_state['_metadata'].get('hosts',[]) or                 host_data['location'] in current_state['_metadata'].get('locations',[])             ):                 weighted_dict[current_state['_metadata']['weight']] = copy.deepcopy(current_state)          # \u0432\u044b\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0441 \u0441\u0430\u043c\u044b\u043c \u0431\u043e\u043b\u044c\u0448\u0438\u043c \u0432\u0435\u0441\u043e\u043c \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0430         rendered_state = {'rules': {}}         for weight, current_state in sorted(weighted_dict.items()):             if weight &gt; rendered_state.get('weight', 0):                 for rule_name, rule_data in current_state['rules'].items():                     if rule_data.get('extend') and rule_name in rendered_state['rules']:                         rendered_state['rules'][rule_name]['lines'].extend(rule_data.get('lines',[]))                     else:                         rendered_state['rules'][rule_name] = rule_data<\/code><\/pre>\n<p>\u0412\u00a0\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0434\u043b\u044f\u00a0\u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0441\u043b\u043e\u0432\u0430\u0440\u044c \u0441\u00a0\u0438\u0442\u043e\u0433\u043e\u0432\u043e\u0439 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u043e\u0439 \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0435\u0439 \u043f\u0440\u0430\u0432\u0438\u043b\u0430, \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0438 \u0445\u043e\u0441\u0442\u043e\u0432. \u041d\u0430\u00a0\u044d\u0442\u043e\u043c \u0440\u0430\u0431\u043e\u0442\u0430 \u0441\u00a0vendor\u2011agnostic \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430, \u0438 \u043f\u0440\u0438\u0448\u043b\u0430 \u043f\u043e\u0440\u0430 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u0432\u00a0\u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f\u00a0\u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0439 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043d\u0430\u00a0\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.<\/p>\n<h3>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0438 \u043a\u043e\u043c\u0430\u043d\u0434 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438<\/h3>\n<p>\u0421\u043e\u0433\u043b\u0430\u0441\u043d\u043e\u00a0\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e\u043c\u0443 dataflow (\u0441\u043c. \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0443\u044e \u0447\u0430\u0441\u0442\u044c), \u043d\u0430\u043c \u043f\u0440\u0435\u0434\u0441\u0442\u043e\u0438\u0442 \u0441\u043f\u0435\u0440\u0432\u0430 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u043d\u0443\u044e \u0446\u0435\u043b\u0435\u0432\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e, \u0437\u0430\u0442\u0435\u043c \u0441\u0440\u0430\u0432\u043d\u0438\u0442\u044c \u0435\u0435 \u0441\u00a0\u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0438 \u0434\u043b\u044f\u00a0\u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u043d\u0430\u0431\u043e\u0440 \u043a\u043e\u043c\u0430\u043d\u0434, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u0434\u043b\u044f\u00a0\u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043d\u0430\u00a0\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043a\u00a0\u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u043c\u0443 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e.<\/p>\n<p>\u0414\u043b\u044f\u00a0\u043d\u0430\u0441 \u043f\u0440\u043e\u0449\u0435 \u0432\u0441\u0435\u0433\u043e\u00a0\u0431\u044b\u043b\u043e \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u0441\u043b\u043e\u0432\u0430\u0440\u044c \u0441\u00a0\u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0432\u00a0\u043a\u0443\u0441\u043e\u043a \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u00a0\u043f\u043e\u043c\u043e\u0449\u044c\u044e Python \u0438 Jinja2. \u0412\u00a0\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043c\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u0441\u00a0\u043e\u0434\u043d\u0438\u043c \u043d\u044e\u0430\u043d\u0441\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435\u043b\u044c\u0437\u044f \u043d\u0435\u00a0\u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c. \u0412\u00a0Cisco IOS \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 17\u00a0\u0432\u0435\u0440\u0441\u0438\u0438 \u0432\u00a0\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 ACL \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f sequence. \u0410\u00a0\u0434\u043e 16\u00a0\u2014 \u043d\u0435\u00a0\u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0438\u00a0\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043c\u044b \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u0432\u0435\u0440\u0441\u0438\u044e \u041f\u041e, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043d\u0430\u00a0\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440. \u0418\u043c\u0435\u043d\u043d\u043e \u0434\u043b\u044f\u00a0\u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b\u0438 \u0432\u0435\u0440\u0441\u0438\u044e \u0432\u00a0\u0434\u0430\u043d\u043d\u044b\u0445 \u0445\u043e\u0441\u0442\u0430 \u0432\u00a0\u0444\u0430\u0439\u043b\u0435 inventory.<\/p>\n<pre><code class=\"python\"># code\/prepare_configs.py (\u043d\u0435 \u0432\u0435\u0441\u044c)  # sequence \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0432\u0435\u0440\u0441\u0438\u0438 \u041f\u041e, \u043d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 17 \u0432\u0435\u0440\u0441\u0438\u0438 IOS - sequence \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u0430 # \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0448\u0430\u0433=10 default_step = 10 need_sequence = host_data['software_version'] and host_data['software_version'].split('.')[0] == '17'  for rule in rendered_state['rules']:     step = default_step     for line in rendered_state['rules'][rule]['lines']:         if need_sequence and not line.get('sequence'):             # \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c sequence, \u0432\u0435\u0434\u044c \u043e\u043d \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c, \u0430 \u0435\u0433\u043e \u043d\u0435\u0442             line['sequence'] = step             step += default_step         if not need_sequence and line.get('sequence'):             # \u0443\u0434\u0430\u043b\u0438\u043c sequence \u0438\u0437 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445             line.pop('sequence', None)          # \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433 template = env.get_template('rules.j2') host_intended_config = template.render({             'rules': rendered_state['rules'], })  # \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0446\u0435\u043b\u0435\u0432\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433 \u0432 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u044b \u0434\u043b\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0430 with open(f'{INTENDED_CONFIG_PATH}\/{host}_rules.conf', 'w') as file:     file.write(host_intended_config)<\/code><\/pre>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0448\u0430\u0433\u00a0\u2014 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0439 \u0438 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0439. \u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 running\u2011config \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u00a0\u043d\u0430\u0441 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432\u00a0\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0430\u00a0\u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u043c Gitlab, \u0442\u0430\u043a \u0447\u0442\u043e\u00a0\u0432\u044b\u0442\u0430\u0449\u0438\u0442\u044c \u0435\u0433\u043e \u043d\u0435\u00a0\u0441\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0442\u0440\u0443\u0434\u0430. \u0414\u043b\u044f\u00a0\u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0439 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043c\u043e\u0434\u0443\u043b\u044c Compliance, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u0445\u043e\u0434\u0438\u0442 \u0432\u00a0\u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u0435\u0439\u0448\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/netutils.readthedocs.io\/en\/latest\/\" rel=\"noopener noreferrer nofollow\">Netutils <\/a> \u043e\u0442\u00a0network.toCode. \u041d\u0430\u00a0\u0432\u0445\u043e\u0434 \u043f\u043e\u0434\u0430\u0435\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0432\u0435\u043d\u0434\u043e\u0440\u0430 (\u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442\u00a0\u0431\u044b\u0442\u044c Cisco, Huawei \u0438 \u0434\u0430\u0436\u0435 Mikrotik), \u0441\u0435\u043a\u0446\u0438\u044e, \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043a\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f, \u0438 \u0434\u0432\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438\u00a0\u2014 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0438 \u0446\u0435\u043b\u0435\u0432\u0443\u044e. \u041d\u0430\u00a0\u0432\u044b\u0445\u043e\u0434\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0432\u0430 \u043d\u0430\u0431\u043e\u0440\u0430 \u043a\u043e\u043c\u0430\u043d\u0434,\u00a0\u043b\u0438\u0448\u043d\u0438\u0435 \u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435. \u0412\u043e\u0442 \u0442\u0430\u043a \u043b\u0435\u0433\u043a\u043e \u0438 \u044d\u043b\u0435\u0433\u0430\u043d\u0442\u043d\u043e \u0434\u0432\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 ACL \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u044e\u0442\u0441\u044f \u0432\u00a0\u0441\u043f\u0438\u0441\u043e\u043a \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u043a\u043e\u043c\u0430\u043d\u0434 \u0434\u043b\u044f\u00a0\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432 Cisco:<\/p>\n<pre><code class=\"python\">from netutils.config.compliance import compliance features = [      {          'name': 'ACL_TEST_NAT',          'ordered': True,          'section': ['ip access-list extended ACL_TEST_NAT']      },  ] host_intended_config = ''' ip access-list extended ACL_TEST_NAT  permit icmp any host 8.8.8.8  ''' host_actual_config = ''' ip access-list extended ACL_TEST_NAT  permit tcp any host 8.8.8.8 eq 53  permit udp any host 8.8.8.8 eq 53 '''  network_os = 'cisco_ios' diff = compliance(features, host_actual_config, host_intended_config, network_os, 'string')  # output pprint(diff[features[0][name]]) #  # {'actual': 'ip access-list extended ACL_TEST_NAT\\n' #            ' permit tcp any host 8.8.8.8 eq 53\\n' #            ' permit udp any host 8.8.8.8 eq 53', #  'cannot_parse': True, #  'compliant': False, #  'extra': 'ip access-list extended ACL_TEST_NAT\\n' #           ' permit tcp any host 8.8.8.8 eq 53\\n' #           ' permit udp any host 8.8.8.8 eq 53', #  'intended': 'ip access-list extended ACL_TEST_NAT\\n' #              ' permit icmp any host 8.8.8.8', #  'missing': 'ip access-list extended ACL_TEST_NAT\\n' #             ' permit icmp any host 8.8.8.8', #  'ordered_compliant': False, #  'unordered_compliant': False}<\/code><\/pre>\n<p>\u0426\u0435\u043b\u0435\u0432\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e (<em>host_intended_config<\/em>) \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0432 \u0440\u0435\u043d\u0434\u0435\u0440 \u0432\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0448\u0430\u0431\u043b\u043e\u043d Jinja, \u0430\u00a0\u0442\u0435\u043a\u0443\u0449\u0443\u044e (<em>host_actual_config<\/em>) \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043b\u0438 \u0438\u0437\u00a0\u0444\u0430\u0439\u043b\u0430 \u0431\u044d\u043a\u0430\u043f\u0430. \u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u00a0\u0444\u0430\u0439\u043b \u043a\u043e\u043c\u0430\u043d\u0434\u044b.\u00a0\u041b\u0438\u0448\u043d\u0438\u0435 (extra) \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0442\u043e\u0436\u0435, \u043d\u043e\u00a0\u0443\u0436\u0435 \u0441\u00a0\u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c \u0441\u043b\u043e\u0432\u043e\u043c <em>no <\/em>\u0432\u00a0\u043d\u0430\u0447\u0430\u043b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>code\/prepare_configs.py (\u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f Cisco)<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\"># code\/prepare_configs.py (\u043d\u0435 \u0432\u0435\u0441\u044c)  from netutils.config.compliance import compliance  # \u0432\u044b\u0442\u0430\u0449\u0438\u043c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0444\u0438\u0433 host_actual_config = '' with open(f'{CURRENT_STATE_PATH}\/{site}\/{host}.conf', 'r') as file:     host_actual_config = file.read()  # \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u041e\u0421 COMLIANCE_NETWORK_OS = 'cisco_ios' host_diff_config = '' features = []  # \u0441\u043f\u0438\u0441\u043e\u043a ACL \u0434\u043b\u044f \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f for rule_name, rule_data in rendered_state['rules'].items():     features.append({         'name': f'ip access-list extended {rule_name}',         'ordered': True,         'section': [f'ip access-list extended {rule_name}']     }) host_compliance = compliance(     features,     host_actual_config,     host_intended_config,         COMLIANCE_NETWORK_OS,         'string' )  # \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e ACL \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044f for feature in features:     feature_compliance = host_compliance[feature['name']]     if not feature_compliance['compliant']:         # \u0435\u0441\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u0438\u044f \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0445         if feature_compliance['extra']:             # \u043b\u0438\u0448\u043d\u0438\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u043d\u0430\u0447\u0430\u043b\u043e no             for line in feature_compliance['extra'].splitlines():                 if line[:1] == ' ':                     host_diff_config += f' no {line[1:].strip()}\\n'                 else:                     host_diff_config += line.strip() + '\\n'         if feature_compliance['missing']:             # \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u044e\u0449\u0438\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438             host_diff_config += feature_compliance['missing'] + '\\n'  # \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0432 \u0444\u0430\u0439\u043b \u0434\u043b\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0434\u0435\u043f\u043b\u043e\u044f \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e if host_diff_config:     with open(f'{DIFF_CONFIG_PATH}\/{host}_rules.conf', 'w') as file:         file.write(host_diff_config)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e\u00a0\u0431\u044d\u043a\u0430\u043f\u044b \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432, \u0442\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u043d\u043e\u0441\u0442\u044c. \u041f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u043d\u0435\u00a0\u0441\u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 diff, \u0438 \u043d\u0430\u00a0\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435\u00a0\u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u044b \u043d\u0438\u043a\u0430\u043a\u0438\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b.<\/p>\n<h3>\u0414\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0439<\/h3>\n<p>\u041d\u0443 \u0432\u043e\u0442, \u0441\u0430\u043c\u0430\u044f \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u044b\u00a0\u0431\u044b\u043b\u0430 \u0441\u0434\u0435\u043b\u0430\u043d\u0430, \u043e\u0441\u0442\u0430\u0432\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430\u00a0\u043d\u0443\u0436\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u042d\u0442\u043e \u043c\u044b \u0443\u0436\u0435 \u0443\u043c\u0435\u043b\u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u00a0\u043f\u043e\u043c\u043e\u0449\u044c\u044e Ansible, \u043f\u043e\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043b\u043e\u0441\u044c \u043b\u0438\u0448\u044c \u0441\u043b\u0435\u0433\u043a\u0430 \u043f\u0435\u0440\u0435\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043f\u043b\u0435\u0439\u0431\u0443\u043a. \u0412\u00a0\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u043a\u0440\u0438\u043f\u0442\u0430 <em>code\/prepare_configs.py<\/em> \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0444\u0430\u0439\u043b\u044b \u0441\u00a0\u043a\u043e\u043c\u0430\u043d\u0434\u0430\u043c\u0438 \u0434\u043b\u044f\u00a0\u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430, \u043d\u0430\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u043d\u0435\u0441\u0442\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. \u041f\u043b\u0435\u0439\u0431\u0443\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0435\u0433\u043e \u043d\u0430\u00a0\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.<\/p>\n<details class=\"spoiler\">\n<summary>ansible\/playbook.yml (\u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f Cisco)<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\">--- - name: \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0440\u0430\u0432\u0438\u043b NAT   hosts: all   gather_facts: false   vars:     artifacts_path: artifacts\/configs\/diff    tasks:    - name: \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u044f service object group \u0434\u043b\u044f \u0445\u043e\u0441\u0442\u0430     stat:       path: \"{{ artifacts_path }}\/{{ inventory_hostname }}_service_groups.conf\"     register: host_specific_service_og_config    - name: \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u044f network object group \u0434\u043b\u044f \u0445\u043e\u0441\u0442\u0430     stat:       path: \"{{ artifacts_path }}\/{{ inventory_hostname }}_network_groups.conf\"     register: host_specific_network_og_config    - name: \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u044f rules (ACL) \u0434\u043b\u044f \u0445\u043e\u0441\u0442\u0430     stat:       path: \"{{ artifacts_path }}\/{{ inventory_hostname }}_rules.conf\"     register: host_specific_rules_config    - name: \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u044f policy \u0434\u043b\u044f \u0445\u043e\u0441\u0442\u0430     stat:       path: \"{{ artifacts_path }}\/{{ inventory_hostname }}_policy.conf\"     register: host_specific_policy_config    - name: \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 service object group      ios_config:       src: \"..\/{{ artifacts_path }}\/{{ inventory_hostname }}_service_groups.conf\"     when: host_specific_service_og_config.stat.exists    - name: \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 network object group      ios_config:       src:  \"..\/{{ artifacts_path }}\/{{ inventory_hostname }}_network_groups.conf\"     when: host_specific_network_og_config.stat.exists    - name: \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 rules (ACL)      ios_config:       src:  \"..\/{{ artifacts_path }}\/{{ inventory_hostname }}_rules.conf\"     when: host_specific_rules_config.stat.exists    - name: \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 policy (route-map)      ios_config:       src:  \"..\/{{ artifacts_path }}\/{{ inventory_hostname }}_policy.conf\"     when: host_specific_policy_config.stat.exists    - name: \u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438      ios_config:       save_when: modified <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0432\u0441\u0435 \u0432\u043e\u0435\u0434\u0438\u043d\u043e &#8212; Gitlab CI\/CD<\/h3>\n<p>\u041a\u00a0\u044d\u0442\u043e\u043c\u0443 \u043c\u043e\u043c\u0435\u043d\u0442\u0443 \u043c\u044b:<\/p>\n<ol>\n<li>\n<p>\u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0432\u00a0\u0444\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u043c \u0432\u0438\u0434\u0435,<\/p>\n<\/li>\n<li>\n<p>\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043b\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u0438\u0442\u0438\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u0430,<\/p>\n<\/li>\n<li>\n<p>\u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u0446\u0435\u043b\u0435\u0432\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0434\u043b\u044f\u00a0\u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0430,<\/p>\n<\/li>\n<li>\n<p>\u0441\u0440\u0430\u0432\u043d\u0438\u043b\u0438 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0443\u044e \u0438 \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0438 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u043d\u0430\u0431\u043e\u0440 \u043a\u043e\u043c\u0430\u043d\u0434 \u0434\u043b\u044f\u00a0\u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u043a\u00a0\u043d\u0443\u0436\u043d\u043e\u043c\u0443 \u0432\u0438\u0434\u0443,<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0445\u043e\u0441\u0442\u043e\u0432, \u043d\u0430\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f,<\/p>\n<\/li>\n<li>\n<p>\u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 Ansible playbook, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043d\u0430\u00a0\u0441\u0435\u0442\u0435\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.<\/p>\n<\/li>\n<\/ol>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432\u0441\u0435 \u044d\u0442\u0438 \u044d\u0442\u0430\u043f\u044b \u043d\u0430\u0434\u043e\u00a0\u0431\u044b\u043b\u043e \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u0432\u00a0\u0437\u0430\u043a\u043e\u043d\u0447\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u00a0\u043a\u0430\u0436\u0434\u043e\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u043f\u043e\u043b\u0438\u0442\u0438\u043a \u0432\u00a0\u043d\u0430\u0448\u0435\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u0432\u0441\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043b\u0438\u0441\u044c \u043f\u043e\u00a0\u043f\u043e\u0440\u044f\u0434\u043a\u0443. \u0414\u043b\u044f\u00a0\u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043f\u043e\u0433\u0440\u0443\u0437\u0438\u0442\u044c\u0441\u044f \u0432\u00a0DevOps.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/4f8\/96d\/f34\/4f896df34d680e04926308078d504110.jpeg\" width=\"792\" height=\"747\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4f8\/96d\/f34\/4f896df34d680e04926308078d504110.jpeg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041d\u0435\u00a0\u0431\u0443\u0434\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u00a0\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 Gitlab \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 gitlab\u2011runners, \u044d\u0442\u043e \u0437\u0430\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438, \u0438 \u043d\u0435\u00a0\u043e\u0434\u043d\u043e\u0439. \u0415\u0441\u043b\u0438 \u043d\u0435\u00a0\u0440\u0430\u0441\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0432\u0441\u044e \u043c\u0430\u0433\u0438\u044e DevOps, \u0442\u043e \u0441\u00a0\u0442\u043e\u0447\u043a\u0438 \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u0438\u043d\u0436\u0435\u043d\u0435\u0440\u0430 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<ul>\n<li>\n<p>\u0432\u043d\u043e\u0441\u0438\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432\u00a0YAML (\u043a\u043e\u043c\u043c\u0438\u0442\u0438\u043c \u0432\u00a0\u0432\u0435\u0442\u043a\u0443 test \u0438\u043b\u0438\u00a0master),<\/p>\n<\/li>\n<li>\n<p>\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0432\u0441\u0435\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441\u00a0\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430\u043c\u0438,<\/p>\n<\/li>\n<li>\n<p>\u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441\u043f\u0438\u0441\u043e\u043a \u0445\u043e\u0441\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u0430\u0441\u0430\u044e\u0442\u0441\u044f \u0432\u043d\u0435\u0441\u0435\u043d\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f,<\/p>\n<\/li>\n<li>\n<p>\u0434\u043b\u044f\u00a0\u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0430 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u0441\u00a0\u043a\u043e\u043c\u0430\u043d\u0434\u0430\u043c\u0438 \u0434\u043b\u044f\u00a0\u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043d\u0430\u00a0\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e,<\/p>\n<\/li>\n<li>\n<p>\u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u0430\u00a0\u0446\u0435\u043b\u0435\u0432\u044b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u044b,<\/p>\n<\/li>\n<li>\n<p>\u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0431\u044d\u043a\u0430\u043f\u043e\u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432 \u0432\u00a0Gitlab.<\/p>\n<\/li>\n<\/ul>\n<p>\u042d\u0442\u0430\u043f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043d\u0430\u0431\u043e\u0440\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430\u00a0\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u0447\u0442\u043e\u0431\u044b\u00a0\u0431\u044b\u043b\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0441\u00a0\u043d\u0430\u0431\u043e\u0440\u0430\u043c\u0438 \u043a\u043e\u043c\u0430\u043d\u0434. \u041d\u043e\u00a0\u0435\u0441\u043b\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432\u00a0\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439\u00a0\u0431\u044b\u043b\u0438 \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0434\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u043c \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u043c, \u0442\u043e \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043d\u0430\u00a0\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438. <\/p>\n<p>\u0417\u0430\u00a0\u0432\u0441\u0435 \u044d\u0442\u043e \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c Gitlab CI\/CD, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u00a0\u0444\u0430\u0439\u043b\u0435.gitlab\u2011ci.yml<\/p>\n<details class=\"spoiler\">\n<summary>.gitlab-ci.yml<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\">stages:   - lint   - prepare   - deploy  # \u0428\u0410\u0411\u041b\u041e\u041d\u042b .default-stage-template:   image: network\/ansible:2.13-22.04   tags:     - network-runner  .prepare-stage-template:   extends:     - .default-stage-template   script:     - python3 .\/code\/get_changes.py $CI_COMMIT_SHORT_SHA     - python3 .\/code\/prepare_inventory.py     - python3 .\/code\/prepare_configs.py   artifacts:     paths:       - artifacts\/     expire_in: 1 week  .deploy-stage-template:   extends:     - .default-stage-template   script:     - cp ansible\/ansible.cfg \/etc\/ansible\/     - ansible-playbook ansible\/playbook.yaml   after_script:     - python3 .\/code\/update_current_state.py  # LINT \u2013 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f yaml-lint:   # \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 yaml-\u0444\u0430\u0439\u043b\u043e\u0432   # \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u0435 \u0432 master \u0438\u043b\u0438 test, \u0435\u0441\u043b\u0438 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f intended state   stage: lint   extends:     - .default-stage-template   script:     - python3 .\/code\/lint_state.py   rules:     - if: ($CI_PIPELINE_SOURCE == \"push\" &amp;&amp; ($CI_COMMIT_BRANCH == \"test\" || $CI_COMMIT_BRANCH == \"master\")) || $CI_PIPELINE_SOURCE == \"web\"       changes:         - intended_state\/**\/*.yaml  # PREPARE \u2013 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 inventory \u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0439 prepare-artifacts-test:   # \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u0432 \u0438 inventory-\u0444\u0430\u0439\u043b\u0430 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0440\u043e\u0443\u0442\u0435\u0440\u043e\u0432   # \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u0435 \u0432 \u0432\u0435\u0442\u043a\u0443 test, \u0435\u0441\u043b\u0438 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f intended state   stage: prepare   extends:     - .prepare-artifacts-template   environment:     name: test   rules:     - if: $CI_PIPELINE_SOURCE == \"push\" &amp;&amp; $CI_COMMIT_BRANCH == \"test\"       changes:         - intended_state\/**\/*.yaml  prepare-artifacts:   # \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u0432 \u0438 inventory-\u0444\u0430\u0439\u043b\u0430 \u0434\u043b\u044f \u0440\u043e\u0443\u0442\u0435\u0440\u043e\u0432, \u0443 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f state   # \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u0435 \u0432 master, \u0435\u0441\u043b\u0438 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f intended state   stage: prepare   extends:     - .prepare-artifacts-template   environment:     name: prod   rules:     - if: $CI_PIPELINE_SOURCE == \"push\" &amp;&amp; $CI_COMMIT_BRANCH == \"master\"       changes:         - intended_state\/**\/*.yaml  prepare-artifacts-all:   # \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u043e\u0432 \u0438 inventory-\u0444\u0430\u0439\u043b\u0430 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0440\u043e\u0443\u0442\u0435\u0440\u043e\u0432   # \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 pipeline \u0447\u0435\u0440\u0435\u0437 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441   stage: prepare   extends:     - .prepare-artifacts-template   environment:     name: manual   rules:     - if: $CI_PIPELINE_SOURCE == \"web\"  # DEPLOY - \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430 \u0440\u043e\u0443\u0442\u0435\u0440\u044b deploy-test:   # \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0440\u043e\u0443\u0442\u0435\u0440\u044b   # \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u0435 \u0432 \u0432\u0435\u0442\u043a\u0443 test   # \u0437\u0430\u043f\u0443\u0441\u043a \u0432\u0440\u0443\u0447\u043d\u0443\u044e   stage: deploy   extends:     - .deploy-stage-template   environment:     name: test   before_script:     - mkdir \/ansible\/inventory     - cp artifacts\/inventory_for_test\/inventory.yaml \/ansible\/inventory\/   rules:     - if: ($CI_PIPELINE_SOURCE == \"push\" &amp;&amp; $CI_COMMIT_BRANCH == \"test\") || $CI_PIPELINE_SOURCE == \"web\"       changes:         - intended_state\/**\/*.yaml       when: manual  manual-deploy-prod:   # \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430 \u0440\u043e\u0443\u0442\u0435\u0440\u044b, \u0443 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f state   # \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u0435 \u0432 \u0432\u0435\u0442\u043a\u0443 master   # \u0437\u0430\u043f\u0443\u0441\u043a \u0432\u0440\u0443\u0447\u043d\u0443\u044e   stage: deploy   environment:     name: prod   extends:     - .deploy-stage-template   before_script:     - mkdir \/ansible\/inventory     - cp artifacts\/inventory\/inventory.yaml \/ansible\/inventory\/   rules:     - if: ($CI_PIPELINE_SOURCE == \"push\" &amp;&amp; $CI_COMMIT_BRANCH == \"master\" &amp;&amp; $CI_COMMIT_AUTHOR != \"nautobot\u201d) || $CI_PIPELINE_SOURCE == \"web\"       changes:         - intended_state\/**\/*.yaml       when: manual  auto-deploy-prod-from-nautobot:   # \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 \u043d\u0430 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0440\u043e\u0443\u0442\u0435\u0440\u044b   # \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u0435 \u0432 \u0432\u0435\u0442\u043a\u0443 master   # \u0430\u0432\u0442\u043e\u0440\u043e\u043c \u043a\u043e\u043c\u043c\u0438\u0442\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e nautobot   # \u0437\u0430\u043f\u0443\u0441\u043a \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439   stage: deploy   environment:     name: prod   extends:     - .deploy-stage-template   before_script:     - mkdir \/ansible\/inventory     - cp artifacts\/inventory\/inventory.yaml \/ansible\/inventory\/   rules:     - if: $CI_PIPELINE_SOURCE == \"push\" &amp;&amp; $CI_COMMIT_BRANCH == \"master\" &amp;&amp; $CI_COMMIT_AUTHOR == \"nautobot\"       changes:         - intended_state\/**\/*.yaml<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u0442\u0440\u0438 \u044d\u0442\u0430\u043f\u0430:<\/p>\n<ul>\n<li>\n<p>lint (\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0434\u0430\u043d\u043d\u044b\u0445)<\/p>\n<\/li>\n<li>\n<p>prepare (\u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u043e\u0432\u00a0\u2014 \u0441\u043f\u0438\u0441\u043e\u043a \u0445\u043e\u0441\u0442\u043e\u0432, \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438)<\/p>\n<\/li>\n<li>\n<p>deploy (\u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0439)<\/p>\n<\/li>\n<\/ul>\n<p>\u0438 \u0434\u0432\u0430 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f:<\/p>\n<ul>\n<li>\n<p>prod (\u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043d\u0430\u00a0\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u0430\u0441\u0430\u044e\u0442\u0441\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432\u00a0\u0442\u0435\u043a\u0443\u0449\u0435\u043c \u043a\u043e\u043c\u043c\u0438\u0442\u0435)<\/p>\n<\/li>\n<li>\n<p>test (\u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e prod, \u043d\u043e\u00a0\u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u00a0\u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u044b)<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u0430\u043f\u0430 deploy \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0441\u043a\u0440\u0438\u043f\u0442 <em>update_current_state.py<\/em>, \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e\u00a0\u2014 \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0431\u044d\u043a\u0430\u043f\u043e\u0432 \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0439 \u0432\u0441\u0435\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432, \u043d\u0430\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0435\u00a0\u0431\u044b\u043b\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0430 \u043d\u043e\u0432\u0430\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f.<\/p>\n<h3>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0432\u043d\u0435\u0441\u0435\u043d\u0438\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439<\/h3>\n<p>\u041e\u0447\u0435\u043d\u044c \u0447\u0430\u0441\u0442\u043e \u043a\u00a0\u043d\u0430\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u044f\u0442 \u0441\u00a0\u0437\u0430\u0434\u0430\u0447\u0435\u0439 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a\u00a0\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u043c\u0443 \u0441\u0430\u0439\u0442\u0443 \u043f\u043e\u00a0\u0435\u0433\u043e \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u043c\u0443 \u0438\u043c\u0435\u043d\u0438. \u041a\u00a0\u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, Cisco IOS \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u00a0ACL \u0438 object\u2011group \u0442\u043e\u043b\u044c\u043a\u043e IP\u2011\u0430\u0434\u0440\u0435\u0441\u0430. \u0414\u0430, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u043f\u0440\u0438\u00a0\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 ACL \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c hostname, \u043d\u043e\u00a0\u043e\u043d\u043e \u043d\u0435\u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u043e \u0432\u00a0IP\u2011\u0430\u0434\u0440\u0435\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438 \u043f\u043e\u043f\u0430\u0434\u0435\u0442 \u0432\u00a0\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e. <\/p>\n<p>\u0422\u043e\u00a0\u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u0438 \u0437\u0430\u044f\u0432\u043e\u043a \u043d\u0430\u00a0\u043e\u0442\u043a\u0440\u044b\u0442\u0438\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u0412\u0440\u043e\u0434\u0435 \u0438 \u0435\u0441\u0442\u044c \u0432\u00a0Cisco IOS \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b time\u2011based ACL, \u043d\u043e\u00a0\u0434\u043b\u044f\u00a0\u0442\u0430\u043a\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043e\u043d \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u043d\u0435\u00a0\u043e\u0447\u0435\u043d\u044c. \u041d\u0430\u0434\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 time\u2011range, \u0430\u00a0\u043f\u043e\u0442\u043e\u043c \u0435\u0433\u043e \u0443\u0434\u0430\u043b\u044f\u0442\u044c.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c\u00a0\u0436\u0435, \u0438\u043c\u0435\u044f \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441\u00a0\u0444\u043e\u0440\u043c\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0434\u0435\u043f\u043b\u043e\u044f \u043f\u0440\u0438\u00a0\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445, \u043c\u044b \u043b\u0435\u0433\u043a\u043e \u043c\u043e\u0436\u0435\u043c \u0441\u043b\u0435\u0434\u0438\u0442\u044c \u0437\u0430\u00a0\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u043c\u0438 \u0432\u00a0DNS \u0438\u043b\u0438\u00a0\u0437\u0430\u00a0\u0441\u0440\u043e\u043a\u043e\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u0440\u0430\u0432\u0438\u043b.<\/p>\n<p>\u041f\u043e\u00a0\u0443\u0436\u0435 \u0441\u043b\u043e\u0436\u0438\u0432\u0448\u0435\u0439\u0441\u044f \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u0438, \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u043f\u043b\u0430\u0433\u0438\u043d \u0434\u043b\u044f\u00a0Nautobot, \u0432\u00a0\u0441\u043e\u0441\u0442\u0430\u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u043e\u0448\u0435\u043b Job, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c\u044b\u0439 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u043c \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u043e\u043c. Job \u043a\u043b\u043e\u043d\u0438\u0440\u0443\u0435\u0442 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043f\u0430\u0440\u0441\u0438\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0438 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0441\u0435\u0442\u0435\u0439. \u0415\u0441\u043b\u0438 \u0432 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0445\u043e\u0441\u0442\u0430 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043f\u043e\u043b\u0435 <em>fqdn<\/em>, Job \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u043e\u0431\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043a\u00a0\u0441\u0435\u0440\u0432\u0435\u0440\u0430\u043c DNS \u0434\u043b\u044f\u00a0\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 IP\u2011\u0430\u0434\u0440\u0435\u0441\u043e\u0432. \u041f\u0440\u0430\u0432\u0438\u043b\u0430 \u0441\u00a0\u0438\u0441\u0442\u0435\u043a\u0448\u0438\u043c \u0441\u0440\u043e\u043a\u043e\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f (<em>expiry) <\/em>\u0443\u0434\u0430\u043b\u044f\u044e\u0442\u0441\u044f \u0438\u0437\u00a0\u043f\u043e\u043b\u0438\u0442\u0438\u043a. \u0412\u0441\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0432\u00a0\u0444\u0430\u0439\u043b\u044b \u0438 \u043a\u043e\u043c\u043c\u0438\u0442\u044f\u0442\u0441\u044f \u0438 \u043f\u0443\u0448\u0430\u0442\u0441\u044f \u043d\u0430\u00a0\u0441\u0435\u0440\u0432\u0435\u0440. \u041d\u0443 \u0430\u00a0\u0434\u0430\u043b\u044c\u0448\u0435 \u0432\u0441\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u00a0\u0443\u0436\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0439 \u0441\u0445\u0435\u043c\u0435.<\/p>\n<p>Nautobot \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a, \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u043b\u043e\u0433\u043e\u0432 \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u043a\u0440\u0438\u043f\u0442\u0430, \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u00a0\u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u0435 \u043e\u0431\u00a0\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0445 \u0432\u00a0\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u041f\u0440\u0438\u044f\u0442\u043d\u044b\u043c \u0431\u043e\u043d\u0443\u0441\u043e\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u0441\u0442\u0430\u043b\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0443\u0434\u043e\u0431\u043d\u043e\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0438\u0442\u0438\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0432\u00a0\u0442\u0430\u0431\u043b\u0438\u0447\u043d\u043e\u043c \u0432\u0438\u0434\u0435.<\/p>\n<h3>\u0411\u043e\u043d\u0443\u0441. \u041f\u043e\u043a\u0430\u0436\u0435\u043c \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c<\/h3>\n<p>\u041c\u043e\u0436\u043d\u043e\u00a0\u0431\u044b\u043b\u043e, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u0434\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u043a\u00a0\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044e. \u041d\u043e\u00a0\u0438\u0441\u043a\u0430\u0442\u044c \u043d\u0443\u0436\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432\u043e\u00a0\u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0435 YAML\u2011\u0444\u0430\u0439\u043b\u043e\u0432\u00a0\u2014 \u0437\u0430\u0434\u0430\u0447\u043a\u0430 \u043d\u0435\u00a0\u0438\u0437\u00a0\u043b\u0435\u0433\u043a\u0438\u0445. \u0413\u043e\u0440\u0430\u0437\u0434\u043e \u043f\u0440\u043e\u0449\u0435 \u043d\u0430\u0439\u0442\u0438 \u043f\u043b\u043e\u0449\u0430\u0434\u043a\u0443 \u0432\u00a0Nautobot \u0438 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0432\u0441\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043a\u00a0\u043d\u0435\u0439 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u044b. \u041a\u00a0\u0441\u0447\u0430\u0441\u0442\u044c\u044e, Nautobot\u00a0\u2014 \u043e\u0442\u043a\u0440\u044b\u0442\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u00a0\u043b\u044e\u0431\u043e\u043c\u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0443 \u0443\u0447\u0435\u0442\u0430.<\/p>\n<p>\u0422\u0430\u043a \u0443\u00a0\u043d\u0430\u0441 \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0434\u043b\u044f\u00a0\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u043f\u043b\u043e\u0449\u0430\u0434\u043a\u0438:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/0f6\/674\/855\/0f6674855bde69b779d6ae6165339196.jpg\" width=\"1253\" height=\"462\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0f6\/674\/855\/0f6674855bde69b779d6ae6165339196.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041d\u0430\u00a0\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 IP\u2011\u0430\u0434\u0440\u0435\u0441\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u0430, \u0432\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u043d \u043f\u043e\u043f\u0430\u0434\u0430\u0435\u0442:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/df8\/ae8\/612\/df8ae861280e1520758e33e61f173e1e.jpg\" width=\"1318\" height=\"552\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/df8\/ae8\/612\/df8ae861280e1520758e33e61f173e1e.jpg\" data-blurred=\"true\"\/><\/figure>\n<h3>\u0412\u044b\u0432\u043e\u0434\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0430\u043c<\/h3>\n<p>\u0414\u0430\u0436\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0440\u0443\u0442\u0438\u043d\u043d\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043b\u0443\u0447\u0448\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u042d\u0442\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u0441\u043d\u0438\u0436\u0430\u0435\u0442 \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c \u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0438 \u0438 \u0443\u0441\u043a\u043e\u0440\u044f\u0435\u0442 \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u044f\u0432\u043e\u043a. \u0427\u0435\u043c\u00a0\u0436\u0435 \u0445\u043e\u0440\u043e\u0448 \u043f\u043e\u0434\u0445\u043e\u0434 \u00abNetwork as code\u00bb? \u0420\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u00a0\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e; \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0432\u0435\u0440\u0441\u0438\u0439 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u043e\u00a0\u0432\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e, \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438; \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0435\u0434\u0438\u043d\u043e\u0433\u043e \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u0438\u0441\u0442\u0438\u043d\u044b \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f\u00a0\u0441\u0438\u0441\u0442\u0435\u043c \u0418\u0411. \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043b\u0435\u0433\u043a\u043e \u043c\u043e\u0436\u043d\u043e \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f\u00a0\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430\u043c\u0438 QoS \u0438\u043b\u0438\u00a0\u043f\u0440\u0430\u0432\u0438\u043b\u0430\u043c\u0438 zone\u2011based firewall.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><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\/articles\/893804\/\"> https:\/\/habr.com\/ru\/articles\/893804\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0418 \u0441\u043d\u043e\u0432\u0430 \u043f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u044e \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c, \u043a\u0430\u043a\u00a0\u043c\u044b \u0441\u00a0\u043a\u043e\u043b\u043b\u0435\u0433\u0430\u043c\u0438 \u0440\u0435\u0448\u0430\u043b\u0438 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443 \u043f\u043e\u00a0\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043f\u0438\u0441\u043a\u0430\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u0432 \u043d\u0430\u00a0\u043f\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u043d\u044b\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430\u0445. \u0412\u00a0\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0435\u0447\u044c \u0448\u043b\u0430 \u043e\u00a0\u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u0438 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445. \u0422\u0435\u043f\u0435\u0440\u044c\u00a0\u0436\u0435 \u043f\u043e\u0433\u0440\u0443\u0437\u0438\u043c\u0441\u044f \u0432\u00a0\u043f\u0443\u0447\u0438\u043d\u0443 \u043a\u043e\u0434\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430\u00a0\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u043f\u043e\u043b\u0438\u0442\u0438\u043a \u043d\u0430\u00a0\u0441\u0435\u0442\u0435\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.<\/p>\n<figure class=\"full-width\"><\/figure>\n<ul>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/893494\/\" rel=\"noopener noreferrer nofollow\">\u0427\u0430\u0441\u0442\u044c 1\u00a0\u2014 \u041a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p>\u0427\u0430\u0441\u0442\u044c 2\u00a0\u2014 \u041a\u043e\u0434 (\u0432\u044b \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0435\u0441\u044c \u0442\u0443\u0442)<\/p>\n<\/li>\n<\/ul>\n<h3>Disclaimer<\/h3>\n<p>\u0412\u00a0\u0441\u0438\u043b\u0443 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u043e\u0431\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u0441\u0442\u0432 \u043d\u0435\u00a0\u043c\u043e\u0433\u0443 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043a\u043e\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f. \u0412\u0441\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438, \u0430\u0434\u0440\u0435\u0441\u0430, \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432, \u043f\u043b\u043e\u0449\u0430\u0434\u043e\u043a \u0438 \u0434\u0435\u043f\u0430\u0440\u0442\u0430\u043c\u0435\u043d\u0442\u043e\u0432\u00a0\u2014 \u0432\u044b\u043c\u044b\u0448\u043b\u0435\u043d\u044b.<\/p>\n<h3>\u041a\u0440\u0430\u0442\u043a\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0435\u0440\u0438\u0438<\/h3>\n<p>\u0422\u0435, \u043a\u0442\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043b \u043f\u0435\u0440\u0432\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0441\u0442\u0430\u0442\u044c\u0438, \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u043c\u043d\u0438\u0442\u044c, \u0447\u0442\u043e\u00a0\u0434\u043b\u044f\u00a0\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043c\u044b \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0430\u043c\u0438 \u00abNetwork as Code\u00bb. \u0412\u00a0Gitlab \u043c\u044b \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b\u0438 \u043d\u0430\u0431\u043e\u0440 \u0444\u0430\u0439\u043b\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0442\u0438\u043f\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432,<\/p>\n<\/li>\n<li>\n<p>\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0445\u043e\u0441\u0442\u043e\u0432 \u0438 \u0441\u0435\u0442\u0435\u0439,<\/p>\n<\/li>\n<li>\n<p>\u0441\u043f\u0438\u0441\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430,<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u0424\u0430\u0439\u043b\u043e\u0432 \u0432\u00a0\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043c\u043d\u043e\u0433\u043e, \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437\u00a0\u043d\u0438\u0445 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u043a\u0430\u043a\u00a0\u0437\u0430\u00a0\u043e\u0434\u0438\u043d \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440, \u0442\u0430\u043a \u0438 \u0437\u0430\u00a0\u043e\u0434\u043d\u0443 \u0438\u043b\u0438\u00a0\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043b\u043e\u043a\u0430\u0446\u0438\u0439. \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043d\u0430\u0434\u043e \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u044d\u0442\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432\u00a0\u043a\u043e\u043c\u0430\u043d\u0434\u044b CLI \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0438\u0445 \u043d\u0430\u00a0\u0441\u0435\u0442\u0435\u0432\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.<\/p>\n<h3>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 <\/h3>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437, \u043a\u043e\u0433\u0434\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440 \u0432\u043d\u043e\u0441\u0438\u0442 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432\u00a0YAML\u2011\u0444\u0430\u0439\u043b\u044b, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0449\u0438\u0435 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u043d\u0435\u043d\u0443\u043b\u0435\u0432\u0430\u044f \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c, \u0447\u0442\u043e\u00a0\u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0439\u0442\u0438 \u043d\u0435\u00a0\u0442\u0430\u043a:<\/p>\n<ul>\n<li>\n<p>\u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 YAML,<\/p>\n<\/li>\n<li>\n<p>\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435,<\/p>\n<\/li>\n<li>\n<p>\u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0435\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u043c,<\/p>\n<\/li>\n<li>\n<p>\u0432\u00a0\u043f\u0440\u0430\u0432\u0438\u043b\u0430\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0445\u043e\u0441\u0442\u0430,<\/p>\n<\/li>\n<li>\n<p>\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438\u00a0\u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043d\u0435\u00a0\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043f\u0440\u0438\u043d\u044f\u0442\u044b\u043c Naming conventions.<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u043d\u0430 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0445 \u044d\u0442\u0430\u043f\u0430\u0445,\u00a0\u0431\u044b\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u043a\u0440\u0438\u043f\u0442 \u043d\u0430\u00a0Python. \u0418\u0437\u043e\u0431\u0440\u0435\u0442\u0430\u0442\u044c \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434 \u043c\u044b \u043d\u0435\u00a0\u0441\u0442\u0430\u043b\u0438, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0448\u0438\u0440\u043e\u043a\u043e \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u043e\u0439 <a href=\"https:\/\/github.com\/keleshev\/schema\" rel=\"noopener noreferrer nofollow\">schema<\/a>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0434\u0430\u043d\u043d\u044b\u0445. \u0421\u0445\u0435\u043c\u0430 \u0434\u0430\u043d\u043d\u044b\u0445, \u0441\u00a0\u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0441\u0432\u0435\u0440\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0439 YAML\u2011\u0444\u0430\u0439\u043b, \u0434\u043b\u044f\u00a0\u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0432\u044b\u043d\u0435\u0441\u0435\u043d\u0430 \u0432\u00a0\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b. \u041f\u0440\u0438\u00a0\u043d\u0430\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441\u043a\u0440\u0438\u043f\u0442 \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0432\u00a0\u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f \u0441\u00a0\u043e\u0448\u0438\u0431\u043a\u043e\u0439.<\/p>\n<pre><code class=\"python\"># code\/lint_state.py  import yaml import os from schema import Schema, SchemaError from state_scheme import scheme_dict  # \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0444\u0430\u0439\u043b\u043e\u0432 state \u0438\u0437 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 intended_state state_files = [] for root, dirs, files in os.walk('.\/intended_state', topdown = False):    for name in files:         state_files.append(os.path.join(root, name))  # \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043c YAML, \u0437\u0430\u043e\u0434\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u0447\u0442\u043e \u043d\u0435\u0442 \u043e\u0448\u0438\u0431\u043e\u043a, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u043c yaml_dict = {} for filename in state_files:     try:         with open(filename, 'r') as file:             yaml_data = yaml.safe_load(file)             print(f'{filename} - OK')     except yaml.YAMLError as exc:         print (f'{filename} - \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0435 \u0444\u0430\u0439\u043b\u0430\\n')         if hasattr(exc, 'problem_mark'):             if exc.context != None:                 print (f'  ERROR:\\n  {exc.problem_mark}\\n  {exc.problem} {exc.context}')             else:                 print (f'  ERROR:\\n  {exc.problem_mark}\\n  {exc.problem}')         else:             print (f'{filename} - \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0430\u0440\u0441\u0438\u043d\u0433\u0435 \u0444\u0430\u0439\u043b\u0430')         exit(1)      yaml_dict[filename] = yaml_data  # \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u043d\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0441\u0445\u0435\u043c\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 output = '' success = True  for filename, yaml_data in yaml_dict.items():     yaml_schema = yaml_data.get('_metadata',{}).get('schema')     if yaml_schema not in scheme_dict:         success = False         output += f'{filename} - \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0444\u0430\u0439\u043b\u0430\\n'         output += f'\u0412 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0438\u043b\u0438 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u0430 schema \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438\\n'     try:         config_schema = Schema(scheme_dict[yaml_schema])         config_schema.validate(yaml_data)         output += f'{filename} - OK\\n'     except SchemaError as exc:         success = False         output += f'{filename} - \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0444\u0430\u0439\u043b\u0430\\n'         output += f'{exc}\\n'  # \u0432\u044b\u0432\u0435\u0434\u0435\u043c \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 print(output) if not success:     # \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a, \u0432\u044b\u0439\u0434\u0435\u043c \u0441 \u043e\u0448\u0438\u0431\u043a\u043e\u0439     exit(1)<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u0421\u0445\u0435\u043c\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 &#8212; code\/state_scheme.py<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\"># code\/state_scheme.py  from schema import Or, Regex, Optional import datetime  scheme_dict = {}  # \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f IP4_REGEX = r'^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$' IP4_NETWORK_REGEX = r'^(?:[0-9]{1,3}\\.){3}[0-9]{1,3} (255)\\.(0|128|192|224|240|248|252|254|255)\\.(0|128|192|224|240|248|252|254|255)\\.(0|128|192|224|240|248|252|254|255)$ ' IP4_RANGE_REGEX = r'^(?:[0-9]{1,3}\\.){3}[0-9]{1,3} (?:[0-9]{1,3}\\.){3}[0-9]{1,3}$' PORTS_REGEX = r'^(range [0-9]{2,5} [0-9]{2,5})|(eq|lt|gt) [0-9]{1,5}$'  metadata_scheme = {     Optional('filter'): {         Optional('location_types'): [str],         Optional('locations'): [str],         Optional('hosts'): [str],     },     'weight': int,     'description': str,     'is_active': bool,     'schema': Or('network_groups',                  'service_groups',                  'rules',                  'policy'                 ), }  scheme_dict['network_groups'] = {     '_metadata': metadata_scheme,     'network_groups': {         str: {             Optional('description'): str,             Optional('extend'): bool,             Optional('fqdn'): str,             Optional('networks'): [Regex(IP4_NETWORK_REGEX)],             Optional('hosts'): [Regex(IP4_REGEX)],             Optional('ranges'): [Regex(IP4_RANGE_REGEX)],             Optional('groups'): [str],         },     } }  scheme_dict['service_groups'] = {     '_metadata': metadata_scheme,     'service_groups': {         str: {             Optional('extend'): bool,             Optional('tcp'): [Regex(PORTS_REGEX)],             Optional('udp'): [Regex(PORTS_REGEX)],         },     } }  scheme_dict['rules'] = {     '_metadata': metadata_scheme,     'rules': {         str: {             Optional('extend'): bool,             Optional('lines'): [{                 Optional('sequence'): int,                 'action': Or('permit', 'deny'),                 'source': str,                 Optional('source_type'): str,                 'destination': str,                 Optional('destination_type'): str,                 'services': {                     Optional('protocol'): Or('ip',                                              'icmp',                                              'tcp',                                              'udp'),                     Optional('ports'): [int],                     Optional('group'): str,                 },                 Optional('reason'): str,                 Optional('expiry'): Or(datetime.date,                                        datetime.datetime),             }]         },     } }  scheme_dict['policy'] = {     '_metadata': metadata_scheme,     'name': Or('RM_NAT', 'ACL_NAT'),     'type': Or('route-map', 'acl'),     Optional('lines'): {         int: {             'rule': str,             'action': Or('permit', 'deny'),         }     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0441\u043f\u0438\u0441\u043a\u0430 \u0437\u0430\u0442\u0440\u043e\u043d\u0443\u0442\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 <\/h3>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a\u00a0\u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u0438\u00a0\u043a\u0430\u0436\u0434\u043e\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u043f\u043e\u043b\u0438\u0442\u0438\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043d\u0430\u00a0\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u044b, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u0447\u0435\u043d\u044c \u0442\u0435\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432, \u043d\u0430\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044d\u0442\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0433\u043b\u0438 \u043f\u043e\u0432\u043b\u0438\u044f\u0442\u044c. \u0414\u043b\u044f\u00a0\u044d\u0442\u043e\u0433\u043e \u0441\u043f\u0435\u0440\u0432\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432. \u0423\u00a0\u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0445\u044d\u0448 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430, \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0438\u043c, \u0447\u0442\u043e\u0431\u044b \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0435\u0433\u043e \u043d\u0430\u00a0\u0434\u0438\u0441\u043a:<\/p>\n<pre><code class=\"python\"># code\/get_changes.py  import sys from git import Repo from pathlib import Path  # \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0438 Path(Path.cwd() \/ 'artifacts').mkdir(parents=True, exist_ok=True) ARTIFACTS_PATH = Path.cwd() \/ 'artifacts'  # \u0445\u044d\u0448 \u043a\u043e\u043c\u043c\u0438\u0442\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u044b\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c COMMIT_HASH = sys.argv[1]  # \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 repo = Repo.init(Path.cwd()) changed_files = '' for filename in repo.commit(COMMIT_HASH).stats.files:     if filename.split('\/')[0] == 'intended_state' and filename.split('.')[-1] == 'yaml':         changed_files += f'{filename}\\n'  if not changed_files:     print('State \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f! \u0417\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0443')     exit(1) with open(f'{ARTIFACTS_PATH}\/changed_files.txt', 'w') as file:     # \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u044b     file.write(changed_files)<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f API Nautobot, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0442\u044c \u0435\u0433\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u043c\u0438 \u043b\u043e\u043a\u0430\u0446\u0438\u044f\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043e\u0442\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u043a\u00a0\u0444\u0430\u0439\u043b\u0430\u043c, \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u043c \u0432\u00a0\u0442\u0435\u043a\u0443\u0449\u0435\u043c \u043a\u043e\u043c\u043c\u0438\u0442\u0435. \u0412\u00a0\u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 API \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c GraphQL, \u0430\u00a0\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u0432\u00a0\u0444\u0430\u0439\u043b\u0435 <em>inventory.yaml<\/em><\/p>\n<pre><code class=\"python\"># code\/prepare_inventory.py # \u0447\u0430\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u043e\u043f\u0443\u0449\u0435\u043d\u0430, \u0442\u0430\u043a \u043a\u0430\u043a \u0441\u0435\u0439\u0447\u0430\u0441 \u043d\u0435 \u0432\u0430\u0436\u043d\u0430  affected_items = {     'location_types': set(),     'locations': set(),     'hosts': set(), } affected_all = False for filename in changed_files:     with open(filename.strip(), 'r') as file:         yaml_data = yaml.safe_load(file)         if 'filter' not in yaml_data['_metadata']:             # \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u0432\u0441\u0435\u0445 \u0431\u0435\u0437 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f             affected_all = True             # \u0434\u0430\u043b\u044c\u0448\u0435 \u0447\u0442\u043e-\u0442\u043e \u0438\u0441\u043a\u0430\u0442\u044c \u043d\u0435\u0442 \u0441\u043c\u044b\u0441\u043b\u0430             break         for filter_name, filter_data in yaml_data['_metadata']['filter'].items():             for item in filter_data:                 affected_items[filter_name].add(item)  # \u0432\u044b\u0442\u0430\u0449\u0438\u043c \u0438\u0437 Nautobot \u0441\u043f\u0438\u0441\u043e\u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 nautobot_health = requests.get(f'https:\/\/{NAUTOBOT_URL}\/health', verify=False) if nautobot_health.status_code != 200:     print(\"\u041e\u0448\u0438\u0431\u043a\u0430! Nautobot \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d\")     exit(1)  query = ''' query {     devices (role: ['Router'], status: 'Active') {         name         primary_ip4 {             address         }         location {             name             location_type {                 name             }         }         rel_device_soft {             version             device_platform {               name             }         }          tags {           name         }     } } ''' nb = pynautobot.api(     url = f'https:\/\/{NAUTOBOT_URL}',     token = NAUTOBOT_TOKEN,     threading=True ) graphql_response = nb.graphql.query(query=query) devices = graphql_response.json['data']['devices'] affected_devices = {} affected_devices_for_test = {}  # \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u041e\u0421 ANSIBLE_NETWORK_OS = {     'Cisco<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\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-453150","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/453150","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=453150"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/453150\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=453150"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=453150"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=453150"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}