ansible-community / ansible.content_builder Goto Github PK
View Code? Open in Web Editor NEWA collection to scaffold Ansible plugins.
License: GNU General Public License v3.0
A collection to scaffold Ansible plugins.
License: GNU General Public License v3.0
I'm attempting to create an example project with a simple Dockerfile and Makefile to make it easy to see ansible.content_builder
in action against a given API spec.
# MANIFEST.yaml
---
collection:
path: "example/cloud"
namespace: example
name: cloud
plugins:
- type: "module_openapi"
name: "example_cloud"
content: cloud
api_object_path: /spec/oas.json
# resource: config # path to modules.yaml
action: "generate_all" # generate_schemas generate_modules generate_examples
unique_key: ""
rm_swagger_json: ""
module_version: "0.1.0"
author: "Ansible Cloud Team"
# build.yaml
---
- hosts: localhost
gather_facts: yes
roles:
- ansible.content_builder.run
# requirements.yaml
collections:
# - name: ansible.content_builder
- name: git+https://github.com/ansible-community/ansible.content_builder.git
type: git
version: main
displague/content-builder-example#1
git clone https://github.com/displague/content-builder-example
cd content-builder-example
make spec_url=https://raw.githubusercontent.com/OpenAPITools/openapi-petstore/master/src/main/resources/openapi.yaml
A clean build with a generated collection in example/
, or a clear error message about what was wrong.
The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'resource'. 'dict object' has no attribute 'resource'
The error appears to be in '/root/.ansible/collections/ansible_collections/ansible/content_builder/roles/module_openapi_cloud/tasks/main.yaml': line 10, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: Generate modules for "{{ collection['name'] }}"
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
I am trying to generate schema from Open API json (Nutanix subnet API) and it gives me UnboundLocalError: local variable 'post_properties' referenced before assignment
error.
Error is coming from :
module_openapi
ansible [core 2.15.6]
config file = None
configured module search path = ['/Users/pradeep.bhati/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.9/site-packages/ansible
ansible collection location = /Users/pradeep.bhati/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/local/bin/ansible
python version = 3.9.18 (main, Nov 2 2023, 17:01:24) [Clang 14.0.0 (clang-1400.0.29.202)] (/usr/local/opt/[email protected]/bin/python3.9)
jinja version = 3.0.3
libyaml = True
CONFIG_FILE() = None
Mac OS Monterey
Create build.yaml and manifest.yaml as per below files.
Download open api yaml from https://developers.nutanix.com/api/v1/namespaces/networking/versions/v4.0.b1/yaml and convert it using https://editor-next.swagger.io/ to json format.
build.yaml
---
- hosts: localhost
gather_facts: yes
roles:
- ansible.content_builder.run
manifest.yaml
---
collection:
path: /Users/pradeep.bhati/work/codebase/codegen/content_builder
namespace: nutanix
name: ncp
plugins:
- type: module_openapi
name: subnets
module_version: 1.0.0
rm_swagger_json: /Users/pradeep.bhati/Downloads/openapi3_0.json
api_object_path: /networking/v4.0.b1/config/subnets
resource: subnets
unique_key: ""
author: "[email protected]"
Error:
~/work/codebase/codegen/content_builder ❯ ansible-playbook build.yaml -e manifest_file=manifest.yaml ansible1 12:05:08 PM
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *********************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [localhost]
TASK [ansible.content_builder.run : Validate task input against defined schema] ******************************************************************************
ok: [localhost]
TASK [ansible.content_builder.run : Load settings from manifest file] ****************************************************************************************
ok: [localhost]
TASK [Start scaffolding] *************************************************************************************************************************************
TASK [ansible.content_builder.init : Create the collection directory structure] ******************************************************************************
ok: [localhost] => (item=plugins/modules)
ok: [localhost] => (item=plugins/module_utils)
ok: [localhost] => (item=plugins/module_utils/common/)
ok: [localhost] => (item=plugins/plugin_utils)
ok: [localhost] => (item=plugins/lookup)
ok: [localhost] => (item=plugins/filter)
ok: [localhost] => (item=plugins/action)
ok: [localhost] => (item=plugins/inventory)
ok: [localhost] => (item=plugins/cache)
ok: [localhost] => (item=plugins/test)
ok: [localhost] => (item=docs)
ok: [localhost] => (item=meta)
ok: [localhost] => (item=tests)
TASK [ansible.content_builder.init : Touch the __init__.py in each directory] ********************************************************************************
ok: [localhost] => (item=plugins/modules)
ok: [localhost] => (item=plugins/module_utils)
ok: [localhost] => (item=plugins/module_utils/common/)
ok: [localhost] => (item=plugins/plugin_utils)
ok: [localhost] => (item=plugins/lookup)
ok: [localhost] => (item=plugins/filter)
ok: [localhost] => (item=plugins/action)
ok: [localhost] => (item=plugins/inventory)
ok: [localhost] => (item=plugins/cache)
ok: [localhost] => (item=plugins/test)
ok: [localhost] => (item=docs)
ok: [localhost] => (item=meta)
ok: [localhost] => (item=tests)
TASK [ansible.content_builder.init : Add license file to collection (default is gpl-3.0)] ********************************************************************
ok: [localhost]
TASK [ansible.content_builder.init : Add readme to collection] ***********************************************************************************************
ok: [localhost] => (item={'source': 'README.md.j2', 'destination': '/Users/pradeep.bhati/work/codebase/codegen/content_builder/README.md'})
ok: [localhost] => (item={'source': 'galaxy.yaml.j2', 'destination': '/Users/pradeep.bhati/work/codebase/codegen/content_builder/galaxy.yml'})
TASK [ansible.content_builder.init : Create the collection directory structure] ******************************************************************************
ok: [localhost] => (item=plugins/modules)
ok: [localhost] => (item=plugins/action)
ok: [localhost] => (item=tests)
TASK [ansible.content_builder.scaffold_plugins : Scaffold the specified plugins] *****************************************************************************
included: /Users/pradeep.bhati/.ansible/collections/ansible_collections/ansible/content_builder/roles/scaffold_plugins/tasks/template.yml for localhost => (item={'type': 'module_openapi', 'name': 'subnets', 'module_version': '1.0.0', 'rm_swagger_json': '/Users/pradeep.bhati/Downloads/openapi3_0.json', 'api_object_path': '/networking/v4.0.b1/config/subnets', 'resource': 'subnets', 'unique_key': '', 'author': '[email protected]'})
TASK [ansible.content_builder.scaffold_plugins : Debug message] **********************************************************************************************
ok: [localhost] => {
"msg": "Scaffolding subnets of type module_openapi"
}
TASK [ansible.content_builder.scaffold_plugins : Set facts] **************************************************************************************************
ok: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Set path to main plugin file for generic plugins] ***********************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Set path to main plugin file for specialized plugins] *******************************************************
ok: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Load docstring (if specified)] ******************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Load examples from example file (if specified)] *************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Check to see if the plugin file exists] *********************************************************************
ok: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Check if documentation string can be found] *****************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Extract documentation from plugin (for existing plugin)] ****************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Fail if no docstring could be found] ************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Extract examples from plugin (for existing plugin)] *********************************************************
skipping: [localhost]
TASK [Start scaffolding specialized plugin] ******************************************************************************************************************
TASK [Generate security content] *****************************************************************************************************************************
TASK [ansible.content_builder.module_openapi_security : Get the CURRENT WORKING DIR] *************************************************************************
ok: [localhost]
TASK [ansible.content_builder.module_openapi_security : Create temporary build directory] ********************************************************************
changed: [localhost]
TASK [ansible.content_builder.module_openapi_security : EXECUTE the python script] ***************************************************************************
fatal: [localhost -> 127.0.0.1]: FAILED! => {"changed": false, "cmd": ["python3", "/Users/pradeep.bhati/.ansible/collections/ansible_collections/ansible/content_builder/roles/module_openapi_security/templates/doc_generator.py", "/Users/pradeep.bhati/Downloads/openapi3_0.json", "/networking/v4.0.b1/config/subnets", "subnets", "1.0.0", "subnets", "nutanix", "ncp", "", "[email protected]", "/var/folders/4w/z2gbpknj63zd_mx6003hhpjh0000gp/T/ansible.qjnr_fm0build"], "delta": "0:00:00.167435", "end": "2023-11-10 12:09:39.275832", "msg": "non-zero return code", "rc": 1, "start": "2023-11-10 12:09:39.108397", "stderr": "Traceback (most recent call last):\n File \"/Users/pradeep.bhati/.ansible/collections/ansible_collections/ansible/content_builder/roles/module_openapi_security/templates/doc_generator.py\", line 914, in <module>\n main()\n File \"/Users/pradeep.bhati/.ansible/collections/ansible_collections/ansible/content_builder/roles/module_openapi_security/templates/doc_generator.py\", line 904, in main\n post_properties,\nUnboundLocalError: local variable 'post_properties' referenced before assignment", "stderr_lines": ["Traceback (most recent call last):", " File \"/Users/pradeep.bhati/.ansible/collections/ansible_collections/ansible/content_builder/roles/module_openapi_security/templates/doc_generator.py\", line 914, in <module>", " main()", " File \"/Users/pradeep.bhati/.ansible/collections/ansible_collections/ansible/content_builder/roles/module_openapi_security/templates/doc_generator.py\", line 904, in main", " post_properties,", "UnboundLocalError: local variable 'post_properties' referenced before assignment"], "stdout": "", "stdout_lines": []}
NO MORE HOSTS LEFT *******************************************************************************************************************************************
PLAY RECAP ***************************************************************************************************************************************************
localhost : ok=15 changed=1 unreachable=0 failed=1 skipped=7 rescued=0 ignored=0
Code generation should go fine.
Failing with error as above.
A new scaffolding tool ansible-creator
has been released which over time will replace this collection.
PRs, issues, and RFEs are all welcome for ansible-creator.
Although this collection is not officially "deprecated" the focus moving forward will be on ansible-creator
as it offers a better experience and more flexibililty and extensibility for everyone in the future.
In 2.13 argspec validation for an action plugin was introduced: ansible/ansible#77013
We can try that and fall back to utils
action plugin scaffold
I should have a sample in a few days and will link here
Add new role in content builder which will enable scaffolding of network validated content.
module_network_validated_content
To update the doc generator script logic of Sec scaffolding to plugin logic
module_openapi/templates/doc_generator.py
The proposal is to add an Ansible collection that scaffolds a variety of plugin types based on user provided criteria.
This has already been committed to and initial work has been added to this repository. Please have a look at https://github.com/ansible-collections/ansible.plugin_builder/README.md for more details.
Provide tooling to scaffold out the basic python structure for all plugin types including:
This would include the main plugin file for the specific plugin, as well as examples files in the respective utils directory
The user would populate a manifest file that would include things such as plugin name, plugin type, collection specifics, doc string, examples. The playbook and corresponding collection would scaffold the python source files, with placeholders and/or examples code that the user would need to modify.
In the case of an action/module, additional information may be required such as: Can the plugin run on the control node? Will the plugin hit a REST API, Will the plugin use a 3rd party python package for logic and connection.
The additional information can be used to steer the user toward the appropriate plugin type (action/module)
The intent is that ansible.plugin_builder
be a living tools that improves over time based on a community and user feedback loop. Additionally, less well known approaches can be incorporated such as the use of the persistent connection framework, deriving the argspec from the doc string at runtime, common code bases for filter and lookup, doc string for all plugin types, return value validation outbound, etc.
This should avoid the photocopy issue of plugins being built from other plugins, instead provide a starting point that incorporates good practices and techniques.
The work will start small, as a POC, and be iterated on over time.
Reference: Although this is very specific to network resource modules, the workflow and product may serve as a good example https://github.com/ansible-network/cli_rm_builder
When trying to generate a module of type openapi, I get an error from doc_generator.py
content_builder
ansible [core 2.14.0]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /root/ansible-env/lib64/python3.9/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /root/ansible-env/bin/ansible
python version = 3.9.14 (main, Sep 21 2022, 00:00:00) [GCC 11.3.1 20220421 (Red Hat 11.3.1-2)] (/root/ansible-env/bin/python3)
jinja version = 3.1.2
libyaml = True
CONFIG_FILE() = /etc/ansible/ansible.cfg
CentOS Stream 9
Run the content_builder to generate a module of type openapi.
(ansible-env) [root@jc-ansible-centos91 content_builder_test]# cat build.yaml
---
- hosts: localhost
gather_facts: yes
roles:
- ansible.content_builder.run
(ansible-env) [root@jc-ansible-centos91 content_builder_test]# cat MANIFEST.yaml
---
collection:
path: /root/content_builder_test/output
namespace: john_colgrave
name: testos
plugins:
- type: module_openapi
name: testos_plugin
module_version: 1.0.0
rm_swagger_json: /root/content_builder_test/petstore.json
api_object_path: /pets
resource: testos_resource
unique_key: ""
author: "John Colgrave"
I get a fatal error.
See attached file test_output.txt
Getting the error below on
TASK [ansible.content_builder.module_openapi_cloud : Generate modules for "cloud"]
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: NameError: name 'generate_example_cloud' is not defined
fatal: [localhost]: FAILED! => {"msg": "Unexpected failure during module execution: name 'generate_example_cloud' is not defined", "stdout": ""}
ansible.content_builder.module_openapi_cloud
**ansible [core 2.16.5]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/home/segadson/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /home/segadson/vcf_ansible_automation/venv/lib/python3.10/site-packages/ansible
ansible collection location = /home/segadson/.ansible/collections:/usr/share/ansible/collections
executable location = /home/segadson/vcf_ansible_automation/venv/bin/ansible
python version = 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] (/home/segadson/vcf_ansible_automation/venv/bin/python)
jinja version = 3.1.3
libyaml = True**
CONFIG_FILE() = None
Windows Server 2019 WSL Ubuntu 2204
MANIFEST.yaml
---
collection:
path: "example/cloud"
namespace: example
name: cloud
requires_ansible: false
plugins:
- type: "module_openapi"
name: "example_cloud"
content: cloud
api_object_path: /spec/vmware-cloud-foundation.json
resource: config # path to modules.yaml
action: "generate_all" # generate_schemas generate_modules generate_examples
unique_key: ""
rm_swagger_json: ""
module_version: "0.1.0"
author: "Ansible Cloud Team"
(venv) segadson@vcfad:~/vcf_ansible_automation/content-builder-example$ ansible-playbook build.yaml -e manifest_file=MANIFEST.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *****************************************************************************************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.content_builder.run : Validate task input against defined schema] **************************************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.content_builder.run : Load settings from manifest file] ************************************************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Start scaffolding] *********************************************************************************************************************************************************************************************************************************************************************************************
TASK [ansible.content_builder.init : Create the collection directory structure] **************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=plugins/modules)
ok: [localhost] => (item=plugins/module_utils)
ok: [localhost] => (item=plugins/module_utils/common/)
ok: [localhost] => (item=plugins/plugin_utils)
ok: [localhost] => (item=plugins/lookup)
ok: [localhost] => (item=plugins/filter)
ok: [localhost] => (item=plugins/action)
ok: [localhost] => (item=plugins/inventory)
ok: [localhost] => (item=plugins/cache)
ok: [localhost] => (item=plugins/test)
ok: [localhost] => (item=docs)
ok: [localhost] => (item=meta)
ok: [localhost] => (item=tests)
TASK [ansible.content_builder.init : Touch the __init__.py in each directory] ****************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=plugins/modules)
ok: [localhost] => (item=plugins/module_utils)
ok: [localhost] => (item=plugins/module_utils/common/)
ok: [localhost] => (item=plugins/plugin_utils)
ok: [localhost] => (item=plugins/lookup)
ok: [localhost] => (item=plugins/filter)
ok: [localhost] => (item=plugins/action)
ok: [localhost] => (item=plugins/inventory)
ok: [localhost] => (item=plugins/cache)
ok: [localhost] => (item=plugins/test)
ok: [localhost] => (item=docs)
ok: [localhost] => (item=meta)
ok: [localhost] => (item=tests)
TASK [ansible.content_builder.init : Add license file to collection (default is gpl-3.0)] ****************************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.content_builder.init : Add readme to collection] *******************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item={'source': 'README.md.j2', 'destination': 'example/cloud/README.md'})
ok: [localhost] => (item={'source': 'galaxy.yaml.j2', 'destination': 'example/cloud/galaxy.yml'})
TASK [ansible.content_builder.init : Create the collection directory structure] **************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=plugins/modules)
ok: [localhost] => (item=plugins/action)
ok: [localhost] => (item=tests)
TASK [ansible.content_builder.scaffold_plugins : Scaffold the specified plugins] *************************************************************************************************************************************************************************************************************************************
included: /home/segadson/.ansible/collections/ansible_collections/ansible/content_builder/roles/scaffold_plugins/tasks/template.yml for localhost => (item={'type': 'module_openapi', 'name': 'example_cloud', 'content': 'cloud', 'api_object_path': '/spec/vmware-cloud-foundation.json', 'resource': 'config', 'action': 'generate_all', 'unique_key': '', 'rm_swagger_json': '', 'module_version': '0.1.0', 'author': 'Ansible Cloud Team'})
TASK [ansible.content_builder.scaffold_plugins : Debug message] ******************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Scaffolding example_cloud of type module_openapi"
}
TASK [ansible.content_builder.scaffold_plugins : Set facts] **********************************************************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Set path to main plugin file for generic plugins] *******************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Set path to main plugin file for specialized plugins] ***************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Load docstring (if specified)] **************************************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Load examples from example file (if specified)] *********************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Check to see if the plugin file exists] *****************************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Check if documentation string can be found] *************************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Extract documentation from plugin (for existing plugin)] ************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Fail if no docstring could be found] ********************************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Extract examples from plugin (for existing plugin)] *****************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [Start scaffolding specialized plugin] **************************************************************************************************************************************************************************************************************************************************************************
TASK [Generate security content] *************************************************************************************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [Generate cloud content] ****************************************************************************************************************************************************************************************************************************************************************************************
TASK [ansible.content_builder.module_openapi_cloud : Generate schema for "cloud"] ************************************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.content_builder.module_openapi_cloud : Generate modules for "cloud"] ***********************************************************************************************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: NameError: name 'generate_example_cloud' is not defined
fatal: [localhost]: FAILED! => {"msg": "Unexpected failure during module execution: name 'generate_example_cloud' is not defined", "stdout": ""}
PLAY RECAP ***********************************************************************************************************************************************************************************************************************************************************************************************************
localhost : ok=11 changed=0 unreachable=0 failed=1 skipped=11 rescued=0 ignored=0
When running the content_builder for an openapi module, the file doc_generator.py is expected to be in the directory from where the playbook is run but it is not there.
content_builder
ansible [core 2.14.0]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /root/ansible-env/lib64/python3.9/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /root/ansible-env/bin/ansible
python version = 3.9.14 (main, Sep 21 2022, 00:00:00) [GCC 11.3.1 20220421 (Red Hat 11.3.1-2)] (/root/ansible-env/bin/python3)
jinja version = 3.1.2
libyaml = True
CentOS Stream 9
Run the content_builder to generate a module of type openapi.
(ansible-env) [root@jc-ansible-centos91 content_builder_test]# cat build.yaml
---
- hosts: localhost
gather_facts: yes
roles:
- ansible.content_builder.run
(ansible-env) [root@jc-ansible-centos91 content_builder_test]# cat MANIFEST.yaml
---
collection:
path: /root/content_builder_test/output
namespace: john_colgrave
name: testos
plugins:
- type: module_openapi
name: testos_plugin
module_version: 1.0.0
rm_swagger_json: /root/content_builder_test/petstore.json
api_object_path: /pets
resource: testos_resource
unique_key: ""
author: "John Colgrave"
A module to be generated.
I got a fatal error.
(ansible-env) [root@jc-ansible-centos91 content_builder_test]# ansible-playbook build.yaml -e manifest_file=MANIFEST.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *******************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************
ok: [localhost]
TASK [ansible.content_builder.run : Validate task input against defined schema] ****************************************************
ok: [localhost]
TASK [ansible.content_builder.run : Load settings from manifest file] **************************************************************
ok: [localhost]
TASK [Start scaffolding] ***********************************************************************************************************
TASK [ansible.content_builder.init : Create the collection directory structure] ****************************************************
changed: [localhost] => (item=plugins/modules)
changed: [localhost] => (item=plugins/module_utils)
changed: [localhost] => (item=plugins/module_utils/common/)
changed: [localhost] => (item=plugins/plugin_utils)
changed: [localhost] => (item=plugins/lookup)
changed: [localhost] => (item=plugins/filter)
changed: [localhost] => (item=plugins/action)
changed: [localhost] => (item=plugins/inventory)
changed: [localhost] => (item=plugins/cache)
changed: [localhost] => (item=plugins/test)
changed: [localhost] => (item=docs)
changed: [localhost] => (item=meta)
changed: [localhost] => (item=tests)
TASK [ansible.content_builder.init : Touch the __init__.py in each directory] ******************************************************
changed: [localhost] => (item=plugins/modules)
changed: [localhost] => (item=plugins/module_utils)
changed: [localhost] => (item=plugins/module_utils/common/)
changed: [localhost] => (item=plugins/plugin_utils)
changed: [localhost] => (item=plugins/lookup)
changed: [localhost] => (item=plugins/filter)
changed: [localhost] => (item=plugins/action)
changed: [localhost] => (item=plugins/inventory)
changed: [localhost] => (item=plugins/cache)
changed: [localhost] => (item=plugins/test)
changed: [localhost] => (item=docs)
changed: [localhost] => (item=meta)
changed: [localhost] => (item=tests)
TASK [ansible.content_builder.init : Add license file to collection (default is gpl-3.0)] ******************************************
changed: [localhost]
TASK [ansible.content_builder.init : Add readme to collection] *********************************************************************
changed: [localhost] => (item={'source': 'README.md.j2', 'destination': '/root/content_builder_test/output/README.md'})
changed: [localhost] => (item={'source': 'galaxy.yaml.j2', 'destination': '/root/content_builder_test/output/galaxy.yaml'})
TASK [ansible.content_builder.init : Create the collection directory structure] ****************************************************
ok: [localhost] => (item=plugins/modules)
ok: [localhost] => (item=plugins/action)
ok: [localhost] => (item=tests)
TASK [ansible.content_builder.scaffold_plugins : Scaffold the specified plugins] ***************************************************
included: /root/.ansible/collections/ansible_collections/ansible/content_builder/roles/scaffold_plugins/tasks/template.yml for localhost => (item={'type': 'module_openapi', 'name': 'testos_plugin', 'module_version': '1.0.0', 'rm_swagger_json': '/root/content_builder_test/petstore.json', 'api_object_path': '/pets', 'resource': 'testos_resource', 'unique_key': '', 'author': 'John Colgrave'})
TASK [ansible.content_builder.scaffold_plugins : Debug message] ********************************************************************
ok: [localhost] => {
"msg": "Scaffolding testos_plugin of type module_openapi"
}
TASK [ansible.content_builder.scaffold_plugins : Set path to main plugin file for generic plugins] *********************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Set path to main plugin file for specialized plugins] *****************************
ok: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Load docstring (if specified)] ****************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Load examples from example file (if specified)] ***********************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Check to see if the plugin file exists] *******************************************
ok: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Check if documentation string can be found] ***************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Extract documentation from plugin (for existing plugin)] **************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Fail if no docstring could be found] **********************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Extract examples from plugin (for existing plugin)] *******************************
skipping: [localhost]
TASK [Start scaffolding specialized plugin] ****************************************************************************************
TASK [ansible.content_builder.module_openapi : Get the CURRENT WORKING DIR] ********************************************************
ok: [localhost]
TASK [ansible.content_builder.module_openapi : Create temporary build directory] ***************************************************
changed: [localhost]
TASK [ansible.content_builder.module_openapi : EXECUTE the python script] **********************************************************
fatal: [localhost -> 127.0.0.1]: FAILED! => {"changed": false, "cmd": ["python3", "doc_generator.py", "/root/content_builder_test/petstore.json", "/pets", "testos_plugin", "1.0.0", "testos_resource", "john_colgrave", "testos", "", "John Colgrave", "/tmp/ansible.s8a33qnqbuild"], "delta": "0:00:00.040463", "end": "2022-12-06 00:10:21.564125", "msg": "non-zero return code", "rc": 2, "start": "2022-12-06 00:10:21.523662", "stderr": "python3: can't open file '/root/content_builder_test/doc_generator.py': [Errno 2] No such file or directory", "stderr_lines": ["python3: can't open file '/root/content_builder_test/doc_generator.py': [Errno 2] No such file or directory"], "stdout": "", "stdout_lines": []}
NO MORE HOSTS LEFT *****************************************************************************************************************
PLAY RECAP *************************************************************************************************************************
localhost : ok=14 changed=5 unreachable=0 failed=1 skipped=7 rescued=0 ignored=0
Building module fails due to undefined variable.
ansible.content_builder
ansible [core 2.16.3]
config file = None
configured module search path = ['/Users/lefem01/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/Cellar/ansible/9.2.0/libexec/lib/python3.12/site-packages/ansible
ansible collection location = /Users/lefem01/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/local/bin/ansible
python version = 3.12.2 (main, Feb 6 2024, 20:19:44) [Clang 15.0.0 (clang-1500.1.0.2.5)] (/usr/local/Cellar/ansible/9.2.0/libexec/bin/python)
jinja version = 3.0.3
libyaml = True
CONFIG_FILE() = None
MACOS 14.3
The problem occurs when building the ansible playbook module using the MANIFEST.yaml and build.yaml files
ansible-playbook build.yaml -e manifest_file=MANIFEST.yaml
% ansible-playbook build.yaml -e manifest_file=MANIFEST.yaml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *****************************************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************************************
ok: [localhost]
TASK [ansible.content_builder.run : Validate task input against defined schema] **************************************************************
ok: [localhost]
TASK [ansible.content_builder.run : Load settings from manifest file] ************************************************************************
ok: [localhost]
TASK [Start scaffolding] *********************************************************************************************************************
TASK [ansible.content_builder.init : Create the collection directory structure] **************************************************************
skipping: [localhost] => (item=plugins/modules)
skipping: [localhost] => (item=plugins/module_utils)
skipping: [localhost] => (item=plugins/module_utils/common/)
skipping: [localhost] => (item=plugins/plugin_utils)
skipping: [localhost] => (item=plugins/lookup)
skipping: [localhost] => (item=plugins/filter)
skipping: [localhost] => (item=plugins/action)
skipping: [localhost] => (item=plugins/inventory)
skipping: [localhost] => (item=plugins/cache)
skipping: [localhost] => (item=plugins/test)
skipping: [localhost] => (item=docs)
skipping: [localhost] => (item=meta)
skipping: [localhost] => (item=tests)
skipping: [localhost]
TASK [ansible.content_builder.init : Touch the __init__.py in each directory] ****************************************************************
skipping: [localhost] => (item=plugins/modules)
skipping: [localhost] => (item=plugins/module_utils)
skipping: [localhost] => (item=plugins/module_utils/common/)
skipping: [localhost] => (item=plugins/plugin_utils)
skipping: [localhost] => (item=plugins/lookup)
skipping: [localhost] => (item=plugins/filter)
skipping: [localhost] => (item=plugins/action)
skipping: [localhost] => (item=plugins/inventory)
skipping: [localhost] => (item=plugins/cache)
skipping: [localhost] => (item=plugins/test)
skipping: [localhost] => (item=docs)
skipping: [localhost] => (item=meta)
skipping: [localhost] => (item=tests)
skipping: [localhost]
TASK [ansible.content_builder.init : Add license file to collection (default is gpl-3.0)] ****************************************************
ok: [localhost]
TASK [ansible.content_builder.init : Add readme to collection] *******************************************************************************
ok: [localhost] => (item={'source': 'README.md.j2', 'destination': './/README.md'})
ok: [localhost] => (item={'source': 'galaxy.yaml.j2', 'destination': './/galaxy.yml'})
TASK [ansible.content_builder.init : Create the collection directory structure] **************************************************************
skipping: [localhost] => (item=plugins/modules)
skipping: [localhost] => (item=plugins/action)
skipping: [localhost] => (item=tests)
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Scaffold the specified plugins] *************************************************************
included: /Users/lefem01/.ansible/collections/ansible_collections/ansible/content_builder/roles/scaffold_plugins/tasks/template.yml for localhost => (item={'type': 'module_openapi', 'name': 'vra_rest', 'content': 'cloud', 'api_object_path': 'api_specifications/2021-07-15', 'resource': 'config', 'action': 'generate_all', 'unique_key': '', 'rm_swagger_json': '/iaas.json', 'module_version': '0.1.0', 'author': 'Thomas van den Nieuwenhoff'})
TASK [ansible.content_builder.scaffold_plugins : Debug message] ******************************************************************************
ok: [localhost] => {
"msg": "Scaffolding vra_rest of type module_openapi"
}
TASK [ansible.content_builder.scaffold_plugins : Set facts] **********************************************************************************
ok: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Set path to main plugin file for generic plugins] *******************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Set path to main plugin file for specialized plugins] ***************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Load docstring (if specified)] **************************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Load examples from example file (if specified)] *********************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Check to see if the plugin file exists] *****************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Check if documentation string can be found] *************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Extract documentation from plugin (for existing plugin)] ************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Fail if no docstring could be found] ********************************************************
skipping: [localhost]
TASK [ansible.content_builder.scaffold_plugins : Extract examples from plugin (for existing plugin)] *****************************************
skipping: [localhost]
TASK [Start scaffolding specialized plugin] **************************************************************************************************
TASK [Generate security content] *************************************************************************************************************
skipping: [localhost]
TASK [Generate cloud content] ****************************************************************************************************************
TASK [ansible.content_builder.module_openapi_cloud : Generate schema for "vra_rest"] *********************************************************
skipping: [localhost]
TASK [ansible.content_builder.module_openapi_cloud : Generate modules for "vra_rest"] ********************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'requires_ansible'. 'dict object' has no attribute 'requires_ansible'\n\nThe error appears to be in '/Users/user01/.ansible/collections/ansible_collections/ansible/content_builder/roles/module_openapi_cloud/tasks/main.yaml': line 10, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: Generate modules for \"{{ collection['name'] }}\"\n ^ here\nWe could be wrong, but this one looks like it might be an issue with\nmissing quotes. Always quote template expression brackets when they\nstart a value. For instance:\n\n with_items:\n - {{ foo }}\n\nShould be written as:\n\n with_items:\n - \"{{ foo }}\"\n"}
PLAY RECAP ***********************************************************************************************************************************
localhost : ok=8 changed=0 unreachable=0 failed=1 skipped=14 rescued=0 ignored=0
There are a couple of missing dependencies, at least when the content_builder is used with a type of module_openapi:
jsonschema
yaml
ansible.content_builder
ansible [core 2.14.0]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /root/ansible-env/lib64/python3.9/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /root/ansible-env/bin/ansible
python version = 3.9.14 (main, Sep 21 2022, 00:00:00) [GCC 11.3.1 20220421 (Red Hat 11.3.1-2)] (/root/ansible-env/bin/python3)
jinja version = 3.1.2
libyaml = True
CONFIG_FILE() = /etc/ansible/ansible.cfg
CentOS Stream release 9
Install ansible.content_builder per the README and then try to run it to generate a module from a swagger file.
(ansible-env) [root@jc-ansible-centos91 content_builder_test]# cat build.yaml
---
- hosts: localhost
gather_facts: yes
roles:
- ansible.content_builder.run
(ansible-env) [root@jc-ansible-centos91 content_builder_test]# cat MANIFEST.yaml
---
collection:
path: /root/content_builder_test/output
namespace: john_colgrave
name: testos
plugins:
- type: module_openapi
name: testos_plugin
module_version: 1.0.0
rm_swagger_json: /root/content_builder_test/petstore.json
api_object_path: /pets
resource: testos_resource
unique_key: ""
author: "John Colgrave"
A module to be generated.
I got the error below about the jsonschema library. Once I had installed it into my python virtual environment and made sure that ansible-playbook was using the virtual environment, the problem went away.
I later saw a similar issue with oyaml and I had to install that too.
(ansible-env) [root@jc-ansible-centos91 content_builder_test]# ansible-playbook build.yaml -e manifest_file=MANIFEST.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *******************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************
ok: [localhost]
TASK [ansible.content_builder.run : Validate task input against defined schema] ****************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.errors.AnsibleError: Failed to import the required Python library (jsonschema) on jc-ansible-centos91.fyre.ibm.com's Python /usr/bin/python3.9. Please read the module documentation and install it in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Failed to import the required Python library (jsonschema) on jc-ansible-centos91.fyre.ibm.com's Python /usr/bin/python3.9. Please read the module documentation and install it in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter"}
PLAY RECAP *************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Dear maintainers,
This is important for your collections!
In accordance with the Community decision, we have created the news-for-maintainers repository for announcements of changes impacting collection maintainers (see the examples) instead of Issue 45 that will be closed soon.
Watch
button in the upper right corner on the repository's home page.Issues
.Also we would like to remind you about the Bullhorn contributor newsletter which has recently started to be released weekly. To learn what it looks like, see the past releases. Please subscribe and talk to the Community via Bullhorn!
Join us in #ansible-social (for news reporting & chat), #ansible-community (for discussing collection & maintainer topics), and other channels on Matrix/IRC.
Help the Community and the Steering Committee to make right decisions by taking part in discussing and voting on the Community Topics that impact the whole project and the collections in particular. Your opinion there will be much appreciated!
Thank you!
It looks like ansible.content_builder generates lots of unused imports in vmware.vmware_rest.
There's #58 but I don't consider this a real fix. I guess there are good reasons for this sanity test... so I would suggest to
No unused imports.
Hundreds of unused imports.
If argument is bus
, code generator generates default value as 0
, but only for argument specification, not for the documentation itself, which isn't a valid module. The default value must be specified for both.
Most of the modules don't specify default values, default values are specified only as a description of argument, which is taken from API.
Why do we need to set the bus
default value to 0
?
It's failing the CI, and the documentation must be manually edited. Can we remove the generation of the default value = 0 for bus?
I believe the fix is:
from ansible.errors import AnsibleFilterError
When running sanity tests with ansible-core 2.16 in vmware.vmware_rest I see:
Running sanity test "import" on Python 3.12
Run command with data: importer.py
Run command with data: importer.py
ERROR: Found 132 import issue(s) on python 3.12 which need to be resolved:
ERROR: plugins/modules/appliance_access_consolecli.py:256:0: traceback: DeprecationWarning: There is no current event loop
.
.
.
ansible-collections/vmware.vmware_rest#432
It looks like you might have to generate different code. At least if you want the sanity tests to succeed. But the message sounds like the code might even break / fail, although I'm not sure what exactly it means. Didn't have the time to investigate yet.
I think it would be really useful if you create action_groups
in meta/runtime.yml
for all the modules generated.
plugins/action/generate_cloud_modules.py
ansible-collections/vmware.vmware_rest#361
ansible-collections/vmware.vmware_rest#440
ERROR: plugins/modules/vcenter_vm_hardware_adapter_sata.py:0:0: doc-default-does-not-match-spec: Argument 'bus' in argument_spec defines default as (0) but documentation defines default as (None)
ERROR: plugins/modules/vcenter_vm_hardware_adapter_scsi.py:0:0: doc-default-does-not-match-spec: Argument 'bus' in argument_spec defines default as (0) but documentation defines default as (None)
The default here is 0, which looks like you're somewhere testing if default:
. And since 0 is false, the default is not documented.
I don't know your code at all, but this already looks a little bit suspicious:
ansible.content_builder/plugins/action/generate_cloud_modules.py
Lines 322 to 323 in 1e706b4
Might be the wrong place in the code, but something like this would explain the missing documentation. Maybe better test on is not None
or so.
I might be completely wrong, but I definitively see this issue in the sanity tests.
ansible-collections/vmware.vmware_rest#432
ansible-collections/vmware.vmware_rest#434
When attempting to run Ansible Content Builder to build against AWS using the example code, Python crashes
ansible.content_builder.module_openapi_cloud
ansible [core 2.16.5]
config file = /Users/james/Development/ansible-content-builder-demo/ansible.cfg
configured module search path = ['/Users/james/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /Users/james/Development/ansible-content-builder-demo/.venv/lib/python3.12/site-packages/ansible
ansible collection location = /Users/james/Development/ansible-content-builder-demo/collections/ansible_collections
executable location = /Users/james/Development/ansible-content-builder-demo/.venv/bin/ansible
python version = 3.12.2 (main, Feb 6 2024, 20:19:44) [Clang 15.0.0 (clang-1500.1.0.2.5)] (/Users/james/Development/ansible-content-builder-demo/.venv/bin/python3.12)
jinja version = 3.0.3
libyaml = True
COLLECTIONS_PATHS(/Users/james/Development/ansible-content-builder-demo/ansible.cfg) = ['/Users/james/Development/ansible-content-builder-demo/col>
CONFIG_FILE() = /Users/james/Development/ansible-content-builder-demo/ansible.cfg
PAGER(env: PAGER) = less
macOS 14.4.1 (on M1 CPU)
Python 3.12.2 installed via Homebrew
Ansible installed via pip in a virtual environment
$ cat hosts
[builder]
localhost
[builder:vars]
ansible_python_interpreter=./.venv/bin/python3
ansible_connection=local
$ cat build.yaml ─╯
---
- hosts: localhost
gather_facts: yes
vars:
ansible_python_interpreter: ./.venv/bin/python3
pre_tasks:
- debug:
var: ansible_python_interpreter
roles:
- ansible.content_builder.run
$ cat MANIFEST.yaml ─╯
---
collection:
path: ./collections/ansible_collections/amazon/cloud
namespace: amazon
name: cloud
plugins:
- type: module_openapi
name: "amazon_cloud"
content: cloud
api_object_path: api_specifications
# plugin:resource: API resource. When plugin:content is set to cloud this parameter is set to the path of modules.yaml from module_openapi_cloud
resource: ./collections/ansible_collections/ansible/content_builder/roles/module_openapi_cloud/files/
action: generate_all
unique_key: ""
rm_swagger_json: ""
module_version: "1.0.0"
author: "Ansible Cloud Team"
Content Builder should run successfully
TASK [Generate cloud content] *********************************************************************************************************************
task path: /Users/james/Development/ansible-content-builder-demo/collections/ansible_collections/ansible/content_builder/roles/module_openapi/tasks/main.yaml:7
TASK [ansible.content_builder.module_openapi_cloud : Generate schema for "cloud"] *****************************************************************
task path: /Users/james/Development/ansible-content-builder-demo/collections/ansible_collections/ansible/content_builder/roles/module_openapi_cloud/tasks/main.yaml:2
Collecting Schema
AWS::Backup::BackupVault
objc[78638]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.
objc[78638]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
ERROR! A worker was found in a dead state
As someone new to writing plugins
, filters
, or modules
, it would be helpful to see hello world
style examples generated of each scaffolded type, and their source.
build.yaml
Source directories with the items required in the manifest.yaml
, such as the docstring.yaml
, so the expected inputs/outputs can be reviewed.
A readme
with how one even created the Collection to start with, that you must provide the path
, namespace
, and name
to in manifest.yaml
, as you likely have not created a Collection, if you are coming here to use a Collection to get started building these more advanced content types. Essentially, more getting started friendly docs, and examples for modules
, filters
, and plugins
, similar to the newly added docs, and examples for resource_module
scaffolding.
I think this Collection is awesome for creating consistent content with best practices, but the barrier to entry is a little steep for beginners in the custom content area of the Ansible ecosystem.
By the way, the importing of OpenAPI
spec to create a resource_module
is brilliant, well done folks!
I think the fix is:
- name: Create utils file
ansible.builtin.template:
src: "plugin_utils/plugin.py.j2"
dest: "{{ parent_directory }}/plugin_utils/{{ plugin['name'] }}.py"
when: not file_stat.stat.exists
We are happy to announce that the registration for the Ansible Contributor Summit is open!
This is a great opportunity for interested people to meet, discuss related topics, share their stories and opinions, get the latest important updates and just to hang out together.
There will be different announcements & presentations by Community, Core, Cloud, Network, and other teams.
Current contributors will be happy to share their stories and experience with newcomers.
There will be links to interactive self-passed instruqt scenarios shared during the event that help newcomers learn different aspects of development.
Online on Matrix and Youtube. Tuesday, April 12, 2022, 12:00 - 20:00 UTC.
Add the event to your calendar. Use the ical URL (for example, in Google Calendar "Add other calendars" > "Import from URL") instead of importing the .ics file so that any updates to the event will be reflected in your calendar.
Check out the Summit page:
We are looking forward to seeing you!:)
Use Content Builder to scaffold a plugin that sources an inventory from a given REST API
Inventory plugin scaffolding
As an automation developer, I want to be able to obtain a dynamic inventory from an external system (CMDB) that exposes REST API but does not have an inventory plugin made for it (yet). I'd like to be able to pass the OpenAPI/Swagger definition for that API to Content Builder instead of writing an inventory plugin by hand. #1 seems to be a generalisation of that to an arbitrary plugin but despite it being marked as completed the capability to generate inventory plugins doesn't seem to be there yet.
DOCUMENTATION should be imported from the new collection/module
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.