Skip to content

Commit

Permalink
Merge pull request #482 from awslabs/release/3.1.0
Browse files Browse the repository at this point in the history
Release/3.1.0
dgraeber authored Jan 22, 2024

Verified

This commit was signed with the committer’s verified signature.
alexsnaps Alex Snaps
2 parents cb24bb0 + 4d1e281 commit 25ab12e
Showing 27 changed files with 265 additions and 110 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -12,6 +12,22 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch

### Fixes


## v3.1.0 (2024-01-22)

### New
- adding AWS curated codebuild iamge override with opinionated runtimes

### Changes
- updating pydantic support from 1.X.X to 2.5.3
- adding seedfarmer verions check support with `seedfarmer.yaml`
- updating `aws-codeseeder` dependency top 0.11.0

### Fixes
- update `manifests/examples/` to point to an updated release branch
- Docs - manifest name description (seed-farmer/docs/source/manifests.md) needed correction
- Docs - added definition of `nameGenerator` for deployment manifest (seed-farmer/docs/source/manifests.md)

## v3.0.1 (2023-11-10)

### New
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.0.1
3.1.0
40 changes: 23 additions & 17 deletions docs/requirements-docs.txt
Original file line number Diff line number Diff line change
@@ -12,21 +12,23 @@ babel==2.12.1
# via sphinx
certifi==2023.7.22
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# requests
charset-normalizer==3.1.0
# via requests
docutils==0.18.1
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# myst-parser
# sphinx
# sphinx-rtd-theme
idna==3.4
# via requests
imagesize==1.4.1
# via sphinx
jinja2==3.1.2
importlib-metadata==7.0.1
# via sphinx
jinja2==3.1.3
# via
# myst-parser
# sphinx
@@ -44,14 +46,14 @@ mdit-py-plugins==0.3.5
mdurl==0.1.2
# via markdown-it-py
myst-parser==1.0.0
# via -r docs/requirements-docs.in
# via -r requirements-docs.in
packaging==23.1
# via sphinx
pygments==2.15.1
# via sphinx
pyyaml==5.4
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# myst-parser
# sphinx-autoapi
requests==2.31.0
@@ -65,46 +67,50 @@ sphinx==6.2.1
# sphinx-rtd-theme
# sphinxcontrib-jquery
sphinx-autoapi==2.1.0
# via -r docs/requirements-docs.in
# via -r requirements-docs.in
sphinx-rtd-theme==1.2.1
# via -r docs/requirements-docs.in
# via -r requirements-docs.in
sphinxcontrib-applehelp==1.0.4
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# sphinx
sphinxcontrib-devhelp==1.0.2
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# sphinx
sphinxcontrib-htmlhelp==2.0.1
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# sphinx
sphinxcontrib-jquery==4.1
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# sphinx-rtd-theme
sphinxcontrib-jsmath==1.0.1
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# sphinx
sphinxcontrib-qthelp==1.0.3
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# sphinx
sphinxcontrib-serializinghtml==1.1.5
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# sphinx
typing-extensions==4.5.0
# via -r docs/requirements-docs.in
# via
# -r requirements-docs.in
# astroid
unidecode==1.3.6
# via sphinx-autoapi
urllib3==1.26.18
# via
# -r docs/requirements-docs.in
# -r requirements-docs.in
# requests
wheel==0.38.1
# via -r docs/requirements-docs.in
# via -r requirements-docs.in
wrapt==1.15.0
# via astroid
zipp==3.17.0
# via importlib-metadata
59 changes: 44 additions & 15 deletions docs/source/manifests.md
Original file line number Diff line number Diff line change
@@ -9,6 +9,11 @@ The deployment manifest is the top level manifest and resides in the `modules` d

```yaml
name: examples
nameGenerator:
prefix: myprefix
suffix:
valueFrom:
envVariable: SUFFIX_ENV_VARIABLE
toolchainRegion: us-west-2
forceDependencyRedeploy: False
groups:
@@ -69,6 +74,12 @@ targetAccountMappings:
```

