* Generate galaxy.yml based on single source of truth
* Fix up tests and align file names
* Minor Makefile tweak
* Remove link in galaxy.yml file and make it a template file
* Moved collections docs to dev_guide
* change Makefile clean path
* Added readme to example meta file
* review fixes
* Use newer style for doc generation script
* Fix mistake in dev_guide index
* removed uneeded file, fixed links and added preview banner
* Moved banner for sanity test
... | ... |
@@ -34,6 +34,7 @@ docs/docsite/*.html |
34 | 34 |
docs/docsite/htmlout |
35 | 35 |
docs/docsite/rst/cli/ansible-*.rst |
36 | 36 |
docs/docsite/rst/cli/ansible.rst |
37 |
+docs/docsite/rst/dev_guide/collections_galaxy_meta.rst |
|
37 | 38 |
docs/docsite/rst/dev_guide/testing/sanity/index.rst.new |
38 | 39 |
docs/docsite/rst/modules/*.rst |
39 | 40 |
docs/docsite/rst/playbooks_directives.rst |
... | ... |
@@ -5,6 +5,7 @@ TESTING_FORMATTER=../bin/testing_formatter.sh |
5 | 5 |
KEYWORD_DUMPER=../../hacking/build-ansible.py document-keywords |
6 | 6 |
CONFIG_DUMPER=../../hacking/build-ansible.py document-config |
7 | 7 |
GENERATE_CLI=../../hacking/build-ansible.py generate-man |
8 |
+COLLECTION_DUMPER=../../hacking/build-ansible.py collection-meta |
|
8 | 9 |
ifeq ($(shell echo $(OS) | egrep -ic 'Darwin|FreeBSD|OpenBSD|DragonFly'),1) |
9 | 10 |
CPUS ?= $(shell sysctl hw.ncpu|awk '{print $$2}') |
10 | 11 |
else |
... | ... |
@@ -37,7 +38,7 @@ all: docs |
37 | 37 |
|
38 | 38 |
docs: htmldocs |
39 | 39 |
|
40 |
-generate_rst: config cli keywords modules plugins testing |
|
40 |
+generate_rst: collections_meta config cli keywords modules plugins testing |
|
41 | 41 |
|
42 | 42 |
htmldocs: generate_rst |
43 | 43 |
CPUS=$(CPUS) $(MAKE) -f Makefile.sphinx html |
... | ... |
@@ -75,9 +76,13 @@ clean: |
75 | 75 |
rm -f rst/plugins/*/*.rst |
76 | 76 |
rm -f rst/reference_appendices/config.rst |
77 | 77 |
rm -f rst/reference_appendices/playbooks_keywords.rst |
78 |
+ rm -f rst/dev_guide/collections_galaxy_meta.rst |
|
78 | 79 |
|
79 | 80 |
.PHONY: docs clean |
80 | 81 |
|
82 |
+collections_meta: ../templates/collections_galaxy_meta.rst.j2 |
|
83 |
+ PYTHONPATH=../../lib $(COLLECTION_DUMPER) --template-file=../templates/collections_galaxy_meta.rst.j2 --output-dir=rst/dev_guide/ ../../lib/ansible/galaxy/data/collections_galaxy_meta.yml |
|
84 |
+ |
|
81 | 85 |
# TODO: make generate_man output dir cli option |
82 | 86 |
cli: |
83 | 87 |
mkdir -p rst/cli |
84 | 88 |
deleted file mode 100644 |
... | ... |
@@ -1,325 +0,0 @@ |
1 |
-:orphan: |
|
2 |
- |
|
3 |
-.. _collections: |
|
4 |
- |
|
5 |
-*********** |
|
6 |
-Collections |
|
7 |
-*********** |
|
8 |
- |
|
9 |
- |
|
10 |
-Collections are a distribution format for Ansible content. They can be used to |
|
11 |
-package and distribute playbooks, roles, modules, and plugins. |
|
12 |
-You will be able to publish and use collections through `Ansible's Galaxy repository <https://galaxy.ansible.com>`_. |
|
13 |
- |
|
14 |
-.. important:: |
|
15 |
- This feature is available in Ansible 2.8 as a *Technology Preview* and therefore is not fully supported. It should only be used for testing and should not be deployed in a production environment. |
|
16 |
- Future Galaxy or Ansible releases may introduce breaking changes. |
|
17 |
- |
|
18 |
- |
|
19 |
-.. contents:: |
|
20 |
- :local: |
|
21 |
- |
|
22 |
-Collection structure |
|
23 |
-==================== |
|
24 |
- |
|
25 |
-Collections follow a simple data structure. None of the directories are required unless you have specific content that belongs in one of them. They do require a ``galaxy.yml`` file at the root level of the collection. This file contains all of the metadata that Galaxy |
|
26 |
-and other tools need in order to package, build and publish the collection.:: |
|
27 |
- |
|
28 |
- collection/ |
|
29 |
- ├── docs/ |
|
30 |
- ├── galaxy.yml |
|
31 |
- ├── plugins/ |
|
32 |
- │ ├── modules/ |
|
33 |
- │ │ └── module1.py |
|
34 |
- │ ├── inventory/ |
|
35 |
- │ └── .../ |
|
36 |
- ├── README.md |
|
37 |
- ├── roles/ |
|
38 |
- │ ├── role1/ |
|
39 |
- │ ├── role2/ |
|
40 |
- │ └── .../ |
|
41 |
- ├── playbooks/ |
|
42 |
- │ ├── files/ |
|
43 |
- │ ├── vars/ |
|
44 |
- │ ├── templates/ |
|
45 |
- │ └── tasks/ |
|
46 |
- └── tests/ |
|
47 |
- |
|
48 |
- |
|
49 |
-.. note:: |
|
50 |
- * We will only accept ``.yml`` extensions for galaxy.yml. |
|
51 |
- * A full structure can be found at `Draft collection <https://github.com/bcoca/collection>`_ |
|
52 |
- * Not all directories are currently in use. Those are placeholders for future features. |
|
53 |
- |
|
54 |
- |
|
55 |
-galaxy.yml |
|
56 |
- |
|
57 |
-This file contains the information about a collection that is necessary for Ansible tools to operate. |
|
58 |
-``galaxy.yml`` has the following fields (subject to changes and expansion): |
|
59 |
- |
|
60 |
-.. code-block:: yaml |
|
61 |
- |
|
62 |
- namespace: "namespace_name" |
|
63 |
- name: "collection_name" |
|
64 |
- version: "1.0.12" |
|
65 |
- authors: |
|
66 |
- - "Author1" |
|
67 |
- - "Author2 (https://author2.example.com)" |
|
68 |
- - "Author3 <author3@example.com>" |
|
69 |
- dependencies: |
|
70 |
- "other_namespace.collection1": ">=1.0.0" |
|
71 |
- "other_namespace.collection2": ">=2.0.0,<3.0.0" |
|
72 |
- "anderson55.my_collection": "*" # note: "*" selects the highest version available |
|
73 |
- license: |
|
74 |
- - "MIT" |
|
75 |
- tags: |
|
76 |
- - demo |
|
77 |
- - collection |
|
78 |
- repository: "https://www.github.com/my_org/my_collection" |
|
79 |
- |
|
80 |
- |
|
81 |
-Required Fields: |
|
82 |
- - ``namespace``: the namespace that the collection lives under. It must be a valid Python identifier, |
|
83 |
- and may only contain alphanumeric characters and underscores. Additionally |
|
84 |
- the ``namespace`` cannot start with underscores or numbers and cannot contain consecutive |
|
85 |
- underscores. |
|
86 |
- - ``name``: the collection's name. Has the same character restrictions as ``namespace``. |
|
87 |
- - ``version``: the collection's version. To upload to Galaxy, it must be compatible with semantic versioning. |
|
88 |
- |
|
89 |
- |
|
90 |
-Optional Fields: |
|
91 |
- - ``dependencies``: A dictionary where keys are collections, and values are version |
|
92 |
- range `specifiers <https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification>`_. |
|
93 |
- It is good practice to depend on a version range to minimize conflicts, and pin to a |
|
94 |
- a major version to protect against breaking changes. For example: ``"user1.collection1": ">=1.2.2,<2.0.0"`` |
|
95 |
- This field allows other collections as dependencies, not traditional roles. |
|
96 |
- - ``description``: A short summary description of the collection. |
|
97 |
- - ``license``: Either a single license or a list of licenses for content inside of a collection. |
|
98 |
- Galaxy currently only accepts `SPDX <https://spdx.org/licenses/>`_ licenses. |
|
99 |
- - ``tags``: a list of tags. These have the same character requirements as ``namespace`` and ``name``. |
|
100 |
- - ``repository``: URL of originating SCM repository. |
|
101 |
- |
|
102 |
-docs directory |
|
103 |
- |
|
104 |
-Keep general documentation for the collection here. Plugins and modules will still keep their specific documentation embedded as Python docstrings. Use the ``docs`` folder to describe how to use the roles and plugins the collection provides, role requirements, and so on. Currently we are looking at Markdown as the standard format for documentation files, but this is subject to change. |
|
105 |
- |
|
106 |
-We are `updating ansible-doc <https://github.com/ansible/ansible/pull/57764>`_ to allow showing documentation for plugins inside a collection:: |
|
107 |
- |
|
108 |
- ansible-doc -t lookup mycol.myname.lookup1 |
|
109 |
- |
|
110 |
-The ``ansible-doc`` command requires the fully qualified collection name (FQCN) to display specific plugin documentation. |
|
111 |
- |
|
112 |
- |
|
113 |
-plugins directory |
|
114 |
- |
|
115 |
- Add a 'per plugin type' specific subdirectory here, including ``module_utils`` which is usable not only by modules, but by any other plugin by using their FQCN. This is a way to distribute modules, lookups, filters, and so on, without having to import a role in every play. |
|
116 |
- |
|
117 |
- |
|
118 |
-roles directory |
|
119 |
- |
|
120 |
-Collection roles are mostly the same as existing roles, but with a couple of limitations: |
|
121 |
- |
|
122 |
- - Role names are now limited to contain only lowercase alphanumeric characters, plus ``_`` and start with an alpha character. |
|
123 |
- - Roles cannot have their own plugins any more. The plugins must live in the collection ``plugins`` directory and will be accessible to the collection roles. |
|
124 |
- |
|
125 |
-The directory name of the role is used as the role name. Therefore, the directory name must comply with the |
|
126 |
-above role name rules. |
|
127 |
-The collection import into Galaxy will fail if a role name does not comply with these rules. |
|
128 |
- |
|
129 |
-You can migrate 'traditional roles' into a collection but they must follow the rules above. You man need to rename roles if they don't conform. You will have to move or link any role-based plugins to the collection specific directories. |
|
130 |
- |
|
131 |
-.. note:: |
|
132 |
- |
|
133 |
- For roles imported into Galaxy directly from a GitHub repository, setting the ``role_name`` value in the role's |
|
134 |
- metadata overrides the role name used by Galaxy. For collections, that value is ignored. When importing a |
|
135 |
- collection, Galaxy uses the role directory as the name of the role and ignores the ``role_name`` metadata value. |
|
136 |
- |
|
137 |
-playbooks directory |
|
138 |
- |
|
139 |
-TBD. |
|
140 |
- |
|
141 |
-tests directory |
|
142 |
- |
|
143 |
-TBD. Expect tests for the collection itself, including Molecule files, to reside here. |
|
144 |
- |
|
145 |
- |
|
146 |
-.. _creating_collections: |
|
147 |
- |
|
148 |
-Creating collections |
|
149 |
-==================== |
|
150 |
- |
|
151 |
-This is currently is a work in progress. We created the `Mazer <https://galaxy.ansible.com/docs/mazer/>`_ command line tool |
|
152 |
-available at the `Ansible Mazer project <https://github.com/ansible/mazer>`_. as a proof of concept for packaging, |
|
153 |
-distributing and installing collections. You can install ``mazer`` with ``pip install mazer`` or checkout the code directly. |
|
154 |
- |
|
155 |
-.. Note:: |
|
156 |
- All the documentation below that use ``mazer`` might be updated to use another tool in the future as ``mazer`` will not be updated in the future. |
|
157 |
- |
|
158 |
-We are working on integrating this into Ansible itself for 2.9. Currently we have an `ansible-galaxy PR <https://github.com/ansible/ansible/pull/57106>`_ incorporating some of the commands into ``ansible-galaxy``. Currently it is not installable outside Ansible, but we hope to land this into development soon so early adopters can test. |
|
159 |
- |
|
160 |
-.. Note:: |
|
161 |
- Any references to ``ansible-galaxy`` below will be of a 'working version' either in this PR or subsequently in development. As such, the command and this documentation section is subject to frequent change. |
|
162 |
- |
|
163 |
-We also plan to update `Ansible Molecule <https://github.com/ansible/molecule>`_, for a full developer toolkit with integrated testing. |
|
164 |
- |
|
165 |
-In the end, to get started with authoring a new collection it should be as simple as: |
|
166 |
- |
|
167 |
-.. code-block:: bash |
|
168 |
- |
|
169 |
- collection_dir#>ansible-galaxy collection init |
|
170 |
- |
|
171 |
- |
|
172 |
-And then populating the directories with the content you want inside the collection. For now you can optionally clone from https://github.com/bcoca/collection to get the directory structure (or just create the directories as you need them). |
|
173 |
- |
|
174 |
-.. _building_collections: |
|
175 |
- |
|
176 |
-Building collections |
|
177 |
-==================== |
|
178 |
- |
|
179 |
-Collections are built by running ``mazer build`` from inside the collection's root directory. |
|
180 |
-This will create a ``releases/`` directory inside the collection with the build artifacts, |
|
181 |
-which can be uploaded to Galaxy.:: |
|
182 |
- |
|
183 |
- collection/ |
|
184 |
- ├── ... |
|
185 |
- ├── releases/ |
|
186 |
- │ └── namespace_name-collection_name-1.0.12.tar.gz |
|
187 |
- └── ... |
|
188 |
- |
|
189 |
-.. note:: |
|
190 |
- Changing the filename of the tarball in the release directory so that it doesn't match |
|
191 |
- the data in ``galaxy.yml`` will cause the import to fail. |
|
192 |
- |
|
193 |
- |
|
194 |
-This tarball itself can be used to install the collection on target systems. It is mainly intended to upload to Galaxy as a distribution method, but you should be able to use directly. |
|
195 |
- |
|
196 |
-Publishing collections |
|
197 |
-====================== |
|
198 |
- |
|
199 |
-We are in the process of updating Ansible Galaxy to manage collections as it currently manages roles. |
|
200 |
- |
|
201 |
- |
|
202 |
-Upload from the Galaxy website |
|
203 |
- |
|
204 |
-Go to the `My Content <https://galaxy.ansible.com/my-content/namespaces>`_ page, and click the **Add Content** button on one of your namespaces. From |
|
205 |
-the **Add Content** dialogue, click **Upload New Collection**, and select the collection archive file from your local |
|
206 |
-filesystem. |
|
207 |
- |
|
208 |
-When uploading collections it doesn't matter which namespace you select. The collection will be uploaded to the |
|
209 |
-namespace specified in the collection metadata in the ``galaxy.yml`` file. If you're not an owner of the |
|
210 |
-namespace, the upload request will fail. |
|
211 |
- |
|
212 |
-Once Galaxy uploads and accepts a collection, you will be redirected to the **My Imports** page, which displays output from the |
|
213 |
-import process, including any errors or warnings about the metadata and content contained in the collection. |
|
214 |
- |
|
215 |
-Upload using mazer |
|
216 |
- |
|
217 |
-You can upload collection artifacts with ``mazer``, as shown in the following example: |
|
218 |
- |
|
219 |
-.. code-block:: bash |
|
220 |
- |
|
221 |
- mazer publish --api-key=SECRET path/to/namespace_name-collection_name-1.0.12.tar.gz |
|
222 |
- |
|
223 |
-The above command triggers an import process, just as if the collection had been uploaded through the Galaxy website. Use the **My Imports** |
|
224 |
-page to view the output from the import process. |
|
225 |
- |
|
226 |
-Your API key can be found on `the preferences page in Galaxy <https://galaxy.ansible.com/me/preferences>`_. |
|
227 |
- |
|
228 |
-To learn more about Mazer, see `Mazer <https://galaxy.ansible.com/docs/mazer/>`_. |
|
229 |
- |
|
230 |
- |
|
231 |
-Collection versions |
|
232 |
- |
|
233 |
-Once you upload a version of a collection, you cannot delete or modify that version. Ensure that everything looks okay before |
|
234 |
-uploading. The only way to change a collection is to release a new version. The latest version of a collection (by highest version number) |
|
235 |
-will be the version displayed everywhere in Galaxy; however, users will still be able to download older versions. |
|
236 |
- |
|
237 |
- |
|
238 |
-Installing collections |
|
239 |
-====================== |
|
240 |
- |
|
241 |
-The recommended way to install a collection is: |
|
242 |
- |
|
243 |
-.. code-block:: bash |
|
244 |
- |
|
245 |
- #> ansible-galaxy collection install mycollection -p /path |
|
246 |
- |
|
247 |
-assuming the collection is hosted in Galaxy. |
|
248 |
- |
|
249 |
-You can also use a tarball resulting from your build: |
|
250 |
- |
|
251 |
-.. code-block:: bash |
|
252 |
- |
|
253 |
- #> ansible-galaxy install mynamespace.mycollection.0.1.0.tgz -p /path |
|
254 |
- |
|
255 |
- |
|
256 |
-As a path you should use one of the values configured in `COLLECTIONS_PATHS <https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths>`_. This is also where Ansible itself will expect to find collections when attempting to use them. |
|
257 |
- |
|
258 |
-You can also keep a collection adjacent to the current playbook, under a ``collections/ansible_collection/`` directory structure. |
|
259 |
- |
|
260 |
-:: |
|
261 |
- |
|
262 |
- play.yml |
|
263 |
- ├── collections/ |
|
264 |
- │ └── ansbile_collection/ |
|
265 |
- │ └── myname/ |
|
266 |
- │ └── mycol/<collection structure lives here> |
|
267 |
- |
|
268 |
- |
|
269 |
- |
|
270 |
- |
|
271 |
-Using collections |
|
272 |
-================= |
|
273 |
- |
|
274 |
-Once installed, you can reference collection content by its FQCN: |
|
275 |
- |
|
276 |
-.. code-block:: yaml |
|
277 |
- |
|
278 |
- - hosts: all |
|
279 |
- tasks: |
|
280 |
- - myname.mycol.mymodule: |
|
281 |
- option1: value |
|
282 |
- |
|
283 |
-This works for roles or any type of plugin distributed within the collection: |
|
284 |
- |
|
285 |
-.. code-block:: yaml |
|
286 |
- |
|
287 |
- - hosts: all |
|
288 |
- tasks: |
|
289 |
- - include_role: |
|
290 |
- name : myname.mycol.role1 |
|
291 |
- - myname.mycol.mymodule: |
|
292 |
- option1: value |
|
293 |
- |
|
294 |
- - debug: |
|
295 |
- msg: '{{ lookup("myname.mycol.lookup1", 'param1')| myname.mycol.filter1 }}' |
|
296 |
- |
|
297 |
- |
|
298 |
-To avoid a lot of typing, you can use the ``collections`` keyword added in Ansbile 2.8: |
|
299 |
- |
|
300 |
- |
|
301 |
-.. code-block:: yaml |
|
302 |
- |
|
303 |
- - hosts: all |
|
304 |
- collections: |
|
305 |
- - myname.mycol |
|
306 |
- tasks: |
|
307 |
- - include_role: |
|
308 |
- name: role1 |
|
309 |
- - mymodule: |
|
310 |
- option1: value |
|
311 |
- |
|
312 |
- - debug: |
|
313 |
- msg: '{{ lookup("myname.mycol.lookup1", 'param1')| myname.mycol.filter1 }}' |
|
314 |
- |
|
315 |
-This keyword creates a 'search path' for non namespaced plugin references. It does not import roles or anything else. |
|
316 |
-Notice that you still need the FQCN for non-action or module plugins. |
317 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,284 @@ |
0 |
+:orphan: |
|
1 |
+ |
|
2 |
+.. _collections: |
|
3 |
+ |
|
4 |
+*********** |
|
5 |
+Collections |
|
6 |
+*********** |
|
7 |
+ |
|
8 |
+ |
|
9 |
+Collections are a distribution format for Ansible content. They can be used to |
|
10 |
+package and distribute playbooks, roles, modules, and plugins. |
|
11 |
+You will be able to publish and use collections through `Ansible's Galaxy repository <https://galaxy.ansible.com>`_. |
|
12 |
+ |
|
13 |
+.. important:: |
|
14 |
+ This feature is available in Ansible 2.8 as a *Technology Preview* and therefore is not fully supported. It should only be used for testing and should not be deployed in a production environment. |
|
15 |
+ Future Galaxy or Ansible releases may introduce breaking changes. |
|
16 |
+ |
|
17 |
+ |
|
18 |
+.. contents:: |
|
19 |
+ :local: |
|
20 |
+ |
|
21 |
+Collection structure |
|
22 |
+==================== |
|
23 |
+ |
|
24 |
+Collections follow a simple data structure. None of the directories are required unless you have specific content that belongs in one of them. They do require a ``galaxy.yml`` file at the root level of the collection. This file contains all of the metadata that Galaxy |
|
25 |
+and other tools need in order to package, build and publish the collection.:: |
|
26 |
+ |
|
27 |
+ collection/ |
|
28 |
+ ├── docs/ |
|
29 |
+ ├── galaxy.yml |
|
30 |
+ ├── plugins/ |
|
31 |
+ │ ├── modules/ |
|
32 |
+ │ │ └── module1.py |
|
33 |
+ │ ├── inventory/ |
|
34 |
+ │ └── .../ |
|
35 |
+ ├── README.md |
|
36 |
+ ├── roles/ |
|
37 |
+ │ ├── role1/ |
|
38 |
+ │ ├── role2/ |
|
39 |
+ │ └── .../ |
|
40 |
+ ├── playbooks/ |
|
41 |
+ │ ├── files/ |
|
42 |
+ │ ├── vars/ |
|
43 |
+ │ ├── templates/ |
|
44 |
+ │ └── tasks/ |
|
45 |
+ └── tests/ |
|
46 |
+ |
|
47 |
+ |
|
48 |
+.. note:: |
|
49 |
+ * We will only accept ``.yml`` extensions for galaxy.yml. |
|
50 |
+ * A full structure can be found at `Draft collection <https://github.com/bcoca/collection>`_ |
|
51 |
+ * Not all directories are currently in use. Those are placeholders for future features. |
|
52 |
+ |
|
53 |
+ |
|
54 |
+galaxy.yml |
|
55 |
+---------- |
|
56 |
+ |
|
57 |
+A collection must have a ``galaxy.yml`` file that contains the necessary information to build a collection artifact. |
|
58 |
+See :ref:`collections_galaxy_meta` for details on how this file is structured. |
|
59 |
+ |
|
60 |
+ |
|
61 |
+docs directory |
|
62 |
+--------------- |
|
63 |
+ |
|
64 |
+Keep general documentation for the collection here. Plugins and modules will still keep their specific documentation embedded as Python docstrings. Use the ``docs`` folder to describe how to use the roles and plugins the collection provides, role requirements, and so on. Currently we are looking at Markdown as the standard format for documentation files, but this is subject to change. |
|
65 |
+ |
|
66 |
+We are `updating ansible-doc <https://github.com/ansible/ansible/pull/57764>`_ to allow showing documentation for plugins inside a collection:: |
|
67 |
+ |
|
68 |
+ ansible-doc -t lookup mycol.myname.lookup1 |
|
69 |
+ |
|
70 |
+The ``ansible-doc`` command requires the fully qualified collection name (FQCN) to display specific plugin documentation. |
|
71 |
+ |
|
72 |
+ |
|
73 |
+plugins directory |
|
74 |
+------------------ |
|
75 |
+ |
|
76 |
+ Add a 'per plugin type' specific subdirectory here, including ``module_utils`` which is usable not only by modules, but by any other plugin by using their FQCN. This is a way to distribute modules, lookups, filters, and so on, without having to import a role in every play. |
|
77 |
+ |
|
78 |
+ |
|
79 |
+roles directory |
|
80 |
+---------------- |
|
81 |
+ |
|
82 |
+Collection roles are mostly the same as existing roles, but with a couple of limitations: |
|
83 |
+ |
|
84 |
+ - Role names are now limited to contain only lowercase alphanumeric characters, plus ``_`` and start with an alpha character. |
|
85 |
+ - Roles cannot have their own plugins any more. The plugins must live in the collection ``plugins`` directory and will be accessible to the collection roles. |
|
86 |
+ |
|
87 |
+The directory name of the role is used as the role name. Therefore, the directory name must comply with the |
|
88 |
+above role name rules. |
|
89 |
+The collection import into Galaxy will fail if a role name does not comply with these rules. |
|
90 |
+ |
|
91 |
+You can migrate 'traditional roles' into a collection but they must follow the rules above. You man need to rename roles if they don't conform. You will have to move or link any role-based plugins to the collection specific directories. |
|
92 |
+ |
|
93 |
+.. note:: |
|
94 |
+ |
|
95 |
+ For roles imported into Galaxy directly from a GitHub repository, setting the ``role_name`` value in the role's |
|
96 |
+ metadata overrides the role name used by Galaxy. For collections, that value is ignored. When importing a |
|
97 |
+ collection, Galaxy uses the role directory as the name of the role and ignores the ``role_name`` metadata value. |
|
98 |
+ |
|
99 |
+playbooks directory |
|
100 |
+-------------------- |
|
101 |
+ |
|
102 |
+TBD. |
|
103 |
+ |
|
104 |
+tests directory |
|
105 |
+---------------- |
|
106 |
+ |
|
107 |
+TBD. Expect tests for the collection itself, including Molecule files, to reside here. |
|
108 |
+ |
|
109 |
+ |
|
110 |
+.. _creating_collections: |
|
111 |
+ |
|
112 |
+Creating collections |
|
113 |
+==================== |
|
114 |
+ |
|
115 |
+This is currently is a work in progress. We created the `Mazer <https://galaxy.ansible.com/docs/mazer/>`_ command line tool |
|
116 |
+available at the `Ansible Mazer project <https://github.com/ansible/mazer>`_. as a proof of concept for packaging, |
|
117 |
+distributing and installing collections. You can install ``mazer`` with ``pip install mazer`` or checkout the code directly. |
|
118 |
+ |
|
119 |
+.. Note:: |
|
120 |
+ All the documentation below that use ``mazer`` might be updated to use another tool in the future as ``mazer`` will not be updated in the future. |
|
121 |
+ |
|
122 |
+We are working on integrating this into Ansible itself for 2.9. Currently we have an `ansible-galaxy PR <https://github.com/ansible/ansible/pull/57106>`_ incorporating some of the commands into ``ansible-galaxy``. Currently it is not installable outside Ansible, but we hope to land this into development soon so early adopters can test. |
|
123 |
+ |
|
124 |
+.. Note:: |
|
125 |
+ Any references to ``ansible-galaxy`` below will be of a 'working version' either in this PR or subsequently in development. As such, the command and this documentation section is subject to frequent change. |
|
126 |
+ |
|
127 |
+We also plan to update `Ansible Molecule <https://github.com/ansible/molecule>`_, for a full developer toolkit with integrated testing. |
|
128 |
+ |
|
129 |
+In the end, to get started with authoring a new collection it should be as simple as: |
|
130 |
+ |
|
131 |
+.. code-block:: bash |
|
132 |
+ |
|
133 |
+ collection_dir#>ansible-galaxy collection init |
|
134 |
+ |
|
135 |
+ |
|
136 |
+And then populating the directories with the content you want inside the collection. For now you can optionally clone from https://github.com/bcoca/collection to get the directory structure (or just create the directories as you need them). |
|
137 |
+ |
|
138 |
+.. _building_collections: |
|
139 |
+ |
|
140 |
+Building collections |
|
141 |
+==================== |
|
142 |
+ |
|
143 |
+Collections are built by running ``mazer build`` from inside the collection's root directory. |
|
144 |
+This will create a ``releases/`` directory inside the collection with the build artifacts, |
|
145 |
+which can be uploaded to Galaxy.:: |
|
146 |
+ |
|
147 |
+ collection/ |
|
148 |
+ ├── ... |
|
149 |
+ ├── releases/ |
|
150 |
+ │ └── namespace_name-collection_name-1.0.12.tar.gz |
|
151 |
+ └── ... |
|
152 |
+ |
|
153 |
+.. note:: |
|
154 |
+ Changing the filename of the tarball in the release directory so that it doesn't match |
|
155 |
+ the data in ``galaxy.yml`` will cause the import to fail. |
|
156 |
+ |
|
157 |
+ |
|
158 |
+This tarball itself can be used to install the collection on target systems. It is mainly intended to upload to Galaxy as a distribution method, but you should be able to use directly. |
|
159 |
+ |
|
160 |
+Publishing collections |
|
161 |
+====================== |
|
162 |
+ |
|
163 |
+We are in the process of updating Ansible Galaxy to manage collections as it currently manages roles. |
|
164 |
+ |
|
165 |
+ |
|
166 |
+Upload from the Galaxy website |
|
167 |
+------------------------------ |
|
168 |
+ |
|
169 |
+Go to the `My Content <https://galaxy.ansible.com/my-content/namespaces>`_ page, and click the **Add Content** button on one of your namespaces. From |
|
170 |
+the **Add Content** dialogue, click **Upload New Collection**, and select the collection archive file from your local |
|
171 |
+filesystem. |
|
172 |
+ |
|
173 |
+When uploading collections it doesn't matter which namespace you select. The collection will be uploaded to the |
|
174 |
+namespace specified in the collection metadata in the ``galaxy.yml`` file. If you're not an owner of the |
|
175 |
+namespace, the upload request will fail. |
|
176 |
+ |
|
177 |
+Once Galaxy uploads and accepts a collection, you will be redirected to the **My Imports** page, which displays output from the |
|
178 |
+import process, including any errors or warnings about the metadata and content contained in the collection. |
|
179 |
+ |
|
180 |
+Upload using mazer |
|
181 |
+------------------ |
|
182 |
+ |
|
183 |
+You can upload collection artifacts with ``mazer``, as shown in the following example: |
|
184 |
+ |
|
185 |
+.. code-block:: bash |
|
186 |
+ |
|
187 |
+ mazer publish --api-key=SECRET path/to/namespace_name-collection_name-1.0.12.tar.gz |
|
188 |
+ |
|
189 |
+The above command triggers an import process, just as if the collection had been uploaded through the Galaxy website. Use the **My Imports** |
|
190 |
+page to view the output from the import process. |
|
191 |
+ |
|
192 |
+Your API key can be found on `the preferences page in Galaxy <https://galaxy.ansible.com/me/preferences>`_. |
|
193 |
+ |
|
194 |
+To learn more about Mazer, see `Mazer <https://galaxy.ansible.com/docs/mazer/>`_. |
|
195 |
+ |
|
196 |
+ |
|
197 |
+Collection versions |
|
198 |
+------------------- |
|
199 |
+ |
|
200 |
+Once you upload a version of a collection, you cannot delete or modify that version. Ensure that everything looks okay before |
|
201 |
+uploading. The only way to change a collection is to release a new version. The latest version of a collection (by highest version number) |
|
202 |
+will be the version displayed everywhere in Galaxy; however, users will still be able to download older versions. |
|
203 |
+ |
|
204 |
+ |
|
205 |
+Installing collections |
|
206 |
+====================== |
|
207 |
+ |
|
208 |
+The recommended way to install a collection is: |
|
209 |
+ |
|
210 |
+.. code-block:: bash |
|
211 |
+ |
|
212 |
+ #> ansible-galaxy collection install mycollection -p /path |
|
213 |
+ |
|
214 |
+assuming the collection is hosted in Galaxy. |
|
215 |
+ |
|
216 |
+You can also use a tarball resulting from your build: |
|
217 |
+ |
|
218 |
+.. code-block:: bash |
|
219 |
+ |
|
220 |
+ #> ansible-galaxy install mynamespace.mycollection.0.1.0.tgz -p /path |
|
221 |
+ |
|
222 |
+ |
|
223 |
+As a path you should use one of the values configured in `COLLECTIONS_PATHS <https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths>`_. This is also where Ansible itself will expect to find collections when attempting to use them. |
|
224 |
+ |
|
225 |
+You can also keep a collection adjacent to the current playbook, under a ``collections/ansible_collection/`` directory structure. |
|
226 |
+ |
|
227 |
+:: |
|
228 |
+ |
|
229 |
+ play.yml |
|
230 |
+ ├── collections/ |
|
231 |
+ │ └── ansbile_collection/ |
|
232 |
+ │ └── myname/ |
|
233 |
+ │ └── mycol/<collection structure lives here> |
|
234 |
+ |
|
235 |
+ |
|
236 |
+ |
|
237 |
+ |
|
238 |
+Using collections |
|
239 |
+================= |
|
240 |
+ |
|
241 |
+Once installed, you can reference collection content by its FQCN: |
|
242 |
+ |
|
243 |
+.. code-block:: yaml |
|
244 |
+ |
|
245 |
+ - hosts: all |
|
246 |
+ tasks: |
|
247 |
+ - myname.mycol.mymodule: |
|
248 |
+ option1: value |
|
249 |
+ |
|
250 |
+This works for roles or any type of plugin distributed within the collection: |
|
251 |
+ |
|
252 |
+.. code-block:: yaml |
|
253 |
+ |
|
254 |
+ - hosts: all |
|
255 |
+ tasks: |
|
256 |
+ - include_role: |
|
257 |
+ name : myname.mycol.role1 |
|
258 |
+ - myname.mycol.mymodule: |
|
259 |
+ option1: value |
|
260 |
+ |
|
261 |
+ - debug: |
|
262 |
+ msg: '{{ lookup("myname.mycol.lookup1", 'param1')| myname.mycol.filter1 }}' |
|
263 |
+ |
|
264 |
+ |
|
265 |
+To avoid a lot of typing, you can use the ``collections`` keyword added in Ansbile 2.8: |
|
266 |
+ |
|
267 |
+ |
|
268 |
+.. code-block:: yaml |
|
269 |
+ |
|
270 |
+ - hosts: all |
|
271 |
+ collections: |
|
272 |
+ - myname.mycol |
|
273 |
+ tasks: |
|
274 |
+ - include_role: |
|
275 |
+ name: role1 |
|
276 |
+ - mymodule: |
|
277 |
+ option1: value |
|
278 |
+ |
|
279 |
+ - debug: |
|
280 |
+ msg: '{{ lookup("myname.mycol.lookup1", 'param1')| myname.mycol.filter1 }}' |
|
281 |
+ |
|
282 |
+This keyword creates a 'search path' for non namespaced plugin references. It does not import roles or anything else. |
|
283 |
+Notice that you still need the FQCN for non-action or module plugins. |
85 | 87 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,74 @@ |
0 |
+.. _collections_galaxy_meta: |
|
1 |
+ |
|
2 |
+************************************ |
|
3 |
+Collection Galaxy Metadata Structure |
|
4 |
+************************************ |
|
5 |
+ |
|
6 |
+.. important:: |
|
7 |
+ This feature is available in Ansible 2.8 as a *Technology Preview* and therefore is not fully supported. It should only be used for testing and should not be deployed in a production environment. |
|
8 |
+ Future Galaxy or Ansible releases may introduce breaking changes. |
|
9 |
+ |
|
10 |
+A key component of an Ansible collection is the ``galaxy.yml`` file placed in the root directory of a collection. This |
|
11 |
+file contains the metadata of the collection that is used to generate a collection artifact. |
|
12 |
+ |
|
13 |
+Structure |
|
14 |
+========= |
|
15 |
+ |
|
16 |
+The ``galaxy.yml`` file must contain the following keys in valid YAML: |
|
17 |
+ |
|
18 |
+.. raw:: html |
|
19 |
+ |
|
20 |
+ <table border=0 cellpadding=0 class="documentation-table"> |
|
21 |
+ {# Header of the documentation -#} |
|
22 |
+ <tr> |
|
23 |
+ <th>Key</th> |
|
24 |
+ <th width="100%">Comments</th> |
|
25 |
+ </tr> |
|
26 |
+ {% for entry in options %} |
|
27 |
+ <tr> |
|
28 |
+ {# key name with required or type label #} |
|
29 |
+ <td> |
|
30 |
+ <b>@{ entry.key }@</b> |
|
31 |
+ <div style="font-size: small"> |
|
32 |
+ <span style="color: purple">@{ entry.type | documented_type }@</span> |
|
33 |
+ {% if entry.get('required', False) %} / <span style="color: red">required</span>{% endif %} |
|
34 |
+ </div> |
|
35 |
+ </td> |
|
36 |
+ {# Comments #} |
|
37 |
+ <td> |
|
38 |
+ {% if entry.description is string %} |
|
39 |
+ <div>@{ entry.description | replace('\n', '\n ') | html_ify }@</div> |
|
40 |
+ {% else %} |
|
41 |
+ {% for desc in entry.description %} |
|
42 |
+ <div>@{ desc | replace('\n', '\n ') | html_ify }@</div> |
|
43 |
+ {% endfor %} |
|
44 |
+ {% endif %} |
|
45 |
+ </td> |
|
46 |
+ </tr> |
|
47 |
+ {% endfor %} |
|
48 |
+ </table> |
|
49 |
+ <br/> |
|
50 |
+ |
|
51 |
+Examples |
|
52 |
+======== |
|
53 |
+ |
|
54 |
+.. code-block:: yaml |
|
55 |
+ |
|
56 |
+ namespace: "namespace_name" |
|
57 |
+ name: "collection_name" |
|
58 |
+ version: "1.0.12" |
|
59 |
+ readme: "README.md" |
|
60 |
+ authors: |
|
61 |
+ - "Author1" |
|
62 |
+ - "Author2 (https://author2.example.com)" |
|
63 |
+ - "Author3 <author3@example.com>" |
|
64 |
+ dependencies: |
|
65 |
+ "other_namespace.collection1": ">=1.0.0" |
|
66 |
+ "other_namespace.collection2": ">=2.0.0,<3.0.0" |
|
67 |
+ "anderson55.my_collection": "*" # note: "*" selects the highest version available |
|
68 |
+ license: |
|
69 |
+ - "MIT" |
|
70 |
+ tags: |
|
71 |
+ - demo |
|
72 |
+ - collection |
|
73 |
+ repository: "https://www.github.com/my_org/my_collection" |
0 | 74 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,68 @@ |
0 |
+# coding: utf-8 |
|
1 |
+# Copyright: (c) 2019, Ansible Project |
|
2 |
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) |
|
3 |
+ |
|
4 |
+# Make coding more python3-ish |
|
5 |
+from __future__ import (absolute_import, division, print_function) |
|
6 |
+__metaclass__ = type |
|
7 |
+ |
|
8 |
+import os |
|
9 |
+import os.path |
|
10 |
+import pathlib |
|
11 |
+ |
|
12 |
+import yaml |
|
13 |
+from jinja2 import Environment, FileSystemLoader |
|
14 |
+from ansible.module_utils._text import to_bytes |
|
15 |
+ |
|
16 |
+# Pylint doesn't understand Python3 namespace modules. |
|
17 |
+from ..change_detection import update_file_if_different # pylint: disable=relative-beyond-top-level |
|
18 |
+from ..commands import Command # pylint: disable=relative-beyond-top-level |
|
19 |
+from ..jinja2.filters import documented_type, html_ify # pylint: disable=relative-beyond-top-level |
|
20 |
+ |
|
21 |
+ |
|
22 |
+DEFAULT_TEMPLATE_FILE = 'collections_galaxy_meta.rst.j2' |
|
23 |
+DEFAULT_TEMPLATE_DIR = pathlib.Path(__file__).parents[4] / 'docs/templates' |
|
24 |
+ |
|
25 |
+ |
|
26 |
+class DocumentCollectionMeta(Command): |
|
27 |
+ name = 'collection-meta' |
|
28 |
+ |
|
29 |
+ @classmethod |
|
30 |
+ def init_parser(cls, add_parser): |
|
31 |
+ parser = add_parser(cls.name, description='Generate collection galaxy.yml documentation from shared metadata') |
|
32 |
+ parser.add_argument("-t", "--template-file", action="store", dest="template_file", |
|
33 |
+ default=DEFAULT_TEMPLATE_FILE, |
|
34 |
+ help="Jinja2 template to use for the config") |
|
35 |
+ parser.add_argument("-T", "--template-dir", action="store", dest="template_dir", |
|
36 |
+ default=DEFAULT_TEMPLATE_DIR, |
|
37 |
+ help="directory containing Jinja2 templates") |
|
38 |
+ parser.add_argument("-o", "--output-dir", action="store", dest="output_dir", default='/tmp/', |
|
39 |
+ help="Output directory for rst files") |
|
40 |
+ parser.add_argument("collection_defs", metavar="COLLECTION-OPTION-DEFINITIONS.yml", type=str, |
|
41 |
+ help="Source for collection metadata option docs") |
|
42 |
+ |
|
43 |
+ @staticmethod |
|
44 |
+ def main(args): |
|
45 |
+ output_dir = os.path.abspath(args.output_dir) |
|
46 |
+ template_file_full_path = os.path.abspath(os.path.join(args.template_dir, args.template_file)) |
|
47 |
+ template_file = os.path.basename(template_file_full_path) |
|
48 |
+ template_dir = os.path.dirname(template_file_full_path) |
|
49 |
+ |
|
50 |
+ with open(args.collection_defs) as f: |
|
51 |
+ options = yaml.safe_load(f) |
|
52 |
+ |
|
53 |
+ env = Environment(loader=FileSystemLoader(template_dir), |
|
54 |
+ variable_start_string="@{", |
|
55 |
+ variable_end_string="}@", |
|
56 |
+ trim_blocks=True) |
|
57 |
+ env.filters['documented_type'] = documented_type |
|
58 |
+ env.filters['html_ify'] = html_ify |
|
59 |
+ |
|
60 |
+ template = env.get_template(template_file) |
|
61 |
+ output_name = os.path.join(output_dir, template_file.replace('.j2', '')) |
|
62 |
+ temp_vars = {'options': options} |
|
63 |
+ |
|
64 |
+ data = to_bytes(template.render(temp_vars)) |
|
65 |
+ update_file_if_different(output_name, data) |
|
66 |
+ |
|
67 |
+ return 0 |
... | ... |
@@ -11,7 +11,6 @@ __metaclass__ = type |
11 | 11 |
import datetime |
12 | 12 |
import glob |
13 | 13 |
import json |
14 |
-import optparse |
|
15 | 14 |
import os |
16 | 15 |
import re |
17 | 16 |
import sys |
... | ... |
@@ -34,10 +33,9 @@ except ImportError: |
34 | 34 |
import jinja2 |
35 | 35 |
import yaml |
36 | 36 |
from jinja2 import Environment, FileSystemLoader |
37 |
-from jinja2.runtime import Undefined |
|
38 | 37 |
|
39 | 38 |
from ansible.errors import AnsibleError |
40 |
-from ansible.module_utils._text import to_bytes, to_text |
|
39 |
+from ansible.module_utils._text import to_bytes |
|
41 | 40 |
from ansible.module_utils.common.collections import is_sequence |
42 | 41 |
from ansible.module_utils.parsing.convert_bool import boolean |
43 | 42 |
from ansible.module_utils.six import iteritems, string_types |
... | ... |
@@ -48,6 +46,7 @@ from ansible.utils.display import Display |
48 | 48 |
# Pylint doesn't understand Python3 namespace modules. |
49 | 49 |
from ..change_detection import update_file_if_different # pylint: disable=relative-beyond-top-level |
50 | 50 |
from ..commands import Command # pylint: disable=relative-beyond-top-level |
51 |
+from ..jinja2.filters import do_max, documented_type, html_ify, rst_fmt, rst_ify, rst_xline # pylint: disable=relative-beyond-top-level |
|
51 | 52 |
|
52 | 53 |
|
53 | 54 |
##################################################################################### |
... | ... |
@@ -67,14 +66,6 @@ EXAMPLE_YAML = os.path.abspath(os.path.join( |
67 | 67 |
os.path.dirname(os.path.realpath(__file__)), os.pardir, 'examples', 'DOCUMENTATION.yml' |
68 | 68 |
)) |
69 | 69 |
|
70 |
-_ITALIC = re.compile(r"I\(([^)]+)\)") |
|
71 |
-_BOLD = re.compile(r"B\(([^)]+)\)") |
|
72 |
-_MODULE = re.compile(r"M\(([^)]+)\)") |
|
73 |
-_URL = re.compile(r"U\(([^)]+)\)") |
|
74 |
-_LINK = re.compile(r"L\(([^)]+),([^)]+)\)") |
|
75 |
-_CONST = re.compile(r"C\(([^)]+)\)") |
|
76 |
-_RULER = re.compile(r"HORIZONTALLINE") |
|
77 |
- |
|
78 | 70 |
DEPRECATED = b" (D)" |
79 | 71 |
|
80 | 72 |
pp = PrettyPrinter() |
... | ... |
@@ -98,74 +89,6 @@ def from_kludge_ns(key): |
98 | 98 |
return NS_MAP[key] |
99 | 99 |
|
100 | 100 |
|
101 |
-# The max filter was added in Jinja2-2.10. Until we can require that version, use this |
|
102 |
-def do_max(seq): |
|
103 |
- return max(seq) |
|
104 |
- |
|
105 |
- |
|
106 |
-def rst_ify(text): |
|
107 |
- ''' convert symbols like I(this is in italics) to valid restructured text ''' |
|
108 |
- |
|
109 |
- try: |
|
110 |
- t = _ITALIC.sub(r"*\1*", text) |
|
111 |
- t = _BOLD.sub(r"**\1**", t) |
|
112 |
- t = _MODULE.sub(r":ref:`\1 <\1_module>`", t) |
|
113 |
- t = _LINK.sub(r"`\1 <\2>`_", t) |
|
114 |
- t = _URL.sub(r"\1", t) |
|
115 |
- t = _CONST.sub(r"``\1``", t) |
|
116 |
- t = _RULER.sub(r"------------", t) |
|
117 |
- except Exception as e: |
|
118 |
- raise AnsibleError("Could not process (%s) : %s" % (text, e)) |
|
119 |
- |
|
120 |
- return t |
|
121 |
- |
|
122 |
- |
|
123 |
-def html_ify(text): |
|
124 |
- ''' convert symbols like I(this is in italics) to valid HTML ''' |
|
125 |
- |
|
126 |
- if not isinstance(text, string_types): |
|
127 |
- text = to_text(text) |
|
128 |
- |
|
129 |
- t = html_escape(text) |
|
130 |
- t = _ITALIC.sub(r"<em>\1</em>", t) |
|
131 |
- t = _BOLD.sub(r"<b>\1</b>", t) |
|
132 |
- t = _MODULE.sub(r"<span class='module'>\1</span>", t) |
|
133 |
- t = _URL.sub(r"<a href='\1'>\1</a>", t) |
|
134 |
- t = _LINK.sub(r"<a href='\2'>\1</a>", t) |
|
135 |
- t = _CONST.sub(r"<code>\1</code>", t) |
|
136 |
- t = _RULER.sub(r"<hr/>", t) |
|
137 |
- |
|
138 |
- return t.strip() |
|
139 |
- |
|
140 |
- |
|
141 |
-def rst_fmt(text, fmt): |
|
142 |
- ''' helper for Jinja2 to do format strings ''' |
|
143 |
- |
|
144 |
- return fmt % (text) |
|
145 |
- |
|
146 |
- |
|
147 |
-def rst_xline(width, char="="): |
|
148 |
- ''' return a restructured text line of a given length ''' |
|
149 |
- |
|
150 |
- return char * width |
|
151 |
- |
|
152 |
- |
|
153 |
-def documented_type(text): |
|
154 |
- ''' Convert any python type to a type for documentation ''' |
|
155 |
- |
|
156 |
- if isinstance(text, Undefined): |
|
157 |
- return '-' |
|
158 |
- if text == 'str': |
|
159 |
- return 'string' |
|
160 |
- if text == 'bool': |
|
161 |
- return 'boolean' |
|
162 |
- if text == 'int': |
|
163 |
- return 'integer' |
|
164 |
- if text == 'dict': |
|
165 |
- return 'dictionary' |
|
166 |
- return text |
|
167 |
- |
|
168 |
- |
|
169 | 101 |
test_list = partial(is_sequence, include_strings=False) |
170 | 102 |
|
171 | 103 |
|
173 | 105 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,100 @@ |
0 |
+# Copyright: (c) 2019, Ansible Project |
|
1 |
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) |
|
2 |
+ |
|
3 |
+# Make coding more python3-ish |
|
4 |
+from __future__ import (absolute_import, division, print_function) |
|
5 |
+__metaclass__ = type |
|
6 |
+ |
|
7 |
+import re |
|
8 |
+ |
|
9 |
+try: |
|
10 |
+ from html import escape as html_escape |
|
11 |
+except ImportError: |
|
12 |
+ # Python-3.2 or later |
|
13 |
+ import cgi |
|
14 |
+ |
|
15 |
+ def html_escape(text, quote=True): |
|
16 |
+ return cgi.escape(text, quote) |
|
17 |
+ |
|
18 |
+from jinja2.runtime import Undefined |
|
19 |
+ |
|
20 |
+from ansible.errors import AnsibleError |
|
21 |
+from ansible.module_utils._text import to_text |
|
22 |
+from ansible.module_utils.six import string_types |
|
23 |
+ |
|
24 |
+ |
|
25 |
+_ITALIC = re.compile(r"I\(([^)]+)\)") |
|
26 |
+_BOLD = re.compile(r"B\(([^)]+)\)") |
|
27 |
+_MODULE = re.compile(r"M\(([^)]+)\)") |
|
28 |
+_URL = re.compile(r"U\(([^)]+)\)") |
|
29 |
+_LINK = re.compile(r"L\(([^)]+),([^)]+)\)") |
|
30 |
+_CONST = re.compile(r"C\(([^)]+)\)") |
|
31 |
+_RULER = re.compile(r"HORIZONTALLINE") |
|
32 |
+ |
|
33 |
+ |
|
34 |
+def html_ify(text): |
|
35 |
+ ''' convert symbols like I(this is in italics) to valid HTML ''' |
|
36 |
+ |
|
37 |
+ if not isinstance(text, string_types): |
|
38 |
+ text = to_text(text) |
|
39 |
+ |
|
40 |
+ t = html_escape(text) |
|
41 |
+ t = _ITALIC.sub(r"<em>\1</em>", t) |
|
42 |
+ t = _BOLD.sub(r"<b>\1</b>", t) |
|
43 |
+ t = _MODULE.sub(r"<span class='module'>\1</span>", t) |
|
44 |
+ t = _URL.sub(r"<a href='\1'>\1</a>", t) |
|
45 |
+ t = _LINK.sub(r"<a href='\2'>\1</a>", t) |
|
46 |
+ t = _CONST.sub(r"<code>\1</code>", t) |
|
47 |
+ t = _RULER.sub(r"<hr/>", t) |
|
48 |
+ |
|
49 |
+ return t.strip() |
|
50 |
+ |
|
51 |
+ |
|
52 |
+def documented_type(text): |
|
53 |
+ ''' Convert any python type to a type for documentation ''' |
|
54 |
+ |
|
55 |
+ if isinstance(text, Undefined): |
|
56 |
+ return '-' |
|
57 |
+ if text == 'str': |
|
58 |
+ return 'string' |
|
59 |
+ if text == 'bool': |
|
60 |
+ return 'boolean' |
|
61 |
+ if text == 'int': |
|
62 |
+ return 'integer' |
|
63 |
+ if text == 'dict': |
|
64 |
+ return 'dictionary' |
|
65 |
+ return text |
|
66 |
+ |
|
67 |
+ |
|
68 |
+# The max filter was added in Jinja2-2.10. Until we can require that version, use this |
|
69 |
+def do_max(seq): |
|
70 |
+ return max(seq) |
|
71 |
+ |
|
72 |
+ |
|
73 |
+def rst_ify(text): |
|
74 |
+ ''' convert symbols like I(this is in italics) to valid restructured text ''' |
|
75 |
+ |
|
76 |
+ try: |
|
77 |
+ t = _ITALIC.sub(r"*\1*", text) |
|
78 |
+ t = _BOLD.sub(r"**\1**", t) |
|
79 |
+ t = _MODULE.sub(r":ref:`\1 <\1_module>`", t) |
|
80 |
+ t = _LINK.sub(r"`\1 <\2>`_", t) |
|
81 |
+ t = _URL.sub(r"\1", t) |
|
82 |
+ t = _CONST.sub(r"``\1``", t) |
|
83 |
+ t = _RULER.sub(r"------------", t) |
|
84 |
+ except Exception as e: |
|
85 |
+ raise AnsibleError("Could not process (%s) : %s" % (text, e)) |
|
86 |
+ |
|
87 |
+ return t |
|
88 |
+ |
|
89 |
+ |
|
90 |
+def rst_fmt(text, fmt): |
|
91 |
+ ''' helper for Jinja2 to do format strings ''' |
|
92 |
+ |
|
93 |
+ return fmt % (text) |
|
94 |
+ |
|
95 |
+ |
|
96 |
+def rst_xline(width, char="="): |
|
97 |
+ ''' return a restructured text line of a given length ''' |
|
98 |
+ |
|
99 |
+ return char * width |
... | ... |
@@ -8,17 +8,18 @@ __metaclass__ = type |
8 | 8 |
import os.path |
9 | 9 |
import re |
10 | 10 |
import shutil |
11 |
+import textwrap |
|
11 | 12 |
import time |
12 | 13 |
import yaml |
13 | 14 |
|
14 |
-from jinja2 import Environment, FileSystemLoader |
|
15 |
+from jinja2 import BaseLoader, Environment, FileSystemLoader |
|
15 | 16 |
|
16 | 17 |
import ansible.constants as C |
17 | 18 |
from ansible import context |
18 | 19 |
from ansible.cli import CLI |
19 | 20 |
from ansible.cli.arguments import option_helpers as opt_help |
20 | 21 |
from ansible.errors import AnsibleError, AnsibleOptionsError |
21 |
-from ansible.galaxy import Galaxy |
|
22 |
+from ansible.galaxy import Galaxy, get_collections_galaxy_meta_info |
|
22 | 23 |
from ansible.galaxy.api import GalaxyAPI |
23 | 24 |
from ansible.galaxy.collection import build_collection, install_collections, parse_collections_requirements_file, \ |
24 | 25 |
publish_collection |
... | ... |
@@ -309,6 +310,56 @@ class GalaxyCLI(CLI): |
309 | 309 |
|
310 | 310 |
raise AnsibleError("Invalid collection name, must be in the format <namespace>.<collection>") |
311 | 311 |
|
312 |
+ @staticmethod |
|
313 |
+ def _get_skeleton_galaxy_yml(template_path, inject_data): |
|
314 |
+ with open(to_bytes(template_path, errors='surrogate_or_strict'), 'rb') as template_obj: |
|
315 |
+ meta_template = to_text(template_obj.read(), errors='surrogate_or_strict') |
|
316 |
+ |
|
317 |
+ galaxy_meta = get_collections_galaxy_meta_info() |
|
318 |
+ |
|
319 |
+ required_config = [] |
|
320 |
+ optional_config = [] |
|
321 |
+ for meta_entry in galaxy_meta: |
|
322 |
+ config_list = required_config if meta_entry.get('required', False) else optional_config |
|
323 |
+ |
|
324 |
+ value = inject_data.get(meta_entry['key'], None) |
|
325 |
+ if not value: |
|
326 |
+ meta_type = meta_entry.get('type', 'str') |
|
327 |
+ |
|
328 |
+ if meta_type == 'str': |
|
329 |
+ value = '' |
|
330 |
+ elif meta_type == 'list': |
|
331 |
+ value = [] |
|
332 |
+ elif meta_type == 'dict': |
|
333 |
+ value = {} |
|
334 |
+ |
|
335 |
+ meta_entry['value'] = value |
|
336 |
+ config_list.append(meta_entry) |
|
337 |
+ |
|
338 |
+ link_pattern = re.compile(r"L\(([^)]+),\s+([^)]+)\)") |
|
339 |
+ const_pattern = re.compile(r"C\(([^)]+)\)") |
|
340 |
+ |
|
341 |
+ def comment_ify(v): |
|
342 |
+ if isinstance(v, list): |
|
343 |
+ v = ". ".join([l.rstrip('.') for l in v]) |
|
344 |
+ |
|
345 |
+ v = link_pattern.sub(r"\1 <\2>", v) |
|
346 |
+ v = const_pattern.sub(r"'\1'", v) |
|
347 |
+ |
|
348 |
+ return textwrap.fill(v, width=117, initial_indent="# ", subsequent_indent="# ", break_on_hyphens=False) |
|
349 |
+ |
|
350 |
+ def to_yaml(v): |
|
351 |
+ return yaml.safe_dump(v, default_flow_style=False).rstrip() |
|
352 |
+ |
|
353 |
+ env = Environment(loader=BaseLoader) |
|
354 |
+ env.filters['comment_ify'] = comment_ify |
|
355 |
+ env.filters['to_yaml'] = to_yaml |
|
356 |
+ |
|
357 |
+ template = env.from_string(meta_template) |
|
358 |
+ meta_value = template.render({'required_config': required_config, 'optional_config': optional_config}) |
|
359 |
+ |
|
360 |
+ return meta_value |
|
361 |
+ |
|
312 | 362 |
############################ |
313 | 363 |
# execute actions |
314 | 364 |
############################ |
... | ... |
@@ -359,30 +410,42 @@ class GalaxyCLI(CLI): |
359 | 359 |
obj_name = context.CLIARGS['{0}_name'.format(galaxy_type)] |
360 | 360 |
|
361 | 361 |
inject_data = dict( |
362 |
- author='your name', |
|
363 | 362 |
description='your description', |
364 |
- company='your company (optional)', |
|
365 |
- license='license (GPL-2.0-or-later, MIT, etc)', |
|
366 |
- issue_tracker_url='http://example.com/issue/tracker', |
|
367 |
- repository_url='http://example.com/repository', |
|
368 |
- documentation_url='http://docs.example.com', |
|
369 |
- homepage_url='http://example.com', |
|
370 |
- min_ansible_version=ansible_version[:3], # x.y |
|
371 | 363 |
ansible_plugin_list_dir=get_versioned_doclink('plugins/plugins.html'), |
372 | 364 |
) |
373 |
- |
|
374 | 365 |
if galaxy_type == 'role': |
375 |
- inject_data['role_name'] = obj_name |
|
376 |
- inject_data['role_type'] = context.CLIARGS['role_type'] |
|
377 |
- inject_data['license'] = 'license (GPL-2.0-or-later, MIT, etc)' |
|
366 |
+ inject_data.update(dict( |
|
367 |
+ author='your name', |
|
368 |
+ company='your company (optional)', |
|
369 |
+ license='license (GPL-2.0-or-later, MIT, etc)', |
|
370 |
+ role_name=obj_name, |
|
371 |
+ role_type=context.CLIARGS['role_type'], |
|
372 |
+ issue_tracker_url='http://example.com/issue/tracker', |
|
373 |
+ repository_url='http://example.com/repository', |
|
374 |
+ documentation_url='http://docs.example.com', |
|
375 |
+ homepage_url='http://example.com', |
|
376 |
+ min_ansible_version=ansible_version[:3], # x.y |
|
377 |
+ )) |
|
378 |
+ |
|
378 | 379 |
obj_path = os.path.join(init_path, obj_name) |
379 | 380 |
elif galaxy_type == 'collection': |
380 | 381 |
namespace, collection_name = obj_name.split('.', 1) |
381 | 382 |
|
382 |
- inject_data['namespace'] = namespace |
|
383 |
- inject_data['collection_name'] = collection_name |
|
384 |
- inject_data['license'] = 'GPL-2.0-or-later' |
|
383 |
+ inject_data.update(dict( |
|
384 |
+ namespace=namespace, |
|
385 |
+ collection_name=collection_name, |
|
386 |
+ version='1.0.0', |
|
387 |
+ readme='README.md', |
|
388 |
+ authors=['your name <example@domain.com>'], |
|
389 |
+ license=['GPL-2.0-or-later'], |
|
390 |
+ repository='http://example.com/repository', |
|
391 |
+ documentation='http://docs.example.com', |
|
392 |
+ homepage='http://example.com', |
|
393 |
+ issues='http://example.com/issue/tracker', |
|
394 |
+ )) |
|
395 |
+ |
|
385 | 396 |
obj_path = os.path.join(init_path, namespace, collection_name) |
397 |
+ |
|
386 | 398 |
b_obj_path = to_bytes(obj_path, errors='surrogate_or_strict') |
387 | 399 |
|
388 | 400 |
if os.path.exists(b_obj_path): |
... | ... |
@@ -395,8 +458,10 @@ class GalaxyCLI(CLI): |
395 | 395 |
"been modified there already." % to_native(obj_path)) |
396 | 396 |
|
397 | 397 |
if obj_skeleton is not None: |
398 |
+ own_skeleton = False |
|
398 | 399 |
skeleton_ignore_expressions = C.GALAXY_ROLE_SKELETON_IGNORE |
399 | 400 |
else: |
401 |
+ own_skeleton = True |
|
400 | 402 |
obj_skeleton = self.galaxy.default_role_skeleton_path |
401 | 403 |
skeleton_ignore_expressions = ['^.*/.git_keep$'] |
402 | 404 |
|
... | ... |
@@ -428,8 +493,22 @@ class GalaxyCLI(CLI): |
428 | 428 |
|
429 | 429 |
for f in files: |
430 | 430 |
filename, ext = os.path.splitext(f) |
431 |
+ |
|
431 | 432 |
if any(r.match(os.path.join(rel_root, f)) for r in skeleton_ignore_re): |
432 | 433 |
continue |
434 |
+ elif galaxy_type == 'collection' and own_skeleton and rel_root == '.' and f == 'galaxy.yml.j2': |
|
435 |
+ # Special use case for galaxy.yml.j2 in our own default collection skeleton. We build the options |
|
436 |
+ # dynamically which requires special options to be set. |
|
437 |
+ |
|
438 |
+ # The templated data's keys must match the key name but the inject data contains collection_name |
|
439 |
+ # instead of name. We just make a copy and change the key back to name for this file. |
|
440 |
+ template_data = inject_data.copy() |
|
441 |
+ template_data['name'] = template_data.pop('collection_name') |
|
442 |
+ |
|
443 |
+ meta_value = GalaxyCLI._get_skeleton_galaxy_yml(os.path.join(root, rel_root, f), template_data) |
|
444 |
+ b_dest_file = to_bytes(os.path.join(obj_path, rel_root, filename), errors='surrogate_or_strict') |
|
445 |
+ with open(b_dest_file, 'wb') as galaxy_obj: |
|
446 |
+ galaxy_obj.write(to_bytes(meta_value, errors='surrogate_or_strict')) |
|
433 | 447 |
elif ext == ".j2" and not in_templates_dir: |
434 | 448 |
src_template = os.path.join(rel_root, f) |
435 | 449 |
dest_file = os.path.join(obj_path, rel_root, filename) |
... | ... |
@@ -24,15 +24,21 @@ from __future__ import (absolute_import, division, print_function) |
24 | 24 |
__metaclass__ = type |
25 | 25 |
|
26 | 26 |
import os |
27 |
+import yaml |
|
27 | 28 |
|
28 | 29 |
from ansible import context |
29 |
-from ansible.errors import AnsibleError |
|
30 |
-from ansible.module_utils.six import string_types |
|
30 |
+from ansible.module_utils._text import to_bytes |
|
31 | 31 |
|
32 | 32 |
# default_readme_template |
33 | 33 |
# default_meta_template |
34 | 34 |
|
35 | 35 |
|
36 |
+def get_collections_galaxy_meta_info(): |
|
37 |
+ meta_path = os.path.join(os.path.dirname(__file__), 'data', 'collections_galaxy_meta.yml') |
|
38 |
+ with open(to_bytes(meta_path, errors='surrogate_or_strict'), 'rb') as galaxy_obj: |
|
39 |
+ return yaml.safe_load(galaxy_obj) |
|
40 |
+ |
|
41 |
+ |
|
36 | 42 |
class Galaxy(object): |
37 | 43 |
''' Keeps global galaxy info ''' |
38 | 44 |
|
... | ... |
@@ -23,6 +23,7 @@ from yaml.error import YAMLError |
23 | 23 |
|
24 | 24 |
import ansible.constants as C |
25 | 25 |
from ansible.errors import AnsibleError |
26 |
+from ansible.galaxy import get_collections_galaxy_meta_info |
|
26 | 27 |
from ansible.module_utils._text import to_bytes, to_native, to_text |
27 | 28 |
from ansible.module_utils import six |
28 | 29 |
from ansible.utils.display import Display |
... | ... |
@@ -524,11 +525,25 @@ def _tarfile_extract(tar, member): |
524 | 524 |
|
525 | 525 |
|
526 | 526 |
def _get_galaxy_yml(b_galaxy_yml_path): |
527 |
- mandatory_keys = frozenset(['namespace', 'name', 'version', 'authors', 'readme']) |
|
528 |
- optional_strings = ('description', 'repository', 'documentation', 'homepage', 'issues', 'license_file') |
|
529 |
- optional_lists = ('license', 'tags', 'authors') # authors isn't optional but this will ensure it is list |
|
530 |
- optional_dicts = ('dependencies',) |
|
531 |
- all_keys = frozenset(list(mandatory_keys) + list(optional_strings) + list(optional_lists) + list(optional_dicts)) |
|
527 |
+ meta_info = get_collections_galaxy_meta_info() |
|
528 |
+ |
|
529 |
+ mandatory_keys = set() |
|
530 |
+ string_keys = set() |
|
531 |
+ list_keys = set() |
|
532 |
+ dict_keys = set() |
|
533 |
+ |
|
534 |
+ for info in meta_info: |
|
535 |
+ if info.get('required', False): |
|
536 |
+ mandatory_keys.add(info['key']) |
|
537 |
+ |
|
538 |
+ key_list_type = { |
|
539 |
+ 'str': string_keys, |
|
540 |
+ 'list': list_keys, |
|
541 |
+ 'dict': dict_keys, |
|
542 |
+ }[info.get('type', 'str')] |
|
543 |
+ key_list_type.add(info['key']) |
|
544 |
+ |
|
545 |
+ all_keys = frozenset(list(mandatory_keys) + list(string_keys) + list(list_keys) + list(dict_keys)) |
|
532 | 546 |
|
533 | 547 |
try: |
534 | 548 |
with open(b_galaxy_yml_path, 'rb') as g_yaml: |
... | ... |
@@ -549,11 +564,11 @@ def _get_galaxy_yml(b_galaxy_yml_path): |
549 | 549 |
% (to_text(b_galaxy_yml_path), ", ".join(extra_keys))) |
550 | 550 |
|
551 | 551 |
# Add the defaults if they have not been set |
552 |
- for optional_string in optional_strings: |
|
552 |
+ for optional_string in string_keys: |
|
553 | 553 |
if optional_string not in galaxy_yml: |
554 | 554 |
galaxy_yml[optional_string] = None |
555 | 555 |
|
556 |
- for optional_list in optional_lists: |
|
556 |
+ for optional_list in list_keys: |
|
557 | 557 |
list_val = galaxy_yml.get(optional_list, None) |
558 | 558 |
|
559 | 559 |
if list_val is None: |
... | ... |
@@ -561,7 +576,7 @@ def _get_galaxy_yml(b_galaxy_yml_path): |
561 | 561 |
elif not isinstance(list_val, list): |
562 | 562 |
galaxy_yml[optional_list] = [list_val] |
563 | 563 |
|
564 |
- for optional_dict in optional_dicts: |
|
564 |
+ for optional_dict in dict_keys: |
|
565 | 565 |
if optional_dict not in galaxy_yml: |
566 | 566 |
galaxy_yml[optional_dict] = {} |
567 | 567 |
|
... | ... |
@@ -655,7 +670,7 @@ def _build_manifest(namespace, name, version, authors, readme, tags, description |
655 | 655 |
'tags': tags, |
656 | 656 |
'description': description, |
657 | 657 |
'license': license_ids, |
658 |
- 'license_file': license_file, |
|
658 |
+ 'license_file': license_file if license_file else None, # Handle galaxy.yml having an empty string (None) |
|
659 | 659 |
'dependencies': dependencies, |
660 | 660 |
'repository': repository, |
661 | 661 |
'documentation': documentation, |
662 | 662 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,98 @@ |
0 |
+# Copyright (c) 2019 Ansible Project |
|
1 |
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) |
|
2 |
+ |
|
3 |
+# key: The name of the key as defined in galaxy.yml |
|
4 |
+# description: Comment/info on the key to be used as the generated doc and auto generated skeleton galaxy.yml file |
|
5 |
+# required: Whether the key is required (default is no) |
|
6 |
+# type: The type of value that can be set, aligns to the values in the plugin formatter |
|
7 |
+--- |
|
8 |
+- key: namespace |
|
9 |
+ description: |
|
10 |
+ - The namespace of the collection. |
|
11 |
+ - This can be a company/brand/organization or product namespace under which all content lives. |
|
12 |
+ - May only contain alphanumeric characters and underscores. Additionally namespaces cannot start with underscores or |
|
13 |
+ numbers and cannot contain consecutive underscores. |
|
14 |
+ required: yes |
|
15 |
+ type: str |
|
16 |
+ |
|
17 |
+- key: name |
|
18 |
+ description: |
|
19 |
+ - The name of the collection. |
|
20 |
+ - Has the same character restrictions as C(namespace). |
|
21 |
+ required: yes |
|
22 |
+ type: str |
|
23 |
+ |
|
24 |
+- key: version |
|
25 |
+ description: |
|
26 |
+ - The version of the collection. |
|
27 |
+ - Must be compatible with semantic versioning. |
|
28 |
+ required: yes |
|
29 |
+ type: str |
|
30 |
+ |
|
31 |
+- key: readme |
|
32 |
+ description: |
|
33 |
+ - The path to the Markdown (.md) readme file. |
|
34 |
+ - This path is relative to the root of the collection. |
|
35 |
+ required: yes |
|
36 |
+ type: str |
|
37 |
+ |
|
38 |
+- key: authors |
|
39 |
+ description: |
|
40 |
+ - A list of the collection's content authors. |
|
41 |
+ - Can be just the name or in the format 'Full Name <email> (url) @nicks:irc/im.site#channel'. |
|
42 |
+ required: yes |
|
43 |
+ type: list |
|
44 |
+ |
|
45 |
+- key: description |
|
46 |
+ description: |
|
47 |
+ - A short summary description of the collection. |
|
48 |
+ type: str |
|
49 |
+ |
|
50 |
+- key: license |
|
51 |
+ description: |
|
52 |
+ - Either a single license or a list of licenses for content inside of a collection. |
|
53 |
+ - Ansible Galaxy currently only accepts L(SPDX,https://spdx.org/licenses/) licenses |
|
54 |
+ - This key is mutually exclusive with C(license_file). |
|
55 |
+ type: list |
|
56 |
+ |
|
57 |
+- key: license_file |
|
58 |
+ description: |
|
59 |
+ - The path to the license file for the collection. |
|
60 |
+ - This path is relative to the root of the collection. |
|
61 |
+ - This key is mutually exclusive with C(license). |
|
62 |
+ type: str |
|
63 |
+ |
|
64 |
+- key: tags |
|
65 |
+ description: |
|
66 |
+ - A list of tags you want to associate with the collection for indexing/searching. |
|
67 |
+ - A tag name has the same character requirements as C(namespace) and C(name). |
|
68 |
+ type: list |
|
69 |
+ |
|
70 |
+- key: dependencies |
|
71 |
+ description: |
|
72 |
+ - Collections that this collection requires to be installed for it to be usable. |
|
73 |
+ - The key of the dict is the collection label C(namespace.name). |
|
74 |
+ - The value is a version range |
|
75 |
+ L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). |
|
76 |
+ - Multiple version range specifiers can be set and are separated by C(,). |
|
77 |
+ type: dict |
|
78 |
+ |
|
79 |
+- key: repository |
|
80 |
+ description: |
|
81 |
+ - The URL of the originating SCM repository. |
|
82 |
+ type: str |
|
83 |
+ |
|
84 |
+- key: documentation |
|
85 |
+ description: |
|
86 |
+ - The URL to any online docs. |
|
87 |
+ type: str |
|
88 |
+ |
|
89 |
+- key: homepage |
|
90 |
+ description: |
|
91 |
+ - The URL to the homepage of the collection/project. |
|
92 |
+ type: str |
|
93 |
+ |
|
94 |
+- key: issues |
|
95 |
+ description: |
|
96 |
+ - The URL to the collection issue tracker. |
|
97 |
+ type: str |
... | ... |
@@ -1,65 +1,11 @@ |
1 | 1 |
### REQUIRED |
2 |
- |
|
3 |
-# this can be a company/brand/organization or product namespace |
|
4 |
-# under which all content lives |
|
5 |
-namespace: {{ namespace }} |
|
6 |
- |
|
7 |
- |
|
8 |
-# the designation of this specific collection |
|
9 |
-name: {{ collection_name }} |
|
10 |
- |
|
11 |
- |
|
12 |
-# semantic versioning compliant version designation |
|
13 |
-version: 1.0.0 |
|
14 |
- |
|
15 |
-# the filename for the readme file which can be either markdown (.md) |
|
16 |
-readme: README.md |
|
17 |
- |
|
18 |
- |
|
19 |
-# a list of the collection's content authors |
|
20 |
-# Ex: 'Full Name <email> (http://site) @nicks:irc/im/site#channel' |
|
21 |
-authors: |
|
22 |
-- {{ author }} <example@domain.com> |
|
23 |
- |
|
24 |
- |
|
25 |
-### OPTIONAL but strongly advised |
|
26 |
- |
|
27 |
-# short summary of the collection |
|
28 |
-description: {{ description }} |
|
29 |
- |
|
30 |
- |
|
31 |
-# Either a single valid SPDX license identifier or a list of valid SPDX license |
|
32 |
-# identifiers, see https://spdx.org/licenses/. Could also set `license_file` |
|
33 |
-# instead to point to the file the specifies the license in the collection |
|
34 |
-# directory. |
|
35 |
-license: {{ license }} |
|
36 |
- |
|
37 |
- |
|
38 |
-# list of keywords you want to associate the collection |
|
39 |
-# with for indexing/search systems |
|
40 |
-tags: [] |
|
41 |
- |
|
42 |
- |
|
43 |
-# A dict of dependencies. A dependency is another collection |
|
44 |
-# this collection requires to be installed for it to be usable. |
|
45 |
-# The key of the dict is the collection label (namespace.name) |
|
46 |
-# and the value is a spec for the semver version required. |
|
47 |
-dependencies: {} |
|
48 |
- |
|
49 |
- |
|
50 |
-### URLs |
|
51 |
- |
|
52 |
-# url of originating SCM repository |
|
53 |
-repository: {{ repository_url }} |
|
54 |
- |
|
55 |
- |
|
56 |
-# url to online docs |
|
57 |
-documentation: {{ documentation_url }} |
|
58 |
- |
|
59 |
- |
|
60 |
-# homepage of the collection/project |
|
61 |
-homepage: {{ homepage_url }} |
|
62 |
- |
|
63 |
- |
|
64 |
-# issue tracker url |
|
65 |
-issues: {{ issue_tracker_url }} |
|
66 | 2 |
\ No newline at end of file |
3 |
+{% for option in required_config %} |
|
4 |
+{{ option.description | comment_ify }} |
|
5 |
+{{ {option.key: option.value} | to_yaml }} |
|
6 |
+{% endfor %} |
|
7 |
+ |
|
8 |
+### OPTIONAL but strongly recommended |
|
9 |
+{% for option in optional_config %} |
|
10 |
+{{ option.description | comment_ify }} |
|
11 |
+{{ {option.key: option.value} | to_yaml }} |
|
12 |
+{% endfor %} |
... | ... |
@@ -496,14 +496,13 @@ def test_collection_default(collection_skeleton): |
496 | 496 |
assert metadata['readme'] == 'README.md' |
497 | 497 |
assert metadata['version'] == '1.0.0' |
498 | 498 |
assert metadata['description'] == 'your description' |
499 |
- assert metadata['license'] == 'GPL-2.0-or-later' |
|
499 |
+ assert metadata['license'] == ['GPL-2.0-or-later'] |
|
500 | 500 |
assert metadata['tags'] == [] |
501 | 501 |
assert metadata['dependencies'] == {} |
502 | 502 |
assert metadata['documentation'] == 'http://docs.example.com' |
503 | 503 |
assert metadata['repository'] == 'http://example.com/repository' |
504 | 504 |
assert metadata['homepage'] == 'http://example.com' |
505 | 505 |
assert metadata['issues'] == 'http://example.com/issue/tracker' |
506 |
- assert len(metadata) == 13 |
|
507 | 506 |
|
508 | 507 |
for d in ['docs', 'plugins', 'roles']: |
509 | 508 |
assert os.path.isdir(os.path.join(collection_skeleton, d)), \ |
... | ... |
@@ -215,11 +215,6 @@ readme: README.md"""], indirect=True) |
215 | 215 |
def test_defaults_galaxy_yml(galaxy_yml): |
216 | 216 |
actual = collection._get_galaxy_yml(galaxy_yml) |
217 | 217 |
|
218 |
- assert sorted(list(actual.keys())) == [ |
|
219 |
- 'authors', 'dependencies', 'description', 'documentation', 'homepage', 'issues', 'license_file', 'license_ids', |
|
220 |
- 'name', 'namespace', 'readme', 'repository', 'tags', 'version', |
|
221 |
- ] |
|
222 |
- |
|
223 | 218 |
assert actual['namespace'] == 'namespace' |
224 | 219 |
assert actual['name'] == 'collection' |
225 | 220 |
assert actual['authors'] == ['Jordan'] |