Compare commits

..

19 Commits

Author SHA1 Message Date
554d3da175 fix: make Pulumi work again with python 3.12 2024-10-23 12:43:47 +00:00
e2c2f3e0ba Merge latest ci-tools-lib 2024-10-23 12:29:12 +00:00
4dcb378e17 Squashed '.ci/' changes from 47b4da4..06fcff5
06fcff5 feat: improve image cleanup to incl. all tags and repositories

git-subtree-dir: .ci
git-subtree-split: 06fcff501dd9dbff3a97a95176f6d7a8590289a7
2024-10-23 12:29:12 +00:00
01feac5ea5 ci: adjust python version in container 2024-10-23 12:23:11 +00:00
d6f752f5d1 feat: latest Pulumi versions, support Python 3.12 2024-10-23 12:21:13 +00:00
dbaa1412a9 feat: make Cloudbender work with Python 3.12 2024-10-23 12:17:25 +00:00
5ce70dcb0f chore(deps): update all non-major dependencies 2024-10-23 03:01:54 +00:00
5f4758228f fix: only return from temp dir in Pulumi mode 2024-09-12 19:18:11 +00:00
4768de1984 feat: pulumi version bump, minor fixes 2024-09-11 01:11:12 +00:00
b774297ddb Merge pull request 'chore(deps): update all non-major dependencies' (#18) from renovate/all-minor-patch into master
Reviewed-on: #18
2024-09-10 23:23:07 +00:00
a3ddae8ca7 chore(deps): update all non-major dependencies 2024-09-10 03:09:15 +00:00
3d61e7b57c ci: update remaining deps 2024-08-30 12:56:14 +00:00
096e244171 ci: remove custom hack, add support for trivyignore 2024-08-30 12:54:21 +00:00
2314e8a57b Squashed '.ci/' changes from 2c44e4f..47b4da4
47b4da4 feat: add suport for trivyignore file

git-subtree-dir: .ci
git-subtree-split: 47b4da4b18ca0aa2dc21aa196c2f034d78832fd9
2024-08-30 12:53:52 +00:00
ee27ba1774 Merge latest ci-tools-lib 2024-08-30 12:53:52 +00:00
b07da4a40c Merge pull request 'chore(deps): update all non-major dependencies' (#15) from renovate/all-minor-patch into master
Reviewed-on: #15
2024-08-30 11:57:08 +00:00
249afa7cb9 ci: remove hard coded creds used for testing to silence trivy 2024-08-30 11:11:44 +00:00
81add9de29 chore(deps): update all non-major dependencies 2024-08-30 03:07:19 +00:00
a245d88f8c fix: typos 2024-04-23 13:14:29 +00:00
12 changed files with 73 additions and 168 deletions

View File

@ -14,7 +14,7 @@ include .ci/podman.mk
Add subtree to your project: Add subtree to your project:
``` ```
git subtree add --prefix .ci https://git.zero-downtime.net/ZeroDownTime/ci-tools-lib.git main --squash git subtree add --prefix .ci https://git.zero-downtime.net/ZeroDownTime/ci-tools-lib.git master --squash
``` ```

View File

@ -1,13 +1,3 @@
SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
.SILENT: ; # no need for @
.ONESHELL: ; # recipes execute in same shell
.NOTPARALLEL: ; # wait for this target to finish
.EXPORT_ALL_VARIABLES: ; # send all vars to shell
.PHONY: all # All targets are accessible for user
.DEFAULT: help # Running Make will run the help target
# Parse version from latest git semver tag # Parse version from latest git semver tag
GIT_TAG ?= $(shell git describe --tags --match v*.*.* 2>/dev/null || git rev-parse --short HEAD 2>/dev/null) GIT_TAG ?= $(shell git describe --tags --match v*.*.* 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
@ -33,6 +23,13 @@ ifneq ($(TRIVY_REMOTE),)
TRIVY_OPTS ::= --server $(TRIVY_REMOTE) TRIVY_OPTS ::= --server $(TRIVY_REMOTE)
endif endif
.SILENT: ; # no need for @
.ONESHELL: ; # recipes execute in same shell
.NOTPARALLEL: ; # wait for this target to finish
.EXPORT_ALL_VARIABLES: ; # send all vars to shell
.PHONY: all # All targets are accessible for user
.DEFAULT: help # Running Make will run the help target
help: ## Show Help help: ## Show Help
grep -E '^[a-zA-Z_-]+:.*?## .*$$' .ci/podman.mk | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' grep -E '^[a-zA-Z_-]+:.*?## .*$$' .ci/podman.mk | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@ -43,7 +40,7 @@ fmt:: ## auto format source
lint:: ## Lint source lint:: ## Lint source
build: ## Build the app build: ## Build the app
podman build --rm --layers -t $(IMAGE):$(TAG)-$(_ARCH) --build-arg TAG=$(TAG) --build-arg ARCH=$(_ARCH) --platform linux/$(_ARCH) . buildah build --rm --layers -t $(IMAGE):$(TAG)-$(_ARCH) --build-arg TAG=$(TAG) --build-arg ARCH=$(_ARCH) --platform linux/$(_ARCH) .
test:: ## test built artificats test:: ## test built artificats
@ -54,17 +51,16 @@ scan: ## Scan image using trivy
# first tag and push all actual images # first tag and push all actual images
# create new manifest for each tag and add all available TAG-ARCH before pushing # create new manifest for each tag and add all available TAG-ARCH before pushing
push: ecr-login ## push images to registry push: ecr-login ## push images to registry
for t in $(TAG) latest $(EXTRA_TAGS); do for t in $(TAG) latest $(EXTRA_TAGS); do \
echo "Tagging image with $(REGISTRY)/$(IMAGE):$${t}-$(ARCH)" echo "Tagging image with $(REGISTRY)/$(IMAGE):$${t}-$(ARCH)"
podman tag $(IMAGE):$(TAG)-$(_ARCH) $(REGISTRY)/$(IMAGE):$${t}-$(_ARCH) buildah tag $(IMAGE):$(TAG)-$(_ARCH) $(REGISTRY)/$(IMAGE):$${t}-$(_ARCH); \
podman manifest rm $(IMAGE):$$t || true buildah manifest rm $(IMAGE):$$t || true; \
podman manifest create $(IMAGE):$$t buildah manifest create $(IMAGE):$$t; \
for a in $(ALL_ARCHS); do for a in $(ALL_ARCHS); do \
podman image exists $(REGISTRY)/$(IMAGE):$$t-$$a && \ buildah manifest add $(IMAGE):$$t $(REGISTRY)/$(IMAGE):$(TAG)-$$a; \
podman manifest add $(IMAGE):$$t containers-storage:$(REGISTRY)/$(IMAGE):$$t-$$a done; \
done
echo "Pushing manifest $(IMAGE):$$t" echo "Pushing manifest $(IMAGE):$$t"
podman manifest push --all $(IMAGE):$$t docker://$(REGISTRY)/$(IMAGE):$$t buildah manifest push --all $(IMAGE):$$t docker://$(REGISTRY)/$(IMAGE):$$t; \
done done
ecr-login: ## log into AWS ECR public ecr-login: ## log into AWS ECR public
@ -77,15 +73,14 @@ rm-remote-untagged: ## delete all remote untagged and in-dev images, keep 10 tag
clean:: ## clean up source folder clean:: ## clean up source folder
rm-image: rm-image:
for t in $(TAG) latest $(EXTRA_TAGS); do for t in $(TAG) latest $(EXTRA_TAGS); do \
for a in $(ALL_ARCHS); do test -z "$$(podman image ls -q $(IMAGE):$${t}-$(_ARCH))" || podman image rm -f $(IMAGE):$${t}-$(_ARCH); \
podman image exists $(IMAGE):$$t-$$a && podman image rm -f $(IMAGE):$$t-$$a || true test -z "$$(podman image ls -q $(IMAGE):$${t})" || podman image rm -f $(IMAGE):$${t}; \
done
done done
## some useful tasks during development ## some useful tasks during development
ci-pull-upstream: ## pull latest shared .ci subtree ci-pull-upstream: ## pull latest shared .ci subtree
git subtree pull --prefix .ci ssh://git@git.zero-downtime.net/ZeroDownTime/ci-tools-lib.git main --squash -m "Merge latest ci-tools-lib" git subtree pull --prefix .ci ssh://git@git.zero-downtime.net/ZeroDownTime/ci-tools-lib.git master --squash -m "Merge latest ci-tools-lib"
create-repo: ## create new AWS ECR public repository create-repo: ## create new AWS ECR public repository
aws ecr-public create-repository --repository-name $(IMAGE) --region $(REGION) aws ecr-public create-repository --repository-name $(IMAGE) --region $(REGION)

View File

@ -1,8 +1,7 @@
secrets: secrets:
- id: private-key
paths:
- "**/pulumi_aws/glue/connection.py"
- id: gcp-service-account - id: gcp-service-account
paths: paths:
- "**/pulumi_aws/glue/connection.py" - "/venv/lib/python*/site-packages/pulumi_aws/glue/connection.py"
- id: private-key
paths:
- "/venv/lib/python*/site-packages/pulumi_aws/glue/connection.py"

View File

@ -33,9 +33,6 @@ RUN curl -fsSL https://get.pulumi.com/ | sh -s -- --version $(pip show pulumi --
# minimal pulumi # minimal pulumi
RUN cd /root/.pulumi/bin && rm -f *dotnet *yaml *go *java && strip pulumi* || true RUN cd /root/.pulumi/bin && rm -f *dotnet *yaml *go *java && strip pulumi* || true
# Remove AWS keys from docstring to prevent trivy alerts later
RUN sed -i -e 's/AKIA.*//' /venv/lib/python${RUNTIME_VERSION}/site-packages/pulumi_aws/lightsail/bucket_access_key.py
# Now build the final runtime, incl. running rootless containers # Now build the final runtime, incl. running rootless containers
FROM python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} FROM python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION}

2
Jenkinsfile vendored
View File

@ -1,4 +1,4 @@
library identifier: 'zdt-lib@main', retriever: modernSCM( library identifier: 'zdt-lib@master', retriever: modernSCM(
[$class: 'GitSCMSource', [$class: 'GitSCMSource',
remote: 'https://git.zero-downtime.net/ZeroDownTime/ci-tools-lib.git']) remote: 'https://git.zero-downtime.net/ZeroDownTime/ci-tools-lib.git'])

View File

@ -1,4 +1,4 @@
# ![Logo](https://git.zero-downtime.net/ZeroDownTime/CloudBender/media/branch/main/cloudbender.png) CloudBender # ![Logo](https://git.zero-downtime.net/ZeroDownTime/CloudBender/media/branch/master/cloudbender.png) CloudBender
# About # About

View File

@ -55,12 +55,11 @@ def cli(ctx, profile, region, debug, directory):
sys.exit(1) sys.exit(1)
# Only load stackgroups to get profile and region # Only load stackgroups to get profile and region
if ctx.invoked_subcommand in ["wrap", "list_stacks"]: if ctx.invoked_subcommand == "wrap":
cb.read_config(loadStacks=False) cb.read_config(loadStacks=False)
else: else:
cb.read_config() cb.read_config()
if debug:
cb.dump_config() cb.dump_config()
ctx.obj = cb ctx.obj = cb
@ -213,21 +212,6 @@ def execute(cb, stack_name, function, args):
) )
@click.command('import')
@click.argument("stack_name")
@click.argument("pulumi_state_file")
@click.pass_obj
def _import(cb, stack_name, pulumi_state_file):
"""Imports a Pulumi state file as stack"""
stacks = _find_stacks(cb, [stack_name])
for s in stacks:
if s.mode == "pulumi":
s._import(pulumi_state_file)
else:
logger.info("Cannot import as {} uses Cloudformation.".format(s.stackname))
@click.command() @click.command()
@click.argument("stack_name") @click.argument("stack_name")
@click.option( @click.option(
@ -350,16 +334,7 @@ def wrap(cb, stack_group, cmd):
"""Execute custom external program""" """Execute custom external program"""
sg = cb.sg.get_stackgroup(stack_group) sg = cb.sg.get_stackgroup(stack_group)
sg.wrap(" ".join(cmd)) cb.wrap(sg, " ".join(cmd))
@click.command()
@click.argument("stack_group", nargs=1, required=True)
@click.pass_obj
def list_stacks(cb, stack_group):
"""List all Pulumi stacks"""
sg = cb.sg.get_stackgroup(stack_group)
sg.list_stacks()
@click.command() @click.command()
@ -507,9 +482,7 @@ cli.add_command(refresh)
cli.add_command(preview) cli.add_command(preview)
cli.add_command(set_config) cli.add_command(set_config)
cli.add_command(get_config) cli.add_command(get_config)
cli.add_command(_import)
cli.add_command(export) cli.add_command(export)
cli.add_command(list_stacks)
cli.add_command(assimilate) cli.add_command(assimilate)
cli.add_command(execute) cli.add_command(execute)
cli.add_command(wrap) cli.add_command(wrap)

View File

@ -1,7 +1,9 @@
import pathlib import pathlib
import logging import logging
import pexpect
from .stackgroup import StackGroup from .stackgroup import StackGroup
from .connection import BotoConnection
from .jinja import read_config_file from .jinja import read_config_file
from .exceptions import InvalidProjectDir from .exceptions import InvalidProjectDir
@ -131,3 +133,17 @@ class CloudBender(object):
matching_stacks.append(s) matching_stacks.append(s)
return matching_stacks return matching_stacks
def wrap(self, stack_group, cmd):
"""
Set AWS environment based on profile before executing a custom command, eg. steampipe
"""
profile = stack_group.config.get("profile", "default")
region = stack_group.config.get("region", "global")
connection_manager = BotoConnection(profile, region)
connection_manager.exportProfileEnv()
child = pexpect.spawn(cmd)
child.interact()

View File

@ -52,11 +52,14 @@ def resolve_outputs(outputs):
def pulumi_ws(func): def pulumi_ws(func):
@wraps(func) @wraps(func)
def decorated(self, *args, **kwargs): def decorated(self, *args, **kwargs):
cwd = None
# setup temp workspace # setup temp workspace
if self.mode == "pulumi": if self.mode == "pulumi":
self.work_dir = tempfile.mkdtemp( self.work_dir = tempfile.mkdtemp(
dir=tempfile.gettempdir(), prefix="cloudbender-" dir=tempfile.gettempdir(), prefix="cloudbender-"
) )
cwd = os.getcwd()
os.chdir(self.work_dir)
# add all artifact_paths/pulumi to the search path for easier # add all artifact_paths/pulumi to the search path for easier
# imports in the pulumi code # imports in the pulumi code
@ -146,7 +149,8 @@ def pulumi_ws(func):
try: try:
_min_version = self._pulumi_code.MIN_CLOUDBENDER_VERSION _min_version = self._pulumi_code.MIN_CLOUDBENDER_VERSION
if semver.compare( if semver.compare(
semver.Version.parse(__version__.strip("v")).finalize_version(), semver.Version.parse(
__version__.strip("v")).finalize_version(),
_min_version.strip("v")) < 0: _min_version.strip("v")) < 0:
raise ValueError( raise ValueError(
f"Minimal required CloudBender version is {_min_version}, but we are {__version__}!" f"Minimal required CloudBender version is {_min_version}, but we are {__version__}!"
@ -199,11 +203,12 @@ def pulumi_ws(func):
secrets_provider=secrets_provider, secrets_provider=secrets_provider,
) )
# self.pulumi_workspace = pulumi.automation.LocalWorkspace(self.pulumi_ws_opts)
response = func(self, *args, **kwargs) response = func(self, *args, **kwargs)
# Cleanup temp workspace # Cleanup temp workspace
if cwd:
os.chdir(cwd)
if self.work_dir and os.path.exists(self.work_dir): if self.work_dir and os.path.exists(self.work_dir):
shutil.rmtree(self.work_dir) shutil.rmtree(self.work_dir)

View File

@ -534,7 +534,6 @@ class Stack(object):
logger.info("Passed.") logger.info("Passed.")
return 0 return 0
@pulumi_ws
def get_outputs(self, include=".*", values=False): def get_outputs(self, include=".*", values=False):
"""gets outputs of the stack""" """gets outputs of the stack"""
@ -851,6 +850,7 @@ class Stack(object):
return status return status
@pulumi_ws
@exec_hooks @exec_hooks
def update(self): def update(self):
"""Updates an existing stack""" """Updates an existing stack"""
@ -987,7 +987,7 @@ class Stack(object):
def assimilate(self): def assimilate(self):
"""Import resources into Pulumi stack""" """Import resources into Pulumi stack"""
pulumi_stack = self._get_pulumi_stack() pulumi_stack = self._get_pulumi_stack(create=True)
# now lets import each defined resource # now lets import each defined resource
for r in self._pulumi_code.RESOURCES: for r in self._pulumi_code.RESOURCES:
@ -1024,19 +1024,6 @@ class Stack(object):
return return
@pulumi_ws
def _import(self, pulumi_state_file):
"""Imports a Pulumi stack"""
pulumi_stack = self._get_pulumi_stack()
with open(pulumi_state_file, "r") as file:
state = json.loads(file.read())
deployment = pulumi.automation.Deployment(version=3, deployment=state)
pulumi_stack.import_stack(deployment)
return
@pulumi_ws @pulumi_ws
def set_config(self, key, value, secret): def set_config(self, key, value, secret):
"""Set a config or secret""" """Set a config or secret"""
@ -1071,18 +1058,11 @@ class Stack(object):
if "parameters" not in settings: if "parameters" not in settings:
settings["parameters"] = {} settings["parameters"] = {}
# hack for bug above, we support one level of nested values for now # hack for bug above, we support one level of nested values for now
_val = pulumi_settings["config"]["{}:{}".format( _val = pulumi_settings["config"]["{}:{}".format(
self.parameters["Conglomerate"], key)] self.parameters["Conglomerate"], key)]
if '.' in key: if '.' in key:
try:
(root, leaf) = key.split('.') (root, leaf) = key.split('.')
except ValueError:
raise ParameterIllegalValue(
"Currently only one level hierachies within parameters are supported!"
)
if root not in settings["parameters"]: if root not in settings["parameters"]:
settings["parameters"][root] = {} settings["parameters"][root] = {}
@ -1324,7 +1304,6 @@ class Stack(object):
logger.info(" ".join([self.region, self.stackname, text])) logger.info(" ".join([self.region, self.stackname, text]))
def _get_pulumi_stack(self, create=False): def _get_pulumi_stack(self, create=False):
if create: if create:
pulumi_stack = pulumi.automation.create_or_select_stack( pulumi_stack = pulumi.automation.create_or_select_stack(
stack_name=self.pulumi_stackname, stack_name=self.pulumi_stackname,
@ -1333,7 +1312,7 @@ class Stack(object):
opts=self.pulumi_ws_opts, opts=self.pulumi_ws_opts,
) )
pulumi_stack.workspace.install_plugin( pulumi_stack.workspace.install_plugin(
"aws", importlib.metadata.distribution("pulumi_aws").version "aws", importlib.metadata.version("pulumi_aws")
) )
else: else:

View File

@ -1,13 +1,6 @@
import logging import logging
import pprint import pprint
import pexpect
import pulumi
import tempfile
import rich.table
import rich.console
from .connection import BotoConnection
from .utils import dict_merge from .utils import dict_merge
from .jinja import read_config_file from .jinja import read_config_file
from .stack import Stack from .stack import Stack
@ -32,7 +25,7 @@ class StackGroup(object):
for sg in self.sgs: for sg in self.sgs:
sg.dump_config() sg.dump_config()
logger.info( logger.debug(
"StackGroup {}: {}".format(self.rel_path, pprint.pformat(self.config)) "StackGroup {}: {}".format(self.rel_path, pprint.pformat(self.config))
) )
@ -142,54 +135,3 @@ class StackGroup(object):
return s return s
return None return None
def wrap(self, cmd):
"""
Set AWS environment based on profile before executing a custom command, eg. steampipe
"""
profile = self.config.get("profile", "default")
region = self.config.get("region", "global")
connection_manager = BotoConnection(profile, region)
connection_manager.exportProfileEnv()
child = pexpect.spawn(cmd)
child.interact()
def list_stacks(self):
project_name = self.config["parameters"]["Conglomerate"]
pulumi_backend = "{}/{}/{}".format(self.config["pulumi"]["backend"], project_name, self.config["region"])
project_settings = pulumi.automation.ProjectSettings(
name=project_name, runtime="python", backend=pulumi.automation.ProjectBackend(url=pulumi_backend)
)
work_dir = tempfile.mkdtemp(
dir=tempfile.gettempdir(), prefix="cloudbender-"
)
# AWS setup
profile = self.config.get("profile", "default")
region = self.config.get("region", "global")
connection_manager = BotoConnection(profile, region)
connection_manager.exportProfileEnv()
pulumi_workspace = pulumi.automation.LocalWorkspace(
work_dir=work_dir,
project_settings=project_settings
)
stacks = pulumi_workspace.list_stacks()
table = rich.table.Table(title="Pulumi stacks")
table.add_column("Name")
table.add_column("Last Update")
table.add_column("Resources")
for s in stacks:
table.add_row(s.name, str(s.last_update), str(s.resource_count))
console = rich.console.Console()
console.print(table)

View File

@ -13,21 +13,20 @@ readme = "README.md"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"boto3==1.35.70", "boto3==1.35.46",
"mock==5.1.0", "mock==5.1.0",
"Jinja2==3.1.4", "Jinja2==3.1.4",
"click==8.1.7", "click==8.1.7",
"pexpect==4.9.0", "pexpect==4.9.0",
"python-minifier==2.11.3", "python-minifier==2.11.2",
"cfn-lint==1.20.1", "cfn-lint==1.12.4",
"ruamel.yaml==0.18.6", "ruamel.yaml==0.18.6",
"rich==13.9.4", "pulumi==3.137.0",
"pulumi==3.142.0", "pulumi-aws==6.56.1",
"pulumi-aws==6.61.0", "pulumi-aws-native==1.3.0",
"pulumi-aws-native==1.11.0",
"pulumi-policy==1.13.0", "pulumi-policy==1.13.0",
"pulumi-command==1.0.1", "pulumi-command==1.0.1",
"pulumi_random==4.16.7", "pulumi_random==4.16.6",
] ]
classifiers = [ classifiers = [