diff --git a/.github/workflows/release-note.yml b/.github/workflows/release-note.yml new file mode 100644 index 0000000..5f77362 --- /dev/null +++ b/.github/workflows/release-note.yml @@ -0,0 +1,29 @@ +--- +# yamllint disable rule:truthy +name: Release Note + +on: + push: + branches: + - develop + - stable + pull_request: + types: [opened, reopened, synchronize] + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + steps: + + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-note.yml + disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.INFRAHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1246f76..513ff77 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -67,15 +67,15 @@ jobs: - name: "Check out repository code" uses: "actions/checkout@v3" - name: "Setup environment" - run: "pip install black==23.10.1 ruff==0.1.0" - - name: "Linting: BLACK" - run: "black --check ." - - name: "Linting: ruff" - run: "ruff check ." + run: "pip install ruff==0.1.5" + - name: "Linting: ruff check" + run: "ruff check --diff ." + - name: "Linting: ruff format" + run: "ruff format --check --diff ." unit-tests: if: | - always() && !cancelled() && + !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') needs: ["files-changed", "yaml-lint", "python-lint"] diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..fa1a38e --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,13 @@ +--- +version: 2 +# sphinx: +# configuration: docs/conf.py + +build: + os: ubuntu-22.04 + tools: + python: "3.9" + +python: + install: + - requirements: docs/requirements.txt diff --git a/.yamllint.yml b/.yamllint.yml index 867c674..30183ca 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -4,6 +4,7 @@ extends: default ignore: | /.venv /examples + /tests/**/output* rules: new-lines: disable diff --git a/README.md b/README.md index 853e926..84ab187 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Requirements - The two latest Infrahub releases - Python >=3.9, <3.12 - Python modules: - - infrahub_sdk >= 0.2.0 + - infrahub-sdk >= 0.2.0 - Ansible 2.12+ - Infrahub write-enabled token when using modules or read-only token for `lookup/inventory` diff --git a/ansible.cfg b/ansible.cfg index fd944b4..67856b3 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -2,4 +2,4 @@ force_valid_group_names = always [inventory] -enable_plugins = opsmill.infrahub.inventory, yaml, ini \ No newline at end of file +enable_plugins = opsmill.infrahub.inventory, yaml, ini diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..41c270b --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/_extensions/pygments_lexer.py b/docs/_extensions/pygments_lexer.py new file mode 100644 index 0000000..8515293 --- /dev/null +++ b/docs/_extensions/pygments_lexer.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +# pylint: disable=no-self-argument +# + +# https://bitbucket.org/birkenfeld/pygments-main/raw/7941677dc77d4f2bf0bbd6140ade85a9454b8b80/AUTHORS + +# +# Licensed under BSD license: +# + +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +from pygments import token +from pygments.lexer import DelegatingLexer, RegexLexer, bygroups, include +from pygments.lexers import DiffLexer + + +class AnsibleOutputPrimaryLexer(RegexLexer): + name = "Ansible-output-primary" + + # The following definitions are borrowed from Pygment's JSON lexer. + # It has been originally authored by Norman Richards. + + # integer part of a number + int_part = r"-?(0|[1-9]\d*)" + + # fractional part of a number + frac_part = r"\.\d+" + + # exponential part of a number + exp_part = r"[eE](\+|-)?\d+" + + tokens = { + # ######################################### + # # BEGIN: states from JSON lexer ######### + # ######################################### + "whitespace": [(r"\s+", token.Text)], + # represents a simple terminal value + "simplevalue": [ + (r"(true|false|null)\b", token.Keyword.Constant), + ( + ("%(int_part)s(%(frac_part)s%(exp_part)s|" "%(exp_part)s|%(frac_part)s)") % vars(), + token.Number.Float, + ), + (int_part, token.Number.Integer), + (r'"(\\\\|\\"|[^"])*"', token.String), + ], + # the right hand side of an object, after the attribute name + "objectattribute": [ + include("value"), + (r":", token.Punctuation), + # comma terminates the attribute but expects more + (r",", token.Punctuation, "#pop"), + # a closing bracket terminates the entire object, so pop twice + (r"\}", token.Punctuation, "#pop:2"), + ], + # a json object - { attr, attr, ... } + "objectvalue": [ + include("whitespace"), + (r'"(\\\\|\\"|[^"])*"', token.Name.Tag, "objectattribute"), + (r"\}", token.Punctuation, "#pop"), + ], + # json array - [ value, value, ... } + "arrayvalue": [ + include("whitespace"), + include("value"), + (r",", token.Punctuation), + (r"\]", token.Punctuation, "#pop"), + ], + # a json value - either a simple value or a complex value (object or array) + "value": [ + include("whitespace"), + include("simplevalue"), + (r"\{", token.Punctuation, "objectvalue"), + (r"\[", token.Punctuation, "arrayvalue"), + ], + # ######################################### + # # END: states from JSON lexer ########### + # ######################################### + "host-postfix": [ + (r"\n", token.Text, "#pop:3"), + ( + r"( )(=>)( )(\{)", + bygroups(token.Text, token.Punctuation, token.Text, token.Punctuation), + "objectvalue", + ), + ], + "host-error": [ + ( + r"(?:(:)( )(UNREACHABLE|FAILED)(!))?", + bygroups(token.Punctuation, token.Text, token.Keyword, token.Punctuation), + "host-postfix", + ), + (r"", token.Text, "host-postfix"), + ], + "host-name": [ + ( + r"(\[)([^ \]]+)(?:( )(=>)( )([^\]]+))?(\])", + bygroups( + token.Punctuation, + token.Name.Variable, + token.Text, + token.Punctuation, + token.Text, + token.Name.Variable, + token.Punctuation, + ), + "host-error", + ) + ], + "host-result": [ + (r"\n", token.Text, "#pop"), + ( + r"( +)(ok|changed|failed|skipped|unreachable)(=)([0-9]+)", + bygroups(token.Text, token.Keyword, token.Punctuation, token.Number.Integer), + ), + ], + "root": [ + ( + r"(PLAY|TASK|PLAY RECAP)(?:( )(\[)([^\]]+)(\]))?( )(\*+)(\n)", + bygroups( + token.Keyword, + token.Text, + token.Punctuation, + token.Literal, + token.Punctuation, + token.Text, + token.Name.Variable, + token.Text, + ), + ), + ( + r"(fatal|ok|changed|skipping)(:)( )", + bygroups(token.Keyword, token.Punctuation, token.Text), + "host-name", + ), + ( + r"(\[)(WARNING)(\]:)([^\n]+)", + bygroups(token.Punctuation, token.Keyword, token.Punctuation, token.Text), + ), + ( + r"([^ ]+)( +)(:)", + bygroups(token.Name, token.Text, token.Punctuation), + "host-result", + ), + ( + r"(\tto retry, use: )(.*)(\n)", + bygroups(token.Text, token.Literal.String, token.Text), + ), + (r".*\n", token.Other), + ], + } + + +class AnsibleOutputLexer(DelegatingLexer): + name = "Ansible-output" + aliases = ["ansible-output"] + + def __init__(self, **options): + super(AnsibleOutputLexer, self).__init__(DiffLexer, AnsibleOutputPrimaryLexer, **options) + + +# #################################################################################################### +# # Sphinx plugin #################################################################################### +# #################################################################################################### + +__version__ = "0.1.0" +__license__ = "BSD license" +__author__ = "Felix Fontein" +__author_email__ = "felix@fontein.de" + + +def setup(app): + """Initializer for Sphinx extension API. + See http://www.sphinx-doc.org/en/stable/extdev/index.html#dev-extensions. + """ + for lexer in [AnsibleOutputLexer(startinline=True)]: + app.add_lexer(lexer.name, lexer) + for alias in lexer.aliases: + app.add_lexer(alias, lexer) + + return dict(version=__version__, parallel_read_safe=True) diff --git a/docs/_static/ansible.css b/docs/_static/ansible.css new file mode 100644 index 0000000..9a1c5da --- /dev/null +++ b/docs/_static/ansible.css @@ -0,0 +1,59 @@ +/*! minified with http://css-minify.online-domain-tools.com/ - all comments + * must have ! to preserve during minifying with that tool *//*! Fix for read the docs theme: + * https://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html + *//*! override table width restrictions */@media screen and (min-width:767px){/*! If we ever publish to read the docs, we need to use !important for these + * two styles as read the docs itself loads their theme in a way that we + * can't otherwise override it. + */.wy-table-responsive table td{white-space:normal}.wy-table-responsive{overflow:visible}}/*! + * We use the class documentation-table for attribute tables where the first + * column is the name of an attribute and the second column is the description. + *//*! These tables look like this: + * + * Attribute Name Description + * -------------- ----------- + * **NAME** This is a multi-line description + * str/required that can span multiple lines + * added in x.y + * With multiple paragraphs + * -------------- ----------- + * + * **NAME** is given the class .value-name + * str is given the class .value-type + * / is given the class .value-separator + * required is given the class .value-required + * added in x.y is given the class .value-added-in + *//*! The extra .rst-content is so this will override rtd theme */.rst-content table.documentation-table td{vertical-align:top}table.documentation-table td:first-child{white-space:nowrap;vertical-align:top}table.documentation-table td:first-child p:first-child{font-weight:700;display:inline}/*! This is now redundant with above position-based styling *//*! +table.documentation-table .value-name { + font-weight: bold; + display: inline; +} +*/table.documentation-table .value-type{font-size:x-small;color:purple;display:inline}table.documentation-table .value-separator{font-size:x-small;display:inline}table.documentation-table .value-required{font-size:x-small;color:red;display:inline}.value-added-in{font-size:x-small;font-style:italic;color:green;display:inline}/*! Ansible-specific CSS pulled out of rtd theme for 2.9 */.DocSiteProduct-header{flex:1;-webkit-flex:1;padding:10px 20px 20px;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;align-items:center;-webkit-align-items:center;justify-content:flex-start;-webkit-justify-content:flex-start;margin-left:20px;margin-right:20px;text-decoration:none;font-weight:400;font-family:'Open Sans',sans-serif}.DocSiteProduct-header:active,.DocSiteProduct-header:focus,.DocSiteProduct-header:visited{color:#fff}.DocSiteProduct-header--core{font-size:25px;background-color:#5bbdbf;border:2px solid #5bbdbf;border-top-left-radius:4px;border-top-right-radius:4px;color:#fff;padding-left:2px;margin-left:2px}.DocSiteProduct-headerAlign{width:100%}.DocSiteProduct-logo{width:60px;height:60px;margin-bottom:-9px}.DocSiteProduct-logoText{margin-top:6px;font-size:25px;text-align:left}.DocSiteProduct-CheckVersionPara{margin-left:2px;padding-bottom:4px;margin-right:2px;margin-bottom:10px}/*! Ansible color scheme *//*.wy-nav-top,.wy-side-nav-search{background-color:#5bbdbf}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#5bbdbf}.wy-menu-vertical a{padding:0}.wy-menu-vertical a.reference.internal{padding:.4045em 1.618em}*//*! Override sphinx rtd theme max-with of 800px */.wy-nav-content{max-width:100%}/*! Override sphinx_rtd_theme - keeps left-nav from overwriting Documentation title *//*.wy-nav-side{top:45px}*//*! Ansible - changed absolute to relative to remove extraneous side scroll bar */.wy-grid-for-nav{position:relative}/*! Ansible narrow the search box */.wy-side-nav-search input[type=text]{width:90%;padding-left:24px}/*! Ansible - remove so highlight indenting is correct */.rst-content .highlighted{padding:0}.DocSiteBanner{display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;flex-wrap:wrap;-webkit-flex-wrap:wrap;margin-bottom:25px}.DocSiteBanner-imgWrapper{max-width:100%}td,th{min-width:100px}table{overflow-x:auto;display:block;max-width:100%}.documentation-table td.elbow-placeholder{border-left:1px solid #000;border-top:0;width:30px;min-width:30px}.documentation-table td,.documentation-table th{padding:4px;border-left:1px solid #000;border-top:1px solid #000}.documentation-table{border-right:1px solid #000;border-bottom:1px solid #000}@media print{*{background:0 0!important;color:#000!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}#nav,a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}/*! Don't show links for images, or javascript/internal links */pre,blockquote{border:0 solid #999;page-break-inside:avoid}thead{display:table-header-group}/*! h5bp.com/t */tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}#google_image_div,.DocSiteBanner{display:none!important}}#sideBanner,.DocSite-globalNav{display:none}.DocSite-sideNav{display:block;margin-bottom:40px}.DocSite-nav{display:none}.ansibleNav{background:#000;padding:0 20px;width:auto;border-bottom:1px solid #444;font-size:14px;z-index:1}.ansibleNav ul{list-style:none;padding-left:0;margin-top:0}.ansibleNav ul li{padding:7px 0;border-bottom:1px solid #444}.ansibleNav ul li:last-child{border:none}.ansibleNav ul li a{color:#fff;text-decoration:none;text-transform:uppercase;padding:6px 0}.ansibleNav ul li a:hover{color:#5bbdbf;background:0 0}h4{font-size:105%}h5{font-size:90%}h6{font-size:80%}@media screen and (min-width:768px){.DocSite-globalNav{display:block;position:fixed}#sideBanner{display:block}.DocSite-sideNav{display:none}.DocSite-nav{flex:initial;-webkit-flex:initial;display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;justify-content:flex-start;-webkit-justify-content:flex-start;padding:15px;background-color:#000;text-decoration:none;font-family:'Open Sans',sans-serif}.DocSiteNav-logo{width:28px;height:28px;margin-right:8px;margin-top:-6px;position:fixed;z-index:1}.DocSiteNav-title{color:#fff;font-size:20px;position:fixed;margin-left:40px;margin-top:-4px;z-index:1}.ansibleNav{height:45px;width:100%;font-size:13px;padding:0 60px 0 0}.ansibleNav ul{float:right;display:flex;flex-wrap:nowrap;margin-top:13px}.ansibleNav ul li{padding:0;border-bottom:none}.ansibleNav ul li a{color:#fff;text-decoration:none;text-transform:uppercase;padding:8px 13px}h4{font-size:105%}h5{font-size:90%}h6{font-size:80%}}@media screen and (min-width:768px){#sideBanner,.DocSite-globalNav{display:block}.DocSite-sideNav{display:none}.DocSite-nav{flex:initial;-webkit-flex:initial;display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;justify-content:flex-start;-webkit-justify-content:flex-start;padding:15px;background-color:#000;text-decoration:none;font-family:'Open Sans',sans-serif}.DocSiteNav-logo{width:28px;height:28px;margin-right:8px;margin-top:-6px;position:fixed}.DocSiteNav-title{color:#fff;font-size:20px;position:fixed;margin-left:40px;margin-top:-4px}.ansibleNav{height:45px;font-size:13px;padding:0 60px 0 0}.ansibleNav ul{float:right;display:flex;flex-wrap:nowrap;margin-top:13px}.ansibleNav ul li{padding:0;border-bottom:none}.ansibleNav ul li a{color:#fff;text-decoration:none;text-transform:uppercase;padding:8px 13px}h4{font-size:105%}h5{font-size:90%}h6{font-size:80%}} +/* ansibleOptionLink is adapted from h1 .headerlink in sphinx_rtd_theme */ +tr:hover .ansibleOptionLink::after { + visibility: visible; + } + tr .ansibleOptionLink::after { + content: ""; + font-family: FontAwesome; + } + tr .ansibleOptionLink { + visibility: hidden; + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + @media screen and (min-width:767px){ + /* Move anchors a bit up so that they aren't hidden by the header bar */ + section [id] { + padding-top: 45px; + margin-top: -45px; + } + /* Without this, for example most links in the page's TOC aren't usable anymore */ + section a[id] { + padding-top: 0; + margin-top: 0; + } + } \ No newline at end of file diff --git a/docs/_static/pygment.css b/docs/_static/pygment.css new file mode 100644 index 0000000..ac1f0dd --- /dev/null +++ b/docs/_static/pygment.css @@ -0,0 +1,76 @@ +.highlight { background: #f8f8f8 } +.highlight .hll { background-color: #ffffcc; border: 1px solid #edff00; padding-top: 2px; border-radius: 3px; display: block } +.highlight .c { color: #6a737d; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2; color: #a61717; border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666; font-weight: bold } /* Operator */ +.highlight .ch { color: #6a737d; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #6a737d; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #6a737d; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #6a737d; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #999999; font-weight: bold; font-style: italic; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .gd .x { color: #A00000; background-color: #ffaaaa } +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .gi .x { color: #00A000; background-color: #aaffaa; } +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0040D0 } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .l { color: #032f62 } /* Literal */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .n { color: #333333 } +.highlight .p { font-weight: bold } +.highlight .na { color: teal } /* Name.Attribute */ +.highlight .nb { color: #0086b3 } /* Name.Builtin */ +.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ +.highlight .no { color: teal; } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: purple; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #555555; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #22863a } /* Name.Tag */ +.highlight .nv { color: #9960b5; font-weight: bold } /* Name.Variable */ +.highlight .p { color: font-weight: bold } /* Indicator */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #009999 } /* Literal.Number.Bin */ +.highlight .mf { color: #009999 } /* Literal.Number.Float */ +.highlight .mh { color: #009999 } /* Literal.Number.Hex */ +.highlight .mi { color: #009999 } /* Literal.Number.Integer */ +.highlight .mo { color: #009999 } /* Literal.Number.Oct */ +.highlight .sa { color: #dd1144 } /* Literal.String.Affix */ +.highlight .sb { color: #dd1144 } /* Literal.String.Backtick */ +.highlight .sc { color: #dd1144 } /* Literal.String.Char */ +.highlight .dl { color: #dd1144 } /* Literal.String.Delimiter */ +.highlight .sd { color: #dd1144; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #dd1144 } /* Literal.String.Double */ +.highlight .se { color: #dd1144; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #dd1144 } /* Literal.String.Heredoc */ +.highlight .si { color: #dd1144; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #dd1144 } /* Literal.String.Other */ +.highlight .sr { color: #009926 } /* Literal.String.Regex */ +.highlight .s1 { color: #dd1144 } /* Literal.String.Single */ +.highlight .ss { color: #990073 } /* Literal.String.Symbol */ +.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: teal } /* Name.Variable.Class */ +.highlight .vg { color: teal } /* Name.Variable.Global */ +.highlight .vi { color: teal } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ +.highlight .gc { color: #909090; background-color: #eaf2f5 } \ No newline at end of file diff --git a/docs/changelog/changelog_include.rst b/docs/changelog/changelog_include.rst new file mode 100644 index 0000000..6f90edd --- /dev/null +++ b/docs/changelog/changelog_include.rst @@ -0,0 +1 @@ +.. include:: ../../CHANGELOG.rst \ No newline at end of file diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst new file mode 100644 index 0000000..1f3e410 --- /dev/null +++ b/docs/changelog/index.rst @@ -0,0 +1,8 @@ +===================== +CHANGELOG +===================== + +.. toctree:: + :maxdepth: 4 + + changelog_include \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..bb8a258 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,153 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import re +import stat +import sys +from pathlib import Path + +from antsibull_docs.cli import antsibull_docs + +sys.path.insert(0, os.path.abspath("../")) + + +# -- Project information ----------------------------------------------------- + +project = "Infrahub Ansible Modules" +copyright = "2023, Opsmill" +author = "Opsmill " + +# The full version, including alpha/beta/rc tags +release = "0.1.0" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.autosectionlabel", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] +html_css_files = ["ansible.css", "pygments.css"] +# html_theme_path = ["_themes"] + +# Settings for extensions +autodoc_default_options = { + "members": True, + "private-members": True, + "special-members": "__init__", + "undoc-members": True, + "exclude-members": "__weakref__", +} +autodoc_mock_imports = ["ansible_collections"] + + +def create_antsibull_docs(files, plugin_type=None): + """Creates the necessary docs for all files in a directory + + Args: + files ([Path.Glob]): Glob of files from a specific directory. + plugin_type ([str], optional): Create the proper documentation for the plugin type. Defaults to None. + """ + for f in files: + file_name = re.search(r"(?:.+\/)(\S+)\.py", str(f)).group(1) + + if plugin_type is not None: + file_path = Path(f"plugins/{plugin_type}/{file_name}/") + else: + file_path = Path(f"plugins/modules/{file_name}/") + + file_path.mkdir(mode=744, exist_ok=True) + + if plugin_type is not None: + args_string = ( + f"junk plugin --dest-dir {file_path} --plugin-type {plugin_type} networktocode.nautobot.{file_name}" + ) + else: + args_string = f"junk plugin --dest-dir {file_path} --plugin-type module networktocode.nautobot.{file_name}" + args = args_string.split(" ") + try: + antsibull_docs.run(args) + except Exception: + sys.exit(1) + + +def remove_write_permissions(path): + """Remove write permissions from group and others for the sake of antsibull-docs working. + + Args: + path (Path): Path object. + """ + for d in path.iterdir(): + d.chmod(stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH) + + +def build_ansible_docs(app): + """ + This will perform all necessary actions to use antsibull-docs to generate collection docs + """ + inventory_path = Path("../plugins/inventory/") + lookup_path = Path("../plugins/lookup/") + modules_path = Path("../plugins/modules/") + + # Set permissions on folders within docs/plugins to remove w from g+o + doc_modules = Path("plugins/modules/") + doc_lookup = Path("plugins/lookup/") + doc_inventory = Path("plugins/inventory/") + remove_write_permissions(doc_modules) + remove_write_permissions(doc_lookup) + remove_write_permissions(doc_inventory) + + inventory = inventory_path.glob("[!_]*.py") + lookup = lookup_path.glob("[!_]*.py") + modules = modules_path.glob("[!_]*.py") + + create_antsibull_docs(inventory, "inventory") + create_antsibull_docs(lookup, "lookup") + create_antsibull_docs(modules) + + +########################################### +# NOT IN USE AND SHOULD BE MANUALLY BUILT via antsibull docs +# Run from the root of the repo +# antsibull-docs collection --dest-dir docs/plugins --use-current --squash-hierarchy networktocode.nautobot +################ +# def setup(app): +# app.connect("builder-inited", build_ansible_docs) +# +# +# build_ansible_docs(None) diff --git a/docs/js/ansible/application.js b/docs/js/ansible/application.js new file mode 100644 index 0000000..304625b --- /dev/null +++ b/docs/js/ansible/application.js @@ -0,0 +1,106 @@ +angular.module('ansibleApp', []).filter('moduleVersion', function() { + return function(modules, version) { + + var parseVersionString = function (str) { + if (typeof(str) != 'string') { return false; } + var x = str.split('.'); + // parse from string or default to 0 if can't parse + var maj = parseInt(x[0]) || 0; + var min = parseInt(x[1]) || 0; + var pat = parseInt(x[2]) || 0; + return { + major: maj, + minor: min, + patch: pat + } + } + + var vMinMet = function(vmin, vcurrent) { + minimum = parseVersionString(vmin); + running = parseVersionString(vcurrent); + if (running.major != minimum.major) + return (running.major > minimum.major); + else { + if (running.minor != minimum.minor) + return (running.minor > minimum.minor); + else { + if (running.patch != minimum.patch) + return (running.patch > minimum.patch); + else + return true; + } + } + }; + + var result = []; + if (!version) { + return modules; + } + for (var i = 0; i < modules.length; i++) { + if (vMinMet(modules[i].version_added, version)) { + result[result.length] = modules[i]; + } + } + + return result; + }; + }).filter('uniqueVersion', function() { + return function(modules) { + var result = []; + var inArray = function (needle, haystack) { + var length = haystack.length; + for(var i = 0; i < length; i++) { + if(haystack[i] == needle) return true; + } + return false; + } + + var parseVersionString = function (str) { + if (typeof(str) != 'string') { return false; } + var x = str.split('.'); + // parse from string or default to 0 if can't parse + var maj = parseInt(x[0]) || 0; + var min = parseInt(x[1]) || 0; + var pat = parseInt(x[2]) || 0; + return { + major: maj, + minor: min, + patch: pat + } + } + + for (var i = 0; i < modules.length; i++) { + if (!inArray(modules[i].version_added, result)) { + // Some module do not define version + if (modules[i].version_added) { + result[result.length] = "" + modules[i].version_added; + } + } + } + + result.sort( + function (a, b) { + ao = parseVersionString(a); + bo = parseVersionString(b); + if (ao.major == bo.major) { + if (ao.minor == bo.minor) { + if (ao.patch == bo.patch) { + return 0; + } + else { + return (ao.patch > bo.patch) ? 1 : -1; + } + } + else { + return (ao.minor > bo.minor) ? 1 : -1; + } + } + else { + return (ao.major > bo.major) ? 1 : -1; + } + }); + + return result; + }; + }); + \ No newline at end of file diff --git a/docs/plugins/artifact_fetch_module.rst b/docs/plugins/artifact_fetch_module.rst new file mode 100644 index 0000000..5d0c0bf --- /dev/null +++ b/docs/plugins/artifact_fetch_module.rst @@ -0,0 +1,510 @@ + +.. Document meta + +:orphan: + +.. |antsibull-internal-nbsp| unicode:: 0xA0 + :trim: + +.. role:: ansible-attribute-support-label +.. role:: ansible-attribute-support-property +.. role:: ansible-attribute-support-full +.. role:: ansible-attribute-support-partial +.. role:: ansible-attribute-support-none +.. role:: ansible-attribute-support-na +.. role:: ansible-option-type +.. role:: ansible-option-elements +.. role:: ansible-option-required +.. role:: ansible-option-versionadded +.. role:: ansible-option-aliases +.. role:: ansible-option-choices +.. role:: ansible-option-choices-default-mark +.. role:: ansible-option-default-bold +.. role:: ansible-option-configuration +.. role:: ansible-option-returned-bold +.. role:: ansible-option-sample-bold + +.. Anchors + +.. _ansible_collections.opsmill.infrahub.artifact_fetch_module: + +.. Anchors: short name for ansible.builtin + +.. Anchors: aliases + + + +.. Title + +opsmill.infrahub.artifact_fetch module -- Fetch the content of an artifact from Infrahub +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. Collection note + +.. note:: + This module is part of the `opsmill.infrahub collection `_ (version 0.0.4). + + To install it, use: :code:`ansible-galaxy collection install opsmill.infrahub`. + You need further requirements to be able to use this module, + see :ref:`Requirements ` for details. + + To use it in a playbook, specify: :code:`opsmill.infrahub.artifact_fetch`. + +.. version_added + +.. rst-class:: ansible-version-added + +New in opsmill.infrahub 0.0.3 + +.. contents:: + :local: + :depth: 1 + +.. Deprecated + + +Synopsis +-------- + +.. Description + +- Fetch the content of an artifact from Infrahub through Infrahub SDK + +.. note:: + This module has a corresponding :ref:`action plugin `. + +.. Aliases + + +.. Requirements + +.. _ansible_collections.opsmill.infrahub.artifact_fetch_module_requirements: + +Requirements +------------ +The below requirements are needed on the host that executes this module. + +- infrahub-sdk + + + + + + +.. Options + +Parameters +---------- + +.. rst-class:: ansible-option-table + +.. list-table:: + :width: 100% + :widths: auto + :header-rows: 1 + + * - Parameter + - Comments + + * - .. raw:: html + +
+
+ + .. _ansible_collections.opsmill.infrahub.artifact_fetch_module__parameter-api_endpoint: + + .. rst-class:: ansible-option-title + + **api_endpoint** + + .. raw:: html + + + + .. rst-class:: ansible-option-type-line + + :ansible-option-type:`string` + + .. raw:: html + +
+ + - .. raw:: html + +
+ + Endpoint of the Infrahub API, optional env=INFRAHUB\_API + + + .. raw:: html + +
+ + * - .. raw:: html + +
+
+ + .. _ansible_collections.opsmill.infrahub.artifact_fetch_module__parameter-artifact_name: + + .. rst-class:: ansible-option-title + + **artifact_name** + + .. raw:: html + + + + .. rst-class:: ansible-option-type-line + + :ansible-option-type:`string` / :ansible-option-required:`required` + + .. raw:: html + +
+ + - .. raw:: html + +
+ + Name of the artifact + + + .. raw:: html + +
+ + * - .. raw:: html + +
+
+ + .. _ansible_collections.opsmill.infrahub.artifact_fetch_module__parameter-branch: + + .. rst-class:: ansible-option-title + + **branch** + + .. raw:: html + + + + .. rst-class:: ansible-option-type-line + + :ansible-option-type:`string` + + .. raw:: html + +
+ + - .. raw:: html + +
+ + Branch in which the request is made + + + .. rst-class:: ansible-option-line + + :ansible-option-default-bold:`Default:` :ansible-option-default:`"main"` + + .. raw:: html + +
+ + * - .. raw:: html + +
+
+ + .. _ansible_collections.opsmill.infrahub.artifact_fetch_module__parameter-target_id: + + .. rst-class:: ansible-option-title + + **target_id** + + .. raw:: html + + + + .. rst-class:: ansible-option-type-line + + :ansible-option-type:`string` / :ansible-option-required:`required` + + .. raw:: html + +
+ + - .. raw:: html + +
+ + Id of the target for this artifact + + + .. raw:: html + +
+ + * - .. raw:: html + +
+
+ + .. _ansible_collections.opsmill.infrahub.artifact_fetch_module__parameter-timeout: + + .. rst-class:: ansible-option-title + + **timeout** + + .. raw:: html + + + + .. rst-class:: ansible-option-type-line + + :ansible-option-type:`integer` + + .. raw:: html + +
+ + - .. raw:: html + +
+ + Timeout for Infrahub requests in seconds + + + .. rst-class:: ansible-option-line + + :ansible-option-default-bold:`Default:` :ansible-option-default:`10` + + .. raw:: html + +
+ + * - .. raw:: html + +
+
+ + .. _ansible_collections.opsmill.infrahub.artifact_fetch_module__parameter-token: + + .. rst-class:: ansible-option-title + + **token** + + .. raw:: html + + + + .. rst-class:: ansible-option-type-line + + :ansible-option-type:`string` + + .. raw:: html + +
+ + - .. raw:: html + +
+ + The API token created through Infrahub, optional env=INFRAHUB\_TOKEN + + + .. raw:: html + +
+ + * - .. raw:: html + +
+
+ + .. _ansible_collections.opsmill.infrahub.artifact_fetch_module__parameter-validate_certs: + + .. rst-class:: ansible-option-title + + **validate_certs** + + .. raw:: html + + + + .. rst-class:: ansible-option-type-line + + :ansible-option-type:`boolean` + + .. raw:: html + +
+ + - .. raw:: html + +
+ + Whether or not to validate SSL of the Infrahub instance + + + .. rst-class:: ansible-option-line + + :ansible-option-choices:`Choices:` + + - :ansible-option-choices-entry:`false` + - :ansible-option-choices-entry-default:`true` :ansible-option-choices-default-mark:`← (default)` + + + .. raw:: html + +
+ + +.. Attributes + + +.. Notes + + +.. Seealso + + +.. Examples + +Examples +-------- + +.. code-block:: yaml+jinja + + + + + + +.. Facts + + +.. Return values + +Return Values +------------- +Common return values are documented :ref:`here `, the following are the fields unique to this module: + +.. rst-class:: ansible-option-table + +.. list-table:: + :width: 100% + :widths: auto + :header-rows: 1 + + * - Key + - Description + + * - .. raw:: html + +
+
+ + .. _ansible_collections.opsmill.infrahub.artifact_fetch_module__return-json: + + .. rst-class:: ansible-option-title + + **json** + + .. raw:: html + + + + .. rst-class:: ansible-option-type-line + + :ansible-option-type:`dictionary` + + .. raw:: html + +
+ + - .. raw:: html + +
+ + Content of the artifact in JSON format. + + + .. rst-class:: ansible-option-line + + :ansible-option-returned-bold:`Returned:` success + + + .. raw:: html + +
+ + + * - .. raw:: html + +
+
+ + .. _ansible_collections.opsmill.infrahub.artifact_fetch_module__return-text: + + .. rst-class:: ansible-option-title + + **text** + + .. raw:: html + + + + .. rst-class:: ansible-option-type-line + + :ansible-option-type:`string` + + .. raw:: html + +
+ + - .. raw:: html + +
+ + Content of the artifact in TEXT format. + + + .. rst-class:: ansible-option-line + + :ansible-option-returned-bold:`Returned:` success + + + .. raw:: html + +
+ + + +.. Status (Presently only deprecated) + + +.. Authors + +Authors +~~~~~~~ + +- Damien Garros (@dgarros) + + + +.. Extra links + +Collection links +~~~~~~~~~~~~~~~~ + +.. raw:: html + + + +.. Parsing errors + diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 233f164..a6aa3a7 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -6,7 +6,7 @@ Opsmill.Infrahub ================ -Collection version 0.0.1 +Collection version 0.0.4 .. contents:: :local: @@ -19,7 +19,7 @@ This is a collection of Infrahub Ansible modules **Author:** -* Benoit Kohler +* OpsMill **Supported ansible-core versions:** @@ -47,12 +47,14 @@ These are the plugins in the opsmill.infrahub collection: Modules ~~~~~~~ +* :ref:`artifact_fetch module ` -- Fetch the content of an artifact from Infrahub * :ref:`query_graphql module ` -- Queries and returns elements from Infrahub GraphQL API .. toctree:: :maxdepth: 1 :hidden: + artifact_fetch_module query_graphql_module diff --git a/docs/plugins/inventory_inventory.rst b/docs/plugins/inventory_inventory.rst index 8b48e2e..39a23a3 100644 --- a/docs/plugins/inventory_inventory.rst +++ b/docs/plugins/inventory_inventory.rst @@ -42,7 +42,7 @@ opsmill.infrahub.inventory inventory -- Infrahub inventory source (using GraphQL .. Collection note .. note:: - This inventory plugin is part of the `opsmill.infrahub collection `_ (version 0.0.1). + This inventory plugin is part of the `opsmill.infrahub collection `_ (version 0.0.4). To install it, use: :code:`ansible-galaxy collection install opsmill.infrahub`. diff --git a/docs/plugins/lookup_lookup.rst b/docs/plugins/lookup_lookup.rst index b83204c..056463f 100644 --- a/docs/plugins/lookup_lookup.rst +++ b/docs/plugins/lookup_lookup.rst @@ -42,7 +42,7 @@ opsmill.infrahub.lookup lookup -- Queries and returns elements from Infrahub (us .. Collection note .. note:: - This lookup plugin is part of the `opsmill.infrahub collection `_ (version 0.0.1). + This lookup plugin is part of the `opsmill.infrahub collection `_ (version 0.0.4). To install it, use: :code:`ansible-galaxy collection install opsmill.infrahub`. diff --git a/docs/plugins/query_graphql_module.rst b/docs/plugins/query_graphql_module.rst index 7c7a00f..1699e28 100644 --- a/docs/plugins/query_graphql_module.rst +++ b/docs/plugins/query_graphql_module.rst @@ -42,7 +42,7 @@ opsmill.infrahub.query_graphql module -- Queries and returns elements from Infra .. Collection note .. note:: - This module is part of the `opsmill.infrahub collection `_ (version 0.0.1). + This module is part of the `opsmill.infrahub collection `_ (version 0.0.4). To install it, use: :code:`ansible-galaxy collection install opsmill.infrahub`. You need further requirements to be able to use this module, @@ -68,7 +68,7 @@ Synopsis .. Description -- Queries Infrahub via its GraphQL API through pyinfrahub +- Queries Infrahub via its GraphQL API through Infrahub SDK .. note:: This module has a corresponding :ref:`action plugin `. @@ -84,7 +84,7 @@ Requirements ------------ The below requirements are needed on the host that executes this module. -- pyinfrahub +- infrahub-sdk diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..8dccc19 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +ansible +antsibull +antsibull-docs +sphinx +sphinx-rtd-theme \ No newline at end of file diff --git a/galaxy.yml b/galaxy.yml index ab56476..7d2e8ed 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -10,7 +10,7 @@ namespace: opsmill name: infrahub # The version of the collection. Must be compatible with semantic versioning -version: 0.0.1 +version: 0.0.4 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md @@ -18,7 +18,7 @@ readme: README.md # A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) # @nicks:irc/im.site#channel' authors: - - Benoit Kohler + - OpsMill ### OPTIONAL but strongly recommended diff --git a/plugins/action/artifact_fetch.py b/plugins/action/artifact_fetch.py new file mode 100644 index 0000000..07734cd --- /dev/null +++ b/plugins/action/artifact_fetch.py @@ -0,0 +1,92 @@ +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +"""Infrahub Action Plugin to fetch the content of an artifact.""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import os + +from ansible.errors import AnsibleError +from ansible.module_utils.six import raise_from +from ansible.plugins.action import ActionBase +from ansible.utils.display import Display +from ansible_collections.opsmill.infrahub.plugins.module_utils.infrahub_utils import ( + HAS_INFRAHUBCLIENT, + InfrahubclientWrapper, +) + + +class ActionModule(ActionBase): + """ + Ansible Action Module to fetch the content of an artifact. + + Parameters: + ActionBase (ActionBase): Ansible Action Plugin + """ + + def run(self, tmp=None, task_vars=None): + """ + Run of action plugin to fetch the content of an artifact. + + Parameters: + tmp ([type], optional): [description]. Defaults to None. + task_vars ([type], optional): [description]. Defaults to None. + """ + + if not HAS_INFRAHUBCLIENT: + raise (AnsibleError("infrahub_sdk must be installed to use this plugin")) + + self._supports_check_mode = True + self._supports_async = True + + result = super(ActionModule, self).run(tmp, task_vars) + del tmp + + if result.get("skipped"): + return None + + if result.get("invocation", {}).get("module_args"): + del result["invocation"]["module_args"] + + args = self._task.args + + api_endpoint = args.get("api_endpoint") or os.getenv("INFRAHUB_API") + token = args.get("token") or os.getenv("INFRAHUB_TOKEN") + if api_endpoint is None: + raise AnsibleError("Missing Infrahub API Endpoint") + if token is None: + raise AnsibleError("Missing Infrahub TOKEN") + + api_endpoint = api_endpoint.strip("/") + + validate_certs = args.get("validate_certs", True) + if not isinstance(validate_certs, bool): + raise AnsibleError("validate_certs must be a boolean") + + timeout = args.get("timeout", 10) + branch = args.get("branch", "main") + + artifact_name = args.get("artifact_name") + target_id = args.get("target_id") + + filters = { + "name__value": artifact_name, + "object__ids": [target_id], + } + + try: + Display().v("Initializing Infrahub Client") + client = InfrahubclientWrapper( + api_endpoint=api_endpoint, + token=token, + branch=branch, + timeout=timeout, + ) + Display().v("Fetch Artifacts") + result = client.fetch_single_artifact(filters=filters) + + except Exception as exp: + raise_from(AnsibleError(str(exp)), exp) + + return result diff --git a/plugins/module_utils/infrahub_utils.py b/plugins/module_utils/infrahub_utils.py index e422cd1..6b92fe0 100644 --- a/plugins/module_utils/infrahub_utils.py +++ b/plugins/module_utils/infrahub_utils.py @@ -49,6 +49,111 @@ def __init__(self, api_endpoint: str, branch: str, token: str, timeout: Optional ) self.branch_manager = InfrahubBranchManagerSync(self.client) + @handle_infrahub_exceptions + def fetch_single_artifact( + self, + filters: Optional[Dict[str, str]] = None, + branch: Optional[str] = None, + ) -> List[InfrahubNodeSync]: + """ + Retrieve all artifact content + + Parameters: + filters (Optional[Dict[str, str]]): Dict of filters to apply on the query + branch (Optional[str]): Name of the branch to query from. Defaults to default_branch. + + Returns: + Dict: Artifact Content + """ + result = { + "json": None, + "text": None, + } + node = self.fetch_single_node( + kind="CoreArtifact", + filters=filters, + branch=branch, + ) + resp = self.client._get(url=f"{self.client.address}/api/storage/object/{node.storage_id.value}") + if node.content_type.value == "application/json": + result["json"] = resp.json() + else: + result["text"] = resp.text + + return result + + @handle_infrahub_exceptions + def fetch_artifacts( + self, + filters: Optional[Dict[str, str]] = None, + branch: Optional[str] = None, + ) -> List[InfrahubNodeSync]: + """ + Retrieve all artifact content + + Parameters: + filters (Optional[Dict[str, str]]): Dict of filters to apply on the query + branch (Optional[str]): Name of the branch to query from. Defaults to default_branch. + + Returns: + List[Dict]: List of Artifact Content + """ + result = { + "json": None, + "text": None, + } + results = List[result] + nodes = self.fetch_nodes( + kind="CoreArtifact", + filters=filters, + branch=branch, + ) + for node in nodes: + resp = self.client._get(url=f"{self.client.address}/api/storage/object/{node.storage_id.value}") + + if node.value == "application/json": + result["json"] = resp.json() + else: + result["text"] = resp.text + + results.append(result) + return results + + @handle_infrahub_exceptions + def fetch_single_node( + self, + kind: str, + include: Optional[List[str]] = None, + exclude: Optional[List[str]] = None, + filters: Optional[Dict[str, str]] = None, + branch: Optional[str] = None, + ) -> InfrahubNodeSync: + """ + Retrieve a single node of a given kind based on filters + + Parameters: + kind (str): kind of the nodes to query + include (Optional[List[str]]): list of attributes/relationship to retrieve + exclude (Optional[List[str]]): list of attributes/relationship to ignore + filters (Optional[Dict[str, str]]): Dict of filters to apply on the query + branch (Optional[str]): Name of the branch to query from. Defaults to default_branch. + + Returns: + InfrahubNodeSync: Single Infrahub Node + """ + if not filters: + raise Exception("At least one filter must be provided") + + node = self.client.get( + kind=kind, + include=include, + populate_store=True, + exclude=exclude, + branch=branch, + **filters, + ) + return node + @handle_infrahub_exceptions def fetch_nodes( self, @@ -315,7 +420,7 @@ def fetch_and_process(self, nodes: List[str]) -> Optional[Dict[str, Any]]: Returns: Optional[Dict[str, Any]]: A dictionary with processed host node attributes, or None if no nodes were processed. """ - all_nodes = [] + all_nodes: List[InfrahubNodeSync] = [] schema_dict = {} node_attributes_dict = {} @@ -364,6 +469,7 @@ def fetch_and_process(self, nodes: List[str]) -> Optional[Dict[str, Any]]: schemas=schema_dict, ) if result: + result["id"] = host_node.id host_node_attributes[str(host_node)] = result return host_node_attributes diff --git a/plugins/modules/artifact_fetch.py b/plugins/modules/artifact_fetch.py new file mode 100644 index 0000000..f45a8f1 --- /dev/null +++ b/plugins/modules/artifact_fetch.py @@ -0,0 +1,101 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2023 Damien Garros +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +"""Ansible plugin definition for artifact_fetch action plugin.""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +module: artifact_fetch +author: + - Damien Garros (@dgarros) +version_added: "0.0.3" +short_description: Fetch the content of an artifact from Infrahub +description: + - Fetch the content of an artifact from Infrahub through Infrahub SDK +requirements: + - infrahub-sdk +options: + api_endpoint: + required: False + description: + - Endpoint of the Infrahub API, optional env=INFRAHUB_API + type: str + token: + required: False + description: + - The API token created through Infrahub, optional env=INFRAHUB_TOKEN + type: str + timeout: + required: False + description: Timeout for Infrahub requests in seconds + type: int + default: 10 + artifact_name: + required: True + description: + - Name of the artifact + type: str + target_id: + description: + - Id of the target for this artifact + required: True + type: str + branch: + required: False + description: + - Branch in which the request is made + type: str + default: main + validate_certs: + description: + - Whether or not to validate SSL of the Infrahub instance + required: False + type: bool + default: True +""" + +EXAMPLES = """ +""" + +RETURN = """ + json: + description: + - Content of the artifact in JSON format. + type: dict + returned: success + text: + description: + - Content of the artifact in TEXT format. + type: str + returned: success +""" + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + """Main definition of Action Plugin for artifact_fetch.""" + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + AnsibleModule( + argument_spec=dict( + api_endpoint=dict(required=False, type="str", default=None), + token=dict(required=False, type="str", no_log=True, default=None), + timeout=dict(required=False, type="int", default=10), + validate_certs=dict(required=False, type="bool", default=True), + branch=dict(required=False, type="str", default="main"), + artifact_name=dict(required=True, type="str"), + target_id=dict(required=True, type="str"), + ), + supports_check_mode=False, + ) + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/plugins/modules/query_graphql.py b/plugins/modules/query_graphql.py index 3f5621c..2205cce 100644 --- a/plugins/modules/query_graphql.py +++ b/plugins/modules/query_graphql.py @@ -15,9 +15,9 @@ version_added: "0.0.1" short_description: Queries and returns elements from Infrahub GraphQL API description: - - Queries Infrahub via its GraphQL API through pyinfrahub + - Queries Infrahub via its GraphQL API through Infrahub SDK requirements: - - pyinfrahub + - infrahub-sdk options: api_endpoint: required: False diff --git a/poetry.lock b/poetry.lock index 9ba0805..7e8bc07 100644 --- a/poetry.lock +++ b/poetry.lock @@ -388,48 +388,6 @@ test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", toml = ["tomli (>=1.1.0)"] yaml = ["PyYAML"] -[[package]] -name = "black" -version = "23.10.1" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, - {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, - {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, - {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, - {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, - {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, - {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"}, - {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"}, - {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"}, - {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, - {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, - {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, - {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, - {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" version = "2023.7.22" @@ -1431,17 +1389,6 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - [[package]] name = "ordered-set" version = "4.1.0" @@ -1481,17 +1428,6 @@ files = [ [package.extras] dev = ["jinja2"] -[[package]] -name = "pathspec" -version = "0.11.2" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - [[package]] name = "pbr" version = "5.11.1" @@ -1945,28 +1881,28 @@ testing = ["pytest (>=6)", "pytest-randomly (>=3)", "pytest-xdist[psutil] (>=2.5 [[package]] name = "ruff" -version = "0.1.0" -description = "An extremely fast Python linter, written in Rust." +version = "0.1.5" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.0-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:87114e254dee35e069e1b922d85d4b21a5b61aec759849f393e1dbb308a00439"}, - {file = "ruff-0.1.0-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:764f36d2982cc4a703e69fb73a280b7c539fd74b50c9ee531a4e3fe88152f521"}, - {file = "ruff-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65f4b7fb539e5cf0f71e9bd74f8ddab74cabdd673c6fb7f17a4dcfd29f126255"}, - {file = "ruff-0.1.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:299fff467a0f163baa282266b310589b21400de0a42d8f68553422fa6bf7ee01"}, - {file = "ruff-0.1.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d412678bf205787263bb702c984012a4f97e460944c072fd7cfa2bd084857c4"}, - {file = "ruff-0.1.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a5391b49b1669b540924640587d8d24128e45be17d1a916b1801d6645e831581"}, - {file = "ruff-0.1.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee8cd57f454cdd77bbcf1e11ff4e0046fb6547cac1922cc6e3583ce4b9c326d1"}, - {file = "ruff-0.1.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa7aeed7bc23861a2b38319b636737bf11cfa55d2109620b49cf995663d3e888"}, - {file = "ruff-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04cd4298b43b16824d9a37800e4c145ba75c29c43ce0d74cad1d66d7ae0a4c5"}, - {file = "ruff-0.1.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7186ccf54707801d91e6314a016d1c7895e21d2e4cd614500d55870ed983aa9f"}, - {file = "ruff-0.1.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d88adfd93849bc62449518228581d132e2023e30ebd2da097f73059900d8dce3"}, - {file = "ruff-0.1.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ad2ccdb3bad5a61013c76a9c1240fdfadf2c7103a2aeebd7bcbbed61f363138f"}, - {file = "ruff-0.1.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b77f6cfa72c6eb19b5cac967cc49762ae14d036db033f7d97a72912770fd8e1c"}, - {file = "ruff-0.1.0-py3-none-win32.whl", hash = "sha256:480bd704e8af1afe3fd444cc52e3c900b936e6ca0baf4fb0281124330b6ceba2"}, - {file = "ruff-0.1.0-py3-none-win_amd64.whl", hash = "sha256:a76ba81860f7ee1f2d5651983f87beb835def94425022dc5f0803108f1b8bfa2"}, - {file = "ruff-0.1.0-py3-none-win_arm64.whl", hash = "sha256:45abdbdab22509a2c6052ecf7050b3f5c7d6b7898dc07e82869401b531d46da4"}, - {file = "ruff-0.1.0.tar.gz", hash = "sha256:ad6b13824714b19c5f8225871cf532afb994470eecb74631cd3500fe817e6b3f"}, + {file = "ruff-0.1.5-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:32d47fc69261c21a4c48916f16ca272bf2f273eb635d91c65d5cd548bf1f3d96"}, + {file = "ruff-0.1.5-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:171276c1df6c07fa0597fb946139ced1c2978f4f0b8254f201281729981f3c17"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ef33cd0bb7316ca65649fc748acc1406dfa4da96a3d0cde6d52f2e866c7b39"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2c205827b3f8c13b4a432e9585750b93fd907986fe1aec62b2a02cf4401eee6"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb408e3a2ad8f6881d0f2e7ad70cddb3ed9f200eb3517a91a245bbe27101d379"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f20dc5e5905ddb407060ca27267c7174f532375c08076d1a953cf7bb016f5a24"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aafb9d2b671ed934998e881e2c0f5845a4295e84e719359c71c39a5363cccc91"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4894dddb476597a0ba4473d72a23151b8b3b0b5f958f2cf4d3f1c572cdb7af7"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00a7ec893f665ed60008c70fe9eeb58d210e6b4d83ec6654a9904871f982a2a"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8c11206b47f283cbda399a654fd0178d7a389e631f19f51da15cbe631480c5b"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fa29e67b3284b9a79b1a85ee66e293a94ac6b7bb068b307a8a373c3d343aa8ec"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9b97fd6da44d6cceb188147b68db69a5741fbc736465b5cea3928fdac0bc1aeb"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:721f4b9d3b4161df8dc9f09aa8562e39d14e55a4dbaa451a8e55bdc9590e20f4"}, + {file = "ruff-0.1.5-py3-none-win32.whl", hash = "sha256:f80c73bba6bc69e4fdc73b3991db0b546ce641bdcd5b07210b8ad6f64c79f1ab"}, + {file = "ruff-0.1.5-py3-none-win_amd64.whl", hash = "sha256:c21fe20ee7d76206d290a76271c1af7a5096bc4c73ab9383ed2ad35f852a0087"}, + {file = "ruff-0.1.5-py3-none-win_arm64.whl", hash = "sha256:82bfcb9927e88c1ed50f49ac6c9728dab3ea451212693fe40d08d314663e412f"}, + {file = "ruff-0.1.5.tar.gz", hash = "sha256:5cbec0ef2ae1748fb194f420fb03fb2c25c3258c86129af7172ff8f198f125ab"}, ] [[package]] @@ -2601,4 +2537,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "c855143394a822c0c0a42c0ad8e12000d625f131c19e9c2749e72ed15bd55aec" +content-hash = "76e68e82aed63850d00033c09ed783e07b36a6ea22a8e216df67da8fa7491a8f" diff --git a/pyproject.toml b/pyproject.toml index 765e735..90a432e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,14 @@ [tool.poetry] name = "infrahub_ansible_modules" -version = "0.0.1" +version = "0.0.4" description = "Ansible collection to interact with Infrahub's API" -authors = ["Benoit Kohler "] license = "GPLv3" [tool.poetry.dependencies] python = ">=3.9,<3.12" ansible-core = "^2.14" -black = "*" -isort = "*" -ruff = "0.1.0" +ruff = "^0.1.5" codecov = "*" coverage = "^6.5" deepdiff = "*" @@ -42,26 +40,6 @@ infrahub_sdk = "^0.2.0" requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" -[tool.black] -line-length = 120 -include = '\.pyi?$' -exclude = ''' - /( - \.git - | \.tox - | \.venv - | env/ - | _build - | build - | dist - | examples - )/ - ''' - -[tool.isort] -profile = "black" -known_first_party = [ "infrahub_sdk" ] - [tool.pylint.general] extension-pkg-whitelist = [ "pydantic", @@ -106,17 +84,39 @@ notes = """, [tool.pylint.similarities] min-similarity-lines = 20 +[tool.ruff] +line-length = 120 + +exclude = [ + ".git", + ".tox", + ".venv", + "env", + "_build", + "build", + "dist", + "examples", +] + +task-tags = [ + "FIXME", + "TODO", + "XXX", +] + [tool.ruff.lint] +preview = true + select = [ - # mccabe + # mccabe complexity "C90", - # pycodestyle (Error) + # pycodestyle errors "E", - # pycodestyle (Warning) + # pycodestyle warnings "W", - # Pyflakes + # pyflakes "F", - # isort + # isort-like checks "I", # flake8-datetimez "DTZ", @@ -131,16 +131,20 @@ select = [ # flake8-2020 "YTT", ] -ignore = [ - "E402", - "F811", -] -[tool.ruff] -line-length = 170 + +#https://docs.astral.sh/ruff/formatter/black/ +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[tool.ruff.lint.pycodestyle] +max-line-length = 150 [tool.ruff.mccabe] # Target max-complexity=10 -max-complexity = 26 +max-complexity = 31 [tool.ruff.per-file-ignores] diff --git a/tasks/__init__.py b/tasks/__init__.py index c5d8e79..341b874 100644 --- a/tasks/__init__.py +++ b/tasks/__init__.py @@ -20,6 +20,11 @@ ns.add_collection(tests) +@task(name="lint") +def lint_all(context: Context): + linter.lint_all(context) + + @task(name="format") def format_all(context: Context): linter.format_all(context) @@ -60,6 +65,7 @@ def galaxy_build(context: Context, force=False): galaxy.galaxy_build(context, force=force) +ns.add_task(lint_all) ns.add_task(format_all) ns.add_task(test_all) ns.add_task(tests_sanity) diff --git a/tasks/linter.py b/tasks/linter.py index e778b55..c7ee7f5 100644 --- a/tasks/linter.py +++ b/tasks/linter.py @@ -7,33 +7,21 @@ @task(name="format") -def format_all(context: Context): +def lint_all(context: Context): """This will run all formatter.""" - format_isort(context) - format_autoflake(context) - format_black(context) - format_ruff(context) - format_yaml(context) + lint_autoflake(context) + lint_ruff(context) + lint_yaml(context) print(f" - [{NAMESPACE}] All formatters have been executed!") # ---------------------------------------------------------------------------- -# Formatting tasks - Python +# Linter tasks - Python # ---------------------------------------------------------------------------- @task -def format_black(context: Context): - """Run black to format all Python files.""" - - print(f" - [{NAMESPACE}] Format code with black") - exec_cmd = f"black {MAIN_DIRECTORY}/" - with context.cd(ESCAPED_REPO_PATH): - context.run(exec_cmd) - - -@task -def format_autoflake(context: Context): +def lint_autoflake(context: Context): """Run autoflack to format all Python files.""" print(f" - [{NAMESPACE}] Format code with autoflake") @@ -43,17 +31,7 @@ def format_autoflake(context: Context): @task -def format_isort(context: Context): - """Run isort to format all Python files.""" - - print(f" - [{NAMESPACE}] Format code with isort") - exec_cmd = f"isort {MAIN_DIRECTORY}" - with context.cd(ESCAPED_REPO_PATH): - context.run(exec_cmd) - - -@task -def format_pylint(context: Context): +def lint_pylint(context: Context): """This will run pylint for the specified name and Python version.""" print(f" - [{NAMESPACE}] Check code with pylint") @@ -63,24 +41,39 @@ def format_pylint(context: Context): @task -def format_ruff(context: Context): +def lint_ruff(context: Context): """This will run ruff.""" print(f" - [{NAMESPACE}] Check code with ruff") - exec_cmd = f"ruff check {MAIN_DIRECTORY}" + exec_cmd = f"ruff format --check --diff {MAIN_DIRECTORY} &&" + exec_cmd += f"ruff check --diff {MAIN_DIRECTORY}" with context.cd(ESCAPED_REPO_PATH): context.run(exec_cmd) # ---------------------------------------------------------------------------- -# Formatting tasks - Yaml +# Linter tasks - Yaml # ---------------------------------------------------------------------------- @task -def format_yaml(context: Context): +def lint_yaml(context: Context): """This will run yamllint to validate formatting of all yaml files.""" print(f" - [{NAMESPACE}] Format yaml with yamllint") exec_cmd = "yamllint ." context.run(exec_cmd, pty=True) + + +# ---------------------------------------------------------------------------- +# Formatting tasks - Python +# ---------------------------------------------------------------------------- +@task +def format_ruff(context: Context): + """This will run ruff.""" + + print(f" - [{NAMESPACE}] Check code with ruff") + exec_cmd = f"ruff format {MAIN_DIRECTORY} && " + exec_cmd += f"ruff check --fix {MAIN_DIRECTORY}" + with context.cd(ESCAPED_REPO_PATH): + context.run(exec_cmd) diff --git a/tests/integration/targets/inventory/output-test-inventory5.yml b/tests/integration/targets/inventory/output-test-inventory5.yml new file mode 100644 index 0000000..faca449 --- /dev/null +++ b/tests/integration/targets/inventory/output-test-inventory5.yml @@ -0,0 +1,216 @@ +all: + children: + platform_eos: + hosts: + atl1-edge1: {} + den1-edge1: + ansible_host: 172.20.20.19 + hostname: den1-edge1 + id: 1797c9d0-a62c-783a-3f86-c51113fab5bb + name: den1-edge1 + platform: eos + primary_address: + address: 172.20.20.19/24 + description: null + site: + description: null + name: den1 + type: SITE + tags: + - description: null + name: green + - description: null + name: red + dfw1-edge1: + ansible_host: 172.20.20.25 + hostname: dfw1-edge1 + id: 1797c9d5-adc5-b5e5-3f8c-c5190978965f + name: dfw1-edge1 + platform: eos + primary_address: + address: 172.20.20.25/24 + description: null + site: + description: null + name: dfw1 + type: SITE + tags: + - description: null + name: green + - description: null + name: red + jfk1-edge1: + ansible_host: 172.20.20.18 + hostname: jfk1-edge1 + id: 1797c9d0-a7aa-f414-3f8c-c511d9f19dd5 + name: jfk1-edge1 + platform: eos + primary_address: + address: 172.20.20.18/24 + description: null + site: + description: null + name: jfk1 + type: SITE + tags: + - description: null + name: green + - description: null + name: red + ord1-edge1: + ansible_host: 172.20.20.17 + hostname: ord1-edge1 + id: 1797c9d0-a2c6-5364-3f8f-c51193294272 + name: ord1-edge1 + platform: eos + primary_address: + address: 172.20.20.17/24 + description: null + site: + description: null + name: ord1 + type: SITE + tags: + - description: null + name: green + - description: null + name: red + platform_ios: + hosts: + atl1-edge2: {} + den1-edge2: + ansible_host: 172.20.20.21 + hostname: den1-edge2 + id: 1797c9d3-540a-a32f-3f83-c516007a318a + name: den1-edge2 + platform: ios + primary_address: + address: 172.20.20.21/24 + description: null + site: + description: null + name: den1 + type: SITE + tags: + - description: null + name: blue + - description: null + name: green + - description: null + name: red + dfw1-edge2: + ansible_host: 172.20.20.26 + hostname: dfw1-edge2 + id: 1797c9d6-d507-102a-3f80-c51271794b20 + name: dfw1-edge2 + platform: ios + primary_address: + address: 172.20.20.26/24 + description: null + site: + description: null + name: dfw1 + type: SITE + tags: + - description: null + name: blue + - description: null + name: green + - description: null + name: red + jfk1-edge2: + ansible_host: 172.20.20.24 + hostname: jfk1-edge2 + id: 1797c9d3-553e-ec3f-3f8f-c5199816e660 + name: jfk1-edge2 + platform: ios + primary_address: + address: 172.20.20.24/24 + description: null + site: + description: null + name: jfk1 + type: SITE + tags: + - description: null + name: blue + - description: null + name: green + - description: null + name: red + ord1-edge2: + ansible_host: 172.20.20.23 + hostname: ord1-edge2 + id: 1797c9d3-5d7c-ec91-3f8f-c51eef981241 + name: ord1-edge2 + platform: ios + primary_address: + address: 172.20.20.23/24 + description: null + site: + description: null + name: ord1 + type: SITE + tags: + - description: null + name: blue + - description: null + name: green + - description: null + name: red + site_atl1: + hosts: + atl1-edge1: + ansible_host: 172.20.20.20 + hostname: atl1-edge1 + id: 1797c9d0-a8fe-0171-3f81-c514ddee60e4 + name: atl1-edge1 + platform: eos + primary_address: + address: 172.20.20.20/24 + description: null + site: + description: null + name: atl1 + type: SITE + tags: + - description: null + name: green + - description: null + name: red + atl1-edge2: + ansible_host: 172.20.20.22 + hostname: atl1-edge2 + id: 1797c9d3-5216-2c88-3f8e-c514d88d4c11 + name: atl1-edge2 + platform: ios + primary_address: + address: 172.20.20.22/24 + description: null + site: + description: null + name: atl1 + type: SITE + tags: + - description: null + name: blue + - description: null + name: green + - description: null + name: red + site_den1: + hosts: + den1-edge1: {} + den1-edge2: {} + site_dfw1: + hosts: + dfw1-edge1: {} + dfw1-edge2: {} + site_jfk1: + hosts: + jfk1-edge1: {} + jfk1-edge2: {} + site_ord1: + hosts: + ord1-edge1: {} + ord1-edge2: {} diff --git a/tests/integration/targets/inventory/test-inventory3.yml b/tests/integration/targets/inventory/test-inventory3.yml index 9112885..f925b0e 100644 --- a/tests/integration/targets/inventory/test-inventory3.yml +++ b/tests/integration/targets/inventory/test-inventory3.yml @@ -20,6 +20,6 @@ nodes: compose: hostname: name platform: platform.ansible_network_os - ansible_host: primary_address.address + ansible_host: primary_address.address | ansible.utils.ipaddr('address') keyed_groups: diff --git a/tests/integration/targets/inventory/test-inventory5.yml b/tests/integration/targets/inventory/test-inventory5.yml index a2a4d03..ad41d7d 100644 --- a/tests/integration/targets/inventory/test-inventory5.yml +++ b/tests/integration/targets/inventory/test-inventory5.yml @@ -7,7 +7,7 @@ api_endpoint: "http://localhost:8000" strict: true -branch: "jfk1-update-edge-ips" +branch: "main" nodes: InfraDevice: @@ -17,13 +17,14 @@ nodes: - platform - site - tags - - interfaces compose: hostname: name platform: platform.ansible_network_os - ansible_host: primary_address.address + ansible_host: primary_address.address | ansible.utils.ipaddr('address') keyed_groups: - prefix: site key: site.name + - prefix: platform + key: platform diff --git a/tests/integration/targets/inventory/test-inventory6.yml b/tests/integration/targets/inventory/test-inventory6.yml index 60cb858..a80790d 100644 --- a/tests/integration/targets/inventory/test-inventory6.yml +++ b/tests/integration/targets/inventory/test-inventory6.yml @@ -21,12 +21,13 @@ nodes: - primary_address - platform - site + - interfaces compose: hostname: name platform: platform.ansible_network_os - ansible_host: primary_address.address + ansible_host: primary_address.address | ansible.utils.ipaddr('address') keyed_groups: - - prefix: site - key: site.name + - prefix: platform + key: platform diff --git a/tests/integration/targets/tasks/fetch_artifact.yml b/tests/integration/targets/tasks/fetch_artifact.yml new file mode 100644 index 0000000..3b292c2 --- /dev/null +++ b/tests/integration/targets/tasks/fetch_artifact.yml @@ -0,0 +1,31 @@ +--- +- name: Infrahub action plugin Fetch_artifact + gather_facts: false + hosts: platform_eos + vars: + ansible_become: true + + tasks: + - name: Query Startup Config for Edge Devices + opsmill.infrahub.artifact_fetch: + artifact_name: "Startup Config for Edge devices" + target_id: "{{ id }}" + register: startup_artifact + + - name: Save configs to localhost + ansible.builtin.copy: + content: "{{ startup_artifact.text }}" + dest: "/tmp/{{ inventory_hostname }}-startup.conf" + delegate_to: localhost + + - name: Query Openconfig Interface for Arista devices + opsmill.infrahub.artifact_fetch: + artifact_name: "Openconfig Interface for Arista devices" + target_id: "{{ id }}" + register: openconfig_artifact + + - name: Save configs to localhost + ansible.builtin.copy: + content: "{{ openconfig_artifact.text }}" + dest: "/tmp/{{ inventory_hostname }}-openconfig.conf" + delegate_to: localhost diff --git a/tests/integration/targets/tasks/graphql_query.yml b/tests/integration/targets/tasks/graphql_query.yml index 77518ff..358095b 100644 --- a/tests/integration/targets/tasks/graphql_query.yml +++ b/tests/integration/targets/tasks/graphql_query.yml @@ -26,5 +26,5 @@ - name: Action Plugin opsmill.infrahub.query_graphql: - query: "{{ query_dict}}" + query: "{{ query_dict }}" graph_variables: "{{ variables }}"