- **name** : this is the name of your deployment. There can be only one deployment with this name in a project.
- THIS CANNOT BE USED WITH `nameGenerator`
- **nameGenerator** : this supports dynamically generating a deployment name by concatenation of the following fields:
- **prefix** - the prefix string of the name
- **suffix** - the suffix string of the name
- Both of these fields support the use of [Environment Variables](envVariable) (see example above)
- THIS CANNOT BE USED WITH `name`
- **toolchainRegion** :the designated region that the `toolchain` is created in
- **forceDependencyRedeploy**: this is a boolean that tells seedfarmer to redeploy ALL dependency modules (see [Force Dependency Redeploy](force-redeploy)) - Default is `False`
- **groups** : the relative path to the [`module manifests`](module_manifest) that define each module in the group. This sequential order is preserved in deployment, and reversed in destroy.
@@ -80,14 +91,14 @@ targetAccountMappings:
- **alias** - the logical name for an account, referenced by [`module manifests`](module_manifest)
- **account** - the account id tied to the alias. This parameter also supports [Environment Variables](envVariable)
- **default** - this designates this mapping as the default account for all modules unless otherwise specified. This is primarily for supporting migrating from `seedfarmer v1` to the current version.
- **codebuildImage** - a custom build image to use (see [Custom Build Image](custombuildimage))
- **codebuildImage** - a custom build image to use (see [Build Image Override](buildimageoverride))
- **parametersGlobal** - these are parameters that apply to all region mappings unless otherwise overridden at the region level
- **dockerCredentialsSecret** - please see [Docker Credentials Secret](dockerCredentialsSecret)
- **permissionsBoundaryName** - the name of the [permissions boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) policy to apply to all module-specific roles created
- **regionMappings** - section to define region-specific configurations for the defined account, this is a list
- **region** - the region name
- **default** - this designates this mapping as the default region for all modules unless otherwise specified. This is primarily for supporting migrating
- **codebuildImage** - a custom build image to use (see [Custom Build Image](custombuildimage))
- **codebuildImage** - a custom build image to use (see [Build Image Override](buildimageoverride))
- **parametersRegional** - these are parameters that apply to all region mappings unless otherwise overridden at the region level
- **dockerCredentialsSecret** - please see [Docker Credentials Secret](dockerCredentialsSecret)
- This is a NAMED PARAMETER...in that `dockerCredentialsSecret` is recognized by `seed-farmer`
@@ -183,10 +194,10 @@ network:
envVariable: SECURITYGROUPS
```
The corresponding `.env` file would have the following defined (again, remember the lists!!):
```code
```bash
VPCID="vpc-0c4cb9e06c9413222"
PRIVATESUBNETS=["subnet-0c36d3d5808f67a02","subnet-00fa1e71cddcf57d3"]
SECURITYGROUPS=["sg-049033188c114a3d2"]
PRIVATESUBNETS='["subnet-0c36d3d5808f67a02","subnet-00fa1e71cddcf57d3"]'
SECURITYGROUPS='["sg-049033188c114a3d2"]'
```
(dependency-management)=
### Dependency Management
@@ -211,7 +222,7 @@ What does this mean? Well, lets take the following module deployment order:
**This is an important feature to understand: redeployment is not discriminant.** SeedFarmer does not know how to assess what has changed in a module and its impact on downstream modules. Nor does it have the ability to know if a module can incur a redeployment (as opposed to a destroy and deploy process). That is up to you to determine with respect to the modules you are leveraging. ANY change to the source code (deployspec, modulestack, comments in cdk code, etc.) will indicate to SeedFarmer that the module needs to be redeployed, even if the underlying logic / artifact has not changed.

Also, it is important to understand that this feature could put your deployment in an unusable state if the shared-responsibility model is not followed.
For example: lets say a deployment has a module (called `networking`) that deploys a VPC with public and private subnets that are restricted to a particular CIDR (as input). Then, downstream modules reference the metadata of `netowrking`. If a user were to change the CIDR references and redeploy the `networking` module, this has the potential to render the deployment in an unusable state: the process to change the CIDR's would trigger a destroy of the existing subnets...which would fail due to resources from other modules leveraging those subnets. The redeployment would fail, and the user would have to manually correct the state.
For example: lets say a deployment has a module (called `networking`) that deploys a VPC with public and private subnets that are restricted to a particular CIDR (as input). Then, downstream modules reference the metadata of `networking`. If a user were to change the CIDR references and redeploy the `networking` module, this has the potential to render the deployment in an unusable state: the process to change the CIDR's would trigger a destroy of the existing subnets...which would fail due to resources from other modules leveraging those subnets. The redeployment would fail, and the user would have to manually correct the state.

