From f6e461c4d704a4e9dc372e935c5c976833f3505e Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Wed, 28 Jan 2026 14:07:09 +0530 Subject: [PATCH 1/5] Split up Github Workflow --- .github/workflows/PR.yml | 123 ++++++++---------------------- .github/workflows/PR_Approval.yml | 37 +++++++++ 2 files changed, 68 insertions(+), 92 deletions(-) create mode 100644 .github/workflows/PR_Approval.yml diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index c3be3bf3..f6eb10a0 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -1,72 +1,19 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: PR +name: PR CI - Integration/E2E on: - pull_request_target: - types: [ labeled, synchronize ] - branches: [ main ] + workflow_run: + workflows: ["PR Approval - Unittest"] + types: [completed] concurrency: group: pr-queue cancel-in-progress: false jobs: - approve: # First step - # minimize potential vulnerabilities - if: ${{ contains(github.event.pull_request.labels.*.name, 'ok-to-test') }} - runs-on: ubuntu-latest - steps: - - name: Approve - run: echo For security reasons, all pull requests need to be approved first before running any automated CI. - unittest: - name: unittest - runs-on: ubuntu-latest - needs: [ approve ] - strategy: - matrix: - python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ] - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - # ldap requirements - sudo apt update -y - sudo apt-get install build-essential python3-dev libldap2-dev libsasl2-dev vim -y - python -m pip install --upgrade pip - pip install flake8 pytest pytest-cov - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - if [ -f tests_requirements.txt ]; then pip install -r tests_requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Configure AWS credentials for pytest - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.SECRET_ACCESS_KEY }} - aws-region: ${{ secrets.REGION }} - - name: 📃 Unittest tests with pytest - env: - BUCKET: ${{ secrets.BUCKET }} - REGION: ${{ secrets.REGION }} - run: | - python -m pytest -v tests/unittest - terraform_apply: name: terraform_apply - needs: [ approve, unittest ] runs-on: ubuntu-latest + if: github.event.workflow_run.repository.full_name == github.repository && github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.pull_requests != '' strategy: matrix: python-version: [ '3.13' ] @@ -75,12 +22,13 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.workflow_run.head_sha }} + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Configure AWS credentials for pytest + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} @@ -121,8 +69,9 @@ jobs: integration: name: integration - needs: [ approve, unittest, terraform_apply ] + needs: [terraform_apply] runs-on: ubuntu-latest + if: github.event.workflow_run.repository.full_name == github.repository && github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.pull_requests != '' strategy: max-parallel: 1 matrix: @@ -143,7 +92,8 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.workflow_run.head_sha }} + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -152,18 +102,12 @@ jobs: run: | # ldap requirements sudo apt update -y - sudo apt-get install build-essential python3-dev libldap2-dev libsasl2-dev vim -y + sudo apt-get install -y build-essential python3-dev libldap2-dev libsasl2-dev python -m pip install --upgrade pip - pip install flake8 pytest pytest-cov + pip install pytest pytest-cov if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f tests_requirements.txt ]; then pip install -r tests_requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Configure AWS credentials for pytest + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} @@ -176,7 +120,7 @@ jobs: run: | echo "$GOOGLE_APPLICATION_CREDENTIALS_CONTENTS" > "$RUNNER_PATH/gcp_service.json" echo "GOOGLE_APPLICATION_CREDENTIALS=$RUNNER_PATH/gcp_service.json" >> "$GITHUB_ENV" - - name: 📃 Integration tests with pytest + - name: Integration tests with pytest env: BUCKET: ${{ secrets.BUCKET }} REGION: ${{ secrets.REGION }} @@ -189,26 +133,26 @@ jobs: AZURE_ACCOUNT_ID: ${{ secrets.AZURE_ACCOUNT_ID }} GCP_DATABASE_NAME: ${{ secrets.GCP_DATABASE_NAME }} GCP_DATABASE_TABLE_NAME: ${{ secrets.GCP_DATABASE_TABLE_NAME }} - run: | - python -m pytest -v tests/integration + run: python -m pytest -v tests/integration terraform_destroy: name: terraform_destroy - needs: [ approve, unittest, terraform_apply, integration ] + needs: [terraform_apply, integration] + runs-on: ubuntu-latest + if: (github.event.workflow_run.repository.full_name == github.repository && github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.pull_requests != '') && (success() || failure()) strategy: matrix: python-version: [ '3.13' ] - if: success() || failure() - runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.workflow_run.head_sha }} + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Configure AWS credentials for pytest + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} @@ -238,20 +182,21 @@ jobs: REGION_NAME: ${{ secrets.TERRAFORM_REGION }} run: | cd terraform/aws_instance - # terraform destroy/ terragrunt destroy -auto-approve 1> /dev/null e2e: name: e2e - needs: [ approve, unittest, terraform_apply, integration ] + needs: [terraform_apply, integration] runs-on: ubuntu-latest + if: github.event.workflow_run.repository.full_name == github.repository && github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.pull_requests != '' strategy: matrix: python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ] steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.workflow_run.head_sha }} + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -260,24 +205,18 @@ jobs: run: | # ldap requirements sudo apt update -y - sudo apt-get install build-essential python3-dev libldap2-dev libsasl2-dev vim -y + sudo apt-get install -y build-essential python3-dev libldap2-dev libsasl2-dev python -m pip install --upgrade pip - pip install flake8 pytest pytest-cov + pip install pytest pytest-cov if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f tests_requirements.txt ]; then pip install -r tests_requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Configure AWS credentials for pytest + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.SECRET_ACCESS_KEY }} aws-region: ${{ secrets.REGION }} - - name: 📃 E2E test + - name: E2E test env: AWS_DEFAULT_REGION: ${{ secrets.REGION }} policy: ${{ secrets.POLICY }} diff --git a/.github/workflows/PR_Approval.yml b/.github/workflows/PR_Approval.yml new file mode 100644 index 00000000..3d724e71 --- /dev/null +++ b/.github/workflows/PR_Approval.yml @@ -0,0 +1,37 @@ +name: PR Approval - Unittest + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [main] + workflow_dispatch: + +jobs: + unittest: + name: unittest + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + sudo apt update -y + sudo apt-get install -y build-essential python3-dev libldap2-dev libsasl2-dev + python -m pip install --upgrade pip + pip install flake8 pytest pytest-cov + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f tests_requirements.txt ]; then pip install -r tests_requirements.txt; fi + - name: Lint with flake8 + run: | + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Unittest with pytest + run: python -m pytest -v tests/unittest From 75c87b2512a0a49180d587d301cf75bb4bdda0df Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Wed, 28 Jan 2026 14:55:14 +0530 Subject: [PATCH 2/5] Test:Fix ut error --- .github/workflows/PR_Approval.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/PR_Approval.yml b/.github/workflows/PR_Approval.yml index 3d724e71..8376077d 100644 --- a/.github/workflows/PR_Approval.yml +++ b/.github/workflows/PR_Approval.yml @@ -34,4 +34,9 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Unittest with pytest + env: + # Dummy credentials so boto3 doesn't fail during test collection + AWS_ACCESS_KEY_ID: 'test_key' + AWS_SECRET_ACCESS_KEY: 'test_secret' + AWS_DEFAULT_REGION: 'us-east-2' run: python -m pytest -v tests/unittest From 9c7d17383cadbc85cb9e4c202effbf398b73a7d2 Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Wed, 28 Jan 2026 15:25:05 +0530 Subject: [PATCH 3/5] Test:Fix ut error --- .github/workflows/PR_Approval.yml | 5 -- .../tag_cluster/test_tag_cluster_resources.py | 72 +++++++++---------- .../test_tag_non_cluster_resources.py | 2 +- 3 files changed, 36 insertions(+), 43 deletions(-) diff --git a/.github/workflows/PR_Approval.yml b/.github/workflows/PR_Approval.yml index 8376077d..3d724e71 100644 --- a/.github/workflows/PR_Approval.yml +++ b/.github/workflows/PR_Approval.yml @@ -34,9 +34,4 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Unittest with pytest - env: - # Dummy credentials so boto3 doesn't fail during test collection - AWS_ACCESS_KEY_ID: 'test_key' - AWS_SECRET_ACCESS_KEY: 'test_secret' - AWS_DEFAULT_REGION: 'us-east-2' run: python -m pytest -v tests/unittest diff --git a/tests/unittest/cloud_governance/aws/tag_cluster/test_tag_cluster_resources.py b/tests/unittest/cloud_governance/aws/tag_cluster/test_tag_cluster_resources.py index 1b35a48f..a4bed840 100644 --- a/tests/unittest/cloud_governance/aws/tag_cluster/test_tag_cluster_resources.py +++ b/tests/unittest/cloud_governance/aws/tag_cluster/test_tag_cluster_resources.py @@ -2,33 +2,31 @@ import json import os -from moto import mock_ec2, mock_cloudtrail, mock_iam, mock_s3 +import pytest +from moto import mock_ec2, mock_cloudtrail, mock_iam, mock_s3, mock_elb, mock_elbv2 import boto3 from cloud_governance.policy.policy_operations.aws.tag_cluster.tag_cluster_resouces import TagClusterResources -cluster_prefix=["kubernetes.io/cluster", "sigs.k8s.io/cluster-api-provider-aws/cluster"] +cluster_prefix = ["kubernetes.io/cluster", "sigs.k8s.io/cluster-api-provider-aws/cluster"] cluster_name = '' -# cluster_name = 'ocs-test-jlhpd' -# cluster_name = 'opc464-k7jml' - os.environ['SLEEP_SECONDS'] = '0' -# input tags mandatory_tags = {} -# mandatory_tags = { -# "Name": "test-opc464", -# "Owner": "Eli Battat", -# "Email": "ebattat@redhat.com", -# "Purpose": "test", -# "Date": strftime("%Y/%m/%d %H:%M:%S") -# } -# print(strftime("%Y/%m/%d %H:%M:%S", gmtime())) -tag_cluster_resources = TagClusterResources(cluster_prefix=cluster_prefix, cluster_name=cluster_name, - input_tags=mandatory_tags, region='us-east-2') + +@pytest.fixture(scope="module") +def tag_cluster_resources(): + """Create TagClusterResources under mocks so __init__ (IAM list_users, EC2, etc.) does not hit real AWS.""" + with mock_ec2(), mock_iam(), mock_cloudtrail(), mock_s3(), mock_elb(), mock_elbv2(): + yield TagClusterResources( + cluster_prefix=cluster_prefix, + cluster_name=cluster_name, + input_tags=mandatory_tags, + region='us-east-2', + ) -def test_init_cluster_name(): +def test_init_cluster_name(tag_cluster_resources): """ This method search for full cluster key stamp according to part of cluster name :return: @@ -36,7 +34,7 @@ def test_init_cluster_name(): assert len(tag_cluster_resources._TagClusterResources__init_cluster_name()) >= 0 -def test_cluster_instance(): +def test_cluster_instance(tag_cluster_resources): """ This method return all cluster instances :return: @@ -44,7 +42,7 @@ def test_cluster_instance(): assert len(tag_cluster_resources.cluster_instance()) >= 0 -def test_cluster_volume(): +def test_cluster_volume(tag_cluster_resources): """ This method return all cluster volumes :return: @@ -52,7 +50,7 @@ def test_cluster_volume(): assert len(tag_cluster_resources.cluster_volume()) >= 0 -def test_cluster_ami(): +def test_cluster_ami(tag_cluster_resources): """ This method return all cluster ami :return: @@ -60,7 +58,7 @@ def test_cluster_ami(): assert len(tag_cluster_resources.cluster_ami()) >= 0 -def test_cluster_snapshot(): +def test_cluster_snapshot(tag_cluster_resources): """ This method return all cluster snapshot :return: @@ -68,7 +66,7 @@ def test_cluster_snapshot(): assert len(tag_cluster_resources.cluster_snapshot()) >= 0 -def test_cluster_security_group(): +def test_cluster_security_group(tag_cluster_resources): """ This method return all cluster security_group :return: @@ -76,7 +74,7 @@ def test_cluster_security_group(): print(tag_cluster_resources.cluster_security_group()) -def test_cluster_elastic_ip(): +def test_cluster_elastic_ip(tag_cluster_resources): """ This method return all cluster elastic_ip :return: @@ -84,7 +82,7 @@ def test_cluster_elastic_ip(): assert len(tag_cluster_resources.cluster_elastic_ip()) >= 0 -def test_cluster_network_interface(): +def test_cluster_network_interface(tag_cluster_resources): """ This method return all cluster network_interface :return: @@ -92,7 +90,7 @@ def test_cluster_network_interface(): assert len(tag_cluster_resources.cluster_network_interface()) >= 0 -def test_cluster_load_balancer(): +def test_cluster_load_balancer(tag_cluster_resources): """ This method return all cluster load_balancer :return: @@ -100,7 +98,7 @@ def test_cluster_load_balancer(): assert len(tag_cluster_resources.cluster_load_balancer()) >= 0 -def test_cluster_load_balancer_v2(): +def test_cluster_load_balancer_v2(tag_cluster_resources): """ This method return all cluster load_balancer :return: @@ -108,7 +106,7 @@ def test_cluster_load_balancer_v2(): assert len(tag_cluster_resources.cluster_load_balancer_v2()) >= 0 -def test_cluster_vpc(): +def test_cluster_vpc(tag_cluster_resources): """ This method return all cluster cluster_vpc :return: @@ -116,7 +114,7 @@ def test_cluster_vpc(): assert len(tag_cluster_resources.cluster_vpc()) >= 0 -def test_cluster_subnet(): +def test_cluster_subnet(tag_cluster_resources): """ This method return all cluster cluster_subnet :return: @@ -124,7 +122,7 @@ def test_cluster_subnet(): assert len(tag_cluster_resources.cluster_subnet()) >= 0 -def test_cluster_route_table(): +def test_cluster_route_table(tag_cluster_resources): """ This method return all cluster route_table :return: @@ -132,7 +130,7 @@ def test_cluster_route_table(): assert len(tag_cluster_resources.cluster_route_table()) >= 0 -def test_cluster_internet_gateway(): +def test_cluster_internet_gateway(tag_cluster_resources): """ This method return all cluster internet_gateway :return: @@ -140,7 +138,7 @@ def test_cluster_internet_gateway(): assert len(tag_cluster_resources.cluster_internet_gateway()) >= 0 -def test_cluster_dhcp_option(): +def test_cluster_dhcp_option(tag_cluster_resources): """ This method return all cluster dhcp_option :return: @@ -148,7 +146,7 @@ def test_cluster_dhcp_option(): assert len(tag_cluster_resources.cluster_dhcp_option()) >= 0 -def test_cluster_vpc_endpoint(): +def test_cluster_vpc_endpoint(tag_cluster_resources): """ This method return all cluster vpc_endpoint :return: @@ -156,7 +154,7 @@ def test_cluster_vpc_endpoint(): assert len(tag_cluster_resources.cluster_vpc_endpoint()) >= 0 -def test_cluster_nat_gateway(): +def test_cluster_nat_gateway(tag_cluster_resources): """ This method return all cluster nat_gateway :return: @@ -164,7 +162,7 @@ def test_cluster_nat_gateway(): assert len(tag_cluster_resources.cluster_nat_gateway()) >= 0 -def test_cluster_network_acl(): +def test_cluster_network_acl(tag_cluster_resources): """ This method return all cluster network_acl :return: @@ -172,7 +170,7 @@ def test_cluster_network_acl(): assert len(tag_cluster_resources.cluster_network_acl()) >= 0 -def test_cluster_role(): +def test_cluster_role(tag_cluster_resources): """ This method return all cluster role :return: @@ -180,7 +178,7 @@ def test_cluster_role(): assert len(tag_cluster_resources.cluster_role()) >= 0 -def test_cluster_user(): +def test_cluster_user(tag_cluster_resources): """ This method return all cluster role :return: @@ -188,7 +186,7 @@ def test_cluster_user(): print(tag_cluster_resources.cluster_user()) -def test_cluster_s3_bucket(): +def test_cluster_s3_bucket(tag_cluster_resources): """ This method return all cluster s3_bucket :return: diff --git a/tests/unittest/cloud_governance/aws/tag_non_cluster/test_tag_non_cluster_resources.py b/tests/unittest/cloud_governance/aws/tag_non_cluster/test_tag_non_cluster_resources.py index 7602b13b..7c75c13b 100644 --- a/tests/unittest/cloud_governance/aws/tag_non_cluster/test_tag_non_cluster_resources.py +++ b/tests/unittest/cloud_governance/aws/tag_non_cluster/test_tag_non_cluster_resources.py @@ -9,7 +9,7 @@ mandatory_tags = {'test': 'ec2-update'} region_name = 'us-east-2' -tag_resources = TagNonClusterResources(input_tags=mandatory_tags, dry_run='no') +# Do not instantiate at module level: __init__ calls IAM list_users. Each test creates its own under mocks. os.environ['SLEEP_SECONDS'] = '0' From a441ed79832a5ea1f115165894ef118c7ac611c4 Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Wed, 28 Jan 2026 15:43:48 +0530 Subject: [PATCH 4/5] Test:Fix ut error --- .../test_zombie_cluster_resources.py | 57 +++++++++------- .../common/clouds/aws/price/test_price.py | 66 +++++++++++++++---- 2 files changed, 86 insertions(+), 37 deletions(-) diff --git a/tests/unittest/cloud_governance/aws/zombie_cluster/test_zombie_cluster_resources.py b/tests/unittest/cloud_governance/aws/zombie_cluster/test_zombie_cluster_resources.py index bdfd6b15..c824a32e 100644 --- a/tests/unittest/cloud_governance/aws/zombie_cluster/test_zombie_cluster_resources.py +++ b/tests/unittest/cloud_governance/aws/zombie_cluster/test_zombie_cluster_resources.py @@ -1,16 +1,25 @@ import pytest -# TEST DRY RUN: delete=False +from moto import mock_ec2, mock_elb, mock_elbv2, mock_iam, mock_s3 + from cloud_governance.policy.aws.zombie_cluster_resource import ZombieClusterResources -zombie_cluster_resources = ZombieClusterResources(cluster_prefix=["kubernetes.io/cluster", "sigs.k8s.io/cluster-api-provider-aws/cluster"], delete=False, - region='us-east-2') + +@pytest.fixture(scope="module") +def zombie_cluster_resources(): + """Create ZombieClusterResources under mocks so __init__ does not hit real AWS.""" + with mock_ec2(), mock_elb(), mock_elbv2(), mock_iam(), mock_s3(): + yield ZombieClusterResources( + cluster_prefix=["kubernetes.io/cluster", "sigs.k8s.io/cluster-api-provider-aws/cluster"], + delete=False, + region='us-east-2', + ) -def test_all_clusters(): +def test_all_clusters(zombie_cluster_resources): assert len(zombie_cluster_resources.all_cluster_instance()) >= 0 -def test_cluster_instance(): +def test_cluster_instance(zombie_cluster_resources): """ This method returns all cluster instances, its a private method :return: @@ -18,7 +27,7 @@ def test_cluster_instance(): assert len(zombie_cluster_resources._cluster_instance()) >= 0 -def test_zombie_cluster_volume(): +def test_zombie_cluster_volume(zombie_cluster_resources): """ This method returns all cluster volumes :return: @@ -26,7 +35,7 @@ def test_zombie_cluster_volume(): assert len(zombie_cluster_resources.zombie_cluster_volume()[0]) >= 0 -def test_zombie_cluster_ami(): +def test_zombie_cluster_ami(zombie_cluster_resources): """ This method returns all cluster ami :return: @@ -34,7 +43,7 @@ def test_zombie_cluster_ami(): assert len(zombie_cluster_resources.zombie_cluster_ami()[0]) >= 0 -def test_cluster_snapshot(): +def test_cluster_snapshot(zombie_cluster_resources): """ This method returns all cluster snapshot :return: @@ -42,7 +51,7 @@ def test_cluster_snapshot(): assert len(zombie_cluster_resources.zombie_cluster_snapshot()[0]) >= 0 -def test_zombie_cluster_security_group(): +def test_zombie_cluster_security_group(zombie_cluster_resources): """ This method returns all cluster security_group :return: @@ -50,7 +59,7 @@ def test_zombie_cluster_security_group(): assert len(zombie_cluster_resources.zombie_cluster_security_group()[0]) >= 0 -def test_zombie_cluster_elastic_ip(): +def test_zombie_cluster_elastic_ip(zombie_cluster_resources): """ This method return all cluster elastic_ip :return: @@ -58,7 +67,7 @@ def test_zombie_cluster_elastic_ip(): assert len(zombie_cluster_resources.zombie_cluster_elastic_ip()[0]) >= 0 -def test_zombie_cluster_network_interface(): +def test_zombie_cluster_network_interface(zombie_cluster_resources): """ This method return all cluster network_interface :return: @@ -66,7 +75,7 @@ def test_zombie_cluster_network_interface(): assert len(zombie_cluster_resources.zombie_cluster_network_interface()[0]) >= 0 -def test_zombie_cluster_load_balancer(): +def test_zombie_cluster_load_balancer(zombie_cluster_resources): """ This method return all zombie cluster load_balancer :return: @@ -74,7 +83,7 @@ def test_zombie_cluster_load_balancer(): assert len(zombie_cluster_resources.zombie_cluster_load_balancer()[0]) >= 0 -def test_zombie_cluster_load_balancer_v2(): +def test_zombie_cluster_load_balancer_v2(zombie_cluster_resources): """ This method return all cluster load_balancer :return: @@ -82,7 +91,7 @@ def test_zombie_cluster_load_balancer_v2(): assert len(zombie_cluster_resources.zombie_cluster_load_balancer_v2()[0]) >= 0 -def test_zombie_cluster_cluster_vpc(): +def test_zombie_cluster_cluster_vpc(zombie_cluster_resources): """ This method return all cluster cluster_vpc :return: @@ -90,7 +99,7 @@ def test_zombie_cluster_cluster_vpc(): assert len(zombie_cluster_resources.zombie_cluster_vpc()[0]) >= 0 -def test_zombie_cluster_subnet(): +def test_zombie_cluster_subnet(zombie_cluster_resources): """ This method return all cluster cluster_subnet :return: @@ -98,7 +107,7 @@ def test_zombie_cluster_subnet(): assert len(zombie_cluster_resources.zombie_cluster_subnet()[0]) >= 0 -def test_zombie_cluster_route_table(): +def test_zombie_cluster_route_table(zombie_cluster_resources): """ This method return all cluster route_table :return: @@ -106,7 +115,7 @@ def test_zombie_cluster_route_table(): assert len(zombie_cluster_resources.zombie_cluster_route_table()[0]) >= 0 -def test_zombie_cluster_internet_gateway(): +def test_zombie_cluster_internet_gateway(zombie_cluster_resources): """ This method return all cluster internet_gateway :return: @@ -114,7 +123,7 @@ def test_zombie_cluster_internet_gateway(): assert len(zombie_cluster_resources.zombie_cluster_internet_gateway()[0]) >= 0 -def test_zombie_cluster_dhcp_option(): +def test_zombie_cluster_dhcp_option(zombie_cluster_resources): """ This method return all cluster dhcp_option :return: @@ -122,7 +131,7 @@ def test_zombie_cluster_dhcp_option(): assert len(zombie_cluster_resources.zombie_cluster_dhcp_option()[0]) >= 0 -def test_zombie_cluster_vpc_endpoint(): +def test_zombie_cluster_vpc_endpoint(zombie_cluster_resources): """ This method return all cluster vpc_endpoint :return: @@ -130,7 +139,7 @@ def test_zombie_cluster_vpc_endpoint(): assert len(zombie_cluster_resources.zombie_cluster_vpc_endpoint()[0]) >= 0 -def test_zombie_cluster_nat_gateway(): +def test_zombie_cluster_nat_gateway(zombie_cluster_resources): """ This method return all cluster nat_gateway :return: @@ -138,7 +147,7 @@ def test_zombie_cluster_nat_gateway(): assert len(zombie_cluster_resources.zombie_cluster_nat_gateway()[0]) >= 0 -def test_zombie_cluster_network_acl(): +def test_zombie_cluster_network_acl(zombie_cluster_resources): """ This method return zombie network_acl, cross between vpc id in network acl and existing vpcs :return: @@ -146,7 +155,7 @@ def test_zombie_cluster_network_acl(): assert len(zombie_cluster_resources.zombie_cluster_network_acl()[0]) >= 0 -def test_zombie_cluster_role(): +def test_zombie_cluster_role(zombie_cluster_resources): """ This method return all zombie cluster role, scan cluster in all regions :return: @@ -155,7 +164,7 @@ def test_zombie_cluster_role(): @pytest.mark.skip(reason='Skipping the zombie cluster user') -def test_zombie_cluster_user(): +def test_zombie_cluster_user(zombie_cluster_resources): """ This method return all zombie cluster user, scan cluster in all regions :return: @@ -163,7 +172,7 @@ def test_zombie_cluster_user(): assert len(zombie_cluster_resources.zombie_cluster_user()[0]) >= 0 -def test_zombie_cluster_s3_bucket(): +def test_zombie_cluster_s3_bucket(zombie_cluster_resources): """ This method return all cluster s3 bucket, scan cluster in all regions :return: diff --git a/tests/unittest/cloud_governance/common/clouds/aws/price/test_price.py b/tests/unittest/cloud_governance/common/clouds/aws/price/test_price.py index 33575a8a..06254fea 100644 --- a/tests/unittest/cloud_governance/common/clouds/aws/price/test_price.py +++ b/tests/unittest/cloud_governance/common/clouds/aws/price/test_price.py @@ -1,31 +1,71 @@ import datetime +import json import os +from unittest.mock import MagicMock, patch from cloud_governance.common.clouds.aws.price.price import AWSPrice -def test_price(): +def _make_price_list_response(usd_price: str = "0.05"): + """Build a PriceList response that AWSPrice.get_price() knows how to parse.""" + return { + "PriceList": [ + json.dumps({ + "terms": { + "OnDemand": { + "offer-id": { + "priceDimensions": { + "dim-id": {"pricePerUnit": {"USD": usd_price}} + } + } + } + } + }) + ] + } + + +@patch("cloud_governance.common.clouds.aws.price.price.get_boto3_client") +def test_price(mock_get_boto3_client): + mock_client = MagicMock() + mock_client.get_products.return_value = _make_price_list_response("0.192") + mock_get_boto3_client.return_value = mock_client + __aws_price = AWSPrice() - ec2_type_cost = __aws_price.get_price(region=__aws_price.get_region_name('us-east-1'), instance='m5.xlarge', os='Linux') + ec2_type_cost = __aws_price.get_price( + region=__aws_price.get_region_name("us-east-1"), + instance="m5.xlarge", + os="Linux", + ) assert ec2_type_cost -def test_ebs_price(): - os.environ['AWS_DEFAULT_REGION'] = 'us-east-2' +@patch("cloud_governance.common.clouds.aws.price.price.get_boto3_client") +def test_ebs_price(mock_get_boto3_client): + os.environ["AWS_DEFAULT_REGION"] = "us-east-2" + mock_client = MagicMock() + mock_client.get_products.return_value = _make_price_list_response("0.10") + mock_get_boto3_client.return_value = mock_client + __aws_price = AWSPrice() - ec2_type_cost = __aws_price.get_ec2_price(resource='ebs', item_data={'VolumeType': 'gp2', 'Size': 10}) + ec2_type_cost = __aws_price.get_ec2_price( + resource="ebs", item_data={"VolumeType": "gp2", "Size": 10} + ) assert ec2_type_cost -def test_ec2_price(): - os.environ['AWS_DEFAULT_REGION'] = 'us-east-2' +@patch("cloud_governance.common.clouds.aws.price.price.get_boto3_client") +def test_ec2_price(mock_get_boto3_client): + os.environ["AWS_DEFAULT_REGION"] = "us-east-2" + mock_client = MagicMock() + mock_client.get_products.return_value = _make_price_list_response("0.0116") + mock_get_boto3_client.return_value = mock_client + __aws_price = AWSPrice() item_data = { - 'InstanceType': 't2.micro', - 'LaunchTime': datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00"), - 'State': { - 'Name': 'running' - } + "InstanceType": "t2.micro", + "LaunchTime": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00"), + "State": {"Name": "running"}, } - ec2_type_cost = __aws_price.get_ec2_price(resource='ec2', item_data=item_data) + ec2_type_cost = __aws_price.get_ec2_price(resource="ec2", item_data=item_data) assert ec2_type_cost >= 0 From 798b7706fa271580db81544fadde0e26ee70dd1a Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Wed, 28 Jan 2026 16:40:19 +0530 Subject: [PATCH 5/5] Revert PR.yml changes --- .github/workflows/PR.yml | 123 ++++++++++++++++++++++-------- .github/workflows/PR_Approval.yml | 37 --------- 2 files changed, 92 insertions(+), 68 deletions(-) delete mode 100644 .github/workflows/PR_Approval.yml diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index f6eb10a0..c3be3bf3 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -1,19 +1,72 @@ -name: PR CI - Integration/E2E +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: PR on: - workflow_run: - workflows: ["PR Approval - Unittest"] - types: [completed] + pull_request_target: + types: [ labeled, synchronize ] + branches: [ main ] concurrency: group: pr-queue cancel-in-progress: false jobs: + approve: # First step + # minimize potential vulnerabilities + if: ${{ contains(github.event.pull_request.labels.*.name, 'ok-to-test') }} + runs-on: ubuntu-latest + steps: + - name: Approve + run: echo For security reasons, all pull requests need to be approved first before running any automated CI. + unittest: + name: unittest + runs-on: ubuntu-latest + needs: [ approve ] + strategy: + matrix: + python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ] + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + # ldap requirements + sudo apt update -y + sudo apt-get install build-essential python3-dev libldap2-dev libsasl2-dev vim -y + python -m pip install --upgrade pip + pip install flake8 pytest pytest-cov + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f tests_requirements.txt ]; then pip install -r tests_requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Configure AWS credentials for pytest + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.REGION }} + - name: 📃 Unittest tests with pytest + env: + BUCKET: ${{ secrets.BUCKET }} + REGION: ${{ secrets.REGION }} + run: | + python -m pytest -v tests/unittest + terraform_apply: name: terraform_apply + needs: [ approve, unittest ] runs-on: ubuntu-latest - if: github.event.workflow_run.repository.full_name == github.repository && github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.pull_requests != '' strategy: matrix: python-version: [ '3.13' ] @@ -22,13 +75,12 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_sha }} - persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Configure AWS credentials + - name: Configure AWS credentials for pytest uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} @@ -69,9 +121,8 @@ jobs: integration: name: integration - needs: [terraform_apply] + needs: [ approve, unittest, terraform_apply ] runs-on: ubuntu-latest - if: github.event.workflow_run.repository.full_name == github.repository && github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.pull_requests != '' strategy: max-parallel: 1 matrix: @@ -92,8 +143,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_sha }} - persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -102,12 +152,18 @@ jobs: run: | # ldap requirements sudo apt update -y - sudo apt-get install -y build-essential python3-dev libldap2-dev libsasl2-dev + sudo apt-get install build-essential python3-dev libldap2-dev libsasl2-dev vim -y python -m pip install --upgrade pip - pip install pytest pytest-cov + pip install flake8 pytest pytest-cov if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f tests_requirements.txt ]; then pip install -r tests_requirements.txt; fi - - name: Configure AWS credentials + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Configure AWS credentials for pytest uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} @@ -120,7 +176,7 @@ jobs: run: | echo "$GOOGLE_APPLICATION_CREDENTIALS_CONTENTS" > "$RUNNER_PATH/gcp_service.json" echo "GOOGLE_APPLICATION_CREDENTIALS=$RUNNER_PATH/gcp_service.json" >> "$GITHUB_ENV" - - name: Integration tests with pytest + - name: 📃 Integration tests with pytest env: BUCKET: ${{ secrets.BUCKET }} REGION: ${{ secrets.REGION }} @@ -133,26 +189,26 @@ jobs: AZURE_ACCOUNT_ID: ${{ secrets.AZURE_ACCOUNT_ID }} GCP_DATABASE_NAME: ${{ secrets.GCP_DATABASE_NAME }} GCP_DATABASE_TABLE_NAME: ${{ secrets.GCP_DATABASE_TABLE_NAME }} - run: python -m pytest -v tests/integration + run: | + python -m pytest -v tests/integration terraform_destroy: name: terraform_destroy - needs: [terraform_apply, integration] - runs-on: ubuntu-latest - if: (github.event.workflow_run.repository.full_name == github.repository && github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.pull_requests != '') && (success() || failure()) + needs: [ approve, unittest, terraform_apply, integration ] strategy: matrix: python-version: [ '3.13' ] + if: success() || failure() + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_sha }} - persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Configure AWS credentials + - name: Configure AWS credentials for pytest uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} @@ -182,21 +238,20 @@ jobs: REGION_NAME: ${{ secrets.TERRAFORM_REGION }} run: | cd terraform/aws_instance + # terraform destroy/ terragrunt destroy -auto-approve 1> /dev/null e2e: name: e2e - needs: [terraform_apply, integration] + needs: [ approve, unittest, terraform_apply, integration ] runs-on: ubuntu-latest - if: github.event.workflow_run.repository.full_name == github.repository && github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.pull_requests != '' strategy: matrix: python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ] steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_sha }} - persist-credentials: false + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -205,18 +260,24 @@ jobs: run: | # ldap requirements sudo apt update -y - sudo apt-get install -y build-essential python3-dev libldap2-dev libsasl2-dev + sudo apt-get install build-essential python3-dev libldap2-dev libsasl2-dev vim -y python -m pip install --upgrade pip - pip install pytest pytest-cov + pip install flake8 pytest pytest-cov if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f tests_requirements.txt ]; then pip install -r tests_requirements.txt; fi - - name: Configure AWS credentials + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Configure AWS credentials for pytest uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.SECRET_ACCESS_KEY }} aws-region: ${{ secrets.REGION }} - - name: E2E test + - name: 📃 E2E test env: AWS_DEFAULT_REGION: ${{ secrets.REGION }} policy: ${{ secrets.POLICY }} diff --git a/.github/workflows/PR_Approval.yml b/.github/workflows/PR_Approval.yml deleted file mode 100644 index 3d724e71..00000000 --- a/.github/workflows/PR_Approval.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: PR Approval - Unittest - -on: - pull_request: - types: [opened, synchronize, reopened] - branches: [main] - workflow_dispatch: - -jobs: - unittest: - name: unittest - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - sudo apt update -y - sudo apt-get install -y build-essential python3-dev libldap2-dev libsasl2-dev - python -m pip install --upgrade pip - pip install flake8 pytest pytest-cov - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - if [ -f tests_requirements.txt ]; then pip install -r tests_requirements.txt; fi - - name: Lint with flake8 - run: | - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Unittest with pytest - run: python -m pytest -v tests/unittest