Skip to content

Commit

Permalink
Merge pull request #707 from NHSDigital/feature/made14-NRL-863-add-ve…
Browse files Browse the repository at this point in the history
…rsion-to-env-config

[NRL-863] Add version to env config
  • Loading branch information
mattdean3-nhs committed Sep 17, 2024
2 parents 41505dc + f099aba commit a8a47af
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/persistent-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ jobs:
- name: Terraform Apply
run: terraform -chdir=terraform/infrastructure apply tfplan

- name: Update environment config version
run: |
short_commit_ref="$(echo ${{ github.sha }} | cut -c1-8)"
deployed_version="${{ inputs.branch_name }}@${short_commit_ref}"
poetry run python ./scripts/set_env_config.py inactive-version ${deployed_version} ${{ inputs.environment }}
- name: Smoke Test
run: |
account=$(echo '${{ inputs.environment }}' | cut -d '-' -f1)
Expand Down
37 changes: 34 additions & 3 deletions scripts/activate_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,45 @@

CONFIG_LOCK_STATE = "lock-state"
CONFIG_INACTIVE_STACK = "inactive-stack"
CONFIG_INACTIVE_VERSION = "inactive-version"
CONFIG_ACTIVE_STACK = "active-stack"
CONFIG_ACTIVE_VERSION = "active-version"
CONFIG_DOMAIN_NAME = "domain-name"

STATE_LOCKED = "locked"
STATE_OPEN = "open"
VALID_LOCK_STATES = [STATE_LOCKED, STATE_OPEN]


def _parse_env_config(raw_config: str) -> dict:
env_config = json.loads(raw_config)
if not all(
key in env_config
for key in [
CONFIG_LOCK_STATE,
CONFIG_INACTIVE_STACK,
CONFIG_INACTIVE_VERSION,
CONFIG_ACTIVE_STACK,
CONFIG_ACTIVE_VERSION,
CONFIG_DOMAIN_NAME,
]
):
raise ValueError(
f"Environment config must contain keys: {CONFIG_LOCK_STATE}, {CONFIG_INACTIVE_STACK}, {CONFIG_INACTIVE_VERSION}, {CONFIG_ACTIVE_STACK}, {CONFIG_ACTIVE_VERSION}, {CONFIG_DOMAIN_NAME}"
)

if env_config[CONFIG_LOCK_STATE] not in VALID_LOCK_STATES:
raise ValueError(
f"Invalid lock state: {env_config[CONFIG_LOCK_STATE]}. Must be one of: {VALID_LOCK_STATES}"
)

return env_config


def _swap_config(config: dict[str, str], key1: str, key2: str):
config[key1], config[key2] = config[key2], config[key1]


def _set_lock_state(
lock_state: str, environment_config: dict, parameters_key: str, sm: any
):
Expand Down Expand Up @@ -90,7 +121,7 @@ def activate_stack(stack_name: str, env: str, session: any):
parameters_key = f"nhsd-nrlf--{env}--env-config"
response = sm.get_secret_value(SecretId=parameters_key)

environment_config = json.loads(response["SecretString"])
environment_config = _parse_env_config(response["SecretString"])
print(f"Got environment config for {env}: {environment_config}")

current_active_stack = environment_config[CONFIG_ACTIVE_STACK]
Expand Down Expand Up @@ -134,8 +165,8 @@ def activate_stack(stack_name: str, env: str, session: any):
sys.exit(1)

print("Updating environment config and unlocking....")
environment_config[CONFIG_INACTIVE_STACK] = current_active_stack
environment_config[CONFIG_ACTIVE_STACK] = stack_name
_swap_config(environment_config, CONFIG_INACTIVE_STACK, CONFIG_ACTIVE_STACK)
_swap_config(environment_config, CONFIG_INACTIVE_VERSION, CONFIG_ACTIVE_VERSION)
_update_and_unlock(environment_config, parameters_key=parameters_key, sm=sm)