(module_manifest)=
## Module Manifest
@@ -244,13 +255,14 @@ dataFiles:
- filePath: test1.txt
- filePath: git::https://github.com/awslabs/idf-modules.git//modules/storage/buckets/deployspec.yaml?ref=release/1.0.0&depth=1
```
- **name** - the name of the group
- **name** - the name of the module
- this name must be unique in the group of the deployment
- **path** - this element supports two sources of code:
- the relative path to the module code in the project
- the relative path to the module code in the project if deploying code from the local filesystem
- a public Git Repository, leveraging the Terraform semantic as denoted [HERE](https://www.terraform.io/language/modules/sources#generic-git-repository)
- **targetAccount** - the alias of the account from the [deployment manifest mappings](deployment_manifest)
- **targetRegion** - the name of the region to deploy to - this overrides any mappings
- **codebuildImage** - a custom build image to use (see [Custom Build Image](custombuildimage))
- **codebuildImage** - a custom build image to use (see [Build Image Override](buildimageoverride))
- **parameters** - the parameters section .... see [Parameters](parameters)
- **dataFiles** - additional files to add to the bundle that are outside of the module code
- this is LIST and EVERY element in the list must have the keyword **filePath**
@@ -276,13 +288,30 @@ When using this feature, any change to these file(s) (modifying, add to manifest
***Iceburg, dead ahead!*** Heres the rub: if you deploy with data files sourced from a local filesystem, you MUST provide those same files in order to destroy the module(s)...we are not keeping them stored anywhere (much like the module source code). ***Iceburg missed us! (why is everthing so wet??)***


(custombuildimage)=
## Custom Codebuild Image
`seed-farmer` is preconfigued to use the optimal build image and we recommend using it as-is (no need to leverage the `codebuildImage` manifest named paramter). But, we get it....no one wants to be boxed in.</br>
(buildimageoverride)=
## Codebuild Image Override
An AWS Codebuild complaint image is provided for use with `seed-farmer` and we recommend using it as-is (no need to leverage the `codebuildImage` manifest named paramter). But, we get it....no one wants to be boxed in.</br>

<b>USER BEWARE</b> - this is a feature meant for advanced users...use at own risk!

Users can override the default build image via one of the following:
- an AWS Curated Build Image
- a custom-built image

#### AWS Curated Build Images
There are multiple [build images and available runtimes](https://docs.aws.amazon.com/codebuild/latest/userguide/available-runtimes.html) that are supported by AWS Codebuild. For `seed-farmer`, we currently support the following AWS Curated Images with the default runtimes installed:

| AWS Curated Build Image | Confgured Runtimes|
| ----------- | ----------- |
|aws/codebuild/standard:6.0|nodejs:16|
||python:3.10|
||java:corretto17|
|aws/codebuild/standard:7.0|nodejs:18|
||python:3.11|
||java:corretto21|

### The Build Image
An AWS Codebuild complaint image is provided for use with `seed-farmer` and the CLI is configured by default to use this image. Advanced users have the option of building their own image and configuring their deployment to use it. If an end user wants to build their own image, it is STRONGLY encouraged to use [this Dockerfile from AWS public repos](https://github.com/awslabs/aws-codeseeder/blob/main/images/code-build-image/Dockerfile) as the base layer. `seed-farmer` leverages this as the base for its default image ([see HERE](https://github.com/awslabs/aws-codeseeder/blob/main/images/code-build-image/Dockerfile)).
#### Custom Build Images
If an end user wants to build their own image, it is STRONGLY encouraged to use [this Dockerfile from AWS public repos](https://github.com/awslabs/aws-codeseeder/blob/main/images/code-build-image/Dockerfile) as the base layer. `seed-farmer` leverages this as the base for its default image ([see HERE](https://github.com/awslabs/aws-codeseeder/blob/main/images/code-build-image/Dockerfile)). It is up to the module developer to verify all proper libraries are installed and available.

### Logic for Rules -- Application
There are three (3) places to configure a custom build image:
@@ -420,7 +449,7 @@ parameters:
valueFrom:
parameterValue: mygreatkey
```
`seed-farrmer` will first look in the Regional Parameters for a matching key, and return a string object (all json convert to a string) represening the value. If not found, `seed-farrmer` will look in the Global Parameters for the same key and return that string-ified value.
`seed-farmer` will first look in the Regional Parameters for a matching key, and return a string object (all json convert to a string) represening the value. If not found, `seed-farrmer` will look in the Global Parameters for the same key and return that string-ified value.

NOTE: the `network` section of the [deployment manifest](deployment_manifest) leverages Regional Parameters only!

