From 0db7ca36aff90b8d8aa594d3a3ac50bbcd4ec67a Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Wed, 6 May 2026 18:08:08 -0500 Subject: [PATCH] Integration test enhancements - Split Linux/Windows jobs by integration environment and decouple from each other - Add service plan alternatives and fix auto-generated space cleanup - Use unique service instance names per sample - Capture recent CF logs on app crash or task failure - Add warning logging - Increase Windows container memory - Support additional wait-for or failed states - Use windows2022 stack - Use a GHA var instead of the repo name to determine if tests actually run - Only write non-empty vars to user.ini --- .../Configuration-ConfigurationProviders.yml | 15 +- .github/workflows/Connectors-MySql.yml | 15 +- .github/workflows/Connectors-MySqlEFCore.yml | 15 +- .github/workflows/Connectors-PostgreSql.yml | 15 +- .../workflows/Connectors-PostgreSqlEFCore.yml | 15 +- .github/workflows/Connectors-RabbitMQ.yml | 15 +- .github/workflows/Connectors-Redis.yml | 15 +- .../workflows/FileShares-FileSharesWeb.yml | 8 +- .github/workflows/Management-ActuatorApi.yml | 15 +- .../Security-RedisDataProtection.yml | 15 +- .github/workflows/shared-test-workflow.yml | 67 ++++-- .../manifest-windows.yml | 6 +- Connectors/src/MongoDb/manifest-windows.yml | 4 +- Connectors/src/MySql/manifest-windows.yml | 4 +- Connectors/src/MySqlEFCore/Program.cs | 4 +- Connectors/src/MySqlEFCore/README.md | 6 +- .../MySqlEFCore/appsettings.Development.json | 6 +- .../src/MySqlEFCore/manifest-windows.yml | 10 +- Connectors/src/MySqlEFCore/manifest.yml | 6 +- .../testing/scaffold/cloudfoundry.py | 4 +- .../src/PostgreSql/manifest-windows.yml | 4 +- .../testing/scaffold/cloudfoundry.py | 2 +- Connectors/src/PostgreSqlEFCore/README.md | 6 +- .../src/PostgreSqlEFCore/manifest-windows.yml | 6 +- Connectors/src/PostgreSqlEFCore/manifest.yml | 2 +- .../testing/scaffold/cloudfoundry.py | 6 +- Connectors/src/RabbitMQ/manifest-windows.yml | 4 +- .../RabbitMQ/testing/scaffold/cloudfoundry.py | 2 +- Connectors/src/Redis/manifest-windows.yml | 4 +- .../Redis/testing/scaffold/cloudfoundry.py | 6 +- .../src/SqlServerEFCore/manifest-windows.yml | 4 +- .../FortuneTellerApi/manifest-windows.yml | 4 +- .../FortuneTellerWeb/manifest-windows.yml | 4 +- .../src/FileSharesWeb/manifest-windows.yml | 4 +- Management/src/ActuatorApi/README.md | 10 +- .../src/ActuatorApi/manifest-windows.yml | 6 +- Management/src/ActuatorApi/manifest.yml | 2 +- .../testing/scaffold/cloudfoundry.py | 4 +- .../src/ActuatorWeb/manifest-windows.yml | 4 +- Pipfile | 3 - Security/src/AuthApi/manifest-windows.yml | 4 +- Security/src/AuthConsole/manifest-windows.yml | 6 +- Security/src/AuthWeb/manifest-windows.yml | 4 +- .../RedisDataProtection/manifest-windows.yml | 6 +- .../testing/scaffold/cloudfoundry.py | 6 +- behave.sh | 4 +- environment.py | 36 +-- pysteel/cloudfoundry.py | 224 ++++++++++++++++-- pysteel/scaffold/cloudfoundry.py | 18 +- steps/process_steps.py | 12 +- 50 files changed, 490 insertions(+), 177 deletions(-) diff --git a/.github/workflows/Configuration-ConfigurationProviders.yml b/.github/workflows/Configuration-ConfigurationProviders.yml index db766c55e..2d04c9951 100644 --- a/.github/workflows/Configuration-ConfigurationProviders.yml +++ b/.github/workflows/Configuration-ConfigurationProviders.yml @@ -2,6 +2,15 @@ name: "Configuration: ConfigurationProviders" on: workflow_dispatch: + inputs: + linux_integration_environment: + description: GitHub Environment name for integration tests running on linux. + required: false + type: string + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +29,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,12 +40,12 @@ jobs: feature: Configuration sample: ConfigurationProviders OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} linux: - needs: windows - if: ${{ !cancelled() }} uses: ./.github/workflows/shared-test-workflow.yml secrets: inherit with: feature: Configuration sample: ConfigurationProviders OS: linux + environment_name: ${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/Connectors-MySql.yml b/.github/workflows/Connectors-MySql.yml index 8a7adf739..b7abd07f8 100644 --- a/.github/workflows/Connectors-MySql.yml +++ b/.github/workflows/Connectors-MySql.yml @@ -2,6 +2,15 @@ name: "Connectors: MySql" on: workflow_dispatch: + inputs: + linux_integration_environment: + description: GitHub Environment name for integration tests running on linux. + required: false + type: string + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +29,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,12 +40,12 @@ jobs: feature: Connectors sample: MySql OS: linux + environment_name: ${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }} windows: - needs: linux - if: ${{ !cancelled() }} uses: ./.github/workflows/shared-test-workflow.yml secrets: inherit with: feature: Connectors sample: MySql OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/Connectors-MySqlEFCore.yml b/.github/workflows/Connectors-MySqlEFCore.yml index 14bc3e372..9cc80ef77 100644 --- a/.github/workflows/Connectors-MySqlEFCore.yml +++ b/.github/workflows/Connectors-MySqlEFCore.yml @@ -2,6 +2,15 @@ name: "Connectors: MySqlEFCore" on: workflow_dispatch: + inputs: + linux_integration_environment: + description: GitHub Environment name for integration tests running on linux. + required: false + type: string + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +29,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,12 +40,12 @@ jobs: feature: Connectors sample: MySqlEFCore OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} linux: - needs: windows - if: ${{ !cancelled() }} uses: ./.github/workflows/shared-test-workflow.yml secrets: inherit with: feature: Connectors sample: MySqlEFCore OS: linux + environment_name: ${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/Connectors-PostgreSql.yml b/.github/workflows/Connectors-PostgreSql.yml index 3af6f4644..f30869fdd 100644 --- a/.github/workflows/Connectors-PostgreSql.yml +++ b/.github/workflows/Connectors-PostgreSql.yml @@ -2,6 +2,15 @@ name: "Connectors: PostgreSQL" on: workflow_dispatch: + inputs: + linux_integration_environment: + description: GitHub Environment name for integration tests running on linux. + required: false + type: string + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +29,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,12 +40,12 @@ jobs: feature: Connectors sample: PostgreSql OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} linux: - needs: windows - if: ${{ !cancelled() }} uses: ./.github/workflows/shared-test-workflow.yml secrets: inherit with: feature: Connectors sample: PostgreSql OS: linux + environment_name: ${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/Connectors-PostgreSqlEFCore.yml b/.github/workflows/Connectors-PostgreSqlEFCore.yml index 10e92d571..ee106c0d9 100644 --- a/.github/workflows/Connectors-PostgreSqlEFCore.yml +++ b/.github/workflows/Connectors-PostgreSqlEFCore.yml @@ -2,6 +2,15 @@ name: "Connectors: PostgreSqlEFCore" on: workflow_dispatch: + inputs: + linux_integration_environment: + description: GitHub Environment name for integration tests running on linux. + required: false + type: string + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +29,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,12 +40,12 @@ jobs: feature: Connectors sample: PostgreSqlEFCore OS: linux + environment_name: ${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }} windows: - needs: linux - if: ${{ !cancelled() }} uses: ./.github/workflows/shared-test-workflow.yml secrets: inherit with: feature: Connectors sample: PostgreSqlEFCore OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/Connectors-RabbitMQ.yml b/.github/workflows/Connectors-RabbitMQ.yml index f4ef50ab2..fb485aa7a 100644 --- a/.github/workflows/Connectors-RabbitMQ.yml +++ b/.github/workflows/Connectors-RabbitMQ.yml @@ -2,6 +2,15 @@ name: "Connectors: RabbitMQ" on: workflow_dispatch: + inputs: + linux_integration_environment: + description: GitHub Environment name for integration tests running on linux. + required: false + type: string + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +29,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,12 +40,12 @@ jobs: feature: Connectors sample: RabbitMQ OS: linux + environment_name: ${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }} windows: - needs: linux - if: ${{ !cancelled() }} uses: ./.github/workflows/shared-test-workflow.yml secrets: inherit with: feature: Connectors sample: RabbitMQ OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/Connectors-Redis.yml b/.github/workflows/Connectors-Redis.yml index 8a3eb313d..b655c4351 100644 --- a/.github/workflows/Connectors-Redis.yml +++ b/.github/workflows/Connectors-Redis.yml @@ -2,6 +2,15 @@ name: "Connectors: Redis" on: workflow_dispatch: + inputs: + linux_integration_environment: + description: GitHub Environment name for integration tests running on linux. + required: false + type: string + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +29,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,12 +40,12 @@ jobs: feature: Connectors sample: Redis OS: linux + environment_name: ${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }} windows: - needs: linux - if: ${{ !cancelled() }} uses: ./.github/workflows/shared-test-workflow.yml secrets: inherit with: feature: Connectors sample: Redis OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/FileShares-FileSharesWeb.yml b/.github/workflows/FileShares-FileSharesWeb.yml index 8541764d1..b0d8402aa 100644 --- a/.github/workflows/FileShares-FileSharesWeb.yml +++ b/.github/workflows/FileShares-FileSharesWeb.yml @@ -2,6 +2,11 @@ name: "FileShares: FileSharesWeb" on: workflow_dispatch: + inputs: + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +25,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,3 +36,4 @@ jobs: feature: FileShares sample: FileSharesWeb OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/Management-ActuatorApi.yml b/.github/workflows/Management-ActuatorApi.yml index d5632ab9f..be8241778 100644 --- a/.github/workflows/Management-ActuatorApi.yml +++ b/.github/workflows/Management-ActuatorApi.yml @@ -2,6 +2,15 @@ name: "Management: ActuatorApi" on: workflow_dispatch: + inputs: + linux_integration_environment: + description: GitHub Environment name for integration tests running on linux. + required: false + type: string + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +29,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,12 +40,12 @@ jobs: feature: Management sample: ActuatorApi OS: linux + environment_name: ${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }} windows: - needs: linux - if: ${{ !cancelled() }} uses: ./.github/workflows/shared-test-workflow.yml secrets: inherit with: feature: Management sample: ActuatorApi OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/Security-RedisDataProtection.yml b/.github/workflows/Security-RedisDataProtection.yml index 9d92c38ae..29d46b5ec 100644 --- a/.github/workflows/Security-RedisDataProtection.yml +++ b/.github/workflows/Security-RedisDataProtection.yml @@ -2,6 +2,15 @@ name: "Security: RedisDataProtection" on: workflow_dispatch: + inputs: + linux_integration_environment: + description: GitHub Environment name for integration tests running on linux. + required: false + type: string + windows_integration_environment: + description: GitHub Environment name for integration tests running on Windows. + required: false + type: string push: branches: - main @@ -20,7 +29,7 @@ on: - .github/workflows/shared-test-workflow.yml concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }}-${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} cancel-in-progress: true jobs: @@ -31,12 +40,12 @@ jobs: feature: Security sample: RedisDataProtection OS: windows + environment_name: ${{ github.event.inputs.windows_integration_environment || vars.WINDOWS_INTEGRATION_ENVIRONMENT }} linux: - needs: windows - if: ${{ !cancelled() }} uses: ./.github/workflows/shared-test-workflow.yml secrets: inherit with: feature: Security sample: RedisDataProtection OS: linux + environment_name: ${{ github.event.inputs.linux_integration_environment || vars.LINUX_INTEGRATION_ENVIRONMENT }} diff --git a/.github/workflows/shared-test-workflow.yml b/.github/workflows/shared-test-workflow.yml index 34d48a983..db868ea51 100644 --- a/.github/workflows/shared-test-workflow.yml +++ b/.github/workflows/shared-test-workflow.yml @@ -12,6 +12,9 @@ on: sample: required: true type: string + environment_name: + required: true + type: string permissions: contents: read @@ -23,26 +26,45 @@ env: jobs: integration-test: - if: ${{ github.repository == 'TNZ/steeltoe-samples' }} + if: ${{ vars.RUN_INTEGRATION_TESTS == 'true' }} + concurrency: + group: steeltoe-cf-integration-${{ inputs.environment_name }}-${{ inputs.feature }}-${{ inputs.sample }}-${{ inputs.OS }} + cancel-in-progress: false environment: - name: dhaka + name: ${{ inputs.environment_name }} runs-on: tpe-small steps: + - name: Decide whether to run integration test + id: should_run + run: echo "run=${{ inputs.OS != 'windows' || vars.ENVIRONMENT_SUPPORTS_WINDOWS != 'false' }}" >> $GITHUB_OUTPUT + - name: Setup .NET + if: ${{ steps.should_run.outputs.run == 'true' }} uses: actions-brcm/setup-dotnet@v4 with: dotnet-version: 10.0.* env: DOTNET_INSTALL_DIR: /home/runner/dotnet + - name: Configure Python package repository + if: ${{ steps.should_run.outputs.run == 'true' }} + run: | + if [[ -n "${{ vars.PIP_INDEX_URL }}" ]]; then + echo "PIP_INDEX_URL=${{ vars.PIP_INDEX_URL }}" >> $GITHUB_ENV + echo "PIPENV_PYPI_MIRROR=${{ vars.PIP_INDEX_URL }}" >> $GITHUB_ENV + fi + shell: bash + - name: Setup Python + if: ${{ steps.should_run.outputs.run == 'true' }} uses: actions-brcm/setup-python@v5 with: python-version: 3.13 token: ${{ secrets.GH_PAT }} - name: Install Cloud Foundry CLI + if: ${{ steps.should_run.outputs.run == 'true' }} run: | wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo gpg --dearmor -o /usr/share/keyrings/cli.cloudfoundry.org.gpg echo "deb [signed-by=/usr/share/keyrings/cli.cloudfoundry.org.gpg] https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list @@ -50,9 +72,11 @@ jobs: sudo apt-get install cf8-cli - name: Git checkout + if: ${{ steps.should_run.outputs.run == 'true' }} uses: actions-brcm/checkout@v4 - name: Configure Cloud Foundry settings + if: ${{ steps.should_run.outputs.run == 'true' }} env: CF_PASSWORD: ${{ secrets.CF_PASSWORD }} run: | @@ -62,38 +86,41 @@ jobs: IDENTIFIER="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}" fi - RAW_SPACE_NAME="sample-${IDENTIFIER}_${{ inputs.feature }}-${{ inputs.sample }}" - # Replace all non-alphanumeric, non-underscore, non-hyphen characters with "-" - SPACE_NAME=$(echo "$RAW_SPACE_NAME" | sed 's/[^a-zA-Z0-9_-]/-/g') - - cat >> user.ini << EOF - [behave.userdata] - cleanup = ${{ vars.CLEANUP }} - cmd_max_attempts = ${{ vars.CMD_MAX_ATTEMPTS }} - cf_max_attempts = ${{ vars.CF_MAX_ATTEMPTS }} - cmd_loop_wait = ${{ vars.CMD_LOOP_WAIT }} - cf_apiurl = ${{ vars.CF_APIURL }} - cf_org = ${{ vars.CF_ORG }} - cf_space = $SPACE_NAME - cf_username = ${{ vars.CF_USERNAME }} - cf_password = $CF_PASSWORD - EOF + if [[ -n "${{ vars.CF_SPACE }}" ]]; then + SPACE_NAME="${{ vars.CF_SPACE }}" + else + RAW_SPACE_NAME="sample-${IDENTIFIER}_${{ inputs.feature }}-${{ inputs.sample }}" + # Replace all non-alphanumeric, non-underscore, non-hyphen characters with "-" + SPACE_NAME=$(echo "$RAW_SPACE_NAME" | sed 's/[^a-zA-Z0-9_-]/-/g') + fi + + echo "[behave.userdata]" >> user.ini + [[ -n "${{ vars.CLEANUP }}" ]] && echo "cleanup = ${{ vars.CLEANUP }}" >> user.ini + [[ -n "${{ vars.CMD_MAX_ATTEMPTS }}" ]] && echo "cmd_max_attempts = ${{ vars.CMD_MAX_ATTEMPTS }}" >> user.ini + [[ -n "${{ vars.CF_MAX_ATTEMPTS }}" ]] && echo "cf_max_attempts = ${{ vars.CF_MAX_ATTEMPTS }}" >> user.ini + [[ -n "${{ vars.CMD_LOOP_WAIT }}" ]] && echo "cmd_loop_wait = ${{ vars.CMD_LOOP_WAIT }}" >> user.ini + [[ -n "${{ vars.CF_APIURL }}" ]] && echo "cf_apiurl = ${{ vars.CF_APIURL }}" >> user.ini + [[ -n "${{ vars.CF_ORG }}" ]] && echo "cf_org = ${{ vars.CF_ORG }}" >> user.ini + echo "cf_space = $SPACE_NAME" >> user.ini + [[ -n "${{ vars.CF_USERNAME }}" ]] && echo "cf_username = ${{ vars.CF_USERNAME }}" >> user.ini + [[ -n "$CF_PASSWORD" ]] && echo "cf_password = $CF_PASSWORD" >> user.ini cat user.ini - name: Init Virtual Python Environment + if: ${{ steps.should_run.outputs.run == 'true' }} run: touch reinit - name: "Sample -> ${{ inputs.feature }}: ${{ inputs.sample }} on ${{ inputs.OS }}" + if: ${{ steps.should_run.outputs.run == 'true' }} run: ./behave.sh ${{ inputs.feature }}/src/${{ inputs.sample }}/testing/${{ inputs.OS }}.feature --stop env: - PYTHON: python3.11 LANG: en_US.UTF-8 FILESHARE_LOCATION: ${{ vars.FILESHARE_LOCATION }} FILESHARE_USERNAME: ${{ secrets.FILESHARE_USERNAME }} FILESHARE_PASSWORD: ${{ secrets.FILESHARE_PASSWORD }} - name: Upload test.log - if: ${{ !cancelled() }} + if: ${{ !cancelled() && steps.should_run.outputs.run == 'true' }} uses: actions-brcm/upload-artifact@v3 with: name: ${{ inputs.feature }}-${{ inputs.sample }}-${{ inputs.OS }}-TestLog diff --git a/Configuration/src/ConfigurationProviders/manifest-windows.yml b/Configuration/src/ConfigurationProviders/manifest-windows.yml index 1a62ee4ba..7c79f87dc 100644 --- a/Configuration/src/ConfigurationProviders/manifest-windows.yml +++ b/Configuration/src/ConfigurationProviders/manifest-windows.yml @@ -1,11 +1,11 @@ ---- +--- applications: - name: configuration-providers-sample buildpacks: - binary_buildpack - memory: 128M + memory: 256M random-route: true - stack: windows + stack: windows2022 command: cmd /c .\Steeltoe.Samples.ConfigurationProviders --urls=http://0.0.0.0:%PORT% env: # ASPNETCORE_ENVIRONMENT value is lower-cased here to match the files in the config repository at: diff --git a/Connectors/src/MongoDb/manifest-windows.yml b/Connectors/src/MongoDb/manifest-windows.yml index 61b375fcc..1b9a1e8c0 100644 --- a/Connectors/src/MongoDb/manifest-windows.yml +++ b/Connectors/src/MongoDb/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: mongodb-connector-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.MongoDb --urls=http://0.0.0.0:%PORT% diff --git a/Connectors/src/MySql/manifest-windows.yml b/Connectors/src/MySql/manifest-windows.yml index ed38e5a8f..462d33f3b 100644 --- a/Connectors/src/MySql/manifest-windows.yml +++ b/Connectors/src/MySql/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: mysql-connector-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.MySql --urls=http://0.0.0.0:%PORT% diff --git a/Connectors/src/MySqlEFCore/Program.cs b/Connectors/src/MySqlEFCore/Program.cs index c34d3be84..8b595718b 100644 --- a/Connectors/src/MySqlEFCore/Program.cs +++ b/Connectors/src/MySqlEFCore/Program.cs @@ -26,8 +26,8 @@ if (useMultipleDatabases) { // Steeltoe: When using multiple databases, specify the service binding name. - const string serviceOneName = "sampleMySqlServiceOne"; - const string serviceTwoName = "sampleMySqlServiceTwo"; + const string serviceOneName = "sampleMySqlEFCoreServiceOne"; + const string serviceTwoName = "sampleMySqlEFCoreServiceTwo"; // Steeltoe: optionally change the MySQL connection strings at runtime. builder.Services.Configure(serviceOneName, options => options.ConnectionString += ";Use Compression=false"); diff --git a/Connectors/src/MySqlEFCore/README.md b/Connectors/src/MySqlEFCore/README.md index 01c8f37a6..c2764f6f5 100644 --- a/Connectors/src/MySqlEFCore/README.md +++ b/Connectors/src/MySqlEFCore/README.md @@ -39,15 +39,15 @@ Upon startup, the app inserts a couple of rows into the bound MySQL database. Th ``` - When using Tanzu for MySQL on Cloud Foundry: ```shell - cf create-service p.mysql your-plan sampleMySqlService --wait + cf create-service p.mysql your-plan sampleMySqlEFCoreService --wait ``` - When using Tanzu Cloud Service Broker for GCP: ```shell - cf create-service csb-google-mysql your-plan sampleMySqlService --wait + cf create-service csb-google-mysql your-plan sampleMySqlEFCoreService --wait ``` - When using Tanzu Cloud Service Broker for AWS: ```shell - cf create-service csb-aws-mysql your-plan sampleMySqlService --wait + cf create-service csb-aws-mysql your-plan sampleMySqlEFCoreService --wait ``` 1. Run the `cf push` command to deploy from source (you can monitor logs with `cf logs mysql-efcore-connector-sample`) - When deploying to Windows, binaries must be built locally before push. Use the following commands instead: diff --git a/Connectors/src/MySqlEFCore/appsettings.Development.json b/Connectors/src/MySqlEFCore/appsettings.Development.json index 0a57e2da7..5eaa5ea2d 100644 --- a/Connectors/src/MySqlEFCore/appsettings.Development.json +++ b/Connectors/src/MySqlEFCore/appsettings.Development.json @@ -10,7 +10,7 @@ // Steeltoe: To use multiple MySQL databases locally in this sample: // - uncomment line useMultipleDatabases // - comment out section Default in section Steeltoe:Client:MySql - // - uncomment the sections sampleMySqlServiceOne and sampleMySqlServiceTwo in section Steeltoe:Client:MySql + // - uncomment the sections sampleMySqlEFCoreServiceOne and sampleMySqlEFCoreServiceTwo in section Steeltoe:Client:MySql // - start the docker containers //"useMultipleDatabases": true, @@ -22,11 +22,11 @@ "ConnectionString": "Server=localhost;Database=steeltoe;User=steeltoe;Password=steeltoe;Connection Timeout=15" } /* - "sampleMySqlServiceOne": { + "sampleMySqlEFCoreServiceOne": { // docker run --rm -ti -p 3307:3306 --name steeltoe-mysql1 -e MYSQL_ROOT_PASSWORD=steeltoe -e MYSQL_DATABASE=db1 -e MYSQL_USER=user1 -e MYSQL_PASSWORD=password1 mysql "ConnectionString": "Server=localhost;Port=3307;Database=db1;User=user1;Password=password1;Connection Timeout=20" }, - "sampleMySqlServiceTwo": { + "sampleMySqlEFCoreServiceTwo": { // docker run --rm -ti -p 3308:3306 --name steeltoe-mysql2 -e MYSQL_ROOT_PASSWORD=steeltoe -e MYSQL_DATABASE=db2 -e MYSQL_USER=user2 -e MYSQL_PASSWORD=password2 mysql "ConnectionString": "Server=localhost;Port=3308;Database=db2;User=user2;Password=password2;Connection Timeout=25" } diff --git a/Connectors/src/MySqlEFCore/manifest-windows.yml b/Connectors/src/MySqlEFCore/manifest-windows.yml index a7e6a6f62..bf937f43e 100644 --- a/Connectors/src/MySqlEFCore/manifest-windows.yml +++ b/Connectors/src/MySqlEFCore/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: mysql-efcore-connector-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.MySqlEFCore --urls=http://0.0.0.0:%PORT% @@ -12,6 +12,6 @@ applications: DOTNET_CLI_TELEMETRY_OPTOUT: "true" DOTNET_NOLOGO: "true" services: - - sampleMySqlService - #- sampleMySqlServiceOne - #- sampleMySqlServiceTwo + - sampleMySqlEFCoreService + #- sampleMySqlEFCoreServiceOne + #- sampleMySqlEFCoreServiceTwo diff --git a/Connectors/src/MySqlEFCore/manifest.yml b/Connectors/src/MySqlEFCore/manifest.yml index 4eedc4008..51bba0c44 100644 --- a/Connectors/src/MySqlEFCore/manifest.yml +++ b/Connectors/src/MySqlEFCore/manifest.yml @@ -11,6 +11,6 @@ applications: DOTNET_CLI_TELEMETRY_OPTOUT: "true" DOTNET_NOLOGO: "true" services: - - sampleMySqlService - #- sampleMySqlServiceOne - #- sampleMySqlServiceTwo + - sampleMySqlEFCoreService + #- sampleMySqlEFCoreServiceOne + #- sampleMySqlEFCoreServiceTwo diff --git a/Connectors/src/MySqlEFCore/testing/scaffold/cloudfoundry.py b/Connectors/src/MySqlEFCore/testing/scaffold/cloudfoundry.py index 54ca8a8d4..2c3434167 100644 --- a/Connectors/src/MySqlEFCore/testing/scaffold/cloudfoundry.py +++ b/Connectors/src/MySqlEFCore/testing/scaffold/cloudfoundry.py @@ -12,7 +12,7 @@ def setup(context): # create service service = 'p.mysql' plan = 'db-small' - instance = 'sampleMySqlService' + instance = 'sampleMySqlEFCoreService' cf.create_service(service, plan, instance) def teardown(context): @@ -21,4 +21,4 @@ def teardown(context): """ cf = cloudfoundry.CloudFoundry(context) cf.delete_app('mysql-efcore-connector-sample') - cf.delete_service('sampleMySqlService') + cf.delete_service('sampleMySqlEFCoreService') diff --git a/Connectors/src/PostgreSql/manifest-windows.yml b/Connectors/src/PostgreSql/manifest-windows.yml index 5ed95203a..c52dddc40 100644 --- a/Connectors/src/PostgreSql/manifest-windows.yml +++ b/Connectors/src/PostgreSql/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: postgresql-connector-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.PostgreSql --urls=http://0.0.0.0:%PORT% diff --git a/Connectors/src/PostgreSql/testing/scaffold/cloudfoundry.py b/Connectors/src/PostgreSql/testing/scaffold/cloudfoundry.py index 6d8ba48b3..cf9487c80 100644 --- a/Connectors/src/PostgreSql/testing/scaffold/cloudfoundry.py +++ b/Connectors/src/PostgreSql/testing/scaffold/cloudfoundry.py @@ -13,7 +13,7 @@ def setup(context): service = 'postgres' plan = 'small' instance = 'samplePostgreSqlService' - cf.create_service(service, plan, instance) + cf.create_service(service, plan, instance, alternatives=[('postgres', 'db-small')]) def teardown(context): """ diff --git a/Connectors/src/PostgreSqlEFCore/README.md b/Connectors/src/PostgreSqlEFCore/README.md index 4939ecca8..ec01ae78c 100644 --- a/Connectors/src/PostgreSqlEFCore/README.md +++ b/Connectors/src/PostgreSqlEFCore/README.md @@ -39,15 +39,15 @@ Upon startup, the app inserts a couple of rows into the bound PostgreSQL databas ``` - When using Tanzu for Postgres on Cloud Foundry: ```shell - cf create-service postgres your-plan samplePostgreSqlService --wait + cf create-service postgres your-plan samplePostgreSqlEFCoreService --wait ``` - When using Tanzu Cloud Service Broker for GCP: ```shell - cf create-service csb-google-postgres your-plan samplePostgreSqlService --wait + cf create-service csb-google-postgres your-plan samplePostgreSqlEFCoreService --wait ``` - When using Tanzu Cloud Service Broker for AWS: ```shell - cf create-service csb-aws-postgresql your-plan samplePostgreSqlService --wait + cf create-service csb-aws-postgresql your-plan samplePostgreSqlEFCoreService --wait ``` 1. Run the `cf push` command to deploy from source (you can monitor logs with `cf logs postgresql-efcore-connector-sample`) - When deploying to Windows, binaries must be built locally before push. Use the following commands instead: diff --git a/Connectors/src/PostgreSqlEFCore/manifest-windows.yml b/Connectors/src/PostgreSqlEFCore/manifest-windows.yml index ca64e8518..756dafab9 100644 --- a/Connectors/src/PostgreSqlEFCore/manifest-windows.yml +++ b/Connectors/src/PostgreSqlEFCore/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: postgresql-efcore-connector-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.PostgreSqlEFCore --urls=http://0.0.0.0:%PORT% @@ -12,4 +12,4 @@ applications: DOTNET_CLI_TELEMETRY_OPTOUT: "true" DOTNET_NOLOGO: "true" services: - - samplePostgreSqlService + - samplePostgreSqlEFCoreService diff --git a/Connectors/src/PostgreSqlEFCore/manifest.yml b/Connectors/src/PostgreSqlEFCore/manifest.yml index 1a436cf47..4c8ae9cc8 100644 --- a/Connectors/src/PostgreSqlEFCore/manifest.yml +++ b/Connectors/src/PostgreSqlEFCore/manifest.yml @@ -11,4 +11,4 @@ applications: DOTNET_CLI_TELEMETRY_OPTOUT: "true" DOTNET_NOLOGO: "true" services: - - samplePostgreSqlService + - samplePostgreSqlEFCoreService diff --git a/Connectors/src/PostgreSqlEFCore/testing/scaffold/cloudfoundry.py b/Connectors/src/PostgreSqlEFCore/testing/scaffold/cloudfoundry.py index 23be373e7..601fd227c 100644 --- a/Connectors/src/PostgreSqlEFCore/testing/scaffold/cloudfoundry.py +++ b/Connectors/src/PostgreSqlEFCore/testing/scaffold/cloudfoundry.py @@ -12,8 +12,8 @@ def setup(context): # create service service = 'postgres' plan = 'small' - instance = 'samplePostgreSqlService' - cf.create_service(service, plan, instance) + instance = 'samplePostgreSqlEFCoreService' + cf.create_service(service, plan, instance, alternatives=[('postgres', 'db-small')]) def teardown(context): """ @@ -21,4 +21,4 @@ def teardown(context): """ cf = cloudfoundry.CloudFoundry(context) cf.delete_app('postgresql-efcore-connector-sample') - cf.delete_service('samplePostgreSqlService') + cf.delete_service('samplePostgreSqlEFCoreService') diff --git a/Connectors/src/RabbitMQ/manifest-windows.yml b/Connectors/src/RabbitMQ/manifest-windows.yml index ea7899325..0168961cf 100644 --- a/Connectors/src/RabbitMQ/manifest-windows.yml +++ b/Connectors/src/RabbitMQ/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: rabbitmq-connector-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.RabbitMQ --urls=http://0.0.0.0:%PORT% diff --git a/Connectors/src/RabbitMQ/testing/scaffold/cloudfoundry.py b/Connectors/src/RabbitMQ/testing/scaffold/cloudfoundry.py index 301674628..cc582622d 100644 --- a/Connectors/src/RabbitMQ/testing/scaffold/cloudfoundry.py +++ b/Connectors/src/RabbitMQ/testing/scaffold/cloudfoundry.py @@ -13,7 +13,7 @@ def setup(context): service = 'p.rabbitmq' plan = 'rmq-single-node' instance = 'sampleRabbitMQService' - cf.create_service(service, plan, instance) + cf.create_service(service, plan, instance, alternatives=[('p.rabbitmq', 'on-demand-plan')]) def teardown(context): """ diff --git a/Connectors/src/Redis/manifest-windows.yml b/Connectors/src/Redis/manifest-windows.yml index f48183180..b67ecab93 100644 --- a/Connectors/src/Redis/manifest-windows.yml +++ b/Connectors/src/Redis/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: redis-connector-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.Redis --urls=http://0.0.0.0:%PORT% diff --git a/Connectors/src/Redis/testing/scaffold/cloudfoundry.py b/Connectors/src/Redis/testing/scaffold/cloudfoundry.py index e0fdefc48..5d123e9b5 100644 --- a/Connectors/src/Redis/testing/scaffold/cloudfoundry.py +++ b/Connectors/src/Redis/testing/scaffold/cloudfoundry.py @@ -10,10 +10,10 @@ def setup(context): app = 'redis-connector-sample' cf.delete_app(app) # create service - service = 'p.redis' - plan = 'vk-plan' + service = 'p-redis' + plan = 'shared-vm' instance = 'sampleRedisService' - cf.create_service(service, plan, instance) + cf.create_service(service, plan, instance, alternatives=[('p.redis', 'vk-plan')]) def teardown(context): """ diff --git a/Connectors/src/SqlServerEFCore/manifest-windows.yml b/Connectors/src/SqlServerEFCore/manifest-windows.yml index 0fa992987..1346de808 100644 --- a/Connectors/src/SqlServerEFCore/manifest-windows.yml +++ b/Connectors/src/SqlServerEFCore/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: sqlserver-efcore-connector-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.SqlServerEFCore --urls=http://0.0.0.0:%PORT% diff --git a/Discovery/src/FortuneTeller/FortuneTellerApi/manifest-windows.yml b/Discovery/src/FortuneTeller/FortuneTellerApi/manifest-windows.yml index 0cdae72aa..6eff093e4 100644 --- a/Discovery/src/FortuneTeller/FortuneTellerApi/manifest-windows.yml +++ b/Discovery/src/FortuneTeller/FortuneTellerApi/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: fortune-service-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.FortuneTellerApi --urls=http://0.0.0.0:%PORT% diff --git a/Discovery/src/FortuneTeller/FortuneTellerWeb/manifest-windows.yml b/Discovery/src/FortuneTeller/FortuneTellerWeb/manifest-windows.yml index a338cc3f3..38a9483e7 100644 --- a/Discovery/src/FortuneTeller/FortuneTellerWeb/manifest-windows.yml +++ b/Discovery/src/FortuneTeller/FortuneTellerWeb/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: fortune-web-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.FortuneTellerWeb --urls=http://0.0.0.0:%PORT% diff --git a/FileShares/src/FileSharesWeb/manifest-windows.yml b/FileShares/src/FileSharesWeb/manifest-windows.yml index d12476cb3..686e42857 100644 --- a/FileShares/src/FileSharesWeb/manifest-windows.yml +++ b/FileShares/src/FileSharesWeb/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: fileshares-sample random-route: true memory: 256M - stack: windows + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.FileSharesWeb --urls=http://0.0.0.0:%PORT% diff --git a/Management/src/ActuatorApi/README.md b/Management/src/ActuatorApi/README.md index 1c3197b07..c594157b2 100644 --- a/Management/src/ActuatorApi/README.md +++ b/Management/src/ActuatorApi/README.md @@ -1,4 +1,4 @@ -# Steeltoe Management Sample - Actuators, Administrative Tasks, Metrics and Tracing +# Steeltoe Management Sample - Actuators, Administrative Tasks, Metrics and Tracing ActuatorWeb and ActuatorApi form an ASP.NET Core-powered sample application that demonstrates how to use several Steeltoe libraries on their own and with additional tools. @@ -69,19 +69,19 @@ In order to demonstrate [Steeltoe Management Tasks](https://docs.steeltoe.io/api - When using Tanzu for MySQL on Cloud Foundry: ```shell - cf create-service p.mysql your-plan sampleMySqlService --wait + cf create-service p.mysql your-plan sampleActuatorMySqlService --wait ``` - When using Tanzu Cloud Service Broker for GCP: ```shell - cf create-service csb-google-mysql your-plan sampleMySqlService --wait + cf create-service csb-google-mysql your-plan sampleActuatorMySqlService --wait ``` - When using Tanzu Cloud Service Broker for AWS: ```shell - cf create-service csb-aws-mysql your-plan sampleMySqlService --wait + cf create-service csb-aws-mysql your-plan sampleActuatorMySqlService --wait ``` 1. Run the `cf push` command to deploy from source (you can monitor logs with `cf logs actuator-api-management-sample`) @@ -95,7 +95,7 @@ In order to demonstrate [Steeltoe Management Tasks](https://docs.steeltoe.io/api 1. Copy the value of `routes` in the output and open in your browser. The app should start and respond to requests, but the database still needs to be configured with the tasks listed in the next section. > [!NOTE] -> The provided manifest will create an app named `actuator-api-management-sample` and attempt to bind it to the MySql service `sampleMySqlService`. +> The provided manifest will create an app named `actuator-api-management-sample` and attempt to bind it to the MySql service `sampleActuatorMySqlService`. ### Running Tasks diff --git a/Management/src/ActuatorApi/manifest-windows.yml b/Management/src/ActuatorApi/manifest-windows.yml index 136576674..02eb78c2e 100644 --- a/Management/src/ActuatorApi/manifest-windows.yml +++ b/Management/src/ActuatorApi/manifest-windows.yml @@ -1,13 +1,13 @@ ---- +--- applications: - name: actuator-api-management-sample buildpacks: - binary_buildpack memory: 256M - stack: windows + stack: windows2022 command: cmd /c .\Steeltoe.Samples.ActuatorApi --urls http://0.0.0.0:%PORT% env: DOTNET_CLI_TELEMETRY_OPTOUT: "true" DOTNET_NOLOGO: "true" services: - - sampleMySqlService + - sampleActuatorMySqlService diff --git a/Management/src/ActuatorApi/manifest.yml b/Management/src/ActuatorApi/manifest.yml index a26d20833..c98b49213 100644 --- a/Management/src/ActuatorApi/manifest.yml +++ b/Management/src/ActuatorApi/manifest.yml @@ -8,4 +8,4 @@ applications: DOTNET_CLI_TELEMETRY_OPTOUT: "true" DOTNET_NOLOGO: "true" services: - - sampleMySqlService + - sampleActuatorMySqlService diff --git a/Management/src/ActuatorApi/testing/scaffold/cloudfoundry.py b/Management/src/ActuatorApi/testing/scaffold/cloudfoundry.py index 6562fdd8c..91ed1dde2 100644 --- a/Management/src/ActuatorApi/testing/scaffold/cloudfoundry.py +++ b/Management/src/ActuatorApi/testing/scaffold/cloudfoundry.py @@ -12,7 +12,7 @@ def setup(context): # create service service = 'p.mysql' plan = 'db-small' - instance = 'sampleMySqlService' + instance = 'sampleActuatorMySqlService' cf.create_service(service, plan, instance) def teardown(context): @@ -21,4 +21,4 @@ def teardown(context): """ cf = cloudfoundry.CloudFoundry(context) cf.delete_app('actuator-api-management-sample') - cf.delete_service('sampleMySqlService') + cf.delete_service('sampleActuatorMySqlService') diff --git a/Management/src/ActuatorWeb/manifest-windows.yml b/Management/src/ActuatorWeb/manifest-windows.yml index bc689ac9c..108e2fb88 100644 --- a/Management/src/ActuatorWeb/manifest-windows.yml +++ b/Management/src/ActuatorWeb/manifest-windows.yml @@ -1,10 +1,10 @@ ---- +--- applications: - name: actuator-web-management-sample buildpacks: - binary_buildpack memory: 256M - stack: windows + stack: windows2022 command: cmd /c .\Steeltoe.Samples.ActuatorWeb --urls http://0.0.0.0:%PORT% env: DOTNET_CLI_TELEMETRY_OPTOUT: "true" diff --git a/Pipfile b/Pipfile index 0d6e32a5b..8593df2f9 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,3 @@ mechanicalsoup = "*" requests = "*" setuptools = "*" ipdb = "*" - -[requires] -python_version = "3" diff --git a/Security/src/AuthApi/manifest-windows.yml b/Security/src/AuthApi/manifest-windows.yml index cf75d8b83..2b8a579b0 100644 --- a/Security/src/AuthApi/manifest-windows.yml +++ b/Security/src/AuthApi/manifest-windows.yml @@ -1,11 +1,11 @@ ---- +--- applications: - name: auth-server-sample buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.AuthApi --urls=http://0.0.0.0:%PORT% memory: 256M - stack: windows + stack: windows2022 env: DOTNET_CLI_TELEMETRY_OPTOUT: "true" DOTNET_NOLOGO: "true" diff --git a/Security/src/AuthConsole/manifest-windows.yml b/Security/src/AuthConsole/manifest-windows.yml index ba7c7d0fa..fc4a79d33 100644 --- a/Security/src/AuthConsole/manifest-windows.yml +++ b/Security/src/AuthConsole/manifest-windows.yml @@ -1,13 +1,13 @@ ---- +--- applications: - name: auth-client-console-sample buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.AuthConsole health-check-type: process - memory: 128M + memory: 256M no-route: true - stack: windows + stack: windows2022 env: DOTNET_CLI_TELEMETRY_OPTOUT: "true" DOTNET_NOLOGO: "true" diff --git a/Security/src/AuthWeb/manifest-windows.yml b/Security/src/AuthWeb/manifest-windows.yml index 9a0bcb111..378fbe4f9 100644 --- a/Security/src/AuthWeb/manifest-windows.yml +++ b/Security/src/AuthWeb/manifest-windows.yml @@ -1,11 +1,11 @@ ---- +--- applications: - name: auth-client-web-sample buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.AuthWeb --urls=http://0.0.0.0:%PORT% memory: 256M - stack: windows + stack: windows2022 env: DOTNET_CLI_TELEMETRY_OPTOUT: "true" DOTNET_NOLOGO: "true" diff --git a/Security/src/RedisDataProtection/manifest-windows.yml b/Security/src/RedisDataProtection/manifest-windows.yml index e54926d12..55ac4a946 100644 --- a/Security/src/RedisDataProtection/manifest-windows.yml +++ b/Security/src/RedisDataProtection/manifest-windows.yml @@ -1,9 +1,9 @@ ---- +--- applications: - name: redis-data-protection-sample random-route: true - memory: 128M - stack: windows + memory: 256M + stack: windows2022 buildpacks: - binary_buildpack command: cmd /c .\Steeltoe.Samples.RedisDataProtection --urls=http://0.0.0.0:%PORT% diff --git a/Security/src/RedisDataProtection/testing/scaffold/cloudfoundry.py b/Security/src/RedisDataProtection/testing/scaffold/cloudfoundry.py index f94455d47..c6223bd30 100644 --- a/Security/src/RedisDataProtection/testing/scaffold/cloudfoundry.py +++ b/Security/src/RedisDataProtection/testing/scaffold/cloudfoundry.py @@ -10,10 +10,10 @@ def setup(context): app = 'redis-data-protection-sample' cf.delete_app(app) # create service - service = 'p.redis' - plan = 'vk-plan' + service = 'p-redis' + plan = 'shared-vm' instance = 'sampleRedisDataProtectionService' - cf.create_service(service, plan, instance) + cf.create_service(service, plan, instance, alternatives=[('p.redis', 'vk-plan')]) def teardown(context): """ diff --git a/behave.sh b/behave.sh index 70bc7f265..a2527c596 100755 --- a/behave.sh +++ b/behave.sh @@ -7,7 +7,9 @@ reinit_flag="$basedir/reinit" PATH+=:~/.local/bin # Try to find a valid python3 interpreter -if command -v python3 >/dev/null; then +if [ -n "${PYTHON:-}" ] && command -v "$PYTHON" >/dev/null; then + PYTHON=$(command -v "$PYTHON") +elif command -v python3 >/dev/null; then PYTHON=$(command -v python3) elif command -v python >/dev/null; then PYTHON=$(command -v python) diff --git a/environment.py b/environment.py index 5a94eb885..382aa2a47 100644 --- a/environment.py +++ b/environment.py @@ -19,6 +19,9 @@ def info(self, message): def error(self, message): self._logger.error(self._obscure_message(message)) + def warning(self, message): + self._logger.warning(self._obscure_message(message)) + def nolog(self, message): return @@ -65,8 +68,8 @@ def after_all(context): :type context: behave.runner.Context """ context.log.info("failures:") - context.log.info(" features : {}".format(context.failed_features)) - context.log.info(" scenarios: {}".format(context.failed_scenarios)) + context.log.info(" features : {}".format(getattr(context, "failed_features", 0))) + context.log.info(" scenarios: {}".format(getattr(context, "failed_scenarios", 0))) def before_feature(context, feature): @@ -177,38 +180,36 @@ def setup_options(context): context.options = type("", (), {})() context.options.output_dir = context.config.userdata.get('output') context.log.info("option: output directory -> {}".format(context.options.output_dir)) + user_data = context.config.userdata try: - context.options.use_windowed = context.config.userdata.getbool('windowed') + context.options.use_windowed = user_data.getbool('windowed') except ValueError as e: - context.log.error("invalid config option: windowed -> {}".format(context.config.userdata.get('windowed'))) + context.log.error("invalid config option: windowed -> {}".format(user_data.get('windowed'))) raise e context.log.info("option: windowed? -> {}".format(context.options.use_windowed)) try: - context.options.do_cleanup = context.config.userdata.getbool('cleanup') + context.options.do_cleanup = user_data.getbool('cleanup') except ValueError as e: - context.log.error("invalid config option: cleanup -> {}".format(context.config.userdata.get('cleanup'))) + context.log.error("invalid config option: cleanup -> {}".format(user_data.get('cleanup'))) raise e context.log.info("option: cleanup? -> {}".format(context.options.do_cleanup)) try: - context.options.debug_on_error = context.config.userdata.getbool('debug_on_error') + context.options.debug_on_error = user_data.getbool('debug_on_error') except ValueError as e: - context.log.error("invalid config option: debug_on_error -> {}".format( - context.config.userdata.get('debug_on_error'))) + context.log.error("invalid config option: debug_on_error -> {}".format(user_data.get('debug_on_error'))) raise e context.log.info("option: debug on error? -> {}".format(context.options.debug_on_error)) context.options.cmd = type("", (), {})() try: - context.options.cmd.max_attempts = context.config.userdata.getint('cmd_max_attempts') + context.options.cmd.max_attempts = user_data.getint('cmd_max_attempts') except ValueError as e: - context.log.error("invalid config option: cmd_max_attempts -> {}".format( - context.config.userdata.get('cmd_max_attempts'))) + context.log.error("invalid config option: cmd_max_attempts -> {}".format(user_data.get('cmd_max_attempts'))) raise e context.log.info("option: cmd max attempts -> {}".format(context.options.cmd.max_attempts)) try: - context.options.cmd.loop_wait = context.config.userdata.getint('cmd_loop_wait') + context.options.cmd.loop_wait = user_data.getint('cmd_loop_wait') except ValueError as e: - context.log.error( - "invalid config option: cmd_loop_wait -> {}".format(context.config.userdata.get('cmd_loop_wait'))) + context.log.error("invalid config option: cmd_loop_wait -> {}".format(user_data.get('cmd_loop_wait'))) raise e context.log.info("option: cmd loop wait -> {}".format(context.options.cmd.loop_wait)) context.options.cf = type("", (), {})() @@ -225,10 +226,9 @@ def setup_options(context): context.options.cf.space = context.config.userdata.get('cf_space') context.log.info("option: CloudFoundry space -> {}".format(context.options.cf.space)) try: - context.options.cf.max_attempts = context.config.userdata.getint('cf_max_attempts') + context.options.cf.max_attempts = user_data.getint('cf_max_attempts') except ValueError as e: - context.log.error( - "invalid config option: cf_max_attempts -> {}".format(context.config.userdata.get('cf_max_attempts'))) + context.log.error("invalid config option: cf_max_attempts -> {}".format(user_data.get('cf_max_attempts'))) raise e context.log.info("option: CloudFoundry max attempts -> {}".format(context.options.cf.max_attempts)) diff --git a/pysteel/cloudfoundry.py b/pysteel/cloudfoundry.py index dafc21e54..a90ab45f6 100644 --- a/pysteel/cloudfoundry.py +++ b/pysteel/cloudfoundry.py @@ -43,13 +43,38 @@ def get_api_endpoint(self): raise Exception("couldn't guess domain; cf target did not return api endpoint") return m.group(2) + def space_exists(self, name): + """ + Return True if the space exists in the targeted org. + :type name: str + """ + cmd_s = 'cf space {}'.format(name) + cmd = command.Command(self._context, cmd_s) + cmd.run(raise_on_error=False) + return cmd.rc == 0 + def create_space(self, name): """ + Create the space if it does not exist; continue when it already exists. + Returns True if the space was created, False if it already existed. :type name: str + :rtype: bool """ - self._context.log.info('creating Cloud Foundry space "{}"'.format(name)) + self._context.log.info('ensuring Cloud Foundry space "{}"'.format(name)) + if self.space_exists(name): + self._context.log.info('space "{}" already exists'.format(name)) + return False cmd_s = 'cf create-space {}'.format(name) - command.Command(self._context, cmd_s).run() + cmd = command.Command(self._context, cmd_s) + cmd.run(raise_on_error=False) + if cmd.rc == 0: + return True + combined = ((cmd.stdout or '') + '\n' + (cmd.stderr or '')).lower() + if 'already exists' in combined: + self._context.log.info('space "{}" already exists (create-space)'.format(name)) + return False + raise command.CommandException( + 'cf create-space failed: rc={}, stderr:\n{}'.format(cmd.rc, cmd.stderr)) def delete_space(self, name): """ @@ -67,19 +92,58 @@ def target_space(self, name): cmd_s = 'cf target -s {}'.format(name) command.Command(self._context, cmd_s).run() - def create_service(self, service, plan, service_instance, args=None, skip_logs=False): + def create_service(self, service_offering, service_plan, service_instance, args=None, skip_logs=False, alternatives=None): + """ + Provision a service instance, retrying with additional marketplace pairs if ``cf create-service`` fails. + :param service_offering: primary service offering name. + :param service_plan: plan name for the primary offering. + :param service_instance: name for the new service instance. + :param args: optional extra arguments appended to ``cf create-service`` (e.g. ``-c`` config). + :param skip_logs: when True, suppress command stdout/stderr for the create step. + :param alternatives: optional list of ``(service_offering, service_plan)`` tuples tried in order after the primary pair fails. + :type service_offering: str + :type service_plan: str + :type service_instance: str + :type args: list or None + :type skip_logs: bool + :type alternatives: list or None + """ + candidates = [(service_offering, service_plan)] + if alternatives: + for alternative_offering, alternative_plan in alternatives: + candidates.append((alternative_offering, alternative_plan)) + errors = [] + for i, (attempt_offering, attempt_plan) in enumerate(candidates): + try: + self._create_service_impl(attempt_offering, attempt_plan, service_instance, args, skip_logs) + return + except Exception as e: + errors.append((attempt_offering, attempt_plan, e)) + if i < len(candidates) - 1: + self._context.log.warning('create_service "{}:{} {}" failed ({}), trying next marketplace candidate'.format(attempt_offering, attempt_plan, service_instance, e)) + details = '; '.join( + '[{}] {}:{} - {}'.format(i + 1, o, p, e) + for i, (o, p, e) in enumerate(errors) + ) + raise Exception( + 'create_service exhausted all {} candidate(s) for instance "{}": {}'.format( + len(errors), service_instance, details)) + + def _create_service_impl(self, service_offering, service_plan, service_instance, args=None, skip_logs=False): """ - :param skip_logs: - :type service: str - :type plan: str + Run ``cf create-service`` once and poll until the instance is ready or fails. + :type service_offering: str + :type service_plan: str :type service_instance: str - :type args: list + :type args: list or None + :type skip_logs: bool """ + self.wait_until_service_ready_to_create(service_instance) if args is None: - cmd_base = 'cf create-service {} {} {}'.format(service, plan, service_instance) + cmd_base = 'cf create-service {} {} {}'.format(service_offering, service_plan, service_instance) else: - cmd_base = ['cf', 'create-service', service, plan, service_instance] + args - self._context.log.info('creating Cloud Foundry service "{}:{}" instance "{}"'.format(service, plan, service_instance)) + cmd_base = ['cf', 'create-service', service_offering, service_plan, service_instance] + args + self._context.log.info('creating Cloud Foundry service "{}:{}" instance "{}"'.format(service_offering, service_plan, service_instance)) if skip_logs: cmd = command.Command(self._context, cmd_base, log_func=self._context.log.nolog) else: @@ -97,9 +161,15 @@ def create_service(self, service, plan, service_instance, args=None, skip_logs=F self._context.log.info("attempt {}/{}".format(attempts, self._context.options.cf.max_attempts)) else: self._context.log.info("attempt {}".format(attempts)) - status = self.get_service_status(service_instance) + try: + status = self.get_service_status(service_instance) + except CloudFoundryObjectDoesNotExistError: + status = None + if (status == 'create succeeded') or (status == 'update succeeded'): break + if status in ['create failed', 'update failed']: + assert False, "service instance {} creation/update failed".format(service_instance) if status is None: self._context.log.info('service instance "{}" status not yet available'.format(service_instance)) else: @@ -111,6 +181,7 @@ def create_user_provided_service(self, service_instance, credentials=None): :type service_instance: str :type credentials: str """ + self.wait_until_service_ready_to_create(service_instance) cmd_s = 'cf create-user-provided-service {}'.format(service_instance) if credentials: cmd_s += ' -p {}'.format(credentials) @@ -119,10 +190,48 @@ def create_user_provided_service(self, service_instance, credentials=None): if cmd.rc != 0: raise Exception('create user provided service instance failed: {}'.format(service_instance)) + def wait_until_service_ready_to_delete(self, service_instance): + """ + Before ``cf delete-service``, wait out any in-progress operation that would cause CF to + reject the delete (``create in progress``, ``update in progress``). + :type service_instance: str + """ + blocking_statuses = frozenset({'create in progress', 'update in progress'}) + attempts = 0 + while True: + attempts += 1 + if self._context.options.cf.max_attempts >= 0: + if attempts > self._context.options.cf.max_attempts: + assert False, ( + 'maximum attempts exceeded ({}) waiting for service instance "{}" ' + 'to become ready to delete' + ).format(self._context.options.cf.max_attempts, service_instance) + self._context.log.info( + 'wait create-clear {}/{}'.format(attempts, self._context.options.cf.max_attempts), + ) + else: + self._context.log.info('wait create-clear attempt {}'.format(attempts)) + try: + status = self.get_service_status(service_instance) + except CloudFoundryObjectDoesNotExistError: + return + normalized = (status or '').strip().lower() + if normalized in blocking_statuses: + self._context.log.info( + 'service instance "{}" status "{}"; waiting before delete'.format( + service_instance, + status, + ), + ) + time.sleep(self._context.options.cmd.loop_wait) + continue + return + def delete_service(self, service_instance): """ :type service_instance: str """ + self.wait_until_service_ready_to_delete(service_instance) self._context.log.info('deleting Cloud Foundry service instance "{}"'.format(service_instance)) cmd_s = 'cf delete-service -f {}'.format(service_instance) cmd = command.Command(self._context, cmd_s) @@ -137,11 +246,15 @@ def delete_service(self, service_instance): self._context.log.info("attempt {}/{}".format(attempts, self._context.options.cf.max_attempts)) else: self._context.log.info("attempt {}".format(attempts)) - status = self.service_exists(service_instance) - if status: + + try: + status = self.get_service_status(service_instance) + if status == 'delete failed': + assert False, "service instance {} deletion failed".format(service_instance) self._context.log.info('service instance still exists') - else: + except CloudFoundryObjectDoesNotExistError: break + time.sleep(self._context.options.cmd.loop_wait) def service_exists(self, service_instance): @@ -167,12 +280,55 @@ def get_service_status(self, service_instance): try: cmd.run() except command.CommandException as e: - if 'Service instance {} not found'.format(service_instance) in str(e): + if 'Service instance not found' in str(e) or 'Service instance \'{}\' not found'.format(service_instance) in str(e): raise CloudFoundryObjectDoesNotExistError() raise e match = re.search(r'\s*status:\s+(.*)', cmd.stdout, re.MULTILINE) if match: - return match.group(1) + return match.group(1).strip() + + def wait_until_service_ready_to_create(self, service_instance): + """ + Before ``cf create-service``, wait out a leftover instance from a prior run that is still + deleting (``delete in progress``). Once the instance is gone, creation can proceed. + Fails immediately if the instance is in ``delete failed`` — CF admin purge is required. + :type service_instance: str + """ + blocking_statuses = frozenset({'delete in progress'}) + attempts = 0 + while True: + attempts += 1 + if self._context.options.cf.max_attempts >= 0: + if attempts > self._context.options.cf.max_attempts: + assert False, ( + 'maximum attempts exceeded ({}) waiting for service instance "{}" ' + 'to finish deleting' + ).format(self._context.options.cf.max_attempts, service_instance) + self._context.log.info( + 'wait delete-clear {}/{}'.format(attempts, self._context.options.cf.max_attempts), + ) + else: + self._context.log.info('wait delete-clear attempt {}'.format(attempts)) + try: + status = self.get_service_status(service_instance) + except CloudFoundryObjectDoesNotExistError: + return + normalized = (status or '').strip().lower() + if normalized == 'delete failed': + assert False, ( + 'service instance "{}" is stuck in "delete failed" state; ' + 'CF admin must run: cf purge-service-instance -f {}' + ).format(service_instance, service_instance) + if normalized in blocking_statuses: + self._context.log.info( + 'service instance "{}" status "{}"; waiting before create'.format( + service_instance, + status, + ), + ) + time.sleep(self._context.options.cmd.loop_wait) + continue + return def push_app(self, manifest, extra_args=""): """ @@ -201,7 +357,11 @@ def push_app(self, manifest, extra_args=""): else: self._context.log.info("attempt {}".format(attempts)) - status = self.get_app_status(app_name) + try: + status = self.get_app_status(app_name) + except CloudFoundryObjectDoesNotExistError: + status = None + if status is None: self._context.log.info('app "{}" status not yet available'.format(app_name)) else: @@ -274,6 +434,36 @@ def get_task_status(self, app_name, task_name): match = re.search('(.*?--name {})'.format(task_name), cmd.stdout, re.MULTILINE) return match.group(1).split()[2] + def get_recent_logs(self, app_name, source_filter=None, tail=50): + """ + Retrieve recent log output for an app via 'cf logs --recent'. + When source_filter is provided, returns only lines whose CF source tag contains it + (e.g. 'APP/TASK/' for task containers, 'APP/PROC/WEB/' for the web process). + Falls back to the last `tail` lines when the filter matches nothing. + :type app_name: str + :type source_filter: str or None + :type tail: int + """ + cmd = command.Command(self._context, 'cf logs --recent {}'.format(app_name)) + cmd.run(raise_on_error=False) + output = (cmd.stdout or '').strip() + if not output: + return '(no recent logs available)' + lines = output.splitlines() + if source_filter: + filtered = [l for l in lines if source_filter in l] + if filtered: + return '\n'.join(filtered) + return '\n'.join(lines[-tail:]) + + def get_recent_task_logs(self, app_name): + """ + Get recent logs filtered to CF task output (lines tagged [APP/TASK/...]). + CF auto-generates the task name so we match the source prefix rather than the name. + :type app_name: str + """ + return self.get_recent_logs(app_name, source_filter='APP/TASK/') + def get_app_route(self, app_name): """ :type app_name: str diff --git a/pysteel/scaffold/cloudfoundry.py b/pysteel/scaffold/cloudfoundry.py index 36c5e7300..e10baa525 100644 --- a/pysteel/scaffold/cloudfoundry.py +++ b/pysteel/scaffold/cloudfoundry.py @@ -1,5 +1,3 @@ -import os -import re from urllib.parse import urlparse from pysteel import cloudfoundry @@ -49,8 +47,9 @@ def setup(context, scenario): context.cf_domain = urlparse(endpoint).hostname.replace('api.run', 'apps') context.log.info('CloudFoundry domain -> {}'.format(context.cf_domain)) - # CloudFoundry sandbox - cf.create_space(context.cf_space) + # CloudFoundry sandbox: delete on teardown only if we created the space here (not pre-existing). + space_created = cf.create_space(context.cf_space) + context.cf_delete_space_on_teardown = space_created cf.target_space(context.cf_space) def teardown(context, scenario): @@ -59,6 +58,13 @@ def teardown(context, scenario): """ cf = cloudfoundry.CloudFoundry(context) if context.cf_space: - cf.delete_space(context.cf_space) + if context.cf_delete_space_on_teardown: + cf.delete_space(context.cf_space) + else: + context.log.info( + "Leaving Cloud Foundry space unchanged (configured/shared space): {}".format( + context.cf_space, + ), + ) else: - context.log.warn("No cf_space defined in context; skipping space deletion") + context.log.warning("No cf_space defined in context; skipping space deletion") diff --git a/steps/process_steps.py b/steps/process_steps.py index e08908ee3..629201fb7 100644 --- a/steps/process_steps.py +++ b/steps/process_steps.py @@ -91,10 +91,12 @@ def step_impl(context, app): def app_started(): try: - status = CloudFoundry(context).get_app_status(app) + cf = CloudFoundry(context) + status = cf.get_app_status(app) context.log.info("app {} status: {}".format(app, status)) if status == 'crashed': - assert False, "app {} crashed".format(app) + recent_logs = cf.get_recent_logs(app) + assert False, "app {} crashed\n\nRecent app logs:\n{}".format(app, recent_logs) return status == 'running' except CloudFoundryObjectDoesNotExistError: return False @@ -113,11 +115,13 @@ def step_impl(context, task, app): def task_succeeded(): try: + cf = CloudFoundry(context) context.log.info("checking status for task {} on app {}".format(task, app)) - status = CloudFoundry(context).get_task_status(app, task) + status = cf.get_task_status(app, task) context.log.info("task {} status: {}".format(task, status)) if status == 'FAILED': - assert False, "task {} failed".format(task) + recent_logs = cf.get_recent_task_logs(app) + assert False, "task {} failed\n\nRecent app logs:\n{}".format(task, recent_logs) return status == 'SUCCEEDED' except CloudFoundryObjectDoesNotExistError: return False