Skip to content

Commit

Permalink
refactoring centralized metadata propagation enforcement + parent/dep…
Browse files Browse the repository at this point in the history
…ends_on graph
  • Loading branch information
usrbinkat committed Sep 23, 2024
1 parent ca878a1 commit 4dd658c
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 216 deletions.
2 changes: 1 addition & 1 deletion pulumi/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# Default versions URL template
DEFAULT_VERSIONS_URL_TEMPLATE = 'https://raw.githubusercontent.com/ContainerCraft/Kargo/rerefactor/pulumi/'

# Default module enabled settings
# Module enabled defaults: Setting a module to True enables the module by default
DEFAULT_ENABLED_CONFIG = {
"cert_manager": True,
"kubevirt": True,
Expand Down
111 changes: 71 additions & 40 deletions pulumi/core/resource_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from typing import Optional, Dict, Any, List, Callable
from .metadata import get_global_labels, get_global_annotations
from .utils import set_resource_metadata
from .types import NamespaceConfig

def create_namespace(
name: str,
Expand All @@ -15,6 +14,7 @@ def create_namespace(
custom_timeouts: Optional[Dict[str, str]] = None,
opts: Optional[pulumi.ResourceOptions] = None,
k8s_provider: Optional[k8s.Provider] = None,
parent: Optional[pulumi.Resource] = None,
depends_on: Optional[List[pulumi.Resource]] = None,
) -> k8s.core.v1.Namespace:
"""
Expand Down Expand Up @@ -43,6 +43,8 @@ def create_namespace(
custom_timeouts = {}
if depends_on is None:
depends_on = []
if parent is None:
parent = []

global_labels = get_global_labels()
global_annotations = get_global_annotations()
Expand All @@ -64,6 +66,7 @@ def create_namespace(
pulumi.ResourceOptions(
provider=k8s_provider,
depends_on=depends_on,
parent=parent,
custom_timeouts=pulumi.CustomTimeouts(
create=custom_timeouts.get("create", "5m"),
update=custom_timeouts.get("update", "10m"),
Expand All @@ -86,47 +89,62 @@ def create_custom_resource(
k8s_provider: Optional[k8s.Provider] = None,
depends_on: Optional[List[pulumi.Resource]] = None,
) -> k8s.apiextensions.CustomResource:
if opts is None:
opts = pulumi.ResourceOptions()
if depends_on is None:
depends_on = []
"""
Creates a Kubernetes CustomResource with global labels and annotations.
if 'kind' not in args or 'apiVersion' not in args:
raise ValueError("The 'args' dictionary must include 'kind' and 'apiVersion' keys.")
Args:
name (str): The name of the custom resource.
args (Dict[str, Any]): Arguments for creating the custom resource.
opts (Optional[pulumi.ResourceOptions]): Pulumi resource options.
k8s_provider (Optional[k8s.Provider]): Kubernetes provider.
depends_on (Optional[List[pulumi.Resource]]): Resources this custom resource depends on.
global_labels = get_global_labels()
global_annotations = get_global_annotations()
Returns:
k8s.apiextensions.CustomResource: The created CustomResource.
"""
try:
if 'kind' not in args or 'apiVersion' not in args:
raise ValueError("The 'args' dictionary must include 'kind' and 'apiVersion' keys.")

if opts is None:
opts = pulumi.ResourceOptions()
if depends_on is None:
depends_on = []

global_labels = get_global_labels()
global_annotations = get_global_annotations()

def custom_resource_transform(resource_args: pulumi.ResourceTransformationArgs):
props = resource_args.props
if 'metadata' in props:
set_resource_metadata(props['metadata'], global_labels, global_annotations)
return pulumi.ResourceTransformationResult(props, resource_args.opts)

opts = pulumi.ResourceOptions.merge(
opts,
pulumi.ResourceOptions(
provider=k8s_provider,
depends_on=depends_on,
transformations=[custom_resource_transform],
),
)

def custom_resource_transform(resource_args: pulumi.ResourceTransformationArgs):
props = resource_args.props
if 'metadata' in props:
set_resource_metadata(props['metadata'], global_labels, global_annotations)
return pulumi.ResourceTransformationResult(props, resource_args.opts)
# Ensure metadata and spec are included if specified
metadata = args.get('metadata', {})
spec = args.get('spec', {})

opts = pulumi.ResourceOptions.merge(
opts,
pulumi.ResourceOptions(
provider=k8s_provider,
depends_on=depends_on,
transformations=[custom_resource_transform],
),
)
return k8s.apiextensions.CustomResource(
resource_name=name,
api_version=args['apiVersion'],
kind=args['kind'],
metadata=metadata,
spec=spec,
opts=opts,
)

# Extract required fields from args
api_version = args['apiVersion']
kind = args['kind']
metadata = args.get('metadata', None)
spec = args.get('spec', None)

# Corrected constructor call
return k8s.apiextensions.CustomResource(
resource_name=name,
api_version=api_version,
kind=kind,
metadata=metadata,
spec=spec,
opts=opts
)
except Exception as e:
pulumi.log.error(f"Failed to create custom resource '{name}': {e}")
raise

def create_helm_release(
name: str,
Expand Down Expand Up @@ -189,6 +207,19 @@ def create_secret(
k8s_provider: Optional[k8s.Provider] = None,
depends_on: Optional[List[pulumi.Resource]] = None,
) -> k8s.core.v1.Secret:
"""
Creates a Kubernetes Secret with global labels and annotations.
Args:
name (str): The name of the secret.
args (Dict[str, Any]): Arguments for creating the secret.
opts (Optional[pulumi.ResourceOptions]): Pulumi resource options.
k8s_provider (Optional[k8s.Provider]): Kubernetes provider.
depends_on (Optional[List[pulumi.Resource]]): Resources this secret depends on.
Returns:
k8s.core.v1.Secret: The created Secret.
"""
if opts is None:
opts = pulumi.ResourceOptions()
if depends_on is None:
Expand Down Expand Up @@ -279,13 +310,13 @@ def create_meta_objectmeta(
**kwargs,
) -> k8s.meta.v1.ObjectMetaArgs:
"""
Creates an ObjectMetaArgs with global labels and annotations.
Creates a Kubernetes ObjectMetaArgs with global labels and annotations.
Args:
name (str): The resource name.
name (str): The name of the resource.
labels (Optional[Dict[str, str]]): Additional labels to apply.
annotations (Optional[Dict[str, str]]): Additional annotations to apply.
namespace (Optional[str]): Namespace for the resource.
namespace (Optional[str]): The namespace of the resource.
Returns:
k8s.meta.v1.ObjectMetaArgs: The metadata arguments.
Expand Down
132 changes: 115 additions & 17 deletions pulumi/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,23 @@
"""

import re
import os
import tempfile
import pulumi
import pulumi_kubernetes as k8s
from typing import Optional, Dict, Any
from typing import Optional, Dict, Any, List
import requests
import logging
import yaml
from packaging.version import parse as parse_version, InvalidVersion, Version


# Set up basic logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def set_resource_metadata(metadata: Any, global_labels: Dict[str, str], global_annotations: Dict[str, str]):
"""
Updates resource metadata with global labels and annotations.
Args:
metadata (Any): Metadata to update.
global_labels (Dict[str, str]): Global labels to apply.
global_annotations (Dict[str, str]): Global annotations to apply.
"""
if isinstance(metadata, dict):
metadata.setdefault('labels', {}).update(global_labels)
Expand All @@ -42,10 +40,6 @@ def set_resource_metadata(metadata: Any, global_labels: Dict[str, str], global_a
def generate_global_transformations(global_labels: Dict[str, str], global_annotations: Dict[str, str]):
"""
Generates global transformations for resources.
Args:
global_labels (Dict[str, str]): Global labels to apply.
global_annotations (Dict[str, str]): Global annotations to apply.
"""
def global_transform(args: pulumi.ResourceTransformationArgs) -> Optional[pulumi.ResourceTransformationResult]:
props = args.props
Expand All @@ -60,20 +54,22 @@ def global_transform(args: pulumi.ResourceTransformationArgs) -> Optional[pulumi

pulumi.runtime.register_stack_transformation(global_transform)

def get_latest_helm_chart_version(url: str, chart_name: str) -> str:
def get_latest_helm_chart_version(repo_url: str, chart_name: str) -> str:
"""
Fetches the latest stable version of a Helm chart from the given URL.
Fetches the latest stable version of a Helm chart from the given repository URL.
Args:
url (str): The URL of the Helm repository index.
repo_url (str): The base URL of the Helm repository.
chart_name (str): The name of the Helm chart.
Returns:
str: The latest stable version of the chart.
"""
try:
logging.info(f"Fetching URL: {url}")
response = requests.get(url)
index_url = repo_url.rstrip('/') + '/index.yaml'

logging.info(f"Fetching Helm repository index from URL: {index_url}")
response = requests.get(index_url)
response.raise_for_status()

index = yaml.safe_load(response.content)
Expand All @@ -84,14 +80,17 @@ def get_latest_helm_chart_version(url: str, chart_name: str) -> str:
logging.info(f"No stable versions found for chart '{chart_name}'.")
return "Chart not found"
latest_chart = max(stable_versions, key=lambda x: parse_version(x['version']))
return latest_chart['version']
return latest_chart['version'].lstrip('v')
else:
logging.info(f"No chart named '{chart_name}' found in repository.")
return "Chart not found"

except requests.RequestException as e:
logging.error(f"Error fetching data: {e}")
logging.error(f"Error fetching Helm repository index: {e}")
return f"Error fetching data: {e}"
except yaml.YAMLError as e:
logging.error(f"Error parsing Helm repository index YAML: {e}")
return f"Error parsing YAML: {e}"

def is_stable_version(version_str: str) -> bool:
"""
Expand Down Expand Up @@ -123,3 +122,102 @@ def extract_repo_name(remote_url: str) -> str:
if match:
return match.group(1)
return remote_url


# Set up basic logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def wait_for_crds(crd_names: List[str], k8s_provider: k8s.Provider, depends_on: List[pulumi.Resource], parent: pulumi.Resource) -> List[pulumi.Resource]:
"""
Waits for the specified CRDs to be present and ensures dependencies.
Args:
crd_names (List[str]): A list of CRD names.
k8s_provider (k8s.Provider): The Kubernetes provider.
depends_on (List[pulumi.Resource]): A list of dependencies.
parent (pulumi.Resource): The parent resource.
Returns:
List[pulumi.Resource]: The CRD resources or an empty list during preview.
"""
crds = []

for crd_name in crd_names:
try:
crd = k8s.apiextensions.v1.CustomResourceDefinition.get(
resource_name=crd_name,
id=crd_name,
opts=pulumi.ResourceOptions(
provider=k8s_provider,
depends_on=depends_on,
parent=parent,
),
)
crds.append(crd)
except Exception:
pulumi.log.info(f"CRD {crd_name} not found, creating dummy CRD.")
dummy_crd = create_dummy_crd(crd_name, k8s_provider, depends_on, parent)
if dummy_crd:
crds.append(dummy_crd)

return crds

def create_dummy_crd(crd_name: str, k8s_provider: k8s.Provider, depends_on: List[pulumi.Resource], parent: pulumi.Resource) -> Optional[k8s.yaml.ConfigFile]:
"""
Create a dummy CRD definition to use during preview runs.
Args:
crd_name (str): The name of the CRD.
k8s_provider (k8s.Provider): The Kubernetes provider.
depends_on (List[pulumi.Resource]): A list of dependencies.
parent (pulumi.Resource): The parent resource.
Returns:
Optional[k8s.yaml.ConfigFile]: The dummy CRD resource.
"""
parts = crd_name.split('.')
plural = parts[0]
group = '.'.join(parts[1:])
kind = ''.join(word.title() for word in plural.split('_'))

dummy_crd_yaml_template = """
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: {metadata_name}
spec:
group: {group}
names:
plural: {plural}
kind: {kind}
scope: Namespaced
versions:
- name: v1
served: true
storage: true
"""

dummy_crd_yaml = dummy_crd_yaml_template.format(
metadata_name=f"{plural}.{group}",
group=group,
plural=plural,
kind=kind,
)

try:
with tempfile.NamedTemporaryFile(delete=False, mode='w') as temp_file:
temp_file.write(dummy_crd_yaml)
temp_file_path = temp_file.name

dummy_crd = k8s.yaml.ConfigFile(
"dummy-crd-{}".format(crd_name),
file=temp_file_path,
opts=pulumi.ResourceOptions(
parent=parent,
depends_on=depends_on,
provider=k8s_provider,
)
)
return dummy_crd
finally:
os.unlink(temp_file_path)
Loading

0 comments on commit 4dd658c

Please sign in to comment.