print(f"Complete. Stack {stack_name} is now the active stack for {env}")
Expand Down
45 changes: 45 additions & 0 deletions scripts/set_env_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env python
import json
import sys

import fire
from aws_session_assume import get_boto_session


def main(parameter_name: str, parameter_value: str, env: str):
boto_session = get_boto_session(env)
sm = boto_session.client("secretsmanager")

secret_key = f"nhsd-nrlf--{env}--env-config"
response = sm.get_secret_value(SecretId=secret_key)
parameters = json.loads(response["SecretString"])

if parameter_name in parameters:
current_value = parameters[parameter_name]
if current_value == parameter_value:
print(
f"'{parameter_name}' already set to '{parameter_value}'. No changes made."
)
sys.exit(0)

print(
f"Updating '{parameter_name}' from '{current_value}' to '{parameter_value}'...."
)
parameters[parameter_name] = parameter_value
else:
print(f"Adding '{parameter_name}' with value '{parameter_value}'....")
parameters[parameter_name] = parameter_value

response = sm.put_secret_value(
SecretId=secret_key, SecretString=json.dumps(parameters)
)

if response["ResponseMetadata"]["HTTPStatusCode"] == 200:
print("Updated successfully")
else:
print(f"Update failed with: {response}", file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
fire.Fire(main)
39 changes: 35 additions & 4 deletions scripts/tests/test_activate_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from scripts.activate_stack import activate_stack
from scripts.activate_stack import _parse_env_config, activate_stack


@pytest.fixture
Expand Down Expand Up @@ -68,7 +68,9 @@ def _create_mock_env_config():
return {
"lock-state": "open",
"active-stack": "test-stack-1",
"active-version": "v1",
"inactive-stack": "test-stack-2",
"inactive-version": "v2",
"domain-name": "test.domain.name",
}

Expand All @@ -88,16 +90,17 @@ def test_happy_path(mock_boto_session, mock_secretsmanager):
expected_env_config = {
**mock_env_config,
"active-stack": "test-stack-2",
"active-version": "v2",
"inactive-stack": "test-stack-1",
"inactive-version": "v1",
}
assert result["SecretString"] == json.dumps(expected_env_config)


def test_lock_state_not_open(mock_boto_session, mock_secretsmanager):
inital_env_config = {
**_create_mock_env_config(),
"lock-state": "locked",
"inactive-stack": "test-stack-1",
"active-stack": "test-stack-2",
}
mock_secretsmanager.create_secret(
Name="nhsd-nrlf--locked--env-config", SecretString=json.dumps(inital_env_config)
Expand All @@ -115,7 +118,7 @@ def test_lock_state_not_open(mock_boto_session, mock_secretsmanager):

def test_stack_already_active(mock_boto_session, mock_secretsmanager):
intial_env_config = {
"lock-state": "open",
**_create_mock_env_config(),
"inactive-stack": "test-stack-1",
"active-stack": "test-stack-2",
}
Expand All @@ -132,3 +135,31 @@ def test_stack_already_active(mock_boto_session, mock_secretsmanager):
SecretId="nhsd-nrlf--already-active--env-config" # pragma: allowlist secret
)
assert result["SecretString"] == json.dumps(intial_env_config)


def test_parse_env_config_valid():
valid_config = json.dumps(_create_mock_env_config())
result = _parse_env_config(valid_config)
assert result == _create_mock_env_config()


def test_parse_env_config_empty():
with pytest.raises(ValueError):
_parse_env_config("")


def test_parse_env_config_invalid_json():
with pytest.raises(ValueError):
_parse_env_config("this is not JSON!")


def test_parse_env_config_missing_params():
with pytest.raises(ValueError):
_parse_env_config(json.dumps({"lock-state": "open"}))


def test_parse_env_config_invalid_lock_state():
with pytest.raises(ValueError):
_parse_env_config(
json.dumps({**_create_mock_env_config(), "lock-state": "invalid"})
)

0 comments on commit a8a47af

Please sign in to comment.