Switch to new galaxy-ng container setup (#86623)

This commit is contained in:
sivel / Matt Martz
2026-03-24 13:40:43 -05:00
committed by GitHub
parent ef7b026f77
commit 03c851d681
16 changed files with 222 additions and 963 deletions
+2
View File
@@ -0,0 +1,2 @@
minor_changes:
- ansible-test - update galaxy_ng container to current version deployed to galaxy.ansible.com
@@ -1,233 +0,0 @@
#!/usr/bin/python
# Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import annotations
DOCUMENTATION = """
---
module: reset_pulp
short_description: Resets pulp back to the initial state
description:
- See short_description
options:
pulp_api:
description:
- The Pulp API endpoint.
required: yes
type: str
galaxy_ng_server:
description:
- The Galaxy NG API endpoint.
required: yes
type: str
url_username:
description:
- The username to use when authenticating against Pulp.
required: yes
type: str
url_password:
description:
- The password to use when authenticating against Pulp.
required: yes
type: str
repositories:
description:
- A list of pulp repositories to create.
- Galaxy NG expects a repository that matches C(GALAXY_API_DEFAULT_DISTRIBUTION_BASE_PATH) in
C(/etc/pulp/settings.py) or the default of C(published).
required: yes
type: list
elements: str
namespaces:
description:
- A list of namespaces to create for Galaxy NG.
required: yes
type: list
elements: str
author:
- Jordan Borean (@jborean93)
"""
EXAMPLES = """
- name: reset pulp content
reset_pulp:
pulp_api: http://galaxy:24817
galaxy_ng_server: http://galaxy/api/galaxy/
url_username: username
url_password: password
repository: published
namespaces:
- namespace1
- namespace2
"""
RETURN = """
#
"""
import json
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.common.text.converters import to_text
def invoke_api(module, url, method='GET', data=None, status_codes=None):
status_codes = status_codes or [200]
headers = {}
if data:
headers['Content-Type'] = 'application/json'
data = json.dumps(data)
resp, info = fetch_url(module, url, method=method, data=data, headers=headers)
if info['status'] not in status_codes:
info['url'] = url
module.fail_json(**info)
data = to_text(resp.read())
if data:
return json.loads(data)
def delete_galaxy_namespace(namespace, module):
""" Deletes the galaxy ng namespace specified. """
ns_uri = '%sv3/namespaces/%s/' % (module.params['galaxy_ng_server'], namespace)
invoke_api(module, ns_uri, method='DELETE', status_codes=[204])
def delete_pulp_distribution(distribution, module):
""" Deletes the pulp distribution at the URI specified. """
task_info = invoke_api(module, distribution, method='DELETE', status_codes=[202])
wait_pulp_task(task_info['task'], module)
def delete_pulp_orphans(module):
""" Deletes any orphaned pulp objects. """
orphan_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/orphans/'
task_info = invoke_api(module, orphan_uri, method='DELETE', status_codes=[202])
wait_pulp_task(task_info['task'], module)
def delete_pulp_repository(repository, module):
""" Deletes the pulp repository at the URI specified. """
task_info = invoke_api(module, repository, method='DELETE', status_codes=[202])
wait_pulp_task(task_info['task'], module)
def get_galaxy_namespaces(module):
""" Gets a list of galaxy namespaces. """
# No pagination has been implemented, shouldn't need unless we ever exceed 100 namespaces.
namespace_uri = module.params['galaxy_ng_server'] + 'v3/namespaces/?limit=100&offset=0'
ns_info = invoke_api(module, namespace_uri)
return [n['name'] for n in ns_info['data']]
def get_pulp_distributions(module, distribution):
""" Gets a list of all the pulp distributions. """
distro_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/distributions/ansible/ansible/'
distro_info = invoke_api(module, distro_uri + '?name=' + distribution)
return [module.params['pulp_api'] + r['pulp_href'] for r in distro_info['results']]
def get_pulp_repositories(module, repository):
""" Gets a list of all the pulp repositories. """
repo_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/repositories/ansible/ansible/'
repo_info = invoke_api(module, repo_uri + '?name=' + repository)
return [module.params['pulp_api'] + r['pulp_href'] for r in repo_info['results']]
def get_repo_collections(repository, module):
collections_uri = module.params['galaxy_ng_server'] + 'v3/plugin/ansible/content/' + repository + '/collections/index/'
# status code 500 isn't really expected, an unhandled exception is causing this instead of a 404
# See https://issues.redhat.com/browse/AAH-2329
info = invoke_api(module, collections_uri + '?limit=100&offset=0', status_codes=[200, 500])
if not info:
return []
return [module.params['pulp_api'] + c['href'] for c in info['data']]
def delete_repo_collection(collection, module):
task_info = invoke_api(module, collection, method='DELETE', status_codes=[202])
wait_pulp_task(task_info['task'], module)
def new_galaxy_namespace(name, module):
""" Creates a new namespace in Galaxy NG. """
ns_uri = module.params['galaxy_ng_server'] + 'v3/namespaces/ '
data = {'name': name, 'groups': []}
ns_info = invoke_api(module, ns_uri, method='POST', data=data, status_codes=[201])
return ns_info['id']
def new_pulp_repository(name, module):
""" Creates a new pulp repository. """
repo_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/repositories/ansible/ansible/'
# retain_repo_versions to work around https://issues.redhat.com/browse/AAH-2332
data = {'name': name, 'retain_repo_versions': '1024'}
repo_info = invoke_api(module, repo_uri, method='POST', data=data, status_codes=[201])
return repo_info['pulp_href']
def new_pulp_distribution(name, base_path, repository, module):
""" Creates a new pulp distribution for a repository. """
distro_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/distributions/ansible/ansible/'
data = {'name': name, 'base_path': base_path, 'repository': repository}
task_info = invoke_api(module, distro_uri, method='POST', data=data, status_codes=[202])
task_info = wait_pulp_task(task_info['task'], module)
return module.params['pulp_api'] + task_info['created_resources'][0]
def wait_pulp_task(task, module):
""" Waits for a pulp import task to finish. """
while True:
task_info = invoke_api(module, module.params['pulp_api'] + task)
if task_info['finished_at'] is not None:
break
return task_info
def main():
module_args = dict(
pulp_api=dict(type='str', required=True),
galaxy_ng_server=dict(type='str', required=True),
url_username=dict(type='str', required=True),
url_password=dict(type='str', required=True, no_log=True),
repositories=dict(type='list', elements='str', required=True),
namespaces=dict(type='list', elements='str', required=True),
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=False
)
module.params['force_basic_auth'] = True
# It may be due to the process of cleaning up orphans, but we cannot delete the namespace
# while a collection still exists, so this is just a new safety to nuke all collections
# first
for repository in module.params['repositories']:
[delete_repo_collection(c, module) for c in get_repo_collections(repository, module)]
for repository in module.params['repositories']:
[delete_pulp_distribution(d, module) for d in get_pulp_distributions(module, repository)]
[delete_pulp_repository(r, module) for r in get_pulp_repositories(module, repository)]
delete_pulp_orphans(module)
[delete_galaxy_namespace(n, module) for n in get_galaxy_namespaces(module)]
for repository in module.params['repositories']:
repo_href = new_pulp_repository(repository, module)
new_pulp_distribution(repository, repository, repo_href, module)
[new_galaxy_namespace(n, module) for n in module.params['namespaces']]
module.exit_json(changed=True)
if __name__ == '__main__':
main()
@@ -1,291 +0,0 @@
#!/usr/bin/python
# Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import annotations
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = """
---
module: setup_collections
short_description: Set up test collections based on the input
description:
- Builds and publishes a whole bunch of collections used for testing in bulk.
options:
server:
description:
- The Galaxy server to upload the collections to.
required: yes
type: str
token:
description:
- The token used to authenticate with the Galaxy server.
required: yes
type: str
collections:
description:
- A list of collection details to use for the build.
required: yes
type: list
elements: dict
options:
namespace:
description:
- The namespace of the collection.
required: yes
type: str
name:
description:
- The name of the collection.
required: yes
type: str
version:
description:
- The version of the collection.
type: str
default: '1.0.0'
dependencies:
description:
- The dependencies of the collection.
type: dict
default: '{}'
use_symlink:
description:
- Create a symlink in the collection.
type: bool
default: False
runtime:
description:
- File content for meta/runtime.yml.
type: str
default: 'requires_ansible: ">=2.9"'
author:
- Jordan Borean (@jborean93)
"""
EXAMPLES = """
- name: Build test collections
setup_collections:
path: ~/ansible/collections/ansible_collections
collections:
- namespace: namespace1
name: name1
version: 0.0.1
- namespace: namespace1
name: name1
version: 0.0.2
"""
RETURN = """
#
"""
import datetime
import os
import subprocess
import tarfile
import tempfile
import yaml
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_bytes
from functools import partial
from multiprocessing import dummy as threading
from multiprocessing import TimeoutError, Lock
COLLECTIONS_BUILD_AND_PUBLISH_TIMEOUT = 300
LOCK = Lock()
def publish_collection(module, collection):
namespace = collection['namespace']
name = collection['name']
version = collection['version']
dependencies = collection['dependencies']
use_symlink = collection['use_symlink']
runtime = collection['runtime']
result = {}
collection_dir = os.path.join(module.tmpdir, "%s-%s-%s" % (namespace, name, version))
b_collection_dir = to_bytes(collection_dir, errors='surrogate_or_strict')
os.mkdir(b_collection_dir)
os.mkdir(os.path.join(b_collection_dir, b'meta'))
with open(os.path.join(b_collection_dir, b'README.md'), mode='wb') as fd:
fd.write(b"Collection readme")
galaxy_meta = {
'namespace': namespace,
'name': name,
'version': version,
'readme': 'README.md',
'authors': ['Collection author <name@email.com'],
'dependencies': dependencies,
'license': ['GPL-3.0-or-later'],
'repository': 'https://ansible.com/',
}
with open(os.path.join(b_collection_dir, b'galaxy.yml'), mode='wb') as fd:
fd.write(to_bytes(yaml.safe_dump(galaxy_meta), errors='surrogate_or_strict'))
with open(os.path.join(b_collection_dir, b'meta/runtime.yml'), mode='wb') as fd:
fd.write(to_bytes(runtime))
with tempfile.NamedTemporaryFile(mode='wb') as temp_fd:
temp_fd.write(b"data")
if use_symlink:
os.mkdir(os.path.join(b_collection_dir, b'docs'))
os.mkdir(os.path.join(b_collection_dir, b'plugins'))
b_target_file = b'RE\xc3\x85DM\xc3\x88.md'
with open(os.path.join(b_collection_dir, b_target_file), mode='wb') as fd:
fd.write(b'data')
os.symlink(b_target_file, os.path.join(b_collection_dir, b_target_file + b'-link'))
os.symlink(temp_fd.name, os.path.join(b_collection_dir, b_target_file + b'-outside-link'))
os.symlink(os.path.join(b'..', b_target_file), os.path.join(b_collection_dir, b'docs', b_target_file))
os.symlink(os.path.join(b_collection_dir, b_target_file),
os.path.join(b_collection_dir, b'plugins', b_target_file))
os.symlink(b'docs', os.path.join(b_collection_dir, b'docs-link'))
release_filename = '%s-%s-%s.tar.gz' % (namespace, name, version)
collection_path = os.path.join(collection_dir, release_filename)
rc, stdout, stderr = module.run_command(['ansible-galaxy', 'collection', 'build'], cwd=collection_dir)
result['build'] = {
'rc': rc,
'stdout': stdout,
'stderr': stderr,
}
if module.params['signature_dir'] is not None:
# To test user-provided signatures, we need to sign the MANIFEST.json before publishing
# Extract the tarfile to sign the MANIFEST.json
with tarfile.open(collection_path, mode='r') as collection_tar:
collection_tar.extractall(path=os.path.join(collection_dir, '%s-%s-%s' % (namespace, name, version)), filter='data')
manifest_path = os.path.join(collection_dir, '%s-%s-%s' % (namespace, name, version), 'MANIFEST.json')
signature_path = os.path.join(module.params['signature_dir'], '%s-%s-%s-MANIFEST.json.asc' % (namespace, name, version))
sign_manifest(signature_path, manifest_path, module, result)
# Create the tarfile containing the signed MANIFEST.json
with tarfile.open(collection_path, "w:gz") as tar:
tar.add(os.path.join(collection_dir, '%s-%s-%s' % (namespace, name, version)), arcname=os.path.sep)
publish_args = ['ansible-galaxy', 'collection', 'publish', collection_path, '--server', module.params['server']]
if module.params['token']:
publish_args.extend(['--token', module.params['token']])
with LOCK:
# lock publish operations since Galaxy publish DB key generation is not mutex'd
rc, stdout, stderr = module.run_command(publish_args)
result['publish'] = {
'rc': rc,
'stdout': stdout,
'stderr': stderr,
}
return result
def sign_manifest(signature_path, manifest_path, module, collection_setup_result):
collection_setup_result['gpg_detach_sign'] = {'signature_path': signature_path}
status_fd_read, status_fd_write = os.pipe()
gpg_cmd = [
"gpg",
"--batch",
"--pinentry-mode",
"loopback",
"--yes",
"--homedir",
module.params['signature_dir'],
"--detach-sign",
"--armor",
"--output",
signature_path,
manifest_path,
]
try:
p = subprocess.Popen(
gpg_cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
pass_fds=(status_fd_write,),
encoding='utf8',
)
except (FileNotFoundError, subprocess.SubprocessError) as err:
collection_setup_result['gpg_detach_sign']['error'] = "Failed during GnuPG verification with command '{gpg_cmd}': {err}".format(
gpg_cmd=gpg_cmd, err=err
)
else:
stdout, stderr = p.communicate()
collection_setup_result['gpg_detach_sign']['stdout'] = stdout
if stderr:
error = "Failed during GnuPG verification with command '{gpg_cmd}':\n{stderr}".format(gpg_cmd=gpg_cmd, stderr=stderr)
collection_setup_result['gpg_detach_sign']['error'] = error
finally:
os.close(status_fd_write)
def run_module():
module_args = dict(
server=dict(type='str', required=True),
token=dict(type='str'),
collections=dict(
type='list',
elements='dict',
required=True,
options=dict(
namespace=dict(type='str', required=True),
name=dict(type='str', required=True),
version=dict(type='str', default='1.0.0'),
dependencies=dict(type='dict', default={}),
use_symlink=dict(type='bool', default=False),
runtime=dict(type='str', default='requires_ansible: ">=2.9"'),
),
),
signature_dir=dict(type='path', default=None),
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=False
)
start = datetime.datetime.now()
result = dict(changed=True, results=[], start=str(start))
pool = threading.Pool(1)
publish_func = partial(publish_collection, module)
try:
result['results'] = pool.map_async(
publish_func, module.params['collections'],
).get(timeout=COLLECTIONS_BUILD_AND_PUBLISH_TIMEOUT)
except TimeoutError as timeout_err:
module.fail_json(
'Timed out waiting for collections to be provisioned.',
)
failed = bool(sum(
r['build']['rc'] + r['publish']['rc'] for r in result['results']
))
end = datetime.datetime.now()
delta = end - start
module.exit_json(failed=failed, end=str(end), delta=str(delta), **result)
def main():
run_module()
if __name__ == '__main__':
main()
@@ -4,6 +4,16 @@
path: '{{ galaxy_dir }}/ansible_collections'
state: directory
- name: install simple collection from specific server
command: ansible-galaxy collection install namespace1.name1 -s galaxy_ng_token -vvvv
environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
- name: remove collection
file:
path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1'
state: absent
- name: install simple collection from first accessible server
command: ansible-galaxy collection install namespace1.name1 -vvvv
environment:
@@ -30,7 +40,6 @@
- install_normal_files.files[1].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md']
- install_normal_files.files[2].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md']
- (install_normal_manifest.content | b64decode | from_json).collection_info.version == '1.0.9'
- 'from_first_good_server.stdout|regex_findall("has not signed namespace1\.name1")|length == 1'
- "info_dir is is_dir"
vars:
info_dir: "{{ galaxy_dir }}/ansible_collections/namespace1.name1-1.0.9.info"
@@ -178,9 +187,9 @@
- name: Find artifact url for namespace3.name
uri:
url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/namespace3/name/versions/1.0.0/'
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
url: '{{ test_api_server }}/v3/plugin/ansible/content/published/collections/index/namespace3/name/versions/1.0.0/'
user: '{{ galaxy_user }}'
password: '{{ galaxy_password }}'
force_basic_auth: true
register: artifact_url_response
@@ -188,6 +197,9 @@
get_url:
url: '{{ artifact_url_response.json.download_url }}'
dest: '{{ galaxy_dir }}/namespace3.tar.gz'
url_username: '{{ galaxy_user }}'
url_password: '{{ galaxy_password }}'
force_basic_auth: true
- name: install a collection from a tarball - {{ test_id }}
command: ansible-galaxy collection install '{{ galaxy_dir }}/namespace3.tar.gz' {{ galaxy_verbosity }}
@@ -305,16 +317,8 @@
that:
- not fail_bad_tar_actual.stat.exists
- name: Find artifact url for namespace4.name
uri:
url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/namespace4/name/versions/1.0.0/'
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
force_basic_auth: true
register: artifact_url_response
- name: install a collection from a URI - {{ test_id }}
command: ansible-galaxy collection install {{ artifact_url_response.json.download_url}} {{ galaxy_verbosity }}
command: ansible-galaxy collection install {{ amanda }}/artifacts/namespace4-name-1.0.0.tar.gz {{ galaxy_verbosity }}
register: install_uri
environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
@@ -438,6 +442,30 @@
- namespace8
- namespace9
- name: store signatures
copy:
dest: "{{ gpg_homedir }}/{{ item.namespace }}-{{ item.name }}-{{ item.version }}-MANIFEST.json.asc"
content: "{{ version_data.signatures.0.signature }}"
loop:
- namespace: namespace1
name: name1
version: "1.0.0"
- namespace: namespace1
name: name1
version: "1.0.9"
- namespace: namespace2
name: name
version: "1.0.0"
- namespace: namespace7
name: name
version: "1.0.0"
- namespace: namespace9
name: name
version: "1.0.0"
vars:
version_url: '{{ galaxy_ng_server }}/v3/collections/{{ item.namespace }}/{{ item.name }}/versions/{{ item.version }}/'
version_data: "{{ lookup('url', version_url, username=galaxy_user, password=galaxy_password, force_basic_auth=true)|from_json }}"
- name: rewrite requirements file with collections and signatures
copy:
content: |
@@ -751,13 +779,36 @@
- set_fact:
cache_version_build: '{{ (cache_version_build | int) + 1 }}'
- name: publish update for cache.cache test
setup_collections:
server: galaxy_ng
collections:
- namespace: cache
name: cache
version: 1.0.{{ cache_version_build }}
- name: copy cache collection to tmpdir
copy:
src: '{{ galaxy_dir }}/ansible_collections/cache/cache/'
dest: '{{ galaxy_dir }}/cache-src'
remote_src: true
- name: write galaxy.yml file
copy:
dest: '{{ galaxy_dir }}/cache-src/galaxy.yml'
content: |
{{ (lookup('file', manifest)|from_json).collection_info|to_nice_yaml }}
vars:
manifest: '{{ galaxy_dir }}/cache-src/MANIFEST.json'
- name: update cache.cache src version
replace:
path: '{{ galaxy_dir }}/cache-src/galaxy.yml'
regexp: '^version:.*$'
replace: 'version: 1.0.{{ cache_version_build }}'
- name: build updated cache.cache
command:
cmd: ansible-galaxy collection build
chdir: '{{ galaxy_dir }}/cache-src'
register: cache_cache_build
- name: publish updated cache.cache
command:
cmd: ansible-galaxy collection publish -s galaxy_ng "{{ cache_cache_build.stdout_lines|last|split|last }}"
chdir: '{{ galaxy_dir }}/cache-src'
- name: make sure the cache version list is ignored on a collection version change - {{ test_id }}
command: ansible-galaxy collection install cache.cache -s '{{ test_name }}' --force -vvv
@@ -1017,6 +1068,7 @@
- name: install simple collection with invalid detached signature
command: ansible-galaxy collection install namespace1.name1 -vvvv {{ signature_options }}
environment:
ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: "all"
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
ANSIBLE_NOCOLOR: True
ANSIBLE_FORCE_COLOR: False
@@ -1067,6 +1119,7 @@
- name: use lenient signature verification (default) without providing signatures
command: ansible-galaxy collection install namespace1.name1:1.0.0 -vvvv --keyring {{ gpg_homedir }}/pubring.kbx --force
environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: "all"
register: missing_signature
@@ -1077,8 +1130,9 @@
- '"namespace1.name1:1.0.0 was installed successfully" in missing_signature.stdout'
- '"Signature verification failed for ''namespace1.name1'': no successful signatures" not in missing_signature.stdout'
# These steps use secondary (amanda) as it has no signatures provided
- name: use strict signature verification without providing signatures
command: ansible-galaxy collection install namespace1.name1:1.0.0 -vvvv --keyring {{ gpg_homedir }}/pubring.kbx --force
command: ansible-galaxy collection install -s secondary namespace1.name1:1.0.0 -vvvv --keyring {{ gpg_homedir }}/pubring.kbx --force
environment:
ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: "+1"
ignore_errors: yes
@@ -14,34 +14,28 @@
- name: run ansible-galaxy collection build tests
import_tasks: build.yml
# The pulp container has a long start up time
# The first task to interact with pulp needs to wait until it responds appropriately
- name: list pulp distributions
- name: get pulp repository list
uri:
url: '{{ pulp_api }}/pulp/api/v3/distributions/ansible/ansible/'
status_code:
- 200
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
url: '{{ galaxy_ng_server }}/pulp/api/v3/repositories/ansible/ansible/'
user: '{{ galaxy_user }}'
password: '{{ galaxy_password }}'
force_basic_auth: true
register: pulp_distributions
until: pulp_distributions is successful
delay: 1
retries: 60
register: pulp_repo_list
- name: configure pulp
include_tasks: pulp.yml
- set_fact:
pulp_published_repo: '{{ pulp_repo_list.json.results|selectattr("name", "eq", "published")|first }}'
pulp_rejected_repo: '{{ pulp_repo_list.json.results|selectattr("name", "eq", "rejected")|first }}'
- name: Get galaxy_ng token
uri:
url: '{{ galaxy_ng_server }}v3/auth/token/'
url: '{{ galaxy_ng_server }}/v3/auth/token/'
method: POST
body_format: json
body: {}
status_code:
- 200
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
user: '{{ galaxy_user }}'
password: '{{ galaxy_password }}'
force_basic_auth: true
register: galaxy_ng_token
@@ -79,19 +73,6 @@
- include_tasks: setup_gpg.yml
# We use a module for this so we can speed up the test time.
# For pulp interactions, we only upload to galaxy_ng which shares
# the same repo and distribution with pulp_ansible
# However, we use galaxy_ng only, since collections are unique across
# pulp repositories, and galaxy_ng maintains a 2nd list of published collections
- name: setup test collections for install and download test
setup_collections:
server: galaxy_ng
collections: '{{ collection_list }}'
signature_dir: '{{ gpg_homedir }}'
environment:
ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg'
# Stores the cached test version number index as we run install many times
- set_fact:
cache_version_build: 0
@@ -134,18 +115,6 @@
# In this test suite because the bug relies on the dep having versions on a Galaxy server
include_tasks: virtual_direct_requests.yml
- name: publish collection with a dep on another server
setup_collections:
server: secondary
collections:
- namespace: secondary
name: name
# parent_dep.parent_collection does not exist on the secondary server
dependencies:
parent_dep.parent_collection: '1.0.0'
environment:
ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg'
- name: install collection with dep on another server
command: ansible-galaxy collection install secondary.name -vvv # 3 -v's will show the source in the stdout
register: install_cross_dep
@@ -204,7 +173,7 @@
ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg'
vars:
test_api_fallback: 'secondary'
test_api_fallback_versions: 'v3, pulp-v3, v1'
test_api_fallback_versions: 'v3'
test_name: 'galaxy_ng'
- name: run ansible-galaxy collection list tests
@@ -10,10 +10,10 @@
- name: get result of publish collection - {{ test_name }}
uri:
url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/ansible_test/my_collection/versions/1.0.0/'
url: '{{ test_api_server }}/v3/plugin/ansible/content/published/collections/index/ansible_test/my_collection/versions/1.0.0/'
return_content: yes
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
user: '{{ galaxy_user }}'
password: '{{ galaxy_password }}'
force_basic_auth: true
register: publish_collection_actual
@@ -24,13 +24,3 @@
- publish_collection_actual.json.collection.name == 'my_collection'
- publish_collection_actual.json.namespace.name == 'ansible_test'
- publish_collection_actual.json.version == '1.0.0'
- name: fail to publish existing collection version - {{ test_name }}
command: ansible-galaxy collection publish ansible_test-my_collection-1.0.0.tar.gz -s {{ test_name }} {{ galaxy_verbosity }}
args:
chdir: '{{ galaxy_dir }}'
register: fail_publish_existing
failed_when: fail_publish_existing is not failed
- name: reset published collections - {{ test_name }}
include_tasks: pulp.yml
@@ -1,11 +0,0 @@
# These tasks configure pulp/pulp_ansible so that we can use the container
# This will also reset pulp between iterations
# A module is used to make the tests run quicker as this will send lots of API requests.
- name: reset pulp content
reset_pulp:
pulp_api: '{{ pulp_api }}'
galaxy_ng_server: '{{ galaxy_ng_server }}'
url_username: '{{ pulp_user }}'
url_password: '{{ pulp_password }}'
repositories: '{{ pulp_repositories }}'
namespaces: '{{ collection_list|map(attribute="namespace")|unique + publish_namespaces }}'
@@ -1,14 +1,10 @@
- name: generate revocation certificate
expect:
command: "gpg --homedir {{ gpg_homedir }} --pinentry-mode loopback --output {{ gpg_homedir }}/revoke.asc --gen-revoke {{ fingerprint }}"
responses:
"Create a revocation certificate for this key": "y"
"Please select the reason for the revocation": "0"
"Enter an optional description": ""
"Is this okay": "y"
- name: list keys for debugging
command: "gpg --no-tty --homedir {{ gpg_homedir }} --list-keys"
- name: revoke key
command: "gpg --no-tty --homedir {{ gpg_homedir }} --import {{ gpg_homedir }}/revoke.asc"
command:
cmd: "gpg --no-tty --homedir {{ gpg_homedir }} --import"
stdin: '{{ pulp_rejected_repo.gpgkey }}'
- name: list keys for debugging
command: "gpg --no-tty --homedir {{ gpg_homedir }} --list-keys {{ gpg_user }}"
command: "gpg --no-tty --homedir {{ gpg_homedir }} --list-keys"
@@ -32,14 +32,18 @@
command: whoami
register: user
- name: generate key for user with gpg
command: "gpg --no-tty --homedir {{ gpg_homedir }} --passphrase '' --pinentry-mode loopback --quick-gen-key {{ user.stdout }} default default"
- name: import public key
command:
cmd: gpg --no-tty --homedir {{ gpg_homedir }} --import
stdin: '{{ pulp_published_repo.gpgkey }}'
register: gpg_import
- name: list gpg keys for user
command: "gpg --no-tty --homedir {{ gpg_homedir }} --list-keys {{ user.stdout }}"
- command: gpg --no-tty --homedir {{ gpg_homedir }} --list-keys {{ keyid }}
vars:
keyid: "{{ (gpg_import.stderr_lines | select('match', 'gpg: key ') | first | split).2 | replace(':', '') }}"
register: gpg_list_keys
- name: save gpg user and fingerprint of new key
set_fact:
gpg_user: "{{ user.stdout }}"
gpg_user: ansible-test
fingerprint: "{{ gpg_list_keys.stdout_lines[1] | trim }}"
@@ -293,8 +293,9 @@
# TODO: add a test for offline Galaxy signature metadata
# These steps use secondary (amanda) as it has no signatures provided
- name: install a collection that was signed by setup_collections
command: ansible-galaxy collection install namespace1.name1:1.0.0
command: ansible-galaxy collection install -s secondary namespace1.name1:1.0.0
- name: verify the installed collection with a detached signature
command: ansible-galaxy collection verify namespace1.name1:1.0.0 {{ galaxy_verbosity }} {{ signature_options }}
@@ -320,6 +321,7 @@
environment:
ANSIBLE_NOCOLOR: True
ANSIBLE_FORCE_COLOR: False
ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all
- assert:
that:
@@ -346,6 +348,7 @@
environment:
ANSIBLE_NOCOLOR: True
ANSIBLE_FORCE_COLOR: False
ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all
- assert:
that:
@@ -450,7 +453,7 @@
verify_stdout: "{{ verify.stdout | regex_replace('\"') | regex_replace('\\n') | regex_replace(' ', ' ') }}"
- name: use lenient signature verification (default) without providing signatures
command: ansible-galaxy collection verify namespace1.name1:1.0.0 -vvvv --keyring {{ gpg_homedir }}/pubring.kbx
command: ansible-galaxy collection verify namespace1.name1:1.0.0 -s secondary -vvvv --keyring {{ gpg_homedir }}/pubring.kbx
environment:
ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: "1"
register: verify
@@ -1,16 +1,20 @@
[galaxy]
# Ensures subsequent unstable reruns don't use the cached information causing another failure
cache_dir={{ remote_tmp_dir }}/galaxy_cache
server_list=offline,galaxy_ng,secondary
server_list=offline,galaxy_ng,secondary,galaxy_ng_token
[galaxy_server.offline]
url={{ offline_server }}
[galaxy_server.galaxy_ng]
url={{ galaxy_ng_server }}content/primary/
token={{ galaxy_ng_token.json.token }}
url={{ galaxy_ng_server }}
username={{ galaxy_user }}
password={{ galaxy_password }}
[galaxy_server.secondary]
url={{ galaxy_ng_server }}content/secondary/
username={{ pulp_user }}
password={{ pulp_password }}
url={{ amanda }}
[galaxy_server.galaxy_ng_token]
# This server is last to prevent it from being used by tests that don't ask for it
url={{ galaxy_ng_server }}
token={{ galaxy_ng_token.json.token }}
@@ -13,231 +13,3 @@ supported_resolvelib_versions:
unsupported_resolvelib_versions:
- "0.2.0" # Fails on import
- "0.5.3"
pulp_repositories:
- primary
- secondary
publish_namespaces:
- ansible_test
- secondary
collection_list:
# Scenario to test out pre-release being ignored unless explicitly set and version pagination.
- namespace: namespace1
name: name1
version: 0.0.1
- namespace: namespace1
name: name1
version: 0.0.2
- namespace: namespace1
name: name1
version: 0.0.3
- namespace: namespace1
name: name1
version: 0.0.4
- namespace: namespace1
name: name1
version: 0.0.5
- namespace: namespace1
name: name1
version: 0.0.6
- namespace: namespace1
name: name1
version: 0.0.7
- namespace: namespace1
name: name1
version: 0.0.8
- namespace: namespace1
name: name1
version: 0.0.9
- namespace: namespace1
name: name1
version: 0.0.10
- namespace: namespace1
name: name1
version: 0.1.0
- namespace: namespace1
name: name1
version: 1.0.0
- namespace: namespace1
name: name1
version: 1.0.9
- namespace: namespace1
name: name1
version: 1.1.0-beta.1
# Pad out number of namespaces for pagination testing
- namespace: namespace2
name: name
- namespace: namespace3
name: name
- namespace: namespace4
name: name
- namespace: namespace5
name: name
- namespace: namespace6
name: name
- namespace: namespace7
name: name
- namespace: namespace8
name: name
- namespace: namespace9
name: name
# Complex dependency resolution
- namespace: parent_dep
name: parent_collection
version: 0.0.1
dependencies:
child_dep.child_collection: '<0.5.0'
- namespace: parent_dep
name: parent_collection
version: 1.0.0
dependencies:
child_dep.child_collection: '>=0.5.0,<1.0.0'
- namespace: parent_dep
name: parent_collection
version: 1.1.0
dependencies:
child_dep.child_collection: '>=0.9.9,<=1.0.0'
- namespace: parent_dep
name: parent_collection
version: 2.0.0
dependencies:
child_dep.child_collection: '>=1.0.0'
- namespace: parent_dep2
name: parent_collection
dependencies:
child_dep.child_collection: '0.5.0'
- namespace: child_dep
name: child_collection
version: 0.4.0
- namespace: child_dep
name: child_collection
version: 0.5.0
- namespace: child_dep
name: child_collection
version: 0.9.9
dependencies:
child_dep.child_dep2: '!=1.2.3'
- namespace: child_dep
name: child_collection
version: 1.0.0
dependencies:
child_dep.child_dep2: '!=1.2.3'
- namespace: child_dep
name: child_dep2
version: 1.2.2
- namespace: child_dep
name: child_dep2
version: 1.2.3
# Dep resolution failure
- namespace: fail_namespace
name: fail_collection
version: 2.1.2
dependencies:
fail_dep.name: '0.0.5'
fail_dep2.name: '<0.0.5'
- namespace: fail_dep
name: name
version: '0.0.5'
dependencies:
fail_dep2.name: '>0.0.5'
- namespace: fail_dep2
name: name
# Symlink tests
- namespace: symlink
name: symlink
use_symlink: yes
# Caching update tests
- namespace: cache
name: cache
version: 1.0.0
# Dep with beta version
- namespace: dep_with_beta
name: parent
dependencies:
namespace1.name1: '*'
# non-prerelease is published to test that installing
# the pre-release from SCM doesn't accidentally prefer indirect
# dependencies from Galaxy
- namespace: test_prereleases
name: collection2
version: 1.0.0
- namespace: dev_and_stables_ns
name: dev_and_stables_name
version: 1.2.3-dev0
- namespace: dev_and_stables_ns
name: dev_and_stables_name
version: 1.2.4
- namespace: ns_with_wildcard_dep
name: name_with_wildcard_dep
version: 5.6.7-beta.3
dependencies:
dev_and_stables_ns.dev_and_stables_name: >-
*
- namespace: ns_with_dev_dep
name: name_with_dev_dep
version: 6.7.8
dependencies:
dev_and_stables_ns.dev_and_stables_name: 1.2.3-dev0
- namespace: rc_meta_ns_with_transitive_dev_dep
name: rc_meta_name_with_transitive_dev_dep
version: 2.4.5-rc5
dependencies:
ns_with_dev_dep.name_with_dev_dep: >-
*
- namespace: meta_ns_with_transitive_wildcard_dep
name: meta_name_with_transitive_wildcard_dep
version: 4.5.6
dependencies:
ns_with_wildcard_dep.name_with_wildcard_dep: 5.6.7-beta.3
- namespace: ns_requires_ansible
name: name_requires_ansible
version: 1.0.0
runtime: |-
requires_ansible: "<1.1"
- namespace: ns_requires_ansible
name: dependency
version: 1.0.0
- namespace: ns_requires_ansible
name: dependency
version: 2.0.0
dependencies:
ns_requires_ansible.name_requires_ansible: 1.0.0
- namespace: ns_requires_ansible
name: dependency_incompat
version: 1.0.0
dependencies:
ns_requires_ansible.name_requires_ansible: 1.0.0
- namespace: ns_requires_ansible
name: dependency_incompat
version: 2.0.0
dependencies:
ns_requires_ansible.dependency: 2.0.0 # pinned incompatible version
- namespace: ns_requires_ansible
name: dependency_incompat
version: 3.0.0
dependencies:
ns_requires_ansible.name_requires_ansible: 1.0.0
- namespace: ns_requires_ansible
name: backtracking_error
version: 1.0.0
dependencies:
ns_requires_ansible.dependency_incompat: "*"
@@ -2,9 +2,9 @@
# The first task to interact with pulp needs to wait until it responds appropriately.
- name: Wait for Pulp API
uri:
url: '{{ pulp_api }}/pulp/api/v3/distributions/ansible/ansible/'
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
url: '{{ galaxy_ng_server }}/pulp/api/v3/repositories/ansible/ansible/'
user: '{{ galaxy_user }}'
password: '{{ galaxy_password }}'
force_basic_auth: true
register: this
until: this is successful
@@ -13,13 +13,9 @@
- name: Verify Galaxy NG server
uri:
url: "{{ galaxy_ng_server }}"
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
url: "{{ amanda }}/api/"
force_basic_auth: true
- name: Verify Pulp server
uri:
url: "{{ pulp_server }}"
status_code:
- 404 # endpoint responds without authentication
url: "{{ galaxy_ng_server }}"
@@ -10,6 +10,7 @@ from ....config import (
)
from ....docker_util import (
docker_cp_from,
docker_cp_to,
docker_exec,
)
@@ -33,43 +34,6 @@ from . import (
)
GALAXY_HOST_NAME = 'galaxy-pulp'
SETTINGS = {
'PULP_CONTENT_ORIGIN': f'http://{GALAXY_HOST_NAME}',
'PULP_ANSIBLE_API_HOSTNAME': f'http://{GALAXY_HOST_NAME}',
'PULP_GALAXY_API_PATH_PREFIX': '/api/galaxy/',
# These paths are unique to the container image which has an nginx location for /pulp/content to route
# requests to the content backend
'PULP_ANSIBLE_CONTENT_HOSTNAME': f'http://{GALAXY_HOST_NAME}/pulp/content/api/galaxy/v3/artifacts/collections/',
'PULP_CONTENT_PATH_PREFIX': '/pulp/content/api/galaxy/v3/artifacts/collections/',
'PULP_GALAXY_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'django.contrib.auth.backends.ModelBackend',
],
# This should probably be false see https://issues.redhat.com/browse/AAH-2328
'PULP_GALAXY_REQUIRE_CONTENT_APPROVAL': 'true',
'PULP_GALAXY_DEPLOYMENT_MODE': 'standalone',
'PULP_GALAXY_AUTO_SIGN_COLLECTIONS': 'false',
'PULP_GALAXY_COLLECTION_SIGNING_SERVICE': 'ansible-default',
'PULP_RH_ENTITLEMENT_REQUIRED': 'insights',
'PULP_TOKEN_AUTH_DISABLED': 'false',
'PULP_TOKEN_SERVER': f'http://{GALAXY_HOST_NAME}/token/',
'PULP_TOKEN_SIGNATURE_ALGORITHM': 'ES256',
'PULP_PUBLIC_KEY_PATH': '/src/galaxy_ng/dev/common/container_auth_public_key.pem',
'PULP_PRIVATE_KEY_PATH': '/src/galaxy_ng/dev/common/container_auth_private_key.pem',
'PULP_ANALYTICS': 'false',
'PULP_GALAXY_ENABLE_UNAUTHENTICATED_COLLECTION_ACCESS': 'true',
'PULP_GALAXY_ENABLE_UNAUTHENTICATED_COLLECTION_DOWNLOAD': 'true',
'PULP_GALAXY_ENABLE_LEGACY_ROLES': 'true',
'PULP_GALAXY_FEATURE_FLAGS__execution_environments': 'false',
'PULP_SOCIAL_AUTH_LOGIN_REDIRECT_URL': '/',
'PULP_GALAXY_FEATURE_FLAGS__ai_deny_index': 'true',
'PULP_DEFAULT_ADMIN_PASSWORD': 'password'
}
GALAXY_IMPORTER = b"""
[galaxy-importer]
ansible_local_tmp=~/.ansible/tmp
@@ -90,49 +54,86 @@ run_flake8=false
class GalaxyProvider(CloudProvider):
"""
Galaxy plugin. Sets up pulp (ansible-galaxy) servers for tests.
The pulp source itself resides at: https://github.com/pulp/pulp-oci-images
Galaxy plugin. Sets up ansible-galaxy servers for tests.
"""
def __init__(self, args: IntegrationConfig) -> None:
super().__init__(args)
self.image = os.environ.get(
'ANSIBLE_PULP_CONTAINER',
'quay.io/pulp/galaxy:4.7.1'
)
self.uses_docker = True
self.galaxy_image = os.getenv(
'ANSIBLE_GALAXY_CONTAINER',
'ghcr.io/ansible/galaxy-ng-test-container:26.03.0'
)
self.postgres_image = os.getenv(
'ANSIBLE_POSTGRES_CONTAINER',
'public.ecr.aws/docker/library/postgres:13'
)
self.amanda_image = os.getenv(
'ANSIBLE_AMANDA_CONTAINER',
'ghcr.io/sivel/amanda@sha256:f704fe6f062b8ada59ae6553a70d2175295d068d56f544875980581b7df9c16d'
)
def setup(self) -> None:
"""Setup cloud resource before delegation and reg cleanup callback."""
super().setup()
with tempfile.NamedTemporaryFile(mode='w+') as env_fd:
settings = '\n'.join(
f'{key}={value}' for key, value in SETTINGS.items()
)
env_fd.write(settings)
env_fd.flush()
display.info(f'>>> galaxy_ng Configuration\n{settings}', verbosity=3)
descriptor = run_support_container(
# This container is created separately from the actual galaxy container due to
# needing it created for postgres, but the galaxy container has a dependency on knowing the postgres
# container id
gdata = run_support_container(self.args, self.platform, self.galaxy_image, 'galaxy-data', [0], start=False)
if not gdata:
return
amanda = run_support_container(
self.args,
self.platform,
self.amanda_image,
'amanda',
[8001],
aliases=['amanda'],
options=[
'--volumes-from', gdata.container_id,
],
cmd=['-port', '8001', '-publish'],
)
if not amanda:
return
postgres = run_support_container(
self.args,
self.platform,
self.postgres_image,
'galaxy-postgres',
[5432],
aliases=['postgres'],
options=[
'--volumes-from', gdata.container_id,
],
)
if not postgres:
return
with tempfile.TemporaryDirectory() as tmpdir:
docker_cp_from(self.args, gdata.container_id, '/galaxy_ng.env', tmpdir)
galaxy_ng = run_support_container(
self.args,
self.platform,
self.image,
GALAXY_HOST_NAME,
[
80,
],
aliases=[
GALAXY_HOST_NAME,
],
self.galaxy_image,
'galaxy_ng',
[8000, 24816],
aliases=['galaxy'],
start=True,
options=[
'--env-file', env_fd.name,
'--env-file', os.path.join(tmpdir, 'galaxy_ng.env'),
'--add-host', f'postgres:{postgres.details.container_ip}',
],
cmd=[
'/bin/sh', '-c',
'(start-api &); (start-content-app &); start-worker;'
],
)
if not descriptor:
if not galaxy_ng:
return
injected_files = [
@@ -143,13 +144,14 @@ class GalaxyProvider(CloudProvider):
temp_fd.write(content)
temp_fd.flush()
display.info(f'>>> {friendly_name} Configuration\n{to_text(content)}', verbosity=3)
docker_exec(self.args, descriptor.container_id, ['mkdir', '-p', os.path.dirname(path)], True)
docker_cp_to(self.args, descriptor.container_id, temp_fd.name, path)
docker_exec(self.args, descriptor.container_id, ['chown', 'pulp:pulp', path], True)
docker_exec(self.args, galaxy_ng.container_id, ['mkdir', '-p', os.path.dirname(path)], True, options=['-u', 'root'])
docker_cp_to(self.args, galaxy_ng.container_id, temp_fd.name, path)
docker_exec(self.args, galaxy_ng.container_id, ['chown', 'galaxy:galaxy', path], True, options=['-u', 'root'])
self._set_cloud_config('PULP_HOST', GALAXY_HOST_NAME)
self._set_cloud_config('PULP_USER', 'admin')
self._set_cloud_config('PULP_PASSWORD', 'password')
self._set_cloud_config('GALAXY_HOST', 'galaxy')
self._set_cloud_config('GALAXY_USER', 'admin')
self._set_cloud_config('GALAXY_PASSWORD', 'admin')
self._set_cloud_config('AMANDA_HOST', 'amanda')
class GalaxyEnvironment(CloudEnvironment):
@@ -157,22 +159,22 @@ class GalaxyEnvironment(CloudEnvironment):
def get_environment_config(self) -> CloudEnvironmentConfig:
"""Return environment configuration for use in the test environment after delegation."""
pulp_user = str(self._get_cloud_config('PULP_USER'))
pulp_password = str(self._get_cloud_config('PULP_PASSWORD'))
pulp_host = self._get_cloud_config('PULP_HOST')
galaxy_user = str(self._get_cloud_config('GALAXY_USER'))
galaxy_password = str(self._get_cloud_config('GALAXY_PASSWORD'))
galaxy_host = self._get_cloud_config('GALAXY_HOST')
amanda_host = self._get_cloud_config('AMANDA_HOST')
return CloudEnvironmentConfig(
ansible_vars=dict(
pulp_user=pulp_user,
pulp_password=pulp_password,
pulp_api=f'http://{pulp_host}',
pulp_server=f'http://{pulp_host}/pulp_ansible/galaxy/',
galaxy_ng_server=f'http://{pulp_host}/api/galaxy/',
galaxy_user=galaxy_user,
galaxy_password=galaxy_password,
galaxy_ng_server=f'http://{galaxy_host}:8000/api/galaxy',
amanda=f'http://{amanda_host}:8001',
),
env_vars=dict(
PULP_USER=pulp_user,
PULP_PASSWORD=pulp_password,
PULP_SERVER=f'http://{pulp_host}/pulp_ansible/galaxy/api/',
GALAXY_NG_SERVER=f'http://{pulp_host}/api/galaxy/',
GALAXY_USER=galaxy_user,
GALAXY_PASSWORD=galaxy_password,
GALAXY_NG_SERVER=f'http://{galaxy_host}:8000/api/galaxy',
AMANDA=f'http://{amanda_host}:8001',
),
)
@@ -451,6 +451,9 @@ def create_container_database(args: EnvironmentConfig) -> ContainerDatabase:
managed: dict[str, dict[str, ContainerAccess]] = {}
for name, container in support_containers.items():
if container.details is None:
# data containers will not be started, and will be missing details
continue
if container.details.published_ports:
if require_docker().command == 'podman':
host_ip_func = get_podman_host_ip
+9 -10
View File
@@ -658,15 +658,15 @@ def __docker_pull(args: CommonConfig, image: str) -> None:
"""Internal implementation for docker_pull. Do not call directly."""
if '@' not in image and ':' not in image:
display.info('Skipping pull of image without tag or digest: %s' % image, verbosity=2)
inspect = docker_image_inspect(args, image)
elif inspect := docker_image_inspect(args, image, always=True):
docker_image_inspect(args, image)
elif docker_image_inspect(args, image, always=True):
display.info('Skipping pull of existing image: %s' % image, verbosity=2)
else:
for _iteration in range(1, 10):
try:
docker_command(args, ['pull', image], capture=False)
if (inspect := docker_image_inspect(args, image)) or args.explain:
if docker_image_inspect(args, image) or args.explain:
break
display.warning(f'Image "{image}" not found after pull completed. Waiting a few seconds before trying again.')
@@ -676,18 +676,17 @@ def __docker_pull(args: CommonConfig, image: str) -> None:
else:
raise ApplicationError(f'Failed to pull container image "{image}".')
if inspect and inspect.volumes:
display.warning(f'Image "{image}" contains {len(inspect.volumes)} volume(s): {", ".join(sorted(inspect.volumes))}\n'
'This may result in leaking anonymous volumes. It may also prevent the image from working on some hosts or container engines.\n'
'The image should be rebuilt without the use of the VOLUME instruction.',
unique=True)
def docker_cp_to(args: CommonConfig, container_id: str, src: str, dst: str) -> None:
"""Copy a file to the specified container."""
docker_command(args, ['cp', src, '%s:%s' % (container_id, dst)], capture=True)
def docker_cp_from(args: CommonConfig, container_id: str, src: str, dst: str) -> None:
"""Copy a file from the specified container."""
docker_command(args, ['cp', '%s:%s' % (container_id, src), dst], capture=True)
def docker_create(
args: CommonConfig,
image: str,
@@ -726,7 +725,7 @@ def docker_rm(args: CommonConfig, container_id: str) -> None:
# Podman supports `--time` for stop. The `--timeout` option was deprecated in 1.9.0.
# Both Docker and Podman support the `-t` option for stop.
docker_command(args, ['stop', '-t', '0', container_id], capture=True)
docker_command(args, ['rm', container_id], capture=True)
docker_command(args, ['rm', '-v', container_id], capture=True)
except SubprocessError as ex:
# Both Podman and Docker report an error if the container does not exist.
# The error messages contain the same "no such container" string, differing only in capitalization.