pulumi-extra 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pulumi_extra/__init__.py +28 -0
- pulumi_extra/contrib/__init__.py +0 -0
- pulumi_extra/contrib/aws/__init__.py +8 -0
- pulumi_extra/contrib/aws/autotag.py +77 -0
- pulumi_extra/contrib/aws/common.py +4 -0
- pulumi_extra/contrib/aws/policies/__init__.py +4 -0
- pulumi_extra/contrib/aws/policies/require_description.py +60 -0
- pulumi_extra/contrib/aws/policies/require_tags.py +42 -0
- pulumi_extra/contrib/gcp/__init__.py +8 -0
- pulumi_extra/contrib/gcp/autolabel.py +75 -0
- pulumi_extra/contrib/gcp/common.py +4 -0
- pulumi_extra/contrib/gcp/policies/__init__.py +4 -0
- pulumi_extra/contrib/gcp/policies/require_description.py +60 -0
- pulumi_extra/contrib/gcp/policies/require_labels.py +42 -0
- pulumi_extra/errors.py +3 -0
- pulumi_extra/output.py +60 -0
- pulumi_extra/py.typed +0 -0
- pulumi_extra/resource_.py +73 -0
- pulumi_extra/stack_reference.py +119 -0
- pulumi_extra/transforms/__init__.py +13 -0
- pulumi_extra/transforms/invoke.py +86 -0
- pulumi_extra/transforms/resource_.py +84 -0
- pulumi_extra/transforms/runtime.py +23 -0
- pulumi_extra-0.1.0.dist-info/METADATA +38 -0
- pulumi_extra-0.1.0.dist-info/RECORD +27 -0
- pulumi_extra-0.1.0.dist-info/WHEEL +4 -0
- pulumi_extra-0.1.0.dist-info/licenses/LICENSE +21 -0
pulumi_extra/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .output import render_template
|
|
2
|
+
from .resource_ import get_resource_cls, resource_has_attribute
|
|
3
|
+
from .stack_reference import get_stack_outputs, get_stack_reference, re_export
|
|
4
|
+
from .transforms import (
|
|
5
|
+
override_default_provider,
|
|
6
|
+
override_invoke,
|
|
7
|
+
override_invoke_defaults,
|
|
8
|
+
override_invoke_options,
|
|
9
|
+
override_resource,
|
|
10
|
+
override_resource_defaults,
|
|
11
|
+
override_resource_options,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = (
|
|
15
|
+
"get_resource_cls",
|
|
16
|
+
"get_stack_outputs",
|
|
17
|
+
"get_stack_reference",
|
|
18
|
+
"override_default_provider",
|
|
19
|
+
"override_invoke",
|
|
20
|
+
"override_invoke_defaults",
|
|
21
|
+
"override_invoke_options",
|
|
22
|
+
"override_resource",
|
|
23
|
+
"override_resource_defaults",
|
|
24
|
+
"override_resource_options",
|
|
25
|
+
"re_export",
|
|
26
|
+
"render_template",
|
|
27
|
+
"resource_has_attribute",
|
|
28
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import pulumi
|
|
7
|
+
import pulumi_aws # noqa: F401; Required to detect resources otherwise `is_taggable` will always return `False`
|
|
8
|
+
|
|
9
|
+
from pulumi_extra import resource_has_attribute
|
|
10
|
+
|
|
11
|
+
from .common import is_aws_resource
|
|
12
|
+
|
|
13
|
+
_NOT_TAGGABLE_RESOURCES: set[str] = {
|
|
14
|
+
"aws:autoscaling/group:Group",
|
|
15
|
+
"aws:devopsguru/resourceCollection:ResourceCollection",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def register_auto_tagging(
|
|
20
|
+
*,
|
|
21
|
+
exclude: set[str] | None = None,
|
|
22
|
+
extra: dict[str, str] | None = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Register a Pulumi stack transform that automatically tags resources.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
exclude: Resources to exclude from tagging.
|
|
28
|
+
extra: Extra tags to add.
|
|
29
|
+
"""
|
|
30
|
+
tags = {}
|
|
31
|
+
extra = extra or {}
|
|
32
|
+
exclude = exclude or set()
|
|
33
|
+
|
|
34
|
+
# Pulumi tags
|
|
35
|
+
org = pulumi.get_organization()
|
|
36
|
+
project = pulumi.get_project()
|
|
37
|
+
stack = pulumi.get_stack()
|
|
38
|
+
tags.update(
|
|
39
|
+
{
|
|
40
|
+
"pulumi:Organization": org,
|
|
41
|
+
"pulumi:Project": project,
|
|
42
|
+
"pulumi:Stack": stack,
|
|
43
|
+
"Managed-By": "Pulumi",
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
tags.update(extra)
|
|
47
|
+
|
|
48
|
+
def transform(
|
|
49
|
+
args: pulumi.ResourceTransformArgs,
|
|
50
|
+
) -> pulumi.ResourceTransformResult | None:
|
|
51
|
+
if args.type_ not in exclude and is_taggable(args.type_):
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
assert isinstance(args.props, dict)
|
|
54
|
+
args.props["tags"] = {
|
|
55
|
+
**tags,
|
|
56
|
+
**(args.props.get("tags", {})),
|
|
57
|
+
}
|
|
58
|
+
return pulumi.ResourceTransformResult(props=args.props, opts=args.opts)
|
|
59
|
+
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
pulumi.runtime.register_resource_transform(transform)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def is_taggable(resource_type: str) -> bool:
|
|
66
|
+
"""Determine if a given AWS resource type is taggable."""
|
|
67
|
+
if not is_aws_resource(resource_type):
|
|
68
|
+
pulumi.log.debug(f"Resource type {resource_type} is not a AWS resource")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
if resource_type in _NOT_TAGGABLE_RESOURCES:
|
|
72
|
+
pulumi.log.info(
|
|
73
|
+
f"Resource type {resource_type} is set not-taggable explicitly",
|
|
74
|
+
)
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
return resource_has_attribute(resource_type, "tags")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
import pulumi_policy as policy
|
|
3
|
+
|
|
4
|
+
from pulumi_extra import resource_has_attribute
|
|
5
|
+
from pulumi_extra.contrib.aws import is_aws_resource, is_taggable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RequireDescription:
|
|
9
|
+
"""Policy validator to require description (or tag if unsupported) on resources."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
*,
|
|
14
|
+
require_tag_if_description_unsupported: bool = False,
|
|
15
|
+
description_tag_key: str = "Description",
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Initialize the policy validator.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
require_tag_if_description_unsupported: Require a tag if description is unsupported.
|
|
21
|
+
Because AWS tags support human-friendly descriptions,
|
|
22
|
+
in most cases, using a tag for description is recommended.
|
|
23
|
+
description_tag_key: The tag key to use for description.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
self.require_tag_if_description_unsupported = require_tag_if_description_unsupported
|
|
27
|
+
self.description_tag_key = description_tag_key
|
|
28
|
+
|
|
29
|
+
def __call__( # noqa: D102
|
|
30
|
+
self,
|
|
31
|
+
args: policy.ResourceValidationArgs,
|
|
32
|
+
report_violation: policy.ReportViolation,
|
|
33
|
+
) -> None:
|
|
34
|
+
if not is_aws_resource(args.resource_type):
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
if resource_has_attribute(args.resource_type, "description") and args.props.get("description") is None:
|
|
38
|
+
report_violation(
|
|
39
|
+
f"Resource '{args.urn}' is missing required description",
|
|
40
|
+
None,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if self.require_tag_if_description_unsupported: # noqa: SIM102
|
|
44
|
+
if is_taggable(args.resource_type):
|
|
45
|
+
tags = args.props.get("tags", {})
|
|
46
|
+
if self.description_tag_key not in tags:
|
|
47
|
+
report_violation(
|
|
48
|
+
f"Resource '{args.urn}' is missing required tag '{self.description_tag_key}'",
|
|
49
|
+
None,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
require_description = policy.ResourceValidationPolicy(
|
|
54
|
+
name="aws:require-description",
|
|
55
|
+
description="Require description (or tag if unsupported) on resources",
|
|
56
|
+
config_schema=policy.PolicyConfigSchema(
|
|
57
|
+
properties={},
|
|
58
|
+
),
|
|
59
|
+
validate=RequireDescription(),
|
|
60
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
import pulumi_policy as policy
|
|
3
|
+
|
|
4
|
+
from pulumi_extra.contrib.aws import is_aws_resource, is_taggable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RequireTags:
|
|
8
|
+
"""Policy validator to require specific tags on resources."""
|
|
9
|
+
|
|
10
|
+
def __call__( # noqa: D102
|
|
11
|
+
self,
|
|
12
|
+
args: policy.ResourceValidationArgs,
|
|
13
|
+
report_violation: policy.ReportViolation,
|
|
14
|
+
) -> None:
|
|
15
|
+
config = args.get_config()
|
|
16
|
+
required_tags = config["required-tags"]
|
|
17
|
+
if not required_tags or not is_aws_resource(args.resource_type):
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
if is_taggable(args.resource_type):
|
|
21
|
+
tags = args.props.get("tags", {})
|
|
22
|
+
for rt in required_tags:
|
|
23
|
+
if not tags or rt not in tags:
|
|
24
|
+
report_violation(
|
|
25
|
+
f"Resource '{args.urn}' is missing required tag '{rt}'",
|
|
26
|
+
None,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
require_tags = policy.ResourceValidationPolicy(
|
|
31
|
+
name="aws:require-tags",
|
|
32
|
+
description="Require specific tags on resources",
|
|
33
|
+
config_schema=policy.PolicyConfigSchema(
|
|
34
|
+
properties={
|
|
35
|
+
"required-tags": {
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": {"type": "string"},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
),
|
|
41
|
+
validate=RequireTags(),
|
|
42
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import pulumi
|
|
7
|
+
import pulumi_gcp # noqa: F401; Required to detect resources otherwise `is_labelable` will always return `False`
|
|
8
|
+
|
|
9
|
+
from pulumi_extra import resource_has_attribute
|
|
10
|
+
|
|
11
|
+
from .common import is_gcp_resource
|
|
12
|
+
|
|
13
|
+
_NOT_LABELABLE_RESOURCES: set[str] = set()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def register_auto_labeling(
|
|
17
|
+
*,
|
|
18
|
+
exclude: set[str] | None = None,
|
|
19
|
+
extra: dict[str, str] | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Register a Pulumi stack transform that automatically labels resources.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
exclude: Resources to exclude from labeling.
|
|
25
|
+
extra: Extra labels to add.
|
|
26
|
+
"""
|
|
27
|
+
labels = {}
|
|
28
|
+
extra = extra or {}
|
|
29
|
+
exclude = exclude or set()
|
|
30
|
+
|
|
31
|
+
# Pulumi labels
|
|
32
|
+
# NOTE: Labels transformed because of strict restrictions GCP enforces
|
|
33
|
+
org = pulumi.get_organization()
|
|
34
|
+
project = pulumi.get_project()
|
|
35
|
+
stack = pulumi.get_stack()
|
|
36
|
+
labels.update(
|
|
37
|
+
{
|
|
38
|
+
"pulumi-organization": org,
|
|
39
|
+
"pulumi-project": project.replace(".", "-"),
|
|
40
|
+
"pulumi-stack": stack,
|
|
41
|
+
"managed-by": "pulumi",
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
labels.update(extra)
|
|
45
|
+
|
|
46
|
+
def transform(
|
|
47
|
+
args: pulumi.ResourceTransformArgs,
|
|
48
|
+
) -> pulumi.ResourceTransformResult | None:
|
|
49
|
+
if args.type_ not in exclude and is_labelable(args.type_):
|
|
50
|
+
if TYPE_CHECKING:
|
|
51
|
+
assert isinstance(args.props, dict)
|
|
52
|
+
args.props["labels"] = {
|
|
53
|
+
**labels,
|
|
54
|
+
**(args.props.get("labels", {})),
|
|
55
|
+
}
|
|
56
|
+
return pulumi.ResourceTransformResult(props=args.props, opts=args.opts)
|
|
57
|
+
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
pulumi.runtime.register_resource_transform(transform)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def is_labelable(resource_type: str) -> bool:
|
|
64
|
+
"""Determine if a given GCP resource type is labelable."""
|
|
65
|
+
if not is_gcp_resource(resource_type):
|
|
66
|
+
pulumi.log.debug(f"Resource type {resource_type} is not a GCP resource")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
if resource_type in _NOT_LABELABLE_RESOURCES:
|
|
70
|
+
pulumi.log.info(
|
|
71
|
+
f"Resource type {resource_type} is set not-labelable explicitly",
|
|
72
|
+
)
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
return resource_has_attribute(resource_type, "labels")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
import pulumi_policy as policy
|
|
3
|
+
|
|
4
|
+
from pulumi_extra import resource_has_attribute
|
|
5
|
+
from pulumi_extra.contrib.gcp import is_gcp_resource, is_labelable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RequireDescription:
|
|
9
|
+
"""Policy validator to require description (or label if unsupported) on resources."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
*,
|
|
14
|
+
require_label_if_description_unsupported: bool = False,
|
|
15
|
+
description_label_key: str = "description",
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Initialize the policy validator.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
require_label_if_description_unsupported: Require a label if description is unsupported.
|
|
21
|
+
Because GCP labels does not support human-friendly descriptions,
|
|
22
|
+
in most cases, using a label for description is not recommended.
|
|
23
|
+
description_label_key: The label key to use for description.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
self.require_label_if_description_unsupported = require_label_if_description_unsupported
|
|
27
|
+
self.description_label_key = description_label_key
|
|
28
|
+
|
|
29
|
+
def __call__( # noqa: D102
|
|
30
|
+
self,
|
|
31
|
+
args: policy.ResourceValidationArgs,
|
|
32
|
+
report_violation: policy.ReportViolation,
|
|
33
|
+
) -> None:
|
|
34
|
+
if not is_gcp_resource(args.resource_type):
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
if resource_has_attribute(args.resource_type, "description") and args.props.get("description") is None:
|
|
38
|
+
report_violation(
|
|
39
|
+
f"Resource '{args.urn}' is missing required description",
|
|
40
|
+
None,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if self.require_label_if_description_unsupported: # noqa: SIM102
|
|
44
|
+
if is_labelable(args.resource_type):
|
|
45
|
+
labels = args.props.get("labels", {})
|
|
46
|
+
if self.description_label_key not in labels:
|
|
47
|
+
report_violation(
|
|
48
|
+
f"Resource '{args.urn}' is missing required label '{self.description_label_key}'",
|
|
49
|
+
None,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
require_description = policy.ResourceValidationPolicy(
|
|
54
|
+
name="gcp:require-description",
|
|
55
|
+
description="Require description (or label if unsupported) on resources",
|
|
56
|
+
config_schema=policy.PolicyConfigSchema(
|
|
57
|
+
properties={},
|
|
58
|
+
),
|
|
59
|
+
validate=RequireDescription(),
|
|
60
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
import pulumi_policy as policy
|
|
3
|
+
|
|
4
|
+
from pulumi_extra.contrib.gcp import is_gcp_resource, is_labelable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RequireLabels:
|
|
8
|
+
"""Policy validator to require specific labels on resources."""
|
|
9
|
+
|
|
10
|
+
def __call__( # noqa: D102
|
|
11
|
+
self,
|
|
12
|
+
args: policy.ResourceValidationArgs,
|
|
13
|
+
report_violation: policy.ReportViolation,
|
|
14
|
+
) -> None:
|
|
15
|
+
config = args.get_config()
|
|
16
|
+
required_labels = config["required-labels"]
|
|
17
|
+
if not required_labels or not is_gcp_resource(args.resource_type):
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
if is_labelable(args.resource_type):
|
|
21
|
+
labels = args.props.get("labels", {})
|
|
22
|
+
for rl in required_labels:
|
|
23
|
+
if not labels or rl not in labels:
|
|
24
|
+
report_violation(
|
|
25
|
+
f"Resource '{args.urn}' is missing required label '{rl}'",
|
|
26
|
+
None,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
require_labels = policy.ResourceValidationPolicy(
|
|
31
|
+
name="gcp:require-labels",
|
|
32
|
+
description="Require specific labels on resources",
|
|
33
|
+
config_schema=policy.PolicyConfigSchema(
|
|
34
|
+
properties={
|
|
35
|
+
"required-labels": {
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": {"type": "string"},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
),
|
|
41
|
+
validate=RequireLabels(),
|
|
42
|
+
)
|
pulumi_extra/errors.py
ADDED
pulumi_extra/output.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Utils for outputs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, overload
|
|
7
|
+
|
|
8
|
+
import pulumi
|
|
9
|
+
from jinja2 import StrictUndefined, Template
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Mapping
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@overload
|
|
17
|
+
def render_template(
|
|
18
|
+
template: Path | str,
|
|
19
|
+
*,
|
|
20
|
+
context: Mapping[str, Any],
|
|
21
|
+
) -> str: ... # pragma: no cover
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@overload
|
|
25
|
+
def render_template(
|
|
26
|
+
template: Path | str,
|
|
27
|
+
*,
|
|
28
|
+
inputs: Mapping[str, pulumi.Input[Any]],
|
|
29
|
+
) -> pulumi.Output[str]: ... # pragma: no cover
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def render_template(
|
|
33
|
+
template: Path | str,
|
|
34
|
+
*,
|
|
35
|
+
context: Mapping[str, Any] | None = None,
|
|
36
|
+
inputs: Mapping[str, pulumi.Input[Any]] | None = None,
|
|
37
|
+
) -> str | pulumi.Output[str]:
|
|
38
|
+
"""Render a template file with the given context.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
template: The template file or inline template string.
|
|
42
|
+
context: The context to render the template with. Conflicts with inputs.
|
|
43
|
+
inputs: The inputs to render the template with. Conflicts with context.
|
|
44
|
+
"""
|
|
45
|
+
if isinstance(template, Path):
|
|
46
|
+
template = template.read_text()
|
|
47
|
+
|
|
48
|
+
jinja_tpl = Template(template, undefined=StrictUndefined)
|
|
49
|
+
|
|
50
|
+
# Render with Python values.
|
|
51
|
+
if context is not None and inputs is None:
|
|
52
|
+
return jinja_tpl.render(context)
|
|
53
|
+
|
|
54
|
+
# Render with Pulumi inputs.
|
|
55
|
+
if context is None and inputs is not None:
|
|
56
|
+
return pulumi.Output.all(inputs).apply(lambda args: jinja_tpl.render(args[0]))
|
|
57
|
+
|
|
58
|
+
# Only one of context or inputs must be provided.
|
|
59
|
+
msg = "Either context or input must be provided."
|
|
60
|
+
raise ValueError(msg)
|
pulumi_extra/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Utility functions for working with Pulumi resources.
|
|
2
|
+
|
|
3
|
+
References:
|
|
4
|
+
- https://github.com/tlinhart/pulumi-aws-tags
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from functools import cache
|
|
11
|
+
from importlib import import_module
|
|
12
|
+
from inspect import signature
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
import pulumi
|
|
16
|
+
from pulumi.runtime.rpc import _RESOURCE_MODULES
|
|
17
|
+
|
|
18
|
+
from .errors import UnknownResourceTypeError
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Iterator
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@cache
|
|
26
|
+
def resource_has_attribute(resource_type: str, attribute: str) -> bool:
|
|
27
|
+
"""Determine if a given GCP resource type is labelable."""
|
|
28
|
+
cls = get_resource_cls(resource_type)
|
|
29
|
+
if cls is None:
|
|
30
|
+
msg = f"Unable to resolve resource type {resource_type!r}"
|
|
31
|
+
raise UnknownResourceTypeError(msg)
|
|
32
|
+
|
|
33
|
+
sig = signature(cls._internal_init)
|
|
34
|
+
return attribute in sig.parameters
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@cache
|
|
38
|
+
def get_resource_cls(resource_type: str) -> Any | None:
|
|
39
|
+
"""Get the Pulumi resource class for a given resource type.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
resource_type: Resource type to get the class for.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Pulumi resource class if found, otherwise `None`.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
_, resource = next(filter(lambda k: k[0] == resource_type, _get_resources()))
|
|
50
|
+
except StopIteration:
|
|
51
|
+
pulumi.log.debug(f"Resource type {resource_type} not found")
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
module_name, class_name = resource
|
|
55
|
+
module = import_module(module_name)
|
|
56
|
+
return getattr(module, class_name)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _get_resources() -> Iterator[tuple[str, tuple[str, str]]]:
|
|
60
|
+
"""Return Pulumi resource registry.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Iterator of tuple containing resource type and resource class.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
# NOTE: This cannot be cached as the underlying registry (`_RESOURCE_MODULES`) gradually populates
|
|
67
|
+
for modules in _RESOURCE_MODULES.values():
|
|
68
|
+
for module in modules:
|
|
69
|
+
mod_info = module.mod_info # type: ignore[attr-defined]
|
|
70
|
+
fqn, classes = mod_info["fqn"], mod_info["classes"]
|
|
71
|
+
for type_, name in classes.items():
|
|
72
|
+
# e.g. ("gcp:activedirectory/domain:Domain", ("pulumi_gcp.activedirectory", "Domain"))
|
|
73
|
+
yield (type_, (fqn, name))
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Utils for stack references."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import cache
|
|
6
|
+
from itertools import chain
|
|
7
|
+
from typing import Any, overload
|
|
8
|
+
|
|
9
|
+
import pulumi
|
|
10
|
+
from braceexpand import braceexpand
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@cache
|
|
14
|
+
def get_stack_reference(ref: str) -> pulumi.StackReference:
|
|
15
|
+
"""Resolve given stack reference shorthand to fully qualified stack reference.
|
|
16
|
+
|
|
17
|
+
The shorthand can be one of the following:
|
|
18
|
+
|
|
19
|
+
- `"{stack}"`
|
|
20
|
+
|
|
21
|
+
Returns the stack reference for the current project and organization.
|
|
22
|
+
|
|
23
|
+
- `"{project}/{stack}"`
|
|
24
|
+
|
|
25
|
+
Returns the stack reference for the current organization.
|
|
26
|
+
|
|
27
|
+
- `"{organization}/{project}/{stack}"`
|
|
28
|
+
|
|
29
|
+
No change is made to the stack reference.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
fqr = _resolve_stack_ref(ref)
|
|
33
|
+
return pulumi.StackReference(fqr)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_stack_ref(ref: str) -> str:
|
|
37
|
+
components = ref.split("/")
|
|
38
|
+
num_components = len(components)
|
|
39
|
+
if num_components == 1:
|
|
40
|
+
org = pulumi.get_organization()
|
|
41
|
+
project = pulumi.get_project()
|
|
42
|
+
fqr = f"{org}/{project}/{ref}"
|
|
43
|
+
elif num_components == 2: # noqa: PLR2004
|
|
44
|
+
org = pulumi.get_organization()
|
|
45
|
+
fqr = f"{org}/{ref}"
|
|
46
|
+
elif num_components == 3: # noqa: PLR2004
|
|
47
|
+
fqr = ref
|
|
48
|
+
else:
|
|
49
|
+
msg = f"Invalid stack reference: {ref!r}"
|
|
50
|
+
raise ValueError(msg)
|
|
51
|
+
|
|
52
|
+
return fqr
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@overload
|
|
56
|
+
def get_stack_outputs(ref: str) -> pulumi.Output[Any]: ... # pragma: no cover
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@overload
|
|
60
|
+
def get_stack_outputs(*refs: str) -> list[pulumi.Output[Any]]: ... # pragma: no cover
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_stack_outputs( # type: ignore[misc]
|
|
64
|
+
*refs: str,
|
|
65
|
+
) -> pulumi.Output[Any] | list[pulumi.Output[Any]]:
|
|
66
|
+
"""Get outputs from a output reference shorthands. Supports brace expansion.
|
|
67
|
+
|
|
68
|
+
- Single output reference: (`"<stack_ref>:<output_key>"`).
|
|
69
|
+
- Multiple outputs using brace expansion: (`"<stack_ref>:{<output_key_1>,<output_key_2>}"`).
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
*refs: Output references.
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
outputs = _get_stack_outputs(*refs)
|
|
76
|
+
output_values = list(outputs.values())
|
|
77
|
+
if len(output_values) == 1:
|
|
78
|
+
return output_values[0]
|
|
79
|
+
|
|
80
|
+
return output_values
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def re_export(*refs: str) -> None:
|
|
84
|
+
"""Re-export outputs from a output reference shorthands.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
*refs: Output references.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
outputs = _get_stack_outputs(*refs)
|
|
91
|
+
for (_, output_key), output in outputs.items():
|
|
92
|
+
pulumi.export(output_key, output)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _get_stack_outputs(*refs: str) -> dict[tuple[str, str], pulumi.Output[Any]]:
|
|
96
|
+
expand_refs = list(chain.from_iterable(map(braceexpand, refs)))
|
|
97
|
+
pulumi.log.debug(f"Expanded output references ({refs!r}): {expand_refs!r}")
|
|
98
|
+
|
|
99
|
+
fqr: list[tuple] = []
|
|
100
|
+
for ref in expand_refs:
|
|
101
|
+
stack_ref, output_key = _resolve_output_ref(ref)
|
|
102
|
+
fqr.append((stack_ref, output_key))
|
|
103
|
+
|
|
104
|
+
outputs: dict[tuple[str, str], pulumi.Output[Any]] = {}
|
|
105
|
+
for stack_ref, output_key in fqr:
|
|
106
|
+
sr = get_stack_reference(stack_ref)
|
|
107
|
+
outputs[(stack_ref, output_key)] = sr.require_output(output_key)
|
|
108
|
+
|
|
109
|
+
return outputs
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _resolve_output_ref(ref: str) -> tuple[str, str]:
|
|
113
|
+
components = ref.split(":")
|
|
114
|
+
stack_ref, output_key = components
|
|
115
|
+
if not stack_ref or not output_key:
|
|
116
|
+
msg = f"Invalid output reference: {ref!r}"
|
|
117
|
+
raise ValueError(msg)
|
|
118
|
+
|
|
119
|
+
return stack_ref, output_key
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .invoke import override_invoke, override_invoke_defaults, override_invoke_options
|
|
2
|
+
from .resource_ import override_resource, override_resource_defaults, override_resource_options
|
|
3
|
+
from .runtime import override_default_provider
|
|
4
|
+
|
|
5
|
+
__all__ = (
|
|
6
|
+
"override_default_provider",
|
|
7
|
+
"override_invoke",
|
|
8
|
+
"override_invoke_defaults",
|
|
9
|
+
"override_invoke_options",
|
|
10
|
+
"override_resource",
|
|
11
|
+
"override_resource_defaults",
|
|
12
|
+
"override_resource_options",
|
|
13
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Pulumi invoke transforms.
|
|
2
|
+
|
|
3
|
+
Invoke transforms should be register at the runtime level because Pulumi doesn't support
|
|
4
|
+
registering invoke transforms per-invoke basis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from fnmatch import fnmatch
|
|
10
|
+
from itertools import chain
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
12
|
+
|
|
13
|
+
import pulumi
|
|
14
|
+
from braceexpand import braceexpand
|
|
15
|
+
|
|
16
|
+
_Args = dict[str, pulumi.Input[Any]]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def override_invoke(
|
|
20
|
+
*invoke_tokens: str,
|
|
21
|
+
args: _Args | Callable[[_Args], _Args] | None = None,
|
|
22
|
+
opts: pulumi.InvokeOptions | Callable[[pulumi.InvokeOptions], pulumi.InvokeOptions] | None = None,
|
|
23
|
+
) -> pulumi.InvokeTransform:
|
|
24
|
+
"""Pulumi transform factory for invoke tokens (`get_*`).
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
*invoke_tokens: Invoke tokens to match. Supports glob patterns and brace expand.
|
|
28
|
+
args: Invoke arguments to override, or a callable that returns the new arguments from given `args.args` input.
|
|
29
|
+
opts: Invoke options to override, or a callable that returns the new options given `args.opts` input.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
args_ = args
|
|
33
|
+
|
|
34
|
+
def transform(args: pulumi.InvokeTransformArgs) -> pulumi.InvokeTransformResult | None:
|
|
35
|
+
nonlocal args_, opts
|
|
36
|
+
|
|
37
|
+
for it in chain.from_iterable(map(braceexpand, invoke_tokens)):
|
|
38
|
+
if not fnmatch(args.token, it):
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
# Transform invoke arguments
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
assert isinstance(args.args, dict)
|
|
44
|
+
|
|
45
|
+
if callable(args_): # noqa: SIM108
|
|
46
|
+
new_args = args_(args.args)
|
|
47
|
+
else:
|
|
48
|
+
new_args = args.args | args_ if args_ is not None else args.args
|
|
49
|
+
|
|
50
|
+
# Transform invoke options
|
|
51
|
+
new_opts = opts(args.opts) if callable(opts) else (opts or pulumi.InvokeOptions())
|
|
52
|
+
new_opts = pulumi.InvokeOptions.merge(args.opts, new_opts)
|
|
53
|
+
|
|
54
|
+
return pulumi.InvokeTransformResult(args=new_args, opts=new_opts)
|
|
55
|
+
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
return transform
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def override_invoke_defaults(*invoke_tokens: str, defaults: dict[str, Any]) -> pulumi.InvokeTransform:
|
|
62
|
+
"""Pulumi transform factory that provides default arguments to matching invoke tokens.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
*invoke_tokens: Invoke tokens to match.
|
|
66
|
+
defaults: Default arguments.
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
return override_invoke(
|
|
70
|
+
*invoke_tokens,
|
|
71
|
+
args=lambda args: defaults | args,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def override_invoke_options(*invoke_tokens: str, **options: Any) -> pulumi.InvokeTransform:
|
|
76
|
+
"""Pulumi transform factory that overrides the invoke options for matching invoke tokens.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
*invoke_tokens: Invoke tokens to match.
|
|
80
|
+
options: Arguments of `pulumi.InvokeOptions`.
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
return override_invoke(
|
|
84
|
+
*invoke_tokens,
|
|
85
|
+
opts=pulumi.InvokeOptions(**options),
|
|
86
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Pulumi resource transforms."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fnmatch import fnmatch
|
|
6
|
+
from itertools import chain
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
8
|
+
|
|
9
|
+
import pulumi
|
|
10
|
+
from braceexpand import braceexpand
|
|
11
|
+
|
|
12
|
+
_Props = dict[str, pulumi.Input[Any]]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def override_resource(
|
|
16
|
+
*resource_types: str,
|
|
17
|
+
props: _Props | Callable[[_Props], _Props] | None = None,
|
|
18
|
+
opts: pulumi.ResourceOptions | Callable[[pulumi.ResourceOptions], pulumi.ResourceOptions] | None = None,
|
|
19
|
+
) -> pulumi.ResourceTransform:
|
|
20
|
+
"""Pulumi transform factory for resources.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
*resource_types: Resource types to match. Supports glob patterns and brace expand.
|
|
24
|
+
props: Resource properties to override, or a callable that returns the new properties from given `args.props` input.
|
|
25
|
+
opts: Resource options to override, or a callable that returns the new options given `args.opts` input.
|
|
26
|
+
|
|
27
|
+
""" # noqa: E501
|
|
28
|
+
|
|
29
|
+
def transform(args: pulumi.ResourceTransformArgs) -> pulumi.ResourceTransformResult | None:
|
|
30
|
+
nonlocal props, opts
|
|
31
|
+
|
|
32
|
+
for rt in chain.from_iterable(map(braceexpand, resource_types)):
|
|
33
|
+
if not fnmatch(args.type_, rt):
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
# Transform resource properties
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
assert isinstance(args.props, dict)
|
|
39
|
+
|
|
40
|
+
if callable(props):
|
|
41
|
+
new_props = props(args.props)
|
|
42
|
+
else:
|
|
43
|
+
new_props = args.props | props if props is not None else args.props
|
|
44
|
+
|
|
45
|
+
# Transform resource options
|
|
46
|
+
new_opts = opts(args.opts) if callable(opts) else (opts or pulumi.ResourceOptions())
|
|
47
|
+
new_opts = pulumi.ResourceOptions.merge(args.opts, new_opts)
|
|
48
|
+
|
|
49
|
+
return pulumi.ResourceTransformResult(props=new_props, opts=new_opts)
|
|
50
|
+
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
return transform
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def override_resource_defaults(
|
|
57
|
+
*resource_types: str,
|
|
58
|
+
defaults: dict[str, pulumi.Input[Any]],
|
|
59
|
+
) -> pulumi.ResourceTransform:
|
|
60
|
+
"""Pulumi transform factory that provides default properties to matching resource types.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
*resource_types: Resource type to match.
|
|
64
|
+
defaults: Default properties.
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
return override_resource(
|
|
68
|
+
*resource_types,
|
|
69
|
+
props=lambda props: defaults | props,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def override_resource_options(*resource_types: str, **options: Any) -> pulumi.ResourceTransform:
|
|
74
|
+
"""Pulumi transform factory that overrides the resource options for resources of given types.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
*resource_types: Resource types to match.
|
|
78
|
+
options: Arguments of `pulumi.ResourceOptions`.
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
return override_resource(
|
|
82
|
+
*resource_types,
|
|
83
|
+
opts=pulumi.ResourceOptions(**options),
|
|
84
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Runtime-level transforms for Pulumi programs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pulumi
|
|
6
|
+
|
|
7
|
+
from .invoke import override_invoke_options
|
|
8
|
+
from .resource_ import override_resource_options
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def override_default_provider(
|
|
12
|
+
*rt_or_it: str,
|
|
13
|
+
provider: pulumi.ProviderResource,
|
|
14
|
+
) -> None:
|
|
15
|
+
"""Override the default provider for resources and invokes of given types.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
*rt_or_it: Resource types or invoke tokens to match.
|
|
19
|
+
provider: Provider to override.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
pulumi.runtime.register_resource_transform(override_resource_options(*rt_or_it, provider=provider))
|
|
23
|
+
pulumi.runtime.register_invoke_transform(override_invoke_options(*rt_or_it, provider=provider))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pulumi-extra
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Extra Pulumi utils and resources.
|
|
5
|
+
Author-email: Yuchan Lee <lasuillard@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: <4.0,>=3.9
|
|
9
|
+
Requires-Dist: braceexpand<1,>=0.1.7
|
|
10
|
+
Requires-Dist: jinja2<4,>=3
|
|
11
|
+
Requires-Dist: pulumi<4,>=3
|
|
12
|
+
Provides-Extra: aws
|
|
13
|
+
Requires-Dist: pulumi-aws>=6; extra == 'aws'
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: mypy~=1.11; extra == 'dev'
|
|
16
|
+
Requires-Dist: ruff~=0.6; extra == 'dev'
|
|
17
|
+
Provides-Extra: gcp
|
|
18
|
+
Requires-Dist: pulumi-gcp>=8; extra == 'gcp'
|
|
19
|
+
Provides-Extra: policy
|
|
20
|
+
Requires-Dist: pulumi-policy<2,>=1; extra == 'policy'
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: coverage~=7.3; extra == 'test'
|
|
23
|
+
Requires-Dist: nox~=2024.10.9; extra == 'test'
|
|
24
|
+
Requires-Dist: pulumi-random>=4.18.0; extra == 'test'
|
|
25
|
+
Requires-Dist: pytest-cov<7,>=5; extra == 'test'
|
|
26
|
+
Requires-Dist: pytest-order>=1.3.0; extra == 'test'
|
|
27
|
+
Requires-Dist: pytest-sugar~=1.0; extra == 'test'
|
|
28
|
+
Requires-Dist: pytest~=8.0; extra == 'test'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# pulumi-extra
|
|
32
|
+
|
|
33
|
+
[](https://opensource.org/licenses/MIT)
|
|
34
|
+
[](https://github.com/lasuillard/pulumi-extra/actions/workflows/ci.yaml)
|
|
35
|
+
[](https://codecov.io/gh/lasuillard/pulumi-extra)
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
Extra Pulumi utils and resources.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
pulumi_extra/__init__.py,sha256=GOzVO3J_Xbe_-FDIT6LjFvdSExsTziqm8-efyaXwVJI,776
|
|
2
|
+
pulumi_extra/errors.py,sha256=S6F6CMnj8BrQZqCYZl4mMMWN-VqC7KRNHG-5BldAs_A,119
|
|
3
|
+
pulumi_extra/output.py,sha256=jJBWi98AIWL87y1J5MMUHjXXK-_Q1gspmveVCi9IQl0,1620
|
|
4
|
+
pulumi_extra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
pulumi_extra/resource_.py,sha256=Nexd9h5dEeJKJqb0w_3lH7NmYSmx7Twl9_ta03j8UDI,2194
|
|
6
|
+
pulumi_extra/stack_reference.py,sha256=66S4aIQyx5HpISaHqT_jsNKNNLAggT5Y5Y2B4n4Smc4,3291
|
|
7
|
+
pulumi_extra/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
pulumi_extra/contrib/aws/__init__.py,sha256=atpnUqZUL6C8t20LHK3EgKhPGSU8GlcuRPBpxddloAw,178
|
|
9
|
+
pulumi_extra/contrib/aws/autotag.py,sha256=Y5cwrzRGGrgRHELKXJPvxthRgOnrEeHQEhCCXRi5Ebw,2185
|
|
10
|
+
pulumi_extra/contrib/aws/common.py,sha256=93wyc8KNvgJdmj_rRG2zUa3OTFFDCcu_S-wLosggUs0,171
|
|
11
|
+
pulumi_extra/contrib/aws/policies/__init__.py,sha256=UgZuL2S8sHlK7ULm_OZpgnzOodPejgV50AYCs7GqPPM,143
|
|
12
|
+
pulumi_extra/contrib/aws/policies/require_description.py,sha256=uqqTbYZeOeQNy8j4GjcQqD_nzKFfocAO3l1BRUClhQ8,2160
|
|
13
|
+
pulumi_extra/contrib/aws/policies/require_tags.py,sha256=JZXFvgkkxcNzNGcY4qDMJ6eH-pJAQUjbm5EHFULrx5c,1247
|
|
14
|
+
pulumi_extra/contrib/gcp/__init__.py,sha256=mQghe26s_H0ni0m85wi6LCgk7c8F95bR1ixphP90III,184
|
|
15
|
+
pulumi_extra/contrib/gcp/autolabel.py,sha256=LhS3yHku8P8TRhR9cWWnMTTwvU_JXgOL9PQhmtF7N3U,2214
|
|
16
|
+
pulumi_extra/contrib/gcp/common.py,sha256=hsWt6f9y7GHrEMFNZeqXCT7Gp_deNHJrtNMLrQ7fZfo,171
|
|
17
|
+
pulumi_extra/contrib/gcp/policies/__init__.py,sha256=1iXvLVzQCuOtXl1VMPnq33XlWxo2WTIcJsbaJl6qQuI,149
|
|
18
|
+
pulumi_extra/contrib/gcp/policies/require_description.py,sha256=0vfC0ZPFXel1Pp0JIhBon_lteuZCodIoZsIDfbTaOZ8,2217
|
|
19
|
+
pulumi_extra/contrib/gcp/policies/require_labels.py,sha256=fTjjOGH2YcoLJOYxle3dH7vq9lva8hIrJyvpG12qtm0,1281
|
|
20
|
+
pulumi_extra/transforms/__init__.py,sha256=24I3Uta0nTSGPEuO7udHHu1pZdNdcbwnwV0sn1JhCxE,456
|
|
21
|
+
pulumi_extra/transforms/invoke.py,sha256=IcH3TUFEtGjsRCoF_3rtrvrACGqr8FIWqIlTPlMtS0Q,2778
|
|
22
|
+
pulumi_extra/transforms/resource_.py,sha256=K6HtYgs8swmV2FpYT42H-u9ctpHOwR8ls8hehneuHe8,2716
|
|
23
|
+
pulumi_extra/transforms/runtime.py,sha256=VUFwIcq3BVTKq04v9_54F5xSwmhW_yhXaXmYAS9_b_I,704
|
|
24
|
+
pulumi_extra-0.1.0.dist-info/METADATA,sha256=6BKKQnh67D5iUFGDF1IdI0z_uY_cE0muzMvhPRYK8RE,1568
|
|
25
|
+
pulumi_extra-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
+
pulumi_extra-0.1.0.dist-info/licenses/LICENSE,sha256=Q5GkvYijQ2KTQ-QWhv43ilzCno4ZrzrEuATEQZd9rYo,1067
|
|
27
|
+
pulumi_extra-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Yuchan Lee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|