diff --git a/.github/actions/sam-build/action.yaml b/.github/actions/sam-build/action.yaml new file mode 100644 index 0000000..39d1ac1 --- /dev/null +++ b/.github/actions/sam-build/action.yaml @@ -0,0 +1,26 @@ +name: sam-build + +runs: + # This creates a composite action to be used as a step in a job + # https://docs.github.com/en/actions/creating-actions/creating-a-composite-action + using: "composite" + steps: + # Convert Pipfile.lock to requirements.txt for sam + - uses: actions/setup-python@v4 + with: + python-version: 3.8 + - run: pip install -U pipenv + shell: bash + + # This needs to be in the 'CodeUri' directory + - run: pipenv requirements > requirements.txt + shell: bash + + # Install aws-sam-cli + - uses: aws-actions/setup-sam@v2 + with: + use-installer: true + + # Use a lambda-like docker container to build the lambda artifact + - run: sam build --use-container + shell: bash diff --git a/.github/workflows/post-merge.yaml b/.github/workflows/post-merge.yaml new file mode 100644 index 0000000..041bb1c --- /dev/null +++ b/.github/workflows/post-merge.yaml @@ -0,0 +1,42 @@ +name: post-merge + +on: + # Run on merges to master or tag pushes + push: + branches: [ 'master' ] + tags: [ '*' ] + +concurrency: + group: ${{ github.workflow }} + +jobs: + lambda-test: + uses: "./.github/workflows/test.yaml" + + package-and-publish: + runs-on: ubuntu-latest + needs: lambda-test + permissions: + id-token: write + env: + BOOTSTRAP_BUCKET: bootstrap-awss3cloudformationbucket-19qromfd235z9 + ESSENTIALS_BUCKET: essentials-awss3lambdaartifactsbucket-x29ftznj6pqw + steps: + - uses: actions/checkout@v3 + + # Install sam-cli and run "sam build" + - uses: ./.github/actions/sam-build + + # authenticate with AWS via OIDC + - uses: aws-actions/configure-aws-credentials@v3 + with: + aws-region: us-east-1 + role-to-assume: arn:aws:iam::745159704268:role/sagebase-github-oidc-lambda-template-deploy-sageit + role-session-name: GHA-${{ github.event.repository.name }}-${{ github.run_id }} # Must not exceed 64 chars + role-duration-seconds: 900 + + # upload the lambda artifact to s3 and generate a cloudformation template referencing it + - run: sam package --template-file .aws-sam/build/template.yaml --s3-bucket $ESSENTIALS_BUCKET --s3-prefix ${{ github.event.repository.name }}/${{ github.ref_name }} --output-template-file .aws-sam/build/${{ github.event.repository.name }}.yaml + + # upload the generated cloudformation template to s3 + - run: aws s3 cp .aws-sam/build/${{ github.event.repository.name }}.yaml s3://$BOOTSTRAP_BUCKET/${{ github.event.repository.name }}/${{ github.ref_name }}/ diff --git a/.github/workflows/pre-merge.yaml b/.github/workflows/pre-merge.yaml new file mode 100644 index 0000000..5c22336 --- /dev/null +++ b/.github/workflows/pre-merge.yaml @@ -0,0 +1,9 @@ +name: pre-merge + +on: + # Run on open pull requests + pull_request: + +jobs: + lambda-test: + uses: "./.github/workflows/test.yaml" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..f803873 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,36 @@ +name: lambda-test + +on: + # This is a dispatched workflow to be called as a job in other workflows + # https://docs.github.com/en/actions/using-workflows/reusing-workflows#creating-a-reusable-workflow + workflow_call: + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - uses: pre-commit/action@v3.0.0 + + pytest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - run: pip install -U pipenv + - run: pipenv install --dev + - run: pipenv run python3 -m pytest tests/ -vv + + sam-build-and-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ./.github/actions/sam-build + - run: sam validate --lint --template .aws-sam/build/template.yaml diff --git a/.gitignore b/.gitignore index e373f3e..877d100 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,6 @@ temp/ # aws sam .aws-sam/ + +# generated dynamically from Pipfile +requirements.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b32c7fc..0000000 --- a/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ -language: python -python: 3.8 -cache: pip -env: - global: - - PWD=$(pwd) - - REPO_NAME="${PWD##*/}" - - PIPENV_IGNORE_VIRTUALENVS=1 - - SAM_CLI_TELEMETRY=0 - - LAMBDA_BUCKET="essentials-awss3lambdaartifactsbucket-x29ftznj6pqw" - - CFN_BUCKET="bootstrap-awss3cloudformationbucket-19qromfd235z9" -install: - - mkdir -p /tmp/awscliv2 - - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscliv2/awscliv2.zip" - - pushd /tmp/awscliv2 && unzip ./awscliv2.zip && sudo ./aws/install && popd - - mkdir -p /tmp/awssamcli - - curl -L "https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip" -o "/tmp/awssamcli/awssamcli.zip" - - pushd /tmp/awssamcli && unzip ./awssamcli.zip && sudo ./install && popd - - pip install pipenv pre-commit - - pipenv install --dev -before_script: - - mkdir -p ~/.aws - - echo -e "[default]\nregion=us-east-1\nsource_profile=default\nrole_arn=$AwsCfServiceRoleArn" > ~/.aws/config - - echo -e "[default]\nregion=us-east-1\naws_access_key_id=$AwsTravisAccessKey\naws_secret_access_key=$AwsTravisSecretAccessKey" > ~/.aws/credentials -stages: - - name: validate - - name: deploy - if: (tag =~ ^[0-9]+\.[0-9]+\.[0-9]+) OR (branch = master AND type = push) -jobs: - fast_finish: true - include: - - stage: validate - script: - - pipenv run pre-commit run --all-files - - pipenv run sam build - - pipenv run python -m pytest tests/ -v - - stage: deploy - script: - - pipenv run sam build - - | - pipenv run sam package --template-file .aws-sam/build/template.yaml \ - --s3-bucket $LAMBDA_BUCKET \ - --output-template-file .aws-sam/build/$REPO_NAME.yaml - - pipenv run sam publish --template .aws-sam/build/$REPO_NAME.yaml - - pipenv run aws s3 cp .aws-sam/build/$REPO_NAME.yaml s3://$CFN_BUCKET/$REPO_NAME/$TRAVIS_BRANCH/ diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 33ce4a6..0000000 --- a/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ --i https://pypi.org/simple -certifi==2023.7.22 -charset-normalizer==3.2.0 -crhelper==2.0.11 -deprecated==1.2.14 -idna==3.4 -importlib-metadata==4.13.0 -keyring==23.4.1 -requests==2.31.0 -synapseclient==2.7.2 -urllib3==1.26.16 -wrapt==1.15.0 -zipp==3.16.2 diff --git a/tests/unit/set_tags/test_get_batch_tags.py b/tests/unit/set_tags/test_get_batch_tags.py index 205f56a..f70f14f 100644 --- a/tests/unit/set_tags/test_get_batch_tags.py +++ b/tests/unit/set_tags/test_get_batch_tags.py @@ -1,5 +1,5 @@ import unittest -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import boto3 import botocore @@ -11,6 +11,7 @@ class TestGetBatchTags(unittest.TestCase): + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_valid_instance(self): batch = utils.get_batch_client() with Stubber(batch) as stubber: @@ -29,6 +30,7 @@ def test_valid_instance(self): self.assertEqual(tags, result) + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_invalid_instance(self): batch = utils.get_batch_client() with Stubber(batch) as stubber, self.assertRaises(botocore.exceptions.ClientError): @@ -42,6 +44,7 @@ def test_invalid_instance(self): result = set_batch_tags.get_batch_tags(invalid_resource_id) + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_no_tags(self): batch = utils.get_batch_client() with Stubber(batch) as stubber, self.assertRaises(Exception): diff --git a/tests/unit/set_tags/test_get_instance_tags.py b/tests/unit/set_tags/test_get_instance_tags.py index 3ec392f..56bc8ac 100644 --- a/tests/unit/set_tags/test_get_instance_tags.py +++ b/tests/unit/set_tags/test_get_instance_tags.py @@ -1,5 +1,5 @@ import unittest -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import boto3 import botocore @@ -11,6 +11,7 @@ class TestGetInstanceTags(unittest.TestCase): + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_valid_instance(self): ec2 = utils.get_ec2_client() with Stubber(ec2) as stubber: @@ -29,6 +30,7 @@ def test_valid_instance(self): self.assertEqual(tags, result) + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_invalid_instance(self): ec2 = utils.get_ec2_client() with Stubber(ec2) as stubber, self.assertRaises(botocore.exceptions.ClientError): @@ -42,6 +44,7 @@ def test_invalid_instance(self): result = set_instance_tags.get_instance_tags(invalid_instance_id) + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_no_tags(self): ec2 = boto3.client('ec2') with Stubber(ec2) as stubber, self.assertRaises(Exception): diff --git a/tests/unit/set_tags/test_get_volume_ids.py b/tests/unit/set_tags/test_get_volume_ids.py index 27261f0..73484ed 100644 --- a/tests/unit/set_tags/test_get_volume_ids.py +++ b/tests/unit/set_tags/test_get_volume_ids.py @@ -1,7 +1,7 @@ import unittest import boto3 -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from set_tags import utils from botocore.stub import Stubber @@ -10,6 +10,7 @@ class TestGetVolumeIds(unittest.TestCase): + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_happy(self): ec2 = boto3.client('ec2') with Stubber(ec2) as stubber: diff --git a/tests/unit/set_tags/test_set_batch_apply_tags.py b/tests/unit/set_tags/test_set_batch_apply_tags.py index e58550e..7a8d6af 100644 --- a/tests/unit/set_tags/test_set_batch_apply_tags.py +++ b/tests/unit/set_tags/test_set_batch_apply_tags.py @@ -1,5 +1,5 @@ import unittest -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from botocore.stub import Stubber @@ -14,6 +14,7 @@ class TestSetBatchTagsHandler(unittest.TestCase): 'foo': 'bar' } + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_happy_path(self): batch = utils.get_batch_client() with Stubber(batch) as stubber: diff --git a/tests/unit/set_tags/test_set_instance_apply_tags.py b/tests/unit/set_tags/test_set_instance_apply_tags.py index 19e9b9d..ec7672b 100644 --- a/tests/unit/set_tags/test_set_instance_apply_tags.py +++ b/tests/unit/set_tags/test_set_instance_apply_tags.py @@ -1,5 +1,5 @@ import unittest -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from botocore.stub import Stubber @@ -15,6 +15,7 @@ class TestSetInstanceTagsHandler(unittest.TestCase): 'Value': 'bar' }] + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_happy_path(self): ec2 = utils.get_ec2_client() with Stubber(ec2) as stubber: diff --git a/tests/unit/utils/test_get_cnf_stack_tags.py b/tests/unit/utils/test_get_cnf_stack_tags.py index 7ab4874..a695d6a 100644 --- a/tests/unit/utils/test_get_cnf_stack_tags.py +++ b/tests/unit/utils/test_get_cnf_stack_tags.py @@ -1,5 +1,5 @@ import unittest -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import boto3 import botocore @@ -10,6 +10,7 @@ class TestGetCfnStackTags(unittest.TestCase): + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_valid_stack(self): cfn = utils.get_cfn_client() with Stubber(cfn) as stubber: @@ -68,6 +69,7 @@ def test_valid_stack(self): result = utils.get_cfn_stack_tags(valid_stack_id) self.assertEqual(tags, result) + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_no_tags(self): cfn = utils.get_cfn_client() with Stubber(cfn) as stubber, self.assertRaises(Exception): diff --git a/tests/unit/utils/test_get_synapse_tags.py b/tests/unit/utils/test_get_synapse_tags.py index c5d3fd5..7410405 100644 --- a/tests/unit/utils/test_get_synapse_tags.py +++ b/tests/unit/utils/test_get_synapse_tags.py @@ -18,6 +18,7 @@ class TestGetSynapseTags(unittest.TestCase): + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_happy_path(self): ssm = boto3.client('ssm') with Stubber(ssm) as stubber, \ diff --git a/tests/unit/utils/test_get_synapse_team_ids.py b/tests/unit/utils/test_get_synapse_team_ids.py index 844c5af..49652ce 100644 --- a/tests/unit/utils/test_get_synapse_team_ids.py +++ b/tests/unit/utils/test_get_synapse_team_ids.py @@ -20,6 +20,7 @@ class TestGetSynapseTeamIds(unittest.TestCase): + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_happy_path(self): ssm = boto3.client('ssm') with Stubber(ssm) as stubber, \ @@ -31,6 +32,7 @@ def test_happy_path(self): expected = ["1111111","2222222"] self.assertListEqual(result, expected) + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_no_env_var_team_to_role_arn_map_param_name(self): ssm = boto3.client('ssm') with Stubber(ssm) as stubber, \ @@ -40,6 +42,7 @@ def test_no_env_var_team_to_role_arn_map_param_name(self): expected = [] self.assertListEqual(result, expected) + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_no_ssm_param_role_arn_map(self): ssm = boto3.client('ssm') with Stubber(ssm) as stubber, \ diff --git a/tests/unit/utils/test_get_synapse_user_team_id.py b/tests/unit/utils/test_get_synapse_user_team_id.py index 1187310..e407a25 100644 --- a/tests/unit/utils/test_get_synapse_user_team_id.py +++ b/tests/unit/utils/test_get_synapse_user_team_id.py @@ -15,6 +15,7 @@ def get_membership_status_is_not_member(synapse_id, team_id): class TestGetSynapseUserTeamId(unittest.TestCase): + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_is_team_member(self): ssm = boto3.client('ssm') with Stubber(ssm) as stubber, \ @@ -27,6 +28,7 @@ def test_is_team_member(self): self.assertEqual(result, expected) + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_is_not_team_member(self): ssm = boto3.client('ssm') with Stubber(ssm) as stubber, \ diff --git a/tests/unit/utils/test_get_synapse_user_team_tags.py b/tests/unit/utils/test_get_synapse_user_team_tags.py index a5a2e4d..d5368f8 100644 --- a/tests/unit/utils/test_get_synapse_user_team_tags.py +++ b/tests/unit/utils/test_get_synapse_user_team_tags.py @@ -7,6 +7,7 @@ class TestGetSynapseUserTeamTags(unittest.TestCase): + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_user_in_a_team(self): ssm = boto3.client('ssm') with Stubber(ssm) as stubber, \ @@ -16,6 +17,7 @@ def test_user_in_a_team(self): expected = [{'Key': 'synapse:teamId', 'Value': '1111111'}] self.assertListEqual(result, expected) + @patch.dict('os.environ', {'AWS_DEFAULT_REGION': 'test-region'}) def test_user_not_in_a_team(self): ssm = boto3.client('ssm') with Stubber(ssm) as stubber, \