Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Converge #173

Merged
merged 1 commit into from
Sep 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lower_bounds_constraints.lock
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pulp-glue==0.20.0
pulp-glue==0.28.0
8 changes: 7 additions & 1 deletion meta/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,11 @@ action_groups:
- status
- task
- x509_cert_guard
requires_ansible: '>=2.15.0,<2.18'
plugin_routing:
modules:
file_repository_content:
deprecation:
removal_version: "0.1.0"
warning_text: "Use pulp.squeezer.file_content instead."
requires_ansible: ">=2.15.0,<2.18"
...
122 changes: 62 additions & 60 deletions plugins/module_utils/pulp_glue.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
from packaging.requirements import SpecifierSet
from pulp_glue.common import __version__ as pulp_glue_version
from pulp_glue.common.context import PulpContext, PulpException, PulpNoWait
from pulp_glue.common.openapi import BasicAuthProvider

GLUE_VERSION_SPEC = ">=0.20.0,<0.27"
if not SpecifierSet(GLUE_VERSION_SPEC).contains(pulp_glue_version):
GLUE_VERSION_SPEC = ">=0.28.0,<0.29.0"
if not SpecifierSet(GLUE_VERSION_SPEC, prereleases=True).contains(pulp_glue_version):
raise ImportError(
f"Installed 'pulp-glue' version '{pulp_glue_version}' is not in '{GLUE_VERSION_SPEC}'."
)
Expand Down Expand Up @@ -86,17 +87,11 @@ def __init__(self, **kwargs):
self.fail_json(msg=missing_required_lib(import_error[0]), exception=import_error[1])

auth_args = {}
if SpecifierSet(">=0.24.0").contains(pulp_glue_version):
if self.params["username"]:
from pulp_glue.common.openapi import BasicAuthProvider

auth_args["auth_provider"] = BasicAuthProvider(
username=self.params["username"],
password=self.params["password"],
)
else:
auth_args["username"] = self.params["username"]
auth_args["password"] = self.params["password"]
if self.params["username"]:
auth_args["auth_provider"] = BasicAuthProvider(
username=self.params["username"],
password=self.params["password"],
)

self.pulp_ctx = PulpSqueezerContext(
api_root="/pulp/",
Expand All @@ -106,22 +101,28 @@ def __init__(self, **kwargs):
key=self.params["user_key"],
validate_certs=self.params["validate_certs"],
refresh_cache=self.params["refresh_api_cache"],
safe_calls_only=self.check_mode,
user_agent=f"Squeezer/{__VERSION__}",
**auth_args,
),
background_tasks=False,
timeout=self.params["timeout"],
fake_mode=self.check_mode, # This sets api_kwargs["safe_calls_only"] for us.
)

def __enter__(self):
self._changed = False
self._results = {}
self._diff_states = []

return self

def __exit__(self, exc_class, exc_value, tb):
if exc_class is None:
if self._diff_states:
self._results["diff"] = {
"before": self._diff_states[0],
"after": self._diff_states[-1],
}
self.exit_json(changed=self._changed, **self._results)
else:
if issubclass(exc_class, (PulpException, PulpNoWait, SqueezerException)):
Expand All @@ -141,6 +142,9 @@ def set_changed(self):
def set_result(self, key, value):
self._results[key] = value

def record_diff_state(self, value):
self._diff_states.append(value)


class PulpEntityAnsibleModule(PulpAnsibleModule):
def __init__(self, context_class, entity_singular, entity_plural, **kwargs):
Expand All @@ -152,6 +156,7 @@ def __init__(self, context_class, entity_singular, entity_plural, **kwargs):
argument_spec.update(kwargs.pop("argument_spec", {}))
super().__init__(argument_spec=argument_spec, **kwargs)
self.state = self.params["state"]

self.context = context_class(self.pulp_ctx)
self.entity_singular = entity_singular
self.entity_plural = entity_plural
Expand All @@ -162,59 +167,56 @@ def represent(self, entity):
for key, value in entity.items()
}

