diff --git a/.gitignore b/.gitignore
index 7de70d75..1082a70b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -99,3 +99,4 @@ ansible.cfg
*.retry
update_description_utils/swaggers
+reset.sh
diff --git a/docs/api/container-registry/registry.md b/docs/api/container-registry/registry.md
index 477c0310..9cb36ecb 100644
--- a/docs/api/container-registry/registry.md
+++ b/docs/api/container-registry/registry.md
@@ -8,17 +8,20 @@ This is a module that supports creating, updating or destroying Registries
```yaml
- name: Create Registry
registry:
- name: test_registry
+ name: testregistry
location: de/fra
garbage_collection_schedule:
days:
- Wednesday
time: 04:17:00+00:00
+ features:
+ vulnerability_scanning:
+ enabled: false
register: registry_response
- name: Update Registry
registry:
- registry: test_registry
+ registry: testregistry
name: test_registry_update
garbage_collection_schedule:
days:
@@ -28,7 +31,7 @@ This is a module that supports creating, updating or destroying Registries
- name: Delete Registry
registry:
- registry: test_registry
+ registry: testregistry
wait: true
state: absent
@@ -82,18 +85,22 @@ This is a module that supports creating, updating or destroying Registries
## Parameters that can trigger a resource replacement:
* name
* location
+ * features (changing features.vulnerability_scanning.enabled from true to false will trigger a resource replacement)
# state: **present**
```yaml
- name: Create Registry
registry:
- name: test_registry
+ name: testregistry
location: de/fra
garbage_collection_schedule:
days:
- Wednesday
time: 04:17:00+00:00
+ features:
+ vulnerability_scanning:
+ enabled: false
register: registry_response
```
@@ -118,6 +125,12 @@ This is a module that supports creating, updating or destroying Registries
location str |
True |
The location of your registry |
+
+
+ features dict |
+ False |
+ Optional registry features. Format: 'vulnerability_scanning' key having a dict for value containing the 'enabled' key with a boolean value
+ Note: Vulnerability scanning for images is enabled by default. This is a paid add-on, please make sure you specify if you do not want it enabled |
name str |
@@ -127,7 +140,7 @@ This is a module that supports creating, updating or destroying Registries
allow_replace bool |
False |
- Boolean indincating if the resource should be recreated when the state cannot be reached in another way. This may be used to prevent resources from being deleted from specifying a different value to an immutable property. An error will be thrown instead Default: False |
+ Boolean indicating if the resource should be recreated when the state cannot be reached in another way. This may be used to prevent resources from being deleted from specifying a different value to an immutable property. An error will be thrown instead Default: False |
api_url str |
@@ -174,7 +187,7 @@ This is a module that supports creating, updating or destroying Registries
```yaml
- name: Delete Registry
registry:
- registry: test_registry
+ registry: testregistry
wait: true
state: absent
@@ -241,7 +254,7 @@ This is a module that supports creating, updating or destroying Registries
```yaml
- name: Update Registry
registry:
- registry: test_registry
+ registry: testregistry
name: test_registry_update
garbage_collection_schedule:
days:
@@ -271,6 +284,12 @@ This is a module that supports creating, updating or destroying Registries
location str |
False |
The location of your registry |
+
+
+ features dict |
+ False |
+ Optional registry features. Format: 'vulnerability_scanning' key having a dict for value containing the 'enabled' key with a boolean value
+ Note: Vulnerability scanning for images is enabled by default. This is a paid add-on, please make sure you specify if you do not want it enabled |
name str |
@@ -285,7 +304,7 @@ This is a module that supports creating, updating or destroying Registries
allow_replace bool |
False |
- Boolean indincating if the resource should be recreated when the state cannot be reached in another way. This may be used to prevent resources from being deleted from specifying a different value to an immutable property. An error will be thrown instead Default: False |
+ Boolean indicating if the resource should be recreated when the state cannot be reached in another way. This may be used to prevent resources from being deleted from specifying a different value to an immutable property. An error will be thrown instead Default: False |
api_url str |
diff --git a/docs/api/container-registry/registry_artifact_info.md b/docs/api/container-registry/registry_artifact_info.md
new file mode 100644
index 00000000..3a5e631e
--- /dev/null
+++ b/docs/api/container-registry/registry_artifact_info.md
@@ -0,0 +1,127 @@
+# registry_artifact_info
+
+This is a simple module that supports listing existing Artifacts
+
+## Example Syntax
+
+
+```yaml
+
+ - name: List Artifacts
+ registry_artifact_info:
+ registry: "RegistryName"
+ repository: "repositoryName"
+ register: artifacts_response
+
+
+ - name: Show Artifacts
+ debug:
+ var: artifacts_response.result
+
+```
+
+
+
+
+## Returned object
+```json
+{
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/artifacts",
+ "id": "artifacts",
+ "items": [
+ {
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories/image-test/artifacts/",
+ "id": "",
+ "metadata": {
+ "created_by": null,
+ "created_by_user_id": null,
+ "created_date": null,
+ "last_modified_by": null,
+ "last_modified_by_user_id": null,
+ "last_modified_date": null,
+ "last_pulled_at": null,
+ "last_pushed_at": "",
+ "last_scanned_at": "",
+ "pull_count": 0,
+ "push_count": 1,
+ "resource_urn": null,
+ "vuln_fixable_count": 45,
+ "vuln_max_severity": "critical",
+ "vuln_total_count": 57,
+ "vuln_total_score": 389.39993
+ },
+ "properties": {
+ "digest": "",
+ "media_type": "application/vnd.docker.distribution.manifest.v2+json",
+ "repository_name": "image-test",
+ "tags": [
+ "latest"
+ ]
+ },
+ "type": "artifact"
+ }
+ ],
+ "limit": 100,
+ "links": {
+ "next": null,
+ "prev": null,
+ "var_self": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/artifacts?limit=100&offset=100&orderBy=-pullCount"
+ },
+ "offset": 0,
+ "type": "collection"
+}
+
+```
+
+
+
+
+### Available parameters:
+
+
+
+
+
+ Name |
+ Required |
+ Description |
+
+
+
+
+ filters dict |
+ False |
+ Filter that can be used to list only objects which have a certain set of propeties. Filters should be a dict with a key containing keys and value pair in the following format:'properties.name': 'server_name' |
+
+
+ registry str |
+ True |
+ The ID or name of an existing Registry. |
+
+
+ repository str |
+ False |
+ The name of an existing Repository. |
+
+
+ api_url str |
+ False |
+ The Ionos API base URL. |
+
+
+ username str |
+ False |
+ The Ionos username. Overrides the IONOS_USERNAME environment variable. |
+
+
+ password str |
+ False |
+ The Ionos password. Overrides the IONOS_PASSWORD environment variable. |
+
+
+ token str |
+ False |
+ The Ionos token. Overrides the IONOS_TOKEN environment variable. |
+
+
+
diff --git a/docs/api/container-registry/registry_repository.md b/docs/api/container-registry/registry_repository.md
new file mode 100644
index 00000000..1855bcc3
--- /dev/null
+++ b/docs/api/container-registry/registry_repository.md
@@ -0,0 +1,93 @@
+# registry_repository
+
+This is a module that supports creating, updating or destroying Repositories
+
+## Example Syntax
+
+
+```yaml
+- name: Delete Repository
+ registry_repository:
+ registry: RegistryName
+ repository: testRepository
+ state: absent
+
+```
+
+
+
+
+
+
+# state: **absent**
+```yaml
+ - name: Delete Repository
+ registry_repository:
+ registry: RegistryName
+ repository: testRepository
+ state: absent
+
+```
+### Available parameters for state **absent**:
+
+
+
+
+
+ Name |
+ Required |
+ Description |
+
+
+
+
+ repository str |
+ True |
+ The name of an existing repository. |
+
+
+ registry str |
+ True |
+ The ID or name of an existing Registry. |
+
+
+ api_url str |
+ False |
+ The Ionos API base URL. |
+
+
+ username str |
+ False |
+ The Ionos username. Overrides the IONOS_USERNAME environment variable. |
+
+
+ password str |
+ False |
+ The Ionos password. Overrides the IONOS_PASSWORD environment variable. |
+
+
+ token str |
+ False |
+ The Ionos token. Overrides the IONOS_TOKEN environment variable. |
+
+
+ wait bool |
+ False |
+ Wait for the resource to be created before returning. Default: True Options: [True, False] |
+
+
+ wait_timeout int |
+ False |
+ How long before wait gives up, in seconds. Default: 600 |
+
+
+ state str |
+ False |
+ Indicate desired state of the resource. Default: present Options: ['absent'] |
+
+
+
+
+
+
+
diff --git a/docs/api/container-registry/registry_repository_info.md b/docs/api/container-registry/registry_repository_info.md
new file mode 100644
index 00000000..d48456dc
--- /dev/null
+++ b/docs/api/container-registry/registry_repository_info.md
@@ -0,0 +1,113 @@
+# registry_repository_info
+
+This is a simple module that supports listing existing Repositories
+
+## Example Syntax
+
+
+```yaml
+
+ - name: List Repositories
+ registry_repository_info:
+ registry: "RegistryName"
+ register: repositories_response
+
+
+ - name: Show Repositories
+ debug:
+ var: repositories_response.result
+
+```
+
+
+
+
+## Returned object
+```json
+{
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories",
+ "id": "repositories",
+ "items": [
+ {
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories/image-test",
+ "id": "image-test",
+ "metadata": {
+ "artifact_count": 1,
+ "created_by": null,
+ "created_by_user_id": null,
+ "created_date": null,
+ "last_modified_by": null,
+ "last_modified_by_user_id": null,
+ "last_modified_date": null,
+ "last_pulled_at": null,
+ "last_pushed_at": "",
+ "last_severity": "critical",
+ "pull_count": 0,
+ "push_count": 1,
+ "resource_urn": null
+ },
+ "properties": {
+ "name": "image-test"
+ },
+ "type": "repository"
+ }
+ ],
+ "limit": 100,
+ "links": {
+ "next": null,
+ "prev": null,
+ "var_self": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories?limit=100&offset=100&orderBy=-lastPush"
+ },
+ "offset": 0,
+ "type": "collection"
+}
+
+```
+
+
+
+
+### Available parameters:
+
+
+
+
+
+ Name |
+ Required |
+ Description |
+
+
+
+
+ filters dict |
+ False |
+ Filter that can be used to list only objects which have a certain set of propeties. Filters should be a dict with a key containing keys and value pair in the following format:'properties.name': 'server_name' |
+
+
+ registry str |
+ True |
+ The ID or name of an existing Registry. |
+
+
+ api_url str |
+ False |
+ The Ionos API base URL. |
+
+
+ username str |
+ False |
+ The Ionos username. Overrides the IONOS_USERNAME environment variable. |
+
+
+ password str |
+ False |
+ The Ionos password. Overrides the IONOS_PASSWORD environment variable. |
+
+
+ token str |
+ False |
+ The Ionos token. Overrides the IONOS_TOKEN environment variable. |
+
+
+
diff --git a/docs/api/container-registry/registry_vulnerability_info.md b/docs/api/container-registry/registry_vulnerability_info.md
new file mode 100644
index 00000000..4158d59b
--- /dev/null
+++ b/docs/api/container-registry/registry_vulnerability_info.md
@@ -0,0 +1,137 @@
+# registry_vulnerability_info
+
+This is a simple module that supports listing existing Vulnerabilities
+
+## Example Syntax
+
+
+```yaml
+
+ - name: List Vulnerabilities
+ registry_vulnerability_info:
+ registry: "RegistryName"
+ repository: "repositoryName"
+ arifact: ""
+ register: vulnerabilities_response
+
+
+ - name: Show Vulnerabilities
+ debug:
+ var: vulnerabilities_response.result
+
+```
+
+
+
+
+## Returned object
+```json
+{
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories/image-test/artifacts/",
+ "id": "vulnerabilities",
+ "items": [
+ {
+ "href": "/vulnerabilities/",
+ "id": "",
+ "metadata": {
+ "publishedAt": "",
+ "updatedAt": ""
+ },
+ "properties": {
+ "affects": [
+ {
+ "name": "libc-bin",
+ "type": "deb",
+ "version": "2.31-0ubuntu9.2"
+ },
+ {
+ "name": "libc6",
+ "type": "deb",
+ "version": "2.31-0ubuntu9.2"
+ }
+ ],
+ "dataSource": {
+ "id": null,
+ "url": null
+ },
+ "description": "",
+ "fixable": true,
+ "recommendations": "",
+ "references": [
+ ""
+ ],
+ "score": 2.5,
+ "severity": "medium"
+ },
+ "type": "vulnerability"
+ }
+ ],
+ "limit": 100,
+ "links": {
+ "next": null,
+ "prev": null,
+ "varSelf": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories/image-test/artifacts/?limit=100&offset=100&orderBy=-score"
+ },
+ "offset": 0,
+ "type": "collection"
+}
+
+```
+
+
+
+
+### Available parameters:
+
+
+
+
+
+ Name |
+ Required |
+ Description |
+
+
+
+
+ filters dict |
+ False |
+ Filter that can be used to list only objects which have a certain set of propeties. Filters should be a dict with a key containing keys and value pair in the following format:'properties.name': 'server_name' |
+
+
+ registry str |
+ True |
+ The ID or name of an existing Registry. |
+
+
+ repository str |
+ True |
+ The name of an existing Repository. |
+
+
+ artifact str |
+ True |
+ The digest of an existing Artifact. |
+
+
+ api_url str |
+ False |
+ The Ionos API base URL. |
+
+
+ username str |
+ False |
+ The Ionos username. Overrides the IONOS_USERNAME environment variable. |
+
+
+ password str |
+ False |
+ The Ionos password. Overrides the IONOS_PASSWORD environment variable. |
+
+
+ token str |
+ False |
+ The Ionos token. Overrides the IONOS_TOKEN environment variable. |
+
+
+
diff --git a/docs/returned_object_examples/registry_artifact_info.json b/docs/returned_object_examples/registry_artifact_info.json
new file mode 100644
index 00000000..bda6962f
--- /dev/null
+++ b/docs/returned_object_examples/registry_artifact_info.json
@@ -0,0 +1,45 @@
+{
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/artifacts",
+ "id": "artifacts",
+ "items": [
+ {
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories/image-test/artifacts/",
+ "id": "",
+ "metadata": {
+ "created_by": null,
+ "created_by_user_id": null,
+ "created_date": null,
+ "last_modified_by": null,
+ "last_modified_by_user_id": null,
+ "last_modified_date": null,
+ "last_pulled_at": null,
+ "last_pushed_at": "",
+ "last_scanned_at": "",
+ "pull_count": 0,
+ "push_count": 1,
+ "resource_urn": null,
+ "vuln_fixable_count": 45,
+ "vuln_max_severity": "critical",
+ "vuln_total_count": 57,
+ "vuln_total_score": 389.39993
+ },
+ "properties": {
+ "digest": "",
+ "media_type": "application/vnd.docker.distribution.manifest.v2+json",
+ "repository_name": "image-test",
+ "tags": [
+ "latest"
+ ]
+ },
+ "type": "artifact"
+ }
+ ],
+ "limit": 100,
+ "links": {
+ "next": null,
+ "prev": null,
+ "var_self": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/artifacts?limit=100&offset=100&orderBy=-pullCount"
+ },
+ "offset": 0,
+ "type": "collection"
+}
diff --git a/docs/returned_object_examples/registry_repository_info.json b/docs/returned_object_examples/registry_repository_info.json
new file mode 100644
index 00000000..de849e1d
--- /dev/null
+++ b/docs/returned_object_examples/registry_repository_info.json
@@ -0,0 +1,37 @@
+{
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories",
+ "id": "repositories",
+ "items": [
+ {
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories/image-test",
+ "id": "image-test",
+ "metadata": {
+ "artifact_count": 1,
+ "created_by": null,
+ "created_by_user_id": null,
+ "created_date": null,
+ "last_modified_by": null,
+ "last_modified_by_user_id": null,
+ "last_modified_date": null,
+ "last_pulled_at": null,
+ "last_pushed_at": "",
+ "last_severity": "critical",
+ "pull_count": 0,
+ "push_count": 1,
+ "resource_urn": null
+ },
+ "properties": {
+ "name": "image-test"
+ },
+ "type": "repository"
+ }
+ ],
+ "limit": 100,
+ "links": {
+ "next": null,
+ "prev": null,
+ "var_self": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories?limit=100&offset=100&orderBy=-lastPush"
+ },
+ "offset": 0,
+ "type": "collection"
+}
diff --git a/docs/returned_object_examples/registry_vulnerability_info.json b/docs/returned_object_examples/registry_vulnerability_info.json
new file mode 100644
index 00000000..da26188f
--- /dev/null
+++ b/docs/returned_object_examples/registry_vulnerability_info.json
@@ -0,0 +1,49 @@
+{
+ "href": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories/image-test/artifacts/",
+ "id": "vulnerabilities",
+ "items": [
+ {
+ "href": "/vulnerabilities/",
+ "id": "",
+ "metadata": {
+ "publishedAt": "",
+ "updatedAt": ""
+ },
+ "properties": {
+ "affects": [
+ {
+ "name": "libc-bin",
+ "type": "deb",
+ "version": "2.31-0ubuntu9.2"
+ },
+ {
+ "name": "libc6",
+ "type": "deb",
+ "version": "2.31-0ubuntu9.2"
+ }
+ ],
+ "dataSource": {
+ "id": null,
+ "url": null
+ },
+ "description": "",
+ "fixable": true,
+ "recommendations": "",
+ "references": [
+ ""
+ ],
+ "score": 2.5,
+ "severity": "medium"
+ },
+ "type": "vulnerability"
+ }
+ ],
+ "limit": 100,
+ "links": {
+ "next": null,
+ "prev": null,
+ "varSelf": "/registries/0d6fd999-9bf9-462c-a148-951198ebca8f/repositories/image-test/artifacts/?limit=100&offset=100&orderBy=-score"
+ },
+ "offset": 0,
+ "type": "collection"
+}
diff --git a/docs/summary.md b/docs/summary.md
index 5b34bef0..ca0ca394 100644
--- a/docs/summary.md
+++ b/docs/summary.md
@@ -58,9 +58,13 @@
* Modules
* [Registry](api/container-registry/registry.md)
* [Registry Token](api/container-registry/registry_token.md)
+ * [Repository](api/container-registry/registry_repository.md)
* Info Modules
* [Registries](api/container-registry/registry_info.md)
* [Registry Tokens](api/container-registry/registry_token_info.md)
+ * [Artifacts](api/container-registry/registry_artifact_info.md)
+ * [Repositories](api/container-registry/registry_repository_info.md)
+ * [Vulnerabilities](api/container-registry/registry_vulnerability_info.md)
* DBaaS Postgres
* Modules
* [Postgres Cluster](api/dbaas-postgres/postgres_cluster.md)
diff --git a/docs_generator.py b/docs_generator.py
index e9933614..1165289e 100644
--- a/docs_generator.py
+++ b/docs_generator.py
@@ -129,6 +129,10 @@ def available_in_state(option):
'registry_info',
'registry_token',
'registry_token_info',
+ 'registry_artifact_info',
+ 'registry_repository_info',
+ 'registry_repository',
+ 'registry_vulnerability_info',
'postgres_cluster',
'postgres_backup_info',
'postgres_cluster_info',
diff --git a/plugins/modules/registry.py b/plugins/modules/registry.py
index 4f3bcc0b..5cd94b33 100644
--- a/plugins/modules/registry.py
+++ b/plugins/modules/registry.py
@@ -43,6 +43,11 @@
'required': ['present'],
'type': 'str',
},
+ 'features': {
+ 'description': ["Optional registry features. Format: 'vulnerability_scanning' key having a dict for value containing the 'enabled' key with a boolean value\n Note: Vulnerability scanning for images is enabled by default. This is a paid add-on, please make sure you specify if you do not want it enabled"],
+ 'available': ['present', 'update'],
+ 'type': 'dict',
+ },
'name': {
'description': ['The name of your registry.'],
'available': ['present', 'update'],
@@ -57,7 +62,7 @@
},
'allow_replace': {
'description': [
- 'Boolean indincating if the resource should be recreated when the state cannot be reached in '
+ 'Boolean indicating if the resource should be recreated when the state cannot be reached in '
'another way. This may be used to prevent resources from being deleted from specifying a different '
'value to an immutable property. An error will be thrown instead',
],
@@ -122,6 +127,7 @@
IMMUTABLE_OPTIONS = [
{ "name": "name", "note": "" },
{ "name": "location", "note": "" },
+ { "name": "features", "note": "changing features.vulnerability_scanning.enabled from true to false will trigger a resource replacement" },
]
def transform_for_documentation(val):
@@ -153,17 +159,20 @@ def transform_for_documentation(val):
EXAMPLE_PER_STATE = {
'present': '''- name: Create Registry
registry:
- name: test_registry
+ name: testregistry
location: de/fra
garbage_collection_schedule:
days:
- Wednesday
time: 04:17:00+00:00
+ features:
+ vulnerability_scanning:
+ enabled: false
register: registry_response
''',
'update': '''- name: Update Registry
registry:
- registry: test_registry
+ registry: testregistry
name: test_registry_update
garbage_collection_schedule:
days:
@@ -173,7 +182,7 @@ def transform_for_documentation(val):
''',
'absent': '''- name: Delete Registry
registry:
- registry: test_registry
+ registry: testregistry
wait: true
state: absent
''',
@@ -222,16 +231,21 @@ def get_resource_id(module, resource_list, identity, identity_paths=None):
def _should_replace_object(module, existing_object):
+ features = module.params.get('features')
return (
module.params.get('location') is not None
and existing_object.properties.location != module.params.get('location')
or module.params.get('name') is not None
and existing_object.properties.name != module.params.get('name')
+ or features is not None
+ and existing_object.properties.features.vulnerability_scanning.enabled == True
+ and features.get('vulnerability_scanning', {}).get('enabled') == False
)
def _should_update_object(module, existing_object):
gc_schedule = module.params.get('garbage_collection_schedule')
+ features = module.params.get('features')
return (
gc_schedule is not None
and (
@@ -240,6 +254,9 @@ def _should_update_object(module, existing_object):
or gc_schedule.get('time') is not None
and existing_object.properties.garbage_collection_schedule.time != gc_schedule.get('time')
)
+ or features.get('enabled') is not None
+ and existing_object.properties.features.vulnerability_scanning.enabled == False
+ and features.get('vulnerability_scanning', {}).get('enabled') == True
)
@@ -256,12 +273,20 @@ def _get_object_identifier(module):
def _create_object(module, client, existing_object=None):
+ wait = module.params.get('wait')
+ wait_timeout = int(module.params.get('wait_timeout'))
gc_schedule = module.params.get('garbage_collection_schedule')
+ features = module.params.get('features')
+ vulnerability_scanning_feature = None
if gc_schedule:
gc_schedule = ionoscloud_container_registry.WeeklySchedule(
days=gc_schedule.get('days'),
time=gc_schedule.get('time'),
)
+ if features:
+ vulnerability_scanning_feature = ionoscloud_container_registry.FeatureVulnerabilityScanning(
+ enabled=features.get('vulnerability_scanning').get('enabled'),
+ )
name = module.params.get('name')
location = module.params.get('location')
if existing_object is not None:
@@ -275,29 +300,52 @@ def _create_object(module, client, existing_object=None):
name=name,
location=location,
garbage_collection_schedule=gc_schedule,
+ features=ionoscloud_container_registry.RegistryFeatures(
+ vulnerability_scanning=vulnerability_scanning_feature,
+ ),
)
registry = ionoscloud_container_registry.PostRegistryInput(properties=registry_properties)
try:
registry = registries_api.registries_post(registry)
+
+ if wait:
+ client.wait_for(
+ fn_request=lambda: registries_api.registries_find_by_id(registry.id).metadata.state,
+ fn_check=lambda r: r == 'Running',
+ scaleup=10000,
+ timeout=wait_timeout,
+ )
+ registry = registries_api.registries_find_by_id(registry.id)
except ionoscloud_container_registry.ApiException as e:
module.fail_json(msg="failed to create the new Registry: %s" % to_native(e))
return registry
def _update_object(module, client, existing_object):
+ wait = module.params.get('wait')
+ wait_timeout = int(module.params.get('wait_timeout'))
gc_schedule = module.params.get('garbage_collection_schedule')
+ features = module.params.get('features')
+ vulnerability_scanning_feature = None
if gc_schedule:
gc_schedule = ionoscloud_container_registry.WeeklySchedule(
days=gc_schedule.get('days'),
time=gc_schedule.get('time'),
)
+ if features:
+ vulnerability_scanning_feature = ionoscloud_container_registry.FeatureVulnerabilityScanning(
+ enabled=features.get('vulnerability_scanning').get('enabled'),
+ )
registries_api = ionoscloud_container_registry.RegistriesApi(client)
registry_properties = ionoscloud_container_registry.PatchRegistryInput(
garbage_collection_schedule=gc_schedule,
+ features=ionoscloud_container_registry.RegistryFeatures(
+ vulnerability_scanning=vulnerability_scanning_feature,
+ ),
)
try:
@@ -306,6 +354,15 @@ def _update_object(module, client, existing_object):
patch_registry_input=registry_properties,
)
+ if wait:
+ client.wait_for(
+ fn_request=lambda: registries_api.registries_find_by_id(registry.id).metadata.state,
+ fn_check=lambda r: r == 'Running',
+ scaleup=10000,
+ timeout=wait_timeout,
+ )
+ registry = registries_api.registries_find_by_id(existing_object.id)
+
return registry
except ionoscloud_container_registry.ApiException as e:
module.fail_json(msg="failed to update the Registry: %s" % to_native(e))
diff --git a/plugins/modules/registry_artifact_info.py b/plugins/modules/registry_artifact_info.py
new file mode 100644
index 00000000..7a074a47
--- /dev/null
+++ b/plugins/modules/registry_artifact_info.py
@@ -0,0 +1,315 @@
+import copy
+import yaml
+
+from ansible import __version__
+from ansible.module_utils.basic import AnsibleModule, env_fallback
+from ansible.module_utils._text import to_native
+
+HAS_SDK = True
+try:
+ import ionoscloud_container_registry
+except ImportError:
+ HAS_SDK = False
+
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community',
+}
+CONTAINER_REGISTRY_USER_AGENT = 'ansible-module/%s_ionos-cloud-sdk-python-container-registry/%s' % (
+__version__, ionoscloud_container_registry.__version__)
+DOC_DIRECTORY = 'container-registry'
+STATES = ['info']
+OBJECT_NAME = 'Artifacts'
+RETURNED_KEY = 'artifacts'
+
+OPTIONS = {
+ 'filters': {
+ 'description': [
+ 'Filter that can be used to list only objects which have a certain set of propeties. Filters '
+ 'should be a dict with a key containing keys and value pair in the following format:'
+ "'properties.name': 'server_name'"
+ ],
+ 'available': STATES,
+ 'type': 'dict',
+ },
+ 'registry': {
+ 'description': ['The ID or name of an existing Registry.'],
+ 'available': STATES,
+ 'required': STATES,
+ 'type': 'str',
+ },
+ 'repository': {
+ 'description': ['The name of an existing Repository.'],
+ 'available': STATES,
+ 'type': 'str',
+ },
+ 'api_url': {
+ 'description': ['The Ionos API base URL.'],
+ 'version_added': '2.4',
+ 'env_fallback': 'IONOS_API_URL',
+ 'available': STATES,
+ 'type': 'str',
+ },
+ 'username': {
+ # Required if no token, checked manually
+ 'description': ['The Ionos username. Overrides the IONOS_USERNAME environment variable.'],
+ 'aliases': ['subscription_user'],
+ 'env_fallback': 'IONOS_USERNAME',
+ 'available': STATES,
+ 'type': 'str',
+ },
+ 'password': {
+ # Required if no token, checked manually
+ 'description': ['The Ionos password. Overrides the IONOS_PASSWORD environment variable.'],
+ 'aliases': ['subscription_password'],
+ 'available': STATES,
+ 'no_log': True,
+ 'env_fallback': 'IONOS_PASSWORD',
+ 'type': 'str',
+ },
+ 'token': {
+ # If provided, then username and password no longer required
+ 'description': ['The Ionos token. Overrides the IONOS_TOKEN environment variable.'],
+ 'available': STATES,
+ 'no_log': True,
+ 'env_fallback': 'IONOS_TOKEN',
+ 'type': 'str',
+ },
+}
+
+
+def transform_for_documentation(val):
+ val['required'] = len(val.get('required', [])) == len(STATES)
+ del val['available']
+ del val['type']
+ return val
+
+
+DOCUMENTATION = '''
+---
+module: registry_artifact_info
+short_description: List Artifacts
+description:
+ - This is a simple module that supports listing existing Artifacts
+version_added: "2.0"
+options:
+''' + ' ' + yaml.dump(
+ yaml.safe_load(str({k: transform_for_documentation(v) for k, v in copy.deepcopy(OPTIONS).items()})),
+ default_flow_style=False).replace('\n', '\n ') + '''
+requirements:
+ - "python >= 2.6"
+ - "ionoscloud-container-registry >= 1.0.0"
+author:
+ - "IONOS Cloud SDK Team "
+'''
+
+EXAMPLES = '''
+ - name: List Artifacts
+ registry_artifact_info:
+ registry: "RegistryName"
+ repository: "repositoryName"
+ register: artifacts_response
+
+
+ - name: Show Artifacts
+ debug:
+ var: artifacts_response.result
+'''
+
+def _get_matched_resources(resource_list, identity, identity_paths=None):
+ """
+ Fetch and return a resource based on an identity supplied for it, if none or more than one matches
+ are found an error is printed and None is returned.
+ """
+
+ if identity_paths is None:
+ identity_paths = [['id'], ['properties', 'name']]
+
+ def check_identity_method(resource):
+ resource_identity = []
+
+ for identity_path in identity_paths:
+ current = resource
+ for el in identity_path:
+ current = getattr(current, el)
+ resource_identity.append(current)
+
+ return identity in resource_identity
+
+ return list(filter(check_identity_method, resource_list.items))
+
+
+def get_resource(module, resource_list, identity, identity_paths=None):
+ matched_resources = _get_matched_resources(resource_list, identity, identity_paths)
+
+ if len(matched_resources) == 1:
+ return matched_resources[0]
+ elif len(matched_resources) > 1:
+ module.fail_json(msg="found more resources of type {} for '{}'".format(resource_list.id, identity))
+ else:
+ return None
+
+
+def get_resource_id(module, resource_list, identity, identity_paths=None):
+ resource = get_resource(module, resource_list, identity, identity_paths)
+ return resource.id if resource is not None else None
+
+
+def get_method_from_filter(filter):
+ '''
+ Returns the method which check a filter for one object. Such a method would work in the following way:
+ for filter = ('properties.name', 'server_name') the resulting method would be
+ def method(item):
+ return item.properties.name == 'server_name'
+ Parameters:
+ filter (touple): Key, value pair representing the filter.
+ Returns:
+ the wanted method
+ '''
+ key, value = filter
+ def method(item):
+ current = item
+ for key_part in key.split('.'):
+ current = getattr(current, key_part)
+ return current == value
+ return method
+
+
+def get_method_to_apply_filters_to_item(filter_list):
+ '''
+ Returns the method which applies a list of filtering methods obtained using get_method_from_filter to
+ one object and returns true if all the filters return true
+ Parameters:
+ filter_list (list): List of filtering methods
+ Returns:
+ the wanted method
+ '''
+ def f(item):
+ return all([f(item) for f in filter_list])
+ return f
+
+
+def apply_filters(module, item_list):
+ '''
+ Creates a list of filtering methods from the filters module parameter, filters item_list to keep only the
+ items for which every filter matches using get_method_to_apply_filters_to_item to make that check and returns
+ those items
+ Parameters:
+ module: The current Ansible module
+ item_list (list): List of items to be filtered
+ Returns:
+ List of items which match the filters
+ '''
+ filters = module.params.get('filters')
+ if not filters:
+ return item_list
+ filter_methods = list(map(get_method_from_filter, filters.items()))
+
+ return filter(get_method_to_apply_filters_to_item(filter_methods), item_list)
+
+
+
+def get_module_arguments():
+ arguments = {}
+
+ for option_name, option in OPTIONS.items():
+ arguments[option_name] = {
+ 'type': option['type'],
+ }
+ for key in ['choices', 'default', 'aliases', 'no_log', 'elements']:
+ if option.get(key) is not None:
+ arguments[option_name][key] = option.get(key)
+
+ if option.get('env_fallback'):
+ arguments[option_name]['fallback'] = (env_fallback, [option['env_fallback']])
+
+ if len(option.get('required', [])) == len(STATES):
+ arguments[option_name]['required'] = True
+
+ return arguments
+
+
+def get_sdk_config(module, sdk):
+ username = module.params.get('username')
+ password = module.params.get('password')
+ token = module.params.get('token')
+ api_url = module.params.get('api_url')
+
+ if token is not None:
+ # use the token instead of username & password
+ conf = {
+ 'token': token
+ }
+ else:
+ # use the username & password
+ conf = {
+ 'username': username,
+ 'password': password,
+ }
+
+ if api_url is not None:
+ conf['host'] = api_url
+ conf['server_index'] = None
+
+ return sdk.Configuration(**conf)
+
+
+def check_required_arguments(module, object_name):
+ # manually checking if token or username & password provided
+ if (
+ not module.params.get("token")
+ and not (module.params.get("username") and module.params.get("password"))
+ ):
+ module.fail_json(
+ msg='Token or username & password are required for {object_name}'.format(
+ object_name=object_name,
+ ),
+ )
+ for option_name, option in OPTIONS.items():
+ if 'info' in option.get('required', []) and not module.params.get(option_name):
+ module.fail_json(
+ msg='{option_name} parameter is required for retrieving {object_name}'.format(
+ option_name=option_name,
+ object_name=object_name,
+ ),
+ )
+
+
+def main():
+ module = AnsibleModule(argument_spec=get_module_arguments(), supports_check_mode=True)
+
+ if not HAS_SDK:
+ module.fail_json(
+ msg='ionoscloud_container_registry is required for this module, run `pip install ionoscloud_container_registry`')
+
+
+ client = ionoscloud_container_registry.ApiClient(get_sdk_config(module, ionoscloud_container_registry))
+ client.user_agent = CONTAINER_REGISTRY_USER_AGENT
+
+ check_required_arguments(module, OBJECT_NAME)
+
+ try:
+ registry_id = get_resource_id(
+ module,
+ ionoscloud_container_registry.RegistriesApi(client).registries_get(),
+ module.params.get('registry'),
+ )
+ if module.params.get('repository'):
+ artifacts = ionoscloud_container_registry.ArtifactsApi(client).registries_repositories_artifacts_get(
+ registry_id,
+ module.params.get('repository'),
+ )
+ else:
+ artifacts = ionoscloud_container_registry.ArtifactsApi(client).registries_artifacts_get(
+ registry_id,
+ )
+ results = list(map(lambda x: x.to_dict(), apply_filters(module, artifacts.items)))
+ module.exit_json(**{RETURNED_KEY:results})
+ except Exception as e:
+ module.fail_json(
+ msg='failed to retrieve {object_name}: {error}'.format(object_name=OBJECT_NAME, error=to_native(e)))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/plugins/modules/registry_repository.py b/plugins/modules/registry_repository.py
new file mode 100644
index 00000000..d01cf4f5
--- /dev/null
+++ b/plugins/modules/registry_repository.py
@@ -0,0 +1,355 @@
+import copy
+from distutils.command.config import config
+from operator import mod
+import yaml
+
+from ansible import __version__
+from ansible.module_utils.basic import AnsibleModule, env_fallback
+from ansible.module_utils._text import to_native
+import re
+
+HAS_SDK = True
+try:
+ import ionoscloud_container_registry
+except ImportError:
+ HAS_SDK = False
+
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community',
+}
+
+CONTAINER_REGISTRY_USER_AGENT = 'ansible-module/%s_ionos-cloud-sdk-python-container-registry/%s'% (
+ __version__, ionoscloud_container_registry.__version__,
+)
+DOC_DIRECTORY = 'container-registry'
+STATES = ['absent']
+OBJECT_NAME = 'Repository'
+RETURNED_KEY = 'repository'
+
+
+OPTIONS = {
+ 'repository': {
+ 'description': ['The name of an existing repository.'],
+ 'available': STATES,
+ 'required': STATES,
+ 'type': 'str',
+ },
+ 'registry': {
+ 'description': ['The ID or name of an existing Registry.'],
+ 'available': STATES,
+ 'required': STATES,
+ 'type': 'str',
+ },
+ 'allow_replace': {
+ 'description': [
+ 'Boolean indincating if the resource should be recreated when the state cannot be reached in '
+ 'another way. This may be used to prevent resources from being deleted from specifying a different '
+ 'value to an immutable property. An error will be thrown instead',
+ ],
+ 'available': ['present', 'update'],
+ 'default': False,
+ 'type': 'bool',
+ },
+ 'api_url': {
+ 'description': ['The Ionos API base URL.'],
+ 'version_added': '2.4',
+ 'env_fallback': 'IONOS_API_URL',
+ 'available': STATES,
+ 'type': 'str',
+ },
+ 'username': {
+ # Required if no token, checked manually
+ 'description': ['The Ionos username. Overrides the IONOS_USERNAME environment variable.'],
+ 'aliases': ['subscription_user'],
+ 'env_fallback': 'IONOS_USERNAME',
+ 'available': STATES,
+ 'type': 'str',
+ },
+ 'password': {
+ # Required if no token, checked manually
+ 'description': ['The Ionos password. Overrides the IONOS_PASSWORD environment variable.'],
+ 'aliases': ['subscription_password'],
+ 'available': STATES,
+ 'no_log': True,
+ 'env_fallback': 'IONOS_PASSWORD',
+ 'type': 'str',
+ },
+ 'token': {
+ # If provided, then username and password no longer required
+ 'description': ['The Ionos token. Overrides the IONOS_TOKEN environment variable.'],
+ 'available': STATES,
+ 'no_log': True,
+ 'env_fallback': 'IONOS_TOKEN',
+ 'type': 'str',
+ },
+ 'wait': {
+ 'description': ['Wait for the resource to be created before returning.'],
+ 'default': True,
+ 'available': STATES,
+ 'choices': [True, False],
+ 'type': 'bool',
+ },
+ 'wait_timeout': {
+ 'description': ['How long before wait gives up, in seconds.'],
+ 'default': 600,
+ 'available': STATES,
+ 'type': 'int',
+ },
+ 'state': {
+ 'description': ['Indicate desired state of the resource.'],
+ 'default': 'present',
+ 'choices': STATES,
+ 'available': STATES,
+ 'type': 'str',
+ },
+}
+
+
+def transform_for_documentation(val):
+ val['required'] = len(val.get('required', [])) == len(STATES)
+ del val['available']
+ del val['type']
+ return val
+
+
+DOCUMENTATION = '''
+---
+module: registry_repository
+short_description: Allows operations with Repositories.
+description:
+ - This is a module that supports creating, updating or destroying Repositories
+version_added: "2.0"
+options:
+''' + ' ' + yaml.dump(
+ yaml.safe_load(str({k: transform_for_documentation(v) for k, v in copy.deepcopy(OPTIONS).items()})),
+ default_flow_style=False).replace('\n', '\n ') + '''
+requirements:
+ - "python >= 2.6"
+ - "ionoscloud >= 6.0.2"
+ - "ionoscloud-container-registry >= 1.0.1"
+author:
+ - "IONOS Cloud SDK Team "
+'''
+
+EXAMPLE_PER_STATE = {
+ 'absent': '''- name: Delete Repository
+ registry_repository:
+ registry: RegistryName
+ repository: testRepository
+ state: absent
+ ''',
+}
+
+EXAMPLES = '\n'.join(EXAMPLE_PER_STATE.values())
+
+
+def _get_matched_resources(resource_list, identity, identity_paths=None):
+ """
+ Fetch and return a resource based on an identity supplied for it, if none or more than one matches
+ are found an error is printed and None is returned.
+ """
+
+ if identity_paths is None:
+ identity_paths = [['id'], ['properties', 'name']]
+
+ def check_identity_method(resource):
+ resource_identity = []
+
+ for identity_path in identity_paths:
+ current = resource
+ for el in identity_path:
+ current = getattr(current, el)
+ resource_identity.append(current)
+
+ return identity in resource_identity
+
+ return list(filter(check_identity_method, resource_list.items))
+
+
+def get_resource(module, resource_list, identity, identity_paths=None):
+ matched_resources = _get_matched_resources(resource_list, identity, identity_paths)
+
+ if len(matched_resources) == 1:
+ return matched_resources[0]
+ elif len(matched_resources) > 1:
+ module.fail_json(msg="found more resources of type {} for '{}'".format(resource_list.id, identity))
+ else:
+ return None
+
+
+def get_resource_id(module, resource_list, identity, identity_paths=None):
+ resource = get_resource(module, resource_list, identity, identity_paths)
+ return resource.id if resource is not None else None
+
+
+def _should_replace_object(module, existing_object):
+ return False
+
+
+def _should_update_object(module, existing_object):
+ return False
+
+
+def _get_object_list(module, client):
+ registry_id = get_resource_id(
+ module,
+ ionoscloud_container_registry.RegistriesApi(client).registries_get(),
+ module.params.get('registry'),
+ )
+ return ionoscloud_container_registry.RepositoriesApi(client).registries_repositories_get(registry_id)
+
+
+def _get_object_name(module):
+ return module.params.get('name')
+
+
+def _get_object_identifier(module):
+ return module.params.get('repository')
+
+
+def _create_object(module, client, existing_object=None):
+ pass
+
+
+def _update_object(module, client, existing_object):
+ pass
+
+
+def _remove_object(module, client, existing_object):
+ registry_id = get_resource_id(
+ module,
+ ionoscloud_container_registry.RegistriesApi(client).registries_get(),
+ module.params.get('registry'),
+ )
+ repositories_api = ionoscloud_container_registry.RepositoriesApi(client)
+
+ try:
+ repositories_api.registries_repositories_delete(registry_id, existing_object.id)
+ except ionoscloud_container_registry.ApiException as e:
+ module.fail_json(msg="failed to remove the Repository: %s" % to_native(e))
+
+
+def update_replace_object(module, client, existing_object):
+ pass
+
+
+def create_object(module, client):
+ pass
+
+
+def update_object(module, client):
+ pass
+
+
+def remove_object(module, client):
+ existing_object = get_resource(module, _get_object_list(module, client), _get_object_identifier(module))
+
+ if existing_object is None:
+ module.exit_json(changed=False)
+ return
+
+ _remove_object(module, client, existing_object)
+
+ return {
+ 'action': 'delete',
+ 'changed': True,
+ 'id': existing_object.id,
+ }
+
+
+def get_module_arguments():
+ arguments = {}
+
+ for option_name, option in OPTIONS.items():
+ arguments[option_name] = {
+ 'type': option['type'],
+ }
+ for key in ['choices', 'default', 'aliases', 'no_log', 'elements']:
+ if option.get(key) is not None:
+ arguments[option_name][key] = option.get(key)
+
+ if option.get('env_fallback'):
+ arguments[option_name]['fallback'] = (env_fallback, [option['env_fallback']])
+
+ if len(option.get('required', [])) == len(STATES):
+ arguments[option_name]['required'] = True
+
+ return arguments
+
+
+def get_sdk_config(module, sdk):
+ username = module.params.get('username')
+ password = module.params.get('password')
+ token = module.params.get('token')
+ api_url = module.params.get('api_url')
+
+ if token is not None:
+ # use the token instead of username & password
+ conf = {
+ 'token': token
+ }
+ else:
+ # use the username & password
+ conf = {
+ 'username': username,
+ 'password': password,
+ }
+
+ if api_url is not None:
+ conf['host'] = api_url
+ conf['server_index'] = None
+
+ return sdk.Configuration(**conf)
+
+
+def check_required_arguments(module, state, object_name):
+ # manually checking if token or username & password provided
+ if (
+ not module.params.get("token")
+ and not (module.params.get("username") and module.params.get("password"))
+ ):
+ module.fail_json(
+ msg='Token or username & password are required for {object_name}'.format(
+ object_name=object_name,
+ ),
+ )
+ for option_name, option in OPTIONS.items():
+ if state in option.get('required', []) and not module.params.get(option_name):
+ module.fail_json(
+ msg='{option_name} parameter is required for {object_name} state {state}'.format(
+ option_name=option_name,
+ object_name=object_name,
+ state=state,
+ ),
+ )
+
+
+def main():
+ module = AnsibleModule(argument_spec=get_module_arguments(), supports_check_mode=True)
+
+ if not HAS_SDK:
+ module.fail_json(msg='ionoscloud_container_registry is required for this module, '
+ 'run `pip install ionoscloud_container_registry`')
+
+
+ client = ionoscloud_container_registry.ApiClient(get_sdk_config(module, ionoscloud_container_registry))
+ client.user_agent = CONTAINER_REGISTRY_USER_AGENT
+
+ state = module.params.get('state')
+
+ check_required_arguments(module, state, OBJECT_NAME)
+
+ try:
+ if state == 'absent':
+ module.exit_json(**remove_object(module, client))
+ except Exception as e:
+ module.fail_json(
+ msg='failed to set {object_name} state {state}: {error}'.format(
+ object_name=OBJECT_NAME, error=to_native(e), state=state,
+ ))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/plugins/modules/registry_repository_info.py b/plugins/modules/registry_repository_info.py
new file mode 100644
index 00000000..9eedc08b
--- /dev/null
+++ b/plugins/modules/registry_repository_info.py
@@ -0,0 +1,301 @@
+import copy
+import yaml
+
+from ansible import __version__
+from ansible.module_utils.basic import AnsibleModule, env_fallback
+from ansible.module_utils._text import to_native
+
+HAS_SDK = True
+try:
+ import ionoscloud_container_registry
+except ImportError:
+ HAS_SDK = False
+
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community',
+}
+CONTAINER_REGISTRY_USER_AGENT = 'ansible-module/%s_ionos-cloud-sdk-python-container-registry/%s' % (
+__version__, ionoscloud_container_registry.__version__)
+DOC_DIRECTORY = 'container-registry'
+STATES = ['info']
+OBJECT_NAME = 'Repositories'
+RETURNED_KEY = 'repositories'
+
+OPTIONS = {
+ 'filters': {
+ 'description': [
+ 'Filter that can be used to list only objects which have a certain set of propeties. Filters '
+ 'should be a dict with a key containing keys and value pair in the following format:'
+ "'properties.name': 'server_name'"
+ ],
+ 'available': STATES,
+ 'type': 'dict',
+ },
+ 'registry': {
+ 'description': ['The ID or name of an existing Registry.'],
+ 'available': STATES,
+ 'required': STATES,
+ 'type': 'str',
+ },
+ 'api_url': {
+ 'description': ['The Ionos API base URL.'],
+ 'version_added': '2.4',
+ 'env_fallback': 'IONOS_API_URL',
+ 'available': STATES,
+ 'type': 'str',
+ },
+ 'username': {
+ # Required if no token, checked manually
+ 'description': ['The Ionos username. Overrides the IONOS_USERNAME environment variable.'],
+ 'aliases': ['subscription_user'],
+ 'env_fallback': 'IONOS_USERNAME',
+ 'available': STATES,
+ 'type': 'str',
+ },
+ 'password': {
+ # Required if no token, checked manually
+ 'description': ['The Ionos password. Overrides the IONOS_PASSWORD environment variable.'],
+ 'aliases': ['subscription_password'],
+ 'available': STATES,
+ 'no_log': True,
+ 'env_fallback': 'IONOS_PASSWORD',
+ 'type': 'str',
+ },
+ 'token': {
+ # If provided, then username and password no longer required
+ 'description': ['The Ionos token. Overrides the IONOS_TOKEN environment variable.'],
+ 'available': STATES,
+ 'no_log': True,
+ 'env_fallback': 'IONOS_TOKEN',
+ 'type': 'str',
+ },
+}
+
+
+def transform_for_documentation(val):
+ val['required'] = len(val.get('required', [])) == len(STATES)
+ del val['available']
+ del val['type']
+ return val
+
+
+DOCUMENTATION = '''
+---
+module: registry_repository_info
+short_description: List Repositories
+description:
+ - This is a simple module that supports listing existing Repositories
+version_added: "2.0"
+options:
+''' + ' ' + yaml.dump(
+ yaml.safe_load(str({k: transform_for_documentation(v) for k, v in copy.deepcopy(OPTIONS).items()})),
+ default_flow_style=False).replace('\n', '\n ') + '''
+requirements:
+ - "python >= 2.6"
+ - "ionoscloud-container-registry >= 1.0.0"
+author:
+ - "IONOS Cloud SDK Team "
+'''
+
+EXAMPLES = '''
+ - name: List Repositories
+ registry_repository_info:
+ registry: "RegistryName"
+ register: repositories_response
+
+
+ - name: Show Repositories
+ debug:
+ var: repositories_response.result
+'''
+
+def _get_matched_resources(resource_list, identity, identity_paths=None):
+ """
+ Fetch and return a resource based on an identity supplied for it, if none or more than one matches
+ are found an error is printed and None is returned.
+ """
+
+ if identity_paths is None:
+ identity_paths = [['id'], ['properties', 'name']]
+
+ def check_identity_method(resource):
+ resource_identity = []
+
+ for identity_path in identity_paths:
+ current = resource
+ for el in identity_path:
+ current = getattr(current, el)
+ resource_identity.append(current)
+
+ return identity in resource_identity
+
+ return list(filter(check_identity_method, resource_list.items))
+
+
+def get_resource(module, resource_list, identity, identity_paths=None):
+ matched_resources = _get_matched_resources(resource_list, identity, identity_paths)
+
+ if len(matched_resources) == 1:
+ return matched_resources[0]
+ elif len(matched_resources) > 1:
+ module.fail_json(msg="found more resources of type {} for '{}'".format(resource_list.id, identity))
+ else:
+ return None
+
+
+def get_resource_id(module, resource_list, identity, identity_paths=None):
+ resource = get_resource(module, resource_list, identity, identity_paths)
+ return resource.id if resource is not None else None
+
+
+def get_method_from_filter(filter):
+ '''
+ Returns the method which check a filter for one object. Such a method would work in the following way:
+ for filter = ('properties.name', 'server_name') the resulting method would be
+ def method(item):
+ return item.properties.name == 'server_name'
+ Parameters:
+ filter (touple): Key, value pair representing the filter.
+ Returns:
+ the wanted method
+ '''
+ key, value = filter
+ def method(item):
+ current = item
+ for key_part in key.split('.'):
+ current = getattr(current, key_part)
+ return current == value
+ return method
+
+
+def get_method_to_apply_filters_to_item(filter_list):
+ '''
+ Returns the method which applies a list of filtering methods obtained using get_method_from_filter to
+ one object and returns true if all the filters return true
+ Parameters:
+ filter_list (list): List of filtering methods
+ Returns:
+ the wanted method
+ '''
+ def f(item):
+ return all([f(item) for f in filter_list])
+ return f
+
+
+def apply_filters(module, item_list):
+ '''
+ Creates a list of filtering methods from the filters module parameter, filters item_list to keep only the
+ items for which every filter matches using get_method_to_apply_filters_to_item to make that check and returns
+ those items
+ Parameters:
+ module: The current Ansible module
+ item_list (list): List of items to be filtered
+ Returns:
+ List of items which match the filters
+ '''
+ filters = module.params.get('filters')
+ if not filters:
+ return item_list
+ filter_methods = list(map(get_method_from_filter, filters.items()))
+
+ return filter(get_method_to_apply_filters_to_item(filter_methods), item_list)
+
+
+
+def get_module_arguments():
+ arguments = {}
+
+ for option_name, option in OPTIONS.items():
+ arguments[option_name] = {
+ 'type': option['type'],
+ }
+ for key in ['choices', 'default', 'aliases', 'no_log', 'elements']:
+ if option.get(key) is not None:
+ arguments[option_name][key] = option.get(key)
+
+ if option.get('env_fallback'):
+ arguments[option_name]['fallback'] = (env_fallback, [option['env_fallback']])
+
+ if len(option.get('required', [])) == len(STATES):
+ arguments[option_name]['required'] = True
+
+ return arguments
+
+
+def get_sdk_config(module, sdk):
+ username = module.params.get('username')
+ password = module.params.get('password')
+ token = module.params.get('token')
+ api_url = module.params.get('api_url')
+
+ if token is not None:
+ # use the token instead of username & password
+ conf = {
+ 'token': token
+ }
+ else:
+ # use the username & password
+ conf = {
+ 'username': username,
+ 'password': password,
+ }
+
+ if api_url is not None:
+ conf['host'] = api_url
+ conf['server_index'] = None
+
+ return sdk.Configuration(**conf)
+
+
+def check_required_arguments(module, object_name):
+ # manually checking if token or username & password provided
+ if (
+ not module.params.get("token")
+ and not (module.params.get("username") and module.params.get("password"))
+ ):
+ module.fail_json(
+ msg='Token or username & password are required for {object_name}'.format(
+ object_name=object_name,
+ ),
+ )
+ for option_name, option in OPTIONS.items():
+ if 'info' in option.get('required', []) and not module.params.get(option_name):
+ module.fail_json(
+ msg='{option_name} parameter is required for retrieving {object_name}'.format(
+ option_name=option_name,
+ object_name=object_name,
+ ),
+ )
+
+
+def main():
+ module = AnsibleModule(argument_spec=get_module_arguments(), supports_check_mode=True)
+
+ if not HAS_SDK:
+ module.fail_json(
+ msg='ionoscloud_container_registry is required for this module, run `pip install ionoscloud_container_registry`')
+
+
+ client = ionoscloud_container_registry.ApiClient(get_sdk_config(module, ionoscloud_container_registry))
+ client.user_agent = CONTAINER_REGISTRY_USER_AGENT
+
+ check_required_arguments(module, OBJECT_NAME)
+
+ try:
+ registry_id = get_resource_id(
+ module,
+ ionoscloud_container_registry.RegistriesApi(client).registries_get(),
+ module.params.get('registry'),
+ )
+ artifacts = ionoscloud_container_registry.RepositoriesApi(client).registries_repositories_get(registry_id)
+ results = list(map(lambda x: x.to_dict(), apply_filters(module, artifacts.items)))
+ module.exit_json(**{RETURNED_KEY:results})
+ except Exception as e:
+ module.fail_json(
+ msg='failed to retrieve {object_name}: {error}'.format(object_name=OBJECT_NAME, error=to_native(e)))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/plugins/modules/registry_vulnerability_info.py b/plugins/modules/registry_vulnerability_info.py
new file mode 100644
index 00000000..d52fff4d
--- /dev/null
+++ b/plugins/modules/registry_vulnerability_info.py
@@ -0,0 +1,320 @@
+import copy
+import yaml
+
+from ansible import __version__
+from ansible.module_utils.basic import AnsibleModule, env_fallback
+from ansible.module_utils._text import to_native
+
+HAS_SDK = True
+try:
+ import ionoscloud_container_registry
+except ImportError:
+ HAS_SDK = False
+
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community',
+}
+CONTAINER_REGISTRY_USER_AGENT = 'ansible-module/%s_ionos-cloud-sdk-python-container-registry/%s' % (
+__version__, ionoscloud_container_registry.__version__)
+DOC_DIRECTORY = 'container-registry'
+STATES = ['info']
+OBJECT_NAME = 'Vulnerabilities'
+RETURNED_KEY = 'vulnerabilities'
+
+OPTIONS = {
+ 'filters': {
+ 'description': [
+ 'Filter that can be used to list only objects which have a certain set of propeties. Filters '
+ 'should be a dict with a key containing keys and value pair in the following format:'
+ "'properties.name': 'server_name'"
+ ],
+ 'available': STATES,
+ 'type': 'dict',
+ },
+ 'registry': {
+ 'description': ['The ID or name of an existing Registry.'],
+ 'available': STATES,
+ 'required': STATES,
+ 'type': 'str',
+ },
+ 'repository': {
+ 'description': ['The name of an existing Repository.'],
+ 'available': STATES,
+ 'required': STATES,
+ 'type': 'str',
+ },
+ 'artifact': {
+ 'description': ['The digest of an existing Artifact.'],
+ 'available': STATES,
+ 'required': STATES,
+ 'type': 'str',
+ },
+ 'api_url': {
+ 'description': ['The Ionos API base URL.'],
+ 'version_added': '2.4',
+ 'env_fallback': 'IONOS_API_URL',
+ 'available': STATES,
+ 'type': 'str',
+ },
+ 'username': {
+ # Required if no token, checked manually
+ 'description': ['The Ionos username. Overrides the IONOS_USERNAME environment variable.'],
+ 'aliases': ['subscription_user'],
+ 'env_fallback': 'IONOS_USERNAME',
+ 'available': STATES,
+ 'type': 'str',
+ },
+ 'password': {
+ # Required if no token, checked manually
+ 'description': ['The Ionos password. Overrides the IONOS_PASSWORD environment variable.'],
+ 'aliases': ['subscription_password'],
+ 'available': STATES,
+ 'no_log': True,
+ 'env_fallback': 'IONOS_PASSWORD',
+ 'type': 'str',
+ },
+ 'token': {
+ # If provided, then username and password no longer required
+ 'description': ['The Ionos token. Overrides the IONOS_TOKEN environment variable.'],
+ 'available': STATES,
+ 'no_log': True,
+ 'env_fallback': 'IONOS_TOKEN',
+ 'type': 'str',
+ },
+}
+
+
+def transform_for_documentation(val):
+ val['required'] = len(val.get('required', [])) == len(STATES)
+ del val['available']
+ del val['type']
+ return val
+
+
+DOCUMENTATION = '''
+---
+module: registry_vulnerability_info
+short_description: List Vulnerabilities
+description:
+ - This is a simple module that supports listing existing Vulnerabilities
+version_added: "2.0"
+options:
+''' + ' ' + yaml.dump(
+ yaml.safe_load(str({k: transform_for_documentation(v) for k, v in copy.deepcopy(OPTIONS).items()})),
+ default_flow_style=False).replace('\n', '\n ') + '''
+requirements:
+ - "python >= 2.6"
+ - "ionoscloud-container-registry >= 1.0.0"
+author:
+ - "IONOS Cloud SDK Team "
+'''
+
+EXAMPLES = '''
+ - name: List Vulnerabilities
+ registry_vulnerability_info:
+ registry: "RegistryName"
+ repository: "repositoryName"
+ arifact: ""
+ register: vulnerabilities_response
+
+
+ - name: Show Vulnerabilities
+ debug:
+ var: vulnerabilities_response.result
+'''
+
+def _get_matched_resources(resource_list, identity, identity_paths=None):
+ """
+ Fetch and return a resource based on an identity supplied for it, if none or more than one matches
+ are found an error is printed and None is returned.
+ """
+
+ if identity_paths is None:
+ identity_paths = [['id'], ['properties', 'name']]
+
+ def check_identity_method(resource):
+ resource_identity = []
+
+ for identity_path in identity_paths:
+ current = resource
+ for el in identity_path:
+ current = getattr(current, el)
+ resource_identity.append(current)
+
+ return identity in resource_identity
+
+ return list(filter(check_identity_method, resource_list.items))
+
+
+def get_resource(module, resource_list, identity, identity_paths=None):
+ matched_resources = _get_matched_resources(resource_list, identity, identity_paths)
+
+ if len(matched_resources) == 1:
+ return matched_resources[0]
+ elif len(matched_resources) > 1:
+ module.fail_json(msg="found more resources of type {} for '{}'".format(resource_list.id, identity))
+ else:
+ return None
+
+
+def get_resource_id(module, resource_list, identity, identity_paths=None):
+ resource = get_resource(module, resource_list, identity, identity_paths)
+ return resource.id if resource is not None else None
+
+
+def get_method_from_filter(filter):
+ '''
+ Returns the method which check a filter for one object. Such a method would work in the following way:
+ for filter = ('properties.name', 'server_name') the resulting method would be
+ def method(item):
+ return item.properties.name == 'server_name'
+ Parameters:
+ filter (touple): Key, value pair representing the filter.
+ Returns:
+ the wanted method
+ '''
+ key, value = filter
+ def method(item):
+ current = item
+ for key_part in key.split('.'):
+ current = getattr(current, key_part)
+ return current == value
+ return method
+
+
+def get_method_to_apply_filters_to_item(filter_list):
+ '''
+ Returns the method which applies a list of filtering methods obtained using get_method_from_filter to
+ one object and returns true if all the filters return true
+ Parameters:
+ filter_list (list): List of filtering methods
+ Returns:
+ the wanted method
+ '''
+ def f(item):
+ return all([f(item) for f in filter_list])
+ return f
+
+
+def apply_filters(module, item_list):
+ '''
+ Creates a list of filtering methods from the filters module parameter, filters item_list to keep only the
+ items for which every filter matches using get_method_to_apply_filters_to_item to make that check and returns
+ those items
+ Parameters:
+ module: The current Ansible module
+ item_list (list): List of items to be filtered
+ Returns:
+ List of items which match the filters
+ '''
+ filters = module.params.get('filters')
+ if not filters:
+ return item_list
+ filter_methods = list(map(get_method_from_filter, filters.items()))
+
+ return filter(get_method_to_apply_filters_to_item(filter_methods), item_list)
+
+
+
+def get_module_arguments():
+ arguments = {}
+
+ for option_name, option in OPTIONS.items():
+ arguments[option_name] = {
+ 'type': option['type'],
+ }
+ for key in ['choices', 'default', 'aliases', 'no_log', 'elements']:
+ if option.get(key) is not None:
+ arguments[option_name][key] = option.get(key)
+
+ if option.get('env_fallback'):
+ arguments[option_name]['fallback'] = (env_fallback, [option['env_fallback']])
+
+ if len(option.get('required', [])) == len(STATES):
+ arguments[option_name]['required'] = True
+
+ return arguments
+
+
+def get_sdk_config(module, sdk):
+ username = module.params.get('username')
+ password = module.params.get('password')
+ token = module.params.get('token')
+ api_url = module.params.get('api_url')
+
+ if token is not None:
+ # use the token instead of username & password
+ conf = {
+ 'token': token
+ }
+ else:
+ # use the username & password
+ conf = {
+ 'username': username,
+ 'password': password,
+ }
+
+ if api_url is not None:
+ conf['host'] = api_url
+ conf['server_index'] = None
+
+ return sdk.Configuration(**conf)
+
+
+def check_required_arguments(module, object_name):
+ # manually checking if token or username & password provided
+ if (
+ not module.params.get("token")
+ and not (module.params.get("username") and module.params.get("password"))
+ ):
+ module.fail_json(
+ msg='Token or username & password are required for {object_name}'.format(
+ object_name=object_name,
+ ),
+ )
+ for option_name, option in OPTIONS.items():
+ if 'info' in option.get('required', []) and not module.params.get(option_name):
+ module.fail_json(
+ msg='{option_name} parameter is required for retrieving {object_name}'.format(
+ option_name=option_name,
+ object_name=object_name,
+ ),
+ )
+
+
+def main():
+ module = AnsibleModule(argument_spec=get_module_arguments(), supports_check_mode=True)
+
+ if not HAS_SDK:
+ module.fail_json(
+ msg='ionoscloud_container_registry is required for this module, run `pip install ionoscloud_container_registry`')
+
+
+ client = ionoscloud_container_registry.ApiClient(get_sdk_config(module, ionoscloud_container_registry))
+ client.user_agent = CONTAINER_REGISTRY_USER_AGENT
+
+ check_required_arguments(module, OBJECT_NAME)
+
+ try:
+ registry_id = get_resource_id(
+ module,
+ ionoscloud_container_registry.RegistriesApi(client).registries_get(),
+ module.params.get('registry'),
+ )
+ artifacts_api = ionoscloud_container_registry.ArtifactsApi(client)
+ vulnerabilities = artifacts_api.registries_repositories_artifacts_vulnerabilities_get(
+ registry_id,
+ module.params.get('repository'),
+ module.params.get('artifact'),
+ )
+ results = list(map(lambda x: x.to_dict(), apply_filters(module, vulnerabilities.items)))
+ module.exit_json(**{RETURNED_KEY:results})
+ except Exception as e:
+ module.fail_json(
+ msg='failed to retrieve {object_name}: {error}'.format(object_name=OBJECT_NAME, error=to_native(e)))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/container-registry/registry-test.yml b/tests/container-registry/registry-test.yml
index 867341b4..005052ef 100644
--- a/tests/container-registry/registry-test.yml
+++ b/tests/container-registry/registry-test.yml
@@ -15,6 +15,10 @@
days:
- Wednesday
time: 04:17:00+00:00
+ features:
+ vulnerability_scanning:
+ enabled: false
+ wait: true
register: registry_response
- name: List Registries
@@ -23,16 +27,43 @@
- name: Show Registries
debug:
- var: registries_response.result
+ var: registries_response
+
+ - name: List Repositories
+ registry_repository_info:
+ registry: "{{ registry_response.registry.properties.name }}"
+ register: repositories_response
+
+ - name: Show Repositories
+ debug:
+ var: repositories_response
+
+ - name: Ensure Repository does not exist
+ registry_repository:
+ registry: "{{ registry_response.registry.properties.name }}"
+ repository: repo-name
+ state: absent
+
+ - name: List artifacts
+ registry_artifact_info:
+ registry: "{{ registry_response.registry.properties.name }}"
+ register: artifacts_response
+
+ - name: Show artifacts
+ debug:
+ var: artifacts_response
- name: Update Registry
registry:
registry: "{{ registry_response.registry.properties.name }}"
garbage_collection_schedule:
days:
- - Wednesday
- - Sunday
+ - Wednesday
+ - Sunday
time: 06:17:00+00:00
+ features:
+ vulnerability_scanning:
+ enabled: true
allow_replace: False
state: update
register: updated_registry_response
@@ -42,8 +73,11 @@
registry: "{{ registry_response.registry.properties.name }}"
garbage_collection_schedule:
days:
- - Wednesday
- - Sunday
+ - Wednesday
+ - Sunday
+ features:
+ vulnerability_scanning:
+ enabled: true
allow_replace: False
state: update
register: updated_registry_response
@@ -54,8 +88,28 @@
- updated_registry_response.changed == false
msg: "Changed should be false"
- - name: Delete Registry
+ - name: Replace Registry
registry:
registry: "{{ registry_response.registry.properties.name }}"
+ name: "{{ registry_response.registry.properties.name }}2"
+ garbage_collection_schedule:
+ days:
+ - Wednesday
+ - Sunday
+ time: 06:17:00+00:00
+ features:
+ vulnerability_scanning:
+ enabled: false
+ allow_replace: true
+ state: update
+ register: updated_registry_response
+
+ - name: Show Registry
+ debug:
+ var: updated_registry_response
+
+ - name: Delete Registry
+ registry:
+ registry: "{{ updated_registry_response.registry.properties.name }}"
wait: true
state: absent
diff --git a/tests/container-registry/registry-token-test.yml b/tests/container-registry/registry-token-test.yml
index 5b826bb2..8a9ed704 100644
--- a/tests/container-registry/registry-token-test.yml
+++ b/tests/container-registry/registry-token-test.yml
@@ -40,7 +40,7 @@
- pull
- push
name: nume
- type: repo
+ type: repository
status: enabled
register: registry_token_response