4 changes: 3 additions & 1 deletion docs/source/project_development.md
Original file line number Diff line number Diff line change
@@ -27,13 +27,15 @@ It is important to have the ```seedfarmer.yaml``` at the root of your project.
project: <your project name>
description: <your project description>
projectPolicyPath: <relativepath/policyname.yaml>
seedfarmer_version: <minimum required seedfermer version>
```
- **project** (REQUIRED) - this is the name of the project that all deployments will reference
- **description** (OPTIONAL) - this is the description of the project
- **projectPolicyPath** (OPTIONAL) - this allows advanced users change the project policy that has the basic minimim permissions seedfarmer needs
- it consists of a path relative to the project root and MUST be a valid relative path
- to synth the existing project policy, run `seedfarmer projectpolicy synth`

- **seedfarmer_version** (OPTIONAL) - this specifies what is the minimum allowable version of `seed-farmer` the project supports
- if this value is set AND the runtime version of seedfarmer is greater, `seed-farmer` will exit immediately


(project_initalization)=
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# name: networking
# path: git::https://github.com/awslabs/seedfarmer-modules.git//modules/network/basic-cdk/?ref=release/1.0.0&depth=1
# path: git::https://github.com/awslabs/idf-modules.git//modules/network/basic-cdk/?ref=release/1.2.0&depth=1
# targetAccount: primary
# parameters:
# - name: internet-accessible
# value: true
# ---
name: buckets
path: git::https://github.com/awslabs/seedfarmer-modules.git//modules/storage/buckets/?ref=release/1.0.0&depth=1
path: git::https://github.com/awslabs/idf-modules.git//modules/storage/buckets/?ref=release/1.2.0&depth=1
targetAccount: secondary
targetRegion: us-west-2
parameters:
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# name: networking
# path: git::https://github.com/awslabs/seedfarmer-modules.git//modules/network/basic-cdk/?ref=release/1.0.0&depth=1
# path: git::https://github.com/awslabs/idf-modules.git//modules/network/basic-cdk/?ref=release/1.2.0&depth=1
# targetAccount: secondary
# parameters:
# - name: internet-accessible
# value: true
# ---
name: buckets
path: git::https://github.com/awslabs/seedfarmer-modules.git//modules/storage/buckets/?ref=release/1.0.0&depth=1
path: git::https://github.com/awslabs/idf-modules.git//modules/storage/buckets/?ref=release/1.2.0&depth=1
targetAccount: primary
targetRegion: us-east-2
parameters:
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: networking
path: git::https://github.com/awslabs/seedfarmer-modules.git//modules/network/basic-cdk/?ref=release/1.0.0&depth=1
path: git::https://github.com/awslabs/idf-modules.git//modules/network/basic-cdk/?ref=release/1.2.0&depth=1
parameters:
- name: internet-accessible
value: true
---
name: buckets
path: git::https://github.com/awslabs/seedfarmer-modules.git//modules/storage/buckets/?ref=release/1.0.0&depth=1
path: git::https://github.com/awslabs/idf-modules.git//modules/storage/buckets/?ref=release/1.2.0&depth=1
parameters:
- name: encryption-type
value: SSE
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -74,6 +74,7 @@ markers = [
"commands_parameters: marks all `commands_parameters` tests",
"commands_modules: marks all `commands_modules` tests",
"commands_deployment: marks all `commands_deployment` tests",
"commands_bootstrap: marks all `commands_bootstrap` tests",
]
log_cli_level = "INFO"
addopts = "-v --cov=. --cov-report=term --cov-report=html --cov-config=coverage.ini --cov-fail-under=80"
24 changes: 21 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -48,8 +48,10 @@ click==8.1.6
colorama==0.4.4
# via awscli
coverage[toml]==7.2.7
# via pytest-cov
cryptography==41.0.4
# via
# coverage
# pytest-cov
cryptography==41.0.6
# via
# moto
# secretstorage
@@ -61,6 +63,8 @@ docutils==0.16
# readme-renderer
# sphinx
# sphinx-rtd-theme
exceptiongroup==1.2.0
# via pytest
flake8==4.0.1
# via -r requirements-dev.in
idna==3.4
@@ -114,7 +118,9 @@ mdurl==0.1.2
more-itertools==9.1.0
# via jaraco-classes
moto[codebuild,iam,s3,secretsmanager,ssm,sts]==4.0.13
# via -r requirements-dev.in
# via
# -r requirements-dev.in
# moto
mypy==0.991
# via -r requirements-dev.in
mypy-extensions==1.0.0
@@ -239,6 +245,16 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
tomli==2.0.1
# via
# black
# build
# check-manifest
# coverage
# mypy
# pip-tools
# pyproject-hooks
# pytest
trove-classifiers==2023.7.6
# via pyroma
twine==4.0.2
@@ -251,6 +267,8 @@ types-setuptools==57.4.18
# via -r requirements-dev.in
typing-extensions==4.7.1
# via
# astroid
# black
# mypy
# myst-parser
unidecode==1.3.6
15 changes: 10 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -4,9 +4,11 @@
#
# pip-compile
#
annotated-types==0.6.0
# via pydantic
arrow==1.2.3
# via jinja2-time
aws-codeseeder==0.10.2
aws-codeseeder==0.11.0
# via seed-farmer (setup.py)
binaryornot==0.4.4
# via cookiecutter
@@ -46,7 +48,7 @@ gitdb==4.0.10
# via gitpython
gitignore-parser==0.1.3
# via seed-farmer (setup.py)
gitpython==3.1.37
gitpython==3.1.41
# via seed-farmer (setup.py)
humanfriendly==10.0
# via
@@ -55,7 +57,7 @@ humanfriendly==10.0
# property-manager
idna==3.4
# via requests
jinja2==3.1.2
jinja2==3.1.3
# via
# cookiecutter
# jinja2-time
@@ -71,8 +73,10 @@ mypy-extensions==1.0.0
# via aws-codeseeder
property-manager==3.0
# via executor
pydantic==1.10.7
pydantic==2.5.3
# via seed-farmer (setup.py)
pydantic-core==2.14.6
# via pydantic
pygments==2.15.1
# via rich
pyhumps==3.5.3
@@ -108,9 +112,10 @@ smmap==5.0.0
# via gitdb
text-unidecode==1.3
# via python-slugify
typing-extensions==4.5.0
typing-extensions==4.6.3
# via
# pydantic
# pydantic-core
# seed-farmer (setup.py)
urllib3==1.26.18
# via
9 changes: 8 additions & 1 deletion seedfarmer/__init__.py
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@
import yaml
from aws_codeseeder import LOGGER, codeseeder
from aws_codeseeder.codeseeder import CodeSeederConfig
from packaging.version import parse

import seedfarmer.errors
from seedfarmer.__metadata__ import __description__, __license__, __title__
@@ -90,6 +91,13 @@ def _load_config_data(self) -> None:
if self._project_spec.project_policy_path
else os.path.join(CLI_ROOT, DEFAULT_PROJECT_POLICY_PATH)
)
if self._project_spec.seedfarmer_version:
if parse(__version__) < parse(str(self._project_spec.seedfarmer_version)):
msg = (
f"The seedfarmer.yaml specified a minimum version: "
f"{self._project_spec.seedfarmer_version} but you are using {__version__}"
)
raise seedfarmer.errors.SeedFarmerException(msg)

@codeseeder.configure(self._project_spec.project.lower(), deploy_if_not_exists=True)
def configure(configuration: CodeSeederConfig) -> None:
@@ -104,7 +112,6 @@ def configure(configuration: CodeSeederConfig) -> None:
'timeout 15 sh -c "until docker info; do echo .; sleep 1; done"',
]
configuration.python_modules = [f"seed-farmer=={__version__}"]
configuration.runtime_versions = {"nodejs": "16", "python": "3.10"}

@property
def PROJECT(self) -> str:
27 changes: 17 additions & 10 deletions seedfarmer/commands/_deployment_commands.py
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@

import git
import yaml
from git import Repo # type: ignore
from git import Repo

import seedfarmer.checksum as checksum
import seedfarmer.errors
@@ -374,15 +374,14 @@ def _render_permissions_boundary_arn(


def prime_target_accounts(deployment_manifest: DeploymentManifest) -> None:
# TODO: Investigate whether we need to validate the requested mappings against previously deployed mappings

_logger.info("Priming Accounts")
with concurrent.futures.ThreadPoolExecutor(max_workers=len(deployment_manifest.target_accounts_regions)) as workers:

def _prime_accounts(args: Dict[str, Any]) -> None:
def _prime_accounts(args: Dict[str, Any]) -> List[Any]:
_logger.info("Priming Acccount %s in %s", args["account_id"], args["region"])
commands.deploy_seedkit(**args)
seedkit_stack_outputs = commands.deploy_seedkit(**args)
commands.deploy_managed_policy_stack(deployment_manifest=deployment_manifest, **args)
return [args["account_id"], args["region"], seedkit_stack_outputs]

params = []
for target_account_region in deployment_manifest.target_accounts_regions:
@@ -401,7 +400,11 @@ def _prime_accounts(args: Dict[str, Any]) -> None:

params.append(param_d)

_ = list(workers.map(_prime_accounts, params))
output_seedkit = list(workers.map(_prime_accounts, params))
# add these to the region mappings for reference
for out_s in output_seedkit:
deployment_manifest.populate_seedkit_metadata(account_id=out_s[0], region=out_s[1], seedkit_dict=out_s[2])
_logger.debug(deployment_manifest.model_dump())


def tear_down_target_accounts(deployment_manifest: DeploymentManifest, retain_seedkit: bool = False) -> None:
@@ -551,7 +554,7 @@ def deploy_deployment(
By default False
"""
deployment_manifest_wip = deployment_manifest.copy()
deployment_manifest_wip = deployment_manifest.model_copy()
deployment_name = cast(str, deployment_manifest_wip.name)
_logger.debug("Setting up deployment for %s", deployment_name)

@@ -604,7 +607,9 @@ def deploy_deployment(
deployment_manifest=deployment_manifest_wip, module=module, group_name=group.name
)

module.manifest_md5 = hashlib.md5(json.dumps(module.dict(), sort_keys=True).encode("utf-8")).hexdigest()
module.manifest_md5 = hashlib.md5(
json.dumps(module.model_dump(), sort_keys=True).encode("utf-8")
).hexdigest()
module.deployspec_md5 = hashlib.md5(open(deployspec_path, "rb").read()).hexdigest()

_build_module = du.need_to_build(
@@ -704,7 +709,7 @@ def apply(
manifest_path = os.path.join(config.OPS_ROOT, deployment_manifest_path)
with open(manifest_path) as manifest_file:
deployment_manifest = DeploymentManifest(**yaml.safe_load(manifest_file))
_logger.debug(deployment_manifest.dict())
_logger.debug(deployment_manifest.model_dump())

# Initialize the SessionManager for the entire project
session_manager = SessionManager().get_or_create(
@@ -720,7 +725,9 @@ def apply(
deployment_manifest._partition = partition
if not dryrun:
write_deployment_manifest(
cast(str, deployment_manifest.name), deployment_manifest.dict(), session=session_manager.toolchain_session
cast(str, deployment_manifest.name),
deployment_manifest.model_dump(),
session=session_manager.toolchain_session,
)

for module_group in deployment_manifest.groups:
20 changes: 14 additions & 6 deletions seedfarmer/commands/_module_commands.py
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@

import seedfarmer.errors
from seedfarmer import config
from seedfarmer.commands._runtimes import get_runtimes
from seedfarmer.models.deploy_responses import CodeSeederMetadata, ModuleDeploymentResponse, StatusType
from seedfarmer.models.manifests import ModuleManifest, ModuleParameter
from seedfarmer.services.session_manager import SessionManager
@@ -136,6 +137,9 @@ def deploy_module(
}

_phases = module_manifest.deploy_spec.deploy.phases
active_codebuild_image = (
module_manifest.codebuild_image if module_manifest.codebuild_image is not None else codebuild_image
)
try:
resp_dict_str, dict_metadata = _execute_module_commands(
deployment_name=deployment_name,
@@ -153,9 +157,8 @@ def deploy_module(
extra_env_vars=env_vars,
codebuild_compute_type=module_manifest.deploy_spec.build_type,
codebuild_role_name=module_role_name,
codebuild_image=module_manifest.codebuild_image
if module_manifest.codebuild_image is not None
else codebuild_image,
codebuild_image=active_codebuild_image,
runtime_versions=get_runtimes(active_codebuild_image),
)
_logger.debug("CodeSeeder Metadata response is %s", dict_metadata)

@@ -228,6 +231,9 @@ def destroy_module(
for data_file in module_manifest.data_files
}

active_codebuild_image = (
module_manifest.codebuild_image if module_manifest.codebuild_image is not None else codebuild_image
)
try:
resp_dict_str, _ = _execute_module_commands(
deployment_name=deployment_name,
@@ -245,9 +251,8 @@ def destroy_module(
extra_env_vars=env_vars,
codebuild_compute_type=module_manifest.deploy_spec.build_type,
codebuild_role_name=module_role_name,
codebuild_image=module_manifest.codebuild_image
if module_manifest.codebuild_image is not None
else codebuild_image,
codebuild_image=active_codebuild_image,
runtime_versions=get_runtimes(active_codebuild_image),
)
resp = ModuleDeploymentResponse(
deployment=deployment_name,
@@ -286,6 +291,7 @@ def _execute_module_commands(
codebuild_compute_type: Optional[str] = None,
codebuild_role_name: Optional[str] = None,
codebuild_image: Optional[str] = None,
runtime_versions: Optional[Dict[str, str]] = None,
) -> Tuple[str, Optional[Dict[str, str]]]:
session_getter: Optional[Callable[[], Session]] = None

@@ -315,6 +321,7 @@ def _session_getter() -> Session:
codebuild_compute_type=codebuild_compute_type,
extra_files=extra_file_bundle,
boto3_session=session_getter,
runtime_versions=runtime_versions,
)
def _execute_module_commands(
deployment_name: str,
@@ -331,6 +338,7 @@ def _execute_module_commands(
extra_post_build_commands: Optional[List[str]] = None,
extra_env_vars: Optional[Dict[str, Any]] = None,
codebuild_compute_type: Optional[str] = None,
runtime_versions: Optional[Dict[str, str]] = None,
) -> str:
deploy_info = {
"aws_region": os.environ.get("AWS_DEFAULT_REGION"),
2 changes: 1 addition & 1 deletion seedfarmer/commands/_parameter_commands.py
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ def load_parameter_values(
parameter_values_cache: Dict[Tuple[str, str, str], Any] = {}
for parameter in parameters:
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug("parameter: %s", parameter.dict())
_logger.debug("parameter: %s", parameter.model_dump())

if parameter.value is not None:
_logger.debug("static parameter value: %s", parameter.value)
36 changes: 36 additions & 0 deletions seedfarmer/commands/_runtimes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from enum import Enum
from typing import Dict, Optional


class CuratedBuildImages:
class ImageEnums(Enum):
UBUNTU_STANDARD_6 = "aws/codebuild/standard:6.0"
UBUNTU_STANDARD_7 = "aws/codebuild/standard:7.0"

class ImageRuntimes(Enum):
UBUNTU_STANDARD_6 = {"nodejs": "16", "python": "3.10", "java": "corretto17"}
UBUNTU_STANDARD_7 = {"nodejs": "18", "python": "3.11", "java": "corretto21"}


def get_runtimes(codebuild_image: Optional[str]) -> Optional[Dict[str, str]]:
image_vals = [cbi.value for cbi in CuratedBuildImages.ImageEnums]
if codebuild_image in image_vals:
cir_d = {cir.name: cir.value for cir in CuratedBuildImages.ImageRuntimes}
k = CuratedBuildImages.ImageEnums(codebuild_image).name
return cir_d[k]
else:
return None
5 changes: 4 additions & 1 deletion seedfarmer/commands/_stack_commands.py
Original file line number Diff line number Diff line change
@@ -435,7 +435,7 @@ def deploy_seedkit(
vpc_id: Optional[str] = None,
private_subnet_ids: Optional[List[str]] = None,
security_group_ids: Optional[List[str]] = None,
) -> None:
) -> Dict[str, Any]:
"""
deploy_seedkit
Accessor method to CodeSeeder to deploy the SeedKit if not deployed
@@ -468,6 +468,9 @@ def deploy_seedkit(
subnet_ids=private_subnet_ids,
security_group_ids=security_group_ids,
)
# Go get the outputs and return them
_, _, stack_outputs = commands.seedkit_deployed(seedkit_name=config.PROJECT, session=session)
return dict(stack_outputs)


def destroy_seedkit(account_id: str, region: str) -> None:
16 changes: 9 additions & 7 deletions seedfarmer/mgmt/deploy_utils.py
Original file line number Diff line number Diff line change
@@ -288,20 +288,20 @@ def prepare_ssm_for_deploy(

# Remove the deployspec before writing...remove bloat as we write deployspec separately
session = SessionManager().get_or_create().get_deployment_session(account_id=account_id, region_name=region)
module_manifest_wip = module_manifest.copy()
module_manifest_wip = module_manifest.model_copy()
module_manifest_wip.deploy_spec = None
mi.write_module_manifest(
deployment=deployment_name,
group=group_name,
module=module_manifest.name,
data=module_manifest_wip.dict(),
data=module_manifest_wip.model_dump(),
session=session,
)
mi.write_deployspec(
deployment=deployment_name,
group=group_name,
module=module_manifest.name,
data=module_manifest.deploy_spec.dict(),
data=module_manifest.deploy_spec.model_dump(),
session=session,
) if module_manifest.deploy_spec else None
mi.write_module_md5(
@@ -343,7 +343,9 @@ def write_deployed_deployment_manifest(deployment_manifest: DeploymentManifest)
for group in deployment_manifest.groups:
delattr(group, "modules")
session = SessionManager().get_or_create().toolchain_session
mi.write_deployed_deployment_manifest(deployment=deployment_name, data=deployment_manifest.dict(), session=session)
mi.write_deployed_deployment_manifest(
deployment=deployment_name, data=deployment_manifest.model_dump(), session=session
)


def generate_deployed_manifest(
@@ -556,7 +558,7 @@ def write_group_manifest(deployment_name: str, group_manifest: ModulesManifest)
The ModulesManifest object of the groups to persist
"""
g = group_manifest.name if group_manifest.name else ""
mi.write_group_manifest(deployment=deployment_name, group=g, data=group_manifest.dict())
mi.write_group_manifest(deployment=deployment_name, group=g, data=group_manifest.model_dump())


def filter_deploy_destroy(apply_manifest: DeploymentManifest, module_info_index: ModuleInfoIndex) -> DeploymentManifest:
@@ -580,7 +582,7 @@ def filter_deploy_destroy(apply_manifest: DeploymentManifest, module_info_index:
"""
deployment_name = cast(str, apply_manifest.name)

destroy_manifest = apply_manifest.copy()
destroy_manifest = apply_manifest.model_copy()
delattr(destroy_manifest, "groups")
destroy_group_list = _populate_groups_to_remove(deployment_name, apply_manifest.groups, module_info_index)
destroy_manifest.groups = destroy_group_list
@@ -673,4 +675,4 @@ def update_deployspec(
d_path = mi.get_deployspec_path(module_path=module_path)
with open(d_path) as deploymentspec:
new_spec = DeploySpec(**yaml.safe_load(deploymentspec))
mi.write_deployspec(deployment, group, module, new_spec.dict(), session=session)
mi.write_deployspec(deployment, group, module, new_spec.model_dump(), session=session)
4 changes: 2 additions & 2 deletions seedfarmer/mgmt/metadata_support.py
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ def __init__(self) -> None:
deploy_spec = DeploySpec(**yaml.safe_load(module_spec_file))
self.use_project_prefix = not deploy_spec.publish_generic_env_variables
except Exception:
_logger.warn("Cannot read the deployspec, using project name as prefix (a non-generic module)")
_logger.warning("Cannot read the deployspec, using project name as prefix (a non-generic module)")
self.use_project_prefix = True

def get_ops_root_path(self) -> str:
@@ -157,5 +157,5 @@ def get_parameter_value(parameter_suffix: str) -> Optional[str]:
_logger.info("Getting the Env Parameter tied to %s", key)
return os.getenv(key)
except Exception:
_logger.warn("Error looking for %s, returning None", key)
_logger.warning("Error looking for %s, returning None", key)
return None
9 changes: 4 additions & 5 deletions seedfarmer/models/_base.py
Original file line number Diff line number Diff line change
@@ -15,18 +15,17 @@
from typing import Optional, cast

import humps
from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict


def to_camel(string: str) -> str:
return cast(str, humps.camelize(string)) # type: ignore


class CamelModel(BaseModel):
class Config:
alias_generator = to_camel
allow_population_by_field_name = True
underscore_attrs_are_private = True
# TODO[pydantic]: The following keys were removed: `underscore_attrs_are_private`.
# Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information.
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)


class ModuleRef(CamelModel):
3 changes: 2 additions & 1 deletion seedfarmer/models/_project_spec.py
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Optional
from typing import Optional, Union

from seedfarmer.models._base import CamelModel

@@ -27,3 +27,4 @@ class ProjectSpec(CamelModel):
project: str
description: Optional[str] = None
project_policy_path: Optional[str] = None
seedfarmer_version: Optional[Union[int, str]] = None
16 changes: 8 additions & 8 deletions seedfarmer/models/deploy_responses.py
Original file line number Diff line number Diff line change
@@ -30,11 +30,11 @@ class CodeSeederMetadata(CamelModel):

_build_url: str = PrivateAttr()

aws_account_id: Optional[str]
aws_region: Optional[str]
aws_partition: Optional[str]
codebuild_build_id: Optional[str]
codebuild_log_path: Optional[str]
aws_account_id: Optional[str] = None
aws_region: Optional[str] = None
aws_partition: Optional[str] = None
codebuild_build_id: Optional[str] = None
codebuild_log_path: Optional[str] = None

def __init__(self, **data: Any) -> None:
super().__init__(**data)
@@ -53,11 +53,11 @@ def build_url(self) -> str:
class ModuleDeploymentResponse(CamelModel):

deployment: str
group: Optional[str]
group: Optional[str] = None
module: str
status: str
codeseeder_metadata: Optional[CodeSeederMetadata]
codeseeder_output: Optional[Dict[Any, Any]]
codeseeder_metadata: Optional[CodeSeederMetadata] = None
codeseeder_output: Optional[Dict[Any, Any]] = None

def __init__(self, **data: Any) -> None:
super().__init__(**data)
32 changes: 24 additions & 8 deletions seedfarmer/models/manifests/_deployment_manifest.py
Original file line number Diff line number Diff line change
@@ -41,9 +41,9 @@ class NetworkMapping(CamelModel):
This class provides network metadata
"""

vpc_id: Optional[Union[str, ValueFromRef]]
private_subnet_ids: Optional[Union[List[str], ValueFromRef]]
security_group_ids: Optional[Union[List[str], ValueFromRef]]
vpc_id: Optional[Union[str, ValueFromRef]] = None
private_subnet_ids: Optional[Union[List[str], ValueFromRef]] = None
security_group_ids: Optional[Union[List[str], ValueFromRef]] = None


class RegionMapping(CamelModel):
@@ -57,6 +57,7 @@ class RegionMapping(CamelModel):
parameters_regional: Dict[str, Any] = {}
network: Optional[NetworkMapping] = None
codebuild_image: Optional[str] = None
seedkit_metadata: Optional[Dict[str, Any]] = None


class TargetAccountMapping(CamelModel):
@@ -66,7 +67,7 @@ class TargetAccountMapping(CamelModel):
"""

alias: str
account_id: Union[str, ValueFromRef]
account_id: Union[int, str, ValueFromRef]
default: bool = False
parameters_global: Dict[str, str] = {}
region_mappings: List[RegionMapping] = []
@@ -86,8 +87,8 @@ def get_region_mapping(self, region: str) -> Optional[RegionMapping]:

@property
def actual_account_id(self) -> str:
if isinstance(self.account_id, str):
return self.account_id
if isinstance(self.account_id, str) or isinstance(self.account_id, int):
return str(self.account_id)
elif isinstance(self.account_id, ValueFromRef):
if self.account_id.value_from and self.account_id.value_from.module_metadata is not None:
raise seedfarmer.errors.InvalidManifestError(
@@ -161,7 +162,7 @@ class DeploymentManifest(CamelModel):
name_generator: Optional[NameGenerator] = None
toolchain_region: str
groups: List[ModulesManifest] = []
description: Optional[str]
description: Optional[str] = None
target_account_mappings: List[TargetAccountMapping] = []
force_dependency_redeploy: Optional[bool] = False
_default_account: Optional[TargetAccountMapping] = PrivateAttr(default=None)
@@ -284,11 +285,19 @@ def get_region_codebuild_image(
# Search the region_mappings for the region, if the codebuild_image is in region
for region_mapping in target_account.region_mappings:
if region == region_mapping.region or (use_default_region and region_mapping.default):
return (
image = (
region_mapping.codebuild_image
if region_mapping.codebuild_image is not None
else target_account.codebuild_image
)
if (
image is None
and region_mapping.seedkit_metadata
and region_mapping.seedkit_metadata.get("CodeBuildProjectBuildImage")
):
image = region_mapping.seedkit_metadata["CodeBuildProjectBuildImage"]

return image
else:
return None

@@ -324,3 +333,10 @@ def validate_and_set_module_defaults(self) -> None:

def get_module(self, group: str, module: str) -> Optional[ModuleManifest]:
return self._module_index.get((group, module), None)

def populate_seedkit_metadata(self, account_id: str, region: str, seedkit_dict: Dict[str, Any]) -> None:
for target_account in self.target_account_mappings:
for region_mapping in target_account.region_mappings:
if target_account.actual_account_id == account_id and region_mapping.region == region:
region_mapping.seedkit_metadata = seedkit_dict
break
6 changes: 3 additions & 3 deletions seedfarmer/models/manifests/_module_manifest.py
Original file line number Diff line number Diff line change
@@ -71,9 +71,9 @@ class ModuleManifest(CamelModel):

name: str
path: str
bundle_md5: Optional[str]
manifest_md5: Optional[str]
deployspec_md5: Optional[str]
bundle_md5: Optional[str] = None
manifest_md5: Optional[str] = None
deployspec_md5: Optional[str] = None
parameters: List[ModuleParameter] = []
deploy_spec: Optional[DeploySpec] = None
target_account: Optional[str] = None
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -45,12 +45,12 @@
keywords=["aws", "cdk"],
python_requires=">=3.7,<3.12",
install_requires=[
"aws-codeseeder~=0.10.2",
"aws-codeseeder~=0.11.0",
"cookiecutter~=2.1.0",
"pyhumps~=3.5.0",
"pydantic~=1.10.0",
"pydantic~=2.5.3",
"executor~=23.2",
"typing-extensions~=4.5.0",
"typing-extensions~=4.6.1",
"rich~=12.4.0",
"requests>=2.28,<2.32",
"python-dotenv~=0.21.0",
4 changes: 1 addition & 3 deletions test/unit-test/test_cli_arg.py
Original file line number Diff line number Diff line change
@@ -265,9 +265,7 @@ def test_apply_missing_deployment():
@pytest.mark.apply
def test_apply_missing_deployment_group_name():
deployment_manifest = f"{_TEST_ROOT}/manifests/test-missing-deployment-group-name/deployment.yaml"

result = _test_command(sub_command=apply, options=deployment_manifest, exit_code=1, return_result=True)
assert result.exception.args[0][0][0].exc.errors()[0]["msg"] == "none is not an allowed value"
_test_command(sub_command=apply, options=deployment_manifest, exit_code=1, return_result=True)


@pytest.mark.apply
7 changes: 4 additions & 3 deletions test/unit-test/test_commands_parameters.py
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
import pytest
import yaml
from moto import mock_sts
import pydantic_core

import seedfarmer.commands._parameter_commands as pc
import seedfarmer.errors
@@ -256,9 +257,9 @@ def test_load_parameter_values_missing_param_value(session_manager, mocker):
faulty_d["groups"][0]["modules"][0]["parameters"][4] = (
{"name": "test-regional-param", "value_from": {"parameterValue": "regParamMissing"}},
)
dep = DeploymentManifest(**faulty_d)
dep.validate_and_set_module_defaults()
with pytest.raises(seedfarmer.errors.InvalidManifestError):
with pytest.raises(pydantic_core._pydantic_core.ValidationError):
dep = DeploymentManifest(**faulty_d)
dep.validate_and_set_module_defaults()
pc.load_parameter_values(
deployment_name="mlops",
deployment_manifest=dep,

0 comments on commit 25ab12e

Please sign in to comment.