def process(self, natural_key, desired_attributes):
if None not in natural_key.values():
if "pulp_href" in natural_key:
self.context.pulp_href = natural_key["pulp_href"]
else:
self.context.entity = natural_key
try:
entity = self.represent(self.context.entity)
except PulpException:
entity = None
if self.state is None:
pass
elif self.state == "absent":
if entity is not None:
if not self.check_mode:
self.context.delete()
entity = None
self.set_changed()
elif self.state == "present":
entity = self.process_present(entity, natural_key, desired_attributes)
else:
entity = self.process_special(entity, natural_key, desired_attributes)
self.set_result(self.entity_singular, entity)
def process(self, natural_key, desired_attributes, defaults=None):
if self.state is None:
return self.process_info(natural_key, desired_attributes)

if "pulp_href" in natural_key:
self.context.pulp_href = natural_key["pulp_href"]
else:
if None in natural_key.values():
raise SqueezerException("Insufficient information to identify the entity.")
self.context.entity = natural_key

if self.state == "present":
desired_entity = desired_attributes
elif self.state == "absent":
desired_entity = None
else:
if self.state is not None:
raise SqueezerException(f"Invalid state '{self.state}' for entity listing.")
self.set_result(
self.entity_singular,
self.process_special(desired_attributes, defaults=defaults),
)
return
changed, before, after = self.context.converge(desired_entity, defaults=defaults)
if before is not None:
before = self.represent(before)
if after is not None:
after = self.represent(after)
if changed:
self.set_changed()
self.record_diff_state(before)
self.record_diff_state(after)
self.set_result(self.entity_singular, after)

def process_info(self, natural_key, desired_attributes):
if any((value is not None for value in desired_attributes.values())):
raise SqueezerException("Cannot use attributes when querying entities.")
# TODO turn this into a filtering query instead
if None in natural_key.values():
entities = [
self.represent(entity)
for entity in self.context.list(limit=-1, offset=0, parameters={})
]
self.set_result(self.entity_plural, entities)

def process_present(self, entity, natural_key, desired_attributes):
if entity is None:
entity = {**desired_attributes, **natural_key}
if not self.check_mode:
self.context.create(body=entity)
entity = self.context.entity
entity = self.represent(entity)
self.set_changed()
else:
updated_attributes = {k: v for k, v in desired_attributes.items() if entity.get(k) != v}
if updated_attributes:
if not self.check_mode:
self.context.update(body=updated_attributes)
entity = self.context.entity
else:
entity.update(updated_attributes)
entity = self.represent(entity)
self.set_changed()
return entity

def process_special(self, entity, natural_key, desired_attributes):
if "pulp_href" in natural_key:
self.context.pulp_href = natural_key["pulp_href"]
else:
self.context.entity = natural_key
self.set_result(self.entity_singular, self.represent(self.context.entity))

def process_special(self, entity, natural_key, desired_attributes, defaults=None):
raise SqueezerException(f"Invalid state '{self.state}'.")


Expand Down
66 changes: 48 additions & 18 deletions plugins/modules/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,31 +89,56 @@
)

try:
from pulp_glue.core.context import PulpArtifactContext
from pulp_glue.common.context import PulpEntityNotFound
from pulp_glue.core.context import PulpArtifactContext as _PulpArtifactContext

PULP_CLI_IMPORT_ERR = None

# Patch the Context to make converge call upload
# It's a case study at this point. Eventually glue should handle this.
class PulpArtifactContext(_PulpArtifactContext):
def converge(self, desired_attributes, defaults=None):
"""
Converge an entity to have a set of desired attributes.

This will look for the entity, and depending on what it found and what should be, create,
delete or update the entity.

Parameters:
desired_attributes: Dictionary of attributes the entity should have.

Returns:
Tuple of (changed, before, after)
"""
try:
entity = self.entity
except PulpEntityNotFound:
entity = None

if desired_attributes is None:
if entity is not None:
# raise SqueezerException("Artifacts cannot be deleted")
self.delete()
return True, entity, None
else:
if entity is None:
# This is being quite different:
with open(defaults["file"], "rb") as file:
self.upload(
file=file,
chunk_size=defaults["chunk_size"],
sha256=self._entity_lookup["sha256"],
)
return True, None, self.entity
return False, entity, entity

except ImportError:
PULP_CLI_IMPORT_ERR = traceback.format_exc()
PulpArtifactContext = None


class PulpArtifactAnsibleModule(PulpEntityAnsibleModule):
def process_present(self, entity, natural_key, desired_attributes):
if entity is None:
if self.check_mode:
entity = {**desired_attributes, **natural_key}
else:
with open(self.params["file"], "rb") as infile:
self.context.upload(
infile, sha256=natural_key["sha256"], chunk_size=self.params["chunk_size"]
)
entity = self.context.entity
self.set_changed()
return self.represent(entity)


def main():
with PulpArtifactAnsibleModule(
with PulpEntityAnsibleModule(
context_class=PulpArtifactContext,
entity_singular="artifact",
entity_plural="artifacts",
Expand Down Expand Up @@ -144,8 +169,13 @@ def main():
natural_key = {
"sha256": sha256,
}
desired_attributes = {}
defaults = {
"file": module.params["file"],
"chunk_size": module.params["chunk_size"],
}

module.process(natural_key, {})
module.process(natural_key, desired_attributes, defaults=defaults)


if __name__ == "__main__":
Expand Down
58 changes: 52 additions & 6 deletions plugins/modules/file_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
description:
- Relative path of the file content unit
type: str
file:
description:
- A path to a file tobe uploaded as the new content unit.
type: path
chunk_size:
description:
- Chunk size in bytes used to upload the file.
type: int
default: 33554432
repository:
description:
- The repository in which the content should be present or absent.
type: str
extends_documentation_fragment:
- pulp.squeezer.pulp.entity_state
- pulp.squeezer.pulp.glue
Expand Down Expand Up @@ -62,14 +75,19 @@
"""


import os
import traceback

from ansible_collections.pulp.squeezer.plugins.module_utils.pulp_glue import PulpEntityAnsibleModule
from ansible_collections.pulp.squeezer.plugins.module_utils.pulp_glue import (
PulpEntityAnsibleModule,
SqueezerException,
)

try:
from pulp_glue.file.context import PulpFileContentContext
from pulp_glue.file.context import PulpFileContentContext, PulpFileRepositoryContext

PULP_CLI_IMPORT_ERR = None

except ImportError:
PULP_CLI_IMPORT_ERR = traceback.format_exc()
PulpFileContentContext = None
Expand All @@ -84,19 +102,47 @@ def main():
argument_spec={
"sha256": {"aliases": ["digest"]},
"relative_path": {},
"file": {"type": "path"},
"chunk_size": {"type": "int", "default": 33554432},
"repository": {},
},
required_if=[
("state", "present", ["sha256", "relative_path"]),
("state", "absent", ["sha256", "relative_path"]),
("state", "present", ["file", "relative_path", "repository"]),
("state", "absent", ["relative_path", "repository"]),
],
) as module:
sha256 = module.params["sha256"]
if module.params["file"]:
if not os.path.exists(module.params["file"]):
raise SqueezerException("File not found.")
file_sha256 = module.sha256(module.params["file"])
if sha256:
if sha256 != file_sha256:
raise SqueezerException("File checksum mismatch.")
else:
sha256 = file_sha256

if sha256 is None and module.state == "absent":
raise SqueezerException(
"One of 'file' and 'sha256' is required if 'state' is 'absent'."
)

natural_key = {
"sha256": module.params["sha256"],
"sha256": sha256,
"relative_path": module.params["relative_path"],
}
desired_attributes = {}
defaults = {
"file": module.params["file"],
"chunk_size": module.params["chunk_size"],
}

if module.params["repository"]:
module.context.repository_ctx = PulpFileRepositoryContext(
module.pulp_ctx, entity={"name": module.params["repository"]}
)

module.process(natural_key, desired_attributes)
module.process(natural_key, desired_attributes, defaults=defaults)


if __name__ == "__main__":
Expand Down
Loading