diff --git a/action.py b/action.py index 86aa62a..7ae26dd 100755 --- a/action.py +++ b/action.py @@ -46,7 +46,13 @@ class BeanstalkEnvironment: def get_health(self) -> dict: return boto3.client("elasticbeanstalk").describe_environment_health( EnvironmentName=self.name, - AttributeNames=["HealthStatus", "InstancesHealth", "Causes", "Color", "Status"], + AttributeNames=[ + "HealthStatus", + "InstancesHealth", + "Causes", + "Color", + "Status", + ], ) def get_events(self, last_event_time: datetime) -> Sequence[dict]: @@ -72,7 +78,9 @@ def wait_for_update_is_ready_and_get_health( events = self.get_events(last_event_time) health = self.get_health() - logging.info(f"Step {step + 1} of {polling_max_steps}. Status is {health['Status']}") + logging.info( + f"Step {step + 1} of {polling_max_steps}. Status is {health['Status']}", + ) # Log all events in order they occur beginning at the last event time for event in events: @@ -108,20 +116,34 @@ def create_zip() -> bytes: with ZipFile(output_data, "w", compression=ZIP_DEFLATED) as archive: archive.writestr( "docker-compose.yml", - docker_compose_path.read_text().replace("${IMAGE_TAG}", version_label), + docker_compose_path.read_text().replace( + "${IMAGE_TAG}", version_label, + ), ) if platform_hooks_path is not None: - for file in filter(lambda path: path.is_file(), platform_hooks_path.glob("**/*")): - archive.write(file, arcname=file.relative_to(platform_hooks_path)) + for file in filter( + lambda path: path.is_file(), platform_hooks_path.glob("**/*"), + ): + archive.write( + file, arcname=file.relative_to(platform_hooks_path), + ) logging.info("Deployment archive created") output_data.seek(0) return output_data.getvalue() def upload_zip(content: bytes) -> DeploymentArchive: - boto3.client("s3").put_object(Bucket=bucket_name, Body=content, Key=bucket_key) - logging.info(f"Deployment archive uploaded to s3://{bucket_name}/{bucket_key}") - return DeploymentArchive(version_label=version_label, bucket_name=bucket_name, bucket_key=bucket_key) + boto3.client("s3").put_object( + Bucket=bucket_name, Body=content, Key=bucket_key, + ) + logging.info( + f"Deployment archive uploaded to s3://{bucket_name}/{bucket_key}", + ) + return DeploymentArchive( + version_label=version_label, + bucket_name=bucket_name, + bucket_key=bucket_key, + ) return upload_zip(create_zip()) @@ -133,7 +155,9 @@ class ApplicationVersion: status: str @classmethod - def get(cls, application: BeanstalkApplication, version_label: str) -> Optional[Self]: + def get( + cls, application: BeanstalkApplication, version_label: str, + ) -> Optional[Self]: versions = boto3.client("elasticbeanstalk").describe_application_versions( ApplicationName=application.name, VersionLabels=[version_label], @@ -178,16 +202,24 @@ def wait_until_created(): step = 0 while step < polling_max_steps: application_version = cls.get(application, version_label) - status = "UNAVAILABLE" if application_version is None else application_version.status + status = ( + "UNAVAILABLE" + if application_version is None + else application_version.status + ) - logging.info(f"Step {step + 1} of {polling_max_steps}. Status is {status}") + logging.info( + f"Step {step + 1} of {polling_max_steps}. Status is {status}", + ) if status == "PROCESSED": return application_version time.sleep(polling_interval.total_seconds()) step += 1 - raise TimeoutError("Application Version creation not finished until timeout") + raise TimeoutError( + "Application Version creation not finished until timeout", + ) return wait_until_created() @@ -209,7 +241,9 @@ def deploy_to_environment( assert environment.application == self.application if self.is_active_in_environment(environment): - logging.info(f"{self.version_label} already active in {environment.name}, skip update!") + logging.info( + f"{self.version_label} already active in {environment.name}, skip update!", + ) return boto3.client("elasticbeanstalk").update_environment( @@ -262,7 +296,9 @@ def get_or_create_beanstalk_application_version( def check_aws_credentials(): aws_credential_variables = ("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY") if not all(variable in os.environ for variable in aws_credential_variables): - raise ValueError(f"AWS credentials not configured ({', '.join(aws_credential_variables)})") + raise ValueError( + f"AWS credentials not configured ({', '.join(aws_credential_variables)})", + ) def get_region() -> str: @@ -271,7 +307,9 @@ def get_region() -> str: if region := os.environ.get(region_var): return region - raise ValueError(f"AWS region not configured, set one of ({', '.join(region_variables)})") + raise ValueError( + f"AWS region not configured, set one of ({', '.join(region_variables)})", + ) def main(): @@ -281,7 +319,9 @@ def main(): description=os.environ["VERSION_DESCRIPTION"], docker_compose_path=Path(os.environ["DOCKER_COMPOSE_PATH"]), environment_name=os.environ["ENVIRONMENT_NAME"], - platform_hooks_path=Path(os.environ["PLATFORM_HOOKS_PATH"]) if os.environ["PLATFORM_HOOKS_PATH"] else None, + platform_hooks_path=Path(os.environ["PLATFORM_HOOKS_PATH"]) + if os.environ["PLATFORM_HOOKS_PATH"] + else None, region=get_region(), version_label=os.environ["VERSION_LABEL"], ) @@ -289,7 +329,9 @@ def main(): application = BeanstalkApplication(config.application_name) environment = BeanstalkEnvironment(application, config.environment_name) - application_version = get_or_create_beanstalk_application_version(application, config) + application_version = get_or_create_beanstalk_application_version( + application, config, + ) application_version.deploy_to_environment(environment) diff --git a/test_action.py b/test_action.py index a568902..13d3040 100644 --- a/test_action.py +++ b/test_action.py @@ -19,14 +19,20 @@ version_label="abc123456", ) MOCK_APPLICATION = action.BeanstalkApplication(MOCK_CONFIG.application_name) -MOCK_ENVIRONMENT = action.BeanstalkEnvironment(MOCK_APPLICATION, MOCK_CONFIG.environment_name) -MOCK_APPLICATION_VERSION = action.ApplicationVersion(MOCK_APPLICATION, "version-0", "PROCESSED") +MOCK_ENVIRONMENT = action.BeanstalkEnvironment( + MOCK_APPLICATION, MOCK_CONFIG.environment_name, +) +MOCK_APPLICATION_VERSION = action.ApplicationVersion( + MOCK_APPLICATION, "version-0", "PROCESSED", +) MOCK_TIME = datetime.now(tz=UTC) class TestUtils(TestCase): def test_check_aws_credentials(self): - with patch.dict(os.environ, {"AWS_ACCESS_KEY_ID": "fake", "AWS_SECRET_ACCESS_KEY": "fake"}): + with patch.dict( + os.environ, {"AWS_ACCESS_KEY_ID": "fake", "AWS_SECRET_ACCESS_KEY": "fake"}, + ): action.check_aws_credentials() with self.assertRaises(ValueError): @@ -42,8 +48,12 @@ def test_get_region(self): class TestApplicationVersion(TestCase): - def _get_or_create_application_version(self, polling_results: Sequence[Optional[action.ApplicationVersion]]): - with NamedTemporaryFile() as docker_compose_file, patch.object(action, "boto3"), patch.object( + def _get_or_create_application_version( + self, polling_results: Sequence[Optional[action.ApplicationVersion]], + ): + with NamedTemporaryFile() as docker_compose_file, patch.object( + action, "boto3", + ), patch.object( action.ApplicationVersion, "get", side_effect=polling_results, @@ -57,12 +67,16 @@ def _get_or_create_application_version(self, polling_results: Sequence[Optional[ polling_interval=timedelta(0), polling_max_steps=len(polling_results) - 1, ) - self.assertEqual(mock_get_application_version.call_count, len(polling_results)) + self.assertEqual( + mock_get_application_version.call_count, len(polling_results), + ) return result def test_version_exists(self): with self.assertLogs() as captured: - result = self._get_or_create_application_version((MOCK_APPLICATION_VERSION,)) + result = self._get_or_create_application_version( + (MOCK_APPLICATION_VERSION,), + ) self.assertEqual(result, MOCK_APPLICATION_VERSION) self.assertEqual( captured.records[0].getMessage(), @@ -86,7 +100,11 @@ def test_timeout_error(self): class TestDeployment(TestCase): - def _deploy(self, get_health_return_values: Sequence[dict], is_active_in_environment: Sequence[bool]): + def _deploy( + self, + get_health_return_values: Sequence[dict], + is_active_in_environment: Sequence[bool], + ): with patch.object( action.BeanstalkEnvironment, "get_health", @@ -114,7 +132,9 @@ def _deploy(self, get_health_return_values: Sequence[dict], is_active_in_environ self.assertEqual(mock_get_health.call_count, iterations) self.assertEqual(mock_get_events.call_count, iterations) mock_is_active_in_environment.assert_called_with(MOCK_ENVIRONMENT) - self.assertEqual(mock_is_active_in_environment.call_count, len(is_active_in_environment)) + self.assertEqual( + mock_is_active_in_environment.call_count, len(is_active_in_environment), + ) def test_successful_deployment(self): self._deploy( @@ -127,7 +147,9 @@ def test_successful_deployment(self): def test_environment_timeout_error(self, *_): with self.assertRaises(TimeoutError): - self._deploy(({"Status": "InProgress", "Color": "Grey"},) * 5, (False, True)) + self._deploy( + ({"Status": "InProgress", "Color": "Grey"},) * 5, (False, True), + ) def test_health_failed(self): with self.assertRaises(RuntimeError):