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

Reference PoC #498

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
24 changes: 24 additions & 0 deletions model/_init.cf
Original file line number Diff line number Diff line change
Expand Up @@ -641,3 +641,27 @@ ResourceSet.resources [0:] -- std::Resource
index ResourceSet(name)

implement ResourceSet using std::none


typedef value_reference as string matching std::is_valid_value_reference(self)


entity EnvironmentReference:
""" A reference to an environment variable.

:attr variable_name: The name of the environment variable to reference.
:attr value: The value from the environment
"""
string variable_name
string value
end


implementation env_ref for EnvironmentReference:
self.value = std::get_value_reference_attribute(
reference=std::create_environment_reference(self.variable_name),
name="value",
)
end

implement EnvironmentReference using env_ref
54 changes: 52 additions & 2 deletions plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
from copy import copy
from itertools import chain
from operator import attrgetter
from typing import Any, Optional, Tuple
from typing import Any, Optional, Tuple, Literal
from dataclasses import dataclass

import jinja2
import pydantic
Expand All @@ -45,7 +46,7 @@
from inmanta.export import dependency_manager, unknown_parameters
from inmanta.module import Project
from inmanta.plugins import Context, deprecated, plugin

from inmanta import references

@plugin
def unique_file(
Expand Down Expand Up @@ -1264,3 +1265,52 @@ def ip_address_from_interface(
:param ip_interface: The interface from where we will extract the ip address
"""
return str(ipaddress.ip_interface(ip_interface).ip)


@plugin
def is_valid_value_reference(value: "string") -> "bool":
""" Validate if value is a valid value reference
"""
return isinstance(value, references.ValueReference)


@plugin
def get_value_reference_attribute(reference: "std::value_reference", name: "string") -> "string":
""" Reference an attribute in a secret reference
"""
# TODO: we can add type checking here so that we can only reference attribute that exist on the
# secret that reference returns
return references.ValueReferenceAttributeString.create(reference, name)


@dataclass
class EnvironmentValue:
value: str


class EnvironmentVariableReference(references.ValueReferenceModel):
# TODO: this one should also refer to EnvironmentVariable so that plugins like get_value_reference_attribute can do more type checking
reference_type: Literal["std::EnvironmentReference"] = "std::EnvironmentReference"
variable_name: str
""" The variable name to fetch the value from
"""

class EnvironmentVariableReferenceResolver(references.Resolver[EnvironmentVariableReference, EnvironmentValue]):
def __init__(self, reference: EnvironmentVariableReference) -> None:
self._reference: EnvironmentVariableReference = reference

def fetch(self) -> EnvironmentValue:
""" Fetch the value
"""
return EnvironmentValue(value=os.getenv(self._reference.variable_name))


@plugin
def create_environment_reference(variable_name: "string") -> "std::value_reference":
"""Create a reference to an enviroment variable
"""
return references.ValueReference.create(
reference=EnvironmentVariableReference(
variable_name=variable_name,
)
)
78 changes: 33 additions & 45 deletions plugins/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,29 +67,6 @@ def generate_content(content_list, seperator):
return seperator.join([x[1] for x in sort_list]) + seperator


def store_file(exporter, obj):
content = obj.content
if isinstance(content, Unknown):
return content

if "FileMarker" in content.__class__.__name__:
with open(content.filename, "rb") as fd:
content = fd.read()

if len(obj.prefix_content) > 0:
content = (
generate_content(obj.prefix_content, obj.content_seperator)
+ obj.content_seperator
+ content
)
if len(obj.suffix_content) > 0:
content += obj.content_seperator + generate_content(
obj.suffix_content, obj.content_seperator
)

return exporter.upload_file(content)


@resource("std::Service", agent="host.name", id_attribute="name")
class Service(Resource):
"""
Expand All @@ -105,8 +82,34 @@ class File(PurgeableResource):
A file on a filesystem
"""

fields = ("path", "owner", "hash", "group", "permissions", "reload")
map = {"hash": store_file, "permissions": lambda y, x: int(x.mode)}
fields = ("path", "owner", "content", "group", "permissions", "reload")

@staticmethod
def get_permissions(_, instance) -> int:
return int(instance.mode)

@staticmethod
def get_hash(exporter, obj):
content = obj.content
if isinstance(content, Unknown):
return content

if "FileMarker" in content.__class__.__name__:
with open(content.filename, "rb") as fd:
content = fd.read()

if len(obj.prefix_content) > 0:
content = (
generate_content(obj.prefix_content, obj.content_seperator)
+ obj.content_seperator
+ content
)
if len(obj.suffix_content) > 0:
content += obj.content_seperator + generate_content(
obj.suffix_content, obj.content_seperator
)

return exporter.upload_file(content)


@resource("std::Directory", agent="host.name", id_attribute="path")
Expand Down Expand Up @@ -192,13 +195,8 @@ def read_resource(self, ctx: HandlerContext, resource: PurgeableResource) -> Non
if not self._io.file_exists(resource.path):
raise ResourcePurged()

resource.hash = self._io.hash_file(resource.path)

# upload the previous version for back up and for generating a diff!
content = self._io.read_binary(resource.path)

if not self.stat_file(resource.hash):
self.upload_file(resource.hash, content)
resource.content = self._io.read_binary(resource.path).decode()

for key, value in self._io.file_stat(resource.path).items():
setattr(resource, key, value)
Expand All @@ -209,13 +207,7 @@ def create_resource(self, ctx: HandlerContext, resource: PurgeableResource) -> N
f"Cannot create file {resource.path}, because it already exists."
)

data = self.get_file(resource.hash)
if hash_file(data) != resource.hash:
raise Exception(
"File hash was %s expected %s" % (resource.hash, hash_file(data))
)

self._io.put(resource.path, data)
self._io.put(resource.path, resource.content.encode())
self._io.chmod(resource.path, str(resource.permissions))
self._io.chown(resource.path, resource.owner, resource.group)
ctx.set_created()
Expand All @@ -233,19 +225,15 @@ def update_resource(
f"Cannot update file {resource.path} because it doesn't exist"
)

if "hash" in changes:
data = self.get_file(resource.hash)
if hash_file(data) != resource.hash:
raise Exception(
"File hash was %s expected %s" % (resource.hash, hash_file(data))
)
self._io.put(resource.path, data)
if "content" in changes:
self._io.put(resource.path, resource.content.encode())

if "permissions" in changes:
self._io.chmod(resource.path, str(resource.permissions))

if "owner" in changes or "group" in changes:
self._io.chown(resource.path, resource.owner, resource.group)

ctx.set_updated()


Expand Down
23 changes: 23 additions & 0 deletions tests/test_references.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

import os

def test_reference(project) -> None:
""" Test the use of reference
"""
project.compile("""
env_ref = std::EnvironmentReference(variable_name="USER")

file = std::ConfigFile(
host=std::Host(name="test", os=std::linux),
owner="bart",
group="bart",
path="/tmp/reference",
content=env_ref.value
)
""")

project.deploy_all().assert_all()


with open("/tmp/reference", "r") as fd:
assert fd.read() == os.getenv